Newer
Older
minerva / Kernel / Arch / aarch64 / PCI / Controller / BCM2712HostController.cpp
@minerva minerva on 13 Jul 9 KB Initial commit
/*
 * Copyright (c) 2024-2025, Sönke Holz <sholz8530@gmail.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <Kernel/Arch/Delay.h>
#include <Kernel/Boot/CommandLine.h>
#include <Kernel/Bus/PCI/Access.h>
#include <Kernel/Bus/PCI/DeviceTreeHelpers.h>
#include <Kernel/Firmware/DeviceTree/DeviceTree.h>
#include <Kernel/Firmware/DeviceTree/Driver.h>
#include <Kernel/Firmware/DeviceTree/Management.h>
#include <Kernel/Memory/TypedMapping.h>

namespace Kernel::PCI {

// This driver requires the host controller to be already initialized by the firmware.

// This host controller is not ECAM-compliant.
// The config space is accessible through a single 4K window that can be mapped to any bus/device/function.
// The b/d/f can be configured by writing an ECAM offset to Registers::config_space_window_address.

struct Registers {
    u8 unknown1[0x400c];

    // Bus base [31:0] @ [31:0]
    u32 bus_window_base_low; // 0x400c

    // Bus base [63:32] @ [31:0]
    u32 bus_window_base_high; // 0x4010

    u8 unknown2[0x4064 - (0x4010 + 4)];

    enum class Control : u32 {
        PERST_N = 1 << 2,
    };
    Control control; // 0x4064

    enum class State : u32 {
        LinkUp = 0b11 << 4,
    };
    State state; // 0x4068

    u8 unknown3[0x4070 - (0x4068 + 4)];

    // The host controller doesn't seem to support 16-bit accesses, so the base and limit are grouped into one field.
    // CPU limit [31:20] @ [31:20]
    // CPU base [31:20] @ [15:4]
    u32 cpu_window_base_low_and_cpu_window_limit_low; // 0x4070

    u8 unknown4[0x4080 - (0x4070 + 4)];

    // CPU base [39:32] @ [7:0]
    u32 cpu_window_base_high; // 0x4080

    // CPU limit [39:32] @ [7:0]
    u32 cpu_window_limit_high; // 0x4084

    u8 unknown5[0x8000 - (0x4084 + 4)];

    u8 config_space_window[0x1000];  // 0x8000
    u32 config_space_window_address; // 0x9000

    u8 unknown6[0x9310 - (0x9000 + 4)]; // 0x9000
};
static_assert(AssertSize<Registers, 0x9310>());

AK_ENUM_BITWISE_OPERATORS(Registers::Control)
AK_ENUM_BITWISE_OPERATORS(Registers::State)

class BCM2712HostController : public HostController {
public:
    static ErrorOr<NonnullOwnPtr<BCM2712HostController>> create(DeviceTree::Device const& device);

private:
    ErrorOr<VirtualAddress> map_config_space_for(BusNumber, DeviceNumber, FunctionNumber);

    virtual void write8_field_locked(BusNumber, DeviceNumber, FunctionNumber, u32 field, u8 value) override;
    virtual void write16_field_locked(BusNumber, DeviceNumber, FunctionNumber, u32 field, u16 value) override;
    virtual void write32_field_locked(BusNumber, DeviceNumber, FunctionNumber, u32 field, u32 value) override;

    virtual u8 read8_field_locked(BusNumber, DeviceNumber, FunctionNumber, u32 field) override;
    virtual u16 read16_field_locked(BusNumber, DeviceNumber, FunctionNumber, u32 field) override;
    virtual u32 read32_field_locked(BusNumber, DeviceNumber, FunctionNumber, u32 field) override;

    explicit BCM2712HostController(PCI::Domain const&, Memory::TypedMapping<Registers volatile>);

    Memory::TypedMapping<Registers volatile> m_registers;
};

ErrorOr<NonnullOwnPtr<BCM2712HostController>> BCM2712HostController::create(DeviceTree::Device const& device)
{
    auto domain = TRY(determine_pci_domain_for_devicetree_node(device.node(), device.node_name()));
    auto registers_resource = TRY(device.get_resource(0));

    if (registers_resource.size < sizeof(Registers))
        return ERANGE;

    auto const* parent_node = device.node().parent();
    if (parent_node == nullptr)
        return EINVAL;

    auto registers = TRY(Memory::map_typed_writable<Registers volatile>(registers_resource.paddr));

    for (auto range : TRY(device.node().ranges())) {
        auto pci_address = TRY(range.child_bus_address().as<OpenFirmwareAddress>());
        auto cpu_base = TRY(TRY(parent_node->translate_child_bus_address_to_root_address(range.parent_bus_address())).as_flatptr());
        auto range_size = TRY(range.length().as_size_t());

        // FIXME: Configure the 64-bit window.
        if (pci_address.space_type != OpenFirmwareAddress::SpaceType::Memory32BitSpace)
            continue;

        auto bus_base = pci_address.io_or_memory_space_address;
        auto cpu_limit = cpu_base + range_size;

        // Configure the CPU -> PCIe window. This determines how CPU addresses are mapped to PCIe addresses.

        registers->bus_window_base_low = bus_base & 0xffff'ffff;
        registers->bus_window_base_high = bus_base >> 32;

        registers->cpu_window_base_low_and_cpu_window_limit_low = (cpu_limit & 0xfff0'0000) | ((cpu_base & 0xfff0'0000) >> 16);
        registers->cpu_window_base_high = cpu_base >> 32;
        registers->cpu_window_limit_high = cpu_limit >> 32;

        // FIXME: Are we guaranteed to only have one 32-bit window?
        break;
    }

    // Deassert PERST# to initialize the link.
    registers->control |= Registers::Control::PERST_N;

    microseconds_delay(100'000);

    // Check the link state.
    if ((registers->state & Registers::State::LinkUp) != Registers::State::LinkUp) {
        dbgln("{}: Link down", device.node_name());

        // We failed to initialize the link; assert PERST# again.
        registers->control &= ~Registers::Control::PERST_N;
        return EIO;
    }

    dbgln("{}: Link up", device.node_name());

    return TRY(adopt_nonnull_own_or_enomem(new (nothrow) BCM2712HostController(domain, move(registers))));
}

BCM2712HostController::BCM2712HostController(PCI::Domain const& domain, Memory::TypedMapping<Registers volatile> registers)
    : HostController(domain)
    , m_registers(move(registers))
{
}

ErrorOr<VirtualAddress> BCM2712HostController::map_config_space_for(BusNumber bus, DeviceNumber device, FunctionNumber function)
{
    if (bus == 0) {
        if (device != 0 || function != 0)
            return EINVAL;

        return m_registers.base_address();
    }

    u32 const address = (bus.value() << 20) | (device.value() << 15) | (function.value() << 12);
    m_registers->config_space_window_address = address;

    return VirtualAddress { bit_cast<FlatPtr>(&m_registers->config_space_window) };
}

void BCM2712HostController::write8_field_locked(BusNumber bus, DeviceNumber device, FunctionNumber function, u32 field, u8 value)
{
    VERIFY(m_access_lock.is_locked());

    auto vaddr_or_error = map_config_space_for(bus, device, function);
    if (vaddr_or_error.is_error())
        return;

    *reinterpret_cast<u8 volatile*>(vaddr_or_error.release_value().offset(field).as_ptr()) = value;
}

void BCM2712HostController::write16_field_locked(BusNumber bus, DeviceNumber device, FunctionNumber function, u32 field, u16 value)
{
    VERIFY(m_access_lock.is_locked());
    VERIFY(field % sizeof(u16) == 0);

    auto vaddr_or_error = map_config_space_for(bus, device, function);
    if (vaddr_or_error.is_error())
        return;

    *reinterpret_cast<u16 volatile*>(vaddr_or_error.release_value().offset(field).as_ptr()) = value;
}

void BCM2712HostController::write32_field_locked(BusNumber bus, DeviceNumber device, FunctionNumber function, u32 field, u32 value)
{
    VERIFY(m_access_lock.is_locked());
    VERIFY(field % sizeof(u32) == 0);

    auto vaddr_or_error = map_config_space_for(bus, device, function);
    if (vaddr_or_error.is_error())
        return;

    *reinterpret_cast<u32 volatile*>(vaddr_or_error.release_value().offset(field).as_ptr()) = value;
}

u8 BCM2712HostController::read8_field_locked(BusNumber bus, DeviceNumber device, FunctionNumber function, u32 field)
{
    VERIFY(m_access_lock.is_locked());

    auto vaddr_or_error = map_config_space_for(bus, device, function);
    if (vaddr_or_error.is_error())
        return 0xff;

    return *reinterpret_cast<u8 volatile*>(vaddr_or_error.release_value().offset(field).as_ptr());
}

u16 BCM2712HostController::read16_field_locked(BusNumber bus, DeviceNumber device, FunctionNumber function, u32 field)
{
    VERIFY(m_access_lock.is_locked());
    VERIFY(field % sizeof(u16) == 0);

    auto vaddr_or_error = map_config_space_for(bus, device, function);
    if (vaddr_or_error.is_error())
        return 0xffff;

    return *reinterpret_cast<u16 volatile*>(vaddr_or_error.release_value().offset(field).as_ptr());
}

u32 BCM2712HostController::read32_field_locked(BusNumber bus, DeviceNumber device, FunctionNumber function, u32 field)
{
    VERIFY(m_access_lock.is_locked());
    VERIFY(field % sizeof(u32) == 0);

    auto vaddr_or_error = map_config_space_for(bus, device, function);
    if (vaddr_or_error.is_error())
        return 0xffff'ffff;

    return *reinterpret_cast<u32 volatile*>(vaddr_or_error.release_value().offset(field).as_ptr());
}

static constinit Array const compatibles_array = {
    "brcm,bcm2712-pcie"sv,
};

DEVICETREE_DRIVER(BCM2712PCIeHostControllerDriver, compatibles_array);

// https://www.kernel.org/doc/Documentation/devicetree/bindings/pci/brcm%2Cstb-pcie.yaml
ErrorOr<void> BCM2712PCIeHostControllerDriver::probe(DeviceTree::Device const& device, StringView) const
{
    if (kernel_command_line().is_pci_disabled())
        return {};

    auto host_controller = TRY(BCM2712HostController::create(device));

    TRY(configure_devicetree_host_controller(*host_controller, device.node(), device.node_name()));
    Access::the().add_host_controller(move(host_controller));

    return {};
}

}