root/src/add-ons/kernel/busses/pci/designware/DWPCIController.cpp
/*
 * Copyright 2022, Haiku, Inc.
 * Distributed under the terms of the MIT License.
 */


#include "DWPCIController.h"
#include <bus/FDT.h>

#include <AutoDeleterDrivers.h>
#include <util/AutoLock.h>

#include <string.h>
#include <new>


static uint32
ReadReg8(addr_t adr)
{
        uint32 ofs = adr % 4;
        adr = adr / 4 * 4;
        union {
                uint32 in;
                uint8 out[4];
        } val{.in = *(vuint32*)adr};
        return val.out[ofs];
}


static uint32
ReadReg16(addr_t adr)
{
        uint32 ofs = adr / 2 % 2;
        adr = adr / 4 * 4;
        union {
                uint32 in;
                uint16 out[2];
        } val{.in = *(vuint32*)adr};
        return val.out[ofs];
}


static void
WriteReg8(addr_t adr, uint32 value)
{
        uint32 ofs = adr % 4;
        adr = adr / 4 * 4;
        union {
                uint32 in;
                uint8 out[4];
        } val{.in = *(vuint32*)adr};
        val.out[ofs] = (uint8)value;
        *(vuint32*)adr = val.in;
}


static void
WriteReg16(addr_t adr, uint32 value)
{
        uint32 ofs = adr / 2 % 2;
        adr = adr / 4 * 4;
        union {
                uint32 in;
                uint16 out[2];
        } val{.in = *(vuint32*)adr};
        val.out[ofs] = (uint16)value;
        *(vuint32*)adr = val.in;
}


//#pragma mark - driver


float
DWPCIController::SupportsDevice(device_node* parent)
{
        const char* bus;
        status_t status = gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false);
        if (status < B_OK)
                return -1.0f;

        if (strcmp(bus, "fdt") != 0)
                return 0.0f;

        const char* compatible;
        status = gDeviceManager->get_attr_string(parent, "fdt/compatible", &compatible, false);
        if (status < B_OK)
                return -1.0f;

        // Support only a variant used in HiFive Unmatched board.
        // TODO: Support more Synapsis Designware IP core based PCIe host controllers.
        if (strcmp(compatible, "sifive,fu740-pcie") != 0)
                return 0.0f;

        return 1.0f;
}


status_t
DWPCIController::RegisterDevice(device_node* parent)
{
        device_attr attrs[] = {
                { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "Designware PCI Host Controller"} },
                { B_DEVICE_FIXED_CHILD, B_STRING_TYPE, {.string = "bus_managers/pci/root/driver_v1"} },
                {}
        };

        return gDeviceManager->register_node(parent, DESIGNWARE_PCI_DRIVER_MODULE_NAME, attrs, NULL,
                NULL);
}


status_t
DWPCIController::InitDriver(device_node* node, DWPCIController*& outDriver)
{
        ObjectDeleter<DWPCIController> driver(new(std::nothrow) DWPCIController());
        if (!driver.IsSet())
                return B_NO_MEMORY;

        CHECK_RET(driver->InitDriverInt(node));
        outDriver = driver.Detach();
        return B_OK;
}


