blob: 46b2b5213c5b079517ee0f4e5c8bcaeacb7d396c [file] [log] [blame]
Nikhil Potadeb669b6b2019-03-13 10:52:21 -07001/*
2// Copyright (c) 2019 Intel 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
17#include "NVMeSensor.hpp"
18
19#include "NVMeDevice.hpp"
20
21#include <crc32c.h>
22#include <libmctp-smbus.h>
23
24#include <boost/algorithm/string/replace.hpp>
25#include <boost/asio/ip/tcp.hpp>
James Feist38fb5982020-05-28 10:09:54 -070026
Nikhil Potadeb669b6b2019-03-13 10:52:21 -070027#include <iostream>
28
29static constexpr double maxReading = 127;
30static constexpr double minReading = 0;
31
32static constexpr bool DEBUG = false;
33
34void rxMessage(uint8_t eid, void* data, void* msg, size_t len);
35
36namespace nvmeMCTP
37{
38struct mctp_binding_smbus* smbus = mctp_smbus_init();
39struct mctp* mctp = mctp_init();
40
41static boost::container::flat_map<int, int> inFds;
42static boost::container::flat_map<int, int> outFds;
43
44int getInFd(int rootBus)
45{
46 auto findBus = inFds.find(rootBus);
47 if (findBus != inFds.end())
48 {
49 return findBus->second;
50 }
51 int fd = mctp_smbus_open_in_bus(smbus, rootBus);
52 if (fd < 0)
53 {
54 std::cerr << "Error opening IN Bus " << rootBus << "\n";
55 }
56 inFds[rootBus] = fd;
57 return fd;
58}
59
60int getOutFd(int bus)
61{
62 auto findBus = outFds.find(bus);
63 if (findBus != outFds.end())
64 {
65 return findBus->second;
66 }
67 int fd = mctp_smbus_open_out_bus(smbus, bus);
68 if (fd < 0)
69 {
70 std::cerr << "Error opening Out Bus " << bus << "\n";
71 }
72 outFds[bus] = fd;
73 return fd;
74}
75
76// we don't close the outFd as multiple sensors could be sharing the fd, we need
77// to close the inFd as it can only be used on 1 socket at a time
78void closeInFd(int rootBus)
79{
80 auto findFd = inFds.find(rootBus);
81 if (findFd == inFds.end())
82 {
83 return;
84 }
85 close(findFd->second);
86 inFds.erase(rootBus);
87}
88
89int getRootBus(int inFd)
90{
91 // we assume that we won't have too many FDs, so looping is OK
92 for (const auto [root, fd] : inFds)
93 {
94 if (fd == inFd)
95 {
96 return root;
97 }
98 }
99
100 return -1;
101}
102
103void init()
104{
105 if (mctp == nullptr || smbus == nullptr)
106 {
107 throw std::runtime_error("Unable to init mctp");
108 }
109 mctp_smbus_register_bus(smbus, nvmeMCTP::mctp, 0);
110 mctp_set_rx_all(mctp, rxMessage, nullptr);
111}
112
113} // namespace nvmeMCTP
114
Nikhil Potadeb669b6b2019-03-13 10:52:21 -0700115void readResponse(const std::shared_ptr<NVMeContext>& nvmeDevice)
116{
117 nvmeDevice->nvmeSlaveSocket.async_wait(
118 boost::asio::ip::tcp::socket::wait_error,
119 [nvmeDevice](const boost::system::error_code errorCode) {
120 if (errorCode)
121 {
122 return;
123 }
124
125 mctp_smbus_set_in_fd(nvmeMCTP::smbus,
126 nvmeMCTP::getInFd(nvmeDevice->rootBus));
127
128 // through libmctp this will invoke rxMessage
129 mctp_smbus_read(nvmeMCTP::smbus);
130 });
131}
132
133int nvmeMessageTransmit(mctp& mctp, nvme_mi_msg_request& req)
134{
135 std::array<uint8_t, NVME_MI_MSG_BUFFER_SIZE> messageBuf = {};
136
137 req.header.flags |= NVME_MI_HDR_MESSAGE_TYPE_MI_COMMAND
138 << NVME_MI_HDR_FLAG_MSG_TYPE_SHIFT;
139 req.header.message_type =
140 NVME_MI_MESSAGE_TYPE | NVME_MI_MCTP_INTEGRITY_CHECK;
141
142 uint32_t integrity = 0;
143 size_t msgSize = NVME_MI_MSG_REQUEST_HEADER_SIZE + req.request_data_len +
144 sizeof(integrity);
145
146 if (sizeof(messageBuf) < msgSize)
147 {
148 return EXIT_FAILURE;
149 }
150
151 messageBuf[0] = req.header.message_type;
152 messageBuf[1] = req.header.flags;
153 // Reserved bytes 2-3
154
155 messageBuf[4] = req.header.opcode;
156 // reserved bytes 5-7
157 messageBuf[8] = req.header.dword0 & 0xff;
158 messageBuf[9] = (req.header.dword0 >> 8) & 0xff;
159 messageBuf[10] = (req.header.dword0 >> 16) & 0xff;
160 messageBuf[11] = (req.header.dword0 >> 24) & 0xff;
161
162 messageBuf[12] = req.header.dword1 & 0xff;
163 messageBuf[13] = (req.header.dword1 >> 8) & 0xff;
164 messageBuf[14] = (req.header.dword1 >> 16) & 0xff;
165 messageBuf[15] = (req.header.dword1 >> 24) & 0xff;
166
167 std::copy_n(req.request_data, req.request_data_len,
168 messageBuf.data() +
169 static_cast<uint8_t>(NVME_MI_MSG_REQUEST_HEADER_SIZE));
170
171 msgSize = NVME_MI_MSG_REQUEST_HEADER_SIZE + req.request_data_len;
172 integrity = crc32c(messageBuf.data(),
173 NVME_MI_MSG_REQUEST_HEADER_SIZE + req.request_data_len);
174 messageBuf[msgSize] = integrity & 0xff;
175 messageBuf[msgSize + 1] = (integrity >> 8) & 0xff;
176 messageBuf[msgSize + 2] = (integrity >> 16) & 0xff;
177 messageBuf[msgSize + 3] = (integrity >> 24) & 0xff;
178 msgSize += sizeof(integrity);
179
180 return mctp_message_tx(&mctp, 0, messageBuf.data(), msgSize);
181}
182
183int verifyIntegrity(uint8_t* msg, size_t len)
184{
185 uint32_t msgIntegrity = {0};
186 if (len < NVME_MI_MSG_RESPONSE_HEADER_SIZE + sizeof(msgIntegrity))
187 {
188 std::cerr << "Not enough bytes for nvme header and trailer\n";
189 return -1;
190 }
191
192 msgIntegrity = (msg[len - 4]) + (msg[len - 3] << 8) + (msg[len - 2] << 16) +
193 (msg[len - 1] << 24);
194
195 uint32_t calculateIntegrity = crc32c(msg, len - sizeof(msgIntegrity));
196 if (msgIntegrity != calculateIntegrity)
197 {
198 std::cerr << "CRC mismatch. Got=" << msgIntegrity
199 << " Expected=" << calculateIntegrity << "\n";
200 return -1;
201 }
202 return 0;
203}
204
205void readAndProcessNVMeSensor(const std::shared_ptr<NVMeContext>& nvmeDevice)
206{
207 struct nvme_mi_msg_request requestMsg = {};
208 requestMsg.header.opcode = NVME_MI_OPCODE_HEALTH_STATUS_POLL;
209 requestMsg.header.dword0 = 0;
210 requestMsg.header.dword1 = 0;
211
212 int mctpResponseTimeout = 1;
213
214 if (nvmeDevice->sensors.empty())
215 {
216 return;
217 }
218
219 std::shared_ptr<NVMeSensor>& sensor = nvmeDevice->sensors.front();
220
221 // setup the timeout timer
222 nvmeDevice->mctpResponseTimer.expires_from_now(
223 boost::posix_time::seconds(mctpResponseTimeout));
224
225 nvmeDevice->mctpResponseTimer.async_wait(
226 [sensor, nvmeDevice](const boost::system::error_code errorCode) {
Nikhil Potadeb669b6b2019-03-13 10:52:21 -0700227 if (errorCode)
228 {
James Feist961bf092020-07-01 16:38:12 -0700229 // timer cancelled successfully
Nikhil Potadeb669b6b2019-03-13 10:52:21 -0700230 return;
231 }
James Feist961bf092020-07-01 16:38:12 -0700232
233 sensor->incrementError();
Nikhil Potadeb669b6b2019-03-13 10:52:21 -0700234
235 // cycle it back
236 nvmeDevice->sensors.pop_front();
237 nvmeDevice->sensors.emplace_back(sensor);
238
239 nvmeDevice->nvmeSlaveSocket.cancel();
240 });
241
242 readResponse(nvmeDevice);
243
244 if (DEBUG)
245 {
246 std::cout << "Sending message to read data from Drive on bus: "
247 << sensor->bus << " , rootBus: " << nvmeDevice->rootBus
248 << " device: " << sensor->name << "\n";
249 }
250
251 mctp_smbus_set_out_fd(nvmeMCTP::smbus, nvmeMCTP::getOutFd(sensor->bus));
252 int rc = nvmeMessageTransmit(*nvmeMCTP::mctp, requestMsg);
253
254 if (rc != 0)
255 {
256 std::cerr << "Error sending request message to NVMe device\n";
257 }
258}
259
260static double getTemperatureReading(int8_t reading)
261{
262
263 if (reading == static_cast<int8_t>(0x80) ||
264 reading == static_cast<int8_t>(0x81))
265 {
266 // 0x80 = No temperature data or temperature data is more the 5 s
267 // old 0x81 = Temperature sensor failure
268 return maxReading;
269 }
270
271 return reading;
272}
273
274void rxMessage(uint8_t eid, void*, void* msg, size_t len)
275{
276 struct nvme_mi_msg_response_header header
James Feist38fb5982020-05-28 10:09:54 -0700277 {};
Nikhil Potadeb669b6b2019-03-13 10:52:21 -0700278
279 int inFd = mctp_smbus_get_in_fd(nvmeMCTP::smbus);
280 int rootBus = nvmeMCTP::getRootBus(inFd);
281
282 NVMEMap& nvmeMap = getNVMEMap();
283 auto findMap = nvmeMap.find(rootBus);
284 if (findMap == nvmeMap.end())
285 {
286 std::cerr << "Unable to lookup root bus " << rootBus << "\n";
287 return;
288 }
289 std::shared_ptr<NVMeContext>& self = findMap->second;
290
291 if (msg == nullptr)
292 {
293 std::cerr << "Bad message received\n";
294 return;
295 }
296
297 if (len <= 0)
298 {
299 std::cerr << "Received message not long enough\n";
300 return;
301 }
302
303 if (DEBUG)
304 {
305 std::cout << "Eid from the received messaged: " << eid << "\n";
306 }
307
308 uint8_t* messageData = static_cast<uint8_t*>(msg);
309
310 if ((*messageData & NVME_MI_MESSAGE_TYPE_MASK) != NVME_MI_MESSAGE_TYPE)
311 {
312 std::cerr << "Got unknown type message_type="
313 << (*messageData & NVME_MI_MESSAGE_TYPE_MASK) << "\n";
314 return;
315 }
316
317 if (len < NVME_MI_MSG_RESPONSE_HEADER_SIZE + sizeof(uint32_t))
318 {
319 std::cerr << "Not enough bytes for NVMe header and trailer\n";
320 return;
321 }
322
323 if (verifyIntegrity(messageData, len) != 0)
324 {
325 std::cerr << "Verification of message integrity failed\n";
326 return;
327 }
328
329 header.message_type = messageData[0];
330 header.flags = messageData[1];
331 header.status = messageData[4];
332
333 if (header.status == NVME_MI_HDR_STATUS_MORE_PROCESSING_REQUIRED)
334 {
335 return;
336 }
337
338 if (header.status != NVME_MI_HDR_STATUS_SUCCESS)
339 {
340 std::cerr << "Command failed with status= " << header.status << "\n";
341 return;
342 }
343
344 messageData += NVME_MI_MSG_RESPONSE_HEADER_SIZE;
345 size_t messageLength =
346 len - NVME_MI_MSG_RESPONSE_HEADER_SIZE - sizeof(uint32_t);
347 if (((header.flags >> NVME_MI_HDR_FLAG_MSG_TYPE_SHIFT) &
348 NVME_MI_HDR_FLAG_MSG_TYPE_MASK) != NVME_MI_HDR_MESSAGE_TYPE_MI_COMMAND)
349 {
350 std::cerr << "Not MI type comamnd\n";
351 return;
352 }
353
354 if (messageLength < NVME_MI_HEALTH_STATUS_POLL_MSG_MIN)
355 {
356 std::cerr << "Got improperly sized health status poll\n";
357 return;
358 }
359
360 std::shared_ptr<NVMeSensor> sensorInfo = self->sensors.front();
361 if (DEBUG)
362 {
363 std::cout << "Temperature Reading: "
364 << getTemperatureReading(messageData[5])
365 << " Celsius for device " << sensorInfo->name << "\n";
366 }
367
368 sensorInfo->updateValue(getTemperatureReading(messageData[5]));
369
370 if (DEBUG)
371 {
372 std::cout << "Cancelling the timer now\n";
373 }
374
375 // move to back of scan queue
376 self->sensors.pop_front();
377 self->sensors.emplace_back(sensorInfo);
378
379 self->mctpResponseTimer.cancel();
380}
381
382NVMeContext::NVMeContext(boost::asio::io_service& io, int rootBus) :
383 rootBus(rootBus), scanTimer(io), nvmeSlaveSocket(io), mctpResponseTimer(io)
384{
385 nvmeSlaveSocket.assign(boost::asio::ip::tcp::v4(),
386 nvmeMCTP::getInFd(rootBus));
387}
388
389void NVMeContext::pollNVMeDevices()
390{
391 scanTimer.expires_from_now(boost::posix_time::seconds(1));
392 scanTimer.async_wait(
393 [self{shared_from_this()}](const boost::system::error_code errorCode) {
394 if (errorCode == boost::asio::error::operation_aborted)
395 {
396 return; // we're being canceled
397 }
398 else if (errorCode)
399 {
400 std::cerr << "Error:" << errorCode.message() << "\n";
401 return;
402 }
403 else
404 {
405 readAndProcessNVMeSensor(self);
406 }
407
408 self->pollNVMeDevices();
409 });
410}
411
James Feist375ade22020-07-16 16:32:10 -0700412void NVMeContext::close()
Nikhil Potadeb669b6b2019-03-13 10:52:21 -0700413{
414 scanTimer.cancel();
415 mctpResponseTimer.cancel();
416 nvmeSlaveSocket.cancel();
417 nvmeMCTP::closeInFd(rootBus);
418}
419
James Feist375ade22020-07-16 16:32:10 -0700420NVMeContext::~NVMeContext()
421{
422 close();
423}
424
Nikhil Potadeb669b6b2019-03-13 10:52:21 -0700425NVMeSensor::NVMeSensor(sdbusplus::asio::object_server& objectServer,
James Feist7d7579f2020-09-02 14:13:08 -0700426 boost::asio::io_service&,
Nikhil Potadeb669b6b2019-03-13 10:52:21 -0700427 std::shared_ptr<sdbusplus::asio::connection>& conn,
428 const std::string& sensorName,
429 std::vector<thresholds::Threshold>&& _thresholds,
430 const std::string& sensorConfiguration,
431 const int busNumber) :
432 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
433 std::move(_thresholds), sensorConfiguration,
James Feist961bf092020-07-01 16:38:12 -0700434 "xyz.openbmc_project.Configuration.NVMe", maxReading, minReading,
James Feiste3338522020-09-15 15:40:30 -0700435 conn, PowerState::on),
James Feist961bf092020-07-01 16:38:12 -0700436 objServer(objectServer), bus(busNumber)
Nikhil Potadeb669b6b2019-03-13 10:52:21 -0700437{
438 sensorInterface = objectServer.add_interface(
439 "/xyz/openbmc_project/sensors/temperature/" + name,
440 "xyz.openbmc_project.Sensor.Value");
441
442 if (thresholds::hasWarningInterface(thresholds))
443 {
444 thresholdInterfaceWarning = objectServer.add_interface(
445 "/xyz/openbmc_project/sensors/temperature/" + name,
446 "xyz.openbmc_project.Sensor.Threshold.Warning");
447 }
448 if (thresholds::hasCriticalInterface(thresholds))
449 {
450 thresholdInterfaceCritical = objectServer.add_interface(
451 "/xyz/openbmc_project/sensors/temperature/" + name,
452 "xyz.openbmc_project.Sensor.Threshold.Critical");
453 }
454 association = objectServer.add_interface(
455 "/xyz/openbmc_project/sensors/temperature/" + name,
456 association::interface);
457
458 setInitialProperties(conn);
Nikhil Potadeb669b6b2019-03-13 10:52:21 -0700459}
460
461NVMeSensor::~NVMeSensor()
462{
463 // close the input dev to cancel async operations
464 objServer.remove_interface(thresholdInterfaceWarning);
465 objServer.remove_interface(thresholdInterfaceCritical);
466 objServer.remove_interface(sensorInterface);
467 objServer.remove_interface(association);
468}
469
470void NVMeSensor::checkThresholds(void)
471{
Nikhil Potadeb669b6b2019-03-13 10:52:21 -0700472 thresholds::checkThresholds(this);
473}