blob: 65d8998f53c816fc9b35957c2c0ae2266af07ac6 [file] [log] [blame]
Chris Cain5d66a0a2022-02-09 08:52:10 -06001#include "occ_status.hpp"
2
Chris Cainb6535e82022-01-21 08:24:13 -06003#include <fmt/core.h>
4
Andrew Geissler32016d12017-06-20 15:46:52 -05005#include <phosphor-logging/log.hpp>
Gunnar Mills94df8c92018-09-14 14:50:03 -05006#include <powercap.hpp>
George Liub5ca1012021-09-10 12:53:11 +08007
8#include <cassert>
Chris Caine2d0a432022-03-28 11:08:49 -05009#include <filesystem>
Andrew Geissler32016d12017-06-20 15:46:52 -050010
11namespace open_power
12{
13namespace occ
14{
15namespace powercap
16{
17
Gunnar Mills94df8c92018-09-14 14:50:03 -050018constexpr auto PCAP_PATH = "/xyz/openbmc_project/control/host0/power_cap";
Andrew Geissler52cf26a2017-07-06 12:56:32 -050019constexpr auto PCAP_INTERFACE = "xyz.openbmc_project.Control.Power.Cap";
20
Andrew Geissler52cf26a2017-07-06 12:56:32 -050021constexpr auto POWER_CAP_PROP = "PowerCap";
22constexpr auto POWER_CAP_ENABLE_PROP = "PowerCapEnable";
Chris Cain5d66a0a2022-02-09 08:52:10 -060023constexpr auto POWER_CAP_HARD_MIN = "MinPowerCapValue";
24constexpr auto POWER_CAP_MAX = "MaxPowerCapValue";
Andrew Geissler52cf26a2017-07-06 12:56:32 -050025
Andrew Geissler32016d12017-06-20 15:46:52 -050026using namespace phosphor::logging;
George Liubcef3b42021-09-10 12:39:02 +080027namespace fs = std::filesystem;
Andrew Geissler32016d12017-06-20 15:46:52 -050028
Chris Cain5d66a0a2022-02-09 08:52:10 -060029void PowerCap::updatePcapBounds()
30{
31 // Build the hwmon string to write the power cap bounds
32 fs::path minName = getPcapFilename(std::regex{"power\\d+_cap_min$"});
Chris Cain5d66a0a2022-02-09 08:52:10 -060033 fs::path maxName = getPcapFilename(std::regex{"power\\d+_cap_max$"});
34
35 // Read the power cap bounds from sysfs files
36 uint64_t cap;
Chris Cain40501a22022-03-14 17:33:27 -050037 uint32_t capHardMin = 0, capMax = INT_MAX;
Chris Cain5d66a0a2022-02-09 08:52:10 -060038 std::ifstream minFile(minName, std::ios::in);
39 if (minFile)
40 {
41 minFile >> cap;
42 minFile.close();
Chris Cain8676e032022-03-24 15:06:23 -050043 // Convert to Input Power in Watts (round up)
44 capHardMin = ((cap / (PS_DERATING_FACTOR / 100.0) / 1000000) + 0.9);
Chris Cain5d66a0a2022-02-09 08:52:10 -060045 }
46 else
47 {
48 log<level::ERR>(
49 fmt::format(
50 "updatePcapBounds: unable to find cap_min file: {} (errno={})",
51 pcapBasePathname.c_str(), errno)
52 .c_str());
53 }
54 std::ifstream maxFile(maxName, std::ios::in);
55 if (maxFile)
56 {
57 maxFile >> cap;
58 maxFile.close();
Chris Cain8676e032022-03-24 15:06:23 -050059 // Convert to Input Power in Watts (truncate remainder)
60 capMax = cap / (PS_DERATING_FACTOR / 100.0) / 1000000;
Chris Cain5d66a0a2022-02-09 08:52:10 -060061 }
62 else
63 {
64 log<level::ERR>(
65 fmt::format(
66 "updatePcapBounds: unable to find cap_max file: {} (errno={})",
67 pcapBasePathname.c_str(), errno)
68 .c_str());
69 }
70
71 // Save the bounds to dbus
Chris Cain40501a22022-03-14 17:33:27 -050072 updateDbusPcap(capHardMin, capMax);
73
74 // Validate dbus and hwmon user caps match
75 const uint32_t dbusUserCap = getPcap();
76 const uint32_t hwmonUserCap = readUserCapHwmon();
77 if ((hwmonUserCap != 0) && (dbusUserCap != hwmonUserCap))
78 {
79 // User power cap is enabled, but does not match dbus
80 log<level::ERR>(
81 fmt::format(
82 "updatePcapBounds: user powercap mismatch (hwmon:{}W, bdus:{}W) - using dbus",
83 hwmonUserCap, dbusUserCap)
84 .c_str());
85 writeOcc(dbusUserCap);
86 }
Chris Cain5d66a0a2022-02-09 08:52:10 -060087}
88
Andrew Geissler4cea4d22017-07-10 15:13:33 -050089uint32_t PowerCap::getOccInput(uint32_t pcap, bool pcapEnabled)
90{
91 if (!pcapEnabled)
92 {
93 // Pcap disabled, return 0 to indicate disabled
94 return 0;
95 }
96
97 // If pcap is not disabled then just return the pcap with the derating
98 // factor applied.
Gunnar Mills94df8c92018-09-14 14:50:03 -050099 return ((static_cast<uint64_t>(pcap) * PS_DERATING_FACTOR) / 100);
Andrew Geissler4cea4d22017-07-10 15:13:33 -0500100}
101
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500102uint32_t PowerCap::getPcap()
103{
George Liuf3b75142021-06-10 11:22:50 +0800104 utils::PropertyValue pcap{};
105 try
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500106 {
George Liuf3b75142021-06-10 11:22:50 +0800107 pcap = utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP);
108
109 return std::get<uint32_t>(pcap);
110 }
Patrick Williams25613622021-09-02 09:29:54 -0500111 catch (const sdbusplus::exception::exception& e)
George Liuf3b75142021-06-10 11:22:50 +0800112 {
113 log<level::ERR>("Failed to get PowerCap property",
114 entry("ERROR=%s", e.what()),
115 entry("PATH=%s", PCAP_PATH));
116
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500117 return 0;
118 }
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500119}
120
121bool PowerCap::getPcapEnabled()
122{
George Liuf3b75142021-06-10 11:22:50 +0800123 utils::PropertyValue pcapEnabled{};
124 try
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500125 {
George Liuf3b75142021-06-10 11:22:50 +0800126 pcapEnabled = utils::getProperty(PCAP_PATH, PCAP_INTERFACE,
127 POWER_CAP_ENABLE_PROP);
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500128
George Liuf3b75142021-06-10 11:22:50 +0800129 return std::get<bool>(pcapEnabled);
130 }
Patrick Williams25613622021-09-02 09:29:54 -0500131 catch (const sdbusplus::exception::exception& e)
George Liuf3b75142021-06-10 11:22:50 +0800132 {
133 log<level::ERR>("Failed to get PowerCapEnable property",
134 entry("ERROR=%s", e.what()),
135 entry("PATH=%s", PCAP_PATH));
136
137 return false;
138 }
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500139}
Andrew Geissler32016d12017-06-20 15:46:52 -0500140
Chris Cain5d66a0a2022-02-09 08:52:10 -0600141fs::path PowerCap::getPcapFilename(const std::regex& expr)
Matt Spinlereaaf3b22019-07-16 10:29:27 -0500142{
Chris Cain5d66a0a2022-02-09 08:52:10 -0600143 if (pcapBasePathname.empty())
144 {
Chris Cain5d66a0a2022-02-09 08:52:10 -0600145 pcapBasePathname = occStatus.getHwmonPath();
Chris Cain5d66a0a2022-02-09 08:52:10 -0600146 }
Chris Caine2d0a432022-03-28 11:08:49 -0500147
148 if (fs::exists(pcapBasePathname))
Matt Spinlereaaf3b22019-07-16 10:29:27 -0500149 {
Chris Caine2d0a432022-03-28 11:08:49 -0500150 // Search for pcap file based on the supplied expr
151 for (auto& file : fs::directory_iterator(pcapBasePathname))
Matt Spinlereaaf3b22019-07-16 10:29:27 -0500152 {
Chris Caine2d0a432022-03-28 11:08:49 -0500153 if (std::regex_search(file.path().string(), expr))
154 {
155 // Found match
156 return file;
157 }
Matt Spinlereaaf3b22019-07-16 10:29:27 -0500158 }
159 }
Chris Caine2d0a432022-03-28 11:08:49 -0500160 else
161 {
162 log<level::ERR>(fmt::format("Power Cap base filename not found: {}",
163 pcapBasePathname.c_str())
164 .c_str());
165 }
166
Chris Cain5d66a0a2022-02-09 08:52:10 -0600167 // return empty path
168 return fs::path{};
Matt Spinlereaaf3b22019-07-16 10:29:27 -0500169}
170
Chris Cain40501a22022-03-14 17:33:27 -0500171// Write the user power cap to sysfs.
172// This will trigger the driver to send the cap to the OCC
Andrew Geissler6ac874e2017-07-10 15:54:58 -0500173void PowerCap::writeOcc(uint32_t pcapValue)
174{
Chris Caind4c19a02022-04-22 15:46:13 -0500175 if (!occStatus.occActive())
176 {
177 // OCC not running, skip update
178 return;
179 }
180
Chris Cain5d66a0a2022-02-09 08:52:10 -0600181 // Build the hwmon string to write the user power cap
182 fs::path fileName = getPcapFilename(std::regex{"power\\d+_cap_user$"});
183 if (fileName.empty())
Matt Spinlereaaf3b22019-07-16 10:29:27 -0500184 {
Chris Cainb6535e82022-01-21 08:24:13 -0600185 log<level::ERR>(
186 fmt::format("Could not find a power cap file to write to: {})",
Chris Cain5d66a0a2022-02-09 08:52:10 -0600187 pcapBasePathname.c_str())
Chris Cainb6535e82022-01-21 08:24:13 -0600188 .c_str());
Matt Spinlereaaf3b22019-07-16 10:29:27 -0500189 return;
190 }
Matt Spinlereaaf3b22019-07-16 10:29:27 -0500191
192 uint64_t microWatts = pcapValue * 1000000ull;
193
194 auto pcapString{std::to_string(microWatts)};
Andrew Geissler6ac874e2017-07-10 15:54:58 -0500195
Andrew Geissler6ac874e2017-07-10 15:54:58 -0500196 // Open the hwmon file and write the power cap
Chris Cain5d66a0a2022-02-09 08:52:10 -0600197 std::ofstream file(fileName, std::ios::out);
198 if (file)
199 {
200 log<level::INFO>(fmt::format("Writing {}uW to {}", pcapString.c_str(),
201 fileName.c_str())
202 .c_str());
203 file << pcapString;
204 file.close();
205 }
206 else
207 {
208 log<level::ERR>(fmt::format("Failed writing {}uW to {} (errno={})",
209 microWatts, fileName.c_str(), errno)
210 .c_str());
211 }
212
Andrew Geissler6ac874e2017-07-10 15:54:58 -0500213 return;
214}
215
Chris Cain40501a22022-03-14 17:33:27 -0500216// Read the current user power cap from sysfs.
217uint32_t PowerCap::readUserCapHwmon()
218{
219 uint32_t userCap = 0;
220
221 // Get the sysfs filename for the user power cap
222 fs::path userCapName = getPcapFilename(std::regex{"power\\d+_cap_user$"});
223 if (userCapName.empty())
224 {
225 log<level::ERR>(
226 fmt::format(
227 "readUserCapHwmon: Could not find a power cap file to read: {})",
228 pcapBasePathname.c_str())
229 .c_str());
230 return 0;
231 }
232
233 // Open the sysfs file and read the power cap
Chris Cain40501a22022-03-14 17:33:27 -0500234 std::ifstream file(userCapName, std::ios::in);
235 if (file)
236 {
Chris Caine2d0a432022-03-28 11:08:49 -0500237 uint64_t cap;
Chris Cain40501a22022-03-14 17:33:27 -0500238 file >> cap;
239 file.close();
240 // Convert to Watts
241 userCap = cap / 1000000;
242 }
243 else
244 {
245 log<level::ERR>(
246 fmt::format("readUserCapHwmon: Failed reading {} (errno={})",
247 userCapName.c_str(), errno)
248 .c_str());
249 }
250
251 return userCap;
252}
253
Andrew Geissler32016d12017-06-20 15:46:52 -0500254void PowerCap::pcapChanged(sdbusplus::message::message& msg)
255{
Andrew Geissler32016d12017-06-20 15:46:52 -0500256 if (!occStatus.occActive())
257 {
Chris Cain5d66a0a2022-02-09 08:52:10 -0600258 // Nothing to do
Andrew Geissler32016d12017-06-20 15:46:52 -0500259 return;
260 }
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500261
262 uint32_t pcap = 0;
263 bool pcapEnabled = false;
264
265 std::string msgSensor;
Patrick Williamse0962702020-05-13 17:50:22 -0500266 std::map<std::string, std::variant<uint32_t, bool>> msgData;
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500267 msg.read(msgSensor, msgData);
268
Chris Cain5d66a0a2022-02-09 08:52:10 -0600269 bool changeFound = false;
270 for (const auto& [prop, value] : msgData)
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500271 {
Chris Cain5d66a0a2022-02-09 08:52:10 -0600272 if (prop == POWER_CAP_PROP)
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500273 {
Chris Cain5d66a0a2022-02-09 08:52:10 -0600274 pcap = std::get<uint32_t>(value);
275 pcapEnabled = getPcapEnabled();
276 changeFound = true;
277 }
278 else if (prop == POWER_CAP_ENABLE_PROP)
279 {
280 pcapEnabled = std::get<bool>(value);
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500281 pcap = getPcap();
Chris Cain5d66a0a2022-02-09 08:52:10 -0600282 changeFound = true;
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500283 }
284 else
285 {
Chris Cain5d66a0a2022-02-09 08:52:10 -0600286 // Ignore other properties
287 log<level::DEBUG>(
288 fmt::format(
289 "pcapChanged: Unknown power cap property changed {} to {}",
290 prop.c_str(), std::get<uint32_t>(value))
291 .c_str());
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500292 }
293 }
294
Chris Cain5d66a0a2022-02-09 08:52:10 -0600295 if (changeFound)
296 {
297 log<level::INFO>(
298 fmt::format(
299 "Power Cap Property Change (cap={}W (input), enabled={})", pcap,
300 pcapEnabled ? 'y' : 'n')
301 .c_str());
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500302
Chris Cain5d66a0a2022-02-09 08:52:10 -0600303 // Determine desired action to write to occ
Andrew Geissler6ac874e2017-07-10 15:54:58 -0500304
Chris Cain5d66a0a2022-02-09 08:52:10 -0600305 auto occInput = getOccInput(pcap, pcapEnabled);
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500306
Chris Cain5d66a0a2022-02-09 08:52:10 -0600307 // Write action to occ
308 writeOcc(occInput);
309 }
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500310
311 return;
Andrew Geissler32016d12017-06-20 15:46:52 -0500312}
313
Chris Cain5d66a0a2022-02-09 08:52:10 -0600314// Update the Power Cap bounds on DBus
Chris Cain40501a22022-03-14 17:33:27 -0500315bool PowerCap::updateDbusPcap(uint32_t hardMin, uint32_t max)
Chris Cain5d66a0a2022-02-09 08:52:10 -0600316{
317 bool complete = true;
318
319 try
320 {
Chris Cain5d66a0a2022-02-09 08:52:10 -0600321 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_HARD_MIN,
322 hardMin);
323 }
324 catch (const sdbusplus::exception::exception& e)
325 {
326 log<level::ERR>(
327 fmt::format(
328 "updateDbusPcap: Failed to set HARD PCAP to {}W due to {}",
329 hardMin, e.what())
330 .c_str());
331 complete = false;
332 }
333
334 try
335 {
336 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX, max);
337 }
338 catch (const sdbusplus::exception::exception& e)
339 {
340 log<level::ERR>(
341 fmt::format(
342 "updateDbusPcap: Failed to set MAX PCAP to {}W due to {}", max,
343 e.what())
344 .c_str());
345 complete = false;
346 }
347
348 return complete;
349}
Gunnar Mills94df8c92018-09-14 14:50:03 -0500350} // namespace powercap
Andrew Geissler32016d12017-06-20 15:46:52 -0500351
352} // namespace occ
353
Gunnar Mills94df8c92018-09-14 14:50:03 -0500354} // namespace open_power