status_t
DWPCIController::ReadResourceInfo()
{
        DeviceNodePutter<&gDeviceManager> fdtNode(gDeviceManager->get_parent_node(fNode));

        const char* bus;
        CHECK_RET(gDeviceManager->get_attr_string(fdtNode.Get(), B_DEVICE_BUS, &bus, false));
        if (strcmp(bus, "fdt") != 0)
                return B_ERROR;

        fdt_device_module_info *fdtModule;
        fdt_device* fdtDev;
        CHECK_RET(gDeviceManager->get_driver(fdtNode.Get(),
                (driver_module_info**)&fdtModule, (void**)&fdtDev));

        const void* prop;
        int propLen;

        prop = fdtModule->get_prop(fdtDev, "bus-range", &propLen);
        if (prop != NULL && propLen == 8) {
                uint32 busBeg = B_BENDIAN_TO_HOST_INT32(*((uint32*)prop + 0));
                uint32 busEnd = B_BENDIAN_TO_HOST_INT32(*((uint32*)prop + 1));
                dprintf("  bus-range: %" B_PRIu32 " - %" B_PRIu32 "\n", busBeg, busEnd);
        }

        prop = fdtModule->get_prop(fdtDev, "interrupt-map-mask", &propLen);
        if (prop == NULL || propLen != 4 * 4) {
                dprintf("  \"interrupt-map-mask\" property not found or invalid");
                return B_ERROR;
        }
        fInterruptMapMask.childAdr = B_BENDIAN_TO_HOST_INT32(*((uint32*)prop + 0));
        fInterruptMapMask.childIrq = B_BENDIAN_TO_HOST_INT32(*((uint32*)prop + 3));

        prop = fdtModule->get_prop(fdtDev, "interrupt-map", &propLen);
        fInterruptMapLen = (uint32)propLen / (6 * 4);
        fInterruptMap.SetTo(new(std::nothrow) InterruptMap[fInterruptMapLen]);
        if (!fInterruptMap.IsSet())
                return B_NO_MEMORY;

        for (uint32_t *it = (uint32_t*)prop; (uint8_t*)it - (uint8_t*)prop < propLen; it += 6) {
                size_t i = (it - (uint32_t*)prop) / 6;

                fInterruptMap[i].childAdr = B_BENDIAN_TO_HOST_INT32(*(it + 0));
                fInterruptMap[i].childIrq = B_BENDIAN_TO_HOST_INT32(*(it + 3));
                fInterruptMap[i].parentIrqCtrl = B_BENDIAN_TO_HOST_INT32(*(it + 4));
                fInterruptMap[i].parentIrq = B_BENDIAN_TO_HOST_INT32(*(it + 5));
        }

        dprintf("  interrupt-map:\n");
        for (size_t i = 0; i < fInterruptMapLen; i++) {
                dprintf("    ");
                // child unit address
                PciAddress pciAddress{.val = fInterruptMap[i].childAdr};
                dprintf("bus: %" B_PRIu32, pciAddress.bus);
                dprintf(", dev: %" B_PRIu32, pciAddress.device);
                dprintf(", fn: %" B_PRIu32, pciAddress.function);

                dprintf(", childIrq: %" B_PRIu32, fInterruptMap[i].childIrq);
                dprintf(", parentIrq: (%" B_PRIu32, fInterruptMap[i].parentIrqCtrl);
                dprintf(", %" B_PRIu32, fInterruptMap[i].parentIrq);
                dprintf(")\n");
                if (i % 4 == 3 && (i + 1 < fInterruptMapLen))
                        dprintf("\n");
        }

        prop = fdtModule->get_prop(fdtDev, "ranges", &propLen);
        if (prop == NULL) {
                dprintf("  \"ranges\" property not found");
                return B_ERROR;
        }
        dprintf("  ranges:\n");
        for (uint32_t *it = (uint32_t*)prop; (uint8_t*)it - (uint8_t*)prop < propLen; it += 7) {
                dprintf("    ");
                uint32_t type      = B_BENDIAN_TO_HOST_INT32(*(it + 0));
                uint64_t childAdr  = B_BENDIAN_TO_HOST_INT64(*(uint64_t*)(it + 1));
                uint64_t parentAdr = B_BENDIAN_TO_HOST_INT64(*(uint64_t*)(it + 3));
                uint64_t len       = B_BENDIAN_TO_HOST_INT64(*(uint64_t*)(it + 5));

                pci_resource_range range = {};
                range.host_address = parentAdr;
                range.pci_address = childAdr;
                range.size = len;

                if ((type & fdtPciRangePrefechable) != 0)
                        range.address_type |= PCI_address_prefetchable;

                switch (type & fdtPciRangeTypeMask) {
                case fdtPciRangeIoPort:
                        range.type = B_IO_PORT;
                        fResourceRanges.Add(range);
                        break;
                case fdtPciRangeMmio32Bit:
                        range.type = B_IO_MEMORY;
                        range.address_type |= PCI_address_type_32;
                        fResourceRanges.Add(range);
                        break;
                case fdtPciRangeMmio64Bit:
                        range.type = B_IO_MEMORY;
                        range.address_type |= PCI_address_type_64;
                        fResourceRanges.Add(range);
                        break;
                }

                switch (type & fdtPciRangeTypeMask) {
                case fdtPciRangeConfig:    dprintf("CONFIG"); break;
                case fdtPciRangeIoPort:    dprintf("IOPORT"); break;
                case fdtPciRangeMmio32Bit: dprintf("MMIO32"); break;
                case fdtPciRangeMmio64Bit: dprintf("MMIO64"); break;
                }

                dprintf(" (0x%08" B_PRIx32 "): ", type);
                dprintf("child: %08" B_PRIx64, childAdr);
                dprintf(", parent: %08" B_PRIx64, parentAdr);
                dprintf(", len: %" B_PRIx64 "\n", len);
        }
        return B_OK;
}


