1 #include "config.h" 2 3 #include "host_check.hpp" 4 5 #include <unistd.h> 6 7 #include <boost/range/adaptor/reversed.hpp> 8 #include <phosphor-logging/lg2.hpp> 9 #include <sdbusplus/bus.hpp> 10 #include <sdbusplus/exception.hpp> 11 #include <xyz/openbmc_project/Condition/HostFirmware/server.hpp> 12 13 #include <cstdio> 14 #include <cstdlib> 15 #include <fstream> 16 #include <iostream> 17 #include <thread> 18 #include <vector> 19 20 namespace phosphor 21 { 22 namespace state 23 { 24 namespace manager 25 { 26 27 PHOSPHOR_LOG2_USING; 28 29 using namespace std::literals; 30 using namespace sdbusplus::xyz::openbmc_project::Condition::server; 31 32 // Required strings for sending the msg to check on host 33 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper"; 34 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper"; 35 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper"; 36 constexpr auto CONDITION_HOST_INTERFACE = 37 "xyz.openbmc_project.Condition.HostFirmware"; 38 constexpr auto CONDITION_HOST_PROPERTY = "CurrentFirmwareCondition"; 39 constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"; 40 41 constexpr auto CHASSIS_STATE_SVC = "xyz.openbmc_project.State.Chassis"; 42 constexpr auto CHASSIS_STATE_PATH = "/xyz/openbmc_project/state/chassis"; 43 constexpr auto CHASSIS_STATE_INTF = "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::bus& bus) 49 { 50 // Find all implementations of host firmware condition interface 51 auto mapper = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH, 52 MAPPER_INTERFACE, "GetSubTree"); 53 54 mapper.append("/", 0, std::vector<std::string>({CONDITION_HOST_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::exception& 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] : 88 boost::adaptors::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(CONDITION_HOST_INTERFACE, 99 CONDITION_HOST_PROPERTY); 100 101 auto response = bus.call(method); 102 103 std::variant<std::string> currentFwCond; 104 response.read(currentFwCond); 105 106 if (std::get<std::string>(currentFwCond) == 107 "xyz.openbmc_project.Condition.HostFirmware." 108 "FirmwareCondition." 109 "Running") 110 { 111 return true; 112 } 113 } 114 catch (const sdbusplus::exception::exception& e) 115 { 116 error("Error reading HostFirmware condition, error: {ERROR}, " 117 "service: {SERVICE} path: {PATH}", 118 "ERROR", e, "SERVICE", service, "PATH", path); 119 throw; 120 } 121 } 122 } 123 return false; 124 } 125 126 // Helper function to check if chassis power is on 127 bool isChassiPowerOn(sdbusplus::bus::bus& bus, size_t id) 128 { 129 auto svcname = std::string{CHASSIS_STATE_SVC} + std::to_string(id); 130 auto objpath = std::string{CHASSIS_STATE_PATH} + std::to_string(id); 131 132 try 133 { 134 auto method = bus.new_method_call(svcname.c_str(), objpath.c_str(), 135 PROPERTY_INTERFACE, "Get"); 136 method.append(CHASSIS_STATE_INTF, CHASSIS_STATE_POWER_PROP); 137 138 auto response = bus.call(method); 139 140 std::variant<std::string> currentPowerState; 141 response.read(currentPowerState); 142 143 if (std::get<std::string>(currentPowerState) == 144 "xyz.openbmc_project.State.Chassis.PowerState.On") 145 { 146 return true; 147 } 148 } 149 catch (const sdbusplus::exception::exception& e) 150 { 151 error("Error reading Chassis Power State, error: {ERROR}, " 152 "service: {SERVICE} path: {PATH}", 153 "ERROR", e, "SERVICE", svcname.c_str(), "PATH", objpath.c_str()); 154 throw; 155 } 156 return false; 157 } 158 159 bool isHostRunning(size_t id) 160 { 161 info("Check if host is running"); 162 163 auto bus = sdbusplus::bus::new_default(); 164 165 // No need to check if chassis power is not on 166 if (!isChassiPowerOn(bus, id)) 167 { 168 info("Chassis power not on, exit"); 169 return false; 170 } 171 172 // This applications systemd service is setup to only run after all other 173 // application that could possibly implement the needed interface have 174 // been started. However, the use of mapper to find those interfaces means 175 // we have a condition where the interface may be on D-Bus but not stored 176 // within mapper yet. Keep it simple and just build one retry into the 177 // check if it's found the host is not up. This service is only called if 178 // chassis power is on when the BMC comes up, so this wont impact most 179 // normal cases where the BMC is rebooted with chassis power off. In 180 // cases where chassis power is on, the host is likely running so we want 181 // to be sure we check all interfaces 182 for (int i = 0; i < 2; i++) 183 { 184 // Give mapper a small window to introspect new objects on bus 185 std::this_thread::sleep_for(std::chrono::milliseconds(500)); 186 if (checkFirmwareConditionRunning(bus)) 187 { 188 info("Host is running!"); 189 // Create file for host instance and create in filesystem to 190 // indicate to services that host is running 191 auto size = std::snprintf(nullptr, 0, HOST_RUNNING_FILE, 0); 192 size++; // null 193 std::unique_ptr<char[]> buf(new char[size]); 194 std::snprintf(buf.get(), size, HOST_RUNNING_FILE, 0); 195 std::ofstream outfile(buf.get()); 196 outfile.close(); 197 return true; 198 } 199 } 200 info("Host is not running!"); 201 return false; 202 } 203 204 } // namespace manager 205 } // namespace state 206 } // namespace phosphor 207