1 #include "bmc_state_manager.hpp" 2 3 #include "utils.hpp" 4 #include "xyz/openbmc_project/Common/error.hpp" 5 6 #include <gpiod.h> 7 #include <sys/sysinfo.h> 8 9 #include <phosphor-logging/elog-errors.hpp> 10 #include <phosphor-logging/lg2.hpp> 11 #include <sdbusplus/exception.hpp> 12 13 #include <cassert> 14 #include <filesystem> 15 #include <fstream> 16 #include <iostream> 17 18 namespace phosphor 19 { 20 namespace state 21 { 22 namespace manager 23 { 24 25 PHOSPHOR_LOG2_USING; 26 27 // When you see server:: you know we're referencing our base class 28 namespace server = sdbusplus::xyz::openbmc_project::State::server; 29 30 using namespace phosphor::logging; 31 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 32 33 constexpr auto obmcStandbyTarget = "multi-user.target"; 34 constexpr auto signalDone = "done"; 35 constexpr auto activeState = "active"; 36 37 /* Map a transition to it's systemd target */ 38 const std::map<server::BMC::Transition, const char*> SYSTEMD_TABLE = { 39 {server::BMC::Transition::Reboot, "reboot.target"}}; 40 41 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 42 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; 43 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 44 constexpr auto SYSTEMD_PRP_INTERFACE = "org.freedesktop.DBus.Properties"; 45 46 void BMC::discoverInitialState() 47 { 48 std::variant<std::string> currentState; 49 sdbusplus::message::object_path unitTargetPath; 50 51 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 52 SYSTEMD_INTERFACE, "GetUnit"); 53 54 method.append(obmcStandbyTarget); 55 56 try 57 { 58 auto result = this->bus.call(method); 59 result.read(unitTargetPath); 60 } 61 catch (const sdbusplus::exception::exception& e) 62 { 63 error("Error in GetUnit call: {ERROR}", "ERROR", e); 64 return; 65 } 66 67 method = this->bus.new_method_call( 68 SYSTEMD_SERVICE, 69 static_cast<const std::string&>(unitTargetPath).c_str(), 70 SYSTEMD_PRP_INTERFACE, "Get"); 71 72 method.append("org.freedesktop.systemd1.Unit", "ActiveState"); 73 74 try 75 { 76 auto result = this->bus.call(method); 77 78 // Is obmc-standby.target active or inactive? 79 result.read(currentState); 80 } 81 catch (const sdbusplus::exception::exception& e) 82 { 83 info("Error in ActiveState Get: {ERROR}", "ERROR", e); 84 return; 85 } 86 87 auto currentStateStr = std::get<std::string>(currentState); 88 if (currentStateStr == activeState) 89 { 90 info("Setting the BMCState field to BMC_READY"); 91 this->currentBMCState(BMCState::Ready); 92 93 // Unsubscribe so we stop processing all other signals 94 method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 95 SYSTEMD_INTERFACE, "Unsubscribe"); 96 try 97 { 98 this->bus.call(method); 99 this->stateSignal.release(); 100 } 101 catch (const sdbusplus::exception::exception& e) 102 { 103 info("Error in Unsubscribe: {ERROR}", "ERROR", e); 104 } 105 } 106 else 107 { 108 info("Setting the BMCState field to BMC_NOTREADY"); 109 this->currentBMCState(BMCState::NotReady); 110 } 111 112 return; 113 } 114 115 void BMC::subscribeToSystemdSignals() 116 { 117 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 118 SYSTEMD_INTERFACE, "Subscribe"); 119 120 try 121 { 122 this->bus.call(method); 123 } 124 catch (const sdbusplus::exception::exception& e) 125 { 126 error("Failed to subscribe to systemd signals: {ERROR}", "ERROR", e); 127 elog<InternalFailure>(); 128 } 129 130 return; 131 } 132 133 void BMC::executeTransition(const Transition tranReq) 134 { 135 // HardReboot does not shutdown any services and immediately transitions 136 // into the reboot process 137 if (server::BMC::Transition::HardReboot == tranReq) 138 { 139 auto method = this->bus.new_method_call( 140 SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Reboot"); 141 try 142 { 143 this->bus.call(method); 144 } 145 catch (const sdbusplus::exception::exception& e) 146 { 147 info("Error in HardReboot: {ERROR}", "ERROR", e); 148 } 149 } 150 else 151 { 152 // Check to make sure it can be found 153 auto iter = SYSTEMD_TABLE.find(tranReq); 154 if (iter == SYSTEMD_TABLE.end()) 155 return; 156 157 const auto& sysdUnit = iter->second; 158 159 auto method = this->bus.new_method_call( 160 SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "StartUnit"); 161 // The only valid transition is reboot and that 162 // needs to be irreversible once started 163 164 method.append(sysdUnit, "replace-irreversibly"); 165 166 try 167 { 168 this->bus.call(method); 169 } 170 catch (const sdbusplus::exception::exception& e) 171 { 172 info("Error in StartUnit - replace-irreversibly: {ERROR}", "ERROR", 173 e); 174 } 175 } 176 return; 177 } 178 179 int BMC::bmcStateChange(sdbusplus::message::message& msg) 180 { 181 uint32_t newStateID{}; 182 sdbusplus::message::object_path newStateObjPath; 183 std::string newStateUnit{}; 184 std::string newStateResult{}; 185 186 // Read the msg and populate each variable 187 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); 188 189 // Caught the signal that indicates the BMC is now BMC_READY 190 if ((newStateUnit == obmcStandbyTarget) && (newStateResult == signalDone)) 191 { 192 info("BMC_READY"); 193 this->currentBMCState(BMCState::Ready); 194 195 // Unsubscribe so we stop processing all other signals 196 auto method = 197 this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 198 SYSTEMD_INTERFACE, "Unsubscribe"); 199 200 try 201 { 202 this->bus.call(method); 203 this->stateSignal.release(); 204 } 205 catch (const sdbusplus::exception::exception& e) 206 { 207 info("Error in Unsubscribe: {ERROR}", "ERROR", e); 208 } 209 } 210 211 return 0; 212 } 213 214 BMC::Transition BMC::requestedBMCTransition(Transition value) 215 { 216 info("Setting the RequestedBMCTransition field to " 217 "{REQUESTED_BMC_TRANSITION}", 218 "REQUESTED_BMC_TRANSITION", value); 219 220 executeTransition(value); 221 return server::BMC::requestedBMCTransition(value); 222 } 223 224 BMC::BMCState BMC::currentBMCState(BMCState value) 225 { 226 227 info("Setting the BMCState field to {CURRENT_BMC_STATE}", 228 "CURRENT_BMC_STATE", value); 229 230 return server::BMC::currentBMCState(value); 231 } 232 233 BMC::RebootCause BMC::lastRebootCause(RebootCause value) 234 { 235 info("Setting the RebootCause field to {LAST_REBOOT_CAUSE}", 236 "LAST_REBOOT_CAUSE", value); 237 238 return server::BMC::lastRebootCause(value); 239 } 240 241 uint64_t BMC::lastRebootTime() const 242 { 243 using namespace std::chrono; 244 struct sysinfo info; 245 246 auto rc = sysinfo(&info); 247 assert(rc == 0); 248 249 // Since uptime is in seconds, also get the current time in seconds. 250 auto now = time_point_cast<seconds>(system_clock::now()); 251 auto rebootTime = now - seconds(info.uptime); 252 253 return duration_cast<milliseconds>(rebootTime.time_since_epoch()).count(); 254 } 255 256 void BMC::discoverLastRebootCause() 257 { 258 uint64_t bootReason = 0; 259 std::ifstream file; 260 auto bootstatusPath = "/sys/class/watchdog/watchdog0/bootstatus"; 261 262 file.exceptions(std::ifstream::failbit | std::ifstream::badbit | 263 std::ifstream::eofbit); 264 265 try 266 { 267 file.open(bootstatusPath); 268 file >> bootReason; 269 } 270 catch (const std::exception& e) 271 { 272 auto rc = errno; 273 error("Failed to read sysfs file {FILE} with errno {ERRNO}", "FILE", 274 bootstatusPath, "ERRNO", rc); 275 } 276 277 switch (bootReason) 278 { 279 case WDIOF_EXTERN1: 280 this->lastRebootCause(RebootCause::Watchdog); 281 return; 282 case WDIOF_CARDRESET: 283 this->lastRebootCause(RebootCause::POR); 284 return; 285 default: 286 this->lastRebootCause(RebootCause::Unknown); 287 // Continue below to see if more details can be found 288 // on reason for reboot 289 break; 290 } 291 292 // If the above code could not detect a reason, look for a the 293 // reset-cause-pinhole gpio to see if it is the reason for the reboot 294 auto gpioval = 295 phosphor::state::manager::utils::getGpioValue("reset-cause-pinhole"); 296 297 if (1 == gpioval) 298 { 299 info("The BMC reset was caused by a pinhole reset"); 300 this->lastRebootCause(RebootCause::PinholeReset); 301 302 // Generate log telling user a pinhole reset has occurred 303 const std::string errorMsg = "xyz.openbmc_project.State.PinholeReset"; 304 phosphor::state::manager::utils::createError( 305 this->bus, errorMsg, 306 sdbusplus::xyz::openbmc_project::Logging::server::Entry::Level:: 307 Notice); 308 } 309 310 return; 311 } 312 313 } // namespace manager 314 } // namespace state 315 } // namespace phosphor 316