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