The mystery of uart0

The FDT file for Raspberry Pi, declares UART.  I've had quite a bit of trouble understanding how declaring a uart in the FDT results in /dev/tty0 and /dev/cuau0 appearing in /dev/

Here are the relevant lines from the FDT:

uart0: uart0 {
  compatible = "broadcom,bcm2835-uart", "broadcom,bcm2708-uart", "arm,pl011", "arm,primecell";
  reg = <0x201000 0x1000>;
  interrupts = <65>;
  interrupt-parent = <&intc>;

  clock-frequency = <3000000>;	/* Set by VideoCore */
  reg-shift = <2>;
};

Eventually this uart device gets bound to the sio driver, and exposed as /dev/cuau0 and /dev/tty0.

From the FDT declaration I can see that:

  • There are some declarations of the devices that this uart driver is compatible with
  • It uses irq 65, and the parent interrupt controller is "intc" which is declared in the same FDT
  • There are some registers declared

It's also interesting to note that the uart device is not a child of the gpio device, it's a child of axi, the bus.

There is a clue, in the kernel conf for Pi, here; it declares a uart device and pl011

device uart
device pl011

The source tree for the uart devices is at /sys/dev/uart ad pl011 is at /sys/dev/uart/uart_dev_pl011.c.

Therefore:

  • The kernel conf file specifies that uart and pl011 need to be compiled into the kernel
  • The FDT file declares that there is a uart and that it's compatible with pl011.

The file /sys/dev/uart/uart_bus_fdt.c, is the uart bus driver for devices with a FDT . When the kernel boots, it will call "probe" on this device.  The code below probes the FDT (via Open Firmware) and will detect that a pl011 is compatible.

static int
uart_fdt_probe(device_t dev)
{
  struct uart_softc *sc;
  phandle_t node;
  pcell_t clock, shift;
  int err;

  sc = device_get_softc(dev);
  if (ofw_bus_is_compatible(dev, "ns16550"))
    sc->sc_class = &uart_ns8250_class;
  else if (ofw_bus_is_compatible(dev, "lpc,uart"))
    sc->sc_class = &uart_lpc_class;
  else if (ofw_bus_is_compatible(dev, "fsl,imx-uart"))
    sc->sc_class = &uart_imx_class;
  else if (ofw_bus_is_compatible(dev, "arm,pl011"))
    sc->sc_class = &uart_pl011_class;
  else if (ofw_bus_is_compatible(dev, "cadence,uart"))
    sc->sc_class = &uart_cdnc_class;
  else
    return (ENXIO);
  node = ofw_bus_get_node(dev);

  if ((err = uart_fdt_get_clock(node, &clock)) != 0)
    return (err);
  uart_fdt_get_shift(node, &shift);

  return (uart_bus_probe(dev, (int)shift, (int)clock, 0, 0));
}

So, the UART bus device knows to bind the device that was specified in the FDT, because it matches this string "arm, pl011".

So, the mystery is solved this way:

  • The kernel conf ensures that the uart and pl011 devices are compiled in
  • The FDT declares the uart and declares that its compatible with "arm, pl011" which is a string that pl011 recognizes
  • The hardware configuration that the pl011 needs, it reads via Open Firmware, which in turn reads from the FDT blob

If you look at the datasheet for the BCM2835, you will see that there is indeed a Primecell pl011 UART on the chip.   On page 177 of the datasheet there is this:

The PL011 USRT is mapped on base adderss 0x7E20100.

This address mapping of "0x7e20100" matches what was declared int the FDT above.

reg = <0x201000 0x1000>;

The reason for the difference between 0x7E20100 and 0X201000 is the I/O mapping on the ARM chip, described on page 6 of the datasheet.

Leave a Reply