blob: 0e0b7b7ac91bad402117b18bf1477b312a44a007 [file] [log] [blame]
Tony Lee84d430c2019-06-13 15:26:15 +08001#include "nvme_manager.hpp"
2
Tony Lee6c595012019-06-19 10:54:59 +08003#include "smbus.hpp"
4
5#include <filesystem>
6#include <map>
7#include <nlohmann/json.hpp>
Tony Lee84d430c2019-06-13 15:26:15 +08008#include <phosphor-logging/elog-errors.hpp>
9#include <phosphor-logging/log.hpp>
Tony Lee89659212019-06-21 17:34:14 +080010#include <sdbusplus/message.hpp>
Tony Lee6c595012019-06-19 10:54:59 +080011#include <sstream>
12#include <string>
Tony Lee89659212019-06-21 17:34:14 +080013#include <xyz/openbmc_project/Led/Physical/server.hpp>
Tony Lee84d430c2019-06-13 15:26:15 +080014
Tony Lee6c595012019-06-19 10:54:59 +080015#include "i2c.h"
Tony Lee84d430c2019-06-13 15:26:15 +080016#define MONITOR_INTERVAL_SECONDS 1
Tony Lee6c595012019-06-19 10:54:59 +080017#define NVME_SSD_SLAVE_ADDRESS 0x6a
18#define GPIO_BASE_PATH "/sys/class/gpio/gpio"
19#define IS_PRESENT "0"
20#define POWERGD "1"
Tony Lee89659212019-06-21 17:34:14 +080021#define NOWARNING_STRING "ff"
Tony Lee6c595012019-06-19 10:54:59 +080022
23static constexpr auto configFile = "/etc/nvme/nvme_config.json";
24static constexpr auto delay = std::chrono::milliseconds{100};
25using Json = nlohmann::json;
26
27static constexpr const uint8_t COMMAND_CODE_0 = 0;
28static constexpr const uint8_t COMMAND_CODE_8 = 8;
29
Tony Lee89659212019-06-21 17:34:14 +080030static constexpr int CapacityFaultMask = 1;
31static constexpr int temperatureFaultMask = 1 << 1;
32static constexpr int DegradesFaultMask = 1 << 2;
33static constexpr int MediaFaultMask = 1 << 3;
34static constexpr int BackupDeviceFaultMask = 1 << 4;
35static constexpr int NOWARNING = 255;
36
Tony Lee6c595012019-06-19 10:54:59 +080037static constexpr int SERIALNUMBER_START_INDEX = 3;
38static constexpr int SERIALNUMBER_END_INDEX = 23;
39
40static constexpr const int TEMPERATURE_SENSOR_FAILURE = 0x81;
41
42namespace fs = std::filesystem;
43
Tony Lee84d430c2019-06-13 15:26:15 +080044namespace phosphor
45{
46namespace nvme
47{
48
49using namespace std;
50using namespace phosphor::logging;
51
Tony Lee89659212019-06-21 17:34:14 +080052void Nvme::setNvmeInventoryProperties(
53 bool present, const phosphor::nvme::Nvme::NVMeData& nvmeData,
54 const std::string& inventoryPath)
55{
56 util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
57 ITEM_IFACE, "Present", present);
58 util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
59 ASSET_IFACE, "Manufacturer", nvmeData.vendor);
60 util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
61 ASSET_IFACE, "SerialNumber",
62 nvmeData.serialNumber);
63 util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
64 NVME_STATUS_IFACE, "SmartWarnings",
65 nvmeData.smartWarnings);
66 util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
67 NVME_STATUS_IFACE, "StatusFlags",
68 nvmeData.statusFlags);
69 util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
70 NVME_STATUS_IFACE, "DriveLifeUsed",
71 nvmeData.driveLifeUsed);
72
73 auto smartWarning = (!nvmeData.smartWarnings.empty())
74 ? std::stoi(nvmeData.smartWarnings, 0, 16)
75 : NOWARNING;
76
77 util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
78 NVME_STATUS_IFACE, "CapacityFault",
79 !(smartWarning & CapacityFaultMask));
80
81 util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
82 NVME_STATUS_IFACE, "TemperatureFault",
83 !(smartWarning & temperatureFaultMask));
84
85 util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
86 NVME_STATUS_IFACE, "DegradesFault",
87 !(smartWarning & DegradesFaultMask));
88
89 util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
90 NVME_STATUS_IFACE, "MediaFault",
91 !(smartWarning & MediaFaultMask));
92
93 util::SDBusPlus::setProperty(bus, INVENTORY_BUSNAME, inventoryPath,
94 NVME_STATUS_IFACE, "BackupDeviceFault",
95 !(smartWarning & BackupDeviceFaultMask));
96}
97
98void Nvme::setFaultLED(const std::string& locateLedGroupPath,
99 const std::string& faultLedGroupPath, bool request)
100{
101 if (locateLedGroupPath.empty() || faultLedGroupPath.empty())
102 {
103 return;
104 }
105
106 // Before toggle LED, check whether is Identify or not.
107 if (!getLEDGroupState(locateLedGroupPath))
108 {
109 util::SDBusPlus::setProperty(bus, LED_GROUP_BUSNAME, faultLedGroupPath,
110 LED_GROUP_IFACE, "Asserted", request);
111 }
112}
113
114void Nvme::setLocateLED(const std::string& locateLedGroupPath,
115 const std::string& locateLedBusName,
116 const std::string& locateLedPath, bool isPresent)
117{
118 if (locateLedGroupPath.empty() || locateLedBusName.empty() ||
119 locateLedPath.empty())
120 {
121 return;
122 }
123
124 namespace server = sdbusplus::xyz::openbmc_project::Led::server;
125
126 if (!getLEDGroupState(locateLedGroupPath))
127 {
128 if (isPresent)
129 util::SDBusPlus::setProperty(
130 bus, locateLedBusName, locateLedPath, LED_CONTROLLER_IFACE,
131 "State",
132 server::convertForMessage(server::Physical::Action::On));
133 else
134 util::SDBusPlus::setProperty(
135 bus, locateLedBusName, locateLedPath, LED_CONTROLLER_IFACE,
136 "State",
137 server::convertForMessage(server::Physical::Action::Off));
138 }
139}
140
141bool Nvme::getLEDGroupState(const std::string& ledPath)
142{
143 auto asserted = util::SDBusPlus::getProperty<bool>(
144 bus, LED_GROUP_BUSNAME, ledPath, LED_GROUP_IFACE, "Asserted");
145
146 return asserted;
147}
148
149void Nvme::setLEDsStatus(const phosphor::nvme::Nvme::NVMeConfig& config,
150 bool success,
151 const phosphor::nvme::Nvme::NVMeData& nvmeData)
152{
153 static std::unordered_map<std::string, bool> isError;
154
155 if (success)
156 {
157 if (!nvmeData.smartWarnings.empty())
158 {
159 auto request =
160 (strcmp(nvmeData.smartWarnings.c_str(), NOWARNING_STRING) == 0)
161 ? false
162 : true;
163
164 setFaultLED(config.locateLedGroupPath, config.faultLedGroupPath,
165 request);
166 setLocateLED(config.locateLedGroupPath,
167 config.locateLedControllerBusName,
168 config.locateLedControllerPath, !request);
169 }
170 isError[config.index] = false;
171 }
172 else
173 {
174 if (isError[config.index] != true)
175 {
176 // Drive is present but can not get data, turn on fault LED.
177 log<level::ERR>("Drive status is good but can not get data.",
178 entry("objPath = %s", config.index.c_str()));
179 isError[config.index] = true;
180 }
181
182 setFaultLED(config.locateLedGroupPath, config.faultLedGroupPath, true);
183 setLocateLED(config.locateLedGroupPath,
184 config.locateLedControllerBusName,
185 config.locateLedControllerPath, false);
186 }
187}
188
Tony Lee6c595012019-06-19 10:54:59 +0800189std::string intToHex(int input)
190{
191 std::stringstream tmp;
192 tmp << std::hex << input;
193
194 return tmp.str();
195}
196
197/** @brief Get NVMe info over smbus */
198bool getNVMeInfobyBusID(int busID, phosphor::nvme::Nvme::NVMeData& nvmeData)
199{
200 nvmeData.present = true;
201 nvmeData.vendor = "";
202 nvmeData.serialNumber = "";
203 nvmeData.smartWarnings = "";
204 nvmeData.statusFlags = "";
205 nvmeData.driveLifeUsed = "";
206 nvmeData.sensorValue = (int8_t)TEMPERATURE_SENSOR_FAILURE;
207
208 phosphor::smbus::Smbus smbus;
209
210 unsigned char rsp_data_command_0[I2C_DATA_MAX] = {0};
211 unsigned char rsp_data_command_8[I2C_DATA_MAX] = {0};
212
213 uint8_t tx_data = COMMAND_CODE_0;
214
215 auto init = smbus.smbusInit(busID);
216
217 static std::unordered_map<int, bool> isErrorSmbus;
218
219 if (init == -1)
220 {
221 if (isErrorSmbus[busID] != true)
222 {
223 log<level::ERR>("smbusInit fail!");
224 isErrorSmbus[busID] = true;
225 }
226
227 nvmeData.present = false;
228
229 return nvmeData.present;
230 }
231
232 auto res_int =
233 smbus.SendSmbusRWBlockCmdRAW(busID, NVME_SSD_SLAVE_ADDRESS, &tx_data,
234 sizeof(tx_data), rsp_data_command_0);
235
236 if (res_int < 0)
237 {
238 if (isErrorSmbus[busID] != true)
239 {
240 log<level::ERR>("Send command code 0 fail!");
241 isErrorSmbus[busID] = true;
242 }
243
244 smbus.smbusClose(busID);
245 nvmeData.present = false;
246 return nvmeData.present;
247 }
248
249 tx_data = COMMAND_CODE_8;
250
251 res_int =
252 smbus.SendSmbusRWBlockCmdRAW(busID, NVME_SSD_SLAVE_ADDRESS, &tx_data,
253 sizeof(tx_data), rsp_data_command_8);
254
255 if (res_int < 0)
256 {
257 if (isErrorSmbus[busID] != true)
258 {
259 log<level::ERR>("Send command code 8 fail!");
260 isErrorSmbus[busID] = true;
261 }
262
263 smbus.smbusClose(busID);
264 nvmeData.present = false;
265 return nvmeData.present;
266 }
267
268 nvmeData.vendor =
269 intToHex(rsp_data_command_8[1]) + " " + intToHex(rsp_data_command_8[2]);
270
271 for (int offset = SERIALNUMBER_START_INDEX; offset < SERIALNUMBER_END_INDEX;
272 offset++)
273 {
274 nvmeData.serialNumber += static_cast<char>(rsp_data_command_8[offset]);
275 }
276
277 nvmeData.statusFlags = intToHex(rsp_data_command_0[1]);
278 nvmeData.smartWarnings = intToHex(rsp_data_command_0[2]);
279 nvmeData.driveLifeUsed = intToHex(rsp_data_command_0[4]);
280 nvmeData.sensorValue = (int8_t)rsp_data_command_0[3];
281
282 smbus.smbusClose(busID);
283
284 isErrorSmbus[busID] = false;
285
286 return nvmeData.present;
287}
288
Tony Lee84d430c2019-06-13 15:26:15 +0800289void Nvme::run()
290{
Tony Lee89659212019-06-21 17:34:14 +0800291 init();
292
Tony Lee84d430c2019-06-13 15:26:15 +0800293 std::function<void()> callback(std::bind(&Nvme::read, this));
294 try
295 {
296 u_int64_t interval = MONITOR_INTERVAL_SECONDS * 1000000;
297 _timer.restart(std::chrono::microseconds(interval));
298 }
299 catch (const std::exception& e)
300 {
301 log<level::ERR>("Error in polling loop. "),
302 entry("ERROR = %s", e.what());
303 }
304}
305
Tony Lee6c595012019-06-19 10:54:59 +0800306/** @brief Parsing NVMe config JSON file */
307Json parseSensorConfig()
Tony Lee84d430c2019-06-13 15:26:15 +0800308{
Tony Lee6c595012019-06-19 10:54:59 +0800309 std::ifstream jsonFile(configFile);
310 if (!jsonFile.is_open())
311 {
312 log<level::ERR>("NVMe config JSON file not found");
313 }
314
315 auto data = Json::parse(jsonFile, nullptr, false);
316 if (data.is_discarded())
317 {
318 log<level::ERR>("NVMe config readings JSON parser failure");
319 }
320
321 return data;
Tony Lee84d430c2019-06-13 15:26:15 +0800322}
323
Tony Lee6c595012019-06-19 10:54:59 +0800324/** @brief Obtain the initial configuration value of NVMe */
325std::vector<phosphor::nvme::Nvme::NVMeConfig> Nvme::getNvmeConfig()
326{
327
328 phosphor::nvme::Nvme::NVMeConfig nvmeConfig;
329 std::vector<phosphor::nvme::Nvme::NVMeConfig> nvmeConfigs;
330 int8_t criticalHigh = 0;
331 int8_t criticalLow = 0;
332 int8_t maxValue = 0;
333 int8_t minValue = 0;
334 int8_t warningHigh = 0;
335 int8_t warningLow = 0;
336
337 try
338 {
339 auto data = parseSensorConfig();
340 static const std::vector<Json> empty{};
341 std::vector<Json> readings = data.value("config", empty);
342 std::vector<Json> thresholds = data.value("threshold", empty);
343 if (!thresholds.empty())
344 {
345 for (const auto& instance : thresholds)
346 {
347 criticalHigh = instance.value("criticalHigh", 0);
348 criticalLow = instance.value("criticalLow", 0);
349 maxValue = instance.value("maxValue", 0);
350 minValue = instance.value("minValue", 0);
351 warningHigh = instance.value("warningHigh", 0);
352 warningLow = instance.value("warningLow", 0);
353 }
354 }
355 else
356 {
357 log<level::ERR>(
358 "Invalid NVMe config file, thresholds dosen't exist");
359 }
360
361 if (!readings.empty())
362 {
363 for (const auto& instance : readings)
364 {
365 uint8_t index = instance.value("NVMeDriveIndex", 0);
366 uint8_t busID = instance.value("NVMeDriveBusID", 0);
Tony Lee89659212019-06-21 17:34:14 +0800367 std::string faultLedGroupPath =
368 instance.value("NVMeDriveFaultLEDGroupPath", "");
369 std::string locateLedGroupPath =
370 instance.value("NVMeDriveLocateLEDGroupPath", "");
Tony Lee6c595012019-06-19 10:54:59 +0800371 uint8_t presentPin = instance.value("NVMeDrivePresentPin", 0);
372 uint8_t pwrGoodPin = instance.value("NVMeDrivePwrGoodPin", 0);
Tony Lee89659212019-06-21 17:34:14 +0800373 std::string locateLedControllerBusName =
374 instance.value("NVMeDriveLocateLEDControllerBusName", "");
375 std::string locateLedControllerPath =
376 instance.value("NVMeDriveLocateLEDControllerPath", "");
Tony Lee6c595012019-06-19 10:54:59 +0800377
378 nvmeConfig.index = std::to_string(index);
379 nvmeConfig.busID = busID;
Tony Lee89659212019-06-21 17:34:14 +0800380 nvmeConfig.faultLedGroupPath = faultLedGroupPath;
Tony Lee6c595012019-06-19 10:54:59 +0800381 nvmeConfig.presentPin = presentPin;
382 nvmeConfig.pwrGoodPin = pwrGoodPin;
Tony Lee89659212019-06-21 17:34:14 +0800383 nvmeConfig.locateLedControllerBusName =
384 locateLedControllerBusName;
385 nvmeConfig.locateLedControllerPath = locateLedControllerPath;
386 nvmeConfig.locateLedGroupPath = locateLedGroupPath;
Tony Lee6c595012019-06-19 10:54:59 +0800387 nvmeConfig.criticalHigh = criticalHigh;
388 nvmeConfig.criticalLow = criticalLow;
389 nvmeConfig.warningHigh = warningHigh;
390 nvmeConfig.warningLow = warningLow;
391 nvmeConfig.maxValue = maxValue;
392 nvmeConfig.minValue = minValue;
393 nvmeConfigs.push_back(nvmeConfig);
394 }
395 }
396 else
397 {
398 log<level::ERR>("Invalid NVMe config file, config dosen't exist");
399 }
400 }
401 catch (const Json::exception& e)
402 {
403 log<level::ERR>("Json Exception caught."), entry("MSG: %s", e.what());
404 }
405
406 return nvmeConfigs;
407}
408
409std::string Nvme::getGPIOValueOfNvme(const std::string& fullPath)
410{
411 std::string val;
412 std::ifstream ifs;
413 auto retries = 3;
414
415 while (retries != 0)
416 {
417 try
418 {
419 if (!ifs.is_open())
420 ifs.open(fullPath);
421 ifs.clear();
422 ifs.seekg(0);
423 ifs >> val;
424 }
425 catch (const std::exception& e)
426 {
427 --retries;
428 std::this_thread::sleep_for(delay);
429 log<level::ERR>("Can not open gpio path.",
430 entry("MSG: %s", e.what()));
431 continue;
432 }
433 break;
434 }
435
436 ifs.close();
437 return val;
438}
439
Tony Lee89659212019-06-21 17:34:14 +0800440void Nvme::createNVMeInventory()
441{
442 using Properties =
443 std::map<std::string, sdbusplus::message::variant<std::string, bool>>;
444 using Interfaces = std::map<std::string, Properties>;
445
446 std::string inventoryPath;
447 std::map<sdbusplus::message::object_path, Interfaces> obj;
448
449 for (const auto config : configs)
450 {
451 inventoryPath = "/system/chassis/motherboard/nvme" + config.index;
452
453 obj = {{
454 inventoryPath,
455 {{ITEM_IFACE, {}}, {NVME_STATUS_IFACE, {}}, {ASSET_IFACE, {}}},
456 }};
457 util::SDBusPlus::CallMethod(bus, INVENTORY_BUSNAME, INVENTORY_NAMESPACE,
458 INVENTORY_MANAGER_IFACE, "Notify", obj);
459 }
460}
461
462void Nvme::init()
463{
464 createNVMeInventory();
465}
466
Tony Lee6c595012019-06-19 10:54:59 +0800467/** @brief Monitor NVMe drives every one second */
Tony Lee84d430c2019-06-13 15:26:15 +0800468void Nvme::read()
469{
Tony Lee6c595012019-06-19 10:54:59 +0800470 std::string devPresentPath;
471 std::string devPwrGoodPath;
Tony Lee89659212019-06-21 17:34:14 +0800472 std::string inventoryPath;
Tony Lee6c595012019-06-19 10:54:59 +0800473
474 static std::unordered_map<std::string, bool> isErrorPower;
475
476 for (auto config : configs)
477 {
478 NVMeData nvmeData;
479 devPresentPath =
480 GPIO_BASE_PATH + std::to_string(config.presentPin) + "/value";
481
482 devPwrGoodPath =
483 GPIO_BASE_PATH + std::to_string(config.pwrGoodPin) + "/value";
484
Tony Lee89659212019-06-21 17:34:14 +0800485 inventoryPath = NVME_INVENTORY_PATH + config.index;
486
Tony Lee6c595012019-06-19 10:54:59 +0800487 auto iter = nvmes.find(config.index);
488
489 if (getGPIOValueOfNvme(devPresentPath) == IS_PRESENT)
490 {
491 // Drive status is good, update value or create d-bus and update
492 // value.
493 if (getGPIOValueOfNvme(devPwrGoodPath) == POWERGD)
494 {
495 // get NVMe information through i2c by busID.
Tony Lee89659212019-06-21 17:34:14 +0800496 auto success = getNVMeInfobyBusID(config.busID, nvmeData);
Tony Lee6c595012019-06-19 10:54:59 +0800497 // can not find. create dbus
498 if (iter == nvmes.end())
499 {
500 log<level::INFO>("SSD plug.",
501 entry("index = %s", config.index.c_str()));
502
503 std::string objPath = NVME_OBJ_PATH + config.index;
504 auto nvmeSSD = std::make_shared<phosphor::nvme::NvmeSSD>(
505 bus, objPath.c_str());
506 nvmes.emplace(config.index, nvmeSSD);
507
Tony Lee89659212019-06-21 17:34:14 +0800508 setNvmeInventoryProperties(true, nvmeData, inventoryPath);
Tony Lee6c595012019-06-19 10:54:59 +0800509 nvmeSSD->setSensorValueToDbus(nvmeData.sensorValue);
510 nvmeSSD->setSensorThreshold(
511 config.criticalHigh, config.criticalLow,
512 config.maxValue, config.minValue, config.warningHigh,
513 config.warningLow);
514
515 nvmeSSD->checkSensorThreshold();
Tony Lee89659212019-06-21 17:34:14 +0800516 setLEDsStatus(config, success, nvmeData);
Tony Lee6c595012019-06-19 10:54:59 +0800517 }
518 else
519 {
Tony Lee89659212019-06-21 17:34:14 +0800520 setNvmeInventoryProperties(true, nvmeData, inventoryPath);
Tony Lee6c595012019-06-19 10:54:59 +0800521 iter->second->setSensorValueToDbus(nvmeData.sensorValue);
522 iter->second->checkSensorThreshold();
Tony Lee89659212019-06-21 17:34:14 +0800523 setLEDsStatus(config, success, nvmeData);
Tony Lee6c595012019-06-19 10:54:59 +0800524 }
Tony Lee89659212019-06-21 17:34:14 +0800525
Tony Lee6c595012019-06-19 10:54:59 +0800526 isErrorPower[config.index] = false;
527 }
528 else
529 {
530 // Present pin is true but power good pin is false
Tony Lee89659212019-06-21 17:34:14 +0800531 // remove nvme d-bus path, clean all properties in inventory
532 // and turn on fault LED
533
534 setFaultLED(config.locateLedGroupPath, config.faultLedGroupPath,
535 true);
536 setLocateLED(config.locateLedGroupPath,
537 config.locateLedControllerBusName,
538 config.locateLedControllerPath, false);
539
Tony Lee6c595012019-06-19 10:54:59 +0800540 nvmeData = NVMeData();
Tony Lee89659212019-06-21 17:34:14 +0800541 setNvmeInventoryProperties(false, nvmeData, inventoryPath);
Tony Lee6c595012019-06-19 10:54:59 +0800542 nvmes.erase(config.index);
543
544 if (isErrorPower[config.index] != true)
545 {
546 log<level::ERR>(
547 "Present pin is true but power good pin is false.",
548 entry("index = %s", config.index.c_str()));
549 log<level::ERR>("Erase SSD from map and d-bus.",
550 entry("index = %s", config.index.c_str()));
551
552 isErrorPower[config.index] = true;
553 }
554 }
555 }
556 else
557 {
Tony Lee89659212019-06-21 17:34:14 +0800558 // Drive not present, remove nvme d-bus path ,
559 // clean all properties in inventory
560 // and turn off fault and locate LED
561
562 setFaultLED(config.locateLedGroupPath, config.faultLedGroupPath,
563 false);
564 setLocateLED(config.locateLedGroupPath,
565 config.locateLedControllerBusName,
566 config.locateLedControllerPath, false);
567
Tony Lee6c595012019-06-19 10:54:59 +0800568 nvmeData = NVMeData();
Tony Lee89659212019-06-21 17:34:14 +0800569 setNvmeInventoryProperties(false, nvmeData, inventoryPath);
Tony Lee6c595012019-06-19 10:54:59 +0800570 nvmes.erase(config.index);
571 }
572 }
Tony Lee84d430c2019-06-13 15:26:15 +0800573}
574} // namespace nvme
575} // namespace phosphor