tools/pci: refactor PCI bridge

Use polymorphism to handle the differences between Aspeed and Nuvoton
PCI devices.

Add unit tests (now at 100% line coverage for tools/pci.cpp).

Signed-off-by: Benjamin Fair <benjaminfair@google.com>
Change-Id: I43e63ec5eb9fce5fb0fc74e0e69667dd13b7433f
diff --git a/tools/pci.cpp b/tools/pci.cpp
index 7fa4d59..6e9cfe4 100644
--- a/tools/pci.cpp
+++ b/tools/pci.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 Google Inc.
+ * Copyright 2020 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,58 +16,141 @@
 
 #include "pci.hpp"
 
+#include "tool_errors.hpp"
+
 extern "C"
 {
 #include <pciaccess.h>
 } // extern "C"
 
-#include <linux/pci_regs.h>
+#include <fmt/format.h>
+
+#include <stdplus/handle/managed.hpp>
 
 #include <cstring>
-#include <optional>
-#include <vector>
+#include <system_error>
 
 namespace host_tool
 {
 
-std::vector<PciDevice>
-    PciUtilImpl::getPciDevices(std::optional<PciFilter> filter)
+namespace
 {
-    struct pci_id_match match = {PCI_MATCH_ANY, PCI_MATCH_ANY, PCI_MATCH_ANY,
-                                 PCI_MATCH_ANY};
-    std::vector<PciDevice> results;
 
-    if (filter.has_value())
+/** @brief RAII wrapper and its destructor for creating a pci_device_iterator */
+static void closeIt(struct pci_device_iterator*&& it,
+                    const PciAccess* const& pci)
+{
+    pci->pci_iterator_destroy(it);
+}
+using It = stdplus::Managed<struct pci_device_iterator*,
+                            const PciAccess* const>::Handle<closeIt>;
+
+} // namespace
+
+PciAccessBridge::PciAccessBridge(const struct pci_id_match* match, int bar,
+                                 std::size_t dataOffset, std::size_t dataLength,
+                                 const PciAccess* pci) :
+    dataOffset(dataOffset),
+    dataLength(dataLength), pci(pci)
+{
+    It it(pci->pci_id_match_iterator_create(match), pci);
+
+    while ((dev = pci->pci_device_next(*it)))
     {
-        match.vendor_id = filter.value().vid;
-        match.device_id = filter.value().did;
-    }
-
-    auto it = pci_id_match_iterator_create(&match);
-    struct pci_device* dev;
-
-    while ((dev = pci_device_next(it)))
-    {
-        PciDevice item;
-
-        pci_device_probe(dev);
-
-        item.bus = dev->bus;
-        item.dev = dev->dev;
-        item.func = dev->func;
-        item.vid = dev->vendor_id;
-        item.did = dev->device_id;
-
-        for (int i = 0; i < PCI_STD_NUM_BARS; i++)
+        int ret = pci->pci_device_probe(dev);
+        if (ret)
         {
-            item.bars[i] = dev->regions[i].base_addr;
+            throw std::system_error(ret, std::generic_category(),
+                                    "Error probing PCI device");
         }
 
-        results.push_back(item);
+        /* Verify it's a memory-based bar. */
+        if (!dev->regions[bar].is_IO)
+            break;
     }
 
-    pci_iterator_destroy(it);
-    return results;
+    if (!dev)
+    {
+        throw NotFoundException(fmt::format(
+            "PCI device {:#04x}:{:#04x}", match->vendor_id, match->device_id));
+    }
+
+    std::fprintf(stderr, "Find [0x%x 0x%x] \n", match->vendor_id,
+                 match->device_id);
+    std::fprintf(stderr, "bar%d[0x%x] \n", bar,
+                 static_cast<unsigned int>(dev->regions[bar].base_addr));
+
+    size = dev->regions[bar].size;
+    int ret = pci->pci_device_map_range(
+        dev, dev->regions[bar].base_addr, dev->regions[bar].size,
+        PCI_DEV_MAP_FLAG_WRITABLE, reinterpret_cast<void**>(&addr));
+    if (ret)
+    {
+        throw std::system_error(ret, std::generic_category(),
+                                "Error mapping PCI device memory");
+    }
+}
+
+PciAccessBridge::~PciAccessBridge()
+{
+    int ret = pci->pci_device_unmap_range(dev, addr, size);
+
+    if (ret)
+    {
+        std::fprintf(stderr, "Error while unmapping PCI device memory: %s\n",
+                     std::strerror(ret));
+    }
+}
+
+void PciAccessBridge::write(const stdplus::span<const std::uint8_t> data)
+{
+    if (data.size() > dataLength)
+    {
+        throw ToolException(
+            fmt::format("Write of {} bytes exceeds maximum of {}", data.size(),
+                        dataLength));
+    }
+
+    std::memcpy(addr + dataOffset, data.data(), data.size());
+}
+
+void AspeedPciBridge::enableBridge()
+{
+    /* We sent the open command before this, so the window should be open and
+     * the bridge enabled on the BMC.
+     */
+    std::uint32_t value;
+    std::memcpy(&value, addr + config, sizeof(value));
+
+    if (0 == (value & bridgeEnabled))
+    {
+        std::fprintf(stderr, "Bridge not enabled - Enabling from host\n");
+
+        value |= bridgeEnabled;
+        std::memcpy(addr + config, &value, sizeof(value));
+    }
+
+    std::fprintf(stderr, "The bridge is enabled!\n");
+}
+
+void AspeedPciBridge::disableBridge()
+{
+    /* addr is valid if the constructor completed */
+
+    /* Read current value, and just blindly unset the bit. */
+    std::uint32_t value;
+    std::memcpy(&value, addr + config, sizeof(value));
+
+    value &= ~bridgeEnabled;
+    std::memcpy(addr + config, &value, sizeof(value));
+}
+
+void AspeedPciBridge::configure(const ipmi_flash::PciConfigResponse& configResp)
+{
+    std::fprintf(stderr, "Received address: 0x%x\n", configResp.address);
+
+    /* Configure the mmio to point there. */
+    std::memcpy(addr + bridge, &configResp.address, sizeof(configResp.address));
 }
 
 } // namespace host_tool