Proven C Book←↑→

Appendix I — How machines talk: wires, buses and interruption

In the appendix on C without an operating system, devices were handled by address. Write *(volatile uint8_t *)0x10000000 = 'A' and a character went out. But what happens after that one write? Which wires carry which voltages in what order, and how does the other end turn them back into a character?

This appendix is that story. The aim is not to become a hardware engineer but to be able to read the first page of a datasheet.

Platform note. what this appendix rests on, and its limits

This machine has no I2C device and no PCI card attached. So there is exactly one thing here that can be called “measured” — the cost of the operating system’s signals. The rest is handled three ways. Protocols on the wire are built byte by byte to the specification and read back to check, and speeds and times are computed. Drivers are one copy of code run twice — on this machine they print the commands they would send instead of touching hardware, and on the RISC-V machine models of QEMU 9.2 they read and write the real register addresses. The models imitate what the registers promise, not the voltages and timing on the wire. Tables and text mark which is “computed” and which is “measured”.

Words to know first#

WordMeaningEasily confused
line, signalone strand carrying a voltage“data lines” and “control lines” do different work
busa bundle of lines shared by several devicespoint-to-point links (PCIe, USB) are often buses in name only
protocolthe agreement about what goes on the wire and whensame wires, different agreement, and nothing is understood
frameone chunk sent at a timenot “one byte” — markers are attached front and back
baudsignal changes per secondnot necessarily bits per second (it depends on the modulation)
throughputbytes actually moved per secondalways below the theoretical figure, because of the frame’s overhead
latencythe time from asking to being answeredthroughput can be large while latency is large too
controller / targetthe side that speaks / the side that answersmodern specifications prefer these words to the older “master”
full duplex / half duplexboth sides at once / by turnsthe number of wires usually decides this

Table 115.1 — The words used in this appendix

★ The third row from the bottom is the most frequently misunderstood in practice. Throughput and latency are different axes. Fill a truck with hard drives and drive it and the throughput is enormous while the latency is hours. Conversely I2C is slow (100 kbit/s), yet the latency of exchanging one byte is under a millisecond.

Four axes that separate connections#

There are dozens of ways to connect things, and only four questions to ask. Settle these four and the rest is detail.

AxisOne sideThe otherWhat it settles
number of wiresserial — queued on one strandparallel — at once on severalpin count and top speed (Figure 115.1)
timingasynchronous — no clock linesynchronous — a separate clock linewhether both sides must agree the speed in advance
addressingnone — there is only one other endpresent — one of several is calledwhether many devices can hang off one wire
who startsonly one side (controller-target)anyone (peer, arbitration)how collisions are prevented

Table 115.2 — Four axes that separate connections

serial-parallel

Figure 115.1 — Sending the same byte serially and in parallel.

Lay the common schemes out along those four axes and this is the result. The sections that follow unpack the rows one at a time.

NameWiresClockAddressIn a phrase
UART (universal asynchronous receiver-transmitter) / RS-232serial, 2 strands (+ flow control)none (asynchronous)nonethe oldest and the simplest
parallel portparallel, 8 strands + controlyes (a strobe)noneit was fast, then hit the wall of speed
I2Cserial, 2 strandsyes (SCL)yes (7 bits)many devices on two wires
SPIserial, 4 strandsyes (SCK)chosen by a wire (CS)fast and simple, and it eats wires
CANserial, 2 strands (differential)nonecarried in the messageanyone speaks and arbitration settles it
PCIeserial lanes ×N (differential)embedded (carried in the coding)yes (configuration space)the motorway inside the machine
USBserial, 2 to 4 strandsembeddedyes (the host assigns it)the host commands everything

Table 115.3 — The connections covered in this appendix

A common misconception. serial is slow and parallel is fast

It was so once. Today it is the reverse, because the faster things go the harder it is to line up the arrival times of several lines (Figure 115.1). So today’s fast links — PCIe, USB, SATA, Ethernet — are all serial, and bundle several of those serial lines (lanes) instead. “Several serial lines rather than parallel” is today’s answer.

The oldest wire — UART and RS-232#

The simplest way to join two machines is this. Send the bits along one wire in turn. There is no clock line either. Instead both sides agree in advance on “so many bits per second” and each counts on its own clock.

The device that handles this is a UART (universal asynchronous receiver-transmitter).

Frames — cutting characters out of an idle wire#

Without a clock, how is “a character starts now” recognised? By an agreed shape.

OrderNameValueWhatWithout it
—the idle state1 (high)the wire when nothing is being sentthere is no baseline against which a start can be seen
1the start bit0 (low)“it begins now” — the receiver syncs its clock herecharacter boundaries cannot be found
2–98 data bitsthe valuethe low-order bit goes first—
(optional)paritycomputedone bit making the count of ones even (or odd)a single-bit error goes unnoticed
10the stop bit1 (high)“it ends here” — distinguishable from the next start bitsending back to back loses the boundary

Table 115.4 — The ten bits of an 8N1 frame

The notation 8N1 is that table abbreviated.

PositionMeaningCommon valuesNote
first digitdata bits8, rarely 77 bits is a trace of the days of ASCII alone
middle letterparityN (none), E (even), O (odd)N is today’s default
last digitstop bits1, rarely 22 gives a slow receiver room to breathe

Table 115.5 — How to read a notation like 8N1

examples-en/apx-links/uart_frame.c (excerpt)

static size_t make_frame(unsigned char *w, uint8_t byte, unsigned parity_bits)
{
    size_t n = 0;
    for (unsigned i = 0; i < OS * 2; i++) w[n++] = 1;      /* high while idle */
    for (unsigned i = 0; i < OS; i++)     w[n++] = 0;      /* start bit: pull it down */
    unsigned ones = 0;
    for (int b = 0; b < 8; b++) {
        unsigned bit = byte >> b & 1;                       /* LSB first */
        ones += bit;
        for (unsigned i = 0; i < OS; i++) w[n++] = (unsigned char)bit;
    }
    if (parity_bits) {                                      /* for even parity */
        unsigned p = ones & 1;                              /* make the count of ones even */
        for (unsigned i = 0; i < OS; i++) w[n++] = (unsigned char)p;
    }
    for (unsigned i = 0; i < OS; i++)     w[n++] = 1;      /* stop bit: high again */
    for (unsigned i = 0; i < OS * 2; i++) w[n++] = 1;
    return n;
}
/* … */
static int receive(const unsigned char *w, size_t n, double rx_bit,
                   uint8_t *out, int *stop_ok)
{
    size_t edge = 0;
    while (edge < n && w[edge] != 0) edge++;                /* the falling edge */
    if (edge >= n) return -1;

    double t0 = (double)edge;
    uint8_t v = 0;
    for (int b = 0; b < 8; b++) {
        double at = t0 + rx_bit * (b + 1) + rx_bit / 2.0;   /* the middle of bit (b+1) */
        size_t idx = (size_t)(at + 0.5);
        if (idx >= n) return -1;
        v |= (uint8_t)(w[idx] << b);
    }
    double sat = t0 + rx_bit * 9 + rx_bit / 2.0;            /* where the stop bit is */
    *stop_ok = (sat < n) && w[(size_t)(sat + 0.5)] == 1;
    *out = v;
    return 0;
}

Output

== the byte to send ==
  'K' = 0x4B = binary 01001011 (most significant first)

== the shape on the wire (8N1) --- low=_ high=- ==
  ----__----__--____--__------
      Std0d1d2d3d4d5d6d7Sp    
  * data goes least significant bit first, so it looks reversed to the eye.

== timing ==
  baud       one bit        one frame (10 bits) bytes per second
  300        3333.333       33333.3        30
  9600       104.167        1041.7         960
  19200      52.083         520.8          1920
  115200     8.681          86.8           11520
  921600     1.085          10.9           92160
  (microseconds. 8N1 spends 10 bits on one byte, so 20% is overhead)

