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 obmcQuiesceTarget = "obmc-bmc-service-quiesce@0.target"; 34 constexpr auto obmcStandbyTarget = "multi-user.target"; 35 constexpr auto signalDone = "done"; 36 constexpr auto activeState = "active"; 37 38 /* Map a transition to it's systemd target */ 39 const std::map<server::BMC::Transition, const char*> SYSTEMD_TABLE = { 40 {server::BMC::Transition::Reboot, "reboot.target"}}; 41 42 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 43 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; 44 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 45 constexpr auto SYSTEMD_PRP_INTERFACE = "org.freedesktop.DBus.Properties"; 46 47 std::string BMC::getUnitState(const std::string& unitToCheck) 48 { 49 std::variant<std::string> currentState; 50 sdbusplus::message::object_path unitTargetPath; 51 52 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 53 SYSTEMD_INTERFACE, "GetUnit"); 54 55 method.append(unitToCheck); 56 57 try 58 { 59 auto result = this->bus.call(method); 60 result.read(unitTargetPath); 61 } 62 catch (const sdbusplus::exception_t& e) 63 { 64 // Not all input units will have been loaded yet so just return an 65 // empty string if an exception is caught in this path 66 info("Unit {UNIT} not found: {ERROR}", "UNIT", unitToCheck, "ERROR", e); 67 return std::string{}; 68 } 69 70 method = this->bus.new_method_call( 71 SYSTEMD_SERVICE, 72 static_cast<const std::string&>(unitTargetPath).c_str(), 73 SYSTEMD_PRP_INTERFACE, "Get"); 74 75 method.append("org.freedesktop.systemd1.Unit", "ActiveState"); 76 77 try 78 { 79 auto result = this->bus.call(method); 80 81 // Is input target active or inactive? 82 result.read(currentState); 83 } 84 catch (const sdbusplus::exception_t& e) 85 { 86 info("Error in ActiveState Get: {ERROR}", "ERROR", e); 87 return std::string{}; 88 } 89 return (std::get<std::string>(currentState)); 90 } 91 92 void BMC::discoverInitialState() 93 { 94 // First look to see if the BMC quiesce target is active 95 auto currentStateStr = getUnitState(obmcQuiesceTarget); 96 if (currentStateStr == activeState) 97 { 98 info("Setting the BMCState field to BMC_QUIESCED"); 99 this->currentBMCState(BMCState::Quiesced); 100 return; 101 } 102 103 // If not quiesced, then check standby target 104 currentStateStr = getUnitState(obmcStandbyTarget); 105 if (currentStateStr == activeState) 106 { 107 info("Setting the BMCState field to BMC_READY"); 108 this->currentBMCState(BMCState::Ready); 109 } 110 else 111 { 112 info("Setting the BMCState field to BMC_NOTREADY"); 113 this->currentBMCState(BMCState::NotReady); 114 } 115 116 return; 117 } 118 119 void BMC::executeTransition(const Transition tranReq) 120 { 121 // HardReboot does not shutdown any services and immediately transitions 122 // into the reboot process 123 if (server::BMC::Transition::HardReboot == tranReq) 124 { 125 // Put BMC state not NotReady when issuing a BMC reboot 126 // and stop monitoring for state changes 127 this->currentBMCState(BMCState::NotReady); 128 this->stateSignal.reset(); 129 130 auto method = this->bus.new_method_call( 131 SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Reboot"); 132 try 133 { 134 this->bus.call(method); 135 } 136 catch (const sdbusplus::exception_t& e) 137 { 138 info("Error in HardReboot: {ERROR}", "ERROR", e); 139 } 140 } 141 else 142 { 143 // Check to make sure it can be found 144 auto iter = SYSTEMD_TABLE.find(tranReq); 145 if (iter == SYSTEMD_TABLE.end()) 146 return; 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 uint64_t BMC::lastRebootTime() const 248 { 249 using namespace std::chrono; 250 struct sysinfo info; 251 252 auto rc = sysinfo(&info); 253 assert(rc == 0); 254 255 // Since uptime is in seconds, also get the current time in seconds. 256 auto now = time_point_cast<seconds>(system_clock::now()); 257 auto rebootTime = now - seconds(info.uptime); 258 259 return duration_cast<milliseconds>(rebootTime.time_since_epoch()).count(); 260 } 261 262 void BMC::discoverLastRebootCause() 263 { 264 uint64_t bootReason = 0; 265 std::ifstream file; 266 auto bootstatusPath = "/sys/class/watchdog/watchdog0/bootstatus"; 267 268 file.exceptions(std::ifstream::failbit | std::ifstream::badbit | 269 std::ifstream::eofbit); 270 271 try 272 { 273 file.open(bootstatusPath); 274 file >> bootReason; 275 } 276 catch (const std::exception& e) 277 { 278 auto rc = errno; 279 error("Failed to read sysfs file {FILE} with errno {ERRNO}", "FILE", 280 bootstatusPath, "ERRNO", rc); 281 } 282 283 switch (bootReason) 284 { 285 case WDIOF_EXTERN1: 286 this->lastRebootCause(RebootCause::Watchdog); 287 return; 288 case WDIOF_CARDRESET: 289 this->lastRebootCause(RebootCause::POR); 290 return; 291 default: 292 this->lastRebootCause(RebootCause::Unknown); 293 // Continue below to see if more details can be found 294 // on reason for reboot 295 break; 296 } 297 298 // If the above code could not detect a reason, look for a the 299 // reset-cause-pinhole gpio to see if it is the reason for the reboot 300 auto gpioval = 301 phosphor::state::manager::utils::getGpioValue("reset-cause-pinhole"); 302 303 // A 0 indicates a pinhole reset occurred 304 if (0 == gpioval) 305 { 306 info("The BMC reset was caused by a pinhole reset"); 307 this->lastRebootCause(RebootCause::PinholeReset); 308 309 // Generate log telling user a pinhole reset has occurred 310 const std::string errorMsg = "xyz.openbmc_project.State.PinholeReset"; 311 phosphor::state::manager::utils::createError( 312 this->bus, errorMsg, 313 sdbusplus::xyz::openbmc_project::Logging::server::Entry::Level:: 314 Notice); 315 return; 316 } 317 318 // If we still haven't found a reason, see if we lost AC power 319 // Note that a pinhole reset will remove AC power to the chassis 320 // on some systems so we always want to look for the pinhole reset 321 // first as that would be the main reason AC power was lost. 322 size_t chassisId = 0; 323 if (phosphor::state::manager::utils::checkACLoss(chassisId)) 324 { 325 this->lastRebootCause(RebootCause::POR); 326 } 327 328 return; 329 } 330 331 } // namespace manager 332 } // namespace state 333 } // namespace phosphor 334