鐵人賽
今天天氣有點涼爽,那就來說明 sbi_ecall 吧!
建議還沒觀看前些日子文章的朋友,觀看完才能更好理解今日文章唷~~
特權模式
CSR 介紹(一)
CSR 介紹(二)
CSR 使用用法與說明
我們可以透過 sbi_ecall 將權限升級到 m mode,接下來筆者將會介紹這段流程與使用方法,最後我們將介紹一個範例,讓大家更有感覺 ლ(╹◡╹ლ)。
先來介紹該如何使用 sbi_ecall 呢~來看一下他的參數。
透過前兩個參數EXT
以及 FID
,來表示要使用 opensbi 的何種服務 id,後面那幾個參數,表示所想要傳入的 data,如果沒有需要傳入的data,寫0即可。
可能會有人想問為什麼我們會需要用到sbi_ecall呢?難道不能在 s mode 完成嗎?
確實有些事情只能在 m mode 操作的,像某些 CSR 以及 cahce 之類的操作都需要在 m mode 才能使用,具體還有哪些項目,需要在 m mode 操作要依據 SPEC 所規範的。
根據下方那張圖,我們可以發現 開始執行 sbi_ecall 時,會透過a0 ~ a7
來傳所需要的參數,而 sbi_ecall結束時,會透過 a0
回傳 error 值,a1
回傳 output 的值。
struct sbiret sbi_ecall(int ext, int fid, unsigned long arg0,
unsigned long arg1, unsigned long arg2,
unsigned long arg3, unsigned long arg4,
unsigned long arg5)
{
struct sbiret ret;
register uintptr_t a0 asm ("a0") = (uintptr_t)(arg0);
register uintptr_t a1 asm ("a1") = (uintptr_t)(arg1);
register uintptr_t a2 asm ("a2") = (uintptr_t)(arg2);
register uintptr_t a3 asm ("a3") = (uintptr_t)(arg3);
register uintptr_t a4 asm ("a4") = (uintptr_t)(arg4);
register uintptr_t a5 asm ("a5") = (uintptr_t)(arg5);
register uintptr_t a6 asm ("a6") = (uintptr_t)(fid);
register uintptr_t a7 asm ("a7") = (uintptr_t)(ext);
asm volatile ("ecall"
: "+r" (a0), "+r" (a1)
: "r" (a2), "r" (a3), "r" (a4), "r" (a5), "r" (a6), "r" (a7)
: "memory");
ret.error = a0;
ret.value = a1;
return ret;
}
大概說明完用法了,那就說說從觸發 sbi_ecall 整個經過的流程吧~
從 sbi_ecall 開始,會觸發 trap_handler -> sbi_trap_handler -> sbi_ecall_hander 再透過 EXT number 決定該去哪個 handler 各自處理。
提醒:需自行下載 risc-v toolchian 並搭配 qemu 或是板子,建議直接購買 andes fpga 以及搭配 andes toolchain 更利於實驗唷,這邊只是簡介大致如何使用而已 ^^
以下使用 load module 的方式,好處是這樣就不用一直重編 kernel 了
下面是 Makefile
KERNELDIR := "<your kernel folder path>"
PWD :=$(shell pwd)
ARCH=riscv
CC=$(CROSS_COMPILE)gcc
LD=$(CROSS_COMPILE)ld
obj-m := example.o
obj-m := example.o
$(MAKE) -C $(KERNELDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules
clean:
rm *.o *.ko *.mod.c *.markers *.order *.symvers
下面是 example.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/irq.h>
#include <asm/sbi.h> /* 要用 sbi_ecall 必要的 header */
#include <linux/delay.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
... /* 簡略 */
sbi_ecall(SBI_EXT_ANDES,SBI_EXT_ANDES_WRITE_EVENT,0,0,0,0,0,0);
sbi_ecall(SBI_EXT_ANDES,SBI_EXT_ANDES_READ_COUNTER,0,0,0,0,0,0);
... /* 簡略 */
}
static void hello_exit(void)
{
printk(KERN_INFO "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
使用方式: 在該資料夾下 make 接著會產生出 example.ko
,將板子開機後使用 nfs 的方式,load module進去(也就是:$ insmod example.ko
),接著預期就會走hello_init的路線,此時可以將想使用的 sbi_ecall 加入其中觀察想實現的功能,以上述為例,sbi_ecall 會根據 ext number 進到各自不同的 handler 去處理,有哪些 EXT可用呢?可以根據下方不同的 ext number 做各自不同的 handler 處理,再根據 fid number 也就是上方的 SBI_EXT_ANDES_WRITE_EVENT,進一步處理。
Note:上述 function 為自行定義的,若想使用已經定義好的 function 可根據不同的 ext 及 fid 做使用。
enum sbi_ext_id { /* <linux folder>/arch/riscv/include/asm/sbi.h */
#ifdef CONFIG_RISCV_SBI_V01
SBI_EXT_0_1_SET_TIMER = 0x0,
SBI_EXT_0_1_CONSOLE_PUTCHAR = 0x1,
SBI_EXT_0_1_CONSOLE_GETCHAR = 0x2,
SBI_EXT_0_1_CLEAR_IPI = 0x3,
SBI_EXT_0_1_SEND_IPI = 0x4,
SBI_EXT_0_1_REMOTE_FENCE_I = 0x5,
SBI_EXT_0_1_REMOTE_SFENCE_VMA = 0x6,
SBI_EXT_0_1_REMOTE_SFENCE_VMA_ASID = 0x7,
SBI_EXT_0_1_SHUTDOWN = 0x8,
#endif
SBI_EXT_BASE = 0x10,
SBI_EXT_TIME = 0x54494D45,
SBI_EXT_IPI = 0x735049,
SBI_EXT_RFENCE = 0x52464E43,
SBI_EXT_HSM = 0x48534D,
SBI_EXT_PMU = 0x504D55,
SBI_EXT_ANDES = 0x0900031E,
};
今日介紹完該如何使用 sbi_ecall了,希望大家明白該如何使用sbi_ecall了,此外若想客製化自己的 EXT 以及 FID 須在相對應的地方做註冊,方可使用,以筆者使用的例子,便是 andes 自己的 EXT,若不太明白如何客製化自己的 EXT 那就改 FID 或許比較簡單呢 XD,因為筆者也沒客製化自己的 EXT,或許未來可以挑戰一下。
由於上方是使用客製的 EXT,因此再舉例 standard EXT 如何新增 FID 吧!
以 SBI_EXT_PMU 為例, 去 sbi_ecall_pmu_handler 新增 一 case 建立自行定義的 function 後,便能使用sbi_ecall(SBI_EXT_PMU, <剛剛建立的case number>,0,0,0,0,0) ,就差不多完成了~。
下圖可在 switch case 新增自己定義的 function,當然這樣想送 upstream 應該是不可能,不過玩玩還是可以的XD。
static int sbi_ecall_pmu_handler(unsigned long extid, unsigned long funcid
const struct sbi_trap_regs *regs,
unsigned long *out_val,
struct sbi_trap_info *out_trap)
{
int ret = 0;
uint64_t temp;
switch (funcid) {
case SBI_EXT_PMU_NUM_COUNTERS:
ret = sbi_pmu_num_ctr();
if (ret >= 0) {
*out_val = ret;
ret = 0;
}
break;
case SBI_EXT_PMU_COUNTER_GET_INFO:
ret = sbi_pmu_ctr_get_info(regs->a0, out_val);
break;
case SBI_EXT_PMU_COUNTER_CFG_MATCH:
#if __riscv_xlen == 32
temp = ((uint64_t)regs->a5 << 32) | regs->a4;
#else
temp = regs->a4;
#endif
ret = sbi_pmu_ctr_cfg_match(regs->a0, regs->a1, regs->a2,
regs->a3, temp);
if (ret >= 0) {
*out_val = ret;
ret = 0;
}
break;
case SBI_EXT_PMU_COUNTER_FW_READ:
ret = sbi_pmu_ctr_read(regs->a0, out_val);
break;
...
case 你自己的 case number:
ret = 你的function();
break;
明日我們來介紹 PLIC ,漸漸進入到我們正式的 AIA 主題,這幾天算是一些初步的基礎醞釀~就讓我們拭目以待吧