OpenSBI 中 misa_xlen() 返回 -1 是什么意思
在平头哥的C910平台进行OpenSBI调试的时候,发现misa_xlen()这个函数返回值是-1?为何会如此呢,按照理解,这个函数只能返回1,2,3,返回-1标识出一种异常,那究竟是什么意思呢?下面通过源码进行问题的定位和解释。
首先明确一下,这个函数在哪里:
lib -> risc_main_asm.c -> misa_xlen()
从源码入手。
int misa_xlen(void)
{
long r;
if (csr_read(CSR_MISA) == 0)
return sbi_platform_misa_xlen(sbi_platform_thishart_ptr());
__asm__ __volatile__(
"csrr t0, misa\n\t"
"slti t1, t0, 0\n\t"
"slli t1, t1, 1\n\t"
"slli t0, t0, 1\n\t"
"slti t0, t0, 0\n\t"
"add %0, t0, t1"
: "=r"(r)
:
: "t0", "t1");
return r ? r : -1;
}
从代码上看,返回-1有两个可能,第一个return,还有就是汇编代码的return.
要看懂第一个return,需要看懂第一个函数:
#define csr_read(csr) \
({ \
register unsigned long __v; \
__asm__ __volatile__("csrr %0, " __ASM_STR(csr) /* 将csr的值赋值给__V */ \
: "=r"(__v) /* 输出列表 */ \
: /* 输入列表,这里为空*/ \
: "memory"); /* 影响的内存,寄存器 */ \
__v; \
})
这种 __ asm __ 开头的写法,叫做内嵌汇编。
里面的%0 和%1,是占位符。
冒号的3个部分分别是 输入列表,输出列表,改变的register(常见的是memory)
有关于这个部分,参考下面的几个链接:
链接: 内联汇编 - 从头开始.
__asm__ __volatile__(assembly template
: output operand list
: input operand list
: clobber list
);
链接: A guide to inline assembly for C and C++.
链接: 内嵌汇编 %0,%1 是什么.
__asm__ __violate__
("movl %1,%0" : "=r" (result) : "m" (input));
//“movl %1,%0”是指令模板;“%0”和“%1”代表指令的操作数,称为占位符,内嵌汇编靠它们将C语言表达式与指令操作数相对应。
//指令模板后面用小括号括起来的是C语言表达式,本例中只有两个:“result”和“input”,他们按照出现的顺序分别与指令操作数“%0”,“%1,”对应;注意对应顺序:第一个C表达式对应“%0”;第二个表达式对应“%1”,依次类推,操作数至多有10个,分别用“%0”,“%1”….“%9,”表示。
//“result”前面的限制字符串是“=r”,其中“=”表示“result”是输出操作数,“r”表示需要将“result”与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而不是“result”本身,当然指令执行完后需要将寄存器中的值存入变量“result”,从表面上看好像是指令直接对“result”进行操作,实际上GCC做了隐式处理,这样我们可以少写一些指令。
链接: csr_read() 函数在Kernel的一个修改记录 commit info.
链接: GCC-Inline-Assembly-HOWTO.
这个简书的文章,把Sandeep.S的GCC-Inline-Assembly-HOWTO给翻译并勘误了,推荐配套阅读。
链接: GCC内联汇编基础.
链接: 总结CSRs寄存器的读写指令.
链接: c语言之#和##.
链接: C语言宏定义##连接符和#符的使用.
链接: GCC内联汇编教程.
从riscV网站可以得知这个register的可能值是0,1,2,3.
链接: 3.1.1 Machine ISA Register misa.
MXL | XLEN |
---|---|
1 | 32 |
2 | 64 |
3 | 128 |
The misa CSR is a WARL read-write register reporting the ISA supported by the hart. This register must be readable in any implementation, but a value of zero can be returned to indicate the misa register has not been implemented, requiring that CPU capabilities be determined through a separate non-standard mechanism.
The MXL (Machine XLEN) field encodes the native base integer ISA width as shown in Table [misabase]. The MXL field may be writable in implementations that support multiple base ISA widths. The effective XLEN in M-mode, MXLEN, is given by the setting of MXL, or has a fixed value if misa is zero. The MXL field is always set to the widest supported ISA variant at reset.
这个值为零的情况,指的是misa还没被实现。
但是我们的工程中,显然不是这种情况,从同时提供的打印信息,并不是第一个return导致的-1.
所以只能从后面的函数入手分析。
int misa_xlen(void)
{
long r;
__asm__ __volatile__(
"csrr t0, misa\n\t"
"slti t1, t0, 0\n\t"
"slli t1, t1, 1\n\t"
"slli t0, t0, 1\n\t"
"slti t0, t0, 0\n\t"
"add %0, t0, t1"
: "=r"(r)
:
: "t0", "t1");
return r ? r : -1;
}
既然是关于汇编的,那么就需要解析每一行的汇编指令。
关于汇编命令的解析,可以参考这个文档
链接: 《RISC-V-Reader-Chinese-v2p1.pdf》.
先解释几个缩写的意思:
CSR :Control and Status Register 控制和状态寄存器
MISA:Machine-Level CSRs
xlen:x length, 指的是你的register位数是多长,支持32或者64。
Risc-V的基础指令集是整数指令集,在任何架构方案中,必须完整实现基础的整数指令集。在整数指令集中,用补码表示有符号整数。
主要有四个基础指令集,它们主要通过整数寄存器的长度来区分,比如RV32I,在该指令集方案中,整数寄存器的长度为32位,在RV64I指令集方案中,整数寄存器的长度为64位。对于RV32I,由于整数寄存器是32位,所以可以提供232=4GB的地址访问空间,对于RV64I,整数寄存器是64位,所以可以提供264=4194304TB的地址访问空间,这是一个非常大的地址空间。我们通常用xlen表示整数寄存器位数或者说地址空间位数,所以对于RV32I, xlen=32, 对于RV64I, xlen=64。
下面我将每一行使用伪代码来进行注释。
__asm__ __volatile__(
"csrr t0, misa\n\t" /* 将misa的状态读到t0. [t0] = misa*/
"slti t1, t0, 0\n\t" /* 如果t0的值 < 0,那么t1写1,否则写0 if [t0] < 0; [t1] = 1; else [t1] = 0;*/
"slli t1, t1, 1\n\t" /* 将t1的值,左移1bit。 [t1] << 1 */
"slli t0, t0, 1\n\t" /* 将t0的值,左移1bit。 [t0] << 1 */
"slti t0, t0, 0\n\t" /* if [t0] < 0; [t0] = 1; else [t0] = 0; */
"add %0, t0, t1" /* %0 是占位符,意思是 将 [r] = [t0] + [t1] */
: "=r"(r) /* 返回值是 r的值,格式是0xff */
:
: "t0", "t1"); /* 影响寄存器的范围是t0和t1 */
链接: GCC-Inline-Assembly-HOWTO.html.
这里面有关于返回限定的意思,rK就是返回一个0xff类型的值。
“r” : Register operand constraint, look table given above.
“q” : Registers a, b, c or d.
“I” : Constant in range 0 to 31 (for 32-bit shifts).
“J” : Constant in range 0 to 63 (for 64-bit shifts).
“K” : 0xff.
“L” : 0xffff.
“M” : 0, 1, 2, or 3 (shifts for lea instruction).
“N” : Constant in range 0 to 255 (for out instruction).
“f” : Floating point register
“t” : First (top of stack) floating point register
“u” : Second floating point register
“A” : Specifies the a’ or
d’ registers. This is primarily useful for 64-bit integer values intended to be returned with the d’ register holding the most significant bits and the
a’ register holding the least significant bits.
从risc-v-asm-manual.pdf 的第一个Table 1.1 RISC-V Base Integer Registers Of Size XLEN的图可以看出,t0,t1是临时使用的register.
Register Name | ABI Name | Description |
---|---|---|
x0 | zero | Hard-Wired Zero |
x1 | ra | Return Address |
x2 | sp | Stack Pointer |
x3 | gp | Global Pointer |
x4 | tp | Thread Pointer |
x5 | t0 | Temporary/Alternate Link Register |
x6-7 | t1-t2 | Temporary Register |
x8 | s0/fp | Saved Register (Frame Pointer) |
x9 | s1 | Saved Register |
x10-11 | a0-a1 | Function Argument/Return Value Registers |
x12-17 | a2-a7 | Function Argument Registers |
x18-27 | s2-s11 | Saved Registers |
x28-31 | t3-t6 | Temporary Registers |
最后回到这个函数
int misa_xlen(void)
{
long r;
__asm__ __volatile__(
"csrr t0, misa\n\t"
"slti t1, t0, 0\n\t"
"slli t1, t1, 1\n\t"
"slli t0, t0, 1\n\t"
"slti t0, t0, 0\n\t"
"add %0, t0, t1"
: "=r"(r)
:
: "t0", "t1");
return r ? r : -1;
}
也就是说,只有r读到非0值,才返回r,否则都返回-1.
因为r的正常值只能是1,2,3,所以如果是-1的话,从misa读取的值是0.
所以我认为最可能的原因是:系统此时还未初始化完毕,misa的值还未正确的填充,所以读出来的才是0值。
当然了这只是基于现有信息的分析,具体的原因仍需进一步的确定。
这一个小小的问题。不仅牵扯了riscV的misa寄存器,asm内嵌汇编,多个asm汇编指令的解析,真是举步维艰,为了求是,必不能放过任何一个疑点,共勉之。
如有错误,请指出,别喷就行,我也不指望你给我捐赠,能帮到大家是最好,我又不是真理转世,出错在所难免。