Artemis Milestone 2h (foundational): sync wait bridge + SCSI READ CAPACITY(10)
Closes the gap block_subsystem.c needs before any of 2h's real work (blkio_usb.c, attach wiring, hot-detach) can start: this driver is fully async/polled with no way for a synchronous caller (blkio_read()/ blkio_info() etc.) to get a result back. xhci_bot_wait_for_idle() is a bounded busy-wait over xhci_poll_events() -- MUST be called only from outside xhci_poll_events()'s own call frame, never from within it or a next_action dispatch (recursion into live Event Ring/ERDP processing, same class of hazard already documented for doorbell rings in this driver). xhci_get_dev() exposes the module-static device handle to outside callers that didn't observe the original hotplug event. SCSI READ CAPACITY(10) (opcode 0x25) is the other half -- nothing could learn a device's block size/capacity before this. First attempt sent it bare and hit the classic first-command UNIT ATTENTION (CSW FAILED); fixed with the same TUR-guard pattern READ(10) already used, generalized via a new bot_tur_chain_target field so TEST UNIT READY's PASS handling can chain into either command. bot_data_buf grown 512->1024 bytes (one Forth block = two 512-byte SCSI blocks, per block_subsystem.c's own 1KiB-unit convention). Verified live via a temporary probe (hot-attached disk/usb-thumbdrive- test.img via QMP, reverted after capture): TUR-guarded READ CAPACITY10 correctly reported last LBA=0x1ffff, block size=0x200 -- exactly 64MiB, matching the test image byte for byte -- followed by a TUR-guarded 1024-byte/2-block READ10, both PASS. All three architectures re-verified clean, probe-free boot to ok> on the reverted tree. FABRIC-2.md Section X 2h updated with the writeup; the blkio_usb.c backend itself is next. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
65effbd1ba
commit
d686f28853
@@ -320,6 +320,19 @@ typedef struct {
|
||||
* xhci_dev_t's bot_tur_retries doc comment. */
|
||||
#define XHCI_BOT_TUR_MAX_RETRIES 3u
|
||||
|
||||
/* SCSI READ CAPACITY(10) (SBC-3 section 5.14) -- 10-byte CDB, opcode 0x25,
|
||||
* every other CDB byte reserved/zero for the standard "report capacity"
|
||||
* form (LBA field left 0, PMI bit left clear). 8-byte Data-In reply:
|
||||
* bytes 0-3 = Returned Logical Block Address (the *last* valid LBA, not a
|
||||
* block count) big-endian, bytes 4-7 = Block Length in Bytes big-endian.
|
||||
* This is Milestone 2h's prerequisite for everything else -- there is no
|
||||
* other way for this driver to learn a device's block size or capacity,
|
||||
* and block_subsystem.c's blk_subsys_attach_device() needs exactly that
|
||||
* (via blkio_info()) before it can do anything with a device. */
|
||||
#define SCSI_CMD_READ_CAPACITY10 0x25u
|
||||
#define SCSI_CDB_LEN_READ_CAPACITY10 10u
|
||||
#define SCSI_READ_CAPACITY10_DATA_LEN 8u
|
||||
|
||||
/* Bulk-Only Transport Command Status Wrapper (same spec, section 5.2) --
|
||||
* received device-to-host on the bulk IN endpoint after the Data-In
|
||||
* stage, closing out every SCSI command. Fixed 13-byte wire layout; same
|
||||
|
||||
@@ -158,17 +158,20 @@ typedef struct {
|
||||
* 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 512-byte Data-In destination -- covers exactly one 512-byte
|
||||
* block, this increment's whole scope (a real multi-block/variable-
|
||||
* block-size transfer is 2h's problem once the block subsystem
|
||||
* actually calls this path with real sizes). 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. */
|
||||
* 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[512];
|
||||
uint8_t bot_data_buf[1024];
|
||||
uint32_t bot_expected_data_len;
|
||||
|
||||
/* Milestone 2g follow-up: TEST UNIT READY unit-init sequence, ahead of
|
||||
@@ -183,16 +186,51 @@ typedef struct {
|
||||
* 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. */
|
||||
* 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_READ10,
|
||||
BOT_CMD_READ_CAPACITY10
|
||||
} 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_target;
|
||||
uint32_t bot_tur_retries;
|
||||
uint32_t bot_read10_lba;
|
||||
uint16_t bot_read10_num_blocks;
|
||||
uint32_t bot_read10_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;
|
||||
|
||||
/* Deferred chaining: a doorbell ring (new control transfer) must
|
||||
* never happen synchronously from inside xhci_poll_events()'s event-
|
||||
@@ -214,7 +252,8 @@ typedef struct {
|
||||
XHCI_NEXT_ACTION_BOT_DATA_IN,
|
||||
XHCI_NEXT_ACTION_BOT_CSW_RECEIVE,
|
||||
XHCI_NEXT_ACTION_BOT_SEND_TUR,
|
||||
XHCI_NEXT_ACTION_BOT_SEND_READ10
|
||||
XHCI_NEXT_ACTION_BOT_SEND_READ10,
|
||||
XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10
|
||||
} next_action;
|
||||
uint32_t next_action_slot_id;
|
||||
uint16_t next_action_length;
|
||||
@@ -256,6 +295,19 @@ int xhci_find_and_map(xhci_dev_t *dev);
|
||||
*/
|
||||
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
|
||||
@@ -473,6 +525,92 @@ int xhci_bot_send_test_unit_ready(xhci_dev_t *dev, uint32_t slot_id);
|
||||
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_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
|
||||
|
||||
Reference in New Issue
Block a user