== when the receiver's baud rate is off ==
  the receiver samples the middle using its own bit length; the error shifts it.

  rx baud      error      value read stop bit result
  9600            +0.0%  0x4B     ok       'K' --- correct
  9700            +1.0%  0x4B     ok       'K' --- correct
  9900            +3.1%  0x4B     ok       'K' --- correct
  10100           +5.2%  0x4B     ok       'K' --- correct
  10600          +10.4%  0x8B     broken   character corrupted
  19200         +100.0%  0x9E     ok       character corrupted

  range of error where the character survives here: -5.2% to +5.9%
  * the arithmetic agrees. The stop bit is sampled at 9.5 bit times,
    and that instant must fall within that bit (one bit wide), so
    0.5 / 9.5 = 5.3% is the margin on one side.
  * yet practice uses 2 to 3% as the rule. This 5.3% is shared by both ends
    (the sender drifts too), oscillators move with temperature and supply,
    and a real receiver samples three points and takes a majority.
    The theoretical limit and the design budget are different numbers.

== what parity catches ==
  'K' has 4 ones -> even parity bit = 0
  one flipped bit changes the parity, so it is caught.
  two flipped bits leave the parity unchanged, so it is not --- parity
  is a device for noticing an error, not for correcting one.

The demonstration shows four things.

First, data goes low-order bit first. Which is why reading the shape on the wire makes the binary number look reversed.

Second, 8N1 spends ten bits on one byte. So 9600 baud is 960 bytes per second — 20% of it is overhead holding the frame together.

Third, a baud mismatch accumulates. The clock is synced at the start bit, but by the tenth bit the error has grown nine and a half times. The demonstration searched for the threshold itself and answered about ±5%, and the arithmetic agrees: 0.5 / 9.5 = 5.3%.

Fourth, and yet the rule in practice for baud error is “2 to 3%”. That these two numbers differ matters — the theoretical limit is shared between the two ends, oscillators drift with temperature and supply, and a real receiver samples several points and takes a majority. In the words of chapter 55, a contract is not the limit but a budget with margin left.

Q. Why such awkward numbers as 9600, 19200, 115200?

A. The crystal old UART chips used was 1.8432 MHz, and dividing it by integers gives these numbers (1.8432 MHz ÷ 16 ÷ 12 = 9600). What became standard was not “a round decimal” but a number an integer divider can make. Which is why some clocks cannot produce exactly 115200, and the error that follows eats into the budget above.

RS-232 — the same frame, different voltages#

A UART is logic; RS-232 is the electrical specification for putting that logic on a wire. Mixing the two burns hardware often enough to deserve a table.

TTL/CMOS UARTRS-232
1 (mark)3.3 V or 5 V−3 V to −15 V
0 (space)0 V+3 V to +15 V
logichigh is 1inverted — negative is 1
reachcentimetres on a boarda dozen metres or so
wired together directly—the voltages differ and the chip is damaged — a converter must sit between

Table 115.6 — TTL-level UART and RS-232

★ The last row is where hardware gets destroyed in practice. A computer’s serial port must not go straight onto a microcontroller’s UART pin — something like a MAX232 has to be in between.

NameDirectionWhatToday
TxDoutdata being sentalways used
RxDindata being receivedalways used
GND—the reference voltage — without it nothing worksalways used
RTS / CTSout / in“may I send” — hardware flow controlused on fast links
DTR / DSRout / in“I am powered” — device readinessa trace of the modem era
DCDinthe modem has a connectionalmost never used
RIinthe phone is ringingalmost never used

Table 115.7 — The signals of RS-232 (on a 9-pin connector)

Flow control — how to say “wait”#

If the receiving side cannot keep up, data simply disappears. A UART has no retransmission.

KindHowGoodBad
hardware (RTS/CTS)a separate line is lowered to say “stop”it does not touch the data, and stops at oncetwo more wires are needed
software (XON/XOFF)0x13 and 0x11 are inserted into the data streamthree wires sufficebinary data cannot be sent — those bytes are misread

Table 115.8 — Two kinds of flow control

Counter-example. sending binary data over an XON/XOFF line

A 0x13 byte in the middle of the data is read by the other end as “stop”. The transfer freezes then and there, and may never resume. On a line that will carry binary data, use hardware flow control or encode the data as text.

The four errors a UART reports#

NameWhenWhat it usually means
framing errorthe stop bit position was lowthe baud rates differ, or the line was disturbed
parity errorthe count of ones does not match the agreementnoise — one bit flipped
overrunthe next byte arrived before the last was readmy code was late — an interrupt or DMA is needed
breakthe line stayed low for longer than a framea deliberate signal from the other end, or a wire broken to ground

Table 115.9 — Errors met in a UART’s status register

★ The third row joins on to the end of this appendix. An overrun is a software problem, not a hardware one — when taking an interrupt per byte becomes too much, DMA (direct memory access) is what is needed.

The driver — the eight registers of the 16550#

The 16550, the UART chip that sat behind the PC’s COM1, is still used with the same register layout, and QEMU’s RISC-V machine model virt imitates it. The driver below is a cut-down version of what Linux’s 8250 driver and firmware’s early consoles do. It loads the divisor for a baud rate and sets the frame to 8N1, waits for the transmit slot to empty before putting in a byte, and uses the chip’s loopback (a test mode that connects transmit straight to receive) to see whether the chip answers.

The drivers of this appendix and the “disk” appendix touch hardware only through four functions (hw_read8, hw_read32, hw_write8, hw_write32). There is one copy of the driver code; only the bodies of those four come in two versions. Built on this machine (x86-64 Linux), they print each command they receive — W address <- value is a write and R address -> value a read. A read needs an answer, so each example has a small reply table, and that table is all the device imitation there is. Built with the cross compiler, they really read and write those addresses, and the program runs on a QEMU machine model. Lines starting with # are not commands; they are the example reporting its own result.

examples-en/apx-links/uart16550/uart16550.c (excerpt)

/* Eight registers of one byte each.
   The same number is a different register for reads and writes. */
enum {
    /* received byte / byte to send / (DLAB=1) divisor low */
    RBR = 0, THR = 0, DLL = 0,
    IER = 1, DLM = 1,            /* interrupt enable / (DLAB=1) divisor high */
    FCR = 2,                     /* FIFO control (write only) */
    LCR = 3,                     /* line control: data bits, parity, stop bits, DLAB */
    MCR = 4,                     /* modem control: DTR, RTS, loopback */
    LSR = 5,                     /* line status: received, room to send, errors */
};
enum { LCR_8N1 = 0x03, LCR_DLAB = 0x80 };
enum { MCR_DTR = 0x01, MCR_RTS = 0x02, MCR_LOOP = 0x10 };
enum { LSR_DR = 0x01, LSR_OE = 0x02, LSR_PE = 0x04, LSR_FE = 0x08, LSR_BI = 0x10,
       LSR_THRE = 0x20 };
/* … */
int uart_init(const struct uart *u, uint32_t baud)
{
    /* The chip samples each bit 16 times, so divisor = clock / (16 x baud). */
    uint32_t div = u->clock / (16u * baud);
    if (div == 0 || div > 0xFFFF) return -1;
    if (u->clock % (16u * baud) * 50 > 16u * baud) return -1;   /* more than 2% off */

    wr(u, IER, 0x00);                    /* interrupts off; we poll */
    wr(u, LCR, LCR_DLAB);                /* turn slots 0 and 1 into the divisor */
    wr(u, DLL, (uint8_t)div);
    wr(u, DLM, (uint8_t)(div >> 8));
    wr(u, LCR, LCR_8N1);                 /* drop DLAB and set 8N1 */
    wr(u, FCR, 0x07);                    /* enable the FIFOs and clear both */
    wr(u, MCR, MCR_DTR | MCR_RTS);       /* tell the other end "I am here" */
    return 0;
}
/* … */
void uart_putc(const struct uart *u, char c)
{
    while (!(rd(u, LSR) & LSR_THRE)) { }  /* wait until the transmit slot is empty */
    wr(u, THR, (uint8_t)c);
}

