为什么我在 QEMU 上使用英特尔 xHC 时没有收到端口状态更改事件的中断?

Why am I not receiving interrupts on Port Status Change Event with the Intel's xHC on QEMU?

我正在编写一个小 OS 内核,它支持 os 具有英特尔 xHC(可扩展 host 控制器)的驱动程序。我已经到了可以通过重置根集线器端口实际生成端口状态更改事件的地步。我正在使用 QEMU 进行虚拟化。

我要求 QEMU 模拟一个 USB 鼠标和一个 USB 键盘,它似乎这样做了,因为当我重置所有根集线器端口时,我实际上得到了 2 个端口状态更改事件。我在中断器 0 的事件环上获得了这些事件。

问题是我无法找出为什么我没有在这些事件上生成中断。

我在这里提供了一个完整的可重现示例。os Bootloader.c 是我通过键入 fs0:bootloader.efi 从 OVMF shell 启动的 UEFI 应用程序。 Bootloader.c 使用 EDK2 工具集编译。我在 Linux Ubuntu 20 上工作。抱歉代码太长了。

文件main.cpp是我的内核的一个完整的最小可复制示例。所有 OS 都使用以下 3 个脚本编译和启动:

编译

g++ -w -static -ffreestanding -nostdlib -c -m64 os/main.cpp -o os/main.o
ld -entry main --oformat elf64-x86-64 --no-dynamic-linker -static -nostdlib os/main.o -oos/kernel.elf

构建(按 o、y、n 的顺序键入,保留默认值,保留默认值,保留默认值,ef00、w、y)

dd if=/dev/zero of=os/disk.img bs=512 count=93750
gdisk os/disk.img  #o n ef00 w
sudo losetup --offset 1048576 --sizelimit 46934528 /dev/loop7 os/disk.img
sudo mkdosfs -F 32 /dev/loop7
sudo mount /dev/loop7 /mnt
sudo cp edk2/BootloaderPkg/Build/DEBUG_GCC5/X64/Bootloader.efi /mnt
sudo cp os/kernel.elf /mnt
sudo umount /mnt
sudo losetup -d /dev/loop7

启动

sudo qemu-system-x86_64 -s -smp 2 -M q35 -m 1024 -device qemu-xhci -device usb-mouse -device usb-kbd -enable-kvm -cpu host -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=/usr/share/OVMF/OVMF_VARS.fd -drive format=raw,file=os/disk.img,if=virtio

要使脚本正常工作,main.cpp 文件需要位于 os 文件夹的主目录中。此外,Bootloader.efi 应用程序需要位于 edk2/BootloaderPkg/Build/DEBUG_GCC5/X64/Bootloader.efi.

当我启动内核时,它似乎工作正常但停止了。我在 UEFI shell 上收到一些消息,但屏幕没有被清除。这意味着没有产生中断。我实际上得到了端口状态更改事件,因为我在内存中验证了我放置事件环的位置(在 0x253000):

您可以在 hexdump 中看到的字节是 xHC 生成的 2 个端口状态更改事件。它的功能只是不抛出中断。

我的主要问题:

  1. 为什么我的代码没有抛出任何中断?我通过创建除以零的错误验证了 IDT 的工作原理,并且它实际上按预期清除了屏幕。

  2. 我应该选择哪个版本的 MSI-X 功能结构ose osdev.org:

