iT邦幫忙

2022 iThome 鐵人賽

DAY 5
1
Software Development

RISC-V: 深入淺出從入門到放棄系列 第 5

DAY5: RISC-V sbi_ecall 流程與用法

  • 分享至 

  • xImage
  •  
tags: 鐵人賽

0. 前言

今天天氣有點涼爽,那就來說明 sbi_ecall 吧!
建議還沒觀看前些日子文章的朋友,觀看完才能更好理解今日文章唷~~
特權模式
CSR 介紹(一)
CSR 介紹(二)
CSR 使用用法與說明

我們可以透過 sbi_ecall 將權限升級到 m mode,接下來筆者將會介紹這段流程與使用方法,最後我們將介紹一個範例,讓大家更有感覺 ლ(╹◡╹ლ)。

sbi_ecall 用法

先來介紹該如何使用 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 整個經過的流程吧~

從 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 主題,這幾天算是一些初步的基礎醞釀~就讓我們拭目以待吧


上一篇
DAY4: RISC-V: CSR指令用法
下一篇
DAY6: RISC-V: 三分鐘學會 PLIC 流程之外部中斷
系列文
RISC-V: 深入淺出從入門到放棄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言