xref: /openbmc/openpower-occ-control/powercap.cpp (revision 03a8fe364d403ec0fb743e190d2f7222de1f4de8)
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