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