blob: 639f85dcd15a34a541358e475623211323cfed05 [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 Lee6c595012019-06-19 10:54:59 +080010#include <sstream>
11#include <string>
Tony Lee84d430c2019-06-13 15:26:15 +080012
Tony Lee6c595012019-06-19 10:54:59 +080013#include "i2c.h"
Tony Lee84d430c2019-06-13 15:26:15 +080014#define MONITOR_INTERVAL_SECONDS 1
Tony Lee6c595012019-06-19 10:54:59 +080015#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
20static constexpr auto configFile = "/etc/nvme/nvme_config.json";
21static constexpr auto delay = std::chrono::milliseconds{100};
22using Json = nlohmann::json;
23
24static constexpr const uint8_t COMMAND_CODE_0 = 0;
25static constexpr const uint8_t COMMAND_CODE_8 = 8;
26
27static constexpr int SERIALNUMBER_START_INDEX = 3;
28static constexpr int SERIALNUMBER_END_INDEX = 23;
29
30static constexpr const int TEMPERATURE_SENSOR_FAILURE = 0x81;
31
32namespace fs = std::filesystem;
33
Tony Lee84d430c2019-06-13 15:26:15 +080034namespace phosphor
35{
36namespace nvme
37{
38
39using namespace std;
40using namespace phosphor::logging;
41
Tony Lee6c595012019-06-19 10:54:59 +080042std::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 */
51bool 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 Lee84d430c2019-06-13 15:26:15 +0800142void Nvme::run()
143{
Tony Lee84d430c2019-06-13 15:26:15 +0800144 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 Lee6c595012019-06-19 10:54:59 +0800157/** @brief Parsing NVMe config JSON file */
158Json parseSensorConfig()
Tony Lee84d430c2019-06-13 15:26:15 +0800159{
Tony Lee6c595012019-06-19 10:54:59 +0800160 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 Lee84d430c2019-06-13 15:26:15 +0800173}
174
Tony Lee6c595012019-06-19 10:54:59 +0800175/** @brief Obtain the initial configuration value of NVMe */
176std::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
247std::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 Lee84d430c2019-06-13 15:26:15 +0800279void Nvme::read()
280{
Tony Lee6c595012019-06-19 10:54:59 +0800281 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 Lee84d430c2019-06-13 15:26:15 +0800357}
358} // namespace nvme
359} // namespace phosphor