LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

OpenSBI背景介绍/编译/启动,及其和Linux交互

基于OpenSBI 1.2和Linux 5.10.110,分析RISC-V特有的OpenSBI及其和Linux Kernel的交互。 

1 OpenSBI介绍

1.1 RISC-V基础

RISC-V Core支持三种Mode:User Mode、Supervisor Mode、Machine Mode:
某一时刻HART仅运行在一种Mode。

1.2 什么是SBI?

SBI指的是RISC-V Supervisor Binary Interface。
SEE指的是Supervisor Execution Environment。
SBI是类系统调用的Supervisor和SEE之间的调用转换。
他们之间的关系如下图:

SBI的作用:
  • 提高不同OS之间的代码复用。
  • 提供不同平台共用的OS通用驱动。
  • 提供直接访问M模式下专有资源接口。

1.3 典型的RISC-V启动流程

典型的RISC-V启动流程如下,以Loader为spl、Bootloader为U-Boot、OS为Linux为例。
  1. 系统POR之后,从ROM开始启动。将SPL加载到SRAM中。跳转到SPL运行。
  2. SPL进行DDR初始化,并将OpenSBI和U-Boot加载到DDR中。跳转到OpenSBI中运行。
  3. OpenSBI从DDR中开始执行,进行相关设置。跳转到U-Boot中执行。
  4. U-Boot加载Linux到DDR中,进行解析、解压等操作。最后跳转到Linux中运行。
  5. 最后处于运行态的仅有OpenSBI和Linux,Linux通过sbi指令和OpenSBI进行交互。

1.4 OpenSBI固件组成

SBI Library:通用功能抽象。
Platform Specific Library:某些平台特有的功能。
固件:提供三种不同类型的Runtime固件,包括Payload、Dynamic、Jump。

1.5 OpenSBI三种形态

固件的三种形态:
FW_PAYLOAD:OpenSBI和下一级启动固件绑定在一起。
缺点:
  • 任何OpenSBI或者Bootloader改变,都需要重新编译。
  • 无法从前一bootloader传递参数到FW_PAYLOAD。
FW_JUMP:跳转到一个固定的地址执行下一个固件。
优点:对于QEMU需要使用预编译的FW_JUMP很有用,不需要传递参数。
缺点:
  • 前一bootload必须将下一boot镜像加载到固定地址。
  • 没有传递参数的机会。
FW_DYNAMIC:跳转的时候传递参数个下一个固件。
优点:下一Bootloader不需要记载到指定地点。
缺点:前一bootloader必须传递号struct fw_dynamic_info参数。

2 OpenSBI代码

2.1 OpenSBI代码结构

.
├── docs--帮助文档,通过make docs生成LaTeX、html、pdf格式。
├── firmware--OpenSBI启动汇编以及连接脚本等。
│   ├── external_deps.mk
│   ├── fw_base.ldS
│   ├── fw_base.S
│   ├── fw_dynamic.elf.ldS
│   ├── fw_dynamic.S
│   ├── fw_jump.elf.ldS
│   ├── fw_jump.S
│   ├── fw_payload.elf.ldS
│   ├── fw_payload.S
│   ├── Kconfig
│   ├── objects.mk
│   └── payloads
├── include--头文件。
├── lib
│   ├── sbi--输出平台无关的libsbi.a库文件。
│   └── utils--参与输出平台相关的libplatsbi.a文件。
├── Makefile
├── platform--不同平台差异部分。
│   ├── fpga
│   ├── generic
│   ├── kendryte
│   ├── nuclei
│   └── template
├── README.md
├── scripts
│   ├── carray.sh
│   ├── create-binary-archive.sh
│   ├── d2c.sh
│   └── Kconfiglib
└── ThirdPartyNotices.md

2.2 启动代码和连接脚本

在firmware目录下存在三种Firmware的启动代码和连接脚本:
firmware/
├── external_deps.mk
├── fw_base.ldS
├── fw_base.S
├── fw_dynamic.elf.ldS
├── fw_dynamic.S
├── fw_jump.elf.ldS
├── fw_jump.S
├── fw_payload.elf.ldS
├── fw_payload.S
├── Kconfig
├── objects.mk
└── payloads
    ├── objects.mk
    ├── test.elf.ldS
    ├── test_head.S
    └── test_main.c

2.2.1 fw_jump.elf.ld

