An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
214 lines
5.6 KiB
C
214 lines
5.6 KiB
C
// Hardware interrupt registration. See include/irq.h for why this exists.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "traps.h"
|
|
#include "spinlock.h"
|
|
#include "irq.h"
|
|
|
|
struct irq_action {
|
|
irq_handler_t fn;
|
|
void *arg;
|
|
const char *name;
|
|
uint64 count;
|
|
};
|
|
|
|
static struct {
|
|
struct irq_action act[NCHAIN];
|
|
int n; // handlers installed; only ever grows during boot
|
|
int enabled;
|
|
int level_low;
|
|
uint64 unhandled;
|
|
} line[NIRQ];
|
|
|
|
// Guards registration against itself. The *dispatch* path deliberately does
|
|
// not take it: an interrupt handler that blocked on a lock held by whoever
|
|
// was registering would deadlock the CPU it fired on. Instead registration
|
|
// publishes an entry by writing all of its fields, issuing a barrier, and
|
|
// only then incrementing n -- so a concurrent dispatcher either does not see
|
|
// the entry at all or sees it complete.
|
|
//
|
|
// Removal is the harder direction, because compacting the array *does* move
|
|
// entries a dispatcher may be walking. irq_unregister therefore masks the
|
|
// line first and only re-routes it once the chain has been rewritten: with
|
|
// the line masked no new interrupt can arrive, and any handler already
|
|
// running holds no reference past its own return.
|
|
static struct spinlock irqlock;
|
|
|
|
void
|
|
irq_init(void)
|
|
{
|
|
initlock(&irqlock, "irq");
|
|
}
|
|
|
|
// IRQ_ERROR and IRQ_SPURIOUS are not IOAPIC pins -- they are local APIC
|
|
// vectors that xv6 numbered as though they were, so T_IRQ0+19 and T_IRQ0+31
|
|
// are already spoken for. A PCI device routed to pin 19 would deliver on the
|
|
// same vector as the LAPIC error interrupt. Refuse it here rather than let it
|
|
// be debugged later.
|
|
static int
|
|
reserved(int irq)
|
|
{
|
|
return irq == IRQ_ERROR || irq == IRQ_SPURIOUS || irq == IRQ_TIMER;
|
|
}
|
|
|
|
int
|
|
irq_register(int irq, irq_handler_t fn, void *arg, const char *name,
|
|
int level_low)
|
|
{
|
|
struct irq_action *a;
|
|
|
|
if(irq < 0 || irq >= NIRQ || fn == 0)
|
|
return -1;
|
|
if(reserved(irq)){
|
|
cprintf("irq_register: %s wants irq %d, which is reserved\n", name, irq);
|
|
return -1;
|
|
}
|
|
|
|
acquire(&irqlock);
|
|
|
|
if(line[irq].n >= NCHAIN){
|
|
release(&irqlock);
|
|
cprintf("irq_register: irq %d chain full, %s not attached\n", irq, name);
|
|
return -1;
|
|
}
|
|
|
|
// Two devices sharing a line must agree on how it is signalled; they
|
|
// physically cannot be wired both ways.
|
|
if(line[irq].n > 0 && line[irq].level_low != level_low){
|
|
release(&irqlock);
|
|
cprintf("irq_register: irq %d shared with conflicting trigger mode "
|
|
"(%s)\n", irq, name);
|
|
return -1;
|
|
}
|
|
|
|
a = &line[irq].act[line[irq].n];
|
|
a->fn = fn;
|
|
a->arg = arg;
|
|
a->name = name;
|
|
a->count = 0;
|
|
__sync_synchronize(); // publish the entry before it is reachable
|
|
line[irq].n++;
|
|
|
|
if(!line[irq].enabled){
|
|
line[irq].level_low = level_low;
|
|
ioapicroute(irq, 0, level_low);
|
|
line[irq].enabled = 1;
|
|
}
|
|
|
|
release(&irqlock);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
irq_unregister(int irq, irq_handler_t fn, void *arg)
|
|
{
|
|
int i, j;
|
|
|
|
if(irq < 0 || irq >= NIRQ)
|
|
return;
|
|
|
|
acquire(&irqlock);
|
|
for(i = 0; i < line[irq].n; i++){
|
|
if(line[irq].act[i].fn == fn && line[irq].act[i].arg == arg){
|
|
// Mask before touching the chain, always -- not only when this is the
|
|
// last handler. Compacting the array moves entries that a dispatcher on
|
|
// another CPU may be walking, so the line has to be quiet for the
|
|
// duration, and then brought back only if anything is left on it.
|
|
ioapicmask(irq);
|
|
line[irq].enabled = 0;
|
|
|
|
for(j = i; j + 1 < line[irq].n; j++)
|
|
line[irq].act[j] = line[irq].act[j+1];
|
|
line[irq].n--;
|
|
__sync_synchronize();
|
|
|
|
if(line[irq].n > 0){
|
|
ioapicroute(irq, 0, line[irq].level_low);
|
|
line[irq].enabled = 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
release(&irqlock);
|
|
}
|
|
|
|
void
|
|
irq_dispatch(int irq)
|
|
{
|
|
int i, n;
|
|
|
|
if(irq < 0 || irq >= NIRQ)
|
|
return;
|
|
|
|
n = line[irq].n;
|
|
if(n == 0){
|
|
line[irq].unhandled++;
|
|
return;
|
|
}
|
|
|
|
// Every handler on a shared line is offered the interrupt; each one is
|
|
// responsible for recognising that its device is idle and returning.
|
|
for(i = 0; i < n; i++){
|
|
line[irq].act[i].count++;
|
|
line[irq].act[i].fn(line[irq].act[i].arg);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// /proc/interrupts
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static int
|
|
emit(char *buf, int cap, int off, const char *s)
|
|
{
|
|
while(*s && off < cap)
|
|
buf[off++] = *s++;
|
|
return off;
|
|
}
|
|
|
|
static int
|
|
emit_dec(char *buf, int cap, int off, uint64 v, int width)
|
|
{
|
|
char tmp[24];
|
|
int n = 0, i;
|
|
|
|
if(v == 0)
|
|
tmp[n++] = '0';
|
|
while(v > 0){
|
|
tmp[n++] = '0' + (v % 10);
|
|
v /= 10;
|
|
}
|
|
for(i = n; i < width && off < cap; i++)
|
|
buf[off++] = ' ';
|
|
while(n > 0 && off < cap)
|
|
buf[off++] = tmp[--n];
|
|
return off;
|
|
}
|
|
|
|
int
|
|
irq_info(char *buf, int cap)
|
|
{
|
|
int irq, i, off = 0;
|
|
|
|
for(irq = 0; irq < NIRQ; irq++){
|
|
if(line[irq].n == 0 && line[irq].unhandled == 0)
|
|
continue;
|
|
off = emit_dec(buf, cap, off, irq, 4);
|
|
off = emit(buf, cap, off, ": ");
|
|
for(i = 0; i < line[irq].n; i++){
|
|
off = emit_dec(buf, cap, off, line[irq].act[i].count, 10);
|
|
off = emit(buf, cap, off, " ");
|
|
off = emit(buf, cap, off,
|
|
line[irq].act[i].name ? line[irq].act[i].name : "?");
|
|
off = emit(buf, cap, off, i + 1 < line[irq].n ? ", " : "");
|
|
}
|
|
if(line[irq].n == 0)
|
|
off = emit(buf, cap, off, "(unhandled)");
|
|
if(line[irq].unhandled)
|
|
off = emit(buf, cap, off, " [spurious]");
|
|
off = emit(buf, cap, off, line[irq].level_low ? " level\n" : " edge\n");
|
|
}
|
|
return off;
|
|
}
|