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