xref: /openbmc/phosphor-state-manager/host_check.cpp (revision 4d2da4bef6656b6b71ecfc09ef8a416ccece50b9)
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 PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
41 
42 constexpr auto CHASSIS_STATE_SVC = "xyz.openbmc_project.State.Chassis";
43 
44 // Find all implementations of Condition interface and check if host is
45 // running over it
checkFirmwareConditionRunning(sdbusplus::bus_t & bus)46 bool checkFirmwareConditionRunning(sdbusplus::bus_t& bus)
47 {
48     // Find all implementations of host firmware condition interface
49     auto mapper = bus.new_method_call(
50         ObjectMapper::default_service, ObjectMapper::instance_path,
51         ObjectMapper::interface, ObjectMapper::method_names::get_sub_tree);
52 
53     mapper.append("/", 0, std::vector<std::string>({HostFirmware::interface}));
54 
55     std::map<std::string, std::map<std::string, std::vector<std::string>>>
56         mapperResponse;
57 
58     try
59     {
60         auto mapperResponseMsg = bus.call(mapper);
61         mapperResponseMsg.read(mapperResponse);
62     }
63     catch (const sdbusplus::exception_t& e)
64     {
65         error(
66             "Error in mapper GetSubTree call for HostFirmware condition: {ERROR}",
67             "ERROR", e);
68         throw;
69     }
70 
71     if (mapperResponse.empty())
72     {
73         info("Mapper response for HostFirmware conditions is empty!");
74         return false;
75     }
76 
77     // Now read the CurrentFirmwareCondition from all interfaces we found
78     // Currently there are two implementations of this interface. One by IPMI
79     // and one by PLDM. The IPMI interface does a realtime check with the host
80     // when the interface is called. This means if the host is not running,
81     // we will have to wait for the timeout (currently set to 3 seconds). The
82     // PLDM interface reads a cached state. The PLDM service does not put itself
83     // on D-Bus until it has checked with the host. Therefore it's most
84     // efficient to call the PLDM interface first. Do that by going in reverse
85     // of the interfaces returned to us (PLDM will be last if available)
86     for (const auto& [path, services] : std::views::reverse(mapperResponse))
87     {
88         for (const auto& serviceIter : services)
89         {
90             const std::string& service = serviceIter.first;
91 
92             try
93             {
94                 auto method = bus.new_method_call(service.c_str(), path.c_str(),
95                                                   PROPERTY_INTERFACE, "Get");
96                 method.append(
97                     HostFirmware::interface,
98                     HostFirmware::property_names::current_firmware_condition);
99 
100                 auto response = bus.call(method);
101                 auto currentFwCondV = response.unpack<
102                     std::variant<HostFirmware::FirmwareCondition>>();
103 
104                 auto currentFwCond =
105                     std::get<HostFirmware::FirmwareCondition>(currentFwCondV);
106 
107                 info(
108                     "Read host fw condition {COND_VALUE} from {COND_SERVICE}, {COND_PATH}",
109                     "COND_VALUE", currentFwCond, "COND_SERVICE", service,
110                     "COND_PATH", path);
111 
112                 if (currentFwCond == HostFirmware::FirmwareCondition::Running)
113                 {
114                     return true;
115                 }
116             }
117             catch (const sdbusplus::exception_t& e)
118             {
119                 error("Error reading HostFirmware condition, error: {ERROR}, "
120                       "service: {SERVICE} path: {PATH}",
121                       "ERROR", e, "SERVICE", service, "PATH", path);
122                 throw;
123             }
124         }
125     }
126     return false;
127 }
128 
129 // Helper function to check if chassis power is on
isChassiPowerOn(sdbusplus::bus_t & bus,size_t id)130 bool isChassiPowerOn(sdbusplus::bus_t& bus, size_t id)
131 {
132     auto svcname = std::string{CHASSIS_STATE_SVC} + std::to_string(id);
133     auto objpath = std::string{Chassis::namespace_path::value} + "/" +
134                    std::string{Chassis::namespace_path::chassis} +
135                    std::to_string(id);
136 
137     try
138     {
139         auto method = bus.new_method_call(svcname.c_str(), objpath.c_str(),
140                                           PROPERTY_INTERFACE, "Get");
141         method.append(Chassis::interface,
142                       Chassis::property_names::current_power_state);
143 
144         auto response = bus.call(method);
145         auto currentPowerStateV =
146             response.unpack<std::variant<Chassis::PowerState>>();
147 
148         auto currentPowerState =
149             std::get<Chassis::PowerState>(currentPowerStateV);
150 
151         if (currentPowerState == Chassis::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 
isHostRunning(size_t id)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 won't 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             std::string hostFile = std::format(HOST_RUNNING_FILE, 0);
202             std::ofstream outfile(hostFile);
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