/* * 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, XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT } 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, XHCI_XFER_CBW_SENT, XHCI_XFER_BOT_DATA_IN, XHCI_XFER_BOT_DATA_OUT, XHCI_XFER_CSW_RECEIVED } 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; /* Milestone 2g: bulk endpoint Transfer Rings, one per direction -- * same fixed-ring-plus-Link-TRB pattern as ep0_ring, lazily allocated * once and reused across every connect (single-device scope, matching * every other ring in this driver). Not usable for actual transfers * until xhci_cmd_configure_endpoint() succeeds -- allocating them * early (rather than only after success) keeps the allocation site in * one place and lets the Input Context's EP Contexts point at real, * already-initialised rings before the command is even submitted. */ xhci_trb_t *bulk_in_ring; uint32_t bulk_in_ring_cycle; uint32_t bulk_in_ring_enq; xhci_trb_t *bulk_out_ring; uint32_t bulk_out_ring_cycle; uint32_t bulk_out_ring_enq; /* Milestone 2g: Bulk-Only Transport. bot_cbw/bot_csw are reused across * every command (single-outstanding-transfer scope, matching every * other buffer in this driver) -- built/overwritten fresh each call, * not preserved between calls. bot_next_tag is a free-running counter * for dCBWTag; bot_last_tag latches the tag of the CBW currently in * flight, so the CSW stage can verify dCSWTag matches (BOT spec * requirement) without needing to re-derive it. bot_data_buf is a * fixed 1024-byte Data-In destination -- sized to cover exactly one * Forth block (BLKIO_FORTH_BLOCK_SIZE, block_subsystem.c's own unit) * as two consecutive 512-byte SCSI blocks, which is what * xhci_bot_read_block() actually requests once Milestone 2h's blkio * backend calls this path with real block-subsystem-driven sizes; grown * from the single-512-byte-block buffer of the increment that first * added it. bot_expected_data_len is the byte count the Data-In stage * was told to read, staged at CBW build time and consumed once the * Data-In TRB is actually enqueued. */ usb_bot_cbw_t bot_cbw; usb_bot_csw_t bot_csw; uint32_t bot_next_tag; uint32_t bot_last_tag; uint8_t bot_data_buf[1024]; uint32_t bot_expected_data_len; /* Milestone 2g follow-up: TEST UNIT READY unit-init sequence, ahead of * a real READ(10). bot_cmd_kind says which SCSI command the CBW/CSW * currently in flight actually is, since XHCI_XFER_CSW_RECEIVED alone * doesn't distinguish a TUR completion from a READ10 completion -- * both go through the identical CBW->Data-In(if any)->CSW chain. * bot_tur_retries counts TUR attempts that came back FAILED/PHASE * ERROR (a fresh SCSI target's standard first-command UNIT ATTENTION * behavior, not a driver defect -- see SCSI_CMD_TEST_UNIT_READY's own * doc comment in xhci.h); capped at XHCI_BOT_TUR_MAX_RETRIES. The * pending bot_read10_* fields latch a caller's requested READ(10) so * it can be issued once TUR reports PASS -- xhci_bot_read_block() is * the entry point that stages these and kicks off TUR first, rather * than callers driving xhci_bot_send_read10() directly. * * Milestone 2h adds BOT_CMD_READ_CAPACITY10 (see * xhci_bot_send_read_capacity10()) and bot_last_status: every command * kind now resets bot_cmd_kind to BOT_CMD_NONE and sets * bot_last_status once its own CSW is fully processed (TEST UNIT * READY is the one exception -- a PASS or an in-progress retry both * stay non-terminal, chaining into the next command instead). This is * what lets xhci_bot_wait_for_idle() -- a synchronous busy-wait, * called from OUTSIDE xhci_poll_events(), never from within it -- * detect "this command's whole chain is finished" without needing to * know which specific command it was waiting on. */ enum { BOT_CMD_NONE = 0, BOT_CMD_TEST_UNIT_READY, BOT_CMD_READ10, BOT_CMD_READ_CAPACITY10, BOT_CMD_WRITE10 } bot_cmd_kind; enum { BOT_STATUS_IDLE = 0, BOT_STATUS_PASS, BOT_STATUS_FAILED, BOT_STATUS_TIMEOUT } bot_last_status; /* Which command a TUR-PASS should chain into -- TEST UNIT READY's own * completion handling can't tell READ10 and READ CAPACITY10 apart * otherwise, since both now go through the identical TUR-first * sequencing xhci_bot_read_block()/xhci_bot_get_capacity() both use. * Set by whichever of those two entry points kicked off the TUR. */ enum { BOT_TUR_CHAIN_NONE = 0, BOT_TUR_CHAIN_READ10, BOT_TUR_CHAIN_READ_CAPACITY10, BOT_TUR_CHAIN_WRITE10 } bot_tur_chain_target; uint32_t bot_tur_retries; uint32_t bot_read10_lba; uint16_t bot_read10_num_blocks; uint32_t bot_read10_block_size; /* WRITE(10) mirror of bot_read10_* above -- kept as separate fields * rather than renaming/reusing the read ones, so the already-tested * READ10 path is never touched by this addition (FABRIC-3.md §F.1). */ uint32_t bot_write10_lba; uint16_t bot_write10_num_blocks; uint32_t bot_write10_block_size; /* Latched from a successful READ CAPACITY(10) Data-In reply -- see * SCSI_CMD_READ_CAPACITY10's own doc comment in xhci.h for field * meaning. Untouched (stale) on a FAILED/TIMEOUT completion; callers * must check xhci_bot_wait_for_idle()'s return value, not just read * these blindly. */ uint32_t bot_cap_last_lba; uint32_t bot_cap_block_size; /* Milestone 2h: set by the SET_CONFIGURATION completion handler * (inside xhci_poll_events()'s own call frame, so it only sets a flag * -- no doorbell ring, no xhci_bot_wait_for_idle() call, both unsafe * from there) once a device is confirmed Mass Storage/SCSI/BOT and * configured. Consumed by sk_repl_idle() strictly after its own * xhci_poll_events() call has returned, which is the only place safe * to actually act on it -- calls blkio_usb_open_msc() (READ CAPACITY(10) * + xhci_bot_wait_for_idle(), both requiring that same "outside * xhci_poll_events()" constraint) then blk_subsys_attach_device(). */ uint8_t bot_msc_attach_pending; uint32_t bot_msc_attach_slot_id; /* Set by sk_repl_idle() once blk_subsys_attach_device() actually * succeeds (not by the SET_CONFIGURATION handler itself -- attach can * still fail, e.g. a bad capacity query, in which case there is * nothing to detach later). Read by the PORTSC disconnect handler * below to decide whether this disconnect needs a block-subsystem * detach at all -- a device that never successfully attached (or that * was already detached) produces no spurious detach flag. */ uint8_t bot_msc_attached; /* Set by the PORTSC disconnect handler (see xhci_poll_events()'s own * disconnect handling) only when bot_msc_attached is set -- same * flag+consume-in-sk_repl_idle() shape as bot_msc_attach_pending, * chosen deliberately over hooking the Disable Slot completion: * disconnect is the unambiguous signal, while Disable Slot is only * even issued when connect_state == XHCI_CONN_IDLE (see the "command * ring busy" skip path) and would silently miss a detach otherwise. * No xhci_bot_wait_for_idle() call is needed for detach itself (no * device round-trip -- it's local block_subsystem.c bookkeeping), but * consuming it in sk_repl_idle() anyway matches the attach path's own * shape and keeps xhci.c decoupled from block_subsystem.c. */ uint8_t bot_msc_detach_pending; /* 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_CONFIGURE_ENDPOINT, XHCI_NEXT_ACTION_SET_CONFIG, XHCI_NEXT_ACTION_BOT_DATA_IN, XHCI_NEXT_ACTION_BOT_DATA_OUT, XHCI_NEXT_ACTION_BOT_CSW_RECEIVE, XHCI_NEXT_ACTION_BOT_SEND_TUR, XHCI_NEXT_ACTION_BOT_SEND_READ10, XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10, XHCI_NEXT_ACTION_BOT_SEND_WRITE10 } 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_get_dev — return the module-static xhci_dev_t* xhci_poll_events() * itself reads (only one controller is supported), or NULL * if xhci_bringup() has not completed successfully yet. * * Milestone 2h: callers outside this driver (sk_repl_idle(), eventually * the block-subsystem attach glue) have no other way to reach the device * handle -- every earlier caller of this driver's API already had one in * hand (kernel_main.c's own local xhci_dev_t), which doesn't help code * that only runs later, on a hotplug event it wasn't the one to observe. */ xhci_dev_t *xhci_get_dev(void); /* * 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_cmd_configure_endpoint — build the Input Context (Slot + the two * bulk EP Contexts, add-only), allocate the * bulk Transfer Rings, and submit a * Configure Endpoint command TRB for * slot_id. * * Per xHCI 1.2 spec section 4.3.5, this must be issued after enumeration * has identified the endpoints a device's chosen configuration/interface * actually uses, and before the USB SET_CONFIGURATION request is sent to * the device -- the reverse of that order (which this driver used before * this increment) works against QEMU's lenient emulation but is not * spec-correct. Requires dev->bulk_in_ep_addr/bulk_out_ep_addr to already * be populated (2f's config descriptor walk) -- refuses if either is * still 0 (not found). * * The Slot Context's Route String/Speed/Root Hub Port/Interrupter Target * fields are copied from the already-addressed device's own Device * Context (populated by a prior successful Address Device) rather than * reconstructed from scratch -- those values aren't retained anywhere * else by the time enumeration reaches this point (pending_connect_port_id * is cleared as soon as Address Device completes). Only Context Entries is * changed, to the highest DCI now in use. * * Called from xhci_poll_events()'s deferred next_action dispatch once the * full Configuration descriptor has confirmed a Mass Storage/BOT interface * and identified both bulk endpoints -- not called directly by other code. * * Returns 0 if the command was posted, -1 on allocation failure or missing * prerequisite state, -2 if 64-byte contexts are required. */ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id); /* * xhci_bot_send_read10 — build a Command Block Wrapper for a SCSI * READ(10) and submit it on the bulk OUT Transfer * Ring; the Data-In stage and CSW receive follow * automatically once this CBW's own completion * arrives (see xhci_bot_read_data_in()/ * xhci_bot_receive_csw() below), same deferred- * chaining pattern as device descriptor -> config * descriptor -> Configure Endpoint -> SET_CONFIG. * * lba is the starting Logical Block Address, num_blocks the SCSI transfer * length (blocks, not bytes -- READ(10)'s own field), block_size the * device's actual bytes-per-block, used only to compute * dCBWDataTransferLength (the data stage's total byte length CBW * declares up front, not carried in the CDB itself). num_blocks*block_size * must fit in dev->bot_data_buf (512 bytes, this increment's whole scope * -- see xhci_dev_t's own doc comment) -- refuses otherwise. * * Does not wait for or read any of the three stages' Transfer Events -- * they arrive asynchronously via xhci_poll_events(), correlated via * dev->transfer_purpose, same pattern as every other transfer in this * driver. The final result (CSW signature/tag/status validated) is only * ever logged, not returned to any caller -- there is no synchronous * "did the read succeed" API yet; that's 2h's problem once something * actually needs the data back. * * Requires bulk_out_ep_addr/bulk_out_ring and bulk_in_ep_addr/ * bulk_in_ring to already be populated (2f/2g's config descriptor walk * and Configure Endpoint command) -- refuses if any prerequisite is * missing. * * Returns 0 if the CBW was posted, -1 if a prerequisite is missing or * the requested transfer size exceeds dev->bot_data_buf. * * Low-level primitive -- sets dev->bot_cmd_kind = BOT_CMD_READ10 but does * not run TEST UNIT READY first. Most callers want xhci_bot_read_block() * below instead; this is called directly only by xhci_poll_events()'s own * deferred dispatch (XHCI_NEXT_ACTION_BOT_SEND_READ10, once a prior TUR * has reported PASS) and by xhci_bot_read_block() itself when unit-ready * confirmation isn't wanted. */ int xhci_bot_send_read10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba, uint16_t num_blocks, uint32_t block_size); /* * xhci_bot_send_write10 — build a Command Block Wrapper for a SCSI * WRITE(10) and submit it on the bulk OUT * Transfer Ring; the Data-Out stage and CSW * receive follow automatically once this CBW's * own completion arrives (see * xhci_bot_write_data_out()/xhci_bot_receive_csw() * below) -- direct mirror of * xhci_bot_send_read10() above, same deferred- * chaining pattern, opposite data direction. * * Unlike READ(10), the caller must have already placed the num_blocks* * block_size bytes to be written into dev->bot_data_buf *before* calling * this -- there is no separate "stage the payload" step, matching how * xhci_bot_read_block()'s caller reads the result back out of * bot_data_buf only *after* the whole chain completes. bmCBWFlags is 0 * (host -> device data stage), not USB_BOT_CBW_FLAG_DATA_IN -- the one * CBW-level difference from xhci_bot_send_read10(). CDB layout (SBC-3 * section 5.32) is otherwise identical to READ(10)'s: opcode, then LBA * and Transfer Length as the same big-endian fields. * * lba/num_blocks/block_size have the same meaning and the same * num_blocks*block_size <= sizeof(dev->bot_data_buf) bound as * xhci_bot_send_read10(). Requires the same bulk endpoint/ring * prerequisites -- refuses if any are missing. * * Returns 0 if the CBW was posted, -1 if a prerequisite is missing or * the requested transfer size exceeds dev->bot_data_buf. * * Low-level primitive -- sets dev->bot_cmd_kind = BOT_CMD_WRITE10 but * does not run TEST UNIT READY first. Most callers want * xhci_bot_write_block() below instead; this is called directly only by * xhci_poll_events()'s own deferred dispatch * (XHCI_NEXT_ACTION_BOT_SEND_WRITE10, once a prior TUR has reported * PASS). */ int xhci_bot_send_write10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba, uint16_t num_blocks, uint32_t block_size); /* * xhci_bot_send_test_unit_ready — build a Command Block Wrapper for SCSI * TEST UNIT READY (6-byte CDB, no data * stage) and submit it on the bulk OUT * Transfer Ring. Sets * dev->bot_expected_data_len = 0 so * xhci_poll_events()'s CBW-completion * handler skips the Data-In stage and * goes straight to CSW receive, per BOT * spec section 6.3 (host expects no * data). dev->bot_cmd_kind is set to * BOT_CMD_TEST_UNIT_READY so the CSW * handler knows to interpret the result * as a unit-ready check, not a data * command. * * Requires the same bulk endpoint/ring prerequisites as * xhci_bot_send_read10() -- refuses if any are missing. * * Called by xhci_bot_read_block() to start its TUR-then-READ10 sequence, * and by xhci_poll_events()'s own deferred dispatch * (XHCI_NEXT_ACTION_BOT_SEND_TUR) to retry a failed TUR -- not intended * to be called directly by other code. * * Returns 0 if the CBW was posted, -1 if a prerequisite is missing. */ int xhci_bot_send_test_unit_ready(xhci_dev_t *dev, uint32_t slot_id); /* * xhci_bot_read_block — the real entry point for reading a block from the * attached SCSI device. Latches lba/num_blocks/ * block_size into dev->bot_read10_*, resets * dev->bot_tur_retries to 0, and issues a TEST * UNIT READY first rather than a bare READ(10). * * A freshly attached SCSI target conventionally fails its first command * with CHECK CONDITION/UNIT ATTENTION until that condition is drained * (see SCSI_CMD_TEST_UNIT_READY's own doc comment in xhci.h) -- this * function's whole purpose is absorbing that via a bounded number of TUR * retries (XHCI_BOT_TUR_MAX_RETRIES) before the actual READ(10) is ever * sent, rather than making every caller reimplement that sequencing. * xhci_poll_events()'s deferred dispatch chains TUR -> (retry TUR | * READ10) -> Data-In -> CSW automatically once this call kicks it off; * the eventual result (PASS/FAILED, or "gave up after N TUR retries") is * only ever logged, matching xhci_bot_send_read10()'s own current scope * -- there is still no synchronous "did the read succeed, here's the * data" API (2h's problem, per xhci_bot_send_read10()'s doc comment). * * Returns 0 if TEST UNIT READY was posted, -1 if a prerequisite is * missing or the requested transfer size exceeds dev->bot_data_buf (the * same check xhci_bot_send_read10() performs, done up front here so a * bad request is rejected before spending a TUR round-trip on it). */ int xhci_bot_read_block(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba, uint16_t num_blocks, uint32_t block_size); /* * xhci_bot_write_block — the real entry point for writing a block to the * attached SCSI device. Direct mirror of * xhci_bot_read_block() above: latches * lba/num_blocks/block_size into * dev->bot_write10_*, resets dev->bot_tur_retries * to 0, and issues a TEST UNIT READY first rather * than a bare WRITE(10), for the same first-command * UNIT ATTENTION reason. * * As with xhci_bot_send_write10(), the caller must have already placed * the payload bytes into dev->bot_data_buf before calling this. * * Returns 0 if TEST UNIT READY was posted, -1 if a prerequisite is * missing or the requested transfer size exceeds dev->bot_data_buf (same * check xhci_bot_send_write10() performs, done up front here so a bad * request is rejected before spending a TUR round-trip on it). */ int xhci_bot_write_block(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba, uint16_t num_blocks, uint32_t block_size); /* * xhci_bot_send_read_capacity10 — build a Command Block Wrapper for SCSI * READ CAPACITY(10) (SBC-3 section 5.14, * opcode 0x25, see xhci.h) and submit it * on the bulk OUT Transfer Ring. Sets * dev->bot_cmd_kind = BOT_CMD_READ_CAPACITY10 * and dev->bot_expected_data_len = * SCSI_READ_CAPACITY10_DATA_LEN (8) so * the existing CBW-completion handler * runs the Data-In stage (unlike TEST * UNIT READY, this command does have a * short reply). On a PASS CSW, * dev->bot_cap_last_lba/bot_cap_block_size * are parsed from the 8-byte reply and * bot_cmd_kind resets to BOT_CMD_NONE -- * this command is always terminal, it * never chains into anything else. * * Requires the same bulk endpoint/ring prerequisites as * xhci_bot_send_read10()/xhci_bot_send_test_unit_ready() -- refuses if any * are missing. * * Low-level primitive -- does not run TEST UNIT READY first. Sent to a * freshly attached device with no TUR ahead of it, this eats the same * first-command UNIT ATTENTION READ(10) used to before xhci_bot_read_block() * existed (confirmed live -- see this driver's own Milestone 2h capture * log). Most callers want xhci_bot_get_capacity() below instead; this is * called directly only by xhci_poll_events()'s own deferred dispatch * (XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10, once a prior TUR has * reported PASS). * * Returns 0 if the CBW was posted, -1 if a prerequisite is missing. */ int xhci_bot_send_read_capacity10(xhci_dev_t *dev, uint32_t slot_id); /* * xhci_bot_get_capacity — the real entry point for learning a device's * block size/capacity. Sets * dev->bot_tur_chain_target = * BOT_TUR_CHAIN_READ_CAPACITY10, resets * dev->bot_tur_retries, and issues a TEST UNIT * READY first rather than a bare READ CAPACITY(10) * -- same reasoning as xhci_bot_read_block(), * and the same TUR retry budget * (XHCI_BOT_TUR_MAX_RETRIES). * * Returns 0 if TEST UNIT READY was posted, -1 if a prerequisite is * missing (same checks xhci_bot_send_read_capacity10() performs, done up * front here so a bad request is rejected before spending a TUR * round-trip on it). */ int xhci_bot_get_capacity(xhci_dev_t *dev, uint32_t slot_id); /* * xhci_bot_wait_for_idle — busy-wait for the BOT command currently in * flight (bot_cmd_kind != BOT_CMD_NONE) to reach * a terminal state, by calling xhci_poll_events() * in a loop up to max_iters times. * * This is Milestone 2h's synchronous bridge over an otherwise fully * asynchronous, polled driver -- block_subsystem.c's blkio_read()/ * blkio_info() etc. are ordinary synchronous function calls with no way * to "come back later" for a result, so something has to spin until the * driver's own event-driven state machine finishes. * * MUST NOT be called from inside xhci_poll_events() itself, or from any * function xhci_poll_events() calls (a next_action dispatch, a Transfer * Event handler) -- xhci_poll_events() is not reentrant, and this * function's own busy-wait loop calls it again on every iteration; doing * so from within an already-running call would recurse into live Event * Ring/ERDP processing (the same class of hazard this driver's * next_action deferral mechanism exists to avoid for doorbell rings, see * xhci_dev_t's own doc comment on next_action). Only call this from a * context that is definitely outside that call frame -- a caller in * sk_repl_idle() invoked strictly after its own xhci_poll_events() call * has already returned, for example. * * Returns BOT_STATUS_PASS/BOT_STATUS_FAILED (dev->bot_last_status, as left * by whichever command was in flight) once bot_cmd_kind returns to * BOT_CMD_NONE, or BOT_STATUS_TIMEOUT if max_iters is exhausted first * (the command may still complete later -- this driver has no cancel * operation, the caller just stops waiting). Returns BOT_STATUS_FAILED * immediately if dev is NULL. */ int xhci_bot_wait_for_idle(xhci_dev_t *dev, uint32_t max_iters); /* * xhci_bot_read_data_in — submit a Normal TRB on the bulk IN Transfer * Ring to read dev->bot_expected_data_len bytes * into dev->bot_data_buf. * * Called from xhci_poll_events()'s deferred next_action dispatch once a * CBW's own Command completion (XHCI_XFER_CBW_SENT) succeeds -- not * called directly by other code. * * Returns 0 if the TRB was posted, -1 if bulk_in_ring isn't set up. */ int xhci_bot_read_data_in(xhci_dev_t *dev, uint32_t slot_id); /* * xhci_bot_write_data_out — submit a Normal TRB on the bulk OUT Transfer * Ring to write dev->bot_expected_data_len * bytes from dev->bot_data_buf. Direct mirror * of xhci_bot_read_data_in() above, opposite * ring/direction. * * Called from xhci_poll_events()'s deferred next_action dispatch once a * WRITE(10) CBW's own Command completion (XHCI_XFER_CBW_SENT) succeeds -- * not called directly by other code. * * Returns 0 if the TRB was posted, -1 if bulk_out_ring isn't set up. */ int xhci_bot_write_data_out(xhci_dev_t *dev, uint32_t slot_id); /* * xhci_bot_receive_csw — submit a Normal TRB on the bulk IN Transfer Ring * to read the 13-byte Command Status Wrapper into * dev->bot_csw. * * Called from xhci_poll_events()'s deferred next_action dispatch once the * Data-In stage's own Transfer Event (XHCI_XFER_BOT_DATA_IN) succeeds -- * not called directly by other code. The CSW's own completion * (XHCI_XFER_CSW_RECEIVED) is where signature/tag/status validation * against dev->bot_last_tag actually happens, in xhci_poll_events() * itself, not here. * * Returns 0 if the TRB was posted, -1 if bulk_in_ring isn't set up. */ int xhci_bot_receive_csw(xhci_dev_t *dev, uint32_t slot_id); /* * 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 */