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
checkFirmwareConditionRunning(sdbusplus::bus_t & bus)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
isChassiPowerOn(sdbusplus::bus_t & bus,size_t id)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 
isHostRunning(size_t id)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 won't 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