int uart_getc(const struct uart *u)
{
    if (!(rd(u, LSR) & LSR_DR)) return -1;
    return rd(u, RBR);
}

unsigned uart_errors(const struct uart *u)
{
    /* The error bits clear the moment LSR is read. Read once and keep the value. */
    return rd(u, LSR) & (LSR_OE | LSR_PE | LSR_FE | LSR_BI);
}
/* … */
/* Connect transmit to receive inside the chip and send one byte round. Before any
   cable, this tells whether the chip is there and its registers obey. */
int uart_loopback_test(const struct uart *u)
{
    uint8_t mcr = rd(u, MCR);
    wr(u, MCR, mcr | MCR_LOOP);
    for (int i = 0; i < 16 && uart_getc(u) >= 0; i++) { }  /* drain the FIFO */
    uart_putc(u, 'K');
    int got = -1;
    for (int tries = 0; tries < 1000 && got < 0; tries++)
        got = uart_getc(u);
    wr(u, MCR, mcr);
    return got == 'K' ? 0 : -1;
}

Output

== this machine: the commands the driver sends (trace backend) ==
W  0x10000001 <- 0x00
W  0x10000003 <- 0x80
W  0x10000000 <- 0x02
W  0x10000001 <- 0x00
W  0x10000003 <- 0x03
W  0x10000002 <- 0x07
W  0x10000004 <- 0x03
R  0x10000004 -> 0x03
W  0x10000004 <- 0x13
R  0x10000005 -> 0x60
R  0x10000005 -> 0x60
W  0x10000000 <- 0x4b 'K'
R  0x10000005 -> 0x61
R  0x10000000 -> 0x4b 'K'
W  0x10000004 <- 0x03
R  0x10000005 -> 0x60
W  0x10000000 <- 0x6f 'o'
R  0x10000005 -> 0x60
W  0x10000000 <- 0x6b 'k'
R  0x10000005 -> 0x60
W  0x10000000 <- 0x0a
W  0x00100000 <- 0x00005555

== QEMU virt (emulated RISC-V): the same driver on the 16550 model ==
ok

The first five writes in the output answer the earlier question “why such awkward numbers as 9600, 19200, 115200?”. The clock QEMU gives this chip in its device tree is 3,686,400 Hz (twice 1.8432 MHz), and the divisor for 115200 baud, 3686400 / (16 × 115200) = 2, divides exactly. The driver raises the DLAB bit in LCR to turn slots 0 and 1 into the divisor, writes 2, and drops DLAB again. An address that is a different register depending on a bit is a saving from the days when address lines were scarce.

The four error bits of the line status register (LSR) are the four rows of the table “errors met in a UART’s status register” above. Reading LSR clears them, so a driver has to keep the value it read once.

The ok on the QEMU side is the chip model returning 'K' through loopback. Built without the loopback bit, the 'K' went out to the console and no was printed. The QEMU 16550 does not imitate baud rates, though: with a wrong divisor the characters still come out. What happens when the baud rates disagree is what the frame demonstration above shows; this one shows what is written to which register, in what order.

Why parallel disappeared#

Sending eight bits at once looks eight times faster. For a while it was. The Centronics port that connected printers worked that way.

GroupLinesWhatDirection
dataD0 to D7eight bits at onceout
controlSTROBE“the data is valid now” — effectively the clockout
statusACK“received”in
statusBUSY“still working, wait”in
statusPAPER OUT, SELECT, ERRORthe printer’s circumstancesin
referenceGND ×8a ground paired with each data line to reduce noise—

Table 115.10 — The signals of a parallel (Centronics) port

Why it vanished despite being fast is in Figure 115.1. The requirement that all eight lines arrive at the same instant becomes the wall.

ProblemWhat it isAs speed rises
skewlines differ in length and load, so arrival times spreadonce one bit’s time is shorter than the spread, they cannot be lined up
crosstalka change on one line leaks into its neighbourthe faster the change, the worse it is
pins and costeight lines means eight of everything: connector, board, shieldingexpensive, thick and unbendable
terminationeach line’s impedance must be matched or it reflectsbeyond a few hundred MHz it is effectively impossible

Table 115.11 — Why parallel loses as things get faster

★ So today’s answer is not “parallel was abandoned” but “make one serial line very fast, and bundle several of them”. PCIe’s lanes, USB 3′s several pairs and SATA all have that shape. Lining the lines up is solved by hardware recovering each line separately and then reassembling — possible because each line carries its own clock inside its coding.

Many devices on two wires — I2C#

The scheme for when wires must be saved above all. Two wires (data SDA, clock SCL) carry several devices, chosen by address.

NameWhatHow it is madeWhy so
SCLthe clockthe controller drives itsynchronous — no need to agree a speed in advance
SDAdatathe two sides use it by turnshalf duplex — one direction at a time
START“a transaction begins”SDA is pulled low while SCL is higha deliberate violation of “data changes only while SCL is low”, which is what makes it a marker
STOP“it ends”SDA is released high while SCL is highlikewise
repeated START“do not end, only turn round”another START without a STOPno other controller can cut in meanwhile
ACK / NACK“received” / “absent, or enough”on the ninth clock the receiver pulls SDA low, or does notpossible precisely because a line can only be pulled down

Table 115.12 — The signals and markers of I2C

examples-en/apx-links/i2c_frame.c (excerpt)

static void put_byte(uint8_t b, const char *what, int acked)
{
    for (int i = 7; i >= 0; i--) put(EV_BIT, b >> i & 1, i == 7 ? what : NULL);
    put(acked ? EV_ACK : EV_NACK, acked ? 0 : 1, NULL);
}
/* … */
put(EV_START, 0, "the controller takes the bus");
put_byte((uint8_t)(dev << 1 | 0), "device address + write", 1);
put_byte(reg, "register number", 1);
put_byte(val, "value to write", 1);
put(EV_STOP, 0, "release the bus");

/* ── a read: a repeated START only changes direction ── */
put(EV_START, 0, "take it again");
put_byte((uint8_t)(dev << 1 | 0), "device address + write", 1);
put_byte(reg, "say which register to read first", 1);
put(EV_RSTART, 0, "* START again without STOP --- nobody can cut in meanwhile");
put_byte((uint8_t)(dev << 1 | 1), "device address + read", 1);
put_byte(val, "the value the device returned", 0);   /* on the last byte the controller signals the end with a NACK */
put(EV_STOP, 0, "end");

Output

== why the address is confusing ==
  the 7-bit address 0x3C is shifted one place on the wire (last bit is read/write).
    write: 0x3C << 1 | 0 = 0x78
    read : 0x3C << 1 | 1 = 0x79
  so datasheets call the same device 0x3C in one place and 0x78 in another.

== the order on the wire ==
  START        the controller takes the bus
  byte         0x78  device address + write
  ACK          the device pulled SDA down = received
  byte         0x00  register number
  ACK          the device pulled SDA down = received
  byte         0xAF  value to write
  ACK          the device pulled SDA down = received
  STOP         release the bus
  START        take it again
  byte         0x78  device address + write
  ACK          the device pulled SDA down = received
  byte         0x00  say which register to read first
  ACK          the device pulled SDA down = received
  repeated START * START again without STOP --- nobody can cut in meanwhile
  byte         0x79  device address + read
  ACK          the device pulled SDA down = received
  byte         0xAF  the value the device returned
  NACK         nobody pulled it down = absent, or done
  STOP         end

