Enabling NVMe sensor support

This commit introduces the support for NVMe drives for sensors.

All the NVMe drives which are detected by the FRU manager and are
present in the inventory are scanned at regular interval for reading
the temperature values of the NVMe devices.

Tested:

NAME                                          TYPE      SIGNATURE RESULT/VALUE
org.freedesktop.DBus.Introspectable           interface -         -
.Introspect                                   method    -         s
org.freedesktop.DBus.Peer                     interface -         -
.GetMachineId                                 method    -         s
.Ping                                         method    -         -
org.freedesktop.DBus.Properties               interface -         -
.Get                                          method    ss        v
.GetAll                                       method    s         a{sv}
.Set                                          method    ssv       -
.PropertiesChanged                            signal    sa{sv}as  -
xyz.openbmc_project.Association.Definitions   interface -         -
.Associations                                 property  a(sss)    1 "chassis" "all_sensors" "/xyz/openb...
xyz.openbmc_project.Sensor.Threshold.Critical interface -         -
.CriticalAlarmHigh                            property  b         false
.CriticalAlarmLow                             property  b         false
.CriticalHigh                                 property  d         115
.CriticalLow                                  property  d         0
xyz.openbmc_project.Sensor.Threshold.Warning  interface -         -
.WarningAlarmHigh                             property  b         false
.WarningAlarmLow                              property  b         false
.WarningHigh                                  property  d         110
.WarningLow                                   property  d         5
xyz.openbmc_project.Sensor.Value              interface -         -
.MaxValue                                     property  d         127
.MinValue                                     property  d         -60
.Value                                        property  d         22

Change-Id: Icb119b424234d548c8ff5cda9c7a9517ec9696bb
Signed-off-by: Nikhil Potade <nikhil.potade@linux.intel.com>
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a28c613..645a1a8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -45,6 +45,7 @@
 option (DISABLE_IPMB "Disable installing IPMB sensor" OFF)
 option (DISABLE_MCUTEMP "Disable installing MCU temperature sensor" OFF)
 option (DISABLE_PSU "Disable installing PSU sensor" OFF)
+option (DISABLE_NVME "Disable installing NVME sensor" ON)
 
 include ("cmake/HunterGate.cmake")
 
@@ -74,10 +75,13 @@
 set (PSU_SRC_FILES src/Utils.cpp src/PSUSensor.cpp src/Thresholds.cpp
      src/PwmSensor.cpp src/PSUEvent.cpp)
 
+set (NVME_SRC_FILES src/Utils.cpp src/NVMeSensorMain.cpp src/NVMeSensor.cpp src/Thresholds.cpp)
+
 set (EXTERNAL_PACKAGES Boost sdbusplus-project nlohmann-json)
 set (SENSOR_LINK_LIBS -lsystemd stdc++fs sdbusplus)
 
 if (NOT YOCTO)
+    set (DISABLE_NVME ON) # todo allow this to build out of tree
     option (ENABLE_TEST "Enable Google Test" OFF)
 
     externalproject_add (
@@ -112,6 +116,7 @@
                          CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND
                          "" LOG_DOWNLOAD ON)
     include_directories (SYSTEM ${CMAKE_BINARY_DIR}/nlohmann-json-src/include)
+
     if (ENABLE_TEST)
         option (HUNTER_ENABLED "Enable hunter package pulling" ON)
         hunter_add_package (GTest)
@@ -190,6 +195,12 @@
 add_dependencies (psusensor sdbusplus-project)
 target_link_libraries (psusensor ${SENSOR_LINK_LIBS})
 
+if (NOT DISABLE_NVME)
+    add_executable (nvmesensor ${NVME_SRC_FILES})
+    add_dependencies (nvmesensor sdbusplus-project)
+    target_link_libraries (nvmesensor liblibmctp.a i2c ${SENSOR_LINK_LIBS})
+endif()
+
 if (NOT YOCTO)
     add_dependencies (adcsensor ${EXTERNAL_PACKAGES})
     add_dependencies (cpusensor ${EXTERNAL_PACKAGES})
@@ -273,3 +284,10 @@
                  ${SERVICE_FILE_SRC_DIR}/xyz.openbmc_project.psusensor.service
                  DESTINATION ${SERVICE_FILE_INSTALL_DIR})
 endif ()
