| Tony Lee | 84d430c | 2019-06-13 15:26:15 +0800 | [diff] [blame] | 1 | #include "nvme_manager.hpp" | 
 | 2 |  | 
| Tony Lee | 6c59501 | 2019-06-19 10:54:59 +0800 | [diff] [blame^] | 3 | #include "smbus.hpp" | 
 | 4 |  | 
 | 5 | #include <filesystem> | 
 | 6 | #include <map> | 
 | 7 | #include <nlohmann/json.hpp> | 
| Tony Lee | 84d430c | 2019-06-13 15:26:15 +0800 | [diff] [blame] | 8 | #include <phosphor-logging/elog-errors.hpp> | 
 | 9 | #include <phosphor-logging/log.hpp> | 
| Tony Lee | 6c59501 | 2019-06-19 10:54:59 +0800 | [diff] [blame^] | 10 | #include <sstream> | 
 | 11 | #include <string> | 
| Tony Lee | 84d430c | 2019-06-13 15:26:15 +0800 | [diff] [blame] | 12 |  | 
| Tony Lee | 6c59501 | 2019-06-19 10:54:59 +0800 | [diff] [blame^] | 13 | #include "i2c.h" | 
| Tony Lee | 84d430c | 2019-06-13 15:26:15 +0800 | [diff] [blame] | 14 | #define MONITOR_INTERVAL_SECONDS 1 | 
| Tony Lee | 6c59501 | 2019-06-19 10:54:59 +0800 | [diff] [blame^] | 15 | #define NVME_SSD_SLAVE_ADDRESS 0x6a | 
 | 16 | #define GPIO_BASE_PATH "/sys/class/gpio/gpio" | 
 | 17 | #define IS_PRESENT "0" | 
 | 18 | #define POWERGD "1" | 
 | 19 |  | 
 | 20 | static constexpr auto configFile = "/etc/nvme/nvme_config.json"; | 
 | 21 | static constexpr auto delay = std::chrono::milliseconds{100}; | 
 | 22 | using Json = nlohmann::json; | 
 | 23 |  | 
 | 24 | static constexpr const uint8_t COMMAND_CODE_0 = 0; | 
 | 25 | static constexpr const uint8_t COMMAND_CODE_8 = 8; | 
 | 26 |  | 
 | 27 | static constexpr int SERIALNUMBER_START_INDEX = 3; | 
 | 28 | static constexpr int SERIALNUMBER_END_INDEX = 23; | 
 | 29 |  | 
 | 30 | static constexpr const int TEMPERATURE_SENSOR_FAILURE = 0x81; | 
 | 31 |  | 
 | 32 | namespace fs = std::filesystem; | 
 | 33 |  | 
