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