1 #include "config.h" 2 3 #include "host_check.hpp" 4 5 #include <unistd.h> 6 7 #include <phosphor-logging/lg2.hpp> 8 #include <sdbusplus/bus.hpp> 9 #include <sdbusplus/exception.hpp> 10 #include <xyz/openbmc_project/Condition/HostFirmware/client.hpp> 11 #include <xyz/openbmc_project/ObjectMapper/client.hpp> 12 #include <xyz/openbmc_project/State/Chassis/client.hpp> 13 14 #include <cstdio> 15 #include <cstdlib> 16 #include <format> 17 #include <fstream> 18 #include <iostream> 19 #include <ranges> 20 #include <thread> 21 #include <vector> 22 23 namespace phosphor 24 { 25 namespace state 26 { 27 namespace manager 28 { 29 30 PHOSPHOR_LOG2_USING; 31 32 using namespace std::literals; 33 34 using ObjectMapper = sdbusplus::client::xyz::openbmc_project::ObjectMapper<>; 35 using Chassis = sdbusplus::client::xyz::openbmc_project::state::Chassis<>; 36 using HostFirmware = 37 sdbusplus::client::xyz::openbmc_project::condition::HostFirmware<>; 38 39 // Required strings for sending the msg to check on host 40 constexpr auto CONDITION_HOST_PROPERTY = "CurrentFirmwareCondition"; 41 constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"; 42 43 constexpr auto CHASSIS_STATE_SVC = "xyz.openbmc_project.State.Chassis"; 44 constexpr auto CHASSIS_STATE_POWER_PROP = "CurrentPowerState"; 45 46 // Find all implementations of Condition interface and check if host is 47 // running over it 48 bool checkFirmwareConditionRunning(sdbusplus::bus_t& bus) 49 { 50 // Find all implementations of host firmware condition interface 51 auto mapper = bus.new_method_call(ObjectMapper::default_service, 52 ObjectMapper::instance_path, 53 ObjectMapper::interface, "GetSubTree"); 54 55 mapper.append("/", 0, std::vector<std::string>({HostFirmware::interface})); 56 57 std::map<std::string, std::map<std::string, std::vector<std::string>>> 58 mapperResponse; 59 60 try 61 { 62 auto mapperResponseMsg = bus.call(mapper); 63 mapperResponseMsg.read(mapperResponse); 64 } 65 catch (const sdbusplus::exception_t& e) 66 { 67 error( 68 "Error in mapper GetSubTree call for HostFirmware condition: {ERROR}", 69 "ERROR", e); 70 throw; 71 } 72 73 if (mapperResponse.empty()) 74 { 75 info("Mapper response for HostFirmware conditions is empty!"); 76 return false; 77 } 78 79 // Now read the CurrentFirmwareCondition from all interfaces we found 80 // Currently there are two implementations of this interface. One by IPMI 81 // and one by PLDM. The IPMI interface does a realtime check with the host 82 // when the interface is called. This means if the host is not running, 83 // we will have to wait for the timeout (currently set to 3 seconds). The 84 // PLDM interface reads a cached state. The PLDM service does not put itself 85 // on D-Bus until it has checked with the host. Therefore it's most 86 // efficient to call the PLDM interface first. Do that by going in reverse 87 // of the interfaces returned to us (PLDM will be last if available) 88 for (const auto& [path, services] : std::views::reverse(mapperResponse)) 89 { 90 for (const auto& serviceIter : services) 91 { 92 const std::string& service = serviceIter.first; 93 94 try 95 { 96 auto method = bus.new_method_call(service.c_str(), path.c_str(), 97 PROPERTY_INTERFACE, "Get"); 98 method.append(HostFirmware::interface, CONDITION_HOST_PROPERTY); 99 100 auto response = bus.call(method); 101 std::variant<HostFirmware::FirmwareCondition> currentFwCondV; 102 response.read(currentFwCondV); 103 auto currentFwCond = 104 std::get<HostFirmware::FirmwareCondition>(currentFwCondV); 105 106 info( 107 "Read host fw condition {COND_VALUE} from {COND_SERVICE}, {COND_PATH}", 108 "COND_VALUE", currentFwCond, "COND_SERVICE", service, 109 "COND_PATH", path); 110 111 if (currentFwCond == HostFirmware::FirmwareCondition::Running) 112 { 113 return true; 114 } 115 } 116 catch (const sdbusplus::exception_t& e) 117 { 118 error("Error reading HostFirmware condition, error: {ERROR}, " 119 "service: {SERVICE} path: {PATH}", 120 "ERROR", e, "SERVICE", service, "PATH", path); 121 throw; 122 } 123 } 124 } 125 return false; 126 } 127 128 // Helper function to check if chassis power is on 129 bool isChassiPowerOn(sdbusplus::bus_t& bus, size_t id) 130 { 131 auto svcname = std::string{CHASSIS_STATE_SVC} + std::to_string(id); 132 auto objpath = std::string{Chassis::namespace_path::value} + "/" + 133 std::string{Chassis::namespace_path::chassis} + 134 std::to_string(id); 135 136 try 137 { 138 auto method = bus.new_method_call(svcname.c_str(), objpath.c_str(), 139 PROPERTY_INTERFACE, "Get"); 140 method.append(Chassis::interface, CHASSIS_STATE_POWER_PROP); 141 142 auto response = bus.call(method); 143 std::variant<Chassis::PowerState> currentPowerStateV; 144 response.read(currentPowerStateV); 145 146 auto currentPowerState = 147 std::get<Chassis::PowerState>(currentPowerStateV); 148 149 if (currentPowerState == Chassis::PowerState::On) 150 { 151 return true; 152 } 153 } 154 catch (const sdbusplus::exception_t& e) 155 { 156 error("Error reading Chassis Power State, error: {ERROR}, " 157 "service: {SERVICE} path: {PATH}", 158 "ERROR", e, "SERVICE", svcname.c_str(), "PATH", objpath.c_str()); 159 throw; 160 } 161 return false; 162 } 163 164 bool isHostRunning(size_t id) 165 { 166 info("Check if host is running"); 167 168 auto bus = sdbusplus::bus::new_default(); 169 170 // No need to check if chassis power is not on 171 if (!isChassiPowerOn(bus, id)) 172 { 173 info("Chassis power not on, exit"); 174 return false; 175 } 176 177 // This applications systemd service is setup to only run after all other 178 // application that could possibly implement the needed interface have 179 // been started. However, the use of mapper to find those interfaces means 180 // we have a condition where the interface may be on D-Bus but not stored 181 // within mapper yet. There are five built in retries to check if it's 182 // found the host is not up. This service is only called if chassis power 183 // is on when the BMC comes up, so this wont impact most normal cases 184 // where the BMC is rebooted with chassis power off. In cases where 185 // chassis power is on, the host is likely running so we want to be sure 186 // we check all interfaces 187 for (int i = 0; i < 5; i++) 188 { 189 debug( 190 "Introspecting new bus objects for bus id: {ID} sleeping for 1 second.", 191 "ID", id); 192 // Give mapper a small window to introspect new objects on bus 193 std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 194 if (checkFirmwareConditionRunning(bus)) 195 { 196 info("Host is running!"); 197 // Create file for host instance and create in filesystem to 198 // indicate to services that host is running 199 std::string hostFile = std::format(HOST_RUNNING_FILE, 0); 200 std::ofstream outfile(hostFile); 201 outfile.close(); 202 return true; 203 } 204 } 205 info("Host is not running!"); 206 return false; 207 } 208 209 } // namespace manager 210 } // namespace state 211 } // namespace phosphor 212