Introduce riscv semihosting
Overview
Semihosting提供了一种机制,让CPU上的代码与主机通信并借助主机侧的功能。其核心原理就是,在CPU侧执行特定序列的指令,在主机侧识别这些指令,并采集参数,调用主机侧响应功能。
RISCV 也支持Semihosting的功能。
RISCV Semihosting
riscv定义的特定指令序列是:
1 | slli x0, x0, 0x1f # 0x01f01013 Entry NOP |
这个指令序列还有一些要求:
- 必须是32 bit 指令码,不能是16 bit指令码
- 如果paging使能了,这个序列必须不能跨页。也就是这个序列在同一个物理页上。因为host需要检查这个序列,而不想发生page fault。
因此,riscv semihosting trap function可以如下写:
1 | .option norvc |
可以看出,这个指令序列都是固定的,那参数怎么传递?答案是寄存器 a0
和 a1
。
- operation number register. a0
- parameter register. a1
- return register. a0
在riscv-tests里面可以找到相关demo。
https://github.com/riscv-software-src/riscv-tests/blob/master/debug/programs/semihosting.c
函数定义:
1 | static inline uintptr_t __attribute__((always_inline)) |
调用方法:
1 | volatile semihostparam_t arg = {.param1 = (uintptr_t)name, |
其中operation参数定义:
1 | // Semihosting operations. |
在openocd代码中也能找到相关处理。
https://github.com/openocd-org/openocd/blob/master/src/target/riscv/riscv_semihosting.c
1 | uint32_t pre = target_buffer_get_u32(target, tmp_buf); |
解释一下工作过程:
- CPU侧:将参数准备好放入a0和a1中,调用semihost_call_host函数。
- CPU侧:第一条指令是nop,执行第二条ebreak指令,进入debug 模式
- HOST侧:识别到CPU进入debug 模式,读取当前PC的指令码,和上一条PC指令码,和下一条PC指令码。判断是否符合semihost的magic number,由于这里固定了必须是32bit指令码,所以openocd代码中只需要判断这三条指令码的编码即可。
- HOST侧:如果匹配编码,就根据a0和a1的值,来调用相应处理函数。a0作为类型,a1作为参数。因为只提供了一个参数,如果需要多个参数,CPU可以将多个参数的内存地址赋给a1,HOST从a1地址中拿多个参数。
- HOST侧:执行完成之后,将返回值赋给a0,退出debug模式
- CPU侧:从debug模式返回,执行第三条指令,也是个nop。然后返回。
Reference
https://github.com/riscv-software-src/riscv-semihosting/blob/main/riscv-semihosting-spec.adoc