6.S081 2020 Lecture 9: Interrupts Interrupts hardware wants attention now! e.g., pkt arrived, clock interrupt software must set aside current work and respond on RISC-V use same trap mechanism as for syscalls and exceptions new issues/complications: asynchronous interrupts running process interrupt handler may not run in context of process who caused interrupt concurrency devices and process run in parallel programming devices device can be difficult to program Where do device interrupts come from? diagram: Fig 1 of SiFive board in FU540-C000-v1.0.pdf CPUs, CLINT, PLIC, devices Fig 3 has more detail for interrupts Section 8 and 9 of FU540-C000-v1.0.pdf UART (universal asynchrnonous receiver/transmitter) See Section 13 of FU540-C000-v1.0.pdf Although the QEMU version is slightly different Follows http://byterunner.com/16550.html the interrupt tells the kernel the device hardware wants attention the driver (in the kernel) knows how to tell the device to do things often the interrupt handler calls the relevant driver but other arrangements are possible (schedule a thread; poll) [diagram: top-half/bottom-half] Case study: console output and keyboard input $ ls how does $ show up on console? printing to simulated console: driver puts characters into UART's send FIFO interrupt when character has been sent informs driver that it can send more how are the characters l and s read from keyboard (and echo to console)? reading from simulated keyboard user hits key, which returns in UART interrupt driver gets character from UART's receive FIFO how does kernel know which device interrupted? each device has a unique source/IRQ (interrupt request) number defined by hardware platform UART0 is 10 on qemu (see kernel/memlayout.h) different on SiFive board RISC-V interrupt-related registers sie --- supervisor interrupt enabled register one bit per software interrupt, external interrupt, timer interrupt sstatus --- supervisor status register one bit to enable interrupts sip --- supervisor interrupt pending register scause --- supervisor cause register stvec --- supervisor trap vector register mdeleg --- machine delegate register Let's look at how xv6 sets up the interrupt machinery start(): w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE); main(): consoleinit(); uartinit() plicinit(); scheduler(); intr_on(); w_sstatus(r_sstatus() | SSTATUS_SIE); Printing "$" shell is started with fd 0, 1, 2 for "console" setup by init Unix presents console device as a file! printf() putc() write(2, "$", 1) sys_write() filewrite() consolewrite() uartputc() add "$" to buffer uartstart() // kick off sending character put the character in UART fifo return to user space ... while at the same time UART is sending character to console shell calls sys_read() to wait for input (see below) UART completes sending character and raises interrupt PLIC passes interrupt on to a CPU the CPU performs the following steps on interrupt: - If the trap is a device interrupt, and the SIE bit is clear, don't do any of the following. - Disable interrupts by clearing SIE. Prevents interrupts being interrupted - Copy the pc to sepc - Save the current mode (user or supervisor) in the SPP bit in sstatus. - Set scause to reflect the interrupt's cause. - Set the mode to supervisor. - Copy stvec to the pc. stvec contains kernelvec or usertrap, depending if interrupt happened in user space or kernel space - Start executing at the new pc. same mechanism we have seen before systems calls, pgfaults, etc. both kernelvec/usertrap call devintr() to check for interrupts external interrupt for UART plic_claim() uartintr() if there are more characters send them may send multiple characters plic_complete() return from kernelvec/usertrap resumes interrupted computation What if several interrupts arrive? The PLIC distributes interrupts among cores Interrupts can be handled in parallel If no CPU claims the interrupt, the interrupt stays pending Eventually each interrupt is delivered to some CPU Interrupts expose several forms of concurrency 1. Between device and CPU Producer/consumer parallelism 2. Interrupt may interrupt the CPU that is returning to shell (still in kernel) Disable interrupts when code must be atomic 3. Interrupt may run on different CPU in parallel with shell (or returning to shell) Locks; topic for Wed Producer/consumer parallelism For printing shell is producer device is consumer To decouple the two: a buffer in the driver top-half puts chars into buffer wait if there is no room runs in the context of the calling process bottom half remove chars from buffer interrupt handler wakes up producers may not run the context of the shell Note: bottom half and top half may run in parallel on different CPUs We will get to this in a later lecture Interrupts interrupt running code Interrupts run between my code For example, my code is 1. addi sp,sp,-48 2. sd ra,40(sp) Q: Might other code run between 1 and 2? Yes! Interrupt may happen between 1 and 2 e.g., timer interrupt or uart interrupt For user code maybe not that bad Kernel will resume user code in in the same state For kernel code could be difficult Interrupt handler may update state that is observable by my code my code: interrupt: x = 0 if x = 0 then x = 1 f() f() may be executed or may not be executed! To make a block of code "atomic", turn off interrupts intr_off(): w_sstatus(r_sstatus() & ~SSTATUS_SIE); RISC-V turns of interrupt on a trap (interrupt/exception) Can kernel handle interrupt in trampoline.S? Our first glimps of "concurrency" We'll get back to this when discussing locking $ shell is in read system call to get input from console usertrap() for system call w_stvec((uint64)kernelvec); consoleread() sleep() scheduler() intr_on() $ l user hits l, which causes UART interrupt kernelvec: save space on current stack; which stack? save registers on the current stack in our example, the scheduler thread's stack kerneltrap() devintr() uartintr() c = uartgetc() consoleintr(c) handle ctrl characters echo character ('l') using uartput_sync() put c in buffer wakeup reader return from devintr() return from kerneltrap() load registers back sret Q: where does sret return too where ever the interrupt happened (in scheduler loop in this case) scheduler runs shell so that it can collect 'l' Producer/consumer parallelism For reading from keyboard opposite from printing shell is consumer device is producer Interrupt evolution Interrupts used to be relatively fast; now they are slow old approach: every event causes an interrupt, simple h/w, smart s/w new approach: h/w completes lots of work before interrupting Some devices generate events faster than one per microsecond e.g. gigabit ethernet can deliver 1.5 million small packets / second An interrupt takes on the order of a microsecond save/restore state cache misses what to do if interrupt comes in faster than 1 per microsecond? Polling: another way of interacting with devices Processor spins until device wants attention Wastes processor cycles if device is slow One example in xv6: uartputc_sync() But inexpensive if device is fast No saving of registers etc. If events are always waiting, no need to keep alerting the software Polling versus interrupts Polling rather than interrupting, for high-rate devices Interrupt for low-rate devices, e.g. keyboard constant polling would waste CPU Switch between polling and interrupting automatically interrupt when rate is low (and polling would waste CPU cycles) poll when rate is high (and interrupting would waste CPU cycles) Faster forwarding of interrupts to user space for page faults and user-handled devices h/w delivers directly to user, w/o kernel intervention? faster forwarding path through kernel? We will be seeing many of these topics later in the course