| Tony Lee | 84d430c | 2019-06-13 15:26:15 +0800 | [diff] [blame] | 34 | namespace phosphor | 
 | 35 | { | 
 | 36 | namespace nvme | 
 | 37 | { | 
 | 38 |  | 
 | 39 | using namespace std; | 
 | 40 | using namespace phosphor::logging; | 
 | 41 |  | 
| Tony Lee | 6c59501 | 2019-06-19 10:54:59 +0800 | [diff] [blame^] | 42 | std::string intToHex(int input) | 
 | 43 | { | 
 | 44 |     std::stringstream tmp; | 
 | 45 |     tmp << std::hex << input; | 
 | 46 |  | 
 | 47 |     return tmp.str(); | 
 | 48 | } | 
 | 49 |  | 
 | 50 | /** @brief Get NVMe info over smbus  */ | 
 | 51 | bool getNVMeInfobyBusID(int busID, phosphor::nvme::Nvme::NVMeData& nvmeData) | 
 | 52 | { | 
 | 53 |     nvmeData.present = true; | 
 | 54 |     nvmeData.vendor = ""; | 
 | 55 |     nvmeData.serialNumber = ""; | 
 | 56 |     nvmeData.smartWarnings = ""; | 
 | 57 |     nvmeData.statusFlags = ""; | 
 | 58 |     nvmeData.driveLifeUsed = ""; | 
 | 59 |     nvmeData.sensorValue = (int8_t)TEMPERATURE_SENSOR_FAILURE; | 
 | 60 |  | 
 | 61 |     phosphor::smbus::Smbus smbus; | 
 | 62 |  | 
 | 63 |     unsigned char rsp_data_command_0[I2C_DATA_MAX] = {0}; | 
 | 64 |     unsigned char rsp_data_command_8[I2C_DATA_MAX] = {0}; | 
 | 65 |  | 
 | 66 |     uint8_t tx_data = COMMAND_CODE_0; | 
 | 67 |  | 
 | 68 |     auto init = smbus.smbusInit(busID); | 
 | 69 |  | 
 | 70 |     static std::unordered_map<int, bool> isErrorSmbus; | 
 | 71 |  | 
 | 72 |     if (init == -1) | 
 | 73 |     { | 
 | 74 |         if (isErrorSmbus[busID] != true) | 
 | 75 |         { | 
 | 76 |             log<level::ERR>("smbusInit fail!"); | 
 | 77 |             isErrorSmbus[busID] = true; | 
 | 78 |         } | 
 | 79 |  | 
 | 80 |         nvmeData.present = false; | 
 | 81 |  | 
 | 82 |         return nvmeData.present; | 
 | 83 |     } | 
 | 84 |  | 
 | 85 |     auto res_int = | 
 | 86 |         smbus.SendSmbusRWBlockCmdRAW(busID, NVME_SSD_SLAVE_ADDRESS, &tx_data, | 
 | 87 |                                      sizeof(tx_data), rsp_data_command_0); | 
 | 88 |  | 
 | 89 |     if (res_int < 0) | 
 | 90 |     { | 
 | 91 |         if (isErrorSmbus[busID] != true) | 
 | 92 |         { | 
 | 93 |             log<level::ERR>("Send command code 0 fail!"); | 
 | 94 |             isErrorSmbus[busID] = true; | 
 | 95 |         } | 
 | 96 |  | 
 | 97 |         smbus.smbusClose(busID); | 
 | 98 |         nvmeData.present = false; | 
 | 99 |         return nvmeData.present; | 
 | 100 |     } | 
 | 101 |  | 
 | 102 |     tx_data = COMMAND_CODE_8; | 
 | 103 |  | 
 | 104 |     res_int = | 
 | 105 |         smbus.SendSmbusRWBlockCmdRAW(busID, NVME_SSD_SLAVE_ADDRESS, &tx_data, | 
 | 106 |                                      sizeof(tx_data), rsp_data_command_8); | 
 | 107 |  | 
 | 108 |     if (res_int < 0) | 
 | 109 |     { | 
 | 110 |         if (isErrorSmbus[busID] != true) | 
 | 111 |         { | 
 | 112 |             log<level::ERR>("Send command code 8 fail!"); | 
 | 113 |             isErrorSmbus[busID] = true; | 
 | 114 |         } | 
 | 115 |  | 
 | 116 |         smbus.smbusClose(busID); | 
 | 117 |         nvmeData.present = false; | 
 | 118 |         return nvmeData.present; | 
 | 119 |     } | 
 | 120 |  | 
 | 121 |     nvmeData.vendor = | 
 | 122 |         intToHex(rsp_data_command_8[1]) + " " + intToHex(rsp_data_command_8[2]); | 
 | 123 |  | 
 | 124 |     for (int offset = SERIALNUMBER_START_INDEX; offset < SERIALNUMBER_END_INDEX; | 
 | 125 |          offset++) | 
 | 126 |     { | 
 | 127 |         nvmeData.serialNumber += static_cast<char>(rsp_data_command_8[offset]); | 
 | 128 |     } | 
 | 129 |  | 
 | 130 |     nvmeData.statusFlags = intToHex(rsp_data_command_0[1]); | 
 | 131 |     nvmeData.smartWarnings = intToHex(rsp_data_command_0[2]); | 
 | 132 |     nvmeData.driveLifeUsed = intToHex(rsp_data_command_0[4]); | 
 | 133 |     nvmeData.sensorValue = (int8_t)rsp_data_command_0[3]; | 
 | 134 |  | 
 | 135 |     smbus.smbusClose(busID); | 
 | 136 |  | 
 | 137 |     isErrorSmbus[busID] = false; | 
 | 138 |  | 
 | 139 |     return nvmeData.present; | 
 | 140 | } | 
 | 141 |  | 
| Tony Lee | 84d430c | 2019-06-13 15:26:15 +0800 | [diff] [blame] | 142 | void Nvme::run() | 
 | 143 | { | 
| Tony Lee | 84d430c | 2019-06-13 15:26:15 +0800 | [diff] [blame] | 144 |     std::function<void()> callback(std::bind(&Nvme::read, this)); | 
 | 145 |     try | 
 | 146 |     { | 
 | 147 |         u_int64_t interval = MONITOR_INTERVAL_SECONDS * 1000000; | 
 | 148 |         _timer.restart(std::chrono::microseconds(interval)); | 
 | 149 |     } | 
 | 150 |     catch (const std::exception& e) | 
 | 151 |     { | 
 | 152 |         log<level::ERR>("Error in polling loop. "), | 
 | 153 |             entry("ERROR = %s", e.what()); | 
 | 154 |     } | 
 | 155 | } | 
 | 156 |  | 
| Tony Lee | 6c59501 | 2019-06-19 10:54:59 +0800 | [diff] [blame^] | 157 | /** @brief Parsing NVMe config JSON file  */ | 
 | 158 | Json parseSensorConfig() | 
| Tony Lee | 84d430c | 2019-06-13 15:26:15 +0800 | [diff] [blame] | 159 | { | 
| Tony Lee | 6c59501 | 2019-06-19 10:54:59 +0800 | [diff] [blame^] | 160 |     std::ifstream jsonFile(configFile); | 
 | 161 |     if (!jsonFile.is_open()) | 
 | 162 |     { | 
 | 163 |         log<level::ERR>("NVMe config JSON file not found"); | 
 | 164 |     } | 
 | 165 |  | 
 | 166 |     auto data = Json::parse(jsonFile, nullptr, false); | 
 | 167 |     if (data.is_discarded()) | 
 | 168 |     { | 
 | 169 |         log<level::ERR>("NVMe config readings JSON parser failure"); | 
 | 170 |     } | 
 | 171 |  | 
 | 172 |     return data; | 
| Tony Lee | 84d430c | 2019-06-13 15:26:15 +0800 | [diff] [blame] | 173 | } | 
 | 174 |  | 
| Tony Lee | 6c59501 | 2019-06-19 10:54:59 +0800 | [diff] [blame^] | 175 | /** @brief Obtain the initial configuration value of NVMe  */ | 
 | 176 | std::vector<phosphor::nvme::Nvme::NVMeConfig> Nvme::getNvmeConfig() | 
 | 177 | { | 
 | 178 |  | 
 | 179 |     phosphor::nvme::Nvme::NVMeConfig nvmeConfig; | 
 | 180 |     std::vector<phosphor::nvme::Nvme::NVMeConfig> nvmeConfigs; | 
 | 181 |     int8_t criticalHigh = 0; | 
 | 182 |     int8_t criticalLow = 0; | 
 | 183 |     int8_t maxValue = 0; | 
 | 184 |     int8_t minValue = 0; | 
 | 185 |     int8_t warningHigh = 0; | 
 | 186 |     int8_t warningLow = 0; | 
 | 187 |  | 
 | 188 |     try | 
 | 189 |     { | 
 | 190 |         auto data = parseSensorConfig(); | 
 | 191 |         static const std::vector<Json> empty{}; | 
 | 192 |         std::vector<Json> readings = data.value("config", empty); | 
 | 193 |         std::vector<Json> thresholds = data.value("threshold", empty); | 
 | 194 |         if (!thresholds.empty()) | 
 | 195 |         { | 
 | 196 |             for (const auto& instance : thresholds) | 
 | 197 |             { | 
 | 198 |                 criticalHigh = instance.value("criticalHigh", 0); | 
 | 199 |                 criticalLow = instance.value("criticalLow", 0); | 
 | 200 |                 maxValue = instance.value("maxValue", 0); | 
 | 201 |                 minValue = instance.value("minValue", 0); | 
 | 202 |                 warningHigh = instance.value("warningHigh", 0); | 
 | 203 |                 warningLow = instance.value("warningLow", 0); | 
 | 204 |             } | 
 | 205 |         } | 
 | 206 |         else | 
 | 207 |         { | 
 | 208 |             log<level::ERR>( | 
 | 209 |                 "Invalid NVMe config file, thresholds dosen't exist"); | 
 | 210 |         } | 
 | 211 |  | 
 | 212 |         if (!readings.empty()) | 
 | 213 |         { | 
 | 214 |             for (const auto& instance : readings) | 
 | 215 |             { | 
 | 216 |                 uint8_t index = instance.value("NVMeDriveIndex", 0); | 
 | 217 |                 uint8_t busID = instance.value("NVMeDriveBusID", 0); | 
 | 218 |                 uint8_t presentPin = instance.value("NVMeDrivePresentPin", 0); | 
 | 219 |                 uint8_t pwrGoodPin = instance.value("NVMeDrivePwrGoodPin", 0); | 
 | 220 |  | 
 | 221 |                 nvmeConfig.index = std::to_string(index); | 
 | 222 |                 nvmeConfig.busID = busID; | 
 | 223 |                 nvmeConfig.presentPin = presentPin; | 
 | 224 |                 nvmeConfig.pwrGoodPin = pwrGoodPin; | 
 | 225 |                 nvmeConfig.criticalHigh = criticalHigh; | 
 | 226 |                 nvmeConfig.criticalLow = criticalLow; | 
 | 227 |                 nvmeConfig.warningHigh = warningHigh; | 
 | 228 |                 nvmeConfig.warningLow = warningLow; | 
 | 229 |                 nvmeConfig.maxValue = maxValue; | 
 | 230 |                 nvmeConfig.minValue = minValue; | 
 | 231 |                 nvmeConfigs.push_back(nvmeConfig); | 
 | 232 |             } | 
 | 233 |         } | 
 | 234 |         else | 
 | 235 |         { | 
 | 236 |             log<level::ERR>("Invalid NVMe config file, config dosen't exist"); | 
 | 237 |         } | 
 | 238 |     } | 
 | 239 |     catch (const Json::exception& e) | 
 | 240 |     { | 
 | 241 |         log<level::ERR>("Json Exception caught."), entry("MSG: %s", e.what()); | 
 | 242 |     } | 
 | 243 |  | 
 | 244 |     return nvmeConfigs; | 
 | 245 | } | 
 | 246 |  | 
 | 247 | std::string Nvme::getGPIOValueOfNvme(const std::string& fullPath) | 
 | 248 | { | 
 | 249 |     std::string val; | 
 | 250 |     std::ifstream ifs; | 
 | 251 |     auto retries = 3; | 
 | 252 |  | 
 | 253 |     while (retries != 0) | 
 | 254 |     { | 
 | 255 |         try | 
 | 256 |         { | 
 | 257 |             if (!ifs.is_open()) | 
 | 258 |                 ifs.open(fullPath); | 
 | 259 |             ifs.clear(); | 
 | 260 |             ifs.seekg(0); | 
 | 261 |             ifs >> val; | 
 | 262 |         } | 
 | 263 |         catch (const std::exception& e) | 
 | 264 |         { | 
 | 265 |             --retries; | 
 | 266 |             std::this_thread::sleep_for(delay); | 
 | 267 |             log<level::ERR>("Can not open gpio path.", | 
 | 268 |                             entry("MSG: %s", e.what())); | 
 | 269 |             continue; | 
 | 270 |         } | 
 | 271 |         break; | 
 | 272 |     } | 
 | 273 |  | 
 | 274 |     ifs.close(); | 
 | 275 |     return val; | 
 | 276 | } | 
 | 277 |  | 
 | 278 | /** @brief Monitor NVMe drives every one second  */ | 
| Tony Lee | 84d430c | 2019-06-13 15:26:15 +0800 | [diff] [blame] | 279 | void Nvme::read() | 
 | 280 | { | 
| Tony Lee | 6c59501 | 2019-06-19 10:54:59 +0800 | [diff] [blame^] | 281 |     std::string devPresentPath; | 
 | 282 |     std::string devPwrGoodPath; | 
 | 283 |  | 
 | 284 |     static std::unordered_map<std::string, bool> isErrorPower; | 
 | 285 |  | 
 | 286 |     for (auto config : configs) | 
 | 287 |     { | 
 | 288 |         NVMeData nvmeData; | 
 | 289 |         devPresentPath = | 
 | 290 |             GPIO_BASE_PATH + std::to_string(config.presentPin) + "/value"; | 
 | 291 |  | 
 | 292 |         devPwrGoodPath = | 
 | 293 |             GPIO_BASE_PATH + std::to_string(config.pwrGoodPin) + "/value"; | 
 | 294 |  | 
 | 295 |         auto iter = nvmes.find(config.index); | 
 | 296 |  | 
 | 297 |         if (getGPIOValueOfNvme(devPresentPath) == IS_PRESENT) | 
 | 298 |         { | 
 | 299 |             // Drive status is good, update value or create d-bus and update | 
 | 300 |             // value. | 
 | 301 |             if (getGPIOValueOfNvme(devPwrGoodPath) == POWERGD) | 
 | 302 |             { | 
 | 303 |                 // get NVMe information through i2c by busID. | 
 | 304 |                 getNVMeInfobyBusID(config.busID, nvmeData); | 
 | 305 |                 // can not find. create dbus | 
 | 306 |                 if (iter == nvmes.end()) | 
 | 307 |                 { | 
 | 308 |                     log<level::INFO>("SSD plug.", | 
 | 309 |                                      entry("index = %s", config.index.c_str())); | 
 | 310 |  | 
 | 311 |                     std::string objPath = NVME_OBJ_PATH + config.index; | 
 | 312 |                     auto nvmeSSD = std::make_shared<phosphor::nvme::NvmeSSD>( | 
 | 313 |                         bus, objPath.c_str()); | 
 | 314 |                     nvmes.emplace(config.index, nvmeSSD); | 
 | 315 |  | 
 | 316 |                     nvmeSSD->setSensorValueToDbus(nvmeData.sensorValue); | 
 | 317 |                     nvmeSSD->setSensorThreshold( | 
 | 318 |                         config.criticalHigh, config.criticalLow, | 
 | 319 |                         config.maxValue, config.minValue, config.warningHigh, | 
 | 320 |                         config.warningLow); | 
 | 321 |  | 
 | 322 |                     nvmeSSD->checkSensorThreshold(); | 
 | 323 |                 } | 
 | 324 |                 else | 
 | 325 |                 { | 
 | 326 |                     iter->second->setSensorValueToDbus(nvmeData.sensorValue); | 
 | 327 |                     iter->second->checkSensorThreshold(); | 
 | 328 |                 } | 
 | 329 |                 isErrorPower[config.index] = false; | 
 | 330 |             } | 
 | 331 |             else | 
 | 332 |             { | 
 | 333 |                 // Present pin is true but power good pin is false | 
 | 334 |                 // remove nvme d-bus path | 
 | 335 |                 nvmeData = NVMeData(); | 
 | 336 |                 nvmes.erase(config.index); | 
 | 337 |  | 
 | 338 |                 if (isErrorPower[config.index] != true) | 
 | 339 |                 { | 
 | 340 |                     log<level::ERR>( | 
 | 341 |                         "Present pin is true but power good pin is false.", | 
 | 342 |                         entry("index = %s", config.index.c_str())); | 
 | 343 |                     log<level::ERR>("Erase SSD from map and d-bus.", | 
 | 344 |                                     entry("index = %s", config.index.c_str())); | 
 | 345 |  | 
 | 346 |                     isErrorPower[config.index] = true; | 
 | 347 |                 } | 
 | 348 |             } | 
 | 349 |         } | 
 | 350 |         else | 
 | 351 |         { | 
 | 352 |             // Drive not present, remove nvme d-bus path | 
 | 353 |             nvmeData = NVMeData(); | 
 | 354 |             nvmes.erase(config.index); | 
 | 355 |         } | 
 | 356 |     } | 
| Tony Lee | 84d430c | 2019-06-13 15:26:15 +0800 | [diff] [blame] | 357 | } | 
 | 358 | } // namespace nvme | 
 | 359 | } // namespace phosphor |