== what means what ==
  START : SDA falls while SCL is high   (deliberately breaking the rule that
  STOP  : SDA rises while SCL is high    data changes only while SCL is low)
  ACK   : on the ninth clock the receiver pulls SDA down
  NACK  : nobody pulls it down and the line stays high --- absent and enough look alike

== timing ==
  clocks in these two transactions: 63 (data and ACK bits), 5 markers
  speed mode     clock          time for these two transactions
  standard       100.0          630.0 microseconds
  fast           400.0          157.5 microseconds
  fast plus      1000.0         63.0 microseconds
  high speed     3400.0         18.5 microseconds
  (clock in kHz; markers and wait states are not counted)

== why the lines are only ever pulled down ==
  if two devices speak at once, one pushing 5V and one pushing 0V, current
  flows straight through and damages the chips. I2C lets nobody push (open drain)
  and leaves the raising to a resistor. So speaking at once is safe,
  and the property that zero wins gives arbitration and ACK for free.

Three things are immediately useful in practice.

First, why an address is written two ways. A 7-bit address is shifted one place left on the wire with the read/write bit appended. So the same device appears in datasheets as 0x3C and as 0x78. Both are right — one is the address, the other is “the first byte on the wire”.

Second, NACK means two things. “No such device” and “stop sending now” look the same on the wire. The convention is that the controller NACKs the last byte of a read to say it is done.

Third, a design where lines are only ever pulled down makes much of this free. Nobody pushes a voltage up, so two speaking at once damages no chip, and from the property “zero wins” both ACK and arbitration simply fall out. In exchange the pull-up is done by a resistor, so rising is slow, and that sets I2C’s speed ceiling.

NameClockBytes per second (roughly)Note
standard100 kHzabout 11,000most sensors
fast400 kHzabout 44,000today’s default
fast plus1 MHzabout 110,000the wiring must be short
high speed3.4 MHzabout 380,000needs dedicated circuitry — uncommon

Table 115.13 — I2C speed modes

Q. I attached several I2C devices and none is found. What do I look at first?

A. Four things in order. ① Are the pull-up resistors there (without them the line never rises)? ② Do the addresses collide (two sensors of the same kind usually do — change one with its address pin)? ③ Is the ground shared? ④ Is the wiring so long, or the device count so high, that the rise time is too slow? Three of the four are electrical problems and only one is software.

The driver — telling a controller one signal at a time#

Software seldom wiggles SDA and SCL itself (bit-banging). Usually an I2C controller drives the wires, and the driver gives the controller commands such as “make a start and send this byte”. Below is a driver for the OpenCores I2C controller, found in chips such as the SiFive FU540, reading the temperature from an LM75 sensor (address 0x48). The controller’s command bits STA, WR, RD, NACK and STO match the signals in the table above (START, a byte, ACK/NACK, STOP) one for one.

examples-en/apx-links/i2c_ocores/i2c.c (excerpt)

/* Issue one command and wait for it to finish.
   One byte plus one ACK bit go over the wire. */
static int step(const struct i2c *b, uint8_t cmd)
{
    wr(b, CR, cmd);
    uint8_t sr;
    while ((sr = rd(b, SR)) & SR_TIP) { }
    if (sr & SR_AL) return -1;                     /* lost the bus to another master */
    return (cmd & CR_WR) && (sr & SR_RXNACK) ? -1 : 0;   /* nobody ACKed */
}
/* … */
int i2c_write_read(const struct i2c *b, uint8_t addr, const uint8_t *w, unsigned nw,
                   uint8_t *r, unsigned nr)
{
    wr(b, TXR, (uint8_t)(addr << 1));                    /* address + write (0) */
    if (step(b, CR_STA | CR_WR)) goto fail;
    for (unsigned i = 0; i < nw; i++) {
        wr(b, TXR, w[i]);
        if (step(b, CR_WR)) goto fail;
    }
    if (nr) {
        wr(b, TXR, (uint8_t)(addr << 1 | 1));  /* repeated start, address + read (1) */
        if (step(b, CR_STA | CR_WR)) goto fail;
        for (unsigned i = 0; i < nr; i++) {
            /* the last byte is answered with NACK ("enough"), and a stop follows */
            int last = i + 1 == nr;
            step(b, (uint8_t)(CR_RD | (last ? CR_NACK | CR_STO : 0)));
            r[i] = rd(b, RXR);
        }
        return 0;
    }
    return step(b, CR_STO);
fail:
    wr(b, CR, CR_STO);                                   /* release the bus */
    return -1;
}

Output

== this machine: the commands the driver sends (trace backend) ==
W  0x10030008 <- 0x00
W  0x10030000 <- 0xe7
W  0x10030004 <- 0x03
W  0x10030008 <- 0x80
W  0x1003000c <- 0x90
W  0x10030010 <- 0x90
R  0x10030010 -> 0x00
W  0x1003000c <- 0x00
W  0x10030010 <- 0x10
R  0x10030010 -> 0x00
W  0x1003000c <- 0x91
W  0x10030010 <- 0x90
R  0x10030010 -> 0x00
W  0x10030010 <- 0x20
R  0x10030010 -> 0x00
R  0x1003000c -> 0x19
W  0x10030010 <- 0x68
R  0x10030010 -> 0x00
R  0x1003000c -> 0x80
#  LM75 at 0x48: 25.5 C

