#include "types.h" #include "param.h" #include "memlayout.h" #include "riscv.h" #include "spinlock.h" #include "proc.h" #include "defs.h" struct spinlock tickslock; uint ticks; extern char trampout[], trampin[]; // in kernelvec.S, calls kerneltrap(). void kernelvec(); extern int devintr(); void trapinit(void) { initlock(&tickslock, "time"); } // set up to take exceptions and traps while in the kernel. void trapinithart(void) { w_stvec((uint64)kernelvec); } // // handle an interrupt, exception, or system call from user space. // called from trampoline.S // void usertrap(void) { int which_dev = 0; if((r_sstatus() & SSTATUS_SPP) != 0) panic("usertrap: not from user mode"); // send interrupts and exceptions to kerneltrap(), // since we're now in the kernel. w_stvec((uint64)kernelvec); struct proc *p = myproc(); // save user program counter. p->tf->epc = r_sepc(); if(r_scause() == 8){ // system call if(p->killed) exit(); // sepc points to the ecall instruction, // but we want to return to the next instruction. p->tf->epc += 4; // an interrupt will change sstatus &c registers, // so don't enable until done with those registers. intr_on(); syscall(); } else if((which_dev = devintr()) != 0){ // ok } else { printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid); printf(" sepc=%p stval=%p\n", r_sepc(), r_stval()); p->killed = 1; } if(p->killed) exit(); // give up the CPU if this is a timer interrupt. if(which_dev == 2) yield(); usertrapret(); } // // return to user space // void usertrapret(void) { struct proc *p = myproc(); // turn off interrupts, since we're switching // now from kerneltrap() to usertrap(). intr_off(); // send interrupts and exceptions to trampoline.S w_stvec(TRAMPOLINE + (trampin - trampout)); // set up values that trampoline.S will need when // the process next re-enters the kernel. p->tf->kernel_satp = r_satp(); p->tf->kernel_sp = (uint64)p->kstack + PGSIZE; p->tf->kernel_trap = (uint64)usertrap; p->tf->kernel_hartid = r_tp(); // set up the registers that trampoline.S's sret will use // to get to user space. // set S Previous Privilege mode to User. unsigned long x = r_sstatus(); x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode x |= SSTATUS_SPIE; // enable interrupts in user mode w_sstatus(x); // set S Exception Program Counter to the saved user pc. w_sepc(p->tf->epc); // tell trampline.S the user page table to switch to. uint64 satp = MAKE_SATP(p->pagetable); // jump to trampoline.S at the top of memory, which // switches to the user page table, restores user registers, // and switches to user mode with sret. ((void (*)(uint64,uint64))TRAMPOLINE)(TRAMPOLINE - PGSIZE, satp); } // interrupts and exceptions from kernel code go here via kernelvec, // on whatever the current kernel stack is. // must be 4-byte aligned to fit in stvec. void kerneltrap() { int which_dev = 0; uint64 sepc = r_sepc(); uint64 sstatus = r_sstatus(); uint64 scause = r_scause(); if((sstatus & SSTATUS_SPP) == 0) panic("kerneltrap: not from supervisor mode"); if(intr_get() != 0) panic("kerneltrap: interrupts enabled"); if((which_dev = devintr()) == 0){ printf("scause %p\n", scause); printf("sepc=%p stval=%p\n", r_sepc(), r_stval()); panic("kerneltrap"); } // give up the CPU if this is a timer interrupt. if(which_dev == 2 && myproc() != 0 && myproc()->state == RUNNING) yield(); // the yield() may have caused some traps to occur, // so restore trap registers for use by kernelvec.S's sepc instruction. w_sepc(sepc); w_sstatus(sstatus); } void clockintr() { acquire(&tickslock); ticks++; wakeup(&ticks); release(&tickslock); } // check if it's an external interrupt or software interrupt, // and handle it. // returns 2 if timer interrupt, // 1 if other device, // 0 if not recognized. int devintr() { uint64 scause = r_scause(); if((scause & 0x8000000000000000L) && (scause & 0xff) == 9){ // supervisor external interrupt, via PLIC. int irq = plic_claim(); if(irq == UART0_IRQ){ uartintr(); } else if(irq == VIRTIO0_IRQ){ virtio_disk_intr(); } plic_complete(irq); return 1; } else if(scause == 0x8000000000000001){ // software interrupt from a machine-mode timer interrupt, // forwarded by machinevec in kernelvec.S. if(cpuid() == 0){ clockintr(); } // acknowledge the software interrupt by clearing // the SSIP bit in sip. w_sip(r_sip() & ~2); return 2; } else { return 0; } }