Jack Li's Blog

Configure miniUART to print characters to terminal

From rpi3 docs, IO physical address started from 0x3F000000.

Physical addresses range from 0x3F000000 to 0x3FFFFFFF for peripherals. The bus 
addresses for peripherals are set up to map onto the peripheral bus address range
starting at 0x7E000000. Thus a peripheral advertised here at bus address 
0x7Ennnnnn is available at physical address 0x3Fnnnnnn.

And the docs said miniuart register virtual address is at:

AddressReg NameDescription
0x7E21 5000AUX_IRQAuxiliary Interrupt status
0x7E21 5004AUX_ENABLESAuxiliary enables
0x7E21 5040AUX_MU_IO_REGMini Uart I/O Data

So we define the registers we need for miniuart:

/* ARM aux peripheral physical address start */
#define BASE_ADDRESS 0x3f000000

#define AUX_IRQ           (volatile unsigned int*) (0x7E215000 - BASE_ADDRESS)
#define AUX_ENABLES       (volatile unsigned int*) (0x7E215004 - BASE_ADDRESS)
#define AUX_MU_IO_REG     (volatile unsigned int*) (0x7E215040 - BASE_ADDRESS)
#define AUX_MU_IER_REG    (volatile unsigned int*) (0x7E215044 - BASE_ADDRESS)
#define AUX_MU_IIR_REG    (volatile unsigned int*) (0x7E215048 - BASE_ADDRESS)
#define AUX_MU_LCR_REG    (volatile unsigned int*) (0x7E21504C - BASE_ADDRESS)
#define AUX_MU_MCR_REG    (volatile unsigned int*) (0x7E215050 - BASE_ADDRESS)
#define AUX_MU_LSR_REG    (volatile unsigned int*) (0x7E215054 - BASE_ADDRESS)
#define AUX_MU_MSR_REG    (volatile unsigned int*) (0x7E215058 - BASE_ADDRESS)
#define AUX_MU_SCRATCH    (volatile unsigned int*) (0x7E21505C - BASE_ADDRESS)
#define AUX_MU_CNTL_REG   (volatile unsigned int*) (0x7E215060 - BASE_ADDRESS)
#define AUX_MU_STAT_REG   (volatile unsigned int*) (0x7E215064 - BASE_ADDRESS)
#define AUX_MU_BAUD_REG   (volatile unsigned int*) (0x7E215068 - BASE_ADDRESS)

Initialize uart

void
uartinit(void)
{
  *AUX_ENABLES |= 1; /* Enable mini UART */

  *AUX_MU_CNTL_REG = 0;   /* Disable transmitter and receiver during configuration */
  *AUX_MU_LCR_REG = 3;    /* 8-bit mode */
  *AUX_MU_MCR_REG = 0;    /* Disable RTS line */
  *AUX_MU_IER_REG = 0;    /* Disable interrupts */
  *AUX_MU_IIR_REG = 6;    /* Clear receive/transmit FIFO */
  *AUX_MU_BAUD_REG = 270; /* Set baud rate to 115200 */

  *AUX_MU_CNTL_REG = 3;   /* Enable transmitter and receiver */
}

Then we write uartputc and uartgetc to interact with uart:

/**
 * Write to uart
 * 
 * Check AUX_MU_LSR_REG’s Transmitter empty field.
 * If set, write to AUX_MU_IO_REG
*/
void
uartputc(int c)
{
  while(!(*AUX_MU_LSR_REG & 0x20));

  *AUX_MU_IO_REG = c;
}

/**
 * Read from uart
 * 
 * Check AUX_MU_LSR_REG’s data ready field.
 * If set, read from AUX_MU_IO_REG
*/
int
uartgetc(void)
{
  while(!(*AUX_MU_LSR_REG & 0x01));

  int c = *AUX_MU_IO_REG;
  c = (c == '\r') ? '\n' : c;
  return c;
}

Add code to start.c

void
start()
{
    uartputc('j');
    uartputc('a');
    uartputc('c');
    uartputc('k');
}

Remember to compile uart.c:

SRCS = start.c uart.c

Add -serial null -serial stdio to qemu. Because default qemu will output stdin/stdout to uart0, we need to connect stdin/stdout to miniuart(uart1).

qemu-system-aarch64 -M raspi3b -kernel kernel.img -display none -serial null -serial stdio

Finally we can see the output on the terminal:

$ make qemu
qemu-system-aarch64 -M raspi3b -kernel kernel.img -display none -serial null -serial stdio
jack