status_t
DWPCIController::InitDriverInt(device_node* node)
{
        fNode = node;
        dprintf("+DWPCIController::InitDriver()\n");

        CHECK_RET(ReadResourceInfo());

        DeviceNodePutter<&gDeviceManager> fdtNode(gDeviceManager->get_parent_node(node));

        fdt_device_module_info *fdtModule;
        fdt_device* fdtDev;
        CHECK_RET(gDeviceManager->get_driver(fdtNode.Get(),
                (driver_module_info**)&fdtModule, (void**)&fdtDev));

        if (!fdtModule->get_reg(fdtDev, 0, &fDbiPhysBase, &fDbiSize))
                return B_ERROR;
        dprintf("  DBI: %08" B_PRIx64 ", %08" B_PRIx64 "\n", fDbiPhysBase, fDbiSize);

        if (!fdtModule->get_reg(fdtDev, 1, &fConfigPhysBase, &fConfigSize))
                return B_ERROR;
        dprintf("  config: %08" B_PRIx64 ", %08" B_PRIx64 "\n", fConfigPhysBase, fConfigSize);

        uint64 msiIrq;
        if (!fdtModule->get_interrupt(fdtDev, 0, NULL, &msiIrq))
                return B_ERROR;

        fDbiArea.SetTo(map_physical_memory("PCI DBI MMIO", fDbiPhysBase, fDbiSize, B_ANY_KERNEL_ADDRESS,
                B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, (void**)&fDbiBase));
        CHECK_RET(fDbiArea.Get());

        fConfigArea.SetTo(map_physical_memory("PCI Config MMIO", fConfigPhysBase, fConfigSize,
                B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, (void**)&fConfigBase));
        CHECK_RET(fConfigArea.Get());

        CHECK_RET(fIrqCtrl.Init(GetDbuRegs(), msiIrq));

        AtuDump();

        dprintf("-DWPCIController::InitDriver()\n");
        return B_OK;
}


void
DWPCIController::UninitDriver()
{
        delete this;
}


addr_t
DWPCIController::ConfigAddress(uint8 bus, uint8 device, uint8 function, uint16 offset)
{
        uint32 atuType;
        if (bus == 0) {
                if (device != 0 || function != 0)
                        return 0;
                return fDbiBase + offset;
        } else if (bus == 1)
                atuType = kPciAtuTypeCfg0;
        else
                atuType = kPciAtuTypeCfg1;

        uint64 address = (uint64)(PciAddress {
                .function = function,
                .device = device,
                .bus = bus
        }.val) << 8;

        status_t res = AtuMap(1, kPciAtuOutbound, atuType, fConfigPhysBase, address, fConfigSize);
        if (res < B_OK)
                return 0;

        return fConfigBase + offset;
}


//#pragma mark - PCI controller


status_t
DWPCIController::ReadConfig(uint8 bus, uint8 device, uint8 function,
        uint16 offset, uint8 size, uint32& value)
{
        InterruptsSpinLocker lock(fLock);

        addr_t address = ConfigAddress(bus, device, function, offset);
        if (address == 0)
                return ERANGE;

        switch (size) {
                case 1: value = ReadReg8(address); break;
                case 2: value = ReadReg16(address); break;
                case 4: value = *(vuint32*)address; break;
                default:
                        return B_BAD_VALUE;
        }

        return B_OK;
}


status_t
DWPCIController::WriteConfig(uint8 bus, uint8 device, uint8 function,
        uint16 offset, uint8 size, uint32 value)
{
        InterruptsSpinLocker lock(fLock);

        addr_t address = ConfigAddress(bus, device, function, offset);
        if (address == 0)
                return ERANGE;

        switch (size) {
                case 1: WriteReg8(address, value); break;
                case 2: WriteReg16(address, value); break;
                case 4: *(vuint32*)address = value; break;
                default:
                        return B_BAD_VALUE;
        }

        return B_OK;
}


