blob: 8317837fc676ed28e7b497cd7a161d888c66d38b [file] [log] [blame]
Matt Spinlerb54357f2017-08-21 14:38:54 -05001/**
2 * Copyright © 2017 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#include <map>
17#include <memory>
18#include <phosphor-logging/elog.hpp>
19#include <phosphor-logging/log.hpp>
20#include <elog-errors.hpp>
21#include <xyz/openbmc_project/Sensor/Device/error.hpp>
22#include <xyz/openbmc_project/Control/Device/error.hpp>
23#include <xyz/openbmc_project/Power/Fault/error.hpp>
Matt Spinlere7e432b2017-08-21 15:01:40 -050024#include "names_values.hpp"
Matt Spinlerb54357f2017-08-21 14:38:54 -050025#include "ucd90160.hpp"
26
27namespace witherspoon
28{
29namespace power
30{
31
32using namespace std::string_literals;
33
Matt Spinlere7e432b2017-08-21 15:01:40 -050034const auto MFR_STATUS = "mfr_status"s;
Matt Spinler1e365692017-08-21 14:43:55 -050035
Matt Spinlerb54357f2017-08-21 14:38:54 -050036const auto DEVICE_NAME = "UCD90160"s;
37const auto DRIVER_NAME = "ucd9000"s;
Matt Spinlere7e432b2017-08-21 15:01:40 -050038constexpr auto NUM_PAGES = 16;
Matt Spinlerb54357f2017-08-21 14:38:54 -050039
Matt Spinler110b2842017-08-21 15:23:27 -050040namespace fs = std::experimental::filesystem;
Matt Spinlerd998b732017-08-21 15:35:54 -050041using namespace gpio;
Matt Spinlerb54357f2017-08-21 14:38:54 -050042using namespace pmbus;
43using namespace phosphor::logging;
44using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error;
45using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error;
46using namespace sdbusplus::xyz::openbmc_project::Power::Fault::Error;
47
48UCD90160::UCD90160(size_t instance) :
Matt Spinlerfcd4a712017-09-19 10:45:07 -050049 Device(DEVICE_NAME, instance),
50 interface(std::get<ucd90160::pathField>(
51 deviceMap.find(instance)->second),
52 DRIVER_NAME,
53 instance),
54 gpioDevice(findGPIODevice(interface.path()))
Matt Spinlerb54357f2017-08-21 14:38:54 -050055{
56}
57
58void UCD90160::onFailure()
59{
60 try
61 {
62 auto voutError = checkVOUTFaults();
63
64 auto pgoodError = checkPGOODFaults(false);
65
66 //Not a voltage or PGOOD fault, but we know something
67 //failed so still create an error log.
68 if (!voutError && !pgoodError)
69 {
70 createPowerFaultLog();
71 }
72 }
73 catch (ReadFailure& e)
74 {
75 if (!accessError)
76 {
77 commit<ReadFailure>();
78 accessError = true;
79 }
80 }
81}
82
83void UCD90160::analyze()
84{
85 try
86 {
87 //Note: Voltage faults are always fatal, so they just
88 //need to be analyzed in onFailure().
89
90 checkPGOODFaults(true);
91 }
92 catch (ReadFailure& e)
93 {
94 if (!accessError)
95 {
96 commit<ReadFailure>();
97 accessError = true;
98 }
99 }
100}
101
Matt Spinlere7e432b2017-08-21 15:01:40 -0500102uint16_t UCD90160::readStatusWord()
103{
104 return interface.read(STATUS_WORD, Type::Debug);
105}
106
107uint32_t UCD90160::readMFRStatus()
108{
109 return interface.read(MFR_STATUS, Type::DeviceDebug);
110}
111
Matt Spinlerb54357f2017-08-21 14:38:54 -0500112bool UCD90160::checkVOUTFaults()
113{
Matt Spinlere7e432b2017-08-21 15:01:40 -0500114 bool errorCreated = false;
115 auto statusWord = readStatusWord();
116
117 //The status_word register has a summary bit to tell us
118 //if each page even needs to be checked
119 if (!(statusWord & status_word::VOUT_FAULT))
120 {
121 return errorCreated;
122 }
123
124 for (size_t page = 0; page < NUM_PAGES; page++)
125 {
126 if (isVoutFaultLogged(page))
127 {
128 continue;
129 }
130
131 auto statusVout = interface.insertPageNum(STATUS_VOUT, page);
132 uint8_t vout = interface.read(statusVout, Type::Debug);
133
134 //Any bit on is an error
135 if (vout)
136 {
137 auto& railNames = std::get<ucd90160::railNamesField>(
138 deviceMap.find(getInstance())->second);
139 auto railName = railNames.at(page);
140
141 util::NamesValues nv;
142 nv.add("STATUS_WORD", statusWord);
143 nv.add("STATUS_VOUT", vout);
144 nv.add("MFR_STATUS", readMFRStatus());
145
146 using metadata = xyz::openbmc_project::Power::Fault::
147 PowerSequencerVoltageFault;
148
149 report<PowerSequencerVoltageFault>(
150 metadata::RAIL(page),
151 metadata::RAIL_NAME(railName.c_str()),
152 metadata::RAW_STATUS(nv.get().c_str()));
153
154 setVoutFaultLogged(page);
155 errorCreated = true;
156 }
157 }
158
159 return errorCreated;
Matt Spinlerb54357f2017-08-21 14:38:54 -0500160}
161
162bool UCD90160::checkPGOODFaults(bool polling)
163{
Matt Spinlerd998b732017-08-21 15:35:54 -0500164 bool errorCreated = false;
165
166 //While PGOOD faults could show up in MFR_STATUS (and we could then
167 //check the summary bit in STATUS_WORD first), they are edge triggered,
168 //and as the device driver sends a clear faults command every time we
169 //do a read, we will never see them. So, we'll have to just read the
170 //real time GPI status GPIO.
171
172 //Check only the GPIs configured on this system.
173 auto& gpiConfigs = std::get<ucd90160::gpiConfigField>(
174 deviceMap.find(getInstance())->second);
175
176 for (const auto& gpiConfig : gpiConfigs)
177 {
178 auto gpiNum = std::get<ucd90160::gpiNumField>(gpiConfig);
179 auto doPoll = std::get<ucd90160::pollField>(gpiConfig);
180
181 //Can skip this one if there is already an error on this input,
182 //or we are polling and these inputs don't need to be polled
183 //(because errors on them are fatal).
184 if (isPGOODFaultLogged(gpiNum) || (polling && !doPoll))
185 {
186 continue;
187 }
188
189 //The real time status is read via the pin ID
190 auto pinID = std::get<ucd90160::pinIDField>(gpiConfig);
191 auto gpio = gpios.find(pinID);
192 Value gpiStatus;
193
194 try
195 {
196 //The first time through, create the GPIO objects
197 if (gpio == gpios.end())
198 {
199 gpios.emplace(
200 pinID,
201 std::make_unique<GPIO>(
202 gpioDevice, pinID, Direction::input));
203 gpio = gpios.find(pinID);
204 }
205
206 gpiStatus = gpio->second->read();
207 }
208 catch (std::exception& e)
209 {
210 if (!accessError)
211 {
212 log<level::ERR>(e.what());
213 accessError = true;
214 }
215 continue;
216 }
217
218 if (gpiStatus == Value::low)
219 {
220 auto& gpiName = std::get<ucd90160::gpiNameField>(gpiConfig);
221 auto status = (gpiStatus == Value::low) ? 0 : 1;
222
223 util::NamesValues nv;
224 nv.add("STATUS_WORD", readStatusWord());
225 nv.add("MFR_STATUS", readMFRStatus());
226 nv.add("INPUT_STATUS", status);
227
228 using metadata = xyz::openbmc_project::Power::Fault::
229 PowerSequencerPGOODFault;
230
231 report<PowerSequencerPGOODFault>(
232 metadata::INPUT_NUM(gpiNum),
233 metadata::INPUT_NAME(gpiName.c_str()),
234 metadata::RAW_STATUS(nv.get().c_str()));
235
236 setPGOODFaultLogged(gpiNum);
237 errorCreated = true;
238 }
239 }
240
241 return errorCreated;
Matt Spinlerb54357f2017-08-21 14:38:54 -0500242}
243
244void UCD90160::createPowerFaultLog()
245{
Matt Spinler9efb3082017-08-21 15:43:43 -0500246 util::NamesValues nv;
247 nv.add("STATUS_WORD", readStatusWord());
248 nv.add("MFR_STATUS", readMFRStatus());
Matt Spinlerb54357f2017-08-21 14:38:54 -0500249
Matt Spinler9efb3082017-08-21 15:43:43 -0500250 using metadata = xyz::openbmc_project::Power::Fault::
251 PowerSequencerFault;
252
253 report<PowerSequencerFault>(
254 metadata::RAW_STATUS(nv.get().c_str()));
Matt Spinlerb54357f2017-08-21 14:38:54 -0500255}
256
Matt Spinlerfcd4a712017-09-19 10:45:07 -0500257fs::path UCD90160::findGPIODevice(const fs::path& path)
Matt Spinler110b2842017-08-21 15:23:27 -0500258{
Matt Spinlerfcd4a712017-09-19 10:45:07 -0500259 fs::path gpioDevicePath;
Matt Spinler110b2842017-08-21 15:23:27 -0500260
261 //In the driver directory, look for a subdirectory
262 //named gpiochipX, where X is some number. Then
263 //we'll access the GPIO at /dev/gpiochipX.
264 if (fs::is_directory(path))
265 {
266 for (auto& f : fs::directory_iterator(path))
267 {
268 if (f.path().filename().string().find("gpiochip") !=
269 std::string::npos)
270 {
Matt Spinlerfcd4a712017-09-19 10:45:07 -0500271 gpioDevicePath = "/dev" / f.path().filename();
Matt Spinler110b2842017-08-21 15:23:27 -0500272 break;
273 }
274 }
275 }
276
Matt Spinlerfcd4a712017-09-19 10:45:07 -0500277 if (gpioDevicePath.empty())
Matt Spinler110b2842017-08-21 15:23:27 -0500278 {
Matt Spinlerfcd4a712017-09-19 10:45:07 -0500279 log<level::ERR>("Could not find GPIO device path",
Matt Spinler110b2842017-08-21 15:23:27 -0500280 entry("BASE_PATH=%s", path.c_str()));
281 }
Matt Spinlerfcd4a712017-09-19 10:45:07 -0500282
283 return gpioDevicePath;
Matt Spinler110b2842017-08-21 15:23:27 -0500284}
285
Matt Spinler7b14db22017-09-19 10:57:54 -0500286void UCD90160::gpuPGOODError(const std::string& callout)
287{
288 util::NamesValues nv;
289 nv.add("STATUS_WORD", readStatusWord());
290 nv.add("MFR_STATUS", readMFRStatus());
291
292 using metadata = xyz::openbmc_project::Power::Fault::GPUPowerFault;
293
294 report<GPUPowerFault>(
295 metadata::RAW_STATUS(nv.get().c_str()),
296 metadata::GPU(callout.c_str()));
297}
298
299void UCD90160::gpuOverTempError(const std::string& callout)
300{
301 util::NamesValues nv;
302 nv.add("STATUS_WORD", readStatusWord());
303 nv.add("MFR_STATUS", readMFRStatus());
304
305 using metadata = xyz::openbmc_project::Power::Fault::GPUOverTemp;
306
307 report<GPUOverTemp>(
308 metadata::RAW_STATUS(nv.get().c_str()),
309 metadata::GPU(callout.c_str()));
310}
311
Matt Spinlerb54357f2017-08-21 14:38:54 -0500312}
313}