1 /**
2  * Copyright © 2024 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "rail.hpp"
18 
19 #include "pmbus.hpp"
20 
21 #include <exception>
22 #include <format>
23 
24 namespace phosphor::power::sequencer
25 {
26 namespace status_vout = phosphor::pmbus::status_vout;
27 
28 bool Rail::isPresent(Services& services)
29 {
30     // Initially assume rail is present
31     bool present{true};
32 
33     // If presence data member contains an inventory path to check
34     if (presence)
35     {
36         const std::string& inventoryPath = *presence;
37         try
38         {
39             present = services.isPresent(inventoryPath);
40         }
41         catch (const std::exception& e)
42         {
43             throw std::runtime_error{std::format(
44                 "Unable to determine presence of rail {} using inventory path {}: {}",
45                 name, inventoryPath, e.what())};
46         }
47     }
48 
49     return present;
50 }
51 
52 uint16_t Rail::getStatusWord(PowerSequencerDevice& device)
53 {
54     uint16_t value{0};
55     try
56     {
57         verifyHasPage();
58         value = device.getStatusWord(*page);
59     }
60     catch (const std::exception& e)
61     {
62         throw std::runtime_error{
63             std::format("Unable to read STATUS_WORD value for rail {}: {}",
64                         name, e.what())};
65     }
66     return value;
67 }
68 
69 uint8_t Rail::getStatusVout(PowerSequencerDevice& device)
70 {
71     uint8_t value{0};
72     try
73     {
74         verifyHasPage();
75         value = device.getStatusVout(*page);
76     }
77     catch (const std::exception& e)
78     {
79         throw std::runtime_error{
80             std::format("Unable to read STATUS_VOUT value for rail {}: {}",
81                         name, e.what())};
82     }
83     return value;
84 }
85 
86 double Rail::getReadVout(PowerSequencerDevice& device)
87 {
88     double value{0.0};
89     try
90     {
91         verifyHasPage();
92         value = device.getReadVout(*page);
93     }
94     catch (const std::exception& e)
95     {
96         throw std::runtime_error{std::format(
97             "Unable to read READ_VOUT value for rail {}: {}", name, e.what())};
98     }
99     return value;
100 }
101 
102 double Rail::getVoutUVFaultLimit(PowerSequencerDevice& device)
103 {
104     double value{0.0};
105     try
106     {
107         verifyHasPage();
108         value = device.getVoutUVFaultLimit(*page);
109     }
110     catch (const std::exception& e)
111     {
112         throw std::runtime_error{std::format(
113             "Unable to read VOUT_UV_FAULT_LIMIT value for rail {}: {}", name,
114             e.what())};
115     }
116     return value;
117 }
118 
119 bool Rail::hasPgoodFault(PowerSequencerDevice& device, Services& services,
120                          const std::vector<int>& gpioValues,
121                          std::map<std::string, std::string>& additionalData)
122 {
123     // If rail is not present, return false and don't check anything else
124     if (!isPresent(services))
125     {
126         services.logInfoMsg(std::format("Rail {} is not present", name));
127         return false;
128     }
129 
130     // Check if STATUS_VOUT indicates a pgood fault occurred
131     bool hasFault = hasPgoodFaultStatusVout(device, services, additionalData);
132 
133     // Check if a GPIO value indicates a pgood fault occurred
134     if (!hasFault)
135     {
136         hasFault = hasPgoodFaultGPIO(services, gpioValues, additionalData);
137     }
138 
139     // Check if output voltage is below UV limit indicating pgood fault occurred
140     if (!hasFault)
141     {
142         hasFault = hasPgoodFaultOutputVoltage(device, services, additionalData);
143     }
144 
145     // If fault detected, store debug data in additional data map
146     if (hasFault)
147     {
148         services.logErrorMsg(
149             std::format("Pgood fault detected in rail {}", name));
150         storePgoodFaultDebugData(device, services, additionalData);
151     }
152 
153     return hasFault;
154 }
155 
156 void Rail::verifyHasPage()
157 {
158     if (!page)
159     {
160         throw std::runtime_error{
161             std::format("No PAGE number defined for rail {}", name)};
162     }
163 }
164 
165 bool Rail::hasPgoodFaultStatusVout(
166     PowerSequencerDevice& device, Services& services,
167     std::map<std::string, std::string>& additionalData)
168 {
169     bool hasFault{false};
170 
171     // If we are checking the value of STATUS_VOUT for the rail
172     if (checkStatusVout)
173     {
174         // Read STATUS_VOUT value from device
175         uint8_t statusVout = getStatusVout(device);
176 
177         // Check if fault (non-warning) bits are set in value
178         if (statusVout & ~status_vout::WARNING_MASK)
179         {
180             hasFault = true;
181             services.logErrorMsg(std::format(
182                 "Rail {} has fault bits set in STATUS_VOUT: {:#04x}", name,
183                 statusVout));
184             additionalData.emplace("STATUS_VOUT",
185                                    std::format("{:#04x}", statusVout));
186         }
187         else if (statusVout != 0)
188         {
189             services.logInfoMsg(std::format(
190                 "Rail {} has warning bits set in STATUS_VOUT: {:#04x}", name,
191                 statusVout));
192         }
193     }
194 
195     return hasFault;
196 }
197 
198 bool Rail::hasPgoodFaultGPIO(Services& services,
199                              const std::vector<int>& gpioValues,
200                              std::map<std::string, std::string>& additionalData)
201 {
202     bool hasFault{false};
203 
204     // If a GPIO is defined for checking pgood status
205     if (gpio)
206     {
207         // Get GPIO value
208         unsigned int line = gpio->line;
209         bool activeLow = gpio->activeLow;
210         if (line >= gpioValues.size())
211         {
212             throw std::runtime_error{std::format(
213                 "Invalid GPIO line offset {} for rail {}: Device only has {} GPIO values",
214                 line, name, gpioValues.size())};
215         }
216         int value = gpioValues[line];
217 
218         // Check if value indicates pgood signal is not active
219         if ((activeLow && (value == 1)) || (!activeLow && (value == 0)))
220         {
221             hasFault = true;
222             services.logErrorMsg(std::format(
223                 "Rail {} pgood GPIO line offset {} has inactive value {}", name,
224                 line, value));
225             additionalData.emplace("GPIO_LINE", std::format("{}", line));
226             additionalData.emplace("GPIO_VALUE", std::format("{}", value));
227         }
228     }
229 
230     return hasFault;
231 }
232 
233 bool Rail::hasPgoodFaultOutputVoltage(
234     PowerSequencerDevice& device, Services& services,
235     std::map<std::string, std::string>& additionalData)
236 {
237     bool hasFault{false};
238 
239     // If we are comparing output voltage to UV limit to check pgood status
240     if (compareVoltageToLimit)
241     {
242         // Read output voltage and UV fault limit values from device
243         double vout = getReadVout(device);
244         double uvLimit = getVoutUVFaultLimit(device);
245 
246         // If output voltage is at or below UV fault limit
247         if (vout <= uvLimit)
248         {
249             hasFault = true;
250             services.logErrorMsg(std::format(
251                 "Rail {} output voltage {}V is <= UV fault limit {}V", name,
252                 vout, uvLimit));
253             additionalData.emplace("READ_VOUT", std::format("{}", vout));
254             additionalData.emplace("VOUT_UV_FAULT_LIMIT",
255                                    std::format("{}", uvLimit));
256         }
257     }
258 
259     return hasFault;
260 }
261 
262 void Rail::storePgoodFaultDebugData(
263     PowerSequencerDevice& device, Services& services,
264     std::map<std::string, std::string>& additionalData)
265 {
266     additionalData.emplace("RAIL_NAME", name);
267     if (page)
268     {
269         try
270         {
271             uint16_t statusWord = getStatusWord(device);
272             services.logInfoMsg(
273                 std::format("Rail {} STATUS_WORD: {:#06x}", name, statusWord));
274             additionalData.emplace("STATUS_WORD",
275                                    std::format("{:#06x}", statusWord));
276         }
277         catch (...)
278         {
279             // Ignore error; don't interrupt pgood fault handling
280         }
281     }
282 }
283 
284 } // namespace phosphor::power::sequencer
285