Move source files into application-specific sub-directories

Currently, dbus-sensors implement multiple applications:
 - psusensor
 - adcsensor
 - intelcpusensor
 - hwmontempsensor
 - ipmbsensor
 - nvmesensor
 - externalsensor
 - mcutempsensor
 - intrusionsensor
 - fansensor
 - exitairtempsensor

This commit is to create separate directories for each application so
that things can be separated more easily and the files are smaller,
instead of creating one huge file for the sensor implementation.

There was some discussion in discord on this. [1][2]

[1]: https://discord.com/channels/775381525260664832/1187158775438778408/1284106093756289067
[2]: https://discord.com/channels/775381525260664832/867820390406422538/1303217796821553214

Signed-off-by: George Liu <liuxiwei@ieisystem.com>
Change-Id: I258fc2ee7d8f939c7b83a07350395e78775b2b8d
diff --git a/src/nvme/NVMeBasicContext.cpp b/src/nvme/NVMeBasicContext.cpp
new file mode 100644
index 0000000..64549a4
--- /dev/null
+++ b/src/nvme/NVMeBasicContext.cpp
@@ -0,0 +1,435 @@
+#include "NVMeBasicContext.hpp"
+
+#include "NVMeContext.hpp"
+#include "NVMeSensor.hpp"
+
+#include <endian.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <FileHandle.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/read.hpp>
+#include <boost/asio/streambuf.hpp>
+#include <boost/asio/write.hpp>
+
+#include <array>
+#include <cerrno>
+#include <chrono>
+#include <cmath>
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <filesystem>
+#include <ios>
+#include <iostream>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <system_error>
+#include <thread>
+#include <utility>
+#include <vector>
+
+extern "C"
+{
+#include <i2c/smbus.h>
+#include <linux/i2c-dev.h>
+}
+
+/*
+ * NVMe-MI Basic Management Command
+ *
+ * https://nvmexpress.org/wp-content/uploads/NVMe_Management_-_Technical_Note_on_Basic_Management_Command.pdf
+ */
+
+static std::shared_ptr<std::array<uint8_t, 6>>
+    encodeBasicQuery(int bus, uint8_t device, uint8_t offset)
+{
+    if (bus < 0)
+    {
+        throw std::domain_error("Invalid bus argument");
+    }
+
+    /* bus + address + command */
+    uint32_t busle = htole32(static_cast<uint32_t>(bus));
+    auto command =
+        std::make_shared<std::array<uint8_t, sizeof(busle) + 1 + 1>>();
+    memcpy(command->data(), &busle, sizeof(busle));
+    (*command)[sizeof(busle) + 0] = device;
+    (*command)[sizeof(busle) + 1] = offset;
+
+    return command;
+}
+
+static void decodeBasicQuery(const std::array<uint8_t, 6>& req, int& bus,
+                             uint8_t& device, uint8_t& offset)
+{
+    uint32_t busle = 0;
+
+    memcpy(&busle, req.data(), sizeof(busle));
+    bus = le32toh(busle);
+    device = req[sizeof(busle) + 0];
+    offset = req[sizeof(busle) + 1];
+}
+
+static void execBasicQuery(int bus, uint8_t addr, uint8_t cmd,
+                           std::vector<uint8_t>& resp)
+{
+    int32_t size = 0;
+    std::filesystem::path devpath = "/dev/i2c-" + std::to_string(bus);
+
+    try
+    {
+        FileHandle fileHandle(devpath);
+
+        /* Select the target device */
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
+        if (::ioctl(fileHandle.handle(), I2C_SLAVE, addr) == -1)
+        {
+            std::cerr << "Failed to configure device address 0x" << std::hex
+                      << (int)addr << " for bus " << std::dec << bus << ": "
+                      << strerror(errno) << "\n";
+            resp.resize(0);
+            return;
+        }
+
+        resp.resize(UINT8_MAX + 1);
+
+        /* Issue the NVMe MI basic command */
+        size = i2c_smbus_read_block_data(fileHandle.handle(), cmd, resp.data());
+        if (size < 0)
+        {
+            std::cerr << "Failed to read block data from device 0x" << std::hex
+                      << (int)addr << " on bus " << std::dec << bus << ": "
+                      << strerror(errno) << "\n";
+            resp.resize(0);
+        }
+        else if (size > UINT8_MAX + 1)
+        {
+            std::cerr << "Unexpected message length from device 0x" << std::hex
+                      << (int)addr << " on bus " << std::dec << bus << ": "
+                      << size << " (" << UINT8_MAX << ")\n";
+            resp.resize(0);
+        }
+        else
+        {
+            resp.resize(size);
+        }
+    }
+    catch (const std::out_of_range& e)
+    {
+        std::cerr << "Failed to create file handle for bus " << std::dec << bus
+                  << ": " << e.what() << "\n";
+        resp.resize(0);
+    }
+}
+
+static ssize_t processBasicQueryStream(FileHandle& in, FileHandle& out)
+{
+    std::vector<uint8_t> resp{};
+    ssize_t rc = 0;
+
+    while (true)
+    {
+        uint8_t device = 0;
+        uint8_t offset = 0;
+        uint8_t len = 0;
+        int bus = 0;
+
+        /* bus + address + command */
+        std::array<uint8_t, sizeof(uint32_t) + 1 + 1> req{};
+
+        /* Read the command parameters */
+        ssize_t rc = ::read(in.handle(), req.data(), req.size());
+        if (rc != static_cast<ssize_t>(req.size()))
+        {
+            std::cerr << "Failed to read request from in descriptor "
+                      << strerror(errno) << "\n";
+            if (rc != 0)
+            {
+                return -errno;
+            }
+            return -EIO;
+        }
+
+        decodeBasicQuery(req, bus, device, offset);
+
+        /* Execute the query */
+        execBasicQuery(bus, device, offset, resp);
+
+        /* Write out the response length */
+        len = resp.size();
+        rc = ::write(out.handle(), &len, sizeof(len));
+        if (rc != sizeof(len))
+        {
+            std::cerr << "Failed to write block (" << std::dec << len
+                      << ") length to out descriptor: "
+                      << strerror(static_cast<int>(-rc)) << "\n";
+            if (rc != 0)
+            {
+                return -errno;
+            }
+            return -EIO;
+        }
+
+        /* Write out the response data */
+        std::vector<uint8_t>::iterator cursor = resp.begin();
+        while (cursor != resp.end())
+        {
+            size_t lenRemaining = std::distance(cursor, resp.end());
+            ssize_t egress = ::write(out.handle(), &(*cursor), lenRemaining);
+            if (egress == -1)
+            {
+                std::cerr << "Failed to write block data of length " << std::dec
+                          << lenRemaining << " to out pipe: " << strerror(errno)
+                          << "\n";
+                if (rc != 0)
+                {
+                    return -errno;
+                }
+                return -EIO;
+            }
+
+            cursor += egress;
+        }
+    }
+
+    return rc;
+}
+
+/* Throws std::error_code on failure */
+/* FIXME: Probably shouldn't do fallible stuff in a constructor */
+NVMeBasicContext::NVMeBasicContext(boost::asio::io_context& io, int rootBus) :
+    NVMeContext::NVMeContext(io, rootBus), io(io), reqStream(io), respStream(io)
+{
+    std::array<int, 2> responsePipe{};
+    std::array<int, 2> requestPipe{};
+
+    /* Set up inter-thread communication */
+    if (::pipe(requestPipe.data()) == -1)
+    {
+        std::cerr << "Failed to create request pipe: " << strerror(errno)
+                  << "\n";
+        throw std::error_code(errno, std::system_category());
+    }
+
+    if (::pipe(responsePipe.data()) == -1)
+    {
+        std::cerr << "Failed to create response pipe: " << strerror(errno)
+                  << "\n";
+
+        if (::close(requestPipe[0]) == -1)
+        {
+            std::cerr << "Failed to close write fd of request pipe: "
+                      << strerror(errno) << "\n";
+        }
+
+        if (::close(requestPipe[1]) == -1)
+        {
+            std::cerr << "Failed to close read fd of request pipe: "
+                      << strerror(errno) << "\n";
+        }
+
+        throw std::error_code(errno, std::system_category());
+    }
+
+    reqStream.assign(requestPipe[1]);
+    FileHandle streamIn(requestPipe[0]);
+    FileHandle streamOut(responsePipe[1]);
+    respStream.assign(responsePipe[0]);
+
+    thread = std::jthread([streamIn{std::move(streamIn)},
+                           streamOut{std::move(streamOut)}]() mutable {
+        ssize_t rc = processBasicQueryStream(streamIn, streamOut);
+
+        if (rc < 0)
+        {
+            std::cerr << "Failure while processing query stream: "
+                      << strerror(static_cast<int>(-rc)) << "\n";
+        }
+
+        std::cerr << "Terminating basic query thread\n";
+    });
+}
+
+void NVMeBasicContext::readAndProcessNVMeSensor()
+{
+    if (pollCursor == sensors.end())
+    {
+        this->pollNVMeDevices();
+        return;
+    }
+
+    std::shared_ptr<NVMeSensor> sensor = *pollCursor++;
+
+    if (!sensor->readingStateGood())
+    {
+        sensor->markAvailable(false);
+        sensor->updateValue(std::numeric_limits<double>::quiet_NaN());
+        readAndProcessNVMeSensor();
+        return;
+    }
+
+    /* Potentially defer sampling the sensor sensor if it is in error */
+    if (!sensor->sample())
+    {
+        readAndProcessNVMeSensor();
+        return;
+    }
+
+    auto command = encodeBasicQuery(sensor->bus, sensor->address, 0x00);
+
+    /* Issue the request */
+    boost::asio::async_write(
+        reqStream, boost::asio::buffer(command->data(), command->size()),
+        [command](boost::system::error_code ec, std::size_t) {
+            if (ec)
+            {
+                std::cerr << "Got error writing basic query: " << ec << "\n";
+            }
+        });
+
+    auto response = std::make_shared<boost::asio::streambuf>();
+    response->prepare(1);
+
+    /* Gather the response and dispatch for parsing */
+    boost::asio::async_read(
+        respStream, *response,
+        [response](const boost::system::error_code& ec, std::size_t n) {
+            if (ec)
+            {
+                std::cerr << "Got error completing basic query: " << ec << "\n";
+                return static_cast<std::size_t>(0);
+            }
+
+            if (n == 0)
+            {
+                return static_cast<std::size_t>(1);
+            }
+
+            std::istream is(response.get());
+            size_t len = static_cast<std::size_t>(is.peek());
+
+            if (n > len + 1)
+            {
+                std::cerr << "Query stream has become unsynchronised: "
+                          << "n: " << n << ", "
+                          << "len: " << len << "\n";
+                return static_cast<std::size_t>(0);
+            }
+
+            if (n == len + 1)
+            {
+                return static_cast<std::size_t>(0);
+            }
+
+            if (n > 1)
+            {
+                return len + 1 - n;
+            }
+
+            response->prepare(len);
+            return len;
+        },
+        [weakSelf{weak_from_this()}, sensor, response](
+            const boost::system::error_code& ec, std::size_t length) mutable {
+            if (ec)
+            {
+                std::cerr << "Got error reading basic query: " << ec << "\n";
+                return;
+            }
+
+            if (length == 0)
+            {
+                std::cerr << "Invalid message length: " << length << "\n";
+                return;
+            }
+
+            if (auto self = weakSelf.lock())
+            {
+                /* Deserialise the response */
+                response->consume(1); /* Drop the length byte */
+                std::istream is(response.get());
+                std::vector<char> data(response->size());
+                is.read(data.data(), response->size());
+
+                /* Update the sensor */
+                self->processResponse(sensor, data.data(), data.size());
+
+                /* Enqueue processing of the next sensor */
+                self->readAndProcessNVMeSensor();
+            }
+        });
+}
+
+void NVMeBasicContext::pollNVMeDevices()
+{
+    pollCursor = sensors.begin();
+
+    scanTimer.expires_after(std::chrono::seconds(1));
+    scanTimer.async_wait([weakSelf{weak_from_this()}](
+                             const boost::system::error_code errorCode) {
+        if (errorCode == boost::asio::error::operation_aborted)
+        {
+            return;
+        }
+
+        if (errorCode)
+        {
+            std::cerr << errorCode.message() << "\n";
+            return;
+        }
+
+        if (auto self = weakSelf.lock())
+        {
+            self->readAndProcessNVMeSensor();
+        }
+    });
+}
+
+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 std::numeric_limits<double>::quiet_NaN();
+    }
+
+    return reading;
+}
+
+void NVMeBasicContext::processResponse(std::shared_ptr<NVMeSensor>& sensor,
+                                       void* msg, size_t len)
+{
+    if (msg == nullptr || len < 6)
+    {
+        sensor->incrementError();
+        return;
+    }
+
+    uint8_t* messageData = static_cast<uint8_t*>(msg);
+
+    uint8_t status = messageData[0];
+    if (((status & NVME_MI_BASIC_SFLGS_DRIVE_NOT_READY) != 0) ||
+        ((status & NVME_MI_BASIC_SFLGS_DRIVE_FUNCTIONAL) == 0))
+    {
+        sensor->markFunctional(false);
+        return;
+    }
+
+    double value = getTemperatureReading(messageData[2]);
+    if (!std::isfinite(value))
+    {
+        sensor->incrementError();
+        return;
+    }
+
+    sensor->updateValue(value);
+}
diff --git a/src/nvme/NVMeBasicContext.hpp b/src/nvme/NVMeBasicContext.hpp
new file mode 100644
index 0000000..52b6a09
--- /dev/null
+++ b/src/nvme/NVMeBasicContext.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "NVMeContext.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/posix/stream_descriptor.hpp>
+
+#include <thread>
+
+class NVMeBasicContext : public NVMeContext
+{
+  public:
+    NVMeBasicContext(boost::asio::io_context& io, int rootBus);
+    ~NVMeBasicContext() override = default;
+    void pollNVMeDevices() override;
+    void readAndProcessNVMeSensor() override;
+    void processResponse(std::shared_ptr<NVMeSensor>& sensor, void* msg,
+                         size_t len) override;
+
+  private:
+    NVMeBasicContext(boost::asio::io_context& io, int rootBus, int cmdOut,
+                     int streamIn, int streamOut, int cmdIn);
+    boost::asio::io_context& io;
+
+    // The IO thread must be destructed after the stream descriptors, so
+    // initialise it first. http://eel.is/c++draft/class.base.init#note-6
+    //
+    // Providing a stop-source to the thread execution function isn't
+    // particularly useful as it will spend most of its time blocked in a system
+    // call - ioctl() for the actual device communication, or read() and write()
+    // on the pipes associated with reqStream and respStream. Rather than trying
+    // to force a stop, rely on read()/write() failures from closed pipes to
+    // coerce it to exit and thus allow completion of the join().
+    std::jthread thread;
+
+    // Destruction of the stream descriptors has the effect of issuing cancel(),
+    // destroying the closure of the callback where we might be carrying
+    // weak_ptrs to `this`.
+    // https://www.boost.org/doc/libs/1_79_0/doc/html/boost_asio/reference/posix__basic_descriptor/_basic_descriptor.html
+    boost::asio::posix::stream_descriptor reqStream;
+    boost::asio::posix::stream_descriptor respStream;
+
+    enum
+    {
+        NVME_MI_BASIC_SFLGS_DRIVE_NOT_READY = 0x40,
+        NVME_MI_BASIC_SFLGS_DRIVE_FUNCTIONAL = 0x20,
+    };
+};
diff --git a/src/nvme/NVMeContext.hpp b/src/nvme/NVMeContext.hpp
new file mode 100644
index 0000000..4eb81e4
--- /dev/null
+++ b/src/nvme/NVMeContext.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+#include "NVMeSensor.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/steady_timer.hpp>
+
+#include <memory>
+#include <stdexcept>
+
+class NVMeContext : public std::enable_shared_from_this<NVMeContext>
+{
+  public:
+    NVMeContext(boost::asio::io_context& io, int rootBus) :
+        scanTimer(io), rootBus(rootBus), pollCursor(sensors.end())
+    {
+        if (rootBus < 0)
+        {
+            throw std::invalid_argument(
+                "Invalid root bus: Bus ID must not be negative");
+        }
+    }
+
+    virtual ~NVMeContext()
+    {
+        scanTimer.cancel();
+    }
+
+    void addSensor(const std::shared_ptr<NVMeSensor>& sensor)
+    {
+        sensors.emplace_back(sensor);
+    }
+
+    std::optional<std::shared_ptr<NVMeSensor>>
+        getSensorAtPath(const std::string& path)
+    {
+        for (auto& sensor : sensors)
+        {
+            if (sensor->configurationPath == path)
+            {
+                return sensor;
+            }
+        }
+
+        return std::nullopt;
+    }
+
+    // Post-condition: The sensor list does not contain the provided sensor
+    // Post-condition: pollCursor is a valid iterator for the sensor list
+    void removeSensor(const std::shared_ptr<NVMeSensor>& sensor)
+    {
+        // Locate the sensor that we're removing in the sensor list
+        auto found = std::find(sensors.begin(), sensors.end(), sensor);
+
+        // If we failed to find the sensor in the list the post-condition is
+        // already satisfied
+        if (found == sensors.end())
+        {
+            return;
+        }
+
+        // We've found the sensor in the list
+
+        // If we're not actively polling the sensor list, then remove the sensor
+        if (pollCursor == sensors.end())
+        {
+            sensors.erase(found);
+            return;
+        }
+
+        // We're actively polling the sensor list
+
+        // If we're not polling the specific sensor that has been removed, then
+        // remove the sensor
+        if (*pollCursor != *found)
+        {
+            sensors.erase(found);
+            return;
+        }
+
+        // We're polling the sensor that is being removed
+
+        // Remove the sensor and update the poll cursor so the cursor remains
+        // valid
+        pollCursor = sensors.erase(found);
+    }
+
+    virtual void close()
+    {
+        scanTimer.cancel();
+    }
+
+    virtual void pollNVMeDevices() = 0;
+
+    virtual void readAndProcessNVMeSensor() = 0;
+
+    virtual void processResponse(std::shared_ptr<NVMeSensor>& sensor, void* msg,
+                                 size_t len) = 0;
+
+  protected:
+    boost::asio::steady_timer scanTimer;
+    int rootBus; // Root bus for this drive
+    std::list<std::shared_ptr<NVMeSensor>> sensors;
+    std::list<std::shared_ptr<NVMeSensor>>::iterator pollCursor;
+};
+
+using NVMEMap = boost::container::flat_map<int, std::shared_ptr<NVMeContext>>;
+
+NVMEMap& getNVMEMap();
diff --git a/src/nvme/NVMeSensor.cpp b/src/nvme/NVMeSensor.cpp
new file mode 100644
index 0000000..4c372d6
--- /dev/null
+++ b/src/nvme/NVMeSensor.cpp
@@ -0,0 +1,106 @@
+/*
+// 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 "SensorPaths.hpp"
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+#include "sensor.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+static constexpr double maxReading = 127;
+static constexpr double minReading = 0;
+
+NVMeSensor::NVMeSensor(sdbusplus::asio::object_server& objectServer,
+                       boost::asio::io_context& /*unused*/,
+                       std::shared_ptr<sdbusplus::asio::connection>& conn,
+                       const std::string& sensorName,
+                       std::vector<thresholds::Threshold>&& thresholdsIn,
+                       const std::string& sensorConfiguration,
+                       const int busNumber, const uint8_t slaveAddr) :
+    Sensor(escapeName(sensorName), std::move(thresholdsIn), sensorConfiguration,
+           NVMeSensor::sensorType, false, false, maxReading, minReading, conn,
+           PowerState::on),
+    bus(busNumber), address(slaveAddr), objServer(objectServer)
+{
+    if (bus < 0)
+    {
+        throw std::invalid_argument("Invalid bus: Bus ID must not be negative");
+    }
+
+    sensorInterface = objectServer.add_interface(
+        "/xyz/openbmc_project/sensors/temperature/" + name,
+        "xyz.openbmc_project.Sensor.Value");
+
+    for (const auto& threshold : thresholds)
+    {
+        std::string interface = thresholds::getInterface(threshold.level);
+        thresholdInterfaces[static_cast<size_t>(threshold.level)] =
+            objectServer.add_interface(
+                "/xyz/openbmc_project/sensors/temperature/" + name, interface);
+    }
+    association = objectServer.add_interface(
+        "/xyz/openbmc_project/sensors/temperature/" + name,
+        association::interface);
+
+    setInitialProperties(sensor_paths::unitDegreesC);
+    // Mark as unavailable until the first packet has been received over NVMe
+    // MI.
+    markAvailable(false);
+}
+
+NVMeSensor::~NVMeSensor()
+{
+    // close the input dev to cancel async operations
+    for (const auto& iface : thresholdInterfaces)
+    {
+        objServer.remove_interface(iface);
+    }
+    objServer.remove_interface(sensorInterface);
+    objServer.remove_interface(association);
+}
+
+bool NVMeSensor::sample()
+{
+    if (inError())
+    {
+        if (scanDelay == 0)
+        {
+            scanDelay = scanDelayTicks;
+        }
+
+        scanDelay--;
+    }
+
+    return scanDelay == 0;
+}
+
+void NVMeSensor::checkThresholds()
+{
+    thresholds::checkThresholds(this);
+}
diff --git a/src/nvme/NVMeSensor.hpp b/src/nvme/NVMeSensor.hpp
new file mode 100644
index 0000000..a7cfba1
--- /dev/null
+++ b/src/nvme/NVMeSensor.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <boost/asio/io_context.hpp>
+#include <sensor.hpp>
+
+class NVMeSensor : public Sensor
+{
+  public:
+    static constexpr const char* sensorType = "NVME1000";
+
+    NVMeSensor(sdbusplus::asio::object_server& objectServer,
+               boost::asio::io_context& io,
+               std::shared_ptr<sdbusplus::asio::connection>& conn,
+               const std::string& sensorName,
+               std::vector<thresholds::Threshold>&& thresholds,
+               const std::string& sensorConfiguration, int busNumber,
+               uint8_t slaveAddr);
+    ~NVMeSensor() override;
+
+    NVMeSensor& operator=(const NVMeSensor& other) = delete;
+
+    bool sample();
+
+    const int bus;
+    const uint8_t address;
+
+  private:
+    const unsigned int scanDelayTicks = 5 * 60;
+    sdbusplus::asio::object_server& objServer;
+    unsigned int scanDelay{0};
+
+    void checkThresholds() override;
+};
diff --git a/src/nvme/NVMeSensorMain.cpp b/src/nvme/NVMeSensorMain.cpp
new file mode 100644
index 0000000..2fe295d
--- /dev/null
+++ b/src/nvme/NVMeSensorMain.cpp
@@ -0,0 +1,320 @@
+/*
+// 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 "NVMeBasicContext.hpp"
+#include "NVMeContext.hpp"
+#include "NVMeSensor.hpp"
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
+#include <boost/asio/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/post.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/message/native_types.hpp>
+
+#include <algorithm>
+#include <array>
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <filesystem>
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <variant>
+#include <vector>
+
+static constexpr uint8_t nvmeMiDefaultSlaveAddr = 0x6A;
+
+static NVMEMap nvmeDeviceMap;
+
+NVMEMap& getNVMEMap()
+{
+    return nvmeDeviceMap;
+}
+
+static std::optional<int> extractBusNumber(
+    const std::string& path, const SensorBaseConfigMap& properties)
+{
+    auto findBus = properties.find("Bus");
+    if (findBus == properties.end())
+    {
+        std::cerr << "could not determine bus number for " << path << "\n";
+        return std::nullopt;
+    }
+
+    return std::visit(VariantToIntVisitor(), findBus->second);
+}
+
+static uint8_t extractSlaveAddr(const std::string& path,
+                                const SensorBaseConfigMap& properties)
+{
+    auto findSlaveAddr = properties.find("Address");
+    if (findSlaveAddr == properties.end())
+    {
+        std::cerr << "could not determine slave address for " << path << "\n"
+                  << "using default as specified in nvme-mi"
+                  << "\n";
+        return nvmeMiDefaultSlaveAddr;
+    }
+
+    return std::visit(VariantToUnsignedIntVisitor(), findSlaveAddr->second);
+}
+
+static std::optional<std::string> extractSensorName(
+    const std::string& path, const SensorBaseConfigMap& properties)
+{
+    auto findSensorName = properties.find("Name");
+    if (findSensorName == properties.end())
+    {
+        std::cerr << "could not determine configuration name for " << path
+                  << "\n";
+        return std::nullopt;
+    }
+
+    return std::get<std::string>(findSensorName->second);
+}
+
+static std::filesystem::path deriveRootBusPath(int busNumber)
+{
+    return "/sys/bus/i2c/devices/i2c-" + std::to_string(busNumber) +
+           "/mux_device";
+}
+
+static std::optional<int> deriveRootBus(std::optional<int> busNumber)
+{
+    if (!busNumber)
+    {
+        return std::nullopt;
+    }
+
+    std::filesystem::path muxPath = deriveRootBusPath(*busNumber);
+
+    if (!std::filesystem::is_symlink(muxPath))
+    {
+        return busNumber;
+    }
+
+    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";
+        return std::nullopt;
+    }
+
+    return std::stoi(rootName.substr(0, dash));
+}
+
+static std::shared_ptr<NVMeContext> provideRootBusContext(
+    boost::asio::io_context& io, NVMEMap& map, int rootBus)
+{
+    auto findRoot = map.find(rootBus);
+    if (findRoot != map.end())
+    {
+        return findRoot->second;
+    }
+
+    std::shared_ptr<NVMeContext> context =
+        std::make_shared<NVMeBasicContext>(io, rootBus);
+    map[rootBus] = context;
+
+    return context;
+}
+
+static void handleSensorConfigurations(
+    boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
+    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+    const ManagedObjectType& sensorConfigurations)
+{
+    // todo: it'd be better to only update the ones we care about
+    for (const auto& [_, nvmeContextPtr] : nvmeDeviceMap)
+    {
+        if (nvmeContextPtr)
+        {
+            nvmeContextPtr->close();
+        }
+    }
+    nvmeDeviceMap.clear();
+
+    // iterate through all found configurations
+    for (const auto& [interfacePath, sensorData] : sensorConfigurations)
+    {
+        // find base configuration
+        auto sensorBase =
+            sensorData.find(configInterfaceName(NVMeSensor::sensorType));
+        if (sensorBase == sensorData.end())
+        {
+            continue;
+        }
+
+        const SensorBaseConfigMap& sensorConfig = sensorBase->second;
+        std::optional<int> busNumber =
+            extractBusNumber(interfacePath, sensorConfig);
+        std::optional<std::string> sensorName =
+            extractSensorName(interfacePath, sensorConfig);
+        uint8_t slaveAddr = extractSlaveAddr(interfacePath, sensorConfig);
+        std::optional<int> rootBus = deriveRootBus(busNumber);
+
+        if (!(busNumber && sensorName && rootBus))
+        {
+            continue;
+        }
+
+        std::vector<thresholds::Threshold> sensorThresholds;
+        if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
+        {
+            std::cerr << "error populating thresholds for " << *sensorName
+                      << "\n";
+        }
+
+        try
+        {
+            // May throw for an invalid rootBus
+            std::shared_ptr<NVMeContext> context =
+                provideRootBusContext(io, nvmeDeviceMap, *rootBus);
+
+            // Construct the sensor after grabbing the context so we don't
+            // glitch D-Bus May throw for an invalid busNumber
+            std::shared_ptr<NVMeSensor> sensorPtr =
+                std::make_shared<NVMeSensor>(
+                    objectServer, io, dbusConnection, *sensorName,
+                    std::move(sensorThresholds), interfacePath, *busNumber,
+                    slaveAddr);
+
+            context->addSensor(sensorPtr);
+        }
+        catch (const std::invalid_argument& ex)
+        {
+            std::cerr << "Failed to add sensor for "
+                      << std::string(interfacePath) << ": " << ex.what()
+                      << "\n";
+        }
+    }
+    for (const auto& [_, context] : nvmeDeviceMap)
+    {
+        context->pollNVMeDevices();
+    }
+}
+
+void createSensors(boost::asio::io_context& io,
+                   sdbusplus::asio::object_server& objectServer,
+                   std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
+{
+    auto getter = std::make_shared<GetSensorConfiguration>(
+        dbusConnection, [&io, &objectServer, &dbusConnection](
+                            const ManagedObjectType& sensorConfigurations) {
+            handleSensorConfigurations(io, objectServer, dbusConnection,
+                                       sensorConfigurations);
+        });
+    getter->getConfiguration(std::vector<std::string>{NVMeSensor::sensorType});
+}
+
+static void interfaceRemoved(sdbusplus::message_t& message, NVMEMap& contexts)
+{
+    if (message.is_method_error())
+    {
+        std::cerr << "interfacesRemoved callback method error\n";
+        return;
+    }
+
+    sdbusplus::message::object_path path;
+    std::vector<std::string> interfaces;
+
+    message.read(path, interfaces);
+
+    for (auto& [_, context] : contexts)
+    {
+        std::optional<std::shared_ptr<NVMeSensor>> sensor =
+            context->getSensorAtPath(path);
+        if (!sensor)
+        {
+            continue;
+        }
+
+        auto interface = std::find(interfaces.begin(), interfaces.end(),
+                                   (*sensor)->configInterface);
+        if (interface == interfaces.end())
+        {
+            continue;
+        }
+
+        context->removeSensor(sensor.value());
+    }
+}
+
+int main()
+{
+    boost::asio::io_context io;
+    auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+    systemBus->request_name("xyz.openbmc_project.NVMeSensor");
+    sdbusplus::asio::object_server objectServer(systemBus, true);
+    objectServer.add_manager("/xyz/openbmc_project/sensors");
+
+    boost::asio::post(io,
+                      [&]() { createSensors(io, objectServer, systemBus); });
+
+    boost::asio::steady_timer filterTimer(io);
+    std::function<void(sdbusplus::message_t&)> eventHandler =
+        [&filterTimer, &io, &objectServer, &systemBus](sdbusplus::message_t&) {
+            // this implicitly cancels the timer
+            filterTimer.expires_after(std::chrono::seconds(1));
+
+            filterTimer.async_wait([&](const boost::system::error_code& ec) {
+                if (ec == boost::asio::error::operation_aborted)
+                {
+                    return; // we're being canceled
+                }
+
+                if (ec)
+                {
+                    std::cerr << "Error: " << ec.message() << "\n";
+                    return;
+                }
+
+                createSensors(io, objectServer, systemBus);
+            });
+        };
+
+    std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
+        setupPropertiesChangedMatches(
+            *systemBus, std::to_array<const char*>({NVMeSensor::sensorType}),
+            eventHandler);
+
+    // Watch for entity-manager to remove configuration interfaces
+    // so the corresponding sensors can be removed.
+    auto ifaceRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
+        static_cast<sdbusplus::bus_t&>(*systemBus),
+        "type='signal',member='InterfacesRemoved',arg0path='" +
+            std::string(inventoryPath) + "/'",
+        [](sdbusplus::message_t& msg) {
+            interfaceRemoved(msg, nvmeDeviceMap);
+        });
+
+    setupManufacturingModeMatch(*systemBus);
+    io.run();
+}
diff --git a/src/nvme/meson.build b/src/nvme/meson.build
new file mode 100644
index 0000000..441d3e7
--- /dev/null
+++ b/src/nvme/meson.build
@@ -0,0 +1,13 @@
+nvme_srcs = files('NVMeSensor.cpp', 'NVMeSensorMain.cpp')
+nvme_srcs += files('NVMeBasicContext.cpp')
+
+nvme_deps = [default_deps, i2c, thresholds_dep, utils_dep, threads]
+src_inc = include_directories('..')
+
+executable(
+    'nvmesensor',
+    sources: nvme_srcs,
+    dependencies: nvme_deps,
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file