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