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 // Number of retries to check if host is running
173 constexpr int MAX_MAPPER_RETRIES = 5;
174 // Delay between mapper retries
175 constexpr auto MAPPER_RETRY_DELAY = std::chrono::milliseconds(1000);
176
177 // No need to check if chassis power is not on
178 if (!isChassiPowerOn(bus, id))
179 {
180 info("Chassis power not on, exit");
181 return false;
182 }
183
184 // This applications systemd service is setup to only run after all other
185 // application that could possibly implement the needed interface have
186 // been started. However, the use of mapper to find those interfaces means
187 // we have a condition where the interface may be on D-Bus but not stored
188 // within mapper yet. There are five built in retries to check if it's
189 // found the host is not up. This service is only called if chassis power
190 // is on when the BMC comes up, so this won't impact most normal cases
191 // where the BMC is rebooted with chassis power off. In cases where
192 // chassis power is on, the host is likely running so we want to be sure
193 // we check all interfaces
194 for (int i = 0; i < MAX_MAPPER_RETRIES; i++)
195 {
196 debug(
197 "Introspecting new bus objects for bus id: {ID} sleeping for 1 second.",
198 "ID", id);
199 // Give mapper a small window to introspect new objects on bus
200 std::this_thread::sleep_for(MAPPER_RETRY_DELAY);
201 if (checkFirmwareConditionRunning(bus))
202 {
203 info("Host is running!");
204 // Create file for host instance and create in filesystem to
205 // indicate to services that host is running
206 std::string hostFile = std::format(HOST_RUNNING_FILE, id);
207 std::ofstream outfile(hostFile);
208 if (!outfile)
209 {
210 error("Failed to create host running file {FILE}", "FILE",
211 hostFile);
212 }
213 else
214 {
215 outfile.close();
216 }
217 return true;
218 }
219 }
220 info("Host is not running!");
221 return false;
222 }
223
224 } // namespace manager
225 } // namespace state
226 } // namespace phosphor
227