1 #include "config.h"
2 
3 #include "occ_manager.hpp"
4 
5 #include "i2c_occ.hpp"
6 #include "occ_dbus.hpp"
7 #include "utils.hpp"
8 
9 #include <cmath>
10 #include <experimental/filesystem>
11 #include <phosphor-logging/elog-errors.hpp>
12 #include <phosphor-logging/log.hpp>
13 #include <regex>
14 #include <xyz/openbmc_project/Common/error.hpp>
15 
16 namespace open_power
17 {
18 namespace occ
19 {
20 
21 constexpr uint32_t fruTypeNotAvailable = 0xFF;
22 
23 using namespace phosphor::logging;
24 
25 void Manager::findAndCreateObjects()
26 {
27     for (auto id = 0; id < MAX_CPUS; ++id)
28     {
29         // Create one occ per cpu
30         auto occ = std::string(OCC_NAME) + std::to_string(id);
31         createObjects(occ);
32     }
33 }
34 
35 int Manager::cpuCreated(sdbusplus::message::message& msg)
36 {
37     namespace fs = std::experimental::filesystem;
38 
39     sdbusplus::message::object_path o;
40     msg.read(o);
41     fs::path cpuPath(std::string(std::move(o)));
42 
43     auto name = cpuPath.filename().string();
44     auto index = name.find(CPU_NAME);
45     name.replace(index, std::strlen(CPU_NAME), OCC_NAME);
46 
47     createObjects(name);
48 
49     return 0;
50 }
51 
52 void Manager::createObjects(const std::string& occ)
53 {
54     auto path = fs::path(OCC_CONTROL_ROOT) / occ;
55 
56     passThroughObjects.emplace_back(
57         std::make_unique<PassThrough>(path.c_str()));
58 
59     statusObjects.emplace_back(std::make_unique<Status>(
60         event, path.c_str(), *this,
61         std::bind(std::mem_fn(&Manager::statusCallBack), this,
62                   std::placeholders::_1)
63 #ifdef PLDM
64             ,
65         std::bind(std::mem_fn(&pldm::Interface::resetOCC), pldmHandle.get(),
66                   std::placeholders::_1)
67 #endif
68             ));
69 
70     // Create the power cap monitor object for master occ (0)
71     if (!pcap)
72     {
73         pcap = std::make_unique<open_power::occ::powercap::PowerCap>(
74             *statusObjects.front());
75     }
76 
77 #ifdef POWER10
78     // Create the power mode monitor object for master occ (0)
79     if (!pmode)
80     {
81         pmode = std::make_unique<open_power::occ::powermode::PowerMode>(
82             *statusObjects.front());
83     }
84 #endif
85 }
86 
87 void Manager::statusCallBack(bool status)
88 {
89     using InternalFailure =
90         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
91 
92     // At this time, it won't happen but keeping it
93     // here just in case something changes in the future
94     if ((activeCount == 0) && (!status))
95     {
96         log<level::ERR>("Invalid update on OCCActive");
97         elog<InternalFailure>();
98     }
99 
100     activeCount += status ? 1 : -1;
101 
102     // Only start presence detection if all the OCCs are bound
103     if (activeCount == statusObjects.size())
104     {
105         for (auto& obj : statusObjects)
106         {
107             obj->addPresenceWatchMaster();
108         }
109     }
110 
111     if ((!_pollTimer->isEnabled()) && (activeCount > 0))
112     {
113         log<level::INFO>(fmt::format("Manager::statusCallBack(): {} OCCs will "
114                                      "be polled every {} seconds",
115                                      activeCount, pollInterval)
116                              .c_str());
117 
118         // Send poll and start OCC poll timer
119         pollerTimerExpired();
120     }
121     else if ((_pollTimer->isEnabled()) && (activeCount == 0))
122     {
123         // Stop OCC poll timer
124         log<level::INFO>("Manager::statusCallBack(): OCCs are not running, "
125                          "stopping poll timer");
126         _pollTimer->setEnabled(false);
127     }
128 }
129 
130 #ifdef I2C_OCC
131 void Manager::initStatusObjects()
132 {
133     // Make sure we have a valid path string
134     static_assert(sizeof(DEV_PATH) != 0);
135 
136     auto deviceNames = i2c_occ::getOccHwmonDevices(DEV_PATH);
137     auto occMasterName = deviceNames.front();
138     for (auto& name : deviceNames)
139     {
140         i2c_occ::i2cToDbus(name);
141         name = std::string(OCC_NAME) + '_' + name;
142         auto path = fs::path(OCC_CONTROL_ROOT) / name;
143         statusObjects.emplace_back(
144             std::make_unique<Status>(event, path.c_str(), *this));
145     }
146     // The first device is master occ
147     pcap = std::make_unique<open_power::occ::powercap::PowerCap>(
148         *statusObjects.front(), occMasterName);
149 #ifdef POWER10
150     pmode = std::make_unique<open_power::occ::powermode::PowerMode>(
151         *statusObjects.front());
152 #endif
153 }
154 #endif
155 
156 #ifdef PLDM
157 bool Manager::updateOCCActive(instanceID instance, bool status)
158 {
159     return (statusObjects[instance])->occActive(status);
160 }
161 #endif
162 
163 void Manager::pollerTimerExpired()
164 {
165     if (activeCount == 0)
166     {
167         // No OCCs running, so poll timer will not be restarted
168         log<level::INFO>("Manager::pollerTimerExpire(): No OCCs running, poll "
169                          "timer not restarted");
170     }
171 
172     if (!_pollTimer)
173     {
174         log<level::ERR>(
175             "Manager::pollerTimerExpired() ERROR: Timer not defined");
176         return;
177     }
178 
179     for (auto& obj : statusObjects)
180     {
181         // Read sysfs to force kernel to poll OCC
182         obj->readOccState();
183 
184 #ifdef READ_OCC_SENSORS
185         // Read occ sensor values
186         auto id = obj->getOccInstanceID();
187         if (!obj->occActive())
188         {
189             // Occ not activated
190             setSensorValueToNaN(id);
191             continue;
192         }
193         getSensorValues(id, obj->isMasterOcc());
194 #endif
195     }
196 
197     // Restart OCC poll timer
198     _pollTimer->restartOnce(std::chrono::seconds(pollInterval));
199 }
200 
201 #ifdef READ_OCC_SENSORS
202 void Manager::readTempSensors(const fs::path& path, uint32_t id)
203 {
204     const int open_errno = errno;
205     std::regex expr{"temp\\d+_label$"}; // Example: temp5_label
206     for (auto& file : fs::directory_iterator(path))
207     {
208         if (!std::regex_search(file.path().string(), expr))
209         {
210             continue;
211         }
212         std::ifstream fileOpen(file.path(), std::ios::in);
213         if (!fileOpen)
214         {
215             // If not able to read, OCC may be offline
216             log<level::DEBUG>(
217                 fmt::format("readTempSensors: open failed(errno = {}) ",
218                             open_errno)
219                     .c_str());
220 
221             continue;
222         }
223         uint32_t labelValue{0};
224         fileOpen >> labelValue;
225         fileOpen.close();
226 
227         const std::string& tempLabel = "label";
228         const std::string filePathString = file.path().string().substr(
229             0, file.path().string().length() - tempLabel.length());
230         std::ifstream fruTypeFile(filePathString + "fru_type", std::ios::in);
231         if (!fruTypeFile)
232         {
233             // If not able to read, OCC may be offline
234             log<level::DEBUG>(
235                 fmt::format("readTempSensors: open failed(errno = {}) ",
236                             open_errno)
237                     .c_str());
238             continue;
239         }
240         uint32_t fruTypeValue{0};
241         fruTypeFile >> fruTypeValue;
242         fruTypeFile.close();
243 
244         std::string sensorPath =
245             OCC_SENSORS_ROOT + std::string("/temperature/");
246 
247         if (fruTypeValue == VRMVdd)
248         {
249             sensorPath.append("vrm_vdd" + std::to_string(id) + "_temp");
250         }
251         else
252         {
253             uint16_t type = (labelValue & 0xFF000000) >> 24;
254             uint16_t instanceID = labelValue & 0x0000FFFF;
255 
256             if (type == OCC_DIMM_TEMP_SENSOR_TYPE)
257             {
258                 if (fruTypeValue == fruTypeNotAvailable)
259                 {
260                     // Not all DIMM related temps are available to read
261                     // (no _input file in this case)
262                     continue;
263                 }
264                 auto iter = dimmTempSensorName.find(fruTypeValue);
265                 if (iter == dimmTempSensorName.end())
266                 {
267                     log<level::ERR>(fmt::format("readTempSensors: Fru type "
268                                                 "error! fruTypeValue = {}) ",
269                                                 fruTypeValue)
270                                         .c_str());
271                     continue;
272                 }
273 
274                 sensorPath.append("dimm" + std::to_string(instanceID) +
275                                   iter->second);
276             }
277             else if (type == OCC_CPU_TEMP_SENSOR_TYPE)
278             {
279                 if (fruTypeValue != processorCore)
280                 {
281                     // TODO: support IO ring temp
282                     continue;
283                 }
284 
285                 sensorPath.append("proc" + std::to_string(id) + "_core" +
286                                   std::to_string(instanceID) + "_temp");
287             }
288             else
289             {
290                 continue;
291             }
292         }
293 
294         std::ifstream faultPathFile(filePathString + "fault", std::ios::in);
295         if (faultPathFile)
296         {
297             uint32_t faultValue;
298             faultPathFile >> faultValue;
299             faultPathFile.close();
300 
301             if (faultValue != 0)
302             {
303                 open_power::occ::dbus::OccDBusSensors::getOccDBus().setValue(
304                     sensorPath, std::numeric_limits<double>::quiet_NaN());
305 
306                 open_power::occ::dbus::OccDBusSensors::getOccDBus()
307                     .setOperationalStatus(sensorPath, false);
308 
309                 continue;
310             }
311         }
312 
313         std::ifstream inputFile(filePathString + "input", std::ios::in);
314         if (inputFile)
315         {
316             double tempValue;
317             inputFile >> tempValue;
318 
319             inputFile.close();
320 
321             open_power::occ::dbus::OccDBusSensors::getOccDBus().setValue(
322                 sensorPath, tempValue * std::pow(10, -3));
323 
324             open_power::occ::dbus::OccDBusSensors::getOccDBus()
325                 .setOperationalStatus(sensorPath, true);
326 
327             existingSensors[sensorPath] = id;
328         }
329         else
330         {
331             // If not able to read, OCC may be offline
332             log<level::DEBUG>(
333                 fmt::format("readTempSensors: open failed(errno = {}) ",
334                             open_errno)
335                     .c_str());
336         }
337     }
338     return;
339 }
340 
341 std::optional<std::string>
342     Manager::getPowerLabelFunctionID(const std::string& value)
343 {
344     // If the value is "system", then the FunctionID is "system".
345     if (value == "system")
346     {
347         return value;
348     }
349 
350     // If the value is not "system", then the label value have 3 numbers, of
351     // which we only care about the middle one:
352     // <sensor id>_<function id>_<apss channel>
353     // eg: The value is "0_10_5" , then the FunctionID is "10".
354     if (value.find("_") == std::string::npos)
355     {
356         return std::nullopt;
357     }
358 
359     auto powerLabelValue = value.substr((value.find("_") + 1));
360 
361     if (powerLabelValue.find("_") == std::string::npos)
362     {
363         return std::nullopt;
364     }
365 
366     return powerLabelValue.substr(0, powerLabelValue.find("_"));
367 }
368 
369 void Manager::readPowerSensors(const fs::path& path, uint32_t id)
370 {
371     const int open_errno = errno;
372     std::regex expr{"power\\d+_label$"}; // Example: power5_label
373     for (auto& file : fs::directory_iterator(path))
374     {
375         if (!std::regex_search(file.path().string(), expr))
376         {
377             continue;
378         }
379         std::ifstream fileOpen(file.path(), std::ios::in);
380         if (!fileOpen)
381         {
382             // If not able to read, OCC may be offline
383             log<level::DEBUG>(
384                 fmt::format("readPowerSensors: open failed(errno = {}) ",
385                             open_errno)
386                     .c_str());
387 
388             continue;
389         }
390         std::string labelValue;
391         fileOpen >> labelValue;
392         fileOpen.close();
393 
394         auto functionID = getPowerLabelFunctionID(labelValue);
395         if (functionID == std::nullopt)
396         {
397             continue;
398         }
399 
400         const std::string& tempLabel = "label";
401         const std::string filePathString = file.path().string().substr(
402             0, file.path().string().length() - tempLabel.length());
403 
404         std::string sensorPath = OCC_SENSORS_ROOT + std::string("/power/");
405 
406         auto iter = powerSensorName.find(*functionID);
407         if (iter == powerSensorName.end())
408         {
409             continue;
410         }
411         sensorPath.append(iter->second);
412 
413         std::ifstream faultPathFile(filePathString + "fault", std::ios::in);
414         if (faultPathFile)
415         {
416             uint32_t faultValue{0};
417             faultPathFile >> faultValue;
418             faultPathFile.close();
419 
420             if (faultValue != 0)
421             {
422                 open_power::occ::dbus::OccDBusSensors::getOccDBus().setValue(
423                     sensorPath, std::numeric_limits<double>::quiet_NaN());
424 
425                 open_power::occ::dbus::OccDBusSensors::getOccDBus()
426                     .setOperationalStatus(sensorPath, false);
427 
428                 continue;
429             }
430         }
431 
432         std::ifstream inputFile(filePathString + "input", std::ios::in);
433         if (inputFile)
434         {
435             double tempValue;
436             inputFile >> tempValue;
437             inputFile.close();
438 
439             open_power::occ::dbus::OccDBusSensors::getOccDBus().setValue(
440                 sensorPath, tempValue * std::pow(10, -3) * std::pow(10, -3));
441 
442             open_power::occ::dbus::OccDBusSensors::getOccDBus()
443                 .setOperationalStatus(sensorPath, true);
444 
445             existingSensors[sensorPath] = id;
446         }
447         else
448         {
449             // If not able to read, OCC may be offline
450             log<level::DEBUG>(
451                 fmt::format("readPowerSensors: open failed(errno = {}) ",
452                             open_errno)
453                     .c_str());
454         }
455     }
456     return;
457 }
458 
459 void Manager::setSensorValueToNaN(uint32_t id)
460 {
461     for (const auto& [sensorPath, occId] : existingSensors)
462     {
463         if (occId == id)
464         {
465             open_power::occ::dbus::OccDBusSensors::getOccDBus().setValue(
466                 sensorPath, std::numeric_limits<double>::quiet_NaN());
467         }
468     }
469     return;
470 }
471 
472 void Manager::getSensorValues(uint32_t id, bool masterOcc)
473 {
474     const auto occ = std::string("occ-hwmon.") + std::to_string(id + 1);
475 
476     fs::path fileName{OCC_HWMON_PATH + occ + "/hwmon/"};
477 
478     // Need to get the hwmonXX directory name, there better only be 1 dir
479     assert(std::distance(fs::directory_iterator(fileName),
480                          fs::directory_iterator{}) == 1);
481     // Now set our path to this full path, including this hwmonXX directory
482     fileName = fs::path(*fs::directory_iterator(fileName));
483 
484     // Read temperature sensors
485     readTempSensors(fileName, id);
486 
487     if (masterOcc)
488     {
489         // Read power sensors
490         readPowerSensors(fileName, id);
491     }
492 
493     return;
494 }
495 #endif
496 
497 } // namespace occ
498 } // namespace open_power
499