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     return (hasPgoodFaultStatusVout(device, services, additionalData) ||
125             hasPgoodFaultGPIO(device, services, gpioValues, additionalData) ||
126             hasPgoodFaultOutputVoltage(device, services, additionalData));
127 }
128 
129 bool Rail::hasPgoodFaultStatusVout(
130     PowerSequencerDevice& device, Services& services,
131     std::map<std::string, std::string>& additionalData)
132 {
133     bool hasFault{false};
134 
135     // If rail is present and we are checking the value of STATUS_VOUT
136     if (isPresent(services) && checkStatusVout)
137     {
138         // Read STATUS_VOUT value from device
139         uint8_t statusVout = getStatusVout(device);
140 
141         // Check if fault (non-warning) bits are set in value
142         if (statusVout & ~status_vout::WARNING_MASK)
143         {
144             hasFault = true;
145             services.logErrorMsg(std::format(
146                 "Rail {} has fault bits set in STATUS_VOUT: {:#04x}", name,
147                 statusVout));
148             additionalData.emplace("STATUS_VOUT",
149                                    std::format("{:#04x}", statusVout));
150             storePgoodFaultDebugData(device, services, additionalData);
151         }
152         else if (statusVout != 0)
153         {
154             services.logInfoMsg(std::format(
155                 "Rail {} has warning bits set in STATUS_VOUT: {:#04x}", name,
156                 statusVout));
157         }
158     }
159 
160     return hasFault;
161 }
162 
163 bool Rail::hasPgoodFaultGPIO(PowerSequencerDevice& device, Services& services,
164                              const std::vector<int>& gpioValues,
165                              std::map<std::string, std::string>& additionalData)
166 {
167     bool hasFault{false};
168 
169     // If rail is present and a GPIO is defined for checking pgood status
170     if (isPresent(services) && gpio)
171     {
172         // Get GPIO value
173         unsigned int line = gpio->line;
174         bool activeLow = gpio->activeLow;
175         if (line >= gpioValues.size())
176         {
177             throw std::runtime_error{std::format(
178                 "Invalid GPIO line offset {} for rail {}: Device only has {} GPIO values",
179                 line, name, gpioValues.size())};
180         }
181         int value = gpioValues[line];
182 
183         // Check if value indicates pgood signal is not active
184         if ((activeLow && (value == 1)) || (!activeLow && (value == 0)))
185         {
186             hasFault = true;
187             services.logErrorMsg(std::format(
188                 "Rail {} pgood GPIO line offset {} has inactive value {}", name,
189                 line, value));
190             additionalData.emplace("GPIO_LINE", std::format("{}", line));
191             additionalData.emplace("GPIO_VALUE", std::format("{}", value));
192             storePgoodFaultDebugData(device, services, additionalData);
193         }
194     }
195 
196     return hasFault;
197 }
198 
199 bool Rail::hasPgoodFaultOutputVoltage(
200     PowerSequencerDevice& device, Services& services,
201     std::map<std::string, std::string>& additionalData)
202 {
203     bool hasFault{false};
204 
205     // If rail is present and we are comparing output voltage to UV limit
206     if (isPresent(services) && compareVoltageToLimit)
207     {
208         // Read output voltage and UV fault limit values from device
209         double vout = getReadVout(device);
210         double uvLimit = getVoutUVFaultLimit(device);
211 
212         // If output voltage is at or below UV fault limit
213         if (vout <= uvLimit)
214         {
215             hasFault = true;
216             services.logErrorMsg(std::format(
217                 "Rail {} output voltage {}V is <= UV fault limit {}V", name,
218                 vout, uvLimit));
219             additionalData.emplace("READ_VOUT", std::format("{}", vout));
220             additionalData.emplace("VOUT_UV_FAULT_LIMIT",
221                                    std::format("{}", uvLimit));
222             storePgoodFaultDebugData(device, services, additionalData);
223         }
224     }
225 
226     return hasFault;
227 }
228 
229 void Rail::verifyHasPage()
230 {
231     if (!page)
232     {
233         throw std::runtime_error{
234             std::format("No PAGE number defined for rail {}", name)};
235     }
236 }
237 
238 void Rail::storePgoodFaultDebugData(
239     PowerSequencerDevice& device, Services& services,
240     std::map<std::string, std::string>& additionalData)
241 {
242     services.logErrorMsg(std::format("Pgood fault detected in rail {}", name));
243     additionalData.emplace("RAIL_NAME", name);
244     if (page)
245     {
246         try
247         {
248             uint16_t statusWord = getStatusWord(device);
249             services.logInfoMsg(
250                 std::format("Rail {} STATUS_WORD: {:#06x}", name, statusWord));
251             additionalData.emplace("STATUS_WORD",
252                                    std::format("{:#06x}", statusWord));
253         }
254         catch (...)
255         {
256             // Ignore error; don't interrupt pgood fault handling
257         }
258     }
259 }
260 
261 } // namespace phosphor::power::sequencer
262