Compatible cooperative multitasking – do a task_yield and actively give up the CPU.
The lowest level of the MSIP register of each HART is actually the MISP bit mapped to the MIP register, indicating whether soft interrupt occurs
void task_yield(a)
{
/* trigger a machine-level software interrupt */
intid = r_mhartid(); * (uint32_t*)CLINT_MSIP(id) = 1;
}
Copy the code
When we write 1 to the MSIP of this HART, it means that there is a soft interrupt to be processed. The specific process is shown in the figure below:
In this case, switch_to is essentially executed.
trap_vector:
# save context(registers).
csrrw t6, mscratch, t6 # swap t6 and mscratch
reg_save t6
csrw mscratch, t6
# save mepc to context of current task
csrr a0, mepc
sw a0, 124(t6)
# call the C trap handler in trap.c
csrr a0, mepc
csrr a1, mcause
call trap_handler
# trap_handler will return the return address via a0.
csrw mepc, a0
# restore context(registers).
csrr t6, mscratch
reg_restore t6
# return to whatever we were doing before trap.
mret
Copy the code
-
The value of mscrach is the address of the context, and reg_save t6 is called to save the value of the generic register into the context.
.macro reg_save base
sw ra, 0(\base)
sw sp, 4(\base)
sw gp, 8(\base)
sw tp, 12(\base)
sw t0, 16(\base)
sw t1, 20(\base)
sw t2, 24(\base)
sw s0, 28(\base)
sw s1, 32(\base)
sw a0, 36(\base)
sw a1, 40(\base)
sw a2, 44(\base)
sw a3, 48(\base)
sw a4, 52(\base)
sw a5, 56(\base)
sw a6, 60(\base)
sw a7, 64(\base)
sw s2, 68(\base)
sw s3, 72(\base)
sw s4, 76(\base)
sw s5, 80(\base)
sw s6, 84(\base)
sw s7, 88(\base)
sw s8, 92(\base)
sw s9, 96(\base)
sw s10, 100(\base)
sw s11, 104(\base)
sw t3, 108(\base)
sw t4, 112(\base)
sw t5, 116(\base)
sw t6, 120(\base)
.endm
Copy the code
Store is to store the data in memory to registers, so here we are writing the value of the universal register to memory.
-
Save the value of mePC to the PC of the context
struct context { /* ignore x0 */ reg_t ra; . Omit... reg_t t6; // upon is trap frame // save the pc to run in next schedule cycle reg_t pc; // offset: 31 *4 = 124 };Copy the code
Make parameter 1: A0 = mePC, parameter 2: a1= McAuse, and then call trap_handler to handle interrupts and exceptions
reg_t trap_handler(reg_t epc, reg_t cause)
{
reg_t return_pc = epc; // Store the return address. This function returns the PC
reg_t cause_code = cause & 0xfff; // The cause of the interruption
if (cause & 0x80000000) { // The highest bit is 1, indicating that this is an interrupt
/* Asynchronous trap - interrupt */
switch (cause_code) {
case 3: // Software is interrupted
uart_puts("software interruption! \n");
/* * acknowledge the software interrupt by clearing * the MSIP bit in mip. */
intid = r_mhartid(); * (uint32_t*)CLINT_MSIP(id) = 0;
schedule();
break;
case 7: // The timer is interrupted
uart_puts("timer interruption! \n");
timer_handler();
break;
case 11: / / uart interrupt
uart_puts("external interruption! \n");
external_interrupt_handler();
break;
default: // Other interrupts
uart_puts("unknown async exception! \n");
break; }}else { / / exception
/* Synchronous trap - exception */
printf("Sync exceptions! , code = %d\n", cause_code);
panic("OOPS! What can I do!");
//return_pc += 4;
}
return return_pc;
}
Copy the code
If it is a timer interrupt, the timer_handler function is executed:
void timer_handler(a)
{
_tick++;
printf("tick: %d\n", _tick);
timer_load(TIMER_INTERVAL);
schedule();
}
Copy the code
Essentially, schedule is executed.