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

1

I'm coding a small OS kernel which is supposed to have a driver for the Intel's xHC (extensible host controller). I got to a point where I can actually generate Port Status Change Events by resetting the root hub ports. I'm using QEMU for virtualization.

I ask QEMU to emulate a USB mouse and a USB keyboard which it seems to do because I actually get 2 Port Status Change Events when I reset all root hub ports. I get these events on the Event Ring of interrupter 0.

The problem is I can't find out why I'm not getting interrupts generated on these events.

I'm posting a complete reproducible example here. Bootloader.c is the UEFI app that I launch from the OVMF shell by typing fs0:bootloader.efi. Bootloader.c is compiled with the EDK2 toolset. I work on Linux Ubuntu 20. Sorry for the long code.

The file main.cpp is a complete minimal reproducible example of my kernel. All the OS is compiled and launched with the 3 following scripts:

compile

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

build (type in order o, y, n, leave default, leave default, leave default, 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

launch

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

For the scripts to work the main.cpp file needs to be in the home directory in the os folder. Also the Bootloader.efi app needs to be in edk2/BootloaderPkg/Build/DEBUG_GCC5/X64/Bootloader.efi.

When I launch the kernel, it seems to work and it halts. I'm getting a few messages on the UEFI shell but the screen doesn't get cleared. It means that the interrupt is not generated. I actually get Port Status Change Events since I verified in memory where I placed the Event Ring (at 0x253000):

gdb

hexdump

The bytes you can see in the hexdump are 2 Port Status Change Events that are generated by the xHC. It is functional just not throwing interrupts.

My main questions:

  1. Why doesn't my code throw any interrupts? I verified that the IDT works by creating a divide by zero error and it actually cleared the screen as expected.

  2. Which version of the MSI-X capability structure should I choose between the one on osdev.org:

osdev.org version of the capability structure

and the one in the PCI Local Bus Specification version 3.0 (http://fpga-faq.narod.ru/PCI_Rev_30.pdf):

pci version of the cap structure

  1. It seems that my code cannot write to the MSI-X table because when I write it and read it back it returns 0. Why is that so?

  2. Should I halt the xHC by clearing the Run/Stop bit in the USBCMD register before I modify the different registers?

  3. Where is the actual MSI-X table in memory? Is the offset I calculated correct? In my code I'm not masking BIR since it is already 0 for the xHC.

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, $0x80" );
}

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 $0x350000, %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");
}
c++
x86
usb
osdev
hci
asked on Stack Overflow Apr 22, 2021 by user123 • edited Apr 22, 2021 by user123

1 Answer

0

I finally got it working by inverting the MSI-X table structure found on osdev.org. I decided to completely reinitialize the xHC after leaving the UEFI environment as it could leave it in an unknown state. Here's the xHCI code for anyone wondering the same thing as me:

#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;
    }
}

Basically, once you get the xHC's PCI config space address call in order SetConfigSpacePhysAddr(), SetRegisters(), XHCIReset() then XHCIInit(). That code should trigger an interrupt on vector 48 of the IDT.

answered on Stack Overflow Apr 26, 2021 by user123

User contributions licensed under CC BY-SA 3.0