Newer
Older
minerva / Kernel / Bus / USB / xHCI / PCIxHCIController.cpp
@minerva minerva on 13 Jul 3 KB Initial commit
/*
 * Copyright (c) 2024, Idan Horowitz <idan.horowitz@serenityos.org>
 * Copyright (c) 2025, Sönke Holz <sholz8530@gmail.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Bus/PCI/BarMapping.h>
#include <Kernel/Bus/PCI/IDs.h>
#include <Kernel/Bus/USB/xHCI/PCIxHCIController.h>
#include <Kernel/Bus/USB/xHCI/xHCIInterrupter.h>

namespace Kernel::USB {

ErrorOr<NonnullLockRefPtr<PCIxHCIController>> PCIxHCIController::try_to_initialize(PCI::DeviceIdentifier const& pci_device_identifier)
{
    PCI::enable_bus_mastering(pci_device_identifier);
    PCI::enable_memory_space(pci_device_identifier);

    auto registers_mapping = TRY(PCI::map_bar<u8>(pci_device_identifier, PCI::HeaderType0BaseRegister::BAR0));
    auto controller = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) PCIxHCIController(pci_device_identifier, move(registers_mapping))));

    auto interrupt_type = TRY(controller->reserve_irqs(1, true)); // TODO: Support more than one interrupter using MSI/MSI-X
    controller->m_using_message_signalled_interrupts = interrupt_type != PCI::InterruptType::PIN;

    TRY(controller->initialize());
    return controller;
}

UNMAP_AFTER_INIT PCIxHCIController::PCIxHCIController(PCI::DeviceIdentifier const& pci_device_identifier, Memory::TypedMapping<u8> registers_mapping)
    : xHCIController(move(registers_mapping))
    , PCI::Device(pci_device_identifier)
{
    if (device_identifier().hardware_id().vendor_id == PCI::VendorID::Intel)
        intel_quirk_enable_xhci_ports();
}

void PCIxHCIController::intel_quirk_enable_xhci_ports()
{
    // Intel chipsets that include both xHCI and EHCI USB controllers default to configuring their USB ports to be attached to the EHCI controller,
    // Attach them to the xHCI controller instead.
    bool ehci_controller_found = false;
    MUST(PCI::enumerate([&ehci_controller_found](PCI::DeviceIdentifier const& device_identifier) {
        if (device_identifier.hardware_id().vendor_id != PCI::VendorID::Intel
            || device_identifier.class_code() != PCI::ClassID::SerialBus
            || device_identifier.subclass_code() != PCI::SerialBus::SubclassID::USB
            || device_identifier.prog_if() != PCI::SerialBus::USBProgIf::EHCI)
            return;
        ehci_controller_found = true;
    }));
    if (!ehci_controller_found)
        return;
    dmesgln_xhci("Switching Intel chipset USB ports to xHCI instead of EHCI");
    SpinlockLocker const locker(device_identifier().operation_lock());
    // Enable USB3 Ports
    PCI::write32_locked(device_identifier(), intel_xhci_usb3_port_super_speed_enable_offset, PCI::read32_locked(device_identifier(), intel_xhci_usb3_port_routing_mask_offset));
    // Enable USB2 Ports
    PCI::write32_locked(device_identifier(), intel_xhci_usb2_port_routing_offset, PCI::read32_locked(device_identifier(), intel_xhci_usb2_port_routing_mask_offset));
}

ErrorOr<OwnPtr<GenericInterruptHandler>> PCIxHCIController::create_interrupter(u16 interrupter_id)
{
    return TRY(xHCIPCIInterrupter::create(*this, interrupter_id));
}

}