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