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 return; 145 146 const auto& sysdUnit = iter->second; 147 148 auto method = this->bus.new_method_call( 149 SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "StartUnit"); 150 // The only valid transition is reboot and that 151 // needs to be irreversible once started 152 153 method.append(sysdUnit, "replace-irreversibly"); 154 155 // Put BMC state not NotReady when issuing a BMC reboot 156 // and stop monitoring for state changes 157 this->currentBMCState(BMCState::NotReady); 158 this->stateSignal.reset(); 159 160 try 161 { 162 this->bus.call(method); 163 } 164 catch (const sdbusplus::exception_t& e) 165 { 166 info("Error in StartUnit - replace-irreversibly: {ERROR}", "ERROR", 167 e); 168 } 169 } 170 return; 171 } 172 173 int BMC::bmcStateChange(sdbusplus::message_t& msg) 174 { 175 uint32_t newStateID{}; 176 sdbusplus::message::object_path newStateObjPath; 177 std::string newStateUnit{}; 178 std::string newStateResult{}; 179 180 // Read the msg and populate each variable 181 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); 182 183 if ((newStateUnit == obmcQuiesceTarget) && (newStateResult == signalDone)) 184 { 185 error("BMC has entered BMC_QUIESCED state"); 186 this->currentBMCState(BMCState::Quiesced); 187 188 // There is no getting out of Quiesced once entered (other then BMC 189 // reboot) so stop watching for signals 190 auto method = 191 this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 192 SYSTEMD_INTERFACE, "Unsubscribe"); 193 194 try 195 { 196 this->bus.call(method); 197 } 198 catch (const sdbusplus::exception_t& e) 199 { 200 info("Error in Unsubscribe: {ERROR}", "ERROR", e); 201 } 202 203 // disable the system state change object as well 204 this->stateSignal.reset(); 205 206 return 0; 207 } 208 209 // Caught the signal that indicates the BMC is now BMC_READY 210 if ((newStateUnit == obmcStandbyTarget) && (newStateResult == signalDone)) 211 { 212 info("BMC_READY"); 213 this->currentBMCState(BMCState::Ready); 214 } 215 216 return 0; 217 } 218 219 BMC::Transition BMC::requestedBMCTransition(Transition value) 220 { 221 info("Setting the RequestedBMCTransition field to " 222 "{REQUESTED_BMC_TRANSITION}", 223 "REQUESTED_BMC_TRANSITION", value); 224 225 executeTransition(value); 226 return server::BMC::requestedBMCTransition(value); 227 } 228 229 BMC::BMCState BMC::currentBMCState(BMCState value) 230 { 231 info("Setting the BMCState field to {CURRENT_BMC_STATE}", 232 "CURRENT_BMC_STATE", value); 233 234 return server::BMC::currentBMCState(value); 235 } 236 237 BMC::RebootCause BMC::lastRebootCause(RebootCause value) 238 { 239 info("Setting the RebootCause field to {LAST_REBOOT_CAUSE}", 240 "LAST_REBOOT_CAUSE", value); 241 242 return server::BMC::lastRebootCause(value); 243 } 244 245 void BMC::updateLastRebootTime() 246 { 247 using namespace std::chrono; 248 struct sysinfo info; 249 250 auto rc = sysinfo(&info); 251 assert(rc == 0); 252 // Since uptime is in seconds, also get the current time in seconds. 253 auto now = time_point_cast<seconds>(system_clock::now()); 254 auto rebootTimeTs = now - seconds(info.uptime); 255 rebootTime = 256 duration_cast<milliseconds>(rebootTimeTs.time_since_epoch()).count(); 257 server::BMC::lastRebootTime(rebootTime); 258 } 259 260 uint64_t BMC::lastRebootTime() const 261 { 262 return rebootTime; 263 } 264 265 void BMC::discoverLastRebootCause() 266 { 267 uint64_t bootReason = 0; 268 std::ifstream file; 269 auto bootstatusPath = "/sys/class/watchdog/watchdog0/bootstatus"; 270 271 file.exceptions(std::ifstream::failbit | std::ifstream::badbit | 272 std::ifstream::eofbit); 273 274 try 275 { 276 file.open(bootstatusPath); 277 file >> bootReason; 278 } 279 catch (const std::exception& e) 280 { 281 auto rc = errno; 282 error("Failed to read sysfs file {FILE} with errno {ERRNO}", "FILE", 283 bootstatusPath, "ERRNO", rc); 284 } 285 286 switch (bootReason) 287 { 288 case WDIOF_EXTERN1: 289 this->lastRebootCause(RebootCause::Watchdog); 290 return; 291 case WDIOF_CARDRESET: 292 this->lastRebootCause(RebootCause::POR); 293 return; 294 default: 295 this->lastRebootCause(RebootCause::Unknown); 296 // Continue below to see if more details can be found 297 // on reason for reboot 298 break; 299 } 300 301 // If the above code could not detect a reason, look for a the 302 // reset-cause-pinhole gpio to see if it is the reason for the reboot 303 auto gpioval = 304 phosphor::state::manager::utils::getGpioValue("reset-cause-pinhole"); 305 306 // A 0 indicates a pinhole reset occurred 307 if (0 == gpioval) 308 { 309 info("The BMC reset was caused by a pinhole reset"); 310 this->lastRebootCause(RebootCause::PinholeReset); 311 312 // Generate log telling user a pinhole reset has occurred 313 const std::string errorMsg = "xyz.openbmc_project.State.PinholeReset"; 314 phosphor::state::manager::utils::createError( 315 this->bus, errorMsg, 316 sdbusplus::server::xyz::openbmc_project::logging::Entry::Level:: 317 Notice); 318 return; 319 } 320 321 // If we still haven't found a reason, see if we lost AC power 322 // Note that a pinhole reset will remove AC power to the chassis 323 // on some systems so we always want to look for the pinhole reset 324 // first as that would be the main reason AC power was lost. 325 size_t chassisId = 0; 326 if (phosphor::state::manager::utils::checkACLoss(chassisId)) 327 { 328 this->lastRebootCause(RebootCause::POR); 329 } 330 331 return; 332 } 333 334 } // namespace manager 335 } // namespace state 336 } // namespace phosphor 337