USB · Module 7
BOS Descriptor
The descriptor built for capabilities that did not exist yet: how length-then-type turns forward compatibility into a parsing rule, and why a forgiving parser and a forgiving device engine are opposite requirements.
Chapter 7.5 finished the descriptors a device has always had. This one is different in origin: the BOS descriptor exists because the original format ran out of room.
Not room in the sense of bytes. Room in the sense of places to put things that had not been invented yet.
The Binary Object Store is USB's answer to a problem every long-lived format eventually faces: how does a device describe a capability that did not exist when the format was designed, to a host that may be older than the capability?
And the answer turns out to rest entirely on a convention introduced in Chapter 7.1 §4 and treated there as a parsing convenience.
1. Why a Second Tree
The device descriptor of Chapter 7.1 is 18 bytes, and always has been. Every field is allocated. There is no reserved space, no extension field, and no way to add one — because a host reads 18 bytes and a device that sent 19 would be malformed to every host ever built.
That rigidity is a feature and a trap. It is a feature because a fixed layout is trivially parseable and unambiguous. It is a trap because USB kept acquiring capabilities: link power management, SuperSpeed operation parameters, platform-specific identifiers, precision timing. None of them had anywhere to live.
Consider the options.
Extend the device descriptor. Impossible for the reason above — its length is baked into every host that exists.
Define a new fixed descriptor per capability. This scales badly: every new capability needs a new type code, every host needs to know about it, and a host that does not simply fails to ask.
Define one extensible container. A single new descriptor type that holds a list of capability records, each self-describing, each skippable. A host asks for the container once and walks whatever it finds.
USB takes the third, and calls it the BOS. It is type 15, its header is 5 bytes, and it is requested independently — not nested inside a configuration and not part of the device descriptor.
2. The Header, and a Familiar Field
The BOS header is 5 bytes, and it is structurally Chapter 7.2's configuration descriptor with the names changed.
| Field | Size | Purpose |
|---|---|---|
bLength | 1 | 5 — this header only |
bDescriptorType | 1 | 15 (0x0F) |
wTotalLength | 2 | the whole tree: header plus every capability |
bNumDeviceCaps | 1 | how many capability descriptors follow |
That is a size and a count, which Chapter 7.2 §8 measured to be complementary and independently necessary — a boundary-aligned truncation preserves the size arithmetic and is caught only by the count. The same two checks are needed here, for the same reason, and §6 implements them.
And the same bootstrap applies. A host cannot know the tree's size before reading it, so it performs Chapter 6.4's short-prefix read: fetch 5 bytes, learn wTotalLength, re-request the whole thing. Fourth occurrence of the pattern, and by now it should be predictable rather than surprising.
One difference from the configuration tree matters. A configuration's subordinate descriptors are interfaces and endpoints, whose types the host must understand to use the device at all. A BOS's subordinates are capabilities the host is explicitly permitted not to understand — and §3 is about what that permission costs.
3. Capability Descriptors and the Skip Rule
Every subordinate in the BOS tree is a device capability descriptor, type 16 (0x10), with the same opening:
bLength how far to the next one
bDescriptorType 16 — "this is a capability"
bDevCapabilityType WHICH capability ← the discriminator
... capability-specific payload ...Three bytes of header rather than two, and the third is what makes the container work: bDevCapabilityType says which capability this is, and everything after it has a layout that depends on that value.
The host's algorithm is three lines and never changes:
while (offset < wTotalLength):
read bLength and bDevCapabilityType at offset
if this capability type is one I understand: parse the payload
else: ignore it
offset += bLengthNotice what the host does not do in the unrecognised case: it does not fail, warn, reject the device, or stop. It steps over the record and carries on — and it can, because bLength told it how far without telling it anything about the content.
4. The Example Device's BOS
Two capabilities, so the skip rule has something to skip.
offset bytes descriptor
──────────────────────────────────────────────────────────────────────────
0.. 4 05 0F 16 00 02 BOS header
│ │ └──┬──┘ └─ bNumDeviceCaps = 2
│ │ └────── wTotalLength = 0x0016 = 22 ← LE: 16 00
│ └──────────── bDescriptorType = 15 (BOS)
└─────────────── bLength = 5 (the HEADER only)
5..11 07 10 02 02 00 00 00 USB 2.0 EXTENSION
│ │ │ └────┬────┘ └─ bmAttributes = 0x00000002 (LE, 4 bytes)
│ │ │ │ bit 1 set → LPM supported
│ │ └───────┴───────── bDevCapabilityType = 2
│ └──────────────────── bDescriptorType = 16 (DEVICE CAPABILITY)
└─────────────────────── bLength = 7
12..21 0A 10 03 00 0E 00 01 0A FF 07 SUPERSPEED USB
│ │ │ │ └──┬─┘ │ │ └──┬─┘
│ │ │ │ │ │ │ └─ wU2DevExitLat = 0x07FF ← LE: FF 07
│ │ │ │ │ │ └─────── bU1DevExitLat = 10
│ │ │ │ │ └────────── bFunctionalitySupport = 1
│ │ │ │ └────────────── wSpeedsSupported = 0x000E ← LE: 0E 00
│ │ │ │ bits 1,2,3 → full, high, SuperSpeed
│ │ │ └──────────────────── bmAttributes = 0
│ │ └─────────────────────── bDevCapabilityType = 3
│ └────────────────────────── bDescriptorType = 16
└───────────────────────────── bLength = 10
──────────────────────────────────────────────────────────────────────────
5 + 7 + 10 = 22 = wTotalLength ✓Check the arithmetic, as Chapter 7.2 §5 asked: 5 + 7 + 10 = 22, and wTotalLength is 16 00 = 0x0016 = 22. ✓
Note wSpeedsSupported. It is a bitmask, not a number: bit 0 is low speed, bit 1 full, bit 2 high, bit 3 SuperSpeed. 0x000E sets bits 1, 2 and 3, so this device claims full, high and SuperSpeed and not low speed — a claim the hardware must actually be able to keep, exactly as Chapter 7.4's endpoint fields must.
And note the two 16-bit fields — wSpeedsSupported and wU2DevExitLat — plus one 32-bit field, bmAttributes in the USB 2.0 extension. All little-endian, and the 32-bit one is the module's only four-byte field: 02 00 00 00 is 0x00000002, bit 1 set.
5. A Second Tree Means a Second Chance to Drift
The BOS repeats the configuration tree's structure, which means it repeats its failure modes — and adds one.
The repeats. wTotalLength too small silently truncates the capability list, and a host loses capabilities the device has. Too large makes the host read past the tree. A wrong bNumDeviceCaps disagrees with the tree's contents. All three are Chapter 7.2 §3, verbatim.
The new one is more interesting: a capability descriptor whose bLength does not match its type.
Every capability type has a defined size — the USB 2.0 extension is 7 bytes, the SuperSpeed capability is 10. A record declaring type 3 with a bLength of 7 is internally contradictory: the type says ten bytes of SuperSpeed parameters follow, the length says step forward seven.
What does the host do? It trusts bLength, because that is the skip rule. It parses seven bytes as if they were a ten-byte record — reading three fields that are partly the next descriptor's bytes — and then steps forward seven, landing three bytes into a record it now misinterprets.
The tree desynchronises from that point on, and everything after the bad record is garbage. wTotalLength may still be correct. The total may still add up. The arithmetic checks all pass, because the walk steps by bLength and bLength is what is wrong.
This is a fourth structural invariant, joining Chapter 7.2's length and count and Chapter 7.3's association: a descriptor's declared length must match what its type requires. §6 checks it, and §7 measures what happens without it.
6. The Skip Rule, as a Model
§3 gave the host's algorithm in three lines and §5 claimed that a type-mismatched bLength desynchronises it. Both are worth building rather than asserting, because the desynchronisation is the part people find surprising.
// ─────────────────────────────────────────────────────────────────────────
// bos_walker
//
// Classification: VERIFICATION MODEL. Not synthesizable and not part of any
// device. It models what a HOST does with a BOS tree, so that a device's
// descriptor image can be checked against the behaviour it will actually
// produce rather than only against its own internal arithmetic.
//
// WHAT IT MODELS. Section 3's skip rule exactly: read a length and a
// capability type, parse the payload if the type is recognised, step over it
// if not, and repeat until the declared total is consumed. It deliberately
// trusts bLength for stepping, because that is what a real host does and
// what makes section 5's failure possible.
//
// WHAT IT DOES NOT MODEL. Any capability's payload semantics, control
// transfers (Module 13), the device side of anything, or error recovery --
// a real host stack has policy about malformed data that is out of scope
// here and varies between implementations.
//
// WHY A MODEL RATHER THAN A CHECK. Section 6's static checks ask whether the
// data is self-consistent. This asks a different question: what does a host
// SEE. Those diverge exactly when a host is misled by data that is
// internally consistent, which is section 5's whole subject.
// ─────────────────────────────────────────────────────────────────────────
module bos_walker
import usb_desc_pkg::*, usb_lookup_pkg::*, usb_bos_pkg::*;
#(
// Which capability types this "host" understands. A host built before a
// capability existed has it absent -- which is the situation section 9 is
// about, and the reason this is a parameter rather than a constant.
parameter bit KNOWS_USB2_EXT = 1,
parameter bit KNOWS_SUPERSPEED = 1
)(
input logic [7:0] rom [0:usb_desc_pkg::DESC_ROM_BYTES-1],
output int unsigned caps_parsed, // recognised and understood
output int unsigned caps_skipped, // walked over without understanding
output bit desynced // the walk lost the record boundary
);
initial begin : walk
int unsigned total, walk_off, blen;
logic [7:0] dtype, ctype;
caps_parsed = 0;
caps_skipped = 0;
desynced = 1'b0;
total = {rom[OFF_BOS + 3], rom[OFF_BOS + 2]};
walk_off = rom[OFF_BOS];
while (walk_off < total && !desynced) begin
blen = rom[OFF_BOS + walk_off];
dtype = rom[OFF_BOS + walk_off + 1];
ctype = rom[OFF_BOS + walk_off + 2];
// THE DESYNC DETECTOR. A real host has no way to know it has lost the
// boundary; it simply misparses. This model reports it, because the
// point is to make the invisible visible -- every subordinate of a BOS
// must be a device capability descriptor, so anything else means the
// walk is no longer landing on record boundaries.
if (dtype != DT_DEV_CAP) begin
desynced = 1'b1;
$display(" walker: DESYNCED at BOS offset %0d -- found descriptor "
, walk_off);
$display(" type 0x%02h where a capability (0x10) was due",
dtype);
end
else if (blen < 3) begin
// A zero or tiny length cannot be stepped over; a real host would
// loop or stall here.
desynced = 1'b1;
$display(" walker: DESYNCED at BOS offset %0d -- bLength %0d",
walk_off, blen);
end
else begin
if ((ctype == CAP_USB2_EXT && KNOWS_USB2_EXT) ||
(ctype == CAP_SUPERSPEED && KNOWS_SUPERSPEED)) begin
caps_parsed++;
$display(" walker: parsed capability type 0x%02h (%0d bytes)",
ctype, blen);
end else begin
// Section 3's skip. No error, no warning, no loss except a
// feature this host could not have used.
caps_skipped++;
$display(" walker: skipped capability type 0x%02h (%0d bytes) "
, ctype, blen);
$display(" -- not recognised by this host");
end
walk_off += blen; // trusts bLength, exactly as a host does
end
end
end
endmoduleClassification. Verification model — it models a host, not a device.
What it models. §3's skip rule, plus a desync detector a real host does not have.
Why it exists alongside §7's static checks. They answer different questions. The static checks ask is this data self-consistent; the walker asks what will a host see. Those diverge exactly when a host is misled by data that is internally consistent — which is §5's subject and §8's B-M1.
Inputs. The ROM image, and parameters saying which capability types this host understands.
Outputs. How many capabilities were parsed, how many were skipped, and whether the walk lost its boundary.
Deliberately omits. Payload semantics, transfers, the device side, and error-recovery policy — which varies between real host stacks and is not something a model should invent.
The KNOWS_* parameters are the point of the model being parameterised. A host built before a capability existed simply has it absent, and §9's scenario is exactly a device meeting two hosts that differ in what they know.
7. Checking the BOS Tree
// ─────────────────────────────────────────────────────────────────────────
// Classification: STATIC DATA-INTEGRITY CHECKS (elaboration-time).
//
// The BOS tree repeats the configuration tree's structure, so it repeats
// Chapter 7.2's length and count checks. It adds one the configuration tree
// does not need: each capability type has a DEFINED SIZE, so bLength and
// bDevCapabilityType must agree (section 5).
//
// SCOPE. Needs `rom` visible and usb_lookup_pkg imported for OFF_BOS.
// ─────────────────────────────────────────────────────────────────────────
package usb_bos_pkg;
// Device capability type codes. Only those this chapter uses.
localparam logic [7:0] CAP_WIRELESS_USB = 8'h01;
localparam logic [7:0] CAP_USB2_EXT = 8'h02;
localparam logic [7:0] CAP_SUPERSPEED = 8'h03;
localparam logic [7:0] CAP_CONTAINER_ID = 8'h04;
localparam logic [7:0] CAP_PLATFORM = 8'h05;
// The size each capability type requires. Zero means "variable or not
// known to this checker" -- the PLATFORM capability genuinely varies, so
// a fixed expectation would be wrong rather than merely unhelpful.
function automatic int unsigned cap_expected_len(input logic [7:0] cap_type);
case (cap_type)
CAP_WIRELESS_USB: return 11;
CAP_USB2_EXT: return 7;
CAP_SUPERSPEED: return 10;
CAP_CONTAINER_ID: return 20;
default: return 0; // variable, or unknown to this checker
endcase
endfunction
endpackage
initial begin : bos_checks
int unsigned total, walk, n_caps;
logic [7:0] cap_type;
int unsigned expect_len;
total = {rom[OFF_BOS + 3], rom[OFF_BOS + 2]};
// B1 -- the header describes itself, not the tree.
assert (rom[OFF_BOS] == 8'd5)
else $fatal(1, "BOS bLength is %0d, expected 5", rom[OFF_BOS]);
assert (rom[OFF_BOS + 1] == DT_BOS)
else $fatal(1, "BOS bDescriptorType is 0x%02h, expected 0x0F",
rom[OFF_BOS + 1]);
// B2 -- LENGTH. Walk the tree and land exactly on the declared total.
// Chapter 7.2's T2, applied to the second tree.
walk = rom[OFF_BOS];
n_caps = 0;
while (walk < total) begin
int unsigned blen;
blen = rom[OFF_BOS + walk];
cap_type = rom[OFF_BOS + walk + 2];
assert (blen >= 3)
else $fatal(1, {"capability at BOS offset %0d has bLength %0d -- a ",
"capability header is 3 bytes"}, walk, blen);
// B3 -- every subordinate must be a DEVICE CAPABILITY descriptor. The
// BOS tree contains nothing else, so anything else means the walk has
// desynchronised -- which is exactly what B4 exists to prevent.
assert (rom[OFF_BOS + walk + 1] == DT_DEV_CAP)
else $fatal(1, {"BOS offset %0d: expected a device capability ",
"descriptor (type 0x10), found type 0x%02h"},
walk, rom[OFF_BOS + walk + 1]);
// B4 -- THE ONE THIS TREE ADDS. A capability's declared length must
// match what its type requires. A mismatch desynchronises the host's
// walk from this point on, and every arithmetic check still passes
// because the walk steps by the value that is wrong (section 5).
expect_len = cap_expected_len(cap_type);
if (expect_len != 0)
assert (blen == expect_len)
else $fatal(1, {"capability type 0x%02h declares bLength %0d but ",
"that type requires %0d bytes"},
cap_type, blen, expect_len);
n_caps++;
walk += blen;
end
assert (walk == total)
else $fatal(1, {"BOS walk ended at %0d but wTotalLength declares %0d ",
"-- %s"}, walk, total,
walk > total ? "a capability overruns the declared end"
: "declared total exceeds the real tree");
// B5 -- COUNT. Chapter 7.2's T3, and necessary for the same reason: a
// truncation landing on a capability boundary passes B2 entirely.
assert (n_caps == rom[OFF_BOS + 4])
else $fatal(1, {"bNumDeviceCaps says %0d but the tree contains %0d ",
"capability descriptors"}, rom[OFF_BOS + 4], n_caps);
$display("BOS tree OK: %0d bytes, %0d capabilities", total, n_caps);
endB4 is what this chapter contributes to the module's checking vocabulary. The other three checks are Chapter 7.2's, transplanted. B4 exists because the BOS is the only tree whose subordinates have type-determined sizes, which creates a way for a descriptor to be self-inconsistent that the configuration tree cannot express.
And cap_expected_len returning zero is deliberate. Some capability types genuinely have variable size — the platform capability carries a payload whose length depends on its content. A checker that demanded a fixed size for those would fail correct data, so it declines to check what it cannot know. A check that is wrong for legitimate inputs is worse than no check, which is the same judgment Chapter 7.5 §6 made about encoding.
8. Mutation Test
Three mutations of the BOS data. All were run; the results are measured.
B-M1 — a capability whose length contradicts its type
Declare the SuperSpeed capability with a bLength of 7 instead of 10, and shrink wTotalLength to 19 so the arithmetic still balances.
Result, measured. B4 fires:
FATAL: capability type 0x03 declares bLength 7 but that type requires 10 bytesAnd to confirm the other checks really are blind to it, the same corrupted image was run with B4 removed:
BOS tree OK: 19 bytes, 2 capabilitiesThe walk lands exactly on 19 and finds exactly 2 capabilities. B2 and B5 both pass; only B4 sees anything wrong.
Why this is the chapter's most instructive mutation. It is the fourth demonstration in this module of the same principle. A corruption that preserves every invariant you happened to check survives — and the one that catches it is the invariant you had to add because this particular structure permits a contradiction the others do not.
B-M2 — truncate the tree on a capability boundary
Set wTotalLength to 12 rather than 22, so the tree ends exactly after the USB 2.0 extension.
Result, measured. B2 passes — 5 + 7 = 12 lands exactly on a capability boundary. B5 fires:
FATAL: bNumDeviceCaps says 2 but the tree contains 1 capability descriptorsIdentical in shape to Chapter 7.2 §8's measurement, in a different tree: the boundary-aligned truncation is invisible to the length check and caught only by the count. That it reproduces exactly is the point — this is a property of declaring both a size and a count, not a quirk of one descriptor.
B-M3 — an unknown capability type
Not a corruption at all: change the SuperSpeed capability's type code to 0x7F, a value no current specification defines, leaving its length at 10.
Result, measured. Every check passes:
BOS tree OK: 22 bytes, 2 capabilitiesB4 does not fire, because cap_expected_len returns zero for an unrecognised type and the check is skipped deliberately.
And that is correct behaviour, not a gap. The record is well-formed: right descriptor type, plausible length, walkable. A checker that rejected it would reject a device carrying a capability newer than the checker — which is precisely the situation the BOS exists to support. The checker declines to have an opinion about content it cannot know, exactly as §3 says a host should.
9. Verification
Stimulus. The BOS at its 5-byte prefix length and at its full length — Chapter 6.4's two-read pattern; a request with a nonzero index, which must be refused; trees containing zero, one and several capabilities; a capability with a type-mismatched length; a boundary-aligned truncation; and an unknown capability type, which must pass.
The stimulus requirement this chapter adds: include an unknown capability type deliberately. It is the one case that distinguishes a checker that tolerates the future from one that merely tolerates what it was written against — and a test suite built only from capabilities the checker knows will never produce it.
Observation. The walk's landing point, the capability count, and each capability's declared length against its type. §7 measured all three being separately necessary.
Reference model. A list of (type, length, payload) records from which the expected byte stream is generated. Generating rather than transcribing is what makes B-M1 unrepresentable, per §7's closing callout.
Representative coverage — crosses:
wTotalLengthcorrect × too small on a boundary × too small mid-record × too largebNumDeviceCapscorrect × too high × too low- capability type known × unknown, crossed with correct and incorrect
bLength - requested length: 5-byte prefix, exact total, greater than total — Chapter 7.4's limiter cases
- BOS present × absent, since a USB 2.0-only device need not have one
Negative cases with defined outcomes: a capability whose length contradicts a known type must fail the build; a truncation on a capability boundary must be caught by the count; an unknown capability type with a plausible length must pass; and a BOS request with a nonzero index must be refused, per Chapter 7.2 §6's lookup.
10. Reason It Through
A device carries a capability whose type is newer than the checker that validates it. Its
bLengthis wrong — 7 where the type requires 10. What happens?
Two versions of this were run, and they behave completely differently.
Version A — wTotalLength left correct at 22
The capability under-declares its length, and nothing else changes.
static checks FATAL: BOS offset 19: expected a device capability descriptor
(type 0x10), found type 0xFF
host walker parsed capability type 0x02 (7 bytes)
skipped capability type 0x7F (7 bytes) -- not recognised
DESYNCED at BOS offset 19 -- found descriptor type 0xFF
where a capability (0x10) was dueBoth hosts desync, the modern one and the older one alike. That is worth stating plainly, because the intuition that a host which recognises a type is protected turns out to be wrong here: the skip rule says step by bLength, and a host that follows it steps by the wrong amount whether or not it understood the record. The walk lands three bytes into the previous capability and reads its tail as a new descriptor header.
And §7's B3 catches it — the check that every subordinate must be a device capability descriptor. It is the structural check that notices a walk has stopped landing on boundaries.
Version B — wTotalLength reduced to 19 to match
Now the arithmetic balances: 5 + 7 + 7 = 19.
static checks BOS tree OK: 19 bytes, 2 capabilities
host walker parsed capability type 0x02 (7 bytes)
skipped capability type 0x7F (7 bytes) -- not recognised
modern host: parsed=1 skipped=1 desynced=0
older host: parsed=1 skipped=1 desynced=0Nothing detects anything. Every static check passes. Both hosts walk cleanly to the end, report two capabilities, and desync nowhere. And three bytes of the device's SuperSpeed parameters are never examined by anybody — not by the checker, not by either host.
Why is B4 silent? Because the type is 0x7F, which cap_expected_len does not recognise, so the length check is deliberately skipped — §8's B-M3, exactly as designed.
Which version is worse? Version B, without question. Version A fails loudly at build time and would fail visibly in a lab. Version B produces a device that is internally consistent, passes every check, walks cleanly on every host, and silently under-reports its own capabilities. No symptom exists anywhere.
And what does that leave? The limit §8's callout already named: an unknown type's correct size is not in the data, so no check on the data can recover it. The only defence is that bLength was computed from a description that knew the size, rather than typed by a person — which is why this module keeps returning to generation over checking, and why this is the case where the distinction stops being stylistic and becomes the only thing that works.
11. Common Misconceptions
12. Understanding Check
13. Summary
The BOS exists because the device descriptor is 18 bytes and always has been, with no room for capabilities invented after it. Rather than extend a fixed descriptor or define a new type per capability, USB defines one extensible container: type 15, a 5-byte header, requested independently.
It is a descriptor about there being capabilities, and that indirection means a new capability requires no format change and no host update to parse. Which retroactively reveals what Chapter 7.1 §4's length-then-type convention was really for: not parsing convenience, but letting the format outlive its designers.
Its header repeats Chapter 7.2's shape — a wTotalLength and a bNumDeviceCaps, a size and a count — and therefore repeats its failure modes and needs the same two complementary checks. §7 reproduced the boundary-aligned truncation exactly: invisible to the length walk, caught only by the count.
Each subordinate is a device capability descriptor, type 16, whose third header byte is a type discriminator. The host's algorithm never changes: read the length, parse if recognised, skip if not, advance. And that produces the chapter's reconciliation — a forgiving parser and an unforgiving device engine are not in tension. The host skips what it was never promised it would understand; the device refuses a request it has no answer to. Robustness is doing the specified thing, which is sometimes continue and sometimes stop.
The BOS adds the module's fourth structural invariant: a capability's bLength must match what its type requires. §7 measured a mutation whose arithmetic balanced perfectly and which was caught by nothing else.
And it closes with the limit of checking. An unknown capability type with a wrong length passes everything, correctly — the checker declines to have an opinion about a size it cannot know, because refusing to would reject devices newer than itself. That hole cannot be closed from inside the data. Checks catch mistakes; generation prevents them — and the cases a check cannot reach are exactly where that distinction stops being a matter of taste.
14. Where This Leaves You
Module 7 is complete. Every descriptor a device exposes has been opened, and each was taught the same way: what host question it answers, what implementation must agree with it, what breaks when it is wrong, and how to find that out before silicon.
What the module actually built is a way of thinking about descriptors that survives contact with real bugs:
- Three levels of correctness — byte, structural, semantic — that are independent, with the third unreachable from descriptor data alone. Two mutations proved byte-perfect, structurally flawless descriptors can describe hardware that does not exist.
- Four structural invariants — length, count, association, type-versus-length — each added because a corruption survived all the previous ones.
- A serving path — store, select, limit, stream — built one block at a time, with every off-by-one, aliasing and vacuity failure measured rather than predicted.
- One architectural principle: descriptor bugs are drift, drift comes from independent copies, and generation prevents what checking can only catch.
What comes next changes the question. Descriptors say what a device is. Module 8, USB Device States, is about what a device is allowed to do, and when — the six canonical states and the transitions enumeration drives through them. Chapter 6.2 and 6.5 used a working subset informally; Module 8 makes it complete and formal.
The connection is direct: a descriptor is a claim about capability, and a device state is a constraint on when that capability may be exercised. A configured device and an addressed one have identical descriptors and are permitted entirely different things.
Browse the full path on the USB tutorials index.
Continue learning
Related tutorials
- Related topic
Configuration Descriptor
Not an object but the header of a tree: why the whole configuration arrives in one transfer, why wTotalLength is the highest-value bug source in USB, and the lookup RTL where an invalid selector must not quietly become a valid one.
- Related topic
Descriptor Discovery
Why the host's first descriptor read is deliberately incomplete, why that partial read is a recurring pattern rather than a workaround, why the walk order is forced, and the gated sequencer RTL that intuition gets wrong.
- Related topic
Device Descriptor
The root of the description tree: what the host infers from each field, why bMaxPacketSize0 carries a bootstrap, why little-endian is three representations that must agree, and the descriptor ROM whose byte-swap bug passes every structural check.
- Related topic
Interface Descriptor
Where a device becomes a set of functions: alternate settings, why endpoint association is positional and load-bearing, and the byte-streamer RTL where one off-by-one decides whether the last descriptor survives.
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.
