using System.Runtime.InteropServices; namespace XFESystem.NativeKernel; public unsafe struct UsbRing { public ulong Physical; public uint Index, Cycle; } public unsafe struct UsbController { public ulong Base, Bytes, Op, Doorbell, Runtime, Dcbaa, Events, Erst; public UsbRing Commands; public ulong Completed, CompletionParameter; public uint CompletionCode, EventIndex, EventCycle, Ports, Slots, ContextBytes, Pci, Running, Address64; public byte* RootSeen; } public unsafe struct UsbDevice { public UsbController* Controller; public UsbDevice* Parent; public ulong Input, Output, Buffer; public UsbRing Control; public ulong Completed; public uint CompletionCode, CompletionRemaining; public uint Slot, RootPort, ParentPort, Speed, Route, Depth, TtSlot, TtPort, Packet, Connected; public uint Vendor, Product, HubPorts, HubSuper, HubTt, Configured; public byte* HubSeen; } public unsafe struct UsbInterruptEndpoint { public UsbDevice* Device; public UsbRing Ring; public ulong Buffer, Pending; public uint Dci, Packet, Kind, Active, Reports, Errors; public UsbHidLayout Layout; public UsbHidState State; } // Independent C# xHCI 1.x polling driver, not the legacy Cosmos driver. // One native scheduler worker owns all rings. No USB waits run in the UI or IRQ. // The same worker serializes HID and the bounded boot-volume BOT storage path. public static unsafe partial class UsbXhci { const uint ControllerLimit = 8, DeviceLimit = 96, EndpointLimit = 96; static UsbController* _controllers; static UsbDevice* _devices; static UsbInterruptEndpoint* _endpoints; static uint _controllerCount, _deviceCount, _endpointCount; public static uint ReadyBits, State; public static ulong Reports, Failures; static bool _firstKey, _firstPointer; [DllImport("xfe_native", EntryPoint = "xfe_network_pci_read")] static extern uint PciRead(uint b, uint d, uint f, uint o); [DllImport("xfe_native", EntryPoint = "xfe_network_pci_write")] static extern int PciWrite(uint b, uint d, uint f, uint o, uint v); static ulong Now() => MonotonicClock.Nanoseconds() / 1000000; static void Sleep(uint ms) => Threads.Sleep(ms); static uint Read(ulong address) => Native.MmioRead32(address); static void Write(ulong address, uint value) => Native.MmioWrite32(address, value); static void Write64(ulong address, ulong value) { Write(address, (uint)value); Write(address + 4, (uint)(value >> 32)); } static ulong Phys(void* address) => (ulong)address - PhysicalMemory.Hhdm; static byte* At(ulong physical) => physical == 0 ? null : (byte*)PhysicalMemory.ToVirtual(physical); static ulong Allocate(uint pages = 1) { ulong flags = Native.IrqSave(); ulong value = PhysicalMemory.Allocate(pages); Native.IrqRestore(flags); return value; } static void Log(string message) { Serial.Write("XFE_USB|"); Serial.Line(message); } static void DeviceLog(UsbDevice* d, string message) { Serial.Write("XFE_USB_DEVICE|Slot|"); Serial.Number(d->Slot); Serial.Write("|Root|"); Serial.Number(d->RootPort); Serial.Write("|Depth|"); Serial.Number(d->Depth); Serial.Write("|Route|"); Serial.Hex(d->Route); Serial.Write("|Vendor|"); Serial.Hex(d->Vendor); Serial.Write("|Product|"); Serial.Hex(d->Product); Serial.Write("|"); Serial.Line(message); } public static bool Start() { State = 1; return Threads.Create(&Worker, null, 262144, 0, false) != 0; } [UnmanagedCallersOnly(EntryPoint = "xfe_usb_worker")] public static uint Worker(void* argument) { _controllers = (UsbController*)At(Allocate(2)); _devices = (UsbDevice*)At(Allocate(8)); _endpoints = (UsbInterruptEndpoint*)At(Allocate(8)); if (_controllers == null || _devices == null || _endpoints == null) { State = 4; Log("STATE_ALLOCATION_FAILED"); return 1; } for (uint b = 0; b < 256; b++) for (uint d = 0; d < 32; d++) { if ((PciRead(b, d, 0, 0) & 65535) == 65535) continue; uint functions = (PciRead(b, d, 0, 12) & 0x800000) != 0 ? 8u : 1u; for (uint f = 0; f < functions; f++) { if ((PciRead(b, d, f, 0) & 65535) == 65535 || (PciRead(b, d, f, 8) >> 8) != 0x0c0330) continue; if (_controllerCount == ControllerLimit) { Log("CONTROLLER_LIMIT"); continue; } UsbController* c = &_controllers[_controllerCount++]; c->Pci = b << 16 | d << 8 | f; Serial.Write("XFE_USB_CONTROLLER|Pci|"); Serial.Hex(c->Pci); Serial.Write("|Id|"); Serial.Hex(PciRead(b,d,f,0)); Serial.Line(""); if (!Initialize(c)) { Failures++; Log("CONTROLLER_INIT_FAILED"); continue; } for (uint p = 1; p <= c->Ports; p++) Root(c, p); } } State = 2; Log("INITIAL_ENUMERATION_COMPLETE"); ulong nextScan = Now() + 2000; for (;;) { for (uint i = 0; i < _controllerCount; i++) if (_controllers[i].Running != 0) Events(&_controllers[i]); ProcessStorageRequest(); if (Now() >= nextScan) { nextScan = Now() + 2000; for (uint i = 0; i < _controllerCount; i++) if (_controllers[i].Running != 0) for (uint p = 1; p <= _controllers[i].Ports; p++) Root(&_controllers[i], p); for (uint i = 0; i < _deviceCount; i++) if (_devices[i].Connected != 0 && _devices[i].HubPorts != 0) ScanHub(&_devices[i]); } Sleep(1); } } static bool WaitBits(ulong address, uint mask, uint expected, uint timeout) { ulong end = Now() + timeout; do { if ((Read(address) & mask) == expected) return true; Sleep(1); } while (Now() < end); return false; } static bool RangeAllowed(ulong physical, ulong bytes) { if (physical < 0x100000 || bytes < 4096 || bytes > 2097152 || physical > ~0UL - bytes || (physical & 4095) != 0) return false; NativeBootInfo* boot = Kernel.Boot; for (ulong i = 0; i < boot->MemoryEntryCount; i++) { MemoryMapEntry* e = boot->MemoryEntries[i]; // MMIO must not alias any owned or firmware/ACPI RAM, or scanout. if (e->Length != 0 && physical < e->Base + e->Length && e->Base < physical + bytes && e->Kind != 1 && e->Kind != 8) return false; } return true; } static bool Initialize(UsbController* c) { uint b = c->Pci >> 16, d = c->Pci >> 8 & 31, f = c->Pci & 7; uint low = PciRead(b,d,f,16), high = (low & 6) == 4 ? PciRead(b,d,f,20) : 0; if ((low & 1) != 0 || (low & 6) is not (0 or 4)) return false; ulong physical = (ulong)high << 32 | (low & ~15u); if (!RangeAllowed(physical, 4096)) return false; ulong irq = Native.IrqSave(); c->Base = VirtualMemory.MapDevice(physical, 4096); Native.IrqRestore(irq); if (c->Base == 0) return false; // Claim the firmware semaphore before taking control. Never force a // BIOS-owned controller, and never touch a host Windows controller. uint hcc = Read(c->Base + 16), ext = hcc >> 16; bool handoff = true; for (uint limit = 0; ext != 0 && limit < 64; limit++) { ulong offset = (ulong)ext * 4; if (offset > 262136) { handoff = false; break; } ulong page = (physical + offset) & ~4095UL; ulong capMapping = c->Base; bool extra = offset >= 4096; if (extra) { if (!RangeAllowed(page, 8192)) { handoff = false; break; } irq = Native.IrqSave(); capMapping = VirtualMemory.MapDevice(page, 8192); Native.IrqRestore(irq); if (capMapping == 0) { handoff = false; break; } } ulong capAddress = extra ? capMapping + ((physical + offset) & 4095) : c->Base + offset; uint cap = Read(capAddress); if ((cap & 255) == 1) { Write(capAddress, cap | (1u << 24)); if (!WaitBits(capAddress, 1u << 16, 0, 1500)) handoff = false; else Write(capAddress + 4, 0xe0000000); // disable legacy SMI enables, acknowledge status } if (extra) { irq = Native.IrqSave(); VirtualMemory.UnmapDevice(capMapping,8192); Native.IrqRestore(irq); } if (!handoff) break; uint next = cap >> 8 & 255; if (next == 0) { ext = 0; break; } ext += next; if (limit == 63) handoff = false; } if (!handoff) { Log("FIRMWARE_HANDOFF_FAILED"); return false; } uint capLength = Read(c->Base) & 255; if (capLength < 32 || capLength > 256) return false; c->Op = c->Base + capLength; Write(c->Op, Read(c->Op) & ~1u); if (!WaitBits(c->Op + 4, 1, 1, 1000)) return false; // Size only this stopped, claimed xHCI BAR. Restore every PCI field // before mapping; no GPU/storage BAR probing or writes are performed. uint command = PciRead(b,d,f,4) & 65535; irq = Native.IrqSave(); PciWrite(b,d,f,4,command & ~6u); PciWrite(b,d,f,16,0xffffffff); if ((low & 6) == 4) PciWrite(b,d,f,20,0xffffffff); ulong mask = (PciRead(b,d,f,16) & ~15u) | ((ulong)((low & 6) == 4 ? PciRead(b,d,f,20) : 0xffffffff) << 32); PciWrite(b,d,f,16,low); if ((low & 6) == 4) PciWrite(b,d,f,20,high); PciWrite(b,d,f,4,command); Native.IrqRestore(irq); ulong bytes = ~mask + 1; if ((bytes & (bytes - 1)) != 0 || !RangeAllowed(physical, bytes)) return false; irq = Native.IrqSave(); VirtualMemory.UnmapDevice(c->Base,4096); c->Base = VirtualMemory.MapDevice(physical,bytes); Native.IrqRestore(irq); if (c->Base == 0) return false; c->Bytes = bytes; c->Op = c->Base + capLength; c->Doorbell = Read(c->Base + 20) & ~3u; c->Runtime = Read(c->Base + 24) & ~31u; uint hcs = Read(c->Base + 4); c->Slots = hcs & 255; if (c->Slots > 32) c->Slots = 32; c->Ports = hcs >> 24; c->ContextBytes = (hcc & 4) != 0 ? 64u : 32u; c->Address64 = hcc & 1; if (c->Slots == 0 || c->Ports == 0 || c->Ports > 128 || c->Doorbell + (c->Slots + 1) * 4 > bytes || c->Runtime + 64 > bytes || capLength + 0x400 + c->Ports * 16 > bytes) return false; c->Doorbell += c->Base; c->Runtime += c->Base; PciWrite(b,d,f,4,(command | 6 | 0x400) & ~0x100u); // bus master, memory; INTx disabled Write(c->Op,2); if (!WaitBits(c->Op,2,0,1000) || !WaitBits(c->Op+4,1u<<11,0,1000) || (Read(c->Op+8)&1)==0) return false; c->Dcbaa=Allocate(); c->Events=Allocate(); c->Erst=Allocate(); c->RootSeen=At(Allocate()); if (c->Dcbaa==0 || c->Events==0 || c->Erst==0 || c->RootSeen==null || !NewRing(&c->Commands)) return false; uint hcs2=Read(c->Base+8), scratch=((hcs2>>27)&31)|((hcs2>>16)&0x3e0); if (scratch > 64) { Log("SCRATCHPAD_LIMIT"); return false; } if (scratch != 0) { ulong table=Allocate(); if(table==0)return false; for(uint i=0;iDcbaa))[0]=table; } if(c->Address64==0 && PhysicalMemory.AllocatedPages!=0 && (c->Dcbaa>=0x100000000 || c->Events>=0x100000000 || c->Commands.Physical>=0x100000000)) return false; Write64(c->Op+0x30,c->Dcbaa); Write64(c->Op+0x18,c->Commands.Physical|1); Write(c->Op+0x38,c->Slots); ulong* erst=(ulong*)At(c->Erst); erst[0]=c->Events; erst[1]=256; ulong intr=c->Runtime+0x20; Write(intr,1); Write(intr+4,0); Write(intr+8,1); Write64(intr+16,c->Erst); Write64(intr+24,c->Events); c->EventCycle=1; Native.MemoryBarrier(); Write(c->Op,1); if(!WaitBits(c->Op+4,1,0,1000)) return false; c->Running=1; Log("RUNNING"); return true; } static bool NewRing(UsbRing* ring) { ring->Physical=Allocate(); ring->Index=0; ring->Cycle=1; return ring->Physical!=0; } static ulong Put(UsbRing* ring, ulong parameter, uint status, uint control) { ulong physical=ring->Physical+ring->Index*16; uint* trb=(uint*)At(physical); ((ulong*)trb)[0]=parameter; trb[2]=status; Native.MemoryBarrier(); trb[3]=control|ring->Cycle; Native.MemoryBarrier(); if(++ring->Index==255) { uint* link=(uint*)At(ring->Physical+255*16); ((ulong*)link)[0]=ring->Physical;link[2]=0; Native.MemoryBarrier();link[3]=(6u<<10)|2|ring->Cycle;Native.MemoryBarrier();ring->Index=0;ring->Cycle^=1; } return physical; } static bool Command(UsbController* c, uint type, ulong parameter, uint control, out uint slot) { slot=0;if(c->Running==0)return false; ulong pending=Put(&c->Commands,parameter,0,(type<<10)|control); c->Completed=0; Write(c->Doorbell,0); ulong end=Now()+1000; do {Events(c);if(c->Completed==pending){slot=(uint)(c->CompletionParameter>>24)&255;return c->CompletionCode==1;}Sleep(1);}while(Now()Running=0;Write(c->Op,0);Failures++;return false; } static void Events(UsbController* c) { if(c->Running==0)return; for(uint budget=0;budget<256;budget++) { uint* trb=(uint*)At(c->Events+c->EventIndex*16); uint flags=Read((ulong)(trb+3));if((flags&1)!=c->EventCycle)break; Native.MemoryBarrier();ulong parameter=((ulong*)trb)[0];uint status=trb[2],type=(flags>>10)&63,code=status>>24; if(type==33){c->Completed=parameter&~15UL;c->CompletionCode=code;c->CompletionParameter=flags;} else if(type==32) { uint slot=flags>>24,endpoint=(flags>>16)&31; for(uint i=0;i<_deviceCount;i++) { UsbDevice* d=&_devices[i];if(d->Controller!=c || d->Slot!=slot || d->Connected==0)continue; if(endpoint==1){d->Completed=parameter&~15UL;d->CompletionCode=code;d->CompletionRemaining=status&0xffffff;} else if(!CompleteStorage(d,endpoint,parameter&~15UL,code,status&0xffffff)) CompleteInterrupt(d,endpoint,parameter&~15UL,code,status&0xffffff); break; } } if(++c->EventIndex==256){c->EventIndex=0;c->EventCycle^=1;} } Write64(c->Runtime+0x38,(c->Events+c->EventIndex*16)|8); } static bool Control(UsbDevice* d, byte requestType, byte request, ushort value, ushort index, ushort length, out uint received) { received=0;if(d->Connected==0 || d->Controller->Running==0 || length>4096)return false; bool input=(requestType&128)!=0; ulong setup=requestType|((ulong)request<<8)|((ulong)value<<16)|((ulong)index<<32)|((ulong)length<<48); Put(&d->Control,setup,8,(2u<<10)|64|((length==0?0u:input?3u:2u)<<16)); // A single Data Stage TRB is already its complete Data Stage TD. // CH must be clear; the Status Stage is a separate TD. if(length!=0)Put(&d->Control,d->Buffer,length,(3u<<10)|4|(input?1u<<16:0)); ulong pending=Put(&d->Control,0,0,(4u<<10)|32|((length==0||!input)?1u<<16:0));d->Completed=0; Write(d->Controller->Doorbell+d->Slot*4,1);ulong end=Now()+1500; do { Events(d->Controller); if(d->Completed==pending) {if(d->CompletionCode!=1)return false;received=length;return true;} // A stalled EP0 must be reset with its dequeue pointer before the // next optional class request. A timeout is quarantined instead. if(d->Completed!=0 && d->CompletionCode!=1 && d->CompletionCode!=13) { uint slot; Command(d->Controller,14,0,d->Slot<<24|1u<<16,out slot); ulong dequeue=d->Control.Physical+d->Control.Index*16|d->Control.Cycle; Command(d->Controller,16,dequeue,d->Slot<<24|1u<<16,out slot);return false; } Sleep(1); }while(Now() (uint*)(At(d->Input)+index*d->Controller->ContextBytes); static void SlotContext(UsbDevice* d,uint last) { uint* s=Context(d,1);s[0]=d->Route|(d->Speed<<20)|(last<<27); if(d->HubPorts!=0)s[0]|=1u<<26; s[1]=d->RootPort<<16|d->HubPorts<<24;s[2]=d->TtSlot|d->TtPort<<8|d->HubTt<<16; } static void CopyContext(UsbDevice* d,uint index) { uint* src=(uint*)(At(d->Output)+(index-1)*d->Controller->ContextBytes),dst=Context(d,index); for(uint i=0;iController->ContextBytes/4;i++)dst[i]=src[i]; } static void Root(UsbController* c,uint port) { ulong reg=c->Op+0x400+(port-1)*16;uint status=Read(reg); if((status&1)==0) { if(c->RootSeen[port]!=0)for(uint i=0;i<_deviceCount;i++)if(_devices[i].Controller==c&&_devices[i].RootPort==port&&_devices[i].Connected!=0)Disconnect(&_devices[i]); c->RootSeen[port]=0; if((status&512)==0)Write(reg,(status&0x0e00c200)|512); return; } if(c->RootSeen[port]!=0)return;c->RootSeen[port]=1; if((status&512)==0){Write(reg,(status&0x0e00c200)|512);Sleep(100);status=Read(reg);} if((status&2)==0) { uint speed=(status>>10)&15; Write(reg,(status&0x0e00c200)|512|(speed>=4?1u<<31:16)); ulong end=Now()+500; do{Sleep(10);status=Read(reg);if((status&2)!=0&&(status&16)==0)break;}while(Now()>10)&15); } static UsbDevice* Enumerate(UsbController* c,UsbDevice* parent,uint port,uint route,uint speed) { if(_deviceCount==DeviceLimit || speed==0 || speed>5 || parent!=null&&parent->Depth>=5)return null; uint slot;if(!Command(c,9,0,0,out slot)||slot==0||slot>c->Slots)return null; UsbDevice* d=&_devices[_deviceCount++];d->Controller=c;d->Parent=parent;d->Slot=slot;d->Connected=1;d->Speed=speed; d->RootPort=parent==null?port:parent->RootPort;d->ParentPort=parent==null?0:port; d->Route=route;d->Depth=parent==null?0:parent->Depth+1; if(parent!=null && speed<=2) {d->TtSlot=parent->Speed==3?parent->Slot:parent->TtSlot;d->TtPort=parent->Speed==3?port:parent->TtPort;} d->Input=Allocate();d->Output=Allocate();d->Buffer=Allocate(); if(d->Input==0||d->Output==0||d->Buffer==0||!NewRing(&d->Control)){Disconnect(d);return null;} ((ulong*)At(c->Dcbaa))[slot]=d->Output; uint* ic=Context(d,0);ic[1]=3;SlotContext(d,1); d->Packet=speed>=4?512u:speed==3?64u:8u; uint* ep=Context(d,2);ep[1]=3u<<1|4u<<3|d->Packet<<16;((ulong*)(ep+2))[0]=d->Control.Physical|1;ep[4]=8; if(!Command(c,11,d->Input,slot<<24,out slot)){DeviceLog(d,"ADDRESS_FAILED");Disconnect(d);return null;} uint got; // Full-speed EP0 may actually use 64-byte packets. Request only the // first eight bytes before updating the provisional 8-byte context; // asking for all 18 first can cause a real controller babble error. if(!Control(d,128,6,0x100,0,8,out got)){DeviceLog(d,"DEVICE_DESCRIPTOR_PREFIX_FAILED");Disconnect(d);return null;} byte* desc=At(d->Buffer); if(desc[0]<18||desc[1]!=1){Disconnect(d);return null;} uint packet=d->Speed>=4?1u<Speed==2&&packet!=8 || d->Speed==3&&packet!=64 || d->Speed>=4&&packet!=512 || d->Speed==1&&packet>64){Disconnect(d);return null;} if(packet!=d->Packet) { d->Packet=packet;ic[0]=0;ic[1]=2;CopyContext(d,2);Context(d,2)[1]=(Context(d,2)[1]&65535)|packet<<16; if(!Command(c,13,d->Input,d->Slot<<24,out slot)){Disconnect(d);return null;} } if(!Control(d,128,6,0x100,0,18,out got)){DeviceLog(d,"DEVICE_DESCRIPTOR_FAILED");Disconnect(d);return null;} desc=At(d->Buffer);if(desc[0]<18||desc[1]!=1){Disconnect(d);return null;} d->Vendor=desc[8]|(uint)desc[9]<<8;d->Product=desc[10]|(uint)desc[11]<<8; uint deviceClass=desc[4],protocol=desc[6]; if(!Control(d,128,6,0x200,0,9,out got)){Disconnect(d);return null;} byte* config=At(d->Buffer);uint total=config[2]|(uint)config[3]<<8; if(config[0]<9||total<9||total>4096||!Control(d,128,6,0x200,0,(ushort)total,out got)){Disconnect(d);return null;} ulong saved=Allocate();if(saved==0){Disconnect(d);return null;}byte* all=At(saved); for(uint i=0;iConfigured=1; DeviceLog(d,"CONFIGURED"); if(deviceClass==9){Hub(d,protocol);return d;} uint iface=0,subclass=0,hidProtocol=0,reportBytes=0,valid=0; for(uint p=0;p+2<=total;) { uint length=all[p];if(length<2||length>total-p)break;uint type=all[p+1]; if(type==4&&length>=9){iface=all[p+2];subclass=all[p+6];hidProtocol=all[p+7];valid=all[p+5]==3&&all[p+3]==0?1u:0;reportBytes=0;} else if(type==0x21&&length>=9&&valid!=0){reportBytes=all[p+7]|(uint)all[p+8]<<8;} else if(type==5&&length>=7&&valid!=0&&(all[p+2]&128)!=0&&(all[p+3]&3)==3) AddInterrupt(d,iface,subclass,hidProtocol,reportBytes,all[p+2],(uint)(all[p+4]|all[p+5]<<8)&2047,all[p+6]); p+=length; } AddStorage(d,all,total); return d; } static void AddInterrupt(UsbDevice* d,uint iface,uint subclass,uint protocol,uint reportBytes,uint address,uint packet,uint interval) { if(_endpointCount==EndpointLimit||packet==0||packet>1024||interval==0)return; uint dci=(address&15)*2+1;if(dci<3||dci>31)return; UsbInterruptEndpoint* e=&_endpoints[_endpointCount++];e->Device=d;e->Dci=dci;e->Packet=packet; e->Buffer=Allocate();e->Layout.Fields=(UsbHidField*)At(Allocate(4));e->Layout.Capacity=512; if(e->Buffer==0||e->Layout.Fields==null||!NewRing(&e->Ring))return; uint got; bool parsed=reportBytes!=0&&reportBytes<=4096&&Control(d,129,6,0x2200,(ushort)iface,(ushort)reportBytes,out got)&&UsbHid.Parse(At(d->Buffer),reportBytes,&e->Layout); bool boot=subclass==1&&protocol==1; if(boot&&Control(d,0x21,11,0,(ushort)iface,0,out got))UsbHid.BootKeyboard(&e->Layout); else if(!parsed){DeviceLog(d,"UNSUPPORTED_HID_DESCRIPTOR");return;} Control(d,0x21,10,0,(ushort)iface,0,out got); // optional SET_IDLE, STALL is recovered e->Kind=e->Layout.Kind; uint* ic=Context(d,0);ic[0]=0;ic[1]=1u|(1u<<(int)dci);CopyContext(d,1); uint* slotContext=Context(d,1);uint last=(slotContext[0]>>27)&31;if(lastSpeed>=3){exponent=interval-1;if(exponent>15)return;} else{exponent=3;uint ms=1;while(msRing.Physical|1;ep[4]=packet|packet<<16; uint slot;if(!Command(d->Controller,12,d->Input,d->Slot<<24,out slot)){DeviceLog(d,"ENDPOINT_CONFIGURATION_FAILED");return;} e->Active=1;RefreshCapabilities(); Serial.Write("XFE_USB_HID_READY|Slot|");Serial.Number(d->Slot);Serial.Write("|Interface|");Serial.Number(iface); Serial.Write("|Dci|");Serial.Number(dci);Serial.Write("|Kind|");Serial.Number(e->Kind);Serial.Write("|BootKeyboard|");Serial.Number(boot?1UL:0UL);Serial.Line(""); Arm(e); } static void Arm(UsbInterruptEndpoint* e) { if(e->Active==0||e->Device->Connected==0)return; e->Pending=Put(&e->Ring,e->Buffer,e->Packet,(1u<<10)|32|4); Write(e->Device->Controller->Doorbell+e->Device->Slot*4,e->Dci); } static void CompleteInterrupt(UsbDevice* d,uint dci,ulong pointer,uint code,uint remaining) { for(uint i=0;i<_endpointCount;i++) { UsbInterruptEndpoint* e=&_endpoints[i];if(e->Device!=d||e->Dci!=dci||e->Active==0||e->Pending!=pointer)continue; if((code==1||code==13)&&remaining<=e->Packet) { byte* reportBytes=stackalloc byte[2048]; NativeInputReport* reports=(NativeInputReport*)reportBytes; uint count=UsbHid.Decode(&e->Layout,&e->State,At(e->Buffer),e->Packet-remaining,reports,64); for(uint j=0;jReports++;Reports++; if(e->Reports==1){Serial.Write("XFE_USB_FIRST_REPORT|Slot|");Serial.Number(d->Slot);Serial.Write("|Dci|");Serial.Number(dci);Serial.Write("|Events|");Serial.Number(count);Serial.Line("");} Arm(e); } else{e->Errors++;e->Active=0;RefreshCapabilities();Failures++;DeviceLog(d,"INTERRUPT_ENDPOINT_STOPPED");Ps2Input.InputLost();} return; } } static bool HubStatus(UsbDevice* d,uint port,out uint status) { status=0;uint got;if(!Control(d,0xa3,0,0,(ushort)port,4,out got))return false;status=*(uint*)At(d->Buffer);return true; } static void Hub(UsbDevice* d,uint protocol) { uint got;d->HubSuper=d->Speed>=4?1u:0; if(!Control(d,0xa0,6,(ushort)(d->HubSuper!=0?0x2a00:0x2900),0,(ushort)(d->HubSuper!=0?12:9),out got))return; byte* desc=At(d->Buffer);uint ports=desc[2];if(ports==0||ports>15){DeviceLog(d,"HUB_PORT_LIMIT");return;} d->HubPorts=ports;d->HubTt=d->Speed==3?(uint)(desc[3]>>5)&3:0;d->HubSeen=At(Allocate());if(d->HubSeen==null)return; uint powerWait=(uint)desc[5]*2+20;if(powerWait>1000)powerWait=1000; uint* ic=Context(d,0);ic[0]=0;ic[1]=1;CopyContext(d,1); uint* s=Context(d,1);s[0]|=1u<<26;s[1]=(s[1]&0xffffff)|ports<<24;s[2]=(s[2]&~0x30000u)|d->HubTt<<16; // xHCI 1.x initializes Hub/MTT/TTT/NumberOfPorts at the first // Configure Endpoint transition from Addressed to Configured. Evaluate // Context ignores these fields (it only evaluates latency/interrupt // targeting in the slot). A full-speed QEMU hub can hide that mistake; // real high-speed hubs need this context for their transaction translator. uint slot;if(!Command(d->Controller,12,d->Input,d->Slot<<24,out slot)){DeviceLog(d,"HUB_CONTEXT_FAILED");return;} Native.MemoryBarrier();uint* actual=(uint*)At(d->Output); if((actual[0]&(1u<<26))==0 || (actual[1]>>24)!=ports || ((actual[2]>>16)&3)!=d->HubTt) { // QEMU 11.1's own xhci_configure_slot only copies Context Entries // and Slot State, not Hub/NumberOfPorts/TTT. Keep that limitation // explicit. Never grant this exception to a physical PCI vendor. uint pci=d->Controller->Pci; if(PciRead(pci>>16,pci>>8&31,pci&7,0)!=0x000d1b36 || (actual[3]>>27)!=3) {DeviceLog(d,"HUB_CONTEXT_NOT_APPLIED");d->HubPorts=0;Failures++;return;} DeviceLog(d,"HUB_CONTEXT_QEMU_NOT_MODELED"); } else DeviceLog(d,"HUB_CONTEXT_APPLIED"); DeviceLog(d,"HUB_CONTEXT_CONFIGURED"); if(d->HubSuper!=0)Control(d,0x20,12,(ushort)d->Depth,0,0,out got); for(uint port=1;port<=ports;port++)Control(d,0x23,3,8,(ushort)port,0,out got); Sleep(powerWait);DeviceLog(d,"HUB_POWERED");ScanHub(d); } static void ScanHub(UsbDevice* d) { for(uint p=1;p<=d->HubPorts&&d->Connected!=0;p++) { uint status,got;if(!HubStatus(d,p,out status))continue; if((status&1)==0) { if(d->HubSeen[p]!=0)for(uint i=0;i<_deviceCount;i++)if(_devices[i].Parent==d&&_devices[i].ParentPort==p)Disconnect(&_devices[i]); d->HubSeen[p]=0;continue; } if(d->HubSeen[p]!=0)continue;d->HubSeen[p]=1; if(!Control(d,0x23,3,4,(ushort)p,0,out got))continue; ulong end=Now()+500; do{Sleep(20);if(!HubStatus(d,p,out status))break;if((status&16)==0&&(status&2)!=0)break;}while(Now()HubSuper!=0?4u:(status&0x400)!=0?3u:(status&0x200)!=0?2u:1u; uint route=d->Route|(p<<(int)(d->Depth*4));Sleep(20);Enumerate(d->Controller,d,p,route,speed); } } static void Disconnect(UsbDevice* d) { if(d->Connected==0)return;d->Connected=0; DisconnectStorage(d); for(uint i=0;i<_endpointCount;i++)if(_endpoints[i].Device==d)_endpoints[i].Active=0; for(uint i=0;i<_deviceCount;i++)if(_devices[i].Parent==d&&_devices[i].Connected!=0)Disconnect(&_devices[i]); RefreshCapabilities(); uint slot;Command(d->Controller,10,0,d->Slot<<24,out slot); // DMA pages remain quarantined for this RAM session. Never recycle a // device buffer while an unresponsive controller may still own it. Ps2Input.InputLost();DeviceLog(d,"DISCONNECTED"); } static void RefreshCapabilities() { uint bits=0; for(uint i=0;i<_endpointCount;i++) { UsbInterruptEndpoint* e=&_endpoints[i];if(e->Active==0||e->Device->Connected==0)continue; bits|=e->Kind; for(uint f=0;fLayout.Count;f++) if(e->Layout.Fields[f].Page==1&&e->Layout.Fields[f].Minimum==0x38&&(e->Layout.Fields[f].Flags&4)!=0)bits|=4; } ReadyBits=bits;Ps2Input.SetUsbCapabilities(bits); } [UnmanagedCallersOnly(EntryPoint = "xfe_usb_info")] public static ulong Info(uint field) => field switch {0=>1,1=>State,2=>_controllerCount,3=>_deviceCount,4=>_endpointCount,5=>ReadyBits,6=>Reports,7=>Failures,_=>0}; }