blob: cac74658fcdbe5196567e22b02c51057fa3673ea [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 Cain5d66a0a2022-02-09 08:52:10 -0600175 // Build the hwmon string to write the user power cap
176 fs::path fileName = getPcapFilename(std::regex{"power\\d+_cap_user$"});
177 if (fileName.empty())
Matt Spinlereaaf3b22019-07-16 10:29:27 -0500178 {
Chris Cainb6535e82022-01-21 08:24:13 -0600179 log<level::ERR>(
180 fmt::format("Could not find a power cap file to write to: {})",
Chris Cain5d66a0a2022-02-09 08:52:10 -0600181 pcapBasePathname.c_str())
Chris Cainb6535e82022-01-21 08:24:13 -0600182 .c_str());
Matt Spinlereaaf3b22019-07-16 10:29:27 -0500183 return;
184 }
Matt Spinlereaaf3b22019-07-16 10:29:27 -0500185
186 uint64_t microWatts = pcapValue * 1000000ull;
187
188 auto pcapString{std::to_string(microWatts)};
Andrew Geissler6ac874e2017-07-10 15:54:58 -0500189
Andrew Geissler6ac874e2017-07-10 15:54:58 -0500190 // Open the hwmon file and write the power cap
Chris Cain5d66a0a2022-02-09 08:52:10 -0600191 std::ofstream file(fileName, std::ios::out);
192 if (file)
193 {
194 log<level::INFO>(fmt::format("Writing {}uW to {}", pcapString.c_str(),
195 fileName.c_str())
196 .c_str());
197 file << pcapString;
198 file.close();
199 }
200 else
201 {
202 log<level::ERR>(fmt::format("Failed writing {}uW to {} (errno={})",
203 microWatts, fileName.c_str(), errno)
204 .c_str());
205 }
206
Andrew Geissler6ac874e2017-07-10 15:54:58 -0500207 return;
208}
209
Chris Cain40501a22022-03-14 17:33:27 -0500210// Read the current user power cap from sysfs.
211uint32_t PowerCap::readUserCapHwmon()
212{
213 uint32_t userCap = 0;
214
215 // Get the sysfs filename for the user power cap
216 fs::path userCapName = getPcapFilename(std::regex{"power\\d+_cap_user$"});
217 if (userCapName.empty())
218 {
219 log<level::ERR>(
220 fmt::format(
221 "readUserCapHwmon: Could not find a power cap file to read: {})",
222 pcapBasePathname.c_str())
223 .c_str());
224 return 0;
225 }
226
227 // Open the sysfs file and read the power cap
Chris Cain40501a22022-03-14 17:33:27 -0500228 std::ifstream file(userCapName, std::ios::in);
229 if (file)
230 {
Chris Caine2d0a432022-03-28 11:08:49 -0500231 uint64_t cap;
Chris Cain40501a22022-03-14 17:33:27 -0500232 file >> cap;
233 file.close();
234 // Convert to Watts
235 userCap = cap / 1000000;
236 }
237 else
238 {
239 log<level::ERR>(
240 fmt::format("readUserCapHwmon: Failed reading {} (errno={})",
241 userCapName.c_str(), errno)
242 .c_str());
243 }
244
245 return userCap;
246}
247
Andrew Geissler32016d12017-06-20 15:46:52 -0500248void PowerCap::pcapChanged(sdbusplus::message::message& msg)
249{
Andrew Geissler32016d12017-06-20 15:46:52 -0500250 if (!occStatus.occActive())
251 {
Chris Cain5d66a0a2022-02-09 08:52:10 -0600252 // Nothing to do
Andrew Geissler32016d12017-06-20 15:46:52 -0500253 return;
254 }
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500255
256 uint32_t pcap = 0;
257 bool pcapEnabled = false;
258
259 std::string msgSensor;
Patrick Williamse0962702020-05-13 17:50:22 -0500260 std::map<std::string, std::variant<uint32_t, bool>> msgData;
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500261 msg.read(msgSensor, msgData);
262
Chris Cain5d66a0a2022-02-09 08:52:10 -0600263 bool changeFound = false;
264 for (const auto& [prop, value] : msgData)
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500265 {
Chris Cain5d66a0a2022-02-09 08:52:10 -0600266 if (prop == POWER_CAP_PROP)
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500267 {
Chris Cain5d66a0a2022-02-09 08:52:10 -0600268 pcap = std::get<uint32_t>(value);
269 pcapEnabled = getPcapEnabled();
270 changeFound = true;
271 }
272 else if (prop == POWER_CAP_ENABLE_PROP)
273 {
274 pcapEnabled = std::get<bool>(value);
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500275 pcap = getPcap();
Chris Cain5d66a0a2022-02-09 08:52:10 -0600276 changeFound = true;
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500277 }
278 else
279 {
Chris Cain5d66a0a2022-02-09 08:52:10 -0600280 // Ignore other properties
281 log<level::DEBUG>(
282 fmt::format(
283 "pcapChanged: Unknown power cap property changed {} to {}",
284 prop.c_str(), std::get<uint32_t>(value))
285 .c_str());
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500286 }
287 }
288
Chris Cain5d66a0a2022-02-09 08:52:10 -0600289 if (changeFound)
290 {
291 log<level::INFO>(
292 fmt::format(
293 "Power Cap Property Change (cap={}W (input), enabled={})", pcap,
294 pcapEnabled ? 'y' : 'n')
295 .c_str());
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500296
Chris Cain5d66a0a2022-02-09 08:52:10 -0600297 // Determine desired action to write to occ
Andrew Geissler6ac874e2017-07-10 15:54:58 -0500298
Chris Cain5d66a0a2022-02-09 08:52:10 -0600299 auto occInput = getOccInput(pcap, pcapEnabled);
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500300
Chris Cain5d66a0a2022-02-09 08:52:10 -0600301 // Write action to occ
302 writeOcc(occInput);
303 }
Andrew Geissler52cf26a2017-07-06 12:56:32 -0500304
305 return;
Andrew Geissler32016d12017-06-20 15:46:52 -0500306}
307
Chris Cain5d66a0a2022-02-09 08:52:10 -0600308// Update the Power Cap bounds on DBus
Chris Cain40501a22022-03-14 17:33:27 -0500309bool PowerCap::updateDbusPcap(uint32_t hardMin, uint32_t max)
Chris Cain5d66a0a2022-02-09 08:52:10 -0600310{
311 bool complete = true;
312
313 try
314 {
Chris Cain5d66a0a2022-02-09 08:52:10 -0600315 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_HARD_MIN,
316 hardMin);
317 }
318 catch (const sdbusplus::exception::exception& e)
319 {
320 log<level::ERR>(
321 fmt::format(
322 "updateDbusPcap: Failed to set HARD PCAP to {}W due to {}",
323 hardMin, e.what())
324 .c_str());
325 complete = false;
326 }
327
328 try
329 {
330 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX, max);
331 }
332 catch (const sdbusplus::exception::exception& e)
333 {
334 log<level::ERR>(
335 fmt::format(
336 "updateDbusPcap: Failed to set MAX PCAP to {}W due to {}", max,
337 e.what())
338 .c_str());
339 complete = false;
340 }
341
342 return complete;
343}
Gunnar Mills94df8c92018-09-14 14:50:03 -0500344} // namespace powercap
Andrew Geissler32016d12017-06-20 15:46:52 -0500345
346} // namespace occ
347
Gunnar Mills94df8c92018-09-14 14:50:03 -0500348} // namespace open_power