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 "standard_device.hpp"
18 
19 #include "format_utils.hpp"
20 
21 #include <exception>
22 #include <format>
23 #include <span>
24 #include <stdexcept>
25 
26 namespace phosphor::power::sequencer
27 {
28 
29 std::string StandardDevice::findPgoodFault(
30     Services& services, const std::string& powerSupplyError,
31     std::map<std::string, std::string>& additionalData)
32 {
33     std::string error{};
34     try
35     {
36         prepareForPgoodFaultDetection(services);
37 
38         // Get all GPIO values (if possible) from device.  They may be slow to
39         // obtain, so obtain them once and then pass values to each Rail object.
40         std::vector<int> gpioValues = getGPIOValuesIfPossible(services);
41 
42         // Try to find a voltage rail where a pgood fault occurred
43         Rail* rail = findRailWithPgoodFault(services, gpioValues,
44                                             additionalData);
45         if (rail != nullptr)
46         {
47             services.logErrorMsg(std::format(
48                 "Pgood fault found in rail monitored by device {}", name));
49 
50             // If this is a PSU rail and a PSU error was previously detected
51             if (rail->isPowerSupplyRail() && !powerSupplyError.empty())
52             {
53                 // Return power supply error as root cause
54                 error = powerSupplyError;
55             }
56             else
57             {
58                 // Return pgood fault as root cause
59                 error =
60                     "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault";
61             }
62 
63             storePgoodFaultDebugData(services, gpioValues, additionalData);
64         }
65     }
66     catch (const std::exception& e)
67     {
68         throw std::runtime_error{std::format(
69             "Unable to determine if a pgood fault occurred in device {}: {}",
70             name, e.what())};
71     }
72     return error;
73 }
74 
75 std::vector<int> StandardDevice::getGPIOValuesIfPossible(Services& services)
76 {
77     std::vector<int> values{};
78     try
79     {
80         values = getGPIOValues(services);
81     }
82     catch (...)
83     {}
84     return values;
85 }
86 
87 Rail* StandardDevice::findRailWithPgoodFault(
88     Services& services, const std::vector<int>& gpioValues,
89     std::map<std::string, std::string>& additionalData)
90 {
91     // Look for the first rail in the power on sequence with a pgood fault based
92     // on STATUS_VOUT.  This is usually the most accurate method.  For example,
93     // if a pgood fault occurs, the power sequencer device may automatically
94     // shut off related rails.  Ideally the device will only set fault bits in
95     // STATUS_VOUT for the rail with the pgood fault.  However, all the related
96     // rails will likely appear to be faulted by the other methods.
97     for (std::unique_ptr<Rail>& rail : rails)
98     {
99         if (rail->hasPgoodFaultStatusVout(*this, services, additionalData))
100         {
101             return rail.get();
102         }
103     }
104 
105     // Look for the first rail in the power on sequence with a pgood fault based
106     // on either a GPIO or the output voltage.  Both methods check if the rail
107     // is powered off.  If a pgood fault occurs during the power on sequence,
108     // the power sequencer device may stop powering on rails.  As a result, all
109     // rails after the faulted one in the sequence may also be powered off.
110     for (std::unique_ptr<Rail>& rail : rails)
111     {
112         if (rail->hasPgoodFaultGPIO(*this, services, gpioValues,
113                                     additionalData) ||
114             rail->hasPgoodFaultOutputVoltage(*this, services, additionalData))
115         {
116             return rail.get();
117         }
118     }
119 
120     // No rail with pgood fault found
121     return nullptr;
122 }
123 
124 void StandardDevice::storePgoodFaultDebugData(
125     Services& services, const std::vector<int>& gpioValues,
126     std::map<std::string, std::string>& additionalData)
127 {
128     try
129     {
130         additionalData.emplace("DEVICE_NAME", name);
131         storeGPIOValues(services, gpioValues, additionalData);
132     }
133     catch (...)
134     {}
135 }
136 
137 void StandardDevice::storeGPIOValues(
138     Services& services, const std::vector<int>& values,
139     std::map<std::string, std::string>& additionalData)
140 {
141     if (!values.empty())
142     {
143         std::string valuesStr = format_utils::toString(std::span(values));
144         services.logInfoMsg(
145             std::format("Device {} GPIO values: {}", name, valuesStr));
146         additionalData.emplace("GPIO_VALUES", valuesStr);
147     }
148 }
149 
150 } // namespace phosphor::power::sequencer
151