FW_JUMP的链接脚本如下:
OUTPUT_ARCH(riscv)
ENTRY(_start)--固件的入口函数。
SECTIONS
{
 . = 0x80000000;
 PROVIDE(_fw_start = .);--镜像的起始地址,也是_start的地址。
 . = ALIGN(0x1000);
 .text :
  {
  PROVIDE(_text_start = .);
  *(.entry)
  *(.text)
  . = ALIGN(8);
  PROVIDE(_text_end = .);
 }
 . = ALIGN(0x1000);
 .rodata :
 {
  PROVIDE(_rodata_start = .);
  *(.rodata .rodata.*)
  . = ALIGN(8);
  PROVIDE(_rodata_end = .);
 }
 . = ALIGN(0x1000);
 .data :
 {
  PROVIDE(_data_start = .);
...
  PROVIDE(_data_end = .);
 }
 .dynsym : {
  PROVIDE(__dyn_sym_start = .);
  *(.dynsym)
  PROVIDE(__dyn_sym_end = .);
 }
 .rela.dyn : {
  PROVIDE(__rel_dyn_start = .);
  *(.rela*)
  . = ALIGN(8);
  PROVIDE(__rel_dyn_end = .);
 }
 . = ALIGN(0x1000);
 .bss :
 {
  PROVIDE(_bss_start = .);
...
  PROVIDE(_bss_end = .);
 }
 . = ALIGN(0x1000);
 PROVIDE(_fw_end = .);
 PROVIDE(_fw_reloc_end = .);
}

2.2.2 主要启动流程

整个镜像的入口是_fw_start(),进行了如下操作

  • 根据需要对镜像进行重定位。
  • 清空bss段。
  • 填充struct sbi_scratch结构体。
  • 设置Trap Handler。
  • 调用sbi_init()。
_fw_start
  _try_lottery--处理重定位。
    _relocate_done
      ->_reset_regs--清指令cache、清寄存器。
      ->_bss_zero--bss段清零。
      ->_scratch_init--初始化struct sbi_scratch。
      ->_fdt_reloc_done
      ->_start_warm
        ->设置CSR_MTVEC异常处理函数为_trap_handler。         ->sbi_platform_nascent_init--调用平台香瓜你的nascent_init()函数。
        ->sbi_init
          ->init_coldboot
            ->sbi_scratch_init
            ->sbi_domain_init
            ->sbi_hsm_init
            ->sbi_platform_early_init--调用平台相关的early_init()函数。
            ->sbi_hart_init
            ->sbi_console_init--初始化串口以及控制台。
            ->sbi_pmu_init
              ->sbi_platform_pmu_init
            ->sbi_boot_print_banner--打印OpenSBI字样横幅。
            ->sbi_irqchip_init
              ->sbi_platform_irqchip_init--调用平台相关的irqchip_init()函数。
            ->sbi_ipi_init
              ->sbi_platform_ipi_init--调用平台相关的ipi_init()函数。
            ->sbi_tlb_init
              ->sbi_ipi_event_create--增加TLB相关的操作函数集tlb_ops。
              ->sbi_platform_tlbr_flush_limit--调用平台相关的get_tlbr_flush_limit()函数。
            ->sbi_timer_init
              ->sbi_platform_timer_init--调用平台相关的timer_init()函数。
            ->sbi_ecall_init
              ->sbi_ecall_register_extension--注册sbi_ecall_exts指定范围ecall的处理函数。
            ->sbi_domain_finalize
              ->sbi_platform_domains_init--调用平台相关domains_init()函数。
              ->sbi_hsm_hart_start
            ->sbi_hart_pmp_configure--配置PMP内存保护生效。
            ->sbi_platform_final_init--调用平台相关的final_init()函数。
            ->sbi_boot_print_general
            ->sbi_boot_print_domains
            ->sbi_boot_print_hart
            ->wake_coldboot_harts
            ->sbi_scratch_offset_ptr
            ->sbi_hsm_prepare_next_jump
            ->sbi_hart_switch_mode--配置CSR_MSTATUS/CSR_MEPC/CSR_STVEC等寄存器,切换HART模式并通过mret跳转到下一个镜像启动。第一个参数为hardid,第二个参数指向FDT地址。
        ->_start_hang
  _wait_relocate_copy_done
    _wait_for_boot_hart
