/* * xhci_driver.h — xHCI USB host controller driver public API for StarKernel * * Register-layout definitions live in xhci.h; this header is the driver's * own state and public entry points, matching virtio_blk.h's split. */ #ifndef STARKERNEL_XHCI_DRIVER_H #define STARKERNEL_XHCI_DRIVER_H #include #include "starkernel/pci.h" #include "starkernel/xhci.h" /* Driver state for one xHCI controller instance. Only one controller is * supported (matches virtio_blk's single-device precedent). */ typedef struct { PciDevice pci; uint64_t bar0_phys; /* physical MMIO base, BAR0 */ xhci_cap_regs_t *cap; /* BAR0 + 0 */ xhci_op_regs_t *op; /* BAR0 + cap->cap_length */ xhci_runtime_regs_t *runtime; /* BAR0 + cap->rts_off */ xhci_doorbell_t *doorbell; /* BAR0 + cap->db_off */ uint32_t max_slots; uint32_t max_ports; uint32_t max_intrs; uint32_t max_scratchpad_bufs; /* Set up by xhci_bringup(); NULL/0 until then. */ void *dcbaa; /* Device Context Base Address Array */ void *scratchpad_arr; /* array of scratchpad buffer pointers, if any */ xhci_trb_t *cmd_ring; /* Command Ring, XHCI_RING_TRB_COUNT TRBs; * index XHCI_RING_TRB_COUNT-1 is a * permanent Link TRB back to index 0 */ uint32_t cmd_ring_cycle; /* current Command Ring Cycle State (RCS) */ uint32_t cmd_ring_enq; /* next free Command Ring index (0..COUNT-2) */ xhci_trb_t *evt_ring; /* Event Ring, XHCI_RING_TRB_COUNT TRBs */ void *evt_ring_seg_table; /* Event Ring Segment Table (1 entry) */ uint32_t evt_ring_cycle; /* current Event Ring Cycle State */ uint32_t evt_ring_deq; /* current Event Ring dequeue index */ xhci_intr_regs_t *intr0; /* Interrupter 0 register set, cached * by xhci_bringup() for * xhci_poll_events() */ /* Milestone 2e: connect -> Enable Slot correlation. port_slot_id is * indexed by port_id - 1 (1-based port IDs, matching PORTSC/Port * Status Change Event numbering); 0 means no slot allocated for that * port yet. Fixed-size, not heap-allocated -- XHCI_MAX_TRACKED_PORTS * comfortably covers any real or emulated root hub's port count * without adding a new kmalloc_aligned() call to xhci_bringup(); ports * beyond this bound (checked against both this array and max_ports) * are simply not tracked, matching this driver's existing preference * for fixed allocations over dynamic growth (xhci.h's own ring-sizing * rationale). Only one Enable Slot is ever in flight at a time (this * driver issues commands synchronously with respect to connect events, * not a queue) -- pending_connect_port_id is 0 when idle, or the * port_id whose Command Completion Event is still outstanding. */ uint32_t port_slot_id[XHCI_MAX_TRACKED_PORTS]; uint32_t pending_connect_port_id; uint32_t pending_connect_speed; /* PORTSC.Port Speed at connect time */ /* Milestone 2e: Address Device. This driver only ever addresses one * device at a time (single-drive-at-a-time scope), so these are * single, reused allocations rather than per-slot -- lazily allocated * on the first connect that reaches xhci_cmd_address_device(), then * reinitialised (not reallocated) on every subsequent connect. connect * state tracks which command a still-outstanding completion event * belongs to, since Enable Slot and Address Device are issued * sequentially, not concurrently, for a given connect. */ enum { XHCI_CONN_IDLE = 0, XHCI_CONN_AWAIT_ENABLE_SLOT, XHCI_CONN_AWAIT_ADDRESS_DEVICE, XHCI_CONN_AWAIT_DISABLE_SLOT } connect_state; uint32_t pending_connect_slot_id; /* Milestone 2e/2g: disconnect teardown. Same single-outstanding- * command assumption as Enable Slot/Address Device above -- a * disconnect that arrives while another Command Ring command is * already outstanding is dropped rather than queued (matches the * existing "enable slot already pending -- dropped" precedent). * pending_disable_slot_id is captured at disconnect time, since the * port's own tracked slot ID (port_slot_id[]) is cleared immediately * on disconnect so a fresh connect on the same port isn't confused * for one already in progress -- by the time the Disable Slot * command's completion arrives, the port array no longer has it. */ uint32_t pending_disable_slot_id; void *input_ctx; /* Input Control Ctx + Slot Ctx + EP0 Ctx (96 bytes, 32-byte contexts) */ void *device_ctx; /* Slot Ctx + EP0 Ctx (64 bytes) -- DCBAA[slot_id] points here */ xhci_trb_t *ep0_ring; /* EP0 Transfer Ring, XHCI_RING_TRB_COUNT TRBs */ uint32_t ep0_ring_cycle; uint32_t ep0_ring_enq; /* Milestone 2f: EP0 control transfers. Like connect_state, this * driver only ever has one control transfer outstanding at a time -- * pending_transfer_slot_id is 0 when idle, else the slot ID whose * Transfer Event (posted only by the Status Stage TRB, which alone * has IOC set) is still outstanding. transfer_purpose says which * request that is, since xhci_poll_events() needs to know which * buffer to interpret and what (if anything) to chain next on * success -- e.g. a successful short Configuration descriptor read * chains into a full-length read once wTotalLength is known. * device_descriptor is the full 18-byte standard USB device * descriptor; config_descriptor holds the Configuration descriptor * and everything after it in the same read (Interface + Endpoint * descriptors, concatenated, per USB spec) -- fixed 128 bytes, * comfortably covers a single-interface Mass Storage device's full * descriptor set without a dynamic allocation. All reused (not * per-slot), matching this driver's single-device scope. */ enum { XHCI_XFER_NONE = 0, XHCI_XFER_DEVICE_DESC, XHCI_XFER_CONFIG_DESC_SHORT, XHCI_XFER_CONFIG_DESC_FULL, XHCI_XFER_SET_CONFIG } transfer_purpose; uint32_t pending_transfer_slot_id; uint8_t device_descriptor[18]; uint8_t config_descriptor[128]; uint16_t config_total_length; /* Milestone 2g: bulk endpoints, discovered by walking the Endpoint * descriptors that follow the confirmed Mass Storage/BOT Interface * descriptor in config_descriptor. bEndpointAddress in full (not just * the endpoint number) -- bit 7 is needed later to pick the right * Doorbell target / EP Context DCI, and callers that want direction * alone can just mask it. 0 means "not found yet" for both -- * endpoint address 0 is always EP0 (control), never a valid bulk * endpoint address, so it's a safe not-found sentinel. */ uint8_t bulk_in_ep_addr; uint16_t bulk_in_max_packet; uint8_t bulk_out_ep_addr; uint16_t bulk_out_max_packet; /* Deferred chaining: a doorbell ring (new control transfer) must * never happen synchronously from inside xhci_poll_events()'s event- * processing loop, before ERDP has been updated for the event * currently being handled -- confirmed live (amd64 QEMU) to hang the * guest outright when tried (a doorbell rung mid-acknowledgment of * the previous event, evidenced by checkpoint logging showing * execution stop exactly at the doorbell MMIO write). Chained * requests (device descriptor -> short config read -> full config * read) instead set these fields during event processing; the actual * doorbell ring happens once, after the main loop and the ERDP * write, from a small dispatch at the end of xhci_poll_events(). */ enum { XHCI_NEXT_ACTION_NONE = 0, XHCI_NEXT_ACTION_GET_DEVICE_DESC, XHCI_NEXT_ACTION_GET_CONFIG_DESC, XHCI_NEXT_ACTION_SET_CONFIG } next_action; uint32_t next_action_slot_id; uint16_t next_action_length; uint8_t next_action_config_value; /* SET_CONFIGURATION's wValue, staged by * the CONFIG_DESC_FULL handler once * bConfigurationValue is known */ } xhci_dev_t; /* * xhci_find_and_map — locate the xHCI controller on PCI bus 0, enable it * (I/O+MEM+bus-master), map its BAR0 MMIO region, and * fill in the four register-region pointers in *dev. * * dev must point to a zero-initialised xhci_dev_t. * * Returns 0 on success. * Returns -1 if no xHCI device was found on the PCI bus. * Returns -2 if the BAR0 mapping failed. */ int xhci_find_and_map(xhci_dev_t *dev); /* * xhci_bringup — reset the controller, allocate and program the DCBAA, * Command Ring, and Event Ring (Interrupter 0), then start * the controller (RUN/STOP=1) and confirm it left the * halted state. * * Must be called after a successful xhci_find_and_map(). Does not enable * interrupts (USBCMD.INTE / IMAN.IE) -- this driver is polled, not * interrupt-driven (see xhci_poll_events()'s own doc comment for why). * * Returns 0 on success. * Returns -1 on reset timeout. * Returns -2 on allocation failure. * Returns -3 if the controller failed to leave the halted state after RUN. * On success, latches dev into the module-static pointer xhci_poll_events() * reads -- only one controller is supported, matching virtio_blk's * single-device precedent. */ int xhci_bringup(xhci_dev_t *dev); /* * xhci_poll_events — read Interrupter 0's Event Ring, dispatching each TRB * by type: Port Status Change reads PORTSC to log * connect/disconnect and acknowledges CSC; Command * Completion and Transfer Event are logged only (slot * allocation and BOT transfers are later increments). * Advances the Event Ring dequeue pointer and clears * ERDP.EHB when done. * * Polled, not interrupt-driven: an initial attempt at IRQ delivery * (Milestone 2d's first draft) found the amd64 PCI INTx routing formula * gives a demonstrably wrong GSI (checked live via QMP query-pci: xHCI at * PCI slot 4 reports IRQ 10, the formula predicted 16), and the * aarch64/riscv64 slot/pin-derived source IDs were unverified at the new * slot this controller occupies. Rather than guess further at chipset * PIRQ routing, this matches Section U item 6's own design intent * (Captain Bob: "interrupt-driven, coarse cadence, cheap early-exit... * quick check blocks... done") via sk_repl_idle()'s existing coarse-cadence * hook instead of a per-arch IRQ path -- USB insertion is a human-timescale * event, not a hot path, so polling costs nothing meaningful here. * * No arguments and no return value -- only one xHCI controller is * supported, so the caller needs no device handle. A no-op if * xhci_bringup() has not completed successfully (dev pointer not yet * latched). */ void xhci_poll_events(void); /* * xhci_cmd_enable_slot — submit an Enable Slot command TRB to the Command * Ring and ring doorbell 0. Does not wait for or * read the resulting Command Completion Event -- it * arrives asynchronously via xhci_poll_events(), * which correlates the returned Slot ID back to * dev->pending_connect_port_id and records it in * dev->port_slot_id[]. * * Called from xhci_poll_events()'s own Port Status Change handling on a * real connect event -- not called directly by other code. * * Returns 0 if the command was posted, -1 if dev/dev->cmd_ring is not set * up (xhci_bringup() has not completed). */ int xhci_cmd_enable_slot(xhci_dev_t *dev); /* * xhci_cmd_disable_slot — submit a Disable Slot command TRB for slot_id * and ring doorbell 0. Does not wait for or read * the resulting Command Completion Event -- it * arrives asynchronously via xhci_poll_events(), * which clears DCBAA[slot_id] on success. * * Called from xhci_poll_events()'s own Port Status Change handling on a * real disconnect event, for a slot that was actually addressed -- not * called directly by other code. * * Returns 0 if the command was posted, -1 if dev/dev->cmd_ring is not set * up. */ int xhci_cmd_disable_slot(xhci_dev_t *dev, uint32_t slot_id); /* * xhci_cmd_address_device — build the Input Context (Slot + EP0, add-only), * program DCBAA[slot_id] with the Device Context, * allocate the EP0 Transfer Ring, and submit an * Address Device command TRB. * * speed is the PORTSC.Port Speed value read live at the connect this call * is servicing (xHCI 1.2 spec table 7-13 speed IDs) -- used to pick EP0's * default Max Packet Size before any device descriptor has been read. * * Refuses (-2) if HCCPARAMS1.CSZ indicates 64-byte contexts -- only * 32-byte contexts are implemented (see xhci.h's own doc comment on * xhci_slot_ctx32_t). * * Called from xhci_poll_events()'s Command Completion handling once Enable * Slot succeeds -- not called directly by other code. * * Returns 0 if the command was posted, -1 on allocation failure, -2 if * 64-byte contexts are required. */ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id, uint32_t port_id, uint32_t speed); /* * xhci_ep0_get_device_descriptor — issue a standard GET_DESCRIPTOR * (Device) control transfer (Setup + * Data-IN + Status-OUT stages) on * slot_id's EP0, reading the 18-byte * result into dev->device_descriptor. * Sets dev->transfer_purpose so * xhci_poll_events() knows how to * interpret the completion. * * Called once Address Device succeeds -- not called directly by other * code. * * Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not * set up. */ int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id); /* * xhci_ep0_get_config_descriptor — issue a GET_DESCRIPTOR (Configuration) * control transfer for `length` bytes, * reading into dev->config_descriptor * (capped to its fixed size). Used * twice per device: once for a short * 9-byte read (just the Configuration * descriptor header, to learn * wTotalLength) and once for the full * read once that length is known -- * xhci_poll_events() chains the second * call automatically on the first * read's success. * * Called once the device descriptor read succeeds -- not called directly * by other code. * * Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not * set up. */ int xhci_ep0_get_config_descriptor(xhci_dev_t *dev, uint32_t slot_id, uint16_t length); /* * xhci_ep0_set_configuration — issue a SET_CONFIGURATION control transfer * (Setup + Status stage only, no Data stage) * with wValue = config_value. Moves the * device from Addressed into Configured * state -- required before any endpoint * other than EP0 (i.e. the bulk IN/OUT * endpoints 2g needs) can be used. * * Called once the Configuration descriptor read confirms a Mass Storage/ * SCSI/BOT device, with config_value = that descriptor's own * bConfigurationValue field -- not called directly by other code. * * Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not * set up. */ int xhci_ep0_set_configuration(xhci_dev_t *dev, uint32_t slot_id, uint8_t config_value); #endif /* STARKERNEL_XHCI_DRIVER_H */