+
+if (NOT DISABLE_NVME)
+    install (TARGETS nvmesensor DESTINATION bin)
+    install (FILES
+                 ${SERVICE_FILE_SRC_DIR}/xyz.openbmc_project.nvmesensor.service
+                 DESTINATION ${SERVICE_FILE_INSTALL_DIR})
+endif ()
diff --git a/include/NVMeDevice.hpp b/include/NVMeDevice.hpp
new file mode 100644
index 0000000..5ebb0b3
--- /dev/null
+++ b/include/NVMeDevice.hpp
@@ -0,0 +1,99 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+
+// NVM Express Management Interface 1.0 section 3.2.1
+const uint8_t NVME_MI_MESSAGE_TYPE = 0x04;
+
+const uint8_t NVME_MI_MESSAGE_TYPE_MASK = 0x7F;
+
+// Indicates this is covered by an MCTP integrity check
+const uint8_t NVME_MI_MCTP_INTEGRITY_CHECK = (1 << 7);
+
+// Indicates whether this is a request or response
+const uint8_t NVME_MI_HDR_FLAG_ROR = (1 << 7);
+
+const uint8_t NVME_MI_HDR_FLAG_MSG_TYPE_MASK = 0x0F;
+const uint8_t NVME_MI_HDR_FLAG_MSG_TYPE_SHIFT = 3;
+
+const uint16_t NVME_MI_MSG_BUFFER_SIZE = 256;
+
+// Minimum length of health status poll response
+// NMH + Status + NVMe-MI Command Response Message (NCRESP)
+const uint8_t NVME_MI_HEALTH_STATUS_POLL_MSG_MIN = 8;
+
+enum NVME_MI_HDR_MESSAGE_TYPE
+{
+    NVME_MI_HDR_MESSAGE_TYPE_CONTROL_PRIMITIVE = 0x00,
+    NVME_MI_HDR_MESSAGE_TYPE_MI_COMMAND = 0x01,
+    NVME_MI_HDR_MESSAGE_TYPE_MI_ADMIN_COMMAND = 0x02,
+    NVME_MI_HDR_MESSAGE_TYPE_PCIE_COMMAND = 0x04,
+};
+
+enum NVME_MI_HDR_COMMAND_SLOT
+{
+    NVME_MI_HDR_COMMAND_SLOT_0 = 0x00,
+    NVME_MI_HDR_COMMAND_SLOT_1 = 0x01,
+};
+
+enum NVME_MI_HDR_STATUS
+{
+    NVME_MI_HDR_STATUS_SUCCESS = 0x00,
+    NVME_MI_HDR_STATUS_MORE_PROCESSING_REQUIRED = 0x01,
+    NVME_MI_HDR_STATUS_INTERNAL_ERROR = 0x02,
+    NVME_MI_HDR_STATUS_INVALID_COMMAND_OPCODE = 0x03,
+    NVME_MI_HDR_STATUS_INVALID_PARAMETER = 0x04,
+    NVME_MI_HDR_STATUS_INVALID_COMMAND_SIZE = 0x05,
+    NVME_MI_HDR_STATUS_INVALID_COMMAND_INPUT_DATA_SIZE = 0x06,
+    NVME_MI_HDR_STATUS_ACCESS_DENIED = 0x07,
+    NVME_MI_HDR_STATUS_VPD_UPDATES_EXCEEDED = 0x20,
+    NVME_MI_HDR_STATUS_PCIE_INACCESSIBLE = 0x21,
+};
+
+enum NVME_MI_OPCODE
+{
+    NVME_MI_OPCODE_READ_MI_DATA = 0x00,
+    NVME_MI_OPCODE_HEALTH_STATUS_POLL = 0x01,
+    NVME_MI_OPCODE_CONTROLLER_HEALTH_STATUS_POLL = 0x02,
+    NVME_MI_OPCODE_CONFIGURATION_GET = 0x03,
+    NVME_MI_OPCODE_CONFIGURATION_SET = 0x04,
+    NVME_MI_OPCODE_VPD_READ = 0x05,
+    NVME_MI_OPCODE_VPD_WRITE = 0x06,
+    NVME_MI_OPCODE_RESET = 0x07,
+};
+
+const uint8_t NVME_MI_MSG_REQUEST_HEADER_SIZE = 16;
+struct nvme_mi_msg_request_header
+{
+    uint8_t message_type;
+    uint8_t flags;
+    uint8_t opcode;
+    uint32_t dword0;
+    uint32_t dword1;
+};
+
+struct nvme_mi_msg_request
+{
+    struct nvme_mi_msg_request_header header;
+    uint8_t request_data[128];
+    size_t request_data_len;
+};
+
+const uint8_t NVME_MI_MSG_RESPONSE_HEADER_SIZE = 5;
+struct nvme_mi_msg_response_header
+{
+    uint8_t message_type;
+    uint8_t flags;
+    // Reserved bytes 2:3
+    uint8_t status;
+};
+
+struct nvme_mi_controller_health
+{
+    uint8_t nvm_subsystem_status;
+    uint8_t smart_warnings;
+    uint8_t composite_temperature;
+    uint8_t percent_used;
+    uint16_t composite_controller_status;
+};
\ No newline at end of file
diff --git a/include/NVMeSensor.hpp b/include/NVMeSensor.hpp
new file mode 100644
index 0000000..ec0bfd1
--- /dev/null
+++ b/include/NVMeSensor.hpp
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <libmctp-smbus.h>
+#include <libmctp.h>
+
+#include <sensor.hpp>
+
+class NVMeSensor : public Sensor
+{
+  public:
+    NVMeSensor(sdbusplus::asio::object_server& objectServer,
+               boost::asio::io_service& io,
+               std::shared_ptr<sdbusplus::asio::connection>& conn,
+               const std::string& sensorName,
+               std::vector<thresholds::Threshold>&& _thresholds,
+               const std::string& sensorConfiguration, const int busNumber);
+    virtual ~NVMeSensor();
+
+    NVMeSensor& operator=(const NVMeSensor& other) = delete;
+
+    size_t errorCount;
+    int bus;
+
+  private:
+    sdbusplus::asio::object_server& objServer;
+
+    void checkThresholds(void) override;
+};
+
+struct NVMeContext : std::enable_shared_from_this<NVMeContext>
+{
+    NVMeContext(boost::asio::io_service& io, int rootBus);
+
+    virtual ~NVMeContext();
+
+    void pollNVMeDevices();
+
+    boost::asio::deadline_timer scanTimer;
+    int rootBus; // Root bus for this drive
+    boost::asio::deadline_timer mctpResponseTimer;
+    boost::asio::ip::tcp::socket nvmeSlaveSocket;
+    std::list<std::shared_ptr<NVMeSensor>> sensors; // used as a poll queue
+};
+
+using NVMEMap = boost::container::flat_map<int, std::shared_ptr<NVMeContext>>;
+
+int verifyIntegrity(uint8_t* msg, size_t len);
+
+namespace nvmeMCTP
+{
+void init(void);
+}
+
+NVMEMap& getNVMEMap(void);
diff --git a/service_files/xyz.openbmc_project.nvmesensor.service b/service_files/xyz.openbmc_project.nvmesensor.service
new file mode 100644
index 0000000..a5c8725
--- /dev/null
+++ b/service_files/xyz.openbmc_project.nvmesensor.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=NVMe Sensor
+StopWhenUnneeded=false
+After=xyz.openbmc_project.FruDevice.service
+
+[Service]
+Restart=always
+RestartSec=5
+ExecStart=/usr/bin/nvmesensor
+
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/src/NVMeSensor.cpp b/src/NVMeSensor.cpp
new file mode 100644
index 0000000..be99f58
--- /dev/null
+++ b/src/NVMeSensor.cpp
@@ -0,0 +1,478 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "NVMeSensor.hpp"
+
+#include "NVMeDevice.hpp"
+
+#include <crc32c.h>
+#include <libmctp-smbus.h>
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <iostream>
+
+static constexpr double maxReading = 127;
+static constexpr double minReading = 0;
+
+static constexpr bool DEBUG = false;
+
+void rxMessage(uint8_t eid, void* data, void* msg, size_t len);
+
+namespace nvmeMCTP
+{
+struct mctp_binding_smbus* smbus = mctp_smbus_init();
+struct mctp* mctp = mctp_init();
+
+static boost::container::flat_map<int, int> inFds;
+static boost::container::flat_map<int, int> outFds;
+
+int getInFd(int rootBus)
+{
+    auto findBus = inFds.find(rootBus);
+    if (findBus != inFds.end())
+    {
+        return findBus->second;
+    }
+    int fd = mctp_smbus_open_in_bus(smbus, rootBus);
+    if (fd < 0)
+    {
+        std::cerr << "Error opening IN Bus " << rootBus << "\n";
+    }
+    inFds[rootBus] = fd;
+    return fd;
+}
+
+int getOutFd(int bus)
+{
+    auto findBus = outFds.find(bus);
+    if (findBus != outFds.end())
+    {
+        return findBus->second;
+    }
+    int fd = mctp_smbus_open_out_bus(smbus, bus);
+    if (fd < 0)
+    {
+        std::cerr << "Error opening Out Bus " << bus << "\n";
+    }
+    outFds[bus] = fd;
+    return fd;
+}
+
+// we don't close the outFd as multiple sensors could be sharing the fd, we need
+// to close the inFd as it can only be used on 1 socket at a time
+void closeInFd(int rootBus)
+{
+    auto findFd = inFds.find(rootBus);
+    if (findFd == inFds.end())
+    {
+        return;
+    }
+    close(findFd->second);
+    inFds.erase(rootBus);
+}
+
+int getRootBus(int inFd)
+{
+    // we assume that we won't have too many FDs, so looping is OK
+    for (const auto [root, fd] : inFds)
+    {
+        if (fd == inFd)
+        {
+            return root;
+        }
+    }
+
+    return -1;
+}
+
+void init()
+{
+    if (mctp == nullptr || smbus == nullptr)
+    {
+        throw std::runtime_error("Unable to init mctp");
+    }
+    mctp_smbus_register_bus(smbus, nvmeMCTP::mctp, 0);
+    mctp_set_rx_all(mctp, rxMessage, nullptr);
+}
+
+} // namespace nvmeMCTP
+
+static int lastQueriedDeviceIndex = -1;
+
+void readResponse(const std::shared_ptr<NVMeContext>& nvmeDevice)
+{
+    nvmeDevice->nvmeSlaveSocket.async_wait(
+        boost::asio::ip::tcp::socket::wait_error,
+        [nvmeDevice](const boost::system::error_code errorCode) {
+            if (errorCode)
+            {
+                return;
+            }
+
+            mctp_smbus_set_in_fd(nvmeMCTP::smbus,
+                                 nvmeMCTP::getInFd(nvmeDevice->rootBus));
+
+            // through libmctp this will invoke rxMessage
+            mctp_smbus_read(nvmeMCTP::smbus);
+        });
+}
+
+int nvmeMessageTransmit(mctp& mctp, nvme_mi_msg_request& req)
+{
+    std::array<uint8_t, NVME_MI_MSG_BUFFER_SIZE> messageBuf = {};
+
+    req.header.flags |= NVME_MI_HDR_MESSAGE_TYPE_MI_COMMAND
+                        << NVME_MI_HDR_FLAG_MSG_TYPE_SHIFT;
+    req.header.message_type =
+        NVME_MI_MESSAGE_TYPE | NVME_MI_MCTP_INTEGRITY_CHECK;
+
+    uint32_t integrity = 0;
+    size_t msgSize = NVME_MI_MSG_REQUEST_HEADER_SIZE + req.request_data_len +
+                     sizeof(integrity);
+
+    if (sizeof(messageBuf) < msgSize)
+    {
+        return EXIT_FAILURE;
+    }
+
+    messageBuf[0] = req.header.message_type;
+    messageBuf[1] = req.header.flags;
+    // Reserved bytes 2-3
+
+    messageBuf[4] = req.header.opcode;
+    // reserved bytes 5-7
+    messageBuf[8] = req.header.dword0 & 0xff;
+    messageBuf[9] = (req.header.dword0 >> 8) & 0xff;
+    messageBuf[10] = (req.header.dword0 >> 16) & 0xff;
+    messageBuf[11] = (req.header.dword0 >> 24) & 0xff;
+
+    messageBuf[12] = req.header.dword1 & 0xff;
+    messageBuf[13] = (req.header.dword1 >> 8) & 0xff;
+    messageBuf[14] = (req.header.dword1 >> 16) & 0xff;
+    messageBuf[15] = (req.header.dword1 >> 24) & 0xff;
+
+    std::copy_n(req.request_data, req.request_data_len,
+                messageBuf.data() +
+                    static_cast<uint8_t>(NVME_MI_MSG_REQUEST_HEADER_SIZE));
+
+    msgSize = NVME_MI_MSG_REQUEST_HEADER_SIZE + req.request_data_len;
+    integrity = crc32c(messageBuf.data(),
+                       NVME_MI_MSG_REQUEST_HEADER_SIZE + req.request_data_len);
+    messageBuf[msgSize] = integrity & 0xff;
+    messageBuf[msgSize + 1] = (integrity >> 8) & 0xff;
+    messageBuf[msgSize + 2] = (integrity >> 16) & 0xff;
+    messageBuf[msgSize + 3] = (integrity >> 24) & 0xff;
+    msgSize += sizeof(integrity);
+
+    return mctp_message_tx(&mctp, 0, messageBuf.data(), msgSize);
+}
+
+int verifyIntegrity(uint8_t* msg, size_t len)
+{
+    uint32_t msgIntegrity = {0};
+    if (len < NVME_MI_MSG_RESPONSE_HEADER_SIZE + sizeof(msgIntegrity))
+    {
+        std::cerr << "Not enough bytes for nvme header and trailer\n";
+        return -1;
+    }
+
+    msgIntegrity = (msg[len - 4]) + (msg[len - 3] << 8) + (msg[len - 2] << 16) +
+                   (msg[len - 1] << 24);
+
+    uint32_t calculateIntegrity = crc32c(msg, len - sizeof(msgIntegrity));
+    if (msgIntegrity != calculateIntegrity)
+    {
+        std::cerr << "CRC mismatch. Got=" << msgIntegrity
+                  << " Expected=" << calculateIntegrity << "\n";
+        return -1;
+    }
+    return 0;
+}
+
+void readAndProcessNVMeSensor(const std::shared_ptr<NVMeContext>& nvmeDevice)
+{
+    struct nvme_mi_msg_request requestMsg = {};
+    requestMsg.header.opcode = NVME_MI_OPCODE_HEALTH_STATUS_POLL;
+    requestMsg.header.dword0 = 0;
+    requestMsg.header.dword1 = 0;
+
+    int mctpResponseTimeout = 1;
+
+    if (nvmeDevice->sensors.empty())
+    {
+        return;
+    }
+
+    std::shared_ptr<NVMeSensor>& sensor = nvmeDevice->sensors.front();
+
+    // setup the timeout timer
+    nvmeDevice->mctpResponseTimer.expires_from_now(
+        boost::posix_time::seconds(mctpResponseTimeout));
+
+    nvmeDevice->mctpResponseTimer.async_wait(
+        [sensor, nvmeDevice](const boost::system::error_code errorCode) {
+            constexpr const size_t errorThreshold = 5;
+            if (errorCode)
+            {
+                return;
+            }
+            if (sensor->errorCount < errorThreshold)
+            {
+                std::cerr << "MCTP timeout device " << sensor->name << "\n";
+                sensor->errorCount++;
+            }
+            else
+            {
+                sensor->updateValue(0);
+            }
+
+            // cycle it back
+            nvmeDevice->sensors.pop_front();
+            nvmeDevice->sensors.emplace_back(sensor);
+
+            nvmeDevice->nvmeSlaveSocket.cancel();
+        });
+
+    readResponse(nvmeDevice);
+
+    if (DEBUG)
+    {
+        std::cout << "Sending message to read data from Drive on bus: "
+                  << sensor->bus << " , rootBus: " << nvmeDevice->rootBus
+                  << " device: " << sensor->name << "\n";
+    }
+
+    mctp_smbus_set_out_fd(nvmeMCTP::smbus, nvmeMCTP::getOutFd(sensor->bus));
+    int rc = nvmeMessageTransmit(*nvmeMCTP::mctp, requestMsg);
+
+    if (rc != 0)
+    {
+        std::cerr << "Error sending request message to NVMe device\n";
+    }
+}
+
+static double getTemperatureReading(int8_t reading)
+{
+
+    if (reading == static_cast<int8_t>(0x80) ||
+        reading == static_cast<int8_t>(0x81))
+    {
+        // 0x80 = No temperature data or temperature data is more the 5 s
+        // old 0x81 = Temperature sensor failure
+        return maxReading;
+    }
+
+    return reading;
+}
+
+void rxMessage(uint8_t eid, void*, void* msg, size_t len)
+{
+    struct nvme_mi_msg_response_header header
+    {
+    };
+
+    int inFd = mctp_smbus_get_in_fd(nvmeMCTP::smbus);
+    int rootBus = nvmeMCTP::getRootBus(inFd);
+
+    NVMEMap& nvmeMap = getNVMEMap();
+    auto findMap = nvmeMap.find(rootBus);
+    if (findMap == nvmeMap.end())
+    {
+        std::cerr << "Unable to lookup root bus " << rootBus << "\n";
+        return;
+    }
+    std::shared_ptr<NVMeContext>& self = findMap->second;
+
+    if (msg == nullptr)
+    {
+        std::cerr << "Bad message received\n";
+        return;
+    }
+
+    if (len <= 0)
+    {
+        std::cerr << "Received message not long enough\n";
+        return;
+    }
+
+    if (DEBUG)
+    {
+        std::cout << "Eid from the received messaged: " << eid << "\n";
+    }
+
+    uint8_t* messageData = static_cast<uint8_t*>(msg);
+
+    if ((*messageData & NVME_MI_MESSAGE_TYPE_MASK) != NVME_MI_MESSAGE_TYPE)
+    {
+        std::cerr << "Got unknown type message_type="
+                  << (*messageData & NVME_MI_MESSAGE_TYPE_MASK) << "\n";
+        return;
+    }
+
+    if (len < NVME_MI_MSG_RESPONSE_HEADER_SIZE + sizeof(uint32_t))
+    {
+        std::cerr << "Not enough bytes for NVMe header and trailer\n";
+        return;
+    }
+
+    if (verifyIntegrity(messageData, len) != 0)
+    {
+        std::cerr << "Verification of message integrity failed\n";
+        return;
+    }
+
+    header.message_type = messageData[0];
+    header.flags = messageData[1];
+    header.status = messageData[4];
+
+    if (header.status == NVME_MI_HDR_STATUS_MORE_PROCESSING_REQUIRED)
+    {
+        return;
+    }
+
+    if (header.status != NVME_MI_HDR_STATUS_SUCCESS)
+    {
+        std::cerr << "Command failed with status= " << header.status << "\n";
+        return;
+    }
+
+    messageData += NVME_MI_MSG_RESPONSE_HEADER_SIZE;
+    size_t messageLength =
+        len - NVME_MI_MSG_RESPONSE_HEADER_SIZE - sizeof(uint32_t);
+    if (((header.flags >> NVME_MI_HDR_FLAG_MSG_TYPE_SHIFT) &
+         NVME_MI_HDR_FLAG_MSG_TYPE_MASK) != NVME_MI_HDR_MESSAGE_TYPE_MI_COMMAND)
+    {
+        std::cerr << "Not MI type comamnd\n";
+        return;
+    }
+
+    if (messageLength < NVME_MI_HEALTH_STATUS_POLL_MSG_MIN)
+    {
+        std::cerr << "Got improperly sized health status poll\n";
+        return;
+    }
+
+    std::shared_ptr<NVMeSensor> sensorInfo = self->sensors.front();
+    if (DEBUG)
+    {
+        std::cout << "Temperature Reading: "
+                  << getTemperatureReading(messageData[5])
+                  << " Celsius for device " << sensorInfo->name << "\n";
+    }
+
+    sensorInfo->updateValue(getTemperatureReading(messageData[5]));
+
+    if (DEBUG)
+    {
+        std::cout << "Cancelling the timer now\n";
+    }
+
+    // move to back of scan queue
+    self->sensors.pop_front();
+    self->sensors.emplace_back(sensorInfo);
+
+    self->mctpResponseTimer.cancel();
+}
+
+NVMeContext::NVMeContext(boost::asio::io_service& io, int rootBus) :
+    rootBus(rootBus), scanTimer(io), nvmeSlaveSocket(io), mctpResponseTimer(io)
+{
+    nvmeSlaveSocket.assign(boost::asio::ip::tcp::v4(),
+                           nvmeMCTP::getInFd(rootBus));
+}
+
+void NVMeContext::pollNVMeDevices()
+{
+    scanTimer.expires_from_now(boost::posix_time::seconds(1));
+    scanTimer.async_wait(
+        [self{shared_from_this()}](const boost::system::error_code errorCode) {
+            if (errorCode == boost::asio::error::operation_aborted)
+            {
+                return; // we're being canceled
+            }
+            else if (errorCode)
+            {
+                std::cerr << "Error:" << errorCode.message() << "\n";
+                return;
+            }
+            else
+            {
+                readAndProcessNVMeSensor(self);
+            }
+
+            self->pollNVMeDevices();
+        });
+}
+
+NVMeContext::~NVMeContext()
+{
+    scanTimer.cancel();
+    mctpResponseTimer.cancel();
+    nvmeSlaveSocket.cancel();
+    nvmeMCTP::closeInFd(rootBus);
+}
+
+NVMeSensor::NVMeSensor(sdbusplus::asio::object_server& objectServer,
+                       boost::asio::io_service& io,
+                       std::shared_ptr<sdbusplus::asio::connection>& conn,
+                       const std::string& sensorName,
+                       std::vector<thresholds::Threshold>&& _thresholds,
+                       const std::string& sensorConfiguration,
+                       const int busNumber) :
+    Sensor(boost::replace_all_copy(sensorName, " ", "_"),
+           std::move(_thresholds), sensorConfiguration,
+           "xyz.openbmc_project.Configuration.NVMe", maxReading, minReading),
+    objServer(objectServer), errorCount(0), bus(busNumber)
+{
+    sensorInterface = objectServer.add_interface(
+        "/xyz/openbmc_project/sensors/temperature/" + name,
+        "xyz.openbmc_project.Sensor.Value");
+
+    if (thresholds::hasWarningInterface(thresholds))
+    {
+        thresholdInterfaceWarning = objectServer.add_interface(
+            "/xyz/openbmc_project/sensors/temperature/" + name,
+            "xyz.openbmc_project.Sensor.Threshold.Warning");
+    }
+    if (thresholds::hasCriticalInterface(thresholds))
+    {
+        thresholdInterfaceCritical = objectServer.add_interface(
+            "/xyz/openbmc_project/sensors/temperature/" + name,
+            "xyz.openbmc_project.Sensor.Threshold.Critical");
+    }
+    association = objectServer.add_interface(
+        "/xyz/openbmc_project/sensors/temperature/" + name,
+        association::interface);
+
+    setInitialProperties(conn);
+    // setup match
+    setupPowerMatch(conn);
+}
+
+NVMeSensor::~NVMeSensor()
+{
+    // close the input dev to cancel async operations
+    objServer.remove_interface(thresholdInterfaceWarning);
+    objServer.remove_interface(thresholdInterfaceCritical);
+    objServer.remove_interface(sensorInterface);
+    objServer.remove_interface(association);
+}
+
+void NVMeSensor::checkThresholds(void)
+{
+    thresholds::checkThresholds(this);
+}
diff --git a/src/NVMeSensorMain.cpp b/src/NVMeSensorMain.cpp
new file mode 100644
index 0000000..1858ea9
--- /dev/null
+++ b/src/NVMeSensorMain.cpp
@@ -0,0 +1,181 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "NVMeSensor.hpp"
+
+#include <boost/asio/deadline_timer.hpp>
+#include <regex>
+
+static constexpr const char* sensorType =
+    "xyz.openbmc_project.Configuration.NVME1000";
+
+static NVMEMap nvmeDeviceMap;
+
+static constexpr bool DEBUG = false;
+
+NVMEMap& getNVMEMap()
+{
+    return nvmeDeviceMap;
+}
+
+void createSensors(boost::asio::io_service& io,
+                   sdbusplus::asio::object_server& objectServer,
+                   std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
+{
+
+    auto getter = std::make_shared<GetSensorConfiguration>(
+        dbusConnection,
+        std::move([&io, &objectServer, &dbusConnection](
+                      const ManagedObjectType& sensorConfigurations) {
+            // todo: it'd be better to only update the ones we care about
+            nvmeDeviceMap.clear();
+
+            // iterate through all found configurations
+            for (const std::pair<sdbusplus::message::object_path, SensorData>&
+                     sensor : sensorConfigurations)
+            {
+                const SensorData& sensorData = sensor.second;
+                const std::string& interfacePath = sensor.first.str;
+                const std::pair<
+                    std::string,
+                    boost::container::flat_map<std::string, BasicVariantType>>*
+                    baseConfiguration = nullptr;
+
+                // find base configuration
+                auto sensorBase = sensor.second.find(sensorType);
+                if (sensorBase != sensor.second.end())
+                {
+                    baseConfiguration = &(*sensorBase);
+                }
+
+                if (baseConfiguration == nullptr)
+                {
+                    continue;
+                }
+                auto findBus = baseConfiguration->second.find("Bus");
+                if (findBus == baseConfiguration->second.end())
+                {
+                    continue;
+                }
+
+                unsigned int busNumber =
+                    std::visit(VariantToUnsignedIntVisitor(), findBus->second);
+
+                auto findSensorName = baseConfiguration->second.find("Name");
+                if (findSensorName == baseConfiguration->second.end())
+                {
+                    std::cerr << "could not determine configuration name for "
+                              << interfacePath << "\n";
+                    continue;
+                }
+                std::string sensorName =
+                    std::get<std::string>(findSensorName->second);
+
+                std::vector<thresholds::Threshold> sensorThresholds;
+
+                if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
+                {
+                    std::cerr << "error populating thresholds for "
+                              << sensorName << "\n";
+                }
+
+                int rootBus = busNumber;
+
+                std::string muxPath = "/sys/bus/i2c/devices/i2c-" +
+                                      std::to_string(busNumber) + "/mux_device";
+
+                if (std::filesystem::is_symlink(muxPath))
+                {
+                    std::string rootName =
+                        std::filesystem::read_symlink(muxPath).filename();
+                    size_t dash = rootName.find("-");
+                    if (dash == std::string::npos)
+                    {
+                        std::cerr << "Error finding root bus for " << rootName
+                                  << "\n";
+                        continue;
+                    }
+                    rootBus = std::stoi(rootName.substr(0, dash));
+                }
+
+                std::shared_ptr<NVMeContext> context;
+                auto findRoot = nvmeDeviceMap.find(rootBus);
+                if (findRoot != nvmeDeviceMap.end())
+                {
+                    context = findRoot->second;
+                }
+                else
+                {
+                    context = std::make_shared<NVMeContext>(io, rootBus);
+                    nvmeDeviceMap[rootBus] = context;
+                }
+
+                std::shared_ptr<NVMeSensor> sensorPtr =
+                    std::make_shared<NVMeSensor>(
+                        objectServer, io, dbusConnection, sensorName,
+                        std::move(sensorThresholds), interfacePath, busNumber);
+
+                context->sensors.emplace_back(sensorPtr);
+            }
+            for (const auto& [_, context] : nvmeDeviceMap)
+            {
+                context->pollNVMeDevices();
+            }
+        }));
+    getter->getConfiguration(std::vector<std::string>{sensorType});
+}
+
+int main()
+{
+    boost::asio::io_service io;
+    auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+    systemBus->request_name("xyz.openbmc_project.NVMeSensor");
+    sdbusplus::asio::object_server objectServer(systemBus);
+    nvmeMCTP::init();
+
+    io.post([&]() { createSensors(io, objectServer, systemBus); });
+
+    boost::asio::deadline_timer filterTimer(io);
+    std::function<void(sdbusplus::message::message&)> eventHandler =
+        [&filterTimer, &io, &objectServer,
+         &systemBus](sdbusplus::message::message& message) {
+            // this implicitly cancels the timer
+            filterTimer.expires_from_now(boost::posix_time::seconds(1));
+
+            filterTimer.async_wait([&](const boost::system::error_code& ec) {
+                if (ec == boost::asio::error::operation_aborted)
+                {
+                    return; // we're being canceled
+                }
+                else if (ec)
+                {
+                    std::cerr << "Error: " << ec.message() << "\n";
+                    return;
+                }
+
+                createSensors(io, objectServer, systemBus);
+            });
+        };
+
+    sdbusplus::bus::match::match configMatch(
+        static_cast<sdbusplus::bus::bus&>(*systemBus),
+        "type='signal',member='PropertiesChanged',path_namespace='" +
+            std::string(inventoryPath) + "',arg0namespace='" +
+            std::string(sensorType) + "'",
+        eventHandler);
+
+    io.run();
+}