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