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 MFR_STATUS = "mfr_status"s; 35 36 const auto DEVICE_NAME = "UCD90160"s; 37 const auto DRIVER_NAME = "ucd9000"s; 38 constexpr auto NUM_PAGES = 16; 39 40 namespace fs = std::experimental::filesystem; 41 using namespace gpio; 42 using namespace pmbus; 43 using namespace phosphor::logging; 44 using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error; 45 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error; 46 using namespace sdbusplus::xyz::openbmc_project::Power::Fault::Error; 47 48 UCD90160::UCD90160(size_t instance) : 49 Device(DEVICE_NAME, instance), 50 interface(std::get<ucd90160::pathField>( 51 deviceMap.find(instance)->second), 52 DRIVER_NAME, 53 instance) 54 { 55 findGPIODevice(); 56 } 57 58 void 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 83 void 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 102 uint16_t UCD90160::readStatusWord() 103 { 104 return interface.read(STATUS_WORD, Type::Debug); 105 } 106 107 uint32_t UCD90160::readMFRStatus() 108 { 109 return interface.read(MFR_STATUS, Type::DeviceDebug); 110 } 111 112 bool UCD90160::checkVOUTFaults() 113 { 114 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; 160 } 161 162 bool UCD90160::checkPGOODFaults(bool polling) 163 { 164 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; 242 } 243 244 void UCD90160::createPowerFaultLog() 245 { 246 util::NamesValues nv; 247 nv.add("STATUS_WORD", readStatusWord()); 248 nv.add("MFR_STATUS", readMFRStatus()); 249 250 using metadata = xyz::openbmc_project::Power::Fault:: 251 PowerSequencerFault; 252 253 report<PowerSequencerFault>( 254 metadata::RAW_STATUS(nv.get().c_str())); 255 } 256 257 void UCD90160::findGPIODevice() 258 { 259 auto& path = interface.path(); 260 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 { 271 gpioDevice = "/dev" / f.path().filename(); 272 break; 273 } 274 } 275 } 276 277 if (gpioDevice.empty()) 278 { 279 log<level::ERR>("Could not find UCD90160 GPIO device path", 280 entry("BASE_PATH=%s", path.c_str())); 281 } 282 } 283 284 } 285 } 286