以及 PCI 本地总线规范版本 3.0 (http://fpga-faq.narod.ru/PCI_Rev_30.pdf) 中的那个:

  1. 我的代码似乎无法写入 MSI-X table 因为当我写入并读回它时 returns 0。为什么会这样?

  2. 我是否应该在修改不同的寄存器之前通过清除 USBCMD 寄存器中的 Run/Stop 位来停止 xHC?

  3. 内存中实际的 MSI-X table 在哪里?我计算的偏移量是否正确?在我的代码中,我没有屏蔽 BIR,因为它对于 xHC 已经是 0。

Bootloader.c

#include <Uefi.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/GraphicsOutput.h>
#include <Library/BaseLib.h>

#define EFI_ACPI_TABLE_GUID { 0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d }}
#define EFI_ACPI_20_TABLE_GUID { 0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x0, 0x80, 0xc7, 0x3c, 0x88, 0x81 }} 

typedef struct {
        CHAR8   Signature[8];
        UINT8   Checksum;
        UINT8   OemId[6];
        UINT8   Revision;
    UINT32  RsdtAddress;
        UINT32  Length;
        UINT64  XsdtAddress;
        UINT8   ExtendedChecksum;
        UINT8   Reserved[3];
} RSDP;

typedef struct {
    UINT64 Address;
    UINT64 Size;
    UINT64 HorizontalResolution;
    UINT64 VerticalResolution;
    UINT64 PixelsPerScanLine;   
} FrameBuffer;

BOOLEAN CompareGUID(EFI_GUID rguid1, EFI_GUID rguid2){
    return 
   rguid1.Data1 == rguid2.Data1 &&
   rguid1.Data2 == rguid2.Data2 &&
   rguid1.Data3 == rguid2.Data3 &&
   rguid1.Data4[0] == rguid2.Data4[0] &&
   rguid1.Data4[1] == rguid2.Data4[1] &&
   rguid1.Data4[2] == rguid2.Data4[2] &&
   rguid1.Data4[3] == rguid2.Data4[3] &&
   rguid1.Data4[4] == rguid2.Data4[4] &&
   rguid1.Data4[5] == rguid2.Data4[5] &&
   rguid1.Data4[6] == rguid2.Data4[6] &&
   rguid1.Data4[7] == rguid2.Data4[7];
}

void MemCpy(void *dest, void *src, UINTN n){  
    CHAR8* cdest = (CHAR8*) dest;
    CHAR8* csrc = (CHAR8*) src;  
    for (UINTN i = 0; i < n; i++) 
        cdest[i] = csrc[i]; 
} 

EFI_STATUS EFIAPI UefiMain (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE  *SystemTable){
    SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Hello World!\n");
    
    /*Locate RSDP table*/
    RSDP* rsdpPointer = NULL;
    RSDP rsdp;
    EFI_GUID AcpiGuid = EFI_ACPI_20_TABLE_GUID;
    for (UINTN i = 0; i < SystemTable->NumberOfTableEntries; i++){
        if (CompareGUID(SystemTable->ConfigurationTable[i].VendorGuid, AcpiGuid)){
                CHAR8* TablePointer = (CHAR8*) SystemTable->ConfigurationTable[i].VendorTable;
                if (TablePointer[0] == 'R' && TablePointer[1] == 'S' && TablePointer[2] == 'D' && TablePointer[3] == ' ' && 
                TablePointer[4] == 'P' && TablePointer[5] == 'T' && TablePointer[6] == 'R' && TablePointer[7] == ' '){
                    rsdpPointer = (RSDP*)SystemTable->ConfigurationTable[i].VendorTable;
                    rsdp = *rsdpPointer;
                }   
            }
    }
    if (rsdpPointer == NULL){
        SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Could not locate the RSDP.\n");
        goto DONE;
    }else{
        SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Found the RSDP.\n");
    }
    RSDP* RSDPTable = (RSDP*)0x350000;
    *RSDPTable = rsdp;
    
    
    /*Get the kernel's file*/
    SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Booting the kernel.\n");
    
    EFI_STATUS Status;
        EFI_BOOT_SERVICES* BS = SystemTable->BootServices;
    EFI_GUID FSPGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
    EFI_HANDLE* Handles = NULL;   
    UINTN HandleCount = 0;
    Status = BS->LocateHandleBuffer(ByProtocol, &FSPGuid, NULL, &HandleCount, &Handles);
    if (EFI_ERROR(Status)){
        SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Could not locate the handle buffer.\n");
        goto DONE;
    }else{
        SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Located the handle buffer for file system protocol.\n");
    }
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* FS = NULL; 
    EFI_FILE_PROTOCOL* Root = NULL;
    EFI_FILE_PROTOCOL* Token = NULL;
    for (UINTN index = 0; index < (UINTN)HandleCount; index++)
    {
            Status = BS->HandleProtocol(Handles[index], &FSPGuid, (void**)&FS);
        Status = FS->OpenVolume(FS, &Root);
        Status = Root->Open(Root, &Token, L"kernel.elf", EFI_FILE_MODE_READ, EFI_FILE_MODE_WRITE);
        if(!EFI_ERROR(Status))
            break;
    }
    UINTN BufferSize = 100000;
    CHAR8 KernelBuffer[100000];
    Status = Token->Read(Token, &BufferSize, KernelBuffer);
    if(EFI_ERROR(Status)){
        SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Located the kernel, but could not read from it.\n");
        goto DONE;
    }else
        SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Could read the Kernel properly now jumping to it's entry point.\n");
    
    /*Get the frame buffer info*/
    EFI_GUID GOPGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
    EFI_GRAPHICS_OUTPUT_PROTOCOL* GOP;
    Status = BS->LocateProtocol(&GOPGuid, NULL, (void**)&GOP);
    //GOP->SetMode(GOP, GOP->Mode->MaxMode - 1);
        FrameBuffer Video = {GOP->Mode->FrameBufferBase, GOP->Mode->FrameBufferSize, GOP->Mode->Info->HorizontalResolution,
            GOP->Mode->Info->VerticalResolution, GOP->Mode->Info->PixelsPerScanLine};
        FrameBuffer* VideoPointer = (FrameBuffer*)0x351000;
        *VideoPointer = Video;
    
    /*
    Got the kernel's file in a buffer, now map it
    in memory and jump to its entry point
    */
    UINT64 EntryPoint;
    MemCpy(&EntryPoint, KernelBuffer + 24, 8);
    UINT64 ProgramHeaderPosition;
    MemCpy(&ProgramHeaderPosition, KernelBuffer + 32, 8);
    UINT16 NumberOfEntriesInProgramHeader;
    MemCpy(&NumberOfEntriesInProgramHeader, KernelBuffer + 56, 2);
    for (UINTN i = 0; i < NumberOfEntriesInProgramHeader; i++){
        UINT32 SegmentType;
        UINT64 SegmentDataPosition;
        UINT64 SegmentVirtualAddress;
        UINT64 SegmentSizeInFile;
        UINT64 SegmentSizeInMemory;
        MemCpy(&SegmentType, KernelBuffer + ProgramHeaderPosition, 4);
        if (SegmentType == 1){
            MemCpy(&SegmentDataPosition, KernelBuffer + ProgramHeaderPosition + 8, 8);
            MemCpy(&SegmentVirtualAddress, KernelBuffer + ProgramHeaderPosition + 16, 8);
            MemCpy(&SegmentSizeInFile, KernelBuffer + ProgramHeaderPosition + 32, 8);
            MemCpy(&SegmentSizeInMemory, KernelBuffer + ProgramHeaderPosition + 40, 8);
            CHAR8* VirtualAddress = (CHAR8*)SegmentVirtualAddress;
            for (UINT64 i = 0; i < SegmentSizeInMemory; i++){
                if (i < SegmentSizeInFile){
                    *(VirtualAddress + i) = *(KernelBuffer + SegmentDataPosition + i);  
                }else{
                    *(VirtualAddress + i) = 0;
                }
            }
        }
        ProgramHeaderPosition += 56;
    }
    
    /*Final jump to the entry point*/
    SystemTable->BootServices->ExitBootServices(ImageHandle, 0);
    BASE_LIBRARY_JUMP_BUFFER JumpBuffer;
    SetJump(&JumpBuffer);
    JumpBuffer.Rip = EntryPoint;
    LongJump(&JumpBuffer, 1);
  
    DONE:
    return EFI_SUCCESS;
}

main.cpp

typedef unsigned char UINT8;
typedef unsigned short UINT16;
typedef unsigned int UINT32;
typedef unsigned long UINT64;

#define PIC1_COMMAND 0x20
#define PIC2_COMMAND 0xa0
#define PIC1_DATA 0x21
#define PIC2_DATA 0xa1

struct RSDP{
        char signature[8];
        char checksum;
        UINT8 oemID[6];
        UINT8 revision;
    UINT32 rsdtAddress;
        UINT32 length;
        UINT64 xsdtAddress;
        UINT8 extendedChecksum;
        UINT8 reserved[3];
};

struct SDTHeader {
    char signature[4];
    UINT32 length;
    UINT8 revision;
    UINT8 checksum;
    char oemID[6];
    char oemTableid[8];
    UINT32 oemRevision;
    UINT32 creatorID;
    UINT32 creatorRevision;
};

struct RSDT {
    SDTHeader header;
    UINT32 otherSDTs[10];
};

struct MCFG{
    SDTHeader header;
    UINT8 reserved[8];
};

struct PCIConfig{
    UINT64 baseAddress;
    UINT16 segmentGroup;
    UINT8 firstBus;
    UINT8 lastBus;
    UINT32 reserved;
};

struct PortInfo{
    UINT8 revisionMinor;
    UINT8 revisionMajor;
    UINT8 portOffset;
    UINT8 portCount;
};

struct RootHubPortRegistersAddress{
    UINT64 portSCAddress;
    UINT64 portPMSCAddress;
    UINT64 portLIAddress;
};

struct IDTEntry{
    UINT16 offset0;     //bits 0-15
    UINT16 selector;    //0x08 = 1000b code selector
    UINT8 ist;      //0
    UINT8 attrib;       //
    UINT16 offset1;     //bits 16-31
    UINT32 offset2; //bits 31-63
    UINT32 zero;        //0
}__attribute__((packed));

struct IDTR{ //IDTR position will be 0x399ff0 just after the actual idt starting at 0x399000
    UINT16 size; //4080
    UINT64 address; //0x399000
}__attribute__((packed));

struct GDT{
    UINT64 nullDescriptor;
    
    UINT16 codeLimit;
    UINT16 codeBaseLow;
    UINT8 codeBaseMid;
    UINT8 codeFlags;
    UINT8 codeLimitMid;
    UINT8 codeBaseHigh;
    
    UINT16 dataLimit;
    UINT16 dataBaseLow;
    UINT8 dataBaseMid;
    UINT8 dataFlags;
    UINT8 dataLimitMid;
    UINT8 dataBaseHigh;
}__attribute__((packed));

struct GDTR{
    UINT16 size;
    UINT64 address;
}__attribute__((packed));

RSDP* rsdp = (RSDP*)0x350000;
MCFG* mcfg;
PCIConfig pciConfig;
UINT64 baseAddressRegisterSpace;
UINT8 capLength;

UINT64 xhcConfigSpacePhysAddr;

/*Operational registers*/
UINT64 usbCommandAddress;
UINT64 usbStatusAddress;
UINT64 crcrAddress;
UINT64 dcbaaAddress;
UINT64 configAddress;

UINT8 capabilitiesOffset;
UINT16 exCapabilitiesOffset;

/*Capability registers*/
UINT32 hcsParams1; 
UINT32 hccParams1;
UINT32 dbOff;
UINT32 rtsOff;
UINT8 maxPorts;
UINT8 maxDeviceSlots;
UINT16 maxInterrupters;
PortInfo portInfos[8];
UINT32 portInfosLength;
RootHubPortRegistersAddress rootHubPorts[32];

/*Interrupter 0 registers addresses*/
UINT64 iman0Address;
UINT64 imod0Address;
UINT64 eventRingSegmentTableSize0Address;
UINT64 eventRingSegmentTableBaseAddress0Address;
UINT64 eventRintDequeuePointer0Address;

/*Doorbells address*/
UINT64 doorbell0Address;

/*Address of the bottom of the msiXTable*/
UINT64 msiXTableAddress;

/*The IDT entries*/
IDTEntry entries[255];

void OutB(UINT16 Port, UINT8 Val){
    asm volatile ( "outb %0, %1" : : "a"(Val), "Nd"(Port) );
}

void IOWait(){
    asm volatile ( "outb %al, [=14=]x80" );
}

void MaskLegacyPIC(){
    OutB(PIC1_COMMAND, 0x11);       //Initialise sequence
    IOWait();
    OutB(PIC2_COMMAND, 0x11);
    IOWait();
    OutB(PIC1_DATA, 0x20);          //Master PIC vector offset
    IOWait();
    OutB(PIC2_DATA, 0x28);          //Slave pic vector offset
    IOWait();
    OutB(PIC1_DATA, 0xff);
    IOWait();
    OutB(PIC2_DATA, 0xff);
    IOWait();
    OutB(PIC1_DATA, 0x01);
    IOWait();
    OutB(PIC2_DATA, 0x01);
    IOWait();
    OutB(PIC1_DATA, 0xff);
    IOWait();
    OutB(PIC2_DATA, 0xff);
    IOWait();
}

void ACPISetup(){
    RSDT* rsdt;
    rsdt = (RSDT*)rsdp->rsdtAddress;  //74 90 bf 07 = 0x07bf9074
    /*Tables locations with QEMU
    00 60 bf 07 = 0x07bf6000 -> FACP
    00 60 bf 07 = 0x07bf5000 -> APIC
    00 60 bf 07 = 0x07bf4000 -> HPET
    00 60 bf 07 = 0x07bf3000 -> BGRT
    */
    /*IOAPIC              id rs   ioapic addr     global system interrupt base      
    00 00 00 00 c0 fe 00 00  00 00 = 00 00   fe c0 00 00    00 00 00 00
    */
    UINT32 otherSDTsLength = (rsdt->header.length - sizeof(SDTHeader)) / 4;
    for (UINT32 i = 0; i < otherSDTsLength; i++){
        if (*((UINT32*)rsdt->otherSDTs[i]) == 0x4746434D){
            //Found the MCFG
            mcfg = (MCFG*)(rsdt->otherSDTs[i]);
        }
    }
}

void SetXHCIConfigSpacePhysAddr(UINT64 configSpacePhysAddr){
    xhcConfigSpacePhysAddr = configSpacePhysAddr;

    /*Bar0 and Bar1 combining*/
    UINT32* pciPtr = (UINT32*)configSpacePhysAddr;
    UINT32 register4 = *(pciPtr + 4);
    UINT32 register5 = *(pciPtr + 5);
    UINT64 bar0 = (UINT64)register4;
    UINT64 bar1 = (UINT64)register5;
    baseAddressRegisterSpace = (bar1 << 32) + (bar0 & 0xfffffff0);
    
    /*Capability registers*/
    UINT8* xhciRegistersPtr8 = (UINT8*)baseAddressRegisterSpace;
    capLength = *(xhciRegistersPtr8);
    UINT32* xhciRegistersPtr32 = (UINT32*)baseAddressRegisterSpace;
    hcsParams1 = *(xhciRegistersPtr32 + 1);
    hccParams1 = *(xhciRegistersPtr32 + 4);
    dbOff = *(xhciRegistersPtr32 + 5);
    rtsOff = *(xhciRegistersPtr32 + 6);
    maxDeviceSlots = (UINT8)hcsParams1;
    maxInterrupters = (hcsParams1 >> 8);
    maxPorts = (UINT8)(hcsParams1 >> 24);
    
    /*Operational registers addresses*/
    usbCommandAddress = baseAddressRegisterSpace + capLength;
    usbStatusAddress = baseAddressRegisterSpace + capLength + 0x4;
    
    /*
    Should I halt the xHC since it seems to already be started when I launch QEMU.
    It shouldn't allow to modify certain registers while it is not halted.
    This code halts the xHC.
    */
    UINT32* usbCommandPtr = (UINT32*)usbCommandAddress;
    UINT32 usbCommandRegister = *usbCommandPtr;
    usbCommandRegister &= 0xfffffffe;
    *usbCommandPtr = usbCommandRegister;
    
    crcrAddress = baseAddressRegisterSpace + capLength + 0x18;
    dcbaaAddress = baseAddressRegisterSpace + capLength + 0x30;
    configAddress = baseAddressRegisterSpace + capLength + 0x38;
    UINT64 rootHubPortsBase = baseAddressRegisterSpace + capLength + 0x400;
    for (UINT32 i = 0; i < maxPorts; i++){
        rootHubPorts[i].portSCAddress = rootHubPortsBase;
        rootHubPorts[i].portPMSCAddress = rootHubPortsBase + 4;
        rootHubPorts[i].portLIAddress = rootHubPortsBase + 8; 
        rootHubPortsBase += 16;
    }
    
    /*
    Runtime registers parsing
    Here we save only the Interrupter register set 0 because we plan on using only this one.
    */
    UINT64 interrupterRegisterSet0Address = baseAddressRegisterSpace + rtsOff + 0x20;
    iman0Address = interrupterRegisterSet0Address;
    imod0Address = interrupterRegisterSet0Address + 4;
    eventRingSegmentTableSize0Address = interrupterRegisterSet0Address + 8;
    eventRingSegmentTableBaseAddress0Address = interrupterRegisterSet0Address + 16;
    eventRintDequeuePointer0Address = interrupterRegisterSet0Address + 24;
    
    /*Save the doorbell registers base address. 
    The formula to calculate the address of another doorbell
    is doorbell0Address + (i * 4) where i is the doorbell index to retrieve.*/
    doorbell0Address = baseAddressRegisterSpace + dbOff;
    
    /*Parsing the capabilities (MSI-X)*/
    UINT32 registerD = *(pciPtr + 0xd);
    capabilitiesOffset = (UINT8)registerD;
    capabilitiesOffset &= 0xfc;
    UINT64 capAddr = configSpacePhysAddr + capabilitiesOffset;
    UINT32* capPtr = (UINT32*)capAddr;
    UINT32 msiRegister0 = *capPtr;
    *(capPtr + 1) = 0;
    UINT32 msiRegister2 = *(capPtr + 2);
    msiXTableAddress = baseAddressRegisterSpace + msiRegister2;
    
    /*Extended capabilities parsing (Supported Protocols)*/
    exCapabilitiesOffset = (UINT16)(hccParams1 >> 16);
    UINT32* capabilitiesPtr = (UINT32*)baseAddressRegisterSpace;
    capabilitiesPtr += exCapabilitiesOffset;
    UINT32 capRegister0 = *capabilitiesPtr;
    UINT8 nextOffset = (UINT8)(capRegister0 >> 8);
    UINT32 counter = 0;
    while(nextOffset != 0){
        if ((UINT8)capRegister0 == 0x2){
            PortInfo portInfo;
            portInfo.revisionMinor = (UINT8)(capRegister0 >> 16);
            portInfo.revisionMajor = (UINT8)(capRegister0 >> 24);
            UINT32 capRegister2 = *(capabilitiesPtr + 2);
            portInfo.portOffset = (UINT8)capRegister2;
            portInfo.portCount = (UINT8)(capRegister2 >> 8);
            portInfos[counter] = portInfo;
            counter++;
        }
        nextOffset = (UINT8)(capRegister0 >> 8);
        capabilitiesPtr += nextOffset;
        capRegister0 = *capabilitiesPtr;
    }
    portInfosLength = counter;
}

UINT8 GetHeaderType(UINT8 bus, UINT8 device, UINT8 function){
    UINT64 configSpacePhysAddr = pciConfig.baseAddress + 
        ((bus - pciConfig.firstBus) << 20 | device << 15 | function << 12);
    UINT32* ptr = (UINT32*)configSpacePhysAddr;
    UINT32 register3 = *(ptr + 3);
    UINT8 headerType = (UINT8)(register3 >> 16);
    return headerType;
}

UINT16 GetVendorID(UINT8 bus, UINT8 device, UINT8 function){
    UINT64 configSpacePhysAddr = pciConfig.baseAddress + 
        ((bus - pciConfig.firstBus) << 20 | device << 15 | function << 12);
    UINT32* ptr = (UINT32*)configSpacePhysAddr;
    UINT32 register0 = *ptr;
    UINT16 vendorID = (UINT16) register0;
    return vendorID;
}

void CheckFunction(UINT8 bus, UINT8 device, UINT8 function){
    UINT64 configSpacePhysAddr = pciConfig.baseAddress + 
        ((bus - pciConfig.firstBus) << 20 | device << 15 | function << 12);
    UINT32* ptr = (UINT32*)configSpacePhysAddr;
    UINT32 register2 = *(ptr + 2);
    UINT8 classID = (UINT8)(register2 >> 24);
    UINT8 subclassID = (UINT8)(register2 >> 16);
    UINT8 progInterface = (UINT8)(register2 >> 8);
    if (classID == 0x0c && subclassID == 0x03 && progInterface == 0x30){
        SetXHCIConfigSpacePhysAddr(configSpacePhysAddr);
    }
}

void CheckDevice(UINT8 bus, UINT8 device){
    UINT8 function = 0;
    UINT16 vendorID = GetVendorID(bus, device, function);
        if(vendorID == 0xFFFF) 
            return;        // Device doesn't exist
        CheckFunction(bus, device, function);
        UINT8 headerType = GetHeaderType(bus, device, function);
        if((headerType & 0x80) != 0) {
            /* It is a multi-function device, so check remaining functions */
            for(function = 1; function < 8; function++) {
                    if(GetVendorID(bus, device, function) != 0xFFFF) {
                        CheckFunction(bus, device, function);
                    }
            }
        }
}

void CheckBus(UINT8 bus){
     UINT8 device;
     for(device = 0; device < 32; device++) {
         CheckDevice(bus, device);
     }
}

void PCISetup(){
    UINT8* ptr = (UINT8*)(mcfg + 1);    //ptr to pci record
    pciConfig = *((PCIConfig*)ptr);
     
    UINT8 function;
        UINT8 bus;
        UINT8 headerType = GetHeaderType(0, 0, 0);
        if((headerType & 0x80) == 0) {
            /* Single PCI host controller */
            CheckBus(0);
        } else {
            /* Multiple PCI host controllers */
            for(function = 0; function < 8; function++) {
                    if(GetVendorID(0, 0, function) != 0xFFFF)
                        break;
                    bus = function;
                    CheckBus(bus);
            }
        }
}

struct FrameBuffer{
    UINT64 address;
    UINT64 size;
    UINT64 horizontalResolution;
    UINT64 verticalResolution;
    UINT64 pixelsPerScanLine;   
};
FrameBuffer* videoInfo = (FrameBuffer*)0x351000;
void ClearScreen(){
    UINT32* videoPointer = (UINT32*)videoInfo->address;
    for (UINT32 i = 0; i < videoInfo->size; i++)
        *(videoPointer + i) = 0;
}

void isr0() {
    ClearScreen();
    asm volatile("hlt");
}
void isr1() {
    asm volatile("hlt");
}
void isr2() {
    asm volatile("hlt");
}
void isr3() {
    asm volatile("hlt");
}
void isr4() {
    asm volatile("hlt");
}
void isr5() {
    asm volatile("hlt");
}
void isr6() {
    asm volatile("hlt");
}
void isr7() {
    asm volatile("hlt");
}
void isr8() {
    asm volatile("hlt");
}
void isr9() {
    asm volatile("hlt");
}
void isr10() {
    asm volatile("hlt");
}
void isr11() {
    asm volatile("hlt");
}
void isr12() {
    asm volatile("hlt");
}
void isr13() {
    asm volatile("hlt");
}
void isr14() {
    asm volatile("hlt");
}
void isr15() {
    asm volatile("hlt");
}
void isr16() {
    asm volatile("hlt");
}
void isr17() {
    asm volatile("hlt");
}
void isr18() {
    asm volatile("hlt");
}
void isr19() {
    asm volatile("hlt");
}
void isr20() {
    asm volatile("hlt");
}
void isr21() {
    asm volatile("hlt");
}
void isr22() {
    asm volatile("hlt");
}
void isr23() {
    asm volatile("hlt");
}
void isr24() {
    asm volatile("hlt");
}
void isr25() {
    asm volatile("hlt");
}
void isr26() {
    asm volatile("hlt");
}
void isr27() {
    asm volatile("hlt");
}
void isr28() {
    asm volatile("hlt");
}
void isr29() {
    asm volatile("hlt");
}
void isr30() {
    asm volatile("hlt");
}
void isr31() {
    asm volatile("hlt");
}
void isr32() {
    asm volatile("hlt");
}
void isr33() {
    asm volatile("hlt");
}
void isr34() {
    asm volatile("hlt");
}
void isr35() {
    asm volatile("hlt");
}
void isr36() {
    asm volatile("hlt");
}
void isr37() {
    asm volatile("hlt");
}
void isr38() {
    asm volatile("hlt");
}
void isr39() {
    asm volatile("hlt");
}
void isr40() {
    asm volatile("hlt");
}
void isr41() {
    asm volatile("hlt");
}
void isr42() {
    asm volatile("hlt");
}
void isr43() {
    asm volatile("hlt");
}
void isr44() {
    asm volatile("hlt");
}
void isr45() {
    asm volatile("hlt");
}
void isr46() {
    asm volatile("hlt");
}
void isr47() {
    asm volatile("hlt");
}
void isr48() {
    ClearScreen();
    asm volatile("hlt");
}

void SetupIDT(){
    GDT gdt;
    gdt.nullDescriptor = 0;
    gdt.codeLimit = 0xFFFF;
    gdt.codeBaseLow = 0;
    gdt.codeBaseMid = 0;
    gdt.codeFlags = 0x9a;
    gdt.codeLimitMid = 0xaf;
    gdt.codeBaseHigh = 0;
    gdt.dataLimit = 0xFFFF;
    gdt.dataBaseLow = 0;
    gdt.dataBaseMid = 0;
    gdt.dataFlags = 0x92;
    gdt.dataLimitMid = 0x0f;
    gdt.dataBaseHigh = 0;
    GDT* gdtPtr = (GDT*)0x398000;
    *gdtPtr = gdt;
    GDTR gdtr = { 24, 0x398000 };
    GDTR* gdtrPtr = (GDTR*)0x398500;
    *gdtrPtr = gdtr;
    asm volatile("lgdt 0x398500");

     IDTEntry* idtEntryPtr = (IDTEntry*)0x399000;
     UINT64 isrAddresses[255] = {
        (UINT64) &isr0,
        (UINT64) &isr1,
        (UINT64) &isr2,
        (UINT64) &isr3,
        (UINT64) &isr4,
        (UINT64) &isr5,
        (UINT64) &isr6,
        (UINT64) &isr7,
        (UINT64) &isr8,
        (UINT64) &isr9,
        (UINT64) &isr10,
        (UINT64) &isr11,
        (UINT64) &isr12,
        (UINT64) &isr13,
        (UINT64) &isr14,
        (UINT64) &isr15,
        (UINT64) &isr16,
        (UINT64) &isr17,
        (UINT64) &isr18,
        (UINT64) &isr19,
        (UINT64) &isr20,
        (UINT64) &isr21,
        (UINT64) &isr22,
        (UINT64) &isr23,
        (UINT64) &isr24,
        (UINT64) &isr25,
        (UINT64) &isr26,
        (UINT64) &isr27,
        (UINT64) &isr28,
        (UINT64) &isr29,
        (UINT64) &isr30,
        (UINT64) &isr31,
        (UINT64) &isr32,
        (UINT64) &isr33,
        (UINT64) &isr34,
        (UINT64) &isr35,
        (UINT64) &isr36,
        (UINT64) &isr37,
        (UINT64) &isr38,
        (UINT64) &isr39,
        (UINT64) &isr40,
        (UINT64) &isr41,
        (UINT64) &isr42,
        (UINT64) &isr43,
        (UINT64) &isr44,
        (UINT64) &isr45,
        (UINT64) &isr46,
        (UINT64) &isr47,
        (UINT64) &isr48,
     };
     IDTEntry entry;
     for (UINT32 i = 0; i < 58; i++){
        entry.offset0 = (UINT16)isrAddresses[i];
        entry.selector = 0x08;
        entry.ist = 0;
        entry.attrib = 0x8e;
        entry.offset1 = (UINT16)(isrAddresses[i] >> 16);
        entry.offset2 = (UINT32)(isrAddresses[i] >> 32);
        entry.zero = 0;
        *idtEntryPtr = entry;
        idtEntryPtr++;
     }
     IDTR idtr = { 4080, 0x399000 };
     IDTR* idtrPtr = (IDTR*)0x399ff0;
     *idtrPtr = idtr;
     asm volatile("lidt 0x399ff0");
}

void XHCIInit(){
    /*
    We place the Device Context Base Address Array 
    to address 0x250000 where it will grow upward.
    */
    UINT64* dcbaaPtr = (UINT64*)dcbaaAddress;
    *dcbaaPtr = 0x250000;
    
    /*
    We define the CRCR (Dequeue pointer of command ring)
    to address 0x251000 where the command ring will lie.
    We also define a link TRB as the last TRB of the ring.
    We define a ring of 32 TRBs. The 32nd TRB will be the
    link TRB to rollback to the beginning. Each TRB comprises
    16 bytes (4 UINT32). The ring will have a size of 16 * 32 = 512 bytes.
    We thus initialize 512 bytes to 0 then initialize the fields
    of the link TRB to their respective value.
    */
    UINT64* crcrPtr = (UINT64*)crcrAddress;
    *crcrPtr = 0x251000;
    
    UINT32* ptr = (UINT32*)0x251000;
    for (UINT32 i = 0; i < 128; i++)
        ptr[i] = 0;
        
    UINT32* linkTRBPtr = (UINT32*)(0x251000 + 512 - 16);
    *linkTRBPtr = 0x251000;
    *(linkTRBPtr + 1) = 0;
    *(linkTRBPtr + 2) = 0;
    *(linkTRBPtr + 3) = 0x00001802;
    
    /*Define the MSI-X table (only vector 0 for now)*/
    UINT32* msiXTablePtr = (UINT32*)msiXTableAddress;
    *msiXTablePtr = 0xfee00000;
    *(msiXTablePtr + 1) = 0x00000030;
    
    /*
    Initialize interrupter 0. There will be only one segment at address 0x253000
    It will be pointed to by the Event Ring segment table at address 0x252000.
    The size in TRBs of the only segment will be 32 including the link TRB.
    Here we don't define a Link TRB at the end since we just want to get it to
    throw interrupts (will do later).
    */
    UINT64* segmentTablePtr = (UINT64*)0x252000;
    *segmentTablePtr = 0x253000;
    *(segmentTablePtr + 1) = 0x0000000000000020;
    
    UINT32* segmentTableSizePtr = (UINT32*)eventRingSegmentTableSize0Address;
    *segmentTableSizePtr = 0x1;
    
    UINT64* dequeuePtr = (UINT64*)eventRintDequeuePointer0Address;
    *dequeuePtr = 0x253000;
    
    UINT64* segmentTableBaseAddressPtr = (UINT64*)eventRingSegmentTableBaseAddress0Address;
    *segmentTableBaseAddressPtr = 0x252000;
    
    UINT64 capAddr = xhcConfigSpacePhysAddr + capabilitiesOffset;
    UINT32* capPtr = (UINT32*)capAddr;
    UINT32 register0 = *capPtr;
    register0 |= 0x80000000;
    *capPtr = register0;
    
    UINT32* iman0Ptr = (UINT32*)iman0Address;
    *iman0Ptr = 0x00000002;
    
    /*Start the xHC by setting the Run/Stop bit and init interrupts by setting the INTE bit*/
    UINT32* usbCommandPtr = (UINT32*)usbCommandAddress;
    UINT32 usbCommandRegister = *usbCommandPtr;
    usbCommandRegister |= 0x00000005;
    *usbCommandPtr = usbCommandRegister;
    
    /*Reset the root hub ports by setting a bit in their postSC register*/
    for (UINT8 i = 0; i < maxPorts; i++){
        RootHubPortRegistersAddress currentPort = rootHubPorts[i];
        UINT32* portSCPtr = (UINT32*)currentPort.portSCAddress;
        UINT32 portSCRegister = *portSCPtr;
        portSCRegister |= 0x00000010;
        *portSCPtr = portSCRegister;
    }
}

void main(){
    asm("movq [=14=]x350000, %rsp"); //Get my own stack
    
    /*Relocate and mask the legacy PIC*/
    MaskLegacyPIC();
    
    /*Write the spurious interrupt register of the LAPIC with 0x1ff
    This should enable and make the spurious interrupts jump to int 255.
    Int 255 is not set since spurious interrupts are not the problem here.*/
    UINT32* spuriousIntPtr = (UINT32*)(0xfee00000 + 0xf0);
    *spuriousIntPtr = 0x000001ff;
    
    /*Get a pointer to the MCFG. Normally I get pointers to other tables
    but there's no need for the question.*/
    ACPISetup();
    
    /*Get the PCI configuration space address for the xHC
    When it finds the xHCI function it will call the SetXHCIConfigSpacePhysAddr.
    This function will save all registers of xHC as global objects. Normally
    they are stored in an object since the kernel is object oriented.*/
    PCISetup();
    
    /*Setup the GDT and IDT
    I place the GDT at 0x398000 and the gdt descriptor at 0x398500
    I place the IDT at 0x399000 and the idt descriptor at 0x399ff0
    I tested this implementation with a divide by zero error and it seems
    to jump to the IDT properly.*/
    SetupIDT();
    
    asm("sti");
    
    /*This function initializes the xHC and is supposed to bring it
    to a functional state*/
    XHCIInit();
    
    asm volatile(
    "halt:\n\t"
    "hlt\n\t"
    "jmp halt\n\t");
}

我终于通过反转 osdev.org 上的 MSI-X table 结构使其工作。我决定在离开 UEFI 环境后完全重新初始化 xHC,因为它可能会使其处于未知状态。这是 xHCI 代码,供与我有同样疑问的人使用:

#include "XHCI.h"

void XHCI::SetConfigSpacePhysAddr(UINT64 configSpacePhysAddr){
    this->configSpacePhysAddr = configSpacePhysAddr;
}

void XHCI::SetRegisters(){
    /*Bar0 and Bar1 combining*/
    UINT32* pciPtr = (UINT32*)configSpacePhysAddr;
    UINT32 register4 = *(pciPtr + 4);
    UINT32 register5 = *(pciPtr + 5);
    UINT64 bar0 = (UINT64)register4;
    UINT64 bar1 = (UINT64)register5;
    baseAddressRegisterSpace = (bar1 << 32) + (bar0 & 0xfffffff0);
    
    /*Capability registers*/
    UINT8* xhciRegistersPtr8 = (UINT8*)baseAddressRegisterSpace;
    capLength = *(xhciRegistersPtr8);
    UINT32* xhciRegistersPtr32 = (UINT32*)baseAddressRegisterSpace;
    hcsParams1 = *(xhciRegistersPtr32 + 1);
    hccParams1 = *(xhciRegistersPtr32 + 4);
    dbOff = *(xhciRegistersPtr32 + 5);
    rtsOff = *(xhciRegistersPtr32 + 6);
    maxDeviceSlots = (UINT8)hcsParams1;
    maxInterrupters = (hcsParams1 >> 8);
    maxPorts = (UINT8)(hcsParams1 >> 24);
    
    /*Operational registers addresses*/
    usbCommandAddress = baseAddressRegisterSpace + capLength;
    usbStatusAddress = baseAddressRegisterSpace + capLength + 0x4;
    crcrAddress = baseAddressRegisterSpace + capLength + 0x18;
    dcbaaAddress = baseAddressRegisterSpace + capLength + 0x30;
    configAddress = baseAddressRegisterSpace + capLength + 0x38;
    UINT64 rootHubPortsBase = baseAddressRegisterSpace + capLength + 0x400;
    for (UINT32 i = 0; i < maxPorts; i++){
        rootHubPorts[i].portSCAddress = rootHubPortsBase;
        rootHubPorts[i].portPMSCAddress = rootHubPortsBase + 4;
        rootHubPorts[i].portLIAddress = rootHubPortsBase + 8; 
        rootHubPortsBase += 16;
    }
    
    /*
    Runtime registers parsing
    Here we save only the Interrupter register set 0 because we plan on using only this one.
    */
    UINT64 interrupterRegisterSet0Address = baseAddressRegisterSpace + rtsOff + 0x20;
    iman0Address = interrupterRegisterSet0Address;
    imod0Address = interrupterRegisterSet0Address + 4;
    eventRingSegmentTableSize0Address = interrupterRegisterSet0Address + 8;
    eventRingSegmentTableBaseAddress0Address = interrupterRegisterSet0Address + 16;
    eventRintDequeuePointer0Address = interrupterRegisterSet0Address + 24;
    
    /*Save the doorbell registers base address. 
    The formula to calculate the address of another doorbell
    is doorbell0Address + (i * 4) where i is the doorbell index to retrieve.*/
    doorbell0Address = baseAddressRegisterSpace + dbOff;
    
    /*Parsing the capabilities (MSI-X)*/
    UINT32 registerD = *(pciPtr + 0xd);
    capabilitiesOffset = (UINT8)registerD;
    UINT64 capAddr = configSpacePhysAddr + capabilitiesOffset;
    UINT32* capPtr = (UINT32*)capAddr;
    UINT32 msiRegister0 = *capPtr;
    UINT32 tableOffset = *(capPtr + 1);
    msiXTableAddress = baseAddressRegisterSpace + tableOffset;
    
    /*Extended capabilities parsing (Supported Protocols)*/
    exCapabilitiesOffset = (UINT16)(hccParams1 >> 16);
    UINT32* capabilitiesPtr = (UINT32*)baseAddressRegisterSpace;
    capabilitiesPtr += exCapabilitiesOffset;
    UINT32 capRegister0 = *capabilitiesPtr;
    UINT8 nextOffset = (UINT8)(capRegister0 >> 8);
    UINT32 counter = 0;
    while(nextOffset != 0){
        if ((UINT8)capRegister0 == 0x2){
            PortInfo portInfo;
            portInfo.revisionMinor = (UINT8)(capRegister0 >> 16);
            portInfo.revisionMajor = (UINT8)(capRegister0 >> 24);
            UINT32 capRegister2 = *(capabilitiesPtr + 2);
            portInfo.portOffset = (UINT8)capRegister2;
            portInfo.portCount = (UINT8)(capRegister2 >> 8);
            portInfos[counter] = portInfo;
            counter++;
        }
        nextOffset = (UINT8)(capRegister0 >> 8);
        capabilitiesPtr += nextOffset;
        capRegister0 = *capabilitiesPtr;
    }
    portInfosLength = counter;
}

void XHCI::XHCIReset(){
    UINT32* usbCommandPtr = (UINT32*)usbCommandAddress;
    UINT32 usbCommandRegister = *usbCommandPtr;
    usbCommandRegister &= 0xfffffffa;
    *usbCommandPtr = usbCommandRegister;

    usbCommandRegister |= 0x00000002;
    *usbCommandPtr = usbCommandRegister;
    
    UINT32* usbStatusPtr = (UINT32*)usbStatusAddress;
    while((*usbStatusPtr >> 11) & 0x00000001 != 0){
        Print::Printf("init");
    }
}

void XHCI::XHCIInit(UINT32 localAPICID){
    /*
    We place the Device Context Base Address Array 
    to address 0x250000 where it will grow upward.
    */
    UINT64* dcbaaPtr = (UINT64*)dcbaaAddress;
    *dcbaaPtr = 0x250000;
    
    /*
    We define the CRCR (Dequeue pointer of command ring)
    to address 0x251000 where the command ring will lie.
    We also define a link TRB as the last TRB of the ring.
    We define a ring of 32 TRBs. The 32nd TRB will be the
    link TRB to rollback to the beginning. Each TRB comprises
    16 bytes (4 UINT32). The ring will have a size of 16 * 32 = 512 bytes.
    We thus initialize 512 bytes to 0 then initialize the fields
    of the link TRB to their respective value.
    */
    UINT64* crcrPtr = (UINT64*)crcrAddress;
    *crcrPtr = 0x251000;
    
    UINT32* ptr = (UINT32*)0x251000;
    for (UINT32 i = 0; i < 128; i++)
        ptr[i] = 0;
        
    UINT32* linkTRBPtr = (UINT32*)(0x251000 + 512 - 16);
    *linkTRBPtr = 0x251000;
    *(linkTRBPtr + 1) = 0;
    *(linkTRBPtr + 2) = 0;
    *(linkTRBPtr + 3) = 0x00001802;
    
    /*Define the MSI-X table (only vector 0 for now)*/
    UINT32* msiXTablePtr = (UINT32*)msiXTableAddress;
    *msiXTablePtr = 0xfee00000;
    *(msiXTablePtr + 1) = 0x00000000;
    *(msiXTablePtr + 2) = 0x00000030;
    *(msiXTablePtr + 3) = 0x00000000;
    
    /*Enable the MSI-X capability*/
    UINT64 capAddr = configSpacePhysAddr + capabilitiesOffset;
    UINT32* capPtr = (UINT32*)capAddr;
    UINT32 register0 = *capPtr;
    register0 |= 0x80000000;
    *capPtr = register0;
    
    /*
    Initialize interrupter 0. There will be only one segment at address 0x253000
    It will be pointed to by the Event Ring segment table at address 0x252000.
    The size in TRBs of the only segment will be 32 including the link TRB.
    */
    UINT64* segmentTablePtr = (UINT64*)0x252000;
    *segmentTablePtr = 0x253000;
    *(segmentTablePtr + 1) = 0x0000000000000020;
    
    UINT32* segmentTableSizePtr = (UINT32*)eventRingSegmentTableSize0Address;
    *segmentTableSizePtr = 0x1;
    
    UINT64* dequeuePtr = (UINT64*)eventRintDequeuePointer0Address;
    *dequeuePtr = 0x253000;
    
    UINT64* segmentTableBaseAddressPtr = (UINT64*)eventRingSegmentTableBaseAddress0Address;
    *segmentTableBaseAddressPtr = 0x252000;
    
    UINT32* iman0Ptr = (UINT32*)iman0Address;
    *iman0Ptr = 0x00000002;
    
    /*Enable Interrupt generation*/
    UINT32* usbCommandPtr = (UINT32*)usbCommandAddress;
    UINT32 usbCommandRegister = *usbCommandPtr;
    usbCommandRegister |= 0x00000004;
    *usbCommandPtr = usbCommandRegister;
    
    /*Start the xHC*/
    usbCommandRegister |= 0x00000001;
    *usbCommandPtr = usbCommandRegister;
    
    for (UINT8 i = 0; i < maxPorts; i++){
        RootHubPortRegistersAddress currentPort = rootHubPorts[i];
        UINT32* portSCPtr = (UINT32*)currentPort.portSCAddress;
        UINT32 portSCRegister = *portSCPtr;
        portSCRegister |= 0x00000010;
        *portSCPtr = portSCRegister;
    }
}

基本上,一旦您获得 xHC 的 PCI 配置 space 地址调用,依次调用 SetConfigSpacePhysAddr()、SetRegisters()、XHCIReset(),然后是 XHCIInit()。该代码应在 IDT 的向量 48 上触发中断。