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