usb_uhci.c (18477B)
1 /* USB UHCI Host Controller Driver with Mouse Support */ 2 #include <stdint.h> 3 #include <stddef.h> 4 #include "usb.h" 5 #include "terminal.h" 6 #include "lua.h" 7 #include "lualib.h" 8 #include "lauxlib.h" 9 10 /* UHCI Register Offsets */ 11 #define UHCI_USBCMD 0x00 /* USB Command */ 12 #define UHCI_USBSTS 0x02 /* USB Status */ 13 #define UHCI_USBINTR 0x04 /* USB Interrupt Enable */ 14 #define UHCI_FRNUM 0x06 /* Frame Number */ 15 #define UHCI_FRBASEADD 0x08 /* Frame List Base Address */ 16 #define UHCI_SOFMOD 0x0C /* Start of Frame Modify */ 17 #define UHCI_PORTSC1 0x10 /* Port 1 Status/Control */ 18 #define UHCI_PORTSC2 0x12 /* Port 2 Status/Control */ 19 20 /* UHCI Command Register Bits */ 21 #define UHCI_CMD_RS 0x0001 /* Run/Stop */ 22 #define UHCI_CMD_HCRESET 0x0002 /* Host Controller Reset */ 23 #define UHCI_CMD_GRESET 0x0004 /* Global Reset */ 24 #define UHCI_CMD_MAXP 0x0080 /* Max Packet (0=32, 1=64) */ 25 #define UHCI_CMD_CF 0x0040 /* Configure Flag */ 26 27 /* UHCI Status Register Bits */ 28 #define UHCI_STS_USBINT 0x0001 /* USB Interrupt */ 29 #define UHCI_STS_ERROR 0x0002 /* USB Error Interrupt */ 30 #define UHCI_STS_RD 0x0004 /* Resume Detect */ 31 #define UHCI_STS_HSE 0x0008 /* Host System Error */ 32 #define UHCI_STS_HCPE 0x0010 /* Host Controller Process Error */ 33 #define UHCI_STS_HCH 0x0020 /* HC Halted */ 34 35 /* UHCI Port Status/Control Bits */ 36 #define UHCI_PORT_CCS 0x0001 /* Current Connect Status */ 37 #define UHCI_PORT_CSC 0x0002 /* Connect Status Change */ 38 #define UHCI_PORT_PED 0x0004 /* Port Enable/Disable */ 39 #define UHCI_PORT_PEDC 0x0008 /* Port Enable/Disable Change */ 40 #define UHCI_PORT_LSDA 0x0100 /* Low Speed Device Attached */ 41 #define UHCI_PORT_PR 0x0200 /* Port Reset */ 42 #define UHCI_PORT_SUSP 0x1000 /* Suspend */ 43 44 /* Transfer Descriptor (TD) */ 45 typedef struct uhci_td { 46 uint32_t link_ptr; /* Link to next TD */ 47 uint32_t status; /* Status and control */ 48 uint32_t token; /* Packet header */ 49 uint32_t buffer; /* Data buffer pointer */ 50 /* Software fields */ 51 uint32_t reserved[4]; 52 } __attribute__((aligned(16))) uhci_td_t; 53 54 /* Queue Head (QH) */ 55 typedef struct uhci_qh { 56 uint32_t head_link_ptr; /* Queue head link pointer */ 57 uint32_t element_link_ptr; /* Queue element link pointer */ 58 /* Software fields */ 59 uint32_t reserved[2]; 60 } __attribute__((aligned(16))) uhci_qh_t; 61 62 /* TD Status Bits */ 63 #define TD_STATUS_ACTLEN_MASK 0x7FF 64 #define TD_STATUS_BITSTUFF (1 << 17) 65 #define TD_STATUS_CRCTO (1 << 18) 66 #define TD_STATUS_NAK (1 << 19) 67 #define TD_STATUS_BABBLE (1 << 20) 68 #define TD_STATUS_DBUFFER (1 << 21) 69 #define TD_STATUS_STALLED (1 << 22) 70 #define TD_STATUS_ACTIVE (1 << 23) 71 #define TD_STATUS_IOC (1 << 24) /* Interrupt on Complete */ 72 #define TD_STATUS_IOS (1 << 25) /* Isochronous Select */ 73 #define TD_STATUS_LS (1 << 26) /* Low Speed Device */ 74 #define TD_STATUS_SPD (1 << 29) /* Short Packet Detect */ 75 76 /* TD Token Bits */ 77 #define TD_TOKEN_PID_IN 0x69 78 #define TD_TOKEN_PID_OUT 0xE1 79 #define TD_TOKEN_PID_SETUP 0x2D 80 81 /* UHCI Controller State */ 82 static uint16_t uhci_iobase = 0; 83 static uint32_t* frame_list = NULL; 84 static uhci_qh_t* control_qh = NULL; 85 static uhci_qh_t* interrupt_qh = NULL; 86 static uhci_td_t* mouse_td = NULL; 87 static usb_mouse_report_t* mouse_report_buffer = NULL; 88 static usb_mouse_state_t mouse_state = {512, 384, 0, 0}; 89 90 /* I/O port functions */ 91 static inline void outb(uint16_t port, uint8_t val) { 92 __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port)); 93 } 94 95 static inline void outw(uint16_t port, uint16_t val) { 96 __asm__ volatile ("outw %0, %1" : : "a"(val), "Nd"(port)); 97 } 98 99 static inline void outl(uint16_t port, uint32_t val) { 100 __asm__ volatile ("outl %0, %1" : : "a"(val), "Nd"(port)); 101 } 102 103 static inline uint8_t inb(uint16_t port) { 104 uint8_t ret; 105 __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port)); 106 return ret; 107 } 108 109 static inline uint16_t inw(uint16_t port) { 110 uint16_t ret; 111 __asm__ volatile ("inw %1, %0" : "=a"(ret) : "Nd"(port)); 112 return ret; 113 } 114 115 static inline uint32_t inl(uint16_t port) { 116 uint32_t ret; 117 __asm__ volatile ("inl %1, %0" : "=a"(ret) : "Nd"(port)); 118 return ret; 119 } 120 121 /* PCI Configuration Space Access */ 122 #define PCI_CONFIG_ADDRESS 0xCF8 123 #define PCI_CONFIG_DATA 0xCFC 124 125 static uint32_t pci_read_config(uint8_t bus, uint8_t slot, uint8_t func, uint8_t offset) { 126 uint32_t address = (1 << 31) | ((uint32_t)bus << 16) | ((uint32_t)slot << 11) | 127 ((uint32_t)func << 8) | (offset & 0xFC); 128 outl(PCI_CONFIG_ADDRESS, address); 129 return inl(PCI_CONFIG_DATA); 130 } 131 132 static void pci_write_config(uint8_t bus, uint8_t slot, uint8_t func, uint8_t offset, uint32_t value) { 133 uint32_t address = (1 << 31) | ((uint32_t)bus << 16) | ((uint32_t)slot << 11) | 134 ((uint32_t)func << 8) | (offset & 0xFC); 135 outl(PCI_CONFIG_ADDRESS, address); 136 outl(PCI_CONFIG_DATA, value); 137 } 138 139 /* Find UHCI controller on PCI bus */ 140 static int find_uhci_controller(void) { 141 terminal_writestring("Scanning PCI bus for UHCI controller...\n"); 142 143 for (uint16_t bus = 0; bus < 256; bus++) { 144 for (uint8_t slot = 0; slot < 32; slot++) { 145 uint32_t vendor_device = pci_read_config(bus, slot, 0, 0); 146 if (vendor_device == 0xFFFFFFFF) continue; 147 148 uint32_t class_code = pci_read_config(bus, slot, 0, 0x08); 149 uint8_t class = (class_code >> 24) & 0xFF; 150 uint8_t subclass = (class_code >> 16) & 0xFF; 151 uint8_t prog_if = (class_code >> 8) & 0xFF; 152 153 /* USB Controller (Class 0x0C, Subclass 0x03, ProgIF 0x00 = UHCI) */ 154 if (class == 0x0C && subclass == 0x03 && prog_if == 0x00) { 155 terminal_writestring("Found UHCI controller at bus "); 156 terminal_putchar('0' + (bus / 100)); 157 terminal_putchar('0' + ((bus / 10) % 10)); 158 terminal_putchar('0' + (bus % 10)); 159 terminal_writestring(" slot "); 160 terminal_putchar('0' + (slot / 10)); 161 terminal_putchar('0' + (slot % 10)); 162 terminal_writestring("\n"); 163 164 /* Read BAR4 (I/O Base Address) */ 165 uint32_t bar4 = pci_read_config(bus, slot, 0, 0x20); 166 uhci_iobase = bar4 & 0xFFE0; /* Mask off lower bits */ 167 168 terminal_writestring("UHCI I/O Base: 0x"); 169 for (int i = 3; i >= 0; i--) { 170 uint8_t nibble = (uhci_iobase >> (i * 4)) & 0xF; 171 terminal_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10); 172 } 173 terminal_writestring("\n"); 174 175 /* Enable bus mastering and I/O space */ 176 uint32_t command = pci_read_config(bus, slot, 0, 0x04); 177 command |= 0x05; /* Enable I/O Space and Bus Master */ 178 pci_write_config(bus, slot, 0, 0x04, command); 179 180 return 0; 181 } 182 } 183 } 184 185 terminal_writestring("No UHCI controller found\n"); 186 return -1; 187 } 188 189 /* Simple memory allocator for USB structures (aligned) */ 190 static uint8_t usb_memory_pool[8192] __attribute__((aligned(4096))); 191 static uint32_t usb_memory_offset = 0; 192 193 static void* usb_malloc(uint32_t size, uint32_t alignment) { 194 /* Align offset */ 195 uint32_t mask = alignment - 1; 196 usb_memory_offset = (usb_memory_offset + mask) & ~mask; 197 198 if (usb_memory_offset + size > sizeof(usb_memory_pool)) { 199 return NULL; 200 } 201 202 void* ptr = &usb_memory_pool[usb_memory_offset]; 203 usb_memory_offset += size; 204 205 /* Zero the memory */ 206 for (uint32_t i = 0; i < size; i++) { 207 ((uint8_t*)ptr)[i] = 0; 208 } 209 210 return ptr; 211 } 212 213 /* Get physical address (identity mapped in our case) */ 214 static uint32_t virt_to_phys(void* ptr) { 215 return (uint32_t)ptr; 216 } 217 218 /* Reset UHCI controller */ 219 static int uhci_reset(void) { 220 terminal_writestring("Resetting UHCI controller...\n"); 221 222 /* Global reset */ 223 outw(uhci_iobase + UHCI_USBCMD, UHCI_CMD_GRESET); 224 225 /* Wait 10ms (approximate) */ 226 for (volatile int i = 0; i < 1000000; i++); 227 228 outw(uhci_iobase + UHCI_USBCMD, 0); 229 230 /* Host controller reset */ 231 outw(uhci_iobase + UHCI_USBCMD, UHCI_CMD_HCRESET); 232 233 /* Wait for reset to complete */ 234 int timeout = 10000; 235 while ((inw(uhci_iobase + UHCI_USBCMD) & UHCI_CMD_HCRESET) && timeout--); 236 237 if (timeout == 0) { 238 terminal_writestring("UHCI reset timeout\n"); 239 return -1; 240 } 241 242 terminal_writestring("UHCI reset complete\n"); 243 return 0; 244 } 245 246 /* Initialize USB controller */ 247 int usb_init(void) { 248 terminal_writestring("Initializing USB subsystem...\n"); 249 250 /* Find UHCI controller */ 251 if (find_uhci_controller() != 0) { 252 return -1; 253 } 254 255 /* Reset controller */ 256 if (uhci_reset() != 0) { 257 return -1; 258 } 259 260 /* Allocate frame list (1024 entries, 4KB aligned) */ 261 frame_list = (uint32_t*)usb_malloc(1024 * sizeof(uint32_t), 4096); 262 if (!frame_list) { 263 terminal_writestring("Failed to allocate frame list\n"); 264 return -1; 265 } 266 267 /* Initialize frame list to terminate */ 268 for (int i = 0; i < 1024; i++) { 269 frame_list[i] = 1; /* Terminate bit */ 270 } 271 272 /* Allocate control queue head */ 273 control_qh = (uhci_qh_t*)usb_malloc(sizeof(uhci_qh_t), 16); 274 if (!control_qh) { 275 terminal_writestring("Failed to allocate control QH\n"); 276 return -1; 277 } 278 control_qh->head_link_ptr = 1; /* Terminate */ 279 control_qh->element_link_ptr = 1; /* Terminate */ 280 281 /* Allocate interrupt queue head */ 282 interrupt_qh = (uhci_qh_t*)usb_malloc(sizeof(uhci_qh_t), 16); 283 if (!interrupt_qh) { 284 terminal_writestring("Failed to allocate interrupt QH\n"); 285 return -1; 286 } 287 interrupt_qh->head_link_ptr = virt_to_phys(control_qh) | 0x02; /* QH pointer */ 288 interrupt_qh->element_link_ptr = 1; /* Terminate */ 289 290 /* Link frame list to interrupt queue */ 291 uint32_t int_qh_ptr = virt_to_phys(interrupt_qh) | 0x02; /* QH pointer */ 292 for (int i = 0; i < 1024; i++) { 293 frame_list[i] = int_qh_ptr; 294 } 295 296 /* Set frame list base address */ 297 outl(uhci_iobase + UHCI_FRBASEADD, virt_to_phys(frame_list)); 298 299 /* Set frame number to 0 */ 300 outw(uhci_iobase + UHCI_FRNUM, 0); 301 302 /* Start controller */ 303 outw(uhci_iobase + UHCI_USBCMD, UHCI_CMD_RS | UHCI_CMD_CF | UHCI_CMD_MAXP); 304 305 terminal_writestring("UHCI controller started\n"); 306 307 /* Wait for controller to start */ 308 for (volatile int i = 0; i < 100000; i++); 309 310 /* Check status */ 311 uint16_t status = inw(uhci_iobase + UHCI_USBSTS); 312 terminal_writestring("UHCI Status: 0x"); 313 for (int i = 3; i >= 0; i--) { 314 uint8_t nibble = (status >> (i * 4)) & 0xF; 315 terminal_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10); 316 } 317 terminal_writestring("\n"); 318 319 /* Check frame number is advancing */ 320 uint16_t frame1 = inw(uhci_iobase + UHCI_FRNUM); 321 for (volatile int i = 0; i < 100000; i++); 322 uint16_t frame2 = inw(uhci_iobase + UHCI_FRNUM); 323 terminal_writestring("Frame#: "); 324 for (int i = 3; i >= 0; i--) { 325 uint8_t nibble = (frame1 >> (i * 4)) & 0xF; 326 terminal_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10); 327 } 328 terminal_writestring(" -> "); 329 for (int i = 3; i >= 0; i--) { 330 uint8_t nibble = (frame2 >> (i * 4)) & 0xF; 331 terminal_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10); 332 } 333 if (frame2 != frame1) { 334 terminal_writestring(" (advancing OK)\n"); 335 } else { 336 terminal_writestring(" (STUCK!)\n"); 337 } 338 339 return 0; 340 } 341 342 /* Reset USB port */ 343 static int uhci_port_reset(int port) { 344 uint16_t port_reg = UHCI_PORTSC1 + (port * 2); 345 346 terminal_writestring("Resetting USB port "); 347 terminal_putchar('0' + port); 348 terminal_writestring("\n"); 349 350 /* Set port reset */ 351 uint16_t val = inw(uhci_iobase + port_reg); 352 outw(uhci_iobase + port_reg, val | UHCI_PORT_PR); 353 354 /* Wait 50ms (approximate) */ 355 for (volatile int i = 0; i < 5000000; i++); 356 357 /* Clear port reset */ 358 val = inw(uhci_iobase + port_reg); 359 outw(uhci_iobase + port_reg, val & ~UHCI_PORT_PR); 360 361 /* Wait for reset to complete */ 362 for (volatile int i = 0; i < 1000000; i++); 363 364 /* Enable port */ 365 val = inw(uhci_iobase + port_reg); 366 outw(uhci_iobase + port_reg, val | UHCI_PORT_PED); 367 368 return 0; 369 } 370 371 /* Enumerate USB devices */ 372 int usb_enumerate_devices(void) { 373 terminal_writestring("Enumerating USB devices...\n"); 374 375 /* Check port 1 */ 376 uint16_t port1 = inw(uhci_iobase + UHCI_PORTSC1); 377 terminal_writestring("Port 1 Status: 0x"); 378 for (int i = 3; i >= 0; i--) { 379 uint8_t nibble = (port1 >> (i * 4)) & 0xF; 380 terminal_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10); 381 } 382 terminal_writestring("\n"); 383 384 if (port1 & UHCI_PORT_CCS) { 385 terminal_writestring("Device detected on port 1\n"); 386 if (port1 & UHCI_PORT_LSDA) { 387 terminal_writestring(" Low speed device\n"); 388 } 389 uhci_port_reset(0); 390 } 391 392 /* Check port 2 */ 393 uint16_t port2 = inw(uhci_iobase + UHCI_PORTSC2); 394 terminal_writestring("Port 2 Status: 0x"); 395 for (int i = 3; i >= 0; i--) { 396 uint8_t nibble = (port2 >> (i * 4)) & 0xF; 397 terminal_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10); 398 } 399 terminal_writestring("\n"); 400 401 if (port2 & UHCI_PORT_CCS) { 402 terminal_writestring("Device detected on port 2\n"); 403 if (port2 & UHCI_PORT_LSDA) { 404 terminal_writestring(" Low speed device\n"); 405 } 406 uhci_port_reset(1); 407 } 408 409 return 0; 410 } 411 412 /* Initialize USB mouse with proper enumeration */ 413 int usb_mouse_init(void) { 414 terminal_writestring("Initializing USB mouse (boot protocol)...\n"); 415 416 /* TODO: Send SET_ADDRESS to device at address 0 to assign it address 1 */ 417 /* For now, we'll use address 0 and endpoint 1 for the interrupt endpoint */ 418 /* This is a simplified approach that may not work with all USB stacks */ 419 420 terminal_writestring("WARNING: Using simplified enumeration (address 0)\n"); 421 terminal_writestring("Mouse may not respond without proper SET_ADDRESS\n"); 422 423 /* Allocate mouse report buffer */ 424 mouse_report_buffer = (usb_mouse_report_t*)usb_malloc(sizeof(usb_mouse_report_t), 16); 425 if (!mouse_report_buffer) { 426 terminal_writestring("Failed to allocate mouse report buffer\n"); 427 return -1; 428 } 429 430 /* Allocate mouse TD */ 431 mouse_td = (uhci_td_t*)usb_malloc(sizeof(uhci_td_t), 16); 432 if (!mouse_td) { 433 terminal_writestring("Failed to allocate mouse TD\n"); 434 return -1; 435 } 436 437 /* Set up interrupt transfer for mouse */ 438 /* Token format: MaxLen(11) | 0 | DataToggle(1) | Endpoint(4) | DevAddr(7) | PID(8) */ 439 /* Using: Device 0, Endpoint 1, Data Toggle 0, MaxLen 2 (3 bytes - 1) */ 440 mouse_td->link_ptr = 1; /* Terminate */ 441 mouse_td->status = TD_STATUS_ACTIVE | TD_STATUS_IOC | TD_STATUS_LS | (3 << 27); /* 3 errors */ 442 mouse_td->token = (2 << 21) | (0 << 19) | (1 << 15) | (0 << 8) | TD_TOKEN_PID_IN; 443 mouse_td->buffer = virt_to_phys(mouse_report_buffer); 444 445 /* Link to interrupt queue */ 446 interrupt_qh->element_link_ptr = virt_to_phys(mouse_td); 447 448 /* Debug: print TD configuration */ 449 terminal_writestring("TD Config:\n"); 450 terminal_writestring(" status=0x"); 451 for (int i = 7; i >= 0; i--) { 452 uint8_t nibble = (mouse_td->status >> (i * 4)) & 0xF; 453 terminal_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10); 454 } 455 terminal_writestring("\n token=0x"); 456 for (int i = 7; i >= 0; i--) { 457 uint8_t nibble = (mouse_td->token >> (i * 4)) & 0xF; 458 terminal_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10); 459 } 460 terminal_writestring("\n buffer=0x"); 461 for (int i = 7; i >= 0; i--) { 462 uint8_t nibble = (mouse_td->buffer >> (i * 4)) & 0xF; 463 terminal_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10); 464 } 465 terminal_writestring("\n IntQH.elem=0x"); 466 for (int i = 7; i >= 0; i--) { 467 uint8_t nibble = (interrupt_qh->element_link_ptr >> (i * 4)) & 0xF; 468 terminal_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10); 469 } 470 terminal_writestring("\n"); 471 472 mouse_state.initialized = 1; 473 terminal_writestring("USB mouse initialized\n"); 474 475 return 0; 476 } 477 478 /* Poll USB mouse for updates */ 479 void usb_mouse_poll(void) { 480 static int poll_counter = 0; 481 if (!mouse_state.initialized || !mouse_td) return; 482 483 poll_counter++; 484 485 /* Check if transfer completed */ 486 if (!(mouse_td->status & TD_STATUS_ACTIVE)) { 487 /* Check for errors */ 488 if (mouse_td->status & (TD_STATUS_STALLED | TD_STATUS_BABBLE | TD_STATUS_CRCTO)) { 489 /* Error - reactivate */ 490 mouse_td->status = TD_STATUS_ACTIVE | TD_STATUS_IOC | TD_STATUS_LS | (3 << 27); 491 return; 492 } 493 494 /* Update mouse state */ 495 mouse_state.buttons = mouse_report_buffer->buttons; 496 mouse_state.x += mouse_report_buffer->x; 497 mouse_state.y -= mouse_report_buffer->y; /* Invert Y */ 498 499 /* Clamp to screen bounds */ 500 if (mouse_state.x < 0) mouse_state.x = 0; 501 if (mouse_state.x >= 1024) mouse_state.x = 1023; 502 if (mouse_state.y < 0) mouse_state.y = 0; 503 if (mouse_state.y >= 768) mouse_state.y = 767; 504 505 /* Reactivate TD for next transfer */ 506 mouse_td->status = TD_STATUS_ACTIVE | TD_STATUS_IOC | TD_STATUS_LS | (3 << 27); 507 } 508 } 509 510 /* Get current USB mouse state */ 511 void usb_mouse_get_state(usb_mouse_state_t* state) { 512 if (state) { 513 state->x = mouse_state.x; 514 state->y = mouse_state.y; 515 state->buttons = mouse_state.buttons; 516 state->initialized = mouse_state.initialized; 517 } 518 } 519 520 /* Lua binding for USB mouse polling */ 521 int lua_usb_mouse_poll(lua_State* L) { 522 (void)L; /* Unused parameter */ 523 usb_mouse_poll(); 524 return 0; 525 } 526 527 /* Lua binding for USB mouse state */ 528 int lua_usb_mouse_get_state(lua_State* L) { 529 lua_newtable(L); 530 531 lua_pushinteger(L, mouse_state.x); 532 lua_setfield(L, -2, "x"); 533 534 lua_pushinteger(L, mouse_state.y); 535 lua_setfield(L, -2, "y"); 536 537 lua_pushinteger(L, mouse_state.buttons); 538 lua_setfield(L, -2, "buttons"); 539 540 lua_pushboolean(L, mouse_state.initialized); 541 lua_setfield(L, -2, "initialized"); 542 543 return 1; 544 }