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