#include <cstdint>
#include <dlfcn.h>
#include <iostream>
#include <stdexcept>
#include <string>

namespace {
template <typename T> T symbol(void *library, const char *name) {
  auto *result = dlsym(library, name);
  if (!result) throw std::runtime_error(std::string("missing symbol ") + name);
  return reinterpret_cast<T>(result);
}
} // namespace

int main(int argc, char **argv) {
  if (argc != 6) {
    std::cerr << "usage: sdk_pcie_probe LIB DEVICE TILE ADDRESS VALUE\n";
    return 2;
  }
  try {
    void *library = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);
    if (!library) throw std::runtime_error(dlerror());
    using Attach = int (*)(int);
    using Detach = int (*)(int);
    using ReadConfig = int (*)(int, std::uint64_t, std::uint32_t *);
    using WriteConfig = int (*)(int, std::uint64_t, std::uint32_t);
    using WriteExchange = int (*)(int, std::uint64_t, const void *, unsigned);
    const auto attach = symbol<Attach>(library, "PCIe_attach");
    const auto detach = symbol<Detach>(library, "PCIe_detach");
    const auto readConfig =
        symbol<ReadConfig>(library, "PCIe_read_config_space");
    const auto writeConfig =
        symbol<WriteConfig>(library, "PCIe_write_config_space");
    const auto writeExchange =
        symbol<WriteExchange>(library, "PCIe_write_exchange_space");

    const int device = std::stoi(argv[2]);
    const unsigned tile = std::stoul(argv[3], nullptr, 0);
    const std::uint32_t address = std::stoul(argv[4], nullptr, 0);
    const std::uint32_t value = std::stoul(argv[5], nullptr, 0);
    if (attach(device) != 0) throw std::runtime_error("PCIe_attach failed");
    std::uint32_t before = 0, selected = 0, after = 0;
    readConfig(device, 0x3054, &before);
    writeConfig(device, 0x3054, tile << 17);
    readConfig(device, 0x3054, &selected);
    const int writeResult =
        writeExchange(device, address - 0x4c000, &value, sizeof(value));
    readConfig(device, 0x3054, &after);
    detach(device);
    std::cout << std::hex << "before=0x" << before << " selected=0x"
              << selected << " after=0x" << after << std::dec
              << " exchangeResult=" << writeResult << '\n';
    dlclose(library);
  } catch (const std::exception &error) {
    std::cerr << "sdk_pcie_probe: " << error.what() << '\n';
    return 1;
  }
}
