1 #include "occ_status.hpp"
2
3 #include <phosphor-logging/lg2.hpp>
4 #include <powercap.hpp>
5
6 #include <cassert>
7 #include <filesystem>
8
9 namespace open_power
10 {
11 namespace occ
12 {
13 namespace powercap
14 {
15
16 constexpr auto PCAP_PATH = "/xyz/openbmc_project/control/host0/power_cap";
17 constexpr auto PCAP_INTERFACE = "xyz.openbmc_project.Control.Power.Cap";
18
19 constexpr auto POWER_CAP_PROP = "PowerCap";
20 constexpr auto POWER_CAP_ENABLE_PROP = "PowerCapEnable";
21 constexpr auto POWER_CAP_SOFT_MIN = "MinSoftPowerCapValue";
22 constexpr auto POWER_CAP_HARD_MIN = "MinPowerCapValue";
23 constexpr auto POWER_CAP_MAX = "MaxPowerCapValue";
24
25 namespace fs = std::filesystem;
26
updatePcapBounds()27 void PowerCap::updatePcapBounds()
28 {
29 // Build the hwmon string to write the power cap bounds
30 fs::path minName = getPcapFilename(std::regex{"power\\d+_cap_min$"});
31 fs::path softMinName =
32 getPcapFilename(std::regex{"power\\d+_cap_min_soft$"});
33 fs::path maxName = getPcapFilename(std::regex{"power\\d+_cap_max$"});
34
35 // Read the current cap bounds from dbus
36 uint32_t capSoftMin, capHardMin, capMax;
37 readDbusPcapLimits(capSoftMin, capHardMin, capMax);
38
39 // Read the power cap bounds from sysfs files (from OCC)
40 uint64_t cap;
41 std::ifstream softMinFile(softMinName, std::ios::in);
42 if (softMinFile)
43 {
44 softMinFile >> cap;
45 softMinFile.close();
46 // Convert to input/AC Power in Watts (round up)
47 capSoftMin = ((cap / (PS_DERATING_FACTOR / 100.0) / 1000000) + 0.9);
48 }
49 else
50 {
51 lg2::error(
52 "updatePcapBounds: unable to find pcap_min_soft file: {FILE} (errno={ERR})",
53 "FILE", pcapBasePathname, "ERR", errno);
54 }
55
56 std::ifstream minFile(minName, std::ios::in);
57 if (minFile)
58 {
59 minFile >> cap;
60 minFile.close();
61 // Convert to input/AC Power in Watts (round up)
62 capHardMin = ((cap / (PS_DERATING_FACTOR / 100.0) / 1000000) + 0.9);
63 }
64 else
65 {
66 lg2::error(
67 "updatePcapBounds: unable to find cap_min file: {FILE} (errno={ERR})",
68 "FILE", pcapBasePathname, "ERR", errno);
69 }
70
71 std::ifstream maxFile(maxName, std::ios::in);
72 if (maxFile)
73 {
74 maxFile >> cap;
75 maxFile.close();
76 // Convert to input/AC Power in Watts (truncate remainder)
77 capMax = cap / (PS_DERATING_FACTOR / 100.0) / 1000000;
78 }
79 else
80 {
81 lg2::error(
82 "updatePcapBounds: unable to find cap_max file: {FILE} (errno={ERR})",
83 "FILE", pcapBasePathname, "ERR", errno);
84 }
85
86 // Save the power cap bounds to dbus
87 updateDbusPcapLimits(capSoftMin, capHardMin, capMax);
88
89 // Validate user power cap (if enabled) is within the bounds
90 const uint32_t dbusUserCap = getPcap();
91 const bool pcapEnabled = getPcapEnabled();
92 if (pcapEnabled && (dbusUserCap != 0))
93 {
94 const uint32_t hwmonUserCap = readUserCapHwmon();
95 if ((dbusUserCap >= capSoftMin) && (dbusUserCap <= capMax))
96 {
97 // Validate dbus and hwmon user caps match
98 if ((hwmonUserCap != 0) && (dbusUserCap != hwmonUserCap))
99 {
100 // User power cap is enabled, but does not match dbus
101 lg2::error(
102 "updatePcapBounds: user powercap mismatch (hwmon:{HCAP}W, bdus:{DCAP}W) - using dbus",
103 "HCAP", hwmonUserCap, "DCAP", dbusUserCap);
104 auto occInput = getOccInput(dbusUserCap, pcapEnabled);
105 writeOcc(occInput);
106 }
107 }
108 else
109 {
110 // User power cap is outside of current bounds
111 uint32_t newCap = capMax;
112 if (dbusUserCap < capSoftMin)
113 {
114 newCap = capSoftMin;
115 }
116 lg2::error(
117 "updatePcapBounds: user powercap {CAP}W is outside bounds "
118 "(soft min:{SMIN}, min:{MIN}, max:{MAX})",
119 "CAP", dbusUserCap, "SMIN", capSoftMin, "MIN", capHardMin,
120 "MAX", capMax);
121 try
122 {
123 lg2::info(
124 "updatePcapBounds: Updating user powercap from {OLD} to {NEW}W",
125 "OLD", hwmonUserCap, "NEW", newCap);
126 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP,
127 newCap);
128 auto occInput = getOccInput(newCap, pcapEnabled);
129 writeOcc(occInput);
130 }
131 catch (const sdbusplus::exception_t& e)
132 {
133 lg2::error(
134 "updatePcapBounds: Failed to update user powercap due to {ERR}",
135 "ERR", e.what());
136 }
137 }
138 }
139 }
140
141 // Get value of power cap to send to the OCC (output/DC power)
getOccInput(uint32_t pcap,bool pcapEnabled)142 uint32_t PowerCap::getOccInput(uint32_t pcap, bool pcapEnabled)
143 {
144 if (!pcapEnabled)
145 {
146 // Pcap disabled, return 0 to indicate disabled
147 return 0;
148 }
149
150 // If pcap is not disabled then just return the pcap with the derating
151 // factor applied (output/DC power).
152 return ((static_cast<uint64_t>(pcap) * PS_DERATING_FACTOR) / 100);
153 }
154
getPcap()155 uint32_t PowerCap::getPcap()
156 {
157 utils::PropertyValue pcap{};
158 try
159 {
160 pcap = utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP);
161
162 return std::get<uint32_t>(pcap);
163 }
164 catch (const sdbusplus::exception_t& e)
165 {
166 lg2::error("Failed to get PowerCap property: path={PATH}: {ERR}",
167 "PATH", PCAP_PATH, "ERR", e.what());
168
169 return 0;
170 }
171 }
172
getPcapEnabled()173 bool PowerCap::getPcapEnabled()
174 {
175 utils::PropertyValue pcapEnabled{};
176 try
177 {
178 pcapEnabled = utils::getProperty(PCAP_PATH, PCAP_INTERFACE,
179 POWER_CAP_ENABLE_PROP);
180
181 return std::get<bool>(pcapEnabled);
182 }
183 catch (const sdbusplus::exception_t& e)
184 {
185 lg2::error("Failed to get PowerCapEnable property: path={PATH}: {ERR}",
186 "PATH", PCAP_PATH, "ERR", e.what());
187
188 return false;
189 }
190 }
191
getPcapFilename(const std::regex & expr)192 fs::path PowerCap::getPcapFilename(const std::regex& expr)
193 {
194 if (pcapBasePathname.empty())
195 {
196 pcapBasePathname = occStatus.getHwmonPath();
197 }
198
199 if (fs::exists(pcapBasePathname))
200 {
201 // Search for pcap file based on the supplied expr
202 for (auto& file : fs::directory_iterator(pcapBasePathname))
203 {
204 if (std::regex_search(file.path().string(), expr))
205 {
206 // Found match
207 return file;
208 }
209 }
210 }
211 else
212 {
213 lg2::error("Power Cap base filename not found: {FILE}", "FILE",
214 pcapBasePathname);
215 }
216
217 // return empty path
218 return fs::path{};
219 }
220
221 // Write the user power cap to sysfs (output/DC power)
222 // This will trigger the driver to send the cap to the OCC
writeOcc(uint32_t pcapValue)223 void PowerCap::writeOcc(uint32_t pcapValue)
224 {
225 if (!occStatus.occActive())
226 {
227 // OCC not running, skip update
228 return;
229 }
230
231 // Build the hwmon string to write the user power cap
232 fs::path fileName = getPcapFilename(std::regex{"power\\d+_cap_user$"});
233 if (fileName.empty())
234 {
235 lg2::error("Could not find a power cap file to write to: {FILE})",
236 "FILE", pcapBasePathname);
237 return;
238 }
239
240 uint64_t microWatts = pcapValue * 1000000ull;
241
242 auto pcapString{std::to_string(microWatts)};
243
244 // Open the hwmon file and write the power cap
245 std::ofstream file(fileName, std::ios::out);
246 if (file)
247 {
248 lg2::info("Writing {CAP}uW to {FILE}", "CAP", pcapString, "FILE",
249 fileName);
250 file << pcapString;
251 file.close();
252 }
253 else
254 {
255 lg2::error("Failed writing {CAP}uW to {FILE} (errno={ERR})", "CAP",
256 microWatts, "FILE", fileName, "ERR", errno);
257 }
258
259 return;
260 }
261
262 // Read the current user power cap from sysfs as input/AC power
readUserCapHwmon()263 uint32_t PowerCap::readUserCapHwmon()
264 {
265 uint32_t userCap = 0;
266
267 // Get the sysfs filename for the user power cap
268 fs::path userCapName = getPcapFilename(std::regex{"power\\d+_cap_user$"});
269 if (userCapName.empty())
270 {
271 lg2::error(
272 "readUserCapHwmon: Could not find a power cap file to read: {FILE})",
273 "FILE", pcapBasePathname);
274 return 0;
275 }
276
277 // Open the sysfs file and read the power cap
278 std::ifstream file(userCapName, std::ios::in);
279 if (file)
280 {
281 uint64_t cap;
282 file >> cap;
283 file.close();
284 // Convert to input/AC Power in Watts
285 userCap = (cap / (PS_DERATING_FACTOR / 100.0) / 1000000);
286 }
287 else
288 {
289 lg2::error("readUserCapHwmon: Failed reading {FILE} (errno={ERR})",
290 "FILE", userCapName, "ERR", errno);
291 }
292
293 return userCap;
294 }
295
pcapChanged(sdbusplus::message_t & msg)296 void PowerCap::pcapChanged(sdbusplus::message_t& msg)
297 {
298 if (!occStatus.occActive())
299 {
300 // Nothing to do
301 return;
302 }
303
304 uint32_t pcap = 0;
305 bool pcapEnabled = false;
306
307 std::string msgSensor;
308 std::map<std::string, std::variant<uint32_t, bool>> msgData;
309 msg.read(msgSensor, msgData);
310
311 bool changeFound = false;
312 for (const auto& [prop, value] : msgData)
313 {
314 if (prop == POWER_CAP_PROP)
315 {
316 pcap = std::get<uint32_t>(value);
317 pcapEnabled = getPcapEnabled();
318 changeFound = true;
319 }
320 else if (prop == POWER_CAP_ENABLE_PROP)
321 {
322 pcapEnabled = std::get<bool>(value);
323 pcap = getPcap();
324 changeFound = true;
325 }
326 else
327 {
328 // Ignore other properties
329 lg2::debug(
330 "pcapChanged: Unknown power cap property changed {PROP} to {VAL}",
331 "PROP", prop, "VAL", std::get<uint32_t>(value));
332 }
333 }
334
335 // Validate the cap is within supported range
336 uint32_t capSoftMin, capHardMin, capMax;
337 readDbusPcapLimits(capSoftMin, capHardMin, capMax);
338 if (((pcap > 0) && (pcap < capSoftMin)) || ((pcap == 0) && (pcapEnabled)))
339 {
340 lg2::error(
341 "pcapChanged: Power cap of {CAP}W is lower than allowed (soft min:{SMIN}, min:{MIN}) - using soft min",
342 "CAP", pcap, "SMIN", capSoftMin, "MIN", capHardMin);
343 pcap = capSoftMin;
344 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP, pcap);
345 }
346 else if (pcap > capMax)
347 {
348 lg2::error(
349 "pcapChanged: Power cap of {CAP}W is higher than allowed (max:{MAX}) - using max",
350 "CAP", pcap, "MAX", capMax);
351 pcap = capMax;
352 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP, pcap);
353 }
354
355 if (changeFound)
356 {
357 lg2::info(
358 "Power Cap Property Change (cap={CAP}W (input), enabled={ENABLE})",
359 "CAP", pcap, "ENABLE", pcapEnabled);
360
361 // Determine desired action to write to occ
362 auto occInput = getOccInput(pcap, pcapEnabled);
363 // Write action to occ
364 writeOcc(occInput);
365 }
366
367 return;
368 }
369
370 // Update the Power Cap bounds on DBus
updateDbusPcapLimits(uint32_t softMin,uint32_t hardMin,uint32_t max)371 bool PowerCap::updateDbusPcapLimits(uint32_t softMin, uint32_t hardMin,
372 uint32_t max)
373 {
374 bool complete = true;
375
376 try
377 {
378 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_SOFT_MIN,
379 softMin);
380 }
381 catch (const sdbusplus::exception_t& e)
382 {
383 lg2::error(
384 "updateDbusPcapLimits: Failed to set SOFT PCAP to {MIN}W due to {ERR}",
385 "MIN", softMin, "ERR", e.what());
386 complete = false;
387 }
388
389 try
390 {
391 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_HARD_MIN,
392 hardMin);
393 }
394 catch (const sdbusplus::exception_t& e)
395 {
396 lg2::error(
397 "updateDbusPcapLimits: Failed to set HARD PCAP to {MIN}W due to {ERR}",
398 "MIN", hardMin, "ERR", e.what());
399 complete = false;
400 }
401
402 try
403 {
404 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX, max);
405 }
406 catch (const sdbusplus::exception_t& e)
407 {
408 lg2::error(
409 "updateDbusPcapLimits: Failed to set MAX PCAP to {MAX}W due to {ERR}",
410 "MAX", max, "ERR", e.what());
411 complete = false;
412 }
413
414 return complete;
415 }
416
417 // Read the Power Cap bounds from DBus
readDbusPcapLimits(uint32_t & softMin,uint32_t & hardMin,uint32_t & max)418 bool PowerCap::readDbusPcapLimits(uint32_t& softMin, uint32_t& hardMin,
419 uint32_t& max)
420 {
421 bool complete = true;
422 utils::PropertyValue prop{};
423
424 try
425 {
426 prop =
427 utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_SOFT_MIN);
428 softMin = std::get<uint32_t>(prop);
429 }
430 catch (const sdbusplus::exception_t& e)
431 {
432 lg2::error("readDbusPcapLimits: Failed to get SOFT PCAP due to {ERR}",
433 "ERR", e.what());
434 softMin = 0;
435 complete = false;
436 }
437
438 try
439 {
440 prop =
441 utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_HARD_MIN);
442 hardMin = std::get<uint32_t>(prop);
443 }
444 catch (const sdbusplus::exception_t& e)
445 {
446 lg2::error("readDbusPcapLimits: Failed to get HARD PCAP due to {ERR}",
447 "ERR", e.what());
448 hardMin = 0;
449 complete = false;
450 }
451
452 try
453 {
454 prop = utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX);
455 max = std::get<uint32_t>(prop);
456 }
457 catch (const sdbusplus::exception_t& e)
458 {
459 lg2::error("readDbusPcapLimits: Failed to get MAX PCAP due to {ERR}",
460 "ERR", e.what());
461 max = INT_MAX;
462 complete = false;
463 }
464
465 return complete;
466 }
467
468 } // namespace powercap
469
470 } // namespace occ
471
472 } // namespace open_power
473