USB · Module 14
Bulk for Storage Devices
A request/response protocol built on a channel that has neither — 31 bytes of command, a data phase, 13 bytes of status, and a failure the class had to invent a name for.
Three chapters of Bulk as a mechanism. This is Bulk as a foundation — and specifically the one that carries nearly all the bulk traffic in the world.
Mass Storage is a case study in a general problem: building a request/response protocol on a channel that provides neither requests nor responses. Bulk gives you two unidirectional pipes and the guarantee that bytes arrive in order. Everything else has to be invented on top.
1. Two Pipes, No Framing
A Bulk-Only Transport device has exactly two bulk endpoints: one IN, one OUT. Chapter 9.4 established that those are separate, unidirectional, and unrelated to each other.
What Bulk provides:
- bytes arrive in order within an endpoint;
- bytes arrive intact or not at all — Chapter 14.2 §1;
- transfers have boundaries, marked by short packets — Chapter 12.2 §2.
What Bulk does not provide:
- any notion of a request or a reply;
- any relationship between what goes OUT and what comes IN;
- any framing above the transfer — a transfer boundary is not a message boundary unless somebody decides it is.
Two pipes that know nothing about each other, and a protocol whose entire job is to make them behave like one conversation.
So the class defines three phases, and their sequence is the conversation:
| Phase | Endpoint | Size |
|---|---|---|
| Command — CBW | bulk OUT | exactly 31 bytes |
| Data | either, as the command says | 0 to megabytes |
| Status — CSW | bulk IN | exactly 13 bytes |
2. The Wrappers
Both wrappers are fixed-length and signature-tagged, and both choices are doing work.
| CBW | CSW | |
|---|---|---|
| Length | 31 bytes | 13 bytes |
| Signature | dCBWSignature = 0x43425355 | dCSWSignature = 0x53425355 |
| On the wire (little-endian) | U S B C | U S B S |
| Direction | bulk OUT | bulk IN |
The signatures are ASCII and readable in a trace — USBC for a command, USBS for status — which is a deliberate debugging affordance rather than a checksum. They are not error detection; Chapter 11.6 already did that.
They are framing detection, and §3 is what that is for.
The other CBW fields that matter here:
| Field | Says |
|---|---|
dCBWDataTransferLength | how many bytes the data phase will move |
bmCBWFlags bit 7 | which direction — 1 = IN, 0 = OUT |
dCBWTag | an identifier the CSW echoes back |
bCBWCBLength + CBWCB | the actual command, up to 16 bytes |
The first two are Chapter 13.1 §1's wLength and direction bit, reinvented — because a SETUP packet's guarantees are not available here and the same two facts still have to be established up front.
3. The Phase Error
The failure the class had to invent a name for, and it is Chapter 12.4 §2's two beliefs, two layers up.
Both ends track which phase they are in, and neither transmits it. The host believes it is sending a command; the device believes it is still finishing the previous one. Nothing on the wire carries a phase, so the disagreement is invisible until something contradicts it.
What contradicts it is direction. The CBW said IN; the host then sends a packet on the OUT endpoint. That packet cannot belong to the command the device is executing, so the two ends are not in the same conversation.
bCSWStatus = 0x02— Phase Error — means: we are not talking about the same thing, and neither of us can work out where it went wrong.
And the recovery is deliberately heavy. A phase error is not retried. The host issues the class-specific Bulk-Only Mass Storage Reset, clears the halt on both endpoints, and starts over.
4. What a Fixed Length Buys
A CBW is exactly 31 bytes and a CSW exactly 13, and a device must reject anything else. That is stricter than it needs to be for parsing, and the strictness is the point.
With a fixed length, a wrapper is either present or absent — there is no partial wrapper, no ambiguity about whether more is coming, and no state where the device holds 20 bytes and waits. Chapter 12.2 §2's short-packet rule already tells it the transfer ended, so a 20-byte transfer on the command endpoint is unambiguously not a CBW rather than a CBW so far.
And the signature turns a resynchronisation problem into a test. After a phase error the device does not know where in the byte stream it is; the next thing it accepts must start with USBC and be 31 bytes long, and anything else is discarded. Two independent conditions, both cheap, and together they make a false positive very unlikely.
A fixed length plus a magic number is how a stream protocol recovers framing without a framing layer.
§6's B1 removes the length check and keeps the signature — and the measurement shows that the signature alone is not enough.
5. The Bulk-Only Transport Controller, as RTL
// ─────────────────────────────────────────────────────────────────────────
// usb_bot_pkg + usb_bot_ctl
//
// Classification: SIMPLIFIED SYNTHESIZABLE TEACHING RTL plus a CONCEPTUAL
// package. It models the three-phase state machine of sections 1 to 3 and
// nothing about the storage behind it.
//
// WHAT IT MODELS. The phase sequence, CBW validation, the data phase's
// direction and length, phase-error detection, and the CSW status.
//
// WHAT IT DOES NOT MODEL. The bulk transfers themselves (Chapters 14.1 to
// 14.3); the SCSI command inside CBWCB, which is a different specification
// entirely and is where dCBWTag, the LUN and REQUEST SENSE live; the medium;
// and the class-specific control requests other than the reset -- Get Max
// LUN is a control transfer (Module 13) and does not touch this machine.
//
// ── ON WHY THE PHASE IS STATE AND NOT DERIVED ───────────────────────────
// Nothing on the wire carries a phase (section 3). Both ends track it
// independently, and the ONLY evidence of disagreement is a packet that
// cannot belong to the current phase. That is why this block is a state
// machine rather than a decoder, and why phase_error is an output rather
// than an assertion: it is information the host needs, not a bug.
// ─────────────────────────────────────────────────────────────────────────
package usb_bot_pkg;
typedef enum logic [1:0] {
BP_CBW = 2'd0, // awaiting a 31-byte Command Block Wrapper
BP_DATA = 2'd1, // the command's data phase
BP_CSW = 2'd2 // sending the 13-byte Command Status Wrapper
} bot_phase_e;
// Section 2: little-endian, so these read as "USBC" and "USBS" on the wire.
localparam logic [31:0] CBW_SIG = 32'h43425355;
localparam logic [31:0] CSW_SIG = 32'h53425355;
localparam int unsigned CBW_LEN = 31;
localparam int unsigned CSW_LEN = 13;
// Section 3: three categories, not three severities.
localparam logic [7:0] CSW_PASS = 8'h00;
localparam logic [7:0] CSW_FAIL = 8'h01;
localparam logic [7:0] CSW_PHASE_ERROR = 8'h02;
endpackage
module usb_bot_ctl
import usb_bot_pkg::*;
(
input logic clk,
input logic rst_n,
input logic bus_reset,
input logic bot_reset, // class-specific Mass Storage Reset
// A bulk OUT transfer arrived on the command endpoint.
input logic out_pkt,
input logic [15:0] out_len,
input logic [31:0] out_sig,
input logic [31:0] out_dCBWDataLength,
input logic out_dir_in, // bmCBWFlags bit 7
input logic data_pkt,
input logic [15:0] data_len,
input logic data_dir_in, // which endpoint it ACTUALLY arrived on
input logic cmd_done,
input logic cmd_failed,
input logic [31:0] cmd_residue,
input logic csw_sent,
output bot_phase_e phase,
output logic cbw_valid,
output logic cbw_invalid, // malformed: stall BOTH endpoints
output logic phase_error,
output logic [7:0] csw_status,
output logic expect_data,
output logic expect_data_in
);
bot_phase_e ph_q;
logic [31:0] want_q, moved_q;
logic dir_q, phase_err_q, failed_q;
assign phase = ph_q;
assign expect_data = (ph_q == BP_DATA) && (want_q != 32'd0);
assign expect_data_in = dir_q;
assign phase_error = phase_err_q;
// SECTION 4: BOTH conditions. The signature alone is one 32-bit pattern
// that random data hits once in four billion; the length alone is one
// value in sixty-five thousand. Together they are a framing test, and
// section 6's B1 measures what dropping the length costs.
assign cbw_valid = out_pkt && (out_len == CBW_LEN[15:0])
&& (out_sig == CBW_SIG);
assign cbw_invalid = out_pkt && (ph_q == BP_CBW) && !cbw_valid;
// Section 3: three categories in priority order. A phase error outranks a
// command failure because it says the command itself is in doubt.
assign csw_status = phase_err_q ? CSW_PHASE_ERROR
: failed_q ? CSW_FAIL
: CSW_PASS;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ph_q <= BP_CBW; want_q <= '0; dir_q <= 1'b0;
phase_err_q <= 1'b0; failed_q <= 1'b0; moved_q <= '0;
end else if (bus_reset || bot_reset) begin
// The class-specific reset exists precisely so the two ends can
// resynchronise WITHOUT re-enumerating -- Chapter 14.2's ladder with
// a rung inserted between "halt" and "bus reset".
ph_q <= BP_CBW; want_q <= '0; dir_q <= 1'b0;
phase_err_q <= 1'b0; failed_q <= 1'b0; moved_q <= '0;
end else begin
case (ph_q)
BP_CBW: if (cbw_valid) begin
want_q <= out_dCBWDataLength;
dir_q <= out_dir_in;
moved_q <= '0;
phase_err_q <= 1'b0;
failed_q <= 1'b0;
// A zero-length command has NO data phase -- Chapter 13.1 section
// 3's rule, reinvented. TEST UNIT READY is the common case.
if (out_dCBWDataLength != 32'd0) ph_q <= BP_DATA;
else ph_q <= BP_CSW;
end
BP_DATA: begin
if (data_pkt) begin
// SECTION 3. A packet on the wrong endpoint cannot belong to
// the command in progress. This comparison is the entire
// phase-error detector.
if (data_dir_in != dir_q) phase_err_q <= 1'b1;
moved_q <= moved_q + {16'd0, data_len};
if ((moved_q + {16'd0, data_len}) >= want_q) ph_q <= BP_CSW;
end
if (cmd_done) begin
failed_q <= cmd_failed;
ph_q <= BP_CSW;
end
end
BP_CSW: begin
if (cmd_done) failed_q <= cmd_failed;
if (csw_sent) ph_q <= BP_CBW;
end
default: ph_q <= BP_CBW;
endcase
end
end
endmoduleWhat it models. The three-phase conversation and the one disagreement it can detect.
Engineering reason. Because two unidirectional pipes with no framing have to be made to behave like a request/response channel, and the seam is where the two ends stop agreeing.
Inputs. Wrapper arrivals with their length and signature, data-phase packets with their actual direction, the command's outcome, and two resets.
State retained. Two bits of phase, a 32-bit expected length, a moved counter, and three flags.
Outputs. The phase, wrapper validity, the phase-error indication, the status byte, and what the data phase should look like.
Hardware implied. A three-state machine, two comparators and two accumulators.
Reset behaviour. Back to the command phase on hard reset, bus reset and the class reset — the last being the whole point of that request existing.
Assumptions. That out_len is the transfer length, so Chapter 12.2 §2's short-packet rule has already framed it; that data_dir_in reports the endpoint the data actually arrived on rather than the one that was expected; and that the SCSI command inside CBWCB is somebody else's problem.
Omissions. The bulk transfers, the SCSI layer, the medium, and the other class requests — in the header.
What DV should verify. That a malformed wrapper is never accepted — wrong length or wrong signature; that a zero-length command skips the data phase; that a data packet in the wrong direction always raises a phase error; that the status byte reflects the right category; that a phase error outranks a failure; and that the machine returns to the command phase after every CSW.
6. Mutation Test
Five mutations over 846 checks across 306 commands, including 31 phase errors, 3 malformed wrappers, a class reset mid-command, and 300 randomised commands mixing lengths, directions, failures and wrong-direction data.
The unmutated block: 0 violations of every obligation.
| malformed CBW accepted | wrong phase after CBW | zero-length did not skip | phase error unreported | CSW status wrong | did not rearm | |
|---|---|---|---|---|---|---|
| golden | 0 | 0 | 0 | 0 | 0 | 0 |
| B1 length not checked | 2 | 0 | 0 | 0 | 0 | 1 |
| B2 always a data phase | 0 | 0 | 73 | 0 | 0 | 0 |
| B3 direction not compared | 0 | 0 | 0 | 31 | 31 | 0 |
| B4 no phase-error status | 0 | 0 | 0 | 31 | 31 | 0 |
| B5 never returns to CBW | 0 | 231 | 0 | 31 | 31 | 306 |
B1 — accept a wrapper of any length
Measured: 2 malformed wrappers accepted, and one failure to return to the command phase.
§4's pair reduced to one condition. The signature survived; the length did not — so a transfer that happens to begin with USBC and is 30 or 64 bytes long is taken as a command.
Two escapes out of three malformed wrappers is the measurement, and it is the right one to quote: the signature caught one and missed two. §4's claim that both conditions are needed, made concrete.
B2 — always enter the data phase
Measured: 73 zero-length commands failed to skip the data phase.
TEST UNIT READY is the most frequently issued command in mass storage and carries no data. A device that waits for a data phase after it waits for a transfer the host will never send, and only a timeout or a reset releases it.
73 out of 306 commands — nearly a quarter, because zero-length commands are common rather than exceptional.
B3 — do not compare the data direction
Measured: all 31 phase errors undetected, and 31 wrong status bytes.
§3's detector removed. The device processes a data packet from the wrong endpoint as though it belonged to the current command — so the wrong bytes go to the medium, or the wrong bytes are returned, and the CSW reports Pass.
This is the mutation that loses data silently, and it does so while every individual transfer succeeds.
B4 — never report a phase error
Measured: identical to B3 — 31 unreported, 31 wrong status bytes.
And that identity is the finding.
B5 — never return to the command phase
Measured: 306 failures to rearm, 231 wrong phases, and every subsequent check corrupted.
The machine sends one CSW and stops. One command works and the device is dead, which is loud, immediate, and included as the contrast for B3.
7. Verification
This chapter's commit point is every command was the one the host sent, or the device said it could not be sure.
Stimulus. A clean read; a clean write; a zero-length command (TEST UNIT READY); a command that fails; a data packet on the wrong endpoint — the phase error; three malformed wrappers — wrong length, wrong signature, and both; a class reset mid-command; and 300 randomised commands with lengths of 0 to 1536 bytes, both directions, a 1-in-8 wrong-direction rate and a 1-in-6 failure rate.
Observation. The phase after every event, wrapper validity, phase_error and csw_status separately — §6's callout — and the phase after every CSW.
Reference model. None. Seven obligations, each a sentence from §§2–4, which is the Chapter 14.2 §8 pattern: a reference model for a three-state machine would be the same three states, and the obligations are what a specification actually says.
Coverage — crosses:
- data length: zero, one packet, several packets
- declared direction × actual direction — all four, two of which are phase errors
- wrapper length: 31, 30, 64 × signature correct and incorrect
- command outcome: pass, fail × phase error present and absent
- class reset in each of the three phases
Negative cases with defined outcomes: no malformed wrapper is accepted; no zero-length command enters a data phase; no wrong-direction packet fails to raise a phase error; the status byte never reports Fail where Phase Error belongs; and the machine always returns to the command phase.
8. Debugging: the Drive That Works Until It Does Not
A USB drive works normally, then stops responding. The operating system logs a reset of the device and it recovers, then fails again minutes later. Copying large files makes it more frequent; idling makes it stop. A trace shows normal CBW/data/CSW sequences and then, occasionally, a CSW with status
0x02.
What is status 0x02? §3: Phase Error — the device is telling you it cannot be sure the command it executed is the one you sent.
So the device is reporting correctly. The recovery the OS performs is exactly the specified one. The question is what caused the disagreement, and the CSW does not say — by construction, because if the device knew, it would not be a phase error.
What produces one? §3: a data packet on the endpoint the command did not name. Which means either the host sent one, or the device lost track of which command it was executing.
How do you tell those apart? Look at the transfer before the phase error. If the host's data direction matches the CBW's flags, the host is correct and the device lost its place. If it does not, the host's driver is at fault.
Why would load matter? Because losing track requires something to go wrong first — a lost transfer, a timeout, a halt — and those scale with traffic. Load does not cause it; load causes the precondition, which is the same shape as Chapter 14.2 §9's and Chapter 11.2 §9's.
What is the most likely device-side cause? §6's B5's milder cousin: a device that does not reliably return to the command phase, so it interprets the next CBW as data. The signature check then rejects it — §4 — and the device is out of step until the reset.
And why is this so hard to attribute? Because the phase error is reported by the party that detected the problem, not the one that caused it. A device reporting phase errors may be the only correct participant in the exchange — it is simply the one with a mechanism for saying so.
The signature to keep: a 0x02 CSW is a symptom whose cause is always earlier in the trace — and the useful evidence is the transfer before it, not the one that reported it.
9. Common Misconceptions
10. Reason It Through
A device designer proposes dropping the
dCBWSignaturecheck, reasoning that the CRC already guarantees the bytes are intact, so checking a constant that is always the same adds nothing.
Is the premise true? Yes. Chapter 11.6 guarantees the bytes arrived as sent — the signature is not protecting against corruption.
So what is it protecting against? §4: framing. The signature is not asking did these bytes arrive correctly; it is asking are these bytes a command at all.
When are those different? After anything that desynchronises the two ends — a phase error, a halt, a reset the device saw and the host did not. The device then has a byte stream and no idea where it is in the conversation, and every byte in it arrived with a perfect CRC.
So what happens without the signature? The device takes the next 31-byte transfer as a command. If it is actually the tail of a data phase, the device executes whatever those bytes happen to encode — a write to an arbitrary sector, plausibly.
Is the length check enough on its own? Better than nothing and much weaker. One value in 65 536 against one 32-bit pattern in four billion — and data phases are full of 31-byte transfers only if the transfer sizes happen to align, which for a storage device they frequently do.
What does the pair actually buy? Two independent conditions that a stream of arbitrary data must satisfy simultaneously. Neither is expensive and neither is sufficient, which is §4's whole claim — and §6's B1 is the measurement, at two escapes from three attempts.
And the transferable point: error detection and framing detection are different problems with different failure modes, and a CRC solves only the first. A protocol that recovers framing needs something a corrupted-but-valid stream cannot accidentally satisfy — which is why magic numbers survive in protocols that also have checksums, and why removing one because the CRC covers it is a category error rather than an optimisation.
11. Understanding Check
12. Summary
Mass Storage is a request/response protocol built on a channel with neither. Bulk supplies two unidirectional pipes, in-order intact delivery, and transfer boundaries; every notion of a command, a reply, or a message is invented above it.
The class defines three phases — a 31-byte CBW signed USBC, a data phase, and a 13-byte CSW signed USBS — and the CBW is recognisably Chapter 13.1's SETUP packet rebuilt: a fixed length, a direction bit, and a declared data length.
When a transport lacks request/response semantics, the protocol above it reinvents them — and the reinvention looks like whatever the transport below already got right.
The phase error is the class's own contribution, and it is Chapter 12.4 §2's two beliefs two layers up: both ends track a phase, neither transmits it, and the only evidence of disagreement is a packet on the wrong endpoint. bCSWStatus = 0x02 is not a worse Fail — Fail means the command failed and can be investigated; Phase Error means nobody knows what happened, which is why the recovery is a reset.
§6 measured five mutations over 306 commands, and two of them are the chapter's findings:
B1 — dropping the length check and keeping the signature let two of three malformed wrappers through, which is §4's claim measured: a fixed length and a magic number are how a stream protocol recovers framing without a framing layer, and neither alone is sufficient.
B3 and B4 produced identical measurements from unrelated defects — a missing detector and a missing reporter.
Detection and reporting are separate mechanisms, and a single output cannot distinguish a failure in one from a failure in the other.
The bench's answer costs nothing — observe the intermediate signal alongside the externally-visible one — and is omitted whenever a bench treats the byte on the wire as the whole story.
13. What Comes Next
Mass Storage is one protocol on Bulk, and the most structured one. Chapter 14.5 is the other end of the range — printer class, vendor class, and the streaming patterns that have no wrappers, no commands, and no status at all.
Their problem is the opposite of Mass Storage's. Mass Storage had to build framing; a streaming protocol has to avoid needing it — and the mechanism that makes streaming fast is one this module has already priced twice: queue depth, because a device that only has one transfer outstanding spends its life waiting for the host to notice it finished.
Browse the full path on the USB tutorials index.
Continue learning
Related tutorials
- Related topic
USB Flash Drives
Every flash drive speaks Bulk-Only Transport — CBW out, data, CSW in. The spec enumerates thirteen cases of host-versus-device disagreement, six of them fatal, and the two rarest are the ones that ship broken.
- Related topic
Four Transfer Types Overview
The four transfer types are not a list to memorise — they fall out of two orthogonal questions plus one bootstrap question. Deriving them, and the policy block whose outputs are all derived and none stored.
- Related topic
Bulk Transfers
Promised nothing and permitted everything: why throughput and guarantee are different axes, and why a fixed-priority arbiter starves an endpoint forever while every safety property passes.
- Related topic
Bulk Throughput
Why 480 Mbit/s never means 60 MB/s — overhead is per transaction and fixed, so efficiency is a function of packet size alone, and a zero-length terminator costs a full transaction for no bytes.
Standards & specifications
- Governing standard
- USB-IF (Universal Serial Bus Specification)(opens USB Implementers Forum (USB-IF) in a new tab)
Defines the USB bus — its electrical signalling, connectors, packet and transaction model, device framework and the descriptors a device must expose — together with the device-class specifications layered on it. It does not define host-controller register interfaces (xHCI and EHCI are separate documents) nor any operating system's driver architecture.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the USB curriculum.