status_t
DWPCIController::GetMaxBusDevices(int32& count)
{
        count = 32;
        return B_OK;
}


status_t
DWPCIController::ReadIrq(uint8 bus, uint8 device, uint8 function,
        uint8 pin, uint8& irq)
{
        return B_UNSUPPORTED;
}


status_t
DWPCIController::WriteIrq(uint8 bus, uint8 device, uint8 function,
        uint8 pin, uint8 irq)
{
        return B_UNSUPPORTED;
}


status_t
DWPCIController::GetRange(uint32 index, pci_resource_range* range)
{
        if (index >= (uint32)fResourceRanges.Count())
                return B_BAD_INDEX;

        *range = fResourceRanges[index];
        return B_OK;
}


//#pragma mark - DWPCIController


status_t
DWPCIController::AtuMap(uint32 index, uint32 direction, uint32 type, uint64 parentAdr,
        uint64 childAdr, uint32 size)
{
        /*
        dprintf("AtuMap(%" B_PRIu32 ", %" B_PRIu32 ", %#" B_PRIx64 ", %#" B_PRIx64 ", "
                "%#" B_PRIx32 ")\n", index, type, parentAdr, childAdr, size);
        */
        volatile PciAtuRegs* atu = (PciAtuRegs*)(fDbiBase + kPciAtuOffset
                + (2 * index + direction) * sizeof(PciAtuRegs));

        atu->baseLo = (uint32)parentAdr;
        atu->baseHi = (uint32)(parentAdr >> 32);
        atu->limit = (uint32)(parentAdr + size - 1);
        atu->targetLo = (uint32)childAdr;
        atu->targetHi = (uint32)(childAdr >> 32);
        atu->ctrl1 = type;
        atu->ctrl2 = kPciAtuEnable;

        for (;;) {
                if ((atu->ctrl2 & kPciAtuEnable) != 0)
                        break;
        }

        return B_OK;
}


void
DWPCIController::AtuDump()
{
        dprintf("ATU:\n");
        for (uint32 direction = 0; direction < 2; direction++) {
                switch (direction) {
                        case kPciAtuOutbound:
                                dprintf("  outbound:\n");
                                break;
                        case kPciAtuInbound:
                                dprintf("  inbound:\n");
                                break;
                }

                for (uint32 index = 0; index < 8; index++) {
                        volatile PciAtuRegs* atu = (PciAtuRegs*)(fDbiBase
                                + kPciAtuOffset + (2 * index + direction) * sizeof(PciAtuRegs));

                        dprintf("    %" B_PRIu32 ": ", index);
                        dprintf("base: %#08" B_PRIx64, atu->baseLo + ((uint64)atu->baseHi << 32));
                        dprintf(", limit: %#08" B_PRIx32, atu->limit);
                        dprintf(", target: %#08" B_PRIx64, atu->targetLo
                                + ((uint64)atu->targetHi << 32));
                        dprintf(", ctrl1: ");
                        uint32 ctrl1 = atu->ctrl1;
                        switch (ctrl1) {
                                case kPciAtuTypeMem:
                                        dprintf("mem");
                                        break;
                                case kPciAtuTypeIo:
                                        dprintf("io");
                                        break;
                                case kPciAtuTypeCfg0:
                                        dprintf("cfg0");
                                        break;
                                case kPciAtuTypeCfg1:
                                        dprintf("cfg1");
                                        break;
                                default:
                                        dprintf("? (%#" B_PRIx32 ")", ctrl1);
                        }
                        dprintf(", ctrl2: {");
                        uint32 ctrl2 = atu->ctrl2;
                        bool first = true;
                        for (uint32 i = 0; i < 32; i++) {
                                if (((1 << i) & ctrl2) != 0) {
                                        if (first)
                                                first = false;
                                        else
                                                dprintf(", ");
                                        switch (i) {
                                                case 30:
                                                        dprintf("barModeEnable");
                                                        break;
                                                case 31:
                                                        dprintf("enable");
                                                        break;
                                                default:
                                                        dprintf("? (%" B_PRIu32 ")", i);
                                                        break;
                                        }
                                }
                        }
                        dprintf("}\n");
                }
        }
}