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