Hardware Engineering · Storage Hardware
Protection Information: Eight Bytes That Catch Misdirected Writes
How T10 protection information appends a CRC, a reference tag, and an application tag to every logical block, and what it costs to check them at the drive and at the host.
A drive’s error-correcting code protects the medium, as the correction path in a flash controller describes. It says nothing about the path the data took to reach the medium, the controller’s internal buffers, the cable, the host’s memory, or the address the host asked for. A block can arrive at the wrong logical address, be written correctly, read back correctly, and be returned to the host as valid data belonging to a different file. The medium’s ECC cannot detect that, because nothing about the block is damaged — it is simply in the wrong place. Protection information exists to make that class of error detectable end to end.
The gap between medium integrity and data integrity
Consider the write path. The host composes a block, the operating system passes it to a driver, the driver programs a DMA engine, the controller receives it, buffers it, appends its own ECC, and programs the array. At each step there is a buffer, an address, and a length, and at each step a fault can corrupt any of the three. A DMA engine that transfers to the wrong offset produces a block whose contents are valid data from elsewhere; the ECC computed over it is consistent with what was written, so every later check passes.
The same applies on the read path. A controller that fetches the wrong page returns a block whose ECC is valid. A RAID controller that reconstructs a stripe from the wrong parity returns plausible bytes. A filesystem that reads the wrong extent gets data that checksums correctly if the checksum was computed at the wrong place too.
The defence is to carry an identifier with the data and to verify it at each boundary. The identifier must be cheap enough to compute at line rate and strong enough that a random corruption does not accidentally satisfy it.
Eight bytes per block
T10 protection information defines an eight-byte trailer for each logical block: a two-byte CRC over the block’s data, a two-byte application tag, and a four-byte reference tag. The trailer is written and read with the block, so a protected block is 520 bytes rather than 512, or 4104 rather than 4096.
The guard is a CRC-16 computed over the data with the polynomial the standard specifies, conventionally written as 0x8BB7. Sixteen bits is a deliberate choice: it detects all single-bit and double-bit errors, all burst errors up to sixteen bits, and a random corruption passes with probability about one in sixty-five thousand. That is weak compared with the CRC-32 a filesystem uses over a whole extent, but the protection is per block and is checked on every access, so the residual risk is the probability that a corruption both survives the medium’s ECC and lands on a value with a matching CRC.
The reference tag is the lower 32 bits of the logical block address the block is supposed to occupy. This is the field that catches misdirected writes: a block written to the wrong LBA carries a reference tag that does not match its new address, and any read that checks the tag against the address it fetched will reject it. The tag is only 32 bits, so it wraps on a large device, but a wrap requires the misdirection to be exactly 2^32 blocks — sixteen terabytes at 4 KiB — which is a far less likely fault than an off-by-one in a buffer offset.
The application tag is opaque to the drive: the host chooses its meaning, and it might carry a generation number that changes when a volume is reformatted, a mirror-side identifier in a redundant pair, or a virtual disk number. The mask that qualifies it is not part of the trailer. It travels with the command, next to the expected tag, and it selects which bits of the application tag the device must compare, so a host can use part of the field for information the device should ignore and part for information it must enforce.
Three protection types
The standard defines three types, differing in which fields are checked.
Type 1 checks the guard, the reference tag, and the application tag. It is the strictest and is used where the host’s tag carries meaning the drive must enforce.
Type 2 checks the guard and the reference tag but ignores the application tag. It is the common choice for filesystems that want misdirection detection without coordinating tag semantics with the storage layer.
Type 3 checks only the guard. It detects corruption of the block’s contents but not misdirection, which makes it equivalent to a per-block checksum and is the least useful of the three for the failure mode that motivates the feature.
The type is a property of the formatted device, set when the drive is formatted with protection information. A drive formatted for type 2 cannot accept type 1 semantics without reformatting, and a drive formatted without protection information cannot be switched on the fly. That formatting decision is why protection information is usually deployed with a partition scheme and filesystem that both understand it, rather than retrofitted to an existing volume.
Checking at the drive and at the host
A drive that supports protection information checks the trailer on every write and every read. On a write, it recomputes the guard over the data and compares; a mismatch fails the command rather than writing a block whose trailer does not match its contents. It also compares the reference tag against the LBA in the command, which is the check that catches a host-side addressing error. On a read, it verifies the guard and the reference tag before returning the data, and returns an unrecovered error if either fails.
The host can also check, and the two checks catch different faults. A host-side check catches corruption between the drive and the host’s memory — a bad cable, a DMA fault, a driver bug — that the drive’s check cannot see because it happened after the drive released the data. A drive-side check catches corruption inside the drive and misdirected writes that the host’s check would only notice if the host compared the reference tag to the address it requested, which is exactly what the tag is for.
The cost of checking twice is that the CRC must be recomputed at the host, which consumes CPU unless the adapter offloads it. Storage adapters that support protection information do the check in hardware and report a separate error for a guard failure, a reference tag failure, and an application tag failure, which matters for diagnosis: a guard failure means the bytes changed, a reference tag failure means the block went to the wrong place.
/* The eight-byte trailer and the two checks a device performs on write. */
#include <stdint.h>
#include <stddef.h>
struct pi {
uint16_t guard; /* CRC-16 over the data */
uint16_t app_tag; /* host-defined, opaque to the device */
uint32_t ref_tag; /* low 32 bits of the LBA */
} __attribute__((packed));
/* crc16_t10dif() computes the guard; it is supplied by the platform. */
uint16_t crc16_t10dif(uint16_t seed, const void *buf, size_t len);
/* app_mask and app_expected arrive in the command, not in the trailer. */
int pi_check_write(const struct pi *pi, const void *data, size_t len,
uint64_t lba, uint16_t app_expected, uint16_t app_mask,
int type)
{
if (crc16_t10dif(0, data, len) != pi->guard)
return -1; /* data changed in flight */
if (pi->ref_tag != (uint32_t)lba)
return -2; /* block is at the wrong address */
if (type == 1 && (pi->app_tag & app_mask) != (app_expected & app_mask))
return -3; /* wrong volume or generation */
return 0;
}
The function is small because the check is meant to be: a CRC over the block and two comparisons. The engineering difficulty is not the arithmetic but placing the check where it runs at the interface rate without adding latency to every command.
The cost of moving a protected block
The reference tag ties a block to an address, and that creates a problem the moment data moves. A RAID controller that reconstructs a missing block for a new location must produce a trailer whose reference tag matches the new address, which means recomputing the CRC over the data. A replication path that copies a block to a different LBA must do the same. A garbage collector inside a drive that relocates a page — the copying that the mapping layer performs during collection — is moving data between physical pages, not logical addresses, so the reference tag is unchanged and the guard is unchanged; but a drive that performs deduplication or compression must recompute both.
Recomputation is not free. A CRC-16 over 4 KiB at line rate requires either a dedicated engine or a table-driven loop that consumes a meaningful fraction of a core. Storage controllers that support protection information therefore include a CRC engine in the data path, and the engine’s throughput sets the maximum rate at which protected data can be relocated. This is one reason protection information is common in enterprise arrays, where the controller already has hardware for parity and checksums, and rare in consumer devices, where the extra engine and the 1.5 percent capacity overhead at 512-byte blocks are hard to justify.
The capacity arithmetic is worth stating plainly. Eight bytes on 512 is 1.56 percent; eight bytes on 4096 is 0.195 percent. The same protection therefore costs eight times more on a 512-byte-block device than on a 4 KiB one, which is one of the quieter arguments for the larger block size.
What it does not protect
Protection information is not a substitute for a filesystem checksum, and the difference is instructive. It covers one block, so a corruption that spans several blocks — a stray write over a range, a controller that returns the wrong extent — may produce blocks that individually pass their guard checks if each block’s trailer was rewritten consistently with its data. It does not protect metadata that lives outside the protected region unless the metadata is itself written as protected blocks. It does not detect a stale read: a block that was correctly written, correctly tagged, and later superseded will pass every check while holding data the host no longer wants.
That last case is the reason filesystems layer their own checksums over the block-level protection, and the reason the two mechanisms are complementary rather than redundant. The protection information catches faults in the transport and in the drive’s addressing; the filesystem’s checksum catches faults in the filesystem’s own logic, including a block that is intact but belongs to an older version of the file. Both are needed because they fail in different places, and neither is expensive enough to justify skipping the other.
Where the block-level check does pay for itself is in the paths a filesystem cannot see: the cable between the host and the drive, the drive’s internal buffers, and the firmware’s own address computations. Those are the layers where a block can be perfectly valid and perfectly wrong, and eight bytes per block is a small price for making that state detectable rather than silent.
References
- SCSI Primary Commands (SPC-4) — INCITS T10
- SCSI Block Commands (SBC-3) — INCITS T10
- NVM Express Base Specification — NVM Express
- Linux kernel — T10 protection information support (crc-t10dif, block/t10-pi.c) — Linux kernel