XV6 Device Driver

Why we have a Driver?

A driver is the code in an operating system that manages a particular device: 1. It tells the device hardware to perform operations,. 2. Configures the device to generate interrupts when done. 3. Handles the resulting interrupts,. 4. Interacts with processes that may be waiting for I/O from the device.

Initialization

Config Hardware

Xv6’s main calls consoleinit (kernel/console.c:189) to initialize the UART hardware, and to configure the UART hardware to generate input interrupts (kernel/uart.c:34).

void
consoleinit(void)
{
  initlock(&cons.lock, “cons”);

  uartinit();

  // connect read and write system calls
  // to consoleread and consolewrite.
  devsw[CONSOLE].read = consoleread;
  devsw[CONSOLE].write = consolewrite;
}

Create console device

In init.c, create a console device:

mknod(“console”, 1, 1);
open(“console”, O_RDWR);

The file path is console, type is T_DEVICE.

mknod implementation.

uint64
sys_mknod(void)
{
  struct inode *ip;
  char path[MAXPATH];
  int major, minor;

  begin_op(ROOTDEV);
  if((argstr(0, path, MAXPATH)) < 0 ||
     argint(1, &major) < 0 ||
     argint(2, &minor) < 0 ||
     (ip = create(path, T_DEVICE, major, minor)) == 0){
    end_op(ROOTDEV);
    return -1;
  }

At this point, we have a console device. A file descriptor 0 points to this device.

How to read or write from device

Let’s look at fileread

int
fileread(struct file *f, uint64 addr, int n)
{
  int r = 0;

  if(f->readable == 0)
    return -1;

  if(f->type == FD_PIPE){
    r = piperead(f->pipe, addr, n);
  } else if(f->type == FD_DEVICE){
    if(f->major < 0 || f->major >= NDEV || !devsw[f->major].read)
      return -1;
    r = devsw[f->major].read(f, 1, addr, n);

If the file type is DEVICE, we call the special read function to perform read operation. Note: The special read is set up in consoleinit.

consoleread sleeps and wait for a line of inputs, then copy to user space. Details see flow section.

Flow

Every input char generates an interrupt

When the user types a character, the UART hardware asks the RISC-V to raise an interrupt.

Trap handler determines interrupt source

xv6’s trap handling code calls devintr (kernel/trap.c:177). devintr looks at the RISC-V scause register to discover that the interrupt is from an external device. Then it asks a hardware unit called the PLIC [1] to tell it which device interrupted (kernel/trap.c:186). If it was the UART, devintrcalls uartintr.

Accumulate inputs and Wake up

Read one char and hand over

uartintr (kernel/uart.c:84) reads any waiting input characters from the UART hardware and hands them to consoleintr.

// trap.c calls here when the uart interrupts.
void
uartintr(void)
{
  while(1){
    int c = uartgetc();
    if(c == -1)
      break;
    consoleintr(c);
  }
}

Accumulate inputs

The job of consoleintr is to accumulate input characters in cons.buf until a whole line arrives.

Wake up when get a whole line

When a newline arrives, consoleintr wakes up a waiting consoleread (if there is one).

Copy to user space

Once woken, consoleread will observe a full line in cons.buf, copy it to user space and return to user space.

Question:

How does open(“console", O_RDWR) opens a file with DEVICE type?

Research: It seems this console string is defined somewhere. I tried to change to other string, and it does not work. Using this console string to open file, the file inode returns a file with type is DEVICE.

Answer:

OS create a console device using system call mknod. Any read or write with console is interacting with the UART hardware.

Last updated