(QEMU's RISC-V machines model no I2C controller: there is no second half here)

The bytes 0x90 and 0x91 sent in the output are the earlier “why an address is written two ways” — the 7-bit address 0x48 shifted one place, with the write (0) or read (1) bit added at the end. The command 0x68 that receives the second byte sets RD, NACK and STO together, saying “enough” and stopping at once. The command 0x20 for the first byte has no NACK, so an ACK goes out.

This demonstration has no QEMU half. QEMU’s RISC-V machine models have no I2C controller, so the answers in the output above are made up by the reply table (the status register always says “done, ACK received”, and the temperature bytes give 25.5 degrees). This driver has not been run on a real controller.

Fast on four wires — SPI#

Where I2C gave up speed to save wires, SPI does the reverse: four wires, and fast.

NameOther namesWhatDirection
SCKSCLK, CLKthe clock — the controller drives itcontroller → device
MOSISDO, COPIdata the controller sendscontroller → device
MISOSDI, CIPOdata the device sendsdevice → controller
CSSS, NSS, CEthe marker meaning I am speaking to this device (usually active low)controller → device

Table 115.14 — The four wires of SPI

★ There is no address. Instead one CS line is run to each device. More devices means more wires — exactly the opposite trade from I2C.

examples-en/apx-links/spi_shift.c (excerpt)

for (int c = 1; c <= 8; c++) {
    unsigned mosi = m >> 7 & 1;          /* the bit the controller puts out (high bit first) */
    unsigned miso = s >> 7 & 1;          /* the bit the device puts out */
    m = (uint8_t)(m << 1 | miso);        /* each takes the other's bit in at the low end */
    s = (uint8_t)(s << 1 | mosi);
    bits8(m, mb); bits8(s, sb);
    printf("  %-6d %-6u %-6u %-12s %-12s\n", c, mosi, miso, mb, sb);
}

Output

== at the start ==
  controller register : 0xA5 (10100101)
  device register     : 0x3C (00111100)

== one bit per clock ==
  clock  MOSI   MISO   controller   device      
  1      1      0      01001010     01111001    
  2      0      0      10010100     11110010    
  3      1      1      00101001     11100101    
  4      0      1      01010011     11001010    
  5      0      1      10100111     10010100    
  6      1      1      01001111     00101001    
  7      0      0      10011110     01010010    
  8      1      0      00111100     10100101    

== after eight clocks ==
  controller register : 0x3C  <- what the device sent
  device register     : 0xA5  <- what the controller sent
  * sending and receiving finished in the same eight clocks. Full duplex is
    free because the two registers form one ring. Even with nothing to receive
    something must be sent (usually 0x00 or 0xFF): a dummy byte.

== the four modes --- which edge presents, which samples ==
  mode     CPOL   CPHA   SCK at rest      sampling edge
  0        0      0      low              rising (first edge)
  1        0      1      low              falling (second edge)
  2        1      0      high             falling (first edge)
  3        1      1      high             rising (second edge)

== when the mode is mismatched ==
  sent 0xA5 (10100101)
  read with one edge of error: 0x52 (01010010)  <- everything shifted one place
  * so when SPI gives half-right values, suspect the mode first.

== speed ==
  SPI has no start, stop, address or ACK. Exactly eight clocks per byte.
  SCK            bytes per second compared
  1 MHz          125000           11 times I2C standard (100 kHz)
  10 MHz         1250000          112 times I2C standard (100 kHz)
  50 MHz         6250000          562 times I2C standard (100 kHz)
  (UART spends 10 bits per byte, I2C 9, SPI 8 --- no overhead)

The demonstration shows what SPI really is. Two shift registers form a single ring. Push one bit per clock and after eight clocks the two values have swapped places. So “sending and receiving at the same time” is free — and even to receive only, you must send something (a dummy byte).

ModeCPOLCPHAClock at restSampling edge
000lowrising (the first edge)
101lowfalling (the second edge)
210highfalling (the first edge)
311highrising (the second edge)

Table 115.15 — SPI’s four modes

A common misconception. SPI has a specification, so any device can just be wired up

SPI has no official specification. There are four modes; whether data goes most-significant or least-significant bit first differs by device; whether CS is raised between bytes or held low for a whole transaction differs too. As in the mode-mismatch part of the demonstration, one edge of mode mismatch shifts every value by one place — which is why a “half-right” value should make you suspect the mode first.

The driver — asking a flash chip its name#

The device most often hung on SPI is a NOR flash, and many boards read their first-stage bootloader from it. The driver below uses the SPI controller of QEMU’s machine model sifive_u (a model of the SiFive FU540 board) to ask the flash for its identity (the JEDEC ID: three bytes for manufacturer, type and capacity) and read its first eight bytes.

examples-en/apx-links/spi_flash/spi.c (excerpt)

/* SPI receives as much as it sends. Push one byte in and one byte comes out. */
void spi_xfer(const struct spi *s, const uint8_t *tx, uint8_t *rx, unsigned n)
{
    wr(s, CSMODE, CSMODE_HOLD);   /* hold chip select between bytes too */
    for (unsigned i = 0; i < n; i++) {
        while (rd(s, TXDATA) & FIFO_FLAG) { }
        wr(s, TXDATA, tx ? tx[i] : 0x00);
        uint32_t v;
        while ((v = rd(s, RXDATA)) & FIFO_FLAG) { }
        if (rx) rx[i] = (uint8_t)v;
    }
}

void spi_release(const struct spi *s) { wr(s, CSMODE, CSMODE_AUTO); }
/* … */
/* A SPI NOR flash command is its first byte.
   One command lasts while chip select is down. */
void flash_jedec_id(const struct spi *s, uint8_t id[3])
{
    uint8_t cmd = 0x9F;  /* READ ID: manufacturer, type, capacity */
    spi_xfer(s, &cmd, 0, 1);
    spi_xfer(s, 0, id, 3);
    spi_release(s);
}

void flash_read(const struct spi *s, uint32_t addr, uint8_t *dst, unsigned n)
{
    uint8_t cmd[4] = { 0x03, (uint8_t)(addr >> 16), (uint8_t)(addr >> 8),
                       (uint8_t)addr };
    spi_xfer(s, cmd, 0, 4);  /* READ: one command byte + 3-byte address */
    spi_xfer(s, 0, dst, n);                   /* then shift out n bytes */
    spi_release(s);
}

Output

== this machine: the commands the driver sends (trace backend) ==
W  0x10040060 <- 0x00000000
W  0x10040000 <- 0x00000003
W  0x10040004 <- 0x00000000
W  0x10040010 <- 0x00000000
W  0x10040040 <- 0x00080000
W  0x10040018 <- 0x00000002
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x0000009f
R  0x1004004c -> 0x00000000
W  0x10040018 <- 0x00000002
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x0000009d
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x00000070
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x00000019
W  0x10040018 <- 0x00000000
W  0x10040018 <- 0x00000002
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000003
R  0x1004004c -> 0x00000000
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x00000000
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x00000000
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x00000000
W  0x10040018 <- 0x00000002
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x00000042
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x0000004f
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x0000004f
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x00000054
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x00000020
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x0000004f
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x0000004b
R  0x10040048 -> 0x00000000
W  0x10040048 <- 0x00000000
R  0x1004004c -> 0x0000000a
W  0x10040018 <- 0x00000000
#  JEDEC id 9d 70 19 (manufacturer 0x9d = ISSI)
#  first 8 bytes of the flash: BOOT OK

== QEMU sifive_u (emulated RISC-V): the same driver on the SPI controller and flash models ==
JEDEC id 9d 70 19 (manufacturer 0x9d = ISSI)
first 8 bytes of the flash: BOOT OK

Where W 0x10040048 <- 0x00000000 repeats in the output are the dummy bytes mentioned above: to make the flash give up its three identity bytes, the master has to push three bytes in. The other way round, the byte pushed out while the command byte 0x9f goes in is thrown away. Chip select has to stay down for the whole command, so the driver sets CSMODE to HOLD (2) and returns it to AUTO (0) when the command ends. The first byte on the QEMU side, 9d, is ISSI’s manufacturer number and matches the flash on this machine model (IS25WP256). The line after it is the first eight bytes of the file handed to QEMU as the flash contents.

I2CSPISo
wires2 (unchanged as devices are added)3 + 1 per devicemany devices favour I2C
speed100 kHz to 1 MHztens of MHzneeding speed favours SPI
bits per byte9 (the ACK included)8SPI has no overhead either
directionhalf duplexfull duplexneeding both at once favours SPI
confirmation of receiptthere is an ACKnoneSPI cannot tell that the other end has died
specificationwritten downconventionwith SPI the datasheet must be read

Table 115.16 — I2C against SPI

On one wire, and with anyone speaking — 1-Wire and CAN#

NameWiresThe ideaWhere
1-Wire1 (+ ground)zero and one are told apart by pulse length, and power is stolen from the data linetemperature sensors, identity chips
CAN2 (differential)anyone speaks, and on a collision the lower message number wins (arbitration)cars, industrial machinery

Table 115.17 — Two unusual connection schemes

★ CAN’s arbitration is the same idea as I2C’s “zero wins”. When two speak at once the signal of the one sending zero remains on the wire, and the one sending a one sees that the wire differs from what it sent and withdraws. It is not collision detection followed by retransmission: the collision itself is the priority decision — which is why an urgent message is never delayed.

The motorway inside the machine — PCI and PCI Express#

So far these were “wires between chips”. Now the scale changes. Where graphics cards, network cards and NVMe storage attach — the PCI family.

The name says “bus”, but today’s PCIe is really point to point. Old PCI really did have several devices sharing the same lines (so one slow device slowed everything), while PCIe gives each device its own lines with a switch joining them in the middle.

old PCIPCIeSo
connectionseveral devices share one buspoint to point per device, plus switchesone device cannot hold up the others
wiresparallel 32/64 bits + clockserial differential lanes ×1 to ×16exactly the conclusion of Figure 115.1
going fasterraise the clock (33 → 66 MHz)add lanes or raise the generationthe direction of growth differs
interruptsshared INTA to INTD linesMSI/MSI-X — sent as a memory writeit becomes data rather than a wire

Table 115.18 — PCI and PCI Express

GenerationOn the wireCodingEffective bandwidth
1.02.5 GT/s8b/10b (20% lost)about 250 MB/s
2.05 GT/s8b/10babout 500 MB/s
3.08 GT/s128b/130b (1.5% lost)about 985 MB/s
4.016 GT/s128b/130babout 1.97 GB/s
5.032 GT/s128b/130babout 3.94 GB/s

Table 115.19 — PCIe generations and effective speed (per lane)

★ The “coding” column matters for a reason. There is no separate clock line, so the clock must be planted in the data (otherwise a long run of zeros loses the receiver’s timing), and what is planted is the loss. Dropping 8b/10b for 128b/130b at 3.0 is why “1.6 times the speed” became “twice the bandwidth”.

Configuration space — the 256 bytes in which a device introduces itself#

PCI’s best idea is this. Every device carries a business card of the same shape. So the operating system can read that card — the configuration space — and find out what is plugged in without knowing in advance.

OffsetSizeNameWhatTypical value
0x002vendor IDa number handed out by PCI-SIG0x8086 (Intel)
0x022device IDa model number chosen by the vendor—
0x042commandthe switch that turns this device on — I/O, memory, bus master0x0007
0x062statuserrors, and a flag saying “there is a capability list”0x0010
0x081revisionthe revision of the same model—
0x09–0x0B3class codewhat kind of device this is — known without a driver02 00 00 = Ethernet
0x0C1cache line sizea trace of old PCI0x10
0x0E1header type0 device, 1 bridge, 0x80 means multiple functions0x00
0x10–0x2424BAR (base address register) 0 to 5the address windows this device requests (Table 115.21)—
0x2C–0x2E4subsystem IDthe company and model that built the board—
0x304expansion ROMwhere firmware on the card lives—
0x341capability pointerthe offset of the first item of a linked list0x50
0x3C1interrupt linewhich IRQ it was wired to in the old scheme11
0x3D1interrupt pinwhich of INTA to INTD1 (A)

Table 115.20 — The main fields of PCI configuration space (header type 0)

BitsValueMeaningNote
00memory spaceusually this — a pointer reaches it
01I/O spacex86′s narrow 64 KiB world
2–10032-bit address
2–11064-bit addressthe next BAR holds the upper 32 bits — it eats two slots
31prefetchablereading has no side effect, so caching and burst transfers are allowed
restaddressthe base addressthe low bits are hardwired to zero for the size

Table 115.21 — The low bits of a BAR say what kind it is

The driver below is a cut-down version of the enumeration (finding every device plugged in and handing out addresses) that firmware does at boot. It scans the PCIe of QEMU’s virt machine model through ECAM (configuration space laid out as a window in the memory addresses), sizes each device’s BARs and gives them addresses, and follows the capability list. Two more devices are plugged into QEMU — its teaching device edu and a virtio random number generator.

examples-en/apx-links/pci_enum/pci.c (excerpt)

/* ECAM: put the bus, device, function and register numbers in their bit positions and
   that is the address. 4 KiB per function, eight functions per device, thirty-two
   devices per bus. */
static uintptr_t cfg_addr(const struct pci_host *h, unsigned bus, unsigned dev,
                          unsigned fn, unsigned off)
{
    return h->ecam + ((uintptr_t)bus << 20 | (uintptr_t)dev << 15
                      | (uintptr_t)fn << 12 | off);
}
/* … */
/* A BAR's size cannot be asked for. Write all ones and read back the low bits the
   device holds at zero. A 64-bit BAR's next slot holds the upper 32 bits. */
static unsigned size_bar(struct pci_host *h, struct pci_fn *f, unsigned i)
{
    unsigned off = BAR0 + 4 * i;
    uint32_t orig = pci_read32(h, f->bus, f->dev, f->fn, off);
    if (orig & 1) return 1;                         /* skip I/O-space BARs */
    int is64 = (orig >> 1 & 3) == 2;

    pci_write32(h, f->bus, f->dev, f->fn, off, 0xFFFFFFFFu);
    uint64_t mask = pci_read32(h, f->bus, f->dev, f->fn, off) & ~0xFull;
    if (is64) {
        pci_write32(h, f->bus, f->dev, f->fn, off + 4, 0xFFFFFFFFu);
        mask |= (uint64_t)pci_read32(h, f->bus, f->dev, f->fn, off + 4) << 32;
    } else {
        mask |= 0xFFFFFFFF00000000ull;
    }
    if ((uint32_t)mask == 0) {                      /* no bits held: there is no BAR */
        pci_write32(h, f->bus, f->dev, f->fn, off, orig);
        return is64 ? 2 : 1;
    }
    uint64_t size = ~mask + 1;  /* the lowest one among the held bits */

    /* Fourth step: write an address back into the BAR. Being firmware, this code writes
       a new address cut from the window, aligned to the size (an operating system would
       restore the firmware's value). That address becomes the address of the device's
       registers. */
    uint64_t at = (h->mem_next + size - 1) & ~(size - 1);
    if (at + size > h->mem_end) at = 0;  /* if the window runs out, assign nothing */
    else h->mem_next = at + size;
    pci_write32(h, f->bus, f->dev, f->fn, off, (uint32_t)at);
    if (is64) pci_write32(h, f->bus, f->dev, f->fn, off + 4, (uint32_t)(at >> 32));
    f->bar[i] = at;
    f->bar_size[i] = size;
    return is64 ? 2 : 1;
}
/* … */
int pci_scan(struct pci_host *h,
             void (*visit)(const struct pci_host *, const struct pci_fn *))
{
    int found = 0;
    for (unsigned dev = 0; dev < 32; dev++) {
        for (unsigned fn = 0; fn < 8; fn++) {
            uint32_t id = pci_read32(h, 0, dev, fn, ID);
            if ((id & 0xFFFF) == 0xFFFF) {          /* all ones when nobody answers */
                if (fn == 0) break;
                continue;
            }
            struct pci_fn f = { .bus = 0, .dev = dev, .fn = fn,
                                .vendor = (uint16_t)id, .device = (uint16_t)(id >> 16),
                                .class_code = pci_read32(h, 0, dev, fn, CLASS) >> 8 };
            uint32_t hdr = pci_read32(h, 0, dev, fn, HDR) >> 16 & 0xFF;
            uint32_t cmd = pci_read32(h, 0, dev, fn, CMD) & 0xFFFF;
            /* While sizing, the BAR holds a nonsense value. Turn off memory and I/O
               decoding so the device does not answer at that address meanwhile. */
            pci_write32(h, 0, dev, fn, CMD, cmd & ~(uint32_t)(CMD_MEM | CMD_IO));
            if ((hdr & 0x7F) == 0)  /* an ordinary device (not a bridge) */
                for (unsigned i = 0; i < 6; )
                    i += size_bar(h, &f, i);
            pci_write32(h, 0, dev, fn, CMD, cmd | CMD_MEM | CMD_MASTER);
            visit(h, &f);
            found++;
            if (fn == 0 && !(hdr & 0x80)) break;  /* a device with a single function */
        }
    }
    return found;
}
/* … */
/* Capability list: if bit 4 of the status register is set, 0x34 holds the offset of the
   first entry. It is a linked list whose entries start [type, 1 byte][next offset, 1
   byte]. */
unsigned pci_caps(const struct pci_host *h, const struct pci_fn *f, uint8_t *ids,
                  unsigned max)
{
    if (!(pci_read32(h, f->bus, f->dev, f->fn, CMD) >> 16 & 0x10)) return 0;
    unsigned n = 0;
    unsigned at = pci_read32(h, f->bus, f->dev, f->fn, 0x34) & 0xFC;
    while (at && n < max) {                         /* max breaks a cycle */
        uint32_t w = pci_read32(h, f->bus, f->dev, f->fn, at);
        ids[n++] = (uint8_t)w;
        at = w >> 8 & 0xFC;
    }
    return n;
}

Output

== this machine: the commands the driver sends (trace backend) ==
R  0x30000000 -> 0x11e81234
R  0x30000008 -> 0x00ff0010
R  0x3000000c -> 0x00000000
R  0x30000004 -> 0x00100000
W  0x30000004 <- 0x00000000
R  0x30000010 -> 0x00000000
W  0x30000010 <- 0xffffffff
R  0x30000010 -> 0xfff00000
W  0x30000010 <- 0x40000000
R  0x30000014 -> 0x00000000
W  0x30000014 <- 0xffffffff
R  0x30000014 -> 0x00000000
W  0x30000014 <- 0x00000000
R  0x30000018 -> 0x00000000
W  0x30000018 <- 0xffffffff
R  0x30000018 -> 0x00000000
W  0x30000018 <- 0x00000000
R  0x3000001c -> 0x00000000
W  0x3000001c <- 0xffffffff
R  0x3000001c -> 0x00000000
W  0x3000001c <- 0x00000000
R  0x30000020 -> 0x00000000
W  0x30000020 <- 0xffffffff
R  0x30000020 -> 0x00000000
W  0x30000020 <- 0x00000000
R  0x30000024 -> 0x00000000
W  0x30000024 <- 0xffffffff
R  0x30000024 -> 0x00000000
W  0x30000024 <- 0x00000000
W  0x30000004 <- 0x00000006
R  0x30000004 -> 0x00100000
R  0x30000034 -> 0x00000040
R  0x30000040 -> 0x00000005
#  00:00.0  1234:11e8  class 00ff00  caps MSI
#     BAR0 at 0x40000000, 1024 KiB
R  0x40000000 -> 0x010000ed
W  0x40000008 <- 0x00000005
R  0x40000020 -> 0x00000001
R  0x40000020 -> 0x00000000
R  0x40000008 -> 0x00000078
#     edu id register 0x010000ed, 5! = 120
R  0x30008000 -> 0xffffffff
R  0x30010000 -> 0xffffffff
   ... 29 more like this, every 0x8000, up to 0x300f8000
#  functions found: 1
W  0x00100000 <- 0x00005555

== QEMU virt (emulated RISC-V): the same driver on the PCIe host bridge model ==
00:00.0  1b36:0008  class 060000
00:01.0  1234:11e8  class 00ff00  caps MSI
   BAR0 at 0x40000000, 1024 KiB
   edu id register 0x010000ed, 5! = 120
00:02.0  1af4:1005  class 00ff00  caps MSI-X vendor vendor vendor vendor vendor
   BAR1 at 0x40100000, 4 KiB
   BAR4 at 0x40104000, 16 KiB
functions found: 3

The middle of the demonstration finds the size of a BAR, and it is the cleverest part of this specification. There is no field in which to ask a device its size. Instead you write all ones into a BAR and read it back, and the low bits the device holds at zero tell you the size. Four steps — write, read back, invert and add one, write an address back — are the heart of PCI enumeration. In the fourth step an operating system restores the value the firmware gave; this driver, being firmware, writes a new address cut from the window and aligned to the size. Leave the step out and the device sits at a nonsense all-ones address. While sizing, memory and I/O decoding are turned off in the command register (W 0x30000004 <- 0x00000000 in the output).

The QEMU side shows the address is real. Writing 5 at 0x40000000, the address given to edu’s BAR0, made the device return 5! = 120. The virtio device’s BAR4 is a 64-bit BAR and took two slots; at 16 KiB it was placed on a 16 KiB boundary, 0x40104000. The five vendor-specific entries in that device’s capability list are how virtio announces where its configuration windows are. On this machine’s half, the thirty-one reads after device 0 all return 0xffffffff, which is PCI’s way of saying “nobody is there” (the reply table makes up only one device).

Q. What is lspci actually showing?

A. It reads this business card (the configuration space) and renders it in human words. The vendor and device IDs are only numbers; the names are looked up in a list file called pci.ids. Which is why a new device in an old system shows up as “Unknown device 8086:10fa” — the device is not broken, the list of names is stale.

The motorway going out — USB#

USB’s design principle is one sentence. The host commands everything. A device cannot speak on its own; it answers only when the host asks.

NameGenerationOn the wireUsed for
Low Speed1.x1.5 Mbit/smice and keyboards
Full Speed1.x12 Mbit/saudio and simple devices
High Speed2.0480 Mbit/sstorage and cameras
SuperSpeed3.x5 to 20 Gbit/sexternal SSDs and displays

Table 115.22 — USB speed grades

NameWhat it guaranteesUsed forNote
controldeliveryrecognising and configuring a deviceevery device must have it
bulkdelivery, but not whenstorage and printersit uses whatever bandwidth is left
interruptdelivery within a set periodmice and keyboardsdespite the name it is not an interrupt — the host asks periodically
isochronoustiming guaranteed, delivery notaudio and videolate data is useless, so it is not retransmitted

Table 115.23 — USB’s four transfer types

★ The name in the third row is the most confusing word in this whole appendix. USB’s “interrupt transfer” is not a device interrupting. The host asks “anything to say?” every interval the device specifies (1 ms, say), and the device answers only then. The real interrupt is the IRQ of the next section.

A common misconception. plug in a USB device and the device speaks to the computer

The device only signals electrically that it is attached (a voltage on the lines changes). Everything after that is commanded by the host — assigning an address, asking for the business card (the descriptors), choosing a configuration. So a USB device can do nothing by itself. That simple rule is what made cheap devices possible, at the price of complexity on the host side.

Interruption — IRQs#

So far it was “how things are moved”. Now it is “who speaks when”. The line by which a device tells the CPU “something happened to me” is the IRQ (interrupt request).

Where chapter 91 and the appendix on C without an operating system covered what may be done inside a handler, here we look at the stage before it — the wire and its value.

edgelevelSo
what is the signalthe instant the line changeswhile the line is held low (or high)
can it be missedyes — miss the instant and it is goneno — it is still therelevel is safer
how it is clearedit clears itselfa device register must be read to clear itnot clearing it raises it endlessly
sharing between deviceshard — two at once look like oneeasy — ask who is still holding ita shared IRQ is properly level triggered

Table 115.24 — Edge triggering and level triggering

★ The third row is the trap seen in the appendix on C without an operating system. With level triggering, a handler that does not read the device’s status register leaves the request asserted, and the program spins inside the handler forever. And the word that stops the compiler removing that read is volatile.

SchemeHowDevice countSo
a dedicated lineone IRQ line per deviceas many as there are linespins run out
a shared lineseveral devices share one lineseveraleveryone must be asked who raised it
MSIan interrupt becomes writing a value to an agreed addresseffectively unlimitedno wire, so no sharing problem
MSI-XMSI extended, a different number per queuethousandseach queue can be routed to a different core

Table 115.25 — How interrupt delivery changed

Q. How can “writing a value to memory” replace an interrupt?

A. PCIe already has a path that carries data. So agree that when a device writes a set value to “a special address”, the interrupt controller catches that write and wakes the CPU. The real gain is not saving a wire but being able to number each interrupt — so “which queue filled up” is known without asking.

examples-en/apx-links/signal_cost.c (excerpt)

static volatile sig_atomic_t hits;
static void handler(int sig) { (void)sig; hits++; }
/* … */
double t0 = now_ns();
for (long i = 0; i < N; i++) raise(SIGUSR1);
double t1 = now_ns();
double per_signal = (t1 - t0) / (double)N;

t0 = now_ns();
for (long i = 0; i < N; i++) plain_p();
t1 = now_ns();
double per_call = (t1 - t0) / (double)N;

Output

== measured on this machine (mean of 200000) ==
  one ordinary function call     :      1.9 ns
  raising and handling one signal:   2239.8 ns
  ratio                          :   1202.5 times

  the handler ran 201000 times (a check that none were missed)

== what this number tells you ==
  that one interruption is not free, and that it costs far more than an
  ordinary function call --- because each interruption must put the state of
  the work somewhere, go to the handler, and take it back out again.

  from it, how many per second can be borne:
  events/s         CPU used         note
  1000             0.22%            comfortable
  10000            2.24%            comfortable
  100000           22.40%           workable, but the margin is thin
  1000000          223.98%          impossible --- it would do nothing but handle them

  * again, this is the cost of an operating system signal. A hardware interrupt
    is usually far cheaper (a few hundred nanoseconds). But the *shape* of the
    arithmetic is the same --- learning that shape is the point of this demonstration.

★ The demonstration above measures operating system signals, not hardware interrupts (and says so itself). A hardware IRQ is usually far cheaper. The number is still useful because the shape of the arithmetic is the same — “cost per event × events per second = CPU occupied”.

Moving it instead — DMA#

If you cannot be interrupted per byte, what then? The answer is “take the CPU out”.

dma-path

Figure 115.2 — The path where the CPU carries the data, and the path where DMA does.

ModeHowTrade
single transferone word at a time, returning the bus to the CPU in betweenthe CPU is blocked less, but it is slower
bursta set number in one gofast, but the CPU waits meanwhile
scatter-gathergiven a list of “4 KiB here, 8 KiB there” it carries them itselfscattered memory in one go — the list itself becomes a data structure

Table 115.26 — How DMA uses the bus

ProblemWhat it isHow it is solved
cache coherenceDMA writes to memory while the CPU looks at the cacheinvalidate before reading, flush before sending. Or an uncached region
kinds of addressthe address the device sees may differ from the one the CPU seesthe driver converts to a “device address” and passes that
security and isolationa device can read and write any memoryan IOMMU limits what each device can see

Table 115.27 — Three things that catch you with DMA

★ The first row joins exactly on to the volatile story in the appendix on C without an operating system. volatile restrains the compiler and does nothing to the cache. Different layers need different remedies.

Counter-example. putting a DMA buffer on the stack

Hand a local array on the stack to DMA and two problems compound. If the function returns first, that space becomes space something else is using (chapter 45), and because that space is cached, what the device wrote and what the CPU sees can diverge. A DMA buffer goes in long-lived space, correctly aligned, allocated the way the driver says.

The three compared — polling, interrupts and DMA#

The demonstration below works out how much of the CPU polling, interrupts and DMA each take; that arithmetic amounts to this appendix’s conclusion. Polling means the CPU repeatedly asking a device whether there is work.

examples-en/apx-links/link_budget.c (excerpt)

static void budget(struct link L, double dma_block)
{
    double irq_rate  = L.bytes_per_sec;                  /* an interrupt per byte */
    double irq_busy  = irq_rate * IRQ_COST_US / 1e6 * 100.0;
    double dma_rate  = L.bytes_per_sec / dma_block;      /* once per block */
    double dma_busy  = dma_rate * IRQ_COST_US / 1e6 * 100.0;

    char a[24], b[24];
    snprintf(a, sizeof a, "%.1f%%", irq_busy);
    snprintf(b, sizeof b, "%.3f%%", dma_busy);
    printf("  %-22s %12.0f %10s %12.0f %10s\n", L.name, irq_rate, a, dma_rate, b);
}

Output

== assumptions (the values used) ==
  handling one interrupt    : 1.0 microseconds
  one poll (read and judge) : 0.2 microseconds
  DMA batch size            : 256 bytes
  * all three differ by machine. Change them and the conclusion changes --- that is the point.

== an interrupt per byte vs DMA in 256-byte batches ==
  link                          IRQ/s        CPU    DMA IRQ/s        CPU
  UART 9600 8N1                   960       0.1%            4     0.000%
  UART 115200 8N1               11520       1.2%           45     0.005%
  I2C 400 kHz                   44000       4.4%          172     0.017%
  SPI 10 MHz                  1250000     125.0%         4883     0.488%
  USB 2.0 (effective)        30000000    3000.0%       117188    11.719%
  1 Gbit Ethernet           118000000   11800.0%       460938    46.094%

  * reading the table downwards shows when DMA becomes necessary.
    on a slow link an interrupt per byte is no trouble; past some point
    that scheme leaves the machine doing nothing else.

== where the crossover is (assuming an interrupt per byte) ==
  CPU used       events/s         the data rate that means
  5%             50000            48.8 KiB/s
  10%            100000           97.7 KiB/s
  20%            200000           195.3 KiB/s
  40%            400000           390.6 KiB/s
  80%            800000           781.2 KiB/s

== the three schemes compared ==
  polling   : the CPU keeps asking.      latency = about half the period.
              suits very frequent events, or very simple places.
  interrupt : it wakes only when there is work. latency = the cost of waking.
              suits events that come now and then.
  DMA       : it wakes only when a batch ends. latency = until the batch fills.
              suits data that is large and steady.

== there are places where polling is better ==
  poll rate        CPU used       mean latency
  1000        /s 0.02%          500.0 microseconds
  10000       /s 0.20%          50.0 microseconds
  100000      /s 2.00%          5.0 microseconds
  1000000     /s 20.00%         0.5 microseconds
  * when events are very frequent polling wins --- there is always work, so
    there is no reason to pay the cost of waking each time. That is why fast
    network drivers switch to polling under load (Linux NAPI).

The demonstration’s arithmetic gives three rules for choosing among the schemes.

First, there is no “good scheme”, only “the scheme that fits this speed”. A 9600 baud UART is only 960 bytes per second, so even an interrupt per byte takes 0.1% of the CPU. Use the same scheme on 1 Gbit Ethernet and the CPU is 118 times short.

Second, DMA does not remove “the moving”; it reduces “how often you are interrupted”. Batch 256 bytes and the events drop to a 256th. In exchange you must wait for the batch to fill, so latency grows — which is why data with a short deadline gets small batches.

Third, when events are very frequent, polling wins. There is always work anyway, so there is no reason to pay the cost of waking each time. Linux network drivers turning interrupts off under load and switching to polling (NAPI) is exactly that. The three are not a ladder but places to move between as the load changes.

pollinginterruptsDMA
what the CPU doeskeeps askingwakes only when there is workonly starts and finishes
latencyabout half the polling periodthe cost of wakinguntil the batch fills
when events are rarewastefulrightexcessive
when events are frequentrightthe machine does nothing elseright
when the data is largeevery byte goes through the CPUlikewiseright — it does not go through the CPU

Table 115.28 — Polling, interrupts and DMA at a glance

What to take from this#

Recap

  • Four questions settle a connection — how many wires, a clock, addressing, who starts.
  • The faster it goes the more parallel loses. Today’s answer is one very fast serial line, bundled several times over.
  • A UART cuts characters out with an agreed shape and no clock. The price is a baud error budget, and the theoretical limit (about 5%) and the design rule (2 to 3%) are different numbers.
  • I2C carries many devices on two wires — a design that only ever pulls down gets ACK and arbitration for free.
  • SPI is a ring of two shift registers. So full duplex is free, and a mode mismatch shifts every value by one place.
  • PCI’s idea is that every device carries a business card of the same shape. Even the size is discovered by “writing ones and reading back”.
  • USB has the host command everything. Its “interrupt transfer” is not an interrupt but a periodic question.
  • Polling, interrupts and DMA are not a ladder but three places chosen by load. The choosing is arithmetic, not taste — cost per event × events per second.