其中struct sbi_scratch是sbi_init()入参。
struct sbi_scratch {
    /** Start (or base) address of firmware linked to OpenSBI library */
    unsigned long fw_start;
    /** Size (in bytes) of firmware linked to OpenSBI library */
    unsigned long fw_size;
    /** Arg1 (or 'a1' register) of next booting stage for this HART */
    unsigned long next_arg1;--下一镜像的参数。
    /** Address of next booting stage for this HART */
    unsigned long next_addr;--下一镜像的启动地址。
    /** Privilege mode of next booting stage for this HART */
    unsigned long next_mode;--下一镜像运行模式。
    /** Warm boot entry point address for this HART */
    unsigned long warmboot_addr;
    /** Address of sbi_platform */
    unsigned long platform_addr;--指向具体平台的struct sbi_platform结构体。
    /** Address of HART ID to sbi_scratch conversion function */
    unsigned long hartid_to_scratch;
    /** Address of trap exit function */
    unsigned long trap_exit;
    /** Temporary storage */
    unsigned long tmp0;
    /** Options for OpenSBI library */
    unsigned long options;
};

2.3 平台无关

2.3.1 libsbi.a

libsbi.a的代码主要来源于lib/sbi。

2.3.1.1 sbi初始化

sbi_init()是C入口,入参为struct sbi_scratch。

2.3.1.2 Trap handler

在__start_warm()中将_trap_handler()设置给CSR_MTVEC。
_trap_handler()根据异常来分别进行处理:M-Mode调用_trap_handler_m_mode();S-Mode/U-Mode调用_trap_handler_s_mode()。无论是M-Mode还是S-Mode/U-Mode都会经过_trap_handler_all_mode()处理,主要是上下文保存,然后调用C函数sbi_trap_handler()进行处理,再恢复上下文,最后调用mret异常处理结束返回。
_trap_handler:
_trap_handler:
    TRAP_SAVE_AND_SETUP_SP_T0
    TRAP_SAVE_MEPC_MSTATUS 0--保存CSR_MEPC和CSR_MSTATUS寄存器。
    TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0--保存SP和T0之外的通用寄存器。
    TRAP_CALL_C_ROUTINE--调用sbi_trap_handler()函数。
_trap_exit: TRAP_RESTORE_GENERAL_REGS_EXCEPT_A0_T0 TRAP_RESTORE_MEPC_MSTATUS
0 TRAP_RESTORE_A0_T0 mret

MCAUSE是机器模式异常事件向量寄存器,用于保存触发异常的异常事件向量号,用于在异常服务程序中处理对应事件。最高位是中断标记位,为1是表示异常的来源是中断:

#define IRQ_S_SOFT            1
#define IRQ_VS_SOFT            2
#define IRQ_M_SOFT            3
#define IRQ_S_TIMER            5
#define IRQ_VS_TIMER            6
#define IRQ_M_TIMER            7
#define IRQ_S_EXT            9
#define IRQ_VS_EXT            10
#define IRQ_M_EXT            11
#define IRQ_S_GEXT            12
#define IRQ_PMU_OVF            13
为0时,表示异常来源不是中断,Exception Code按照异常解析。
#define CAUSE_MISALIGNED_FETCH        0x0
#define CAUSE_FETCH_ACCESS        0x1
#define CAUSE_ILLEGAL_INSTRUCTION    0x2
#define CAUSE_BREAKPOINT        0x3
#define CAUSE_MISALIGNED_LOAD        0x4
#define CAUSE_LOAD_ACCESS        0x5
#define CAUSE_MISALIGNED_STORE        0x6
#define CAUSE_STORE_ACCESS        0x7
#define CAUSE_USER_ECALL        0x8
#define CAUSE_SUPERVISOR_ECALL        0x9
#define CAUSE_VIRTUAL_SUPERVISOR_ECALL    0xa
#define CAUSE_MACHINE_ECALL        0xb
#define CAUSE_FETCH_PAGE_FAULT        0xc
#define CAUSE_LOAD_PAGE_FAULT        0xd
#define CAUSE_STORE_PAGE_FAULT        0xf
#define CAUSE_FETCH_GUEST_PAGE_FAULT    0x14
#define CAUSE_LOAD_GUEST_PAGE_FAULT    0x15
#define CAUSE_VIRTUAL_INST_FAULT    0x16
#define CAUSE_STORE_GUEST_PAGE_FAULT    0x17
sbi_trap_handler()是Trap处理的C入口函数,进行送到M-Mode的中断和异常处理。
sbi_trap_handler
  ->sbi_trap_aia_irq/sbi_trap_nonaia_irq--进入中断处理。
    ->sbi_timer_process
    ->sbi_ipi_process
    ->sbi_irqchip_process
  ->进入异常处理。
    ->sbi_illegal_insn_handler--非法指令处理。
    ->sbi_misaligned_load_handler--非对齐加载处理。
    ->sbi_misaligned_store_handler--非对齐存储处理。
    ->sbi_ecall_handler--处理M或S模式下ecall调用。
      ->sbi_ecall_find_extension--根据extension_id找到ext并调用handle()进行处理。
    ->sbi_pmu_ctr_incr_fw
    ->sbi_trap_redirect
sbi_ecall_find_extension()在ecall_exts_list上查找,ecall_exts_list成员在sbi_ecall_init()中插入。
struct sbi_ecall_extension *sbi_ecall_find_extension(unsigned long extid);--根据extid查找对应struct sbi_ecall_extension。
int sbi_ecall_register_extension(struct sbi_ecall_extension *ext);--注册一个struct sbi_ecall_extension。
void sbi_ecall_unregister_extension(struct sbi_ecall_extension *ext);--移除一个struct sbi_ecall_extension。
int sbi_ecall_handler(struct sbi_trap_regs *regs);--根据struct sbi_trap_regs->a7内容调用对应的extension handler。
int sbi_ecall_init(void);--将sbi_ecall_exts内容逐个进行注册。

SBI支持的Extension ID:

#define SBI_EXT_0_1_SET_TIMER            0x0
#define SBI_EXT_0_1_CONSOLE_PUTCHAR        0x1
#define SBI_EXT_0_1_CONSOLE_GETCHAR        0x2
#define SBI_EXT_0_1_CLEAR_IPI            0x3
#define SBI_EXT_0_1_SEND_IPI            0x4
#define SBI_EXT_0_1_REMOTE_FENCE_I        0x5
#define SBI_EXT_0_1_REMOTE_SFENCE_VMA        0x6
#define SBI_EXT_0_1_REMOTE_SFENCE_VMA_ASID    0x7
#define SBI_EXT_0_1_SHUTDOWN            0x8
#define SBI_EXT_BASE                0x10
#define SBI_EXT_TIME                0x54494D45
#define SBI_EXT_IPI                0x735049
#define SBI_EXT_RFENCE                0x52464E43
#define SBI_EXT_HSM                0x48534D
#define SBI_EXT_SRST                0x53525354
#define SBI_EXT_PMU                0x504D55

2.4 OpenSBI平台相关

libplatsbi.a = libsbi.a + struct sbi_platform
其中libsbi.a是平台独立部分代码,struct sbi_paltform是平台相关回调函数。

2.4.1 libplatsbi.a - struct sbi_platform

libplatsbi.a对应的文件为platform/generic/platform.c,以及一系列struct platform_override。
主要是实现了一个struct sbi_platform变量platform,如下:
const struct sbi_platform_operations platform_ops = {
    .nascent_init        = generic_nascent_init,
    .early_init        = generic_early_init,
    .final_init        = generic_final_init,
    .early_exit        = generic_early_exit,
    .final_exit        = generic_final_exit,
    .extensions_init    = generic_extensions_init,
    .domains_init        = generic_domains_init,
    .console_init        = generic_console_init,
    .irqchip_init        = fdt_irqchip_init,
    .irqchip_exit        = fdt_irqchip_exit,
    .ipi_init        = fdt_ipi_init,
    .ipi_exit        = fdt_ipi_exit,
    .pmu_init        = generic_pmu_init,
    .pmu_xlate_to_mhpmevent = generic_pmu_xlate_to_mhpmevent,
    .get_tlbr_flush_limit    = generic_tlbr_flush_limit,
    .timer_init        = fdt_timer_init,
    .timer_exit        = fdt_timer_exit,
    .vendor_ext_check    = generic_vendor_ext_check,
    .vendor_ext_provider    = generic_vendor_ext_provider,
};

struct sbi_platform platform = {
    .opensbi_version    = OPENSBI_VERSION,--OpenSBI软件版本号。
    .platform_version    =
        SBI_PLATFORM_VERSION(CONFIG_PLATFORM_GENERIC_MAJOR_VER,
                     CONFIG_PLATFORM_GENERIC_MINOR_VER),--厂家自定义平台版本号。
    .name            = CONFIG_PLATFORM_GENERIC_NAME,--平台名称。
    .features        = SBI_PLATFORM_DEFAULT_FEATURES,
    .hart_count        = SBI_HARTMASK_MAX_BITS,--HART总数。
    .hart_index2id        = generic_hart_index2id,--HART的index到id转换表格。
    .hart_stack_size    = SBI_PLATFORM_DEFAULT_HART_STACK_SIZE,--每个HART预留给异常或中断栈空间大小。
    .platform_ops_addr    = (unsigned long)&platform_ops--指向sbi_platform_operations结构体,操作函数集。
};

2.4.2 增加新平台

如下步骤:
  1. 在platform目录下添加xyz目录。
  2. 在xyz目录中创建config.mk文件。
  3. 在xyz目录中创建objects.mk文件。
  4. 在platform.c实例化一个struct sbi_platform。

3 OpenSBI编译

OpenSBI在Buildroot中配置:

Bootloaders
  opensbi
    OpenSBI Version (Custom version)
    (1.2) OpenSBI version
    (generic) OpenSBI Platform
    Install fw_dynamic image
    Install fw_jump image
    Install fw_payload image
    Include Linux as OpenSBI Payload
    Additional build variables

单独编译命令:

make opensbi-clean-for-rebuild && make opensbi

编译配置在platform/generic/objects.mk中定义:

FW_TEXT_START=0x80000000
FW_JUMP_ADDR=$(shell printf "0x%X" $$(($(FW_TEXT_START) + 0x200000)))
FW_JUMP_FDT_ADDR=$(shell printf "0x%X" $$(($(FW_TEXT_START) + 0x2200000)))
FW_PAYLOAD_OFFSET=0x200000
FW_PAYLOAD_FDT_ADDR=$(FW_JUMP_FDT_ADDR)

在OpenSBI目录下生成帮助文档:

make docs

4 Linux中OpenSBI相关调用

Linux通过ecall进行SBI调用,让CPU核进入M-Mode进行处理。
Linux中SBI的应用包括3方面:
一是支持SBI v0.1规格。
二是支持Timer、IPI、RFence等。
三是支持扩展的HSM,对CPU进行Start/Stop。

4.1 sbi初始化

start_kernel
  setup_arch
    sbi_init
      ->sbi_set_power_off--设置pm_power_off为sbi_shutdown。
      ->sbi_get_spec_version--获取SBI版本号。
      ->如果版本高于0.1,则配置Timer/IPI/Rfence等函数指针。

4.2 sbi_ecall

sbi_ecall()通过ecall指令进入M-Mode触发ecall异常。
入参:
  • a0-a5一共6个参数
  • a6作为function id
  • a7作为extension id
返回值:
  • a0为sbiret.error
  • a1为sbiret.value

ebi_call()发起ecall调用,并从a0/a1获取返回值:

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;
}

Linux下SBI Extension ID定义在arch/riscv/include/asm/sbi.h中,和OpenSBI中匹配:

enum sbi_ext_id {
#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,
};

4.3 cpu_ops设置

如果CONFIG_RISCV_SBI定义了,那么cpu_ops会通过SBI进行处理。
start_kernel
  setup_arch
    setup_smp
      cpu_set_ops--cpu_ops指向cpu_ops_sbi。
cpu_ops_sbi函数通过sbi实现其功能,主要包括:
  • SBI_EXT_HSM_HART_START
  • SBI_EXT_HSM_HART_STOP
  • SBI_EXT_HSM_HART_STATUS
代码如下:
const struct cpu_operations cpu_ops_sbi = {
    .name        = "sbi",
    .cpu_prepare    = sbi_cpu_prepare,
    .cpu_start    = sbi_cpu_start,
#ifdef CONFIG_HOTPLUG_CPU
    .cpu_disable    = sbi_cpu_disable,
    .cpu_stop    = sbi_cpu_stop,
    .cpu_is_stopped    = sbi_cpu_is_stopped,
#endif
};

5 参考文档

posted on 2024-05-06 23:59  ArnoldLu  阅读(3594)  评论(0编辑  收藏  举报

导航