本系列參考: 學(xué)習(xí)開發(fā)一個(gè)RISC-V上的操作系統(tǒng) - 汪辰 - 2021春 整理而來,主要作為xv6操作系統(tǒng)學(xué)習(xí)的一個(gè)前置基礎(chǔ)。
RVOS是本課程基于RISC-V搭建的簡易操作系統(tǒng)名稱。
課程代碼和環(huán)境搭建教程參考github倉庫: https://github.com/plctlab/riscv-operating-system-mooc/blob/main/howto-run-with-ubuntu1804_zh.md
前置知識(shí):
- RVOS環(huán)境搭建-01
- RVOS操作系統(tǒng)內(nèi)存管理簡單實(shí)現(xiàn)-02
- RVOS操作系統(tǒng)協(xié)作式多任務(wù)切換實(shí)現(xiàn)-03
- RISC-V 學(xué)習(xí)篇之特權(quán)架構(gòu)下的中斷異常處理
RISC-V 中斷(Interrupt)的分類
RISC-V Trap (中斷)處理中涉及的寄存器
寄存器 mie、mip
mstatus寄存器中的MIE位用于控制全局中斷是否開啟,而mie作為次級(jí)中斷控制寄存器,用于在全局中斷打開的情況下,控制某個(gè)具體的中斷類型是否開啟:
mip寄存器用于告訴我們是否發(fā)生了某類中斷。
中斷處理流程
-
中斷發(fā)生時(shí) Hart 自動(dòng)執(zhí)行如下狀態(tài)轉(zhuǎn)換
- 退出中斷 :編程調(diào)用 MRET 指令
PLIC 介紹
外部中斷(external interrupt )
我們的外設(shè)想要和CPU進(jìn)行通信,就需要通過中斷的方式進(jìn)行通信,那么考慮到外設(shè)的可插拔性,我們需要做好中斷信號(hào)的轉(zhuǎn)換和匯聚處理:
PLIC
中斷信號(hào)轉(zhuǎn)換和匯聚的工作由PLIC完成,也就是中斷平臺(tái)控制器:
PLIC 是 “Platform-Level Interrupt Controller” 的縮寫,它是一種用于處理中斷的硬件模塊,常用于處理器系統(tǒng)中。PLIC 負(fù)責(zé)管理和分發(fā)各種中斷信號(hào),并將它們傳遞給適當(dāng)?shù)奶幚砥骱诵幕蚱渌O(shè)備。
PLIC 的主要功能包括:
- 中斷管理:PLIC 能夠接收來自不同外設(shè)的中斷請(qǐng)求,并為每個(gè)中斷分配一個(gè)唯一的中斷號(hào)。
- 中斷優(yōu)先級(jí):PLIC 支持設(shè)置中斷優(yōu)先級(jí),以確保高優(yōu)先級(jí)的中斷能夠及時(shí)響應(yīng)。
- 中斷分發(fā):根據(jù)中斷的優(yōu)先級(jí)和目標(biāo)處理器核心的可用性,PLIC 將中斷請(qǐng)求分發(fā)給適當(dāng)?shù)奶幚砥骱诵摹?/li>
- 中斷確認(rèn)和清除:當(dāng)處理器核心接收到中斷時(shí),PLIC 負(fù)責(zé)確認(rèn)中斷并在中斷處理完成后清除中斷狀態(tài)。
PLIC 在大型多核處理器系統(tǒng)中特別有用,因?yàn)樗梢詤f(xié)調(diào)多個(gè)處理器核心之間的中斷處理。每個(gè)處理器核心可以向 PLIC 注冊(cè)其中斷處理程序,并通過 PLIC 獲取適當(dāng)?shù)闹袛唷?/p>
PLIC 的具體實(shí)現(xiàn)和配置取決于具體的處理器架構(gòu)和系統(tǒng)設(shè)計(jì)。在 RISC-V 架構(gòu)中,PLIC 是標(biāo)準(zhǔn)的中斷控制器,用于處理中斷請(qǐng)求和分發(fā)。在某些 SOC 中,例如 FU540-C000,PLIC 是其中的一部分,用于管理系統(tǒng)中的中斷。
PLIC Interrupt Source
PLIC(Platform-Level Interrupt Controller)中的中斷源是指可以觸發(fā)中斷請(qǐng)求的硬件設(shè)備或其他事件。每個(gè)中斷源都有一個(gè)唯一的標(biāo)識(shí)符或中斷號(hào),用于在 PLIC 中進(jìn)行識(shí)別和管理。
PLIC 中的中斷源可以是各種外設(shè)或模塊,例如:
- 定時(shí)器:定時(shí)器可以生成周期性的中斷請(qǐng)求,用于實(shí)現(xiàn)定時(shí)功能。
- 外部設(shè)備:外部設(shè)備(如串口、網(wǎng)絡(luò)控制器、GPIO 等)可以觸發(fā)中斷請(qǐng)求,通知處理器有數(shù)據(jù)可用或事件發(fā)生。
- 總線錯(cuò)誤:當(dāng)在總線上發(fā)生錯(cuò)誤時(shí),例如內(nèi)存訪問錯(cuò)誤或設(shè)備通信錯(cuò)誤,可以生成中斷請(qǐng)求來通知系統(tǒng)。
- 異常和故障:處理器內(nèi)部的異?;蚬收蠗l件,例如除以零、內(nèi)存訪問異常等,也可以作為中斷源。
在 PLIC 中,每個(gè)中斷源都被分配一個(gè)唯一的中斷號(hào),這些中斷號(hào)用于識(shí)別和區(qū)分不同的中斷源。當(dāng)某個(gè)中斷源產(chǎn)生中斷請(qǐng)求時(shí),PLIC 根據(jù)中斷號(hào)和優(yōu)先級(jí)確定中斷的處理順序,并將中斷請(qǐng)求發(fā)送給適當(dāng)?shù)奶幚砥骱诵摹?/p>
具體的 PLIC 中斷源數(shù)量和配置取決于處理器架構(gòu)和 SOC 設(shè)計(jì)。在 FU540-C000 等特定 SOC 中,具體的中斷源和中斷號(hào)分配可能會(huì)有所不同。
上圖顯示的串口設(shè)備的中斷源為10
中斷源–>中斷號(hào)—>中斷向量–>中斷服務(wù)程序(ISR)–>中斷返回
PLIC 編程接口 - 寄存器
- 優(yōu)先級(jí)寄存器的作用就是根據(jù)其設(shè)置的優(yōu)先級(jí)級(jí)別,確定中斷的處理順序。通過設(shè)置不同中斷源的優(yōu)先級(jí),可以在處理多個(gè)中斷時(shí),確保高優(yōu)先級(jí)的中斷得到及時(shí)處理,提高系統(tǒng)的響應(yīng)性能。
- 舉一個(gè)簡單的例子,假設(shè)系統(tǒng)有兩個(gè)中斷源:定時(shí)器中斷和串口中斷。定時(shí)器中斷的處理優(yōu)先級(jí)設(shè)置為高,串口中斷的處理優(yōu)先級(jí)設(shè)置為低。
- 如果定時(shí)器中斷和串口中斷同時(shí)發(fā)生,由于定時(shí)器中斷的優(yōu)先級(jí)高于串口中斷,系統(tǒng)會(huì)首先處理定時(shí)器中斷。
在PLIC(Platform-Level Interrupt Controller)中,"claim"寄存器和"complete"寄存器是用于處理中斷請(qǐng)求和中斷完成的寄存器。
-
Claim寄存器(claim register):用于處理中斷請(qǐng)求。每個(gè)處理器核心在PLIC中有一個(gè)claim寄存器。當(dāng)處理器核心準(zhǔn)備處理中斷時(shí),它會(huì)讀取claim寄存器,以獲取待處理的中斷源(interrupt source)。當(dāng)處理器核心讀取claim寄存器時(shí),PLIC會(huì)將最高優(yōu)先級(jí)的未處理中斷源的標(biāo)識(shí)位設(shè)置為1,并將中斷源的ID(interrupt ID)寫入claim寄存器。這樣,處理器核心就可以知道要處理的中斷源是哪一個(gè),并將其從PLIC中“claim”(申請(qǐng))出來。
-
Complete寄存器(complete register):用于處理中斷完成。每個(gè)處理器核心在PLIC中有一個(gè)complete寄存器。當(dāng)一個(gè)處理器核心完成對(duì)某個(gè)中斷源的處理后,它會(huì)將中斷源的ID寫入complete寄存器,PLIC會(huì)更新中斷的狀態(tài),并將該中斷標(biāo)記為已完成。這意味著該中斷不再是待處理狀態(tài),而是已經(jīng)處理完畢。然后,PLIC會(huì)繼續(xù)檢查是否有其他中斷源處于就緒狀態(tài),并將就緒的中斷源寫入到相應(yīng)的可認(rèn)領(lǐng)寄存器中,以通知處理器核心有新的中斷可供處理。
這樣,其他處理器核心可以通過讀取認(rèn)領(lǐng)寄存器來獲取待處理的中斷,并開始處理這些中斷。當(dāng)一個(gè)處理器核心正在處理一個(gè)中斷時(shí),其他處理器核心可以認(rèn)領(lǐng)并處理其他已經(jīng)就緒的中斷,從而實(shí)現(xiàn)并行處理多個(gè)中斷。
因此,PLIC在將中斷標(biāo)記為已完成后,會(huì)繼續(xù)處理其他已經(jīng)就緒的中斷,并允許其他處理器核心去處理這些中斷。這樣可以提高系統(tǒng)的并發(fā)性和響應(yīng)性。
操作流程
大家可以對(duì)照下圖,看看各個(gè)寄存器在PILC電路圖中的位置,以及其作用域范圍:
采用中斷方式從 UART 實(shí)現(xiàn)輸入
實(shí)現(xiàn)思路:
代碼實(shí)現(xiàn):
- 初始化plic設(shè)備
void plic_init(void)
{
//獲取hartId
int hart = r_tp();
/*
* Set priority for UART0.
*
* Each PLIC interrupt source can be assigned a priority by writing
* to its 32-bit memory-mapped priority register.
* The QEMU-virt (the same as FU540-C000) supports 7 levels of priority.
* A priority value of 0 is reserved to mean "never interrupt" and
* effectively disables the interrupt.
* Priority 1 is the lowest active priority, and priority 7 is the highest.
* Ties between global interrupts of the same priority are broken by
* the Interrupt ID; interrupts with the lowest ID have the highest
* effective priority.
*/
//設(shè)置UART中斷源的優(yōu)先級(jí)
*(uint32_t*)PLIC_PRIORITY(UART0_IRQ) = 1;
/*
* Enable UART0
*
* Each global interrupt can be enabled by setting the corresponding
* bit in the enables registers.
*/
//設(shè)置當(dāng)前hart對(duì)應(yīng)的enable寄存器中UART位為1,即針對(duì)當(dāng)前hart開啟UART中斷源
*(uint32_t*)PLIC_MENABLE(hart)= (1 << UART0_IRQ);
/*
* Set priority threshold for UART0.
*
* PLIC will mask all interrupts of a priority less than or equal to threshold.
* Maximum threshold is 7.
* For example, a threshold value of zero permits all interrupts with
* non-zero priority, whereas a value of 7 masks all interrupts.
* Notice, the threshold is global for PLIC, not for each interrupt source.
*/
//針對(duì)當(dāng)前hart設(shè)置中斷源優(yōu)先級(jí)閾值為0
*(uint32_t*)PLIC_MTHRESHOLD(hart) = 0;
/* enable machine-mode external interrupts. */
//打開mie次級(jí)中斷控制器中外部中斷使能
w_mie(r_mie() | MIE_MEIE);
/* enable machine-mode global interrupts. */
//開啟全局中斷
w_mstatus(r_mstatus() | MSTATUS_MIE);
}
- 獲取待處理的最高優(yōu)先級(jí)中斷
/*
* DESCRIPTION:
* Query the PLIC what interrupt we should serve.
* Perform an interrupt claim by reading the claim register, which
* returns the ID of the highest-priority pending interrupt or zero if there
* is no pending interrupt.
* A successful claim also atomically clears the corresponding pending bit
* on the interrupt source.
* RETURN VALUE:
* the ID of the highest-priority pending interrupt or zero if there
* is no pending interrupt.
*/
int plic_claim(void)
{
//獲取當(dāng)前hart id
int hart = r_tp();
//返回待處理的中斷源ID
int irq = *(uint32_t*)PLIC_MCLAIM(hart);
return irq;
}
- 通過PLIC某個(gè)中斷處理完畢
/*
* DESCRIPTION:
* Writing the interrupt ID it received from the claim (irq) to the
* complete register would signal the PLIC we've served this IRQ.
* The PLIC does not check whether the completion ID is the same as the
* last claim ID for that target. If the completion ID does not match an
* interrupt source that is currently enabled for the target, the completion
* is silently ignored.
* RETURN VALUE: none
*/
void plic_complete(int irq)
{
int hart = r_tp();
//將處理完畢的中斷源id寫入complete寄存器
*(uint32_t*)PLIC_MCOMPLETE(hart) = irq;
}
- 在trap_handler中增加對(duì)外部中斷的處理—外部中斷的中斷號(hào)為11
reg_t trap_handler(reg_t epc, reg_t cause)
{
reg_t return_pc = epc;
reg_t cause_code = cause & 0xfff;
if (cause & 0x80000000) {
/* Asynchronous trap - interrupt */
switch (cause_code) {
case 3:
uart_puts("software interruption!\n");
break;
case 7:
uart_puts("timer interruption!\n");
break;
//處理外部中斷
case 11:
uart_puts("external interruption!\n");
external_interrupt_handler();
break;
default:
uart_puts("unknown async exception!\n");
break;
}
} else {
/* Synchronous trap - exception */
printf("Sync exceptions!, code = %d\n", cause_code);
panic("OOPS! What can I do!");
//return_pc += 4;
}
return return_pc;
}
- 外部中斷實(shí)際處理函數(shù)
void external_interrupt_handler()
{
//獲取中斷源ID
int irq = plic_claim();
//處理UART中斷源
if (irq == UART0_IRQ){
uart_isr();
} else if (irq) {
//其他中斷源不進(jìn)行處理
printf("unexpected interrupt irq = %d\n", irq);
}
//中斷源合法,告知PLIC中斷源處理完畢
if (irq) {
plic_complete(irq);
}
}
文章來源:http://www.zghlxwxcb.cn/news/detail-471584.html
- 在uart設(shè)備初始化邏輯中開啟接收中斷
- 將接收中斷使能位設(shè)置為 1,表示允許接收中斷的觸發(fā)。當(dāng)有數(shù)據(jù)到達(dá) UART 接收緩沖區(qū)時(shí),將觸發(fā)接收中斷請(qǐng)求,從而執(zhí)行相應(yīng)的中斷處理程序。
/*
* handle a uart interrupt, raised because input has arrived, called from trap.c.
*/
void uart_isr(void)
{
while (1) {
//獲取uart接收到字符
int c = uart_getc();
//將字符寫出
if (c == -1) {
break;
} else {
uart_puts("uart revice word: ");
uart_putc((char)c);
uart_putc('\n');
}
}
}
測(cè)試
void start_kernel(void)
{
uart_init();
uart_puts("Hello, RVOS!\n");
page_init();
trap_init();
//新增plic模塊初始化
plic_init();
sched_init();
os_main();
schedule();
uart_puts("Would not go here!\n");
while (1) {}; // stop here!
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-471584.html
到了這里,關(guān)于從零手寫操作系統(tǒng)之RVOS外設(shè)中斷實(shí)現(xiàn)-04的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!