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