修好了D1板板使用Clang编译内核的问题,错误的Variables in Specified Registers用法

内容纲要

最近在修D1板板的驱动。遇到一些很迷惑的问题。

GCC编译的D1内核是好的,Clang不开LTO的内核是坏的,Clang编译的LTO THIN的内核是坏的,Clang编译的LTO Full内核勉强能用,Clang编译的LTO Full+CFI的内核是坏的。

但由于我们科研需要必须要使用Clang LTO+CFI,因此必须要保证能跑通开启了LTO和CFI的Kernel。

上述勉强能用的内核网卡还出现了异常的延迟、丢包、DUP。且从板子上ping外部延迟恒定为1s,而从外部ping板子延迟恒定为2s。而IPv6甚至完全不可用。

而勉强能用的内核上的SD卡,挂载上去写完文件再卸载后,接到别的电脑上挂载,却出现了文件系统损坏的情况。

而后来,经过2老师测试勉强能用的内核上的网卡驱动,发现以下问题:

经研究,clang编译的d1 linux,发包时,如果同时收到了包(比如别人发的广播包),那么发出去的包部分字节会被覆盖为收到的包

因此,我开始怀疑该问题是访问IO时Cache一致性出了问题,我就把目光从驱动本身转向了内核里维护Cache一致性的相关代码。

后来,我发现了一个这样的函数:

void sbi_dma_sync(unsigned long start,
          unsigned long size,
          enum sbi_dma_sync_data_direction dir)
{
#if 0
    sbi_ecall(SBI_EXT_DMA, SBI_DMA_SYNC, start, size, dir,
          0, 0, 0);
#else
    /* Just for try, it should be in sbi ecall and will be removed before merged */
    register unsigned long i asm("a0") = start & ~(L1_CACHE_BYTES - 1);

    for (; i < ALIGN(start + size, L1_CACHE_BYTES); i += L1_CACHE_BYTES)
        __asm__ __volatile__(".long 0x02b5000b");

    __asm__ __volatile__(".long 0x01b0000b");
#endif
}

这个代码看起来不会被merge到内核mainline,因此它也使用了一个奇怪的指令来完成Cache的刷新。后续应该使用sbi_ecall实现。

其中,这里的0x02b5000b指令,查阅处理器文档后发现,该指令为DCACHE.CIPA,意思是指定物理地址清脏并无效指令。其中该指令的rs1([19:15])位用于指定物理地址所在的寄存器号,可以发现这里的这条指令选用的寄存器号刚好是10,也就是RISC-V中的a0寄存器。

而0x01b0000b指令,查阅文档后未找到相关指令,但它与SYNC.I指令仅在rs2的最低位相差1,猜测是一条SYNC相关指令,它在内核代码中的宏名为SYNC_IS。

这个代码乍一看没问题,局部变量i声明了register,并且用了a0寄存器号。但我当时猜想这个asm volatile里没有声明使用变量i,有可能在调用过程中导致a0被篡改。

然后2老师使用objdump对比了gcc和clang编译的结果,如下:

GCC:

Clang+LLVM:

果然可以看出,gcc编译的内核使用的寄存器号为a0,而clang则为a2。且clang编译的结果只有在变量初始化时将值写入的a0。

这就导致,Clang编译的内核不会正确进行dma同步。

此时,我便想到了一个非常简单的修复,但代码修改较长,如下:

register unsigned long i = start & ~(L1_CACHE_BYTES - 1);

for (; i < ALIGN(start + size, L1_CACHE_BYTES); i += L1_CACHE_BYTES)
    __asm__ __volatile__(
        "addi a0,%0,0;\n"
        ".long 0x02b5000b;\n"
        :
        : "r"(i)
        : "a0"
    );

经过测试,这样修改后的内核工作一切正常,再开启CFI依旧一切正常。

而后经过2老师指点,相较于原代码只需要修改for循环内的asm volatile为如下形式。

__asm__ __volatile__(".long 0x02b5000b" : : "r"(i));

我起初不理解这种写法,相当于把原始的变量i放在asm volatile语句的InputOperands中,但是asm volatile语句中并没有使用它。

后来,我查询了GCC官方对于Variables in Specified Registers的介绍,如下:

Local register variables in specific registers do not reserve the registers, except at the point where they are used as input or output operands in an asm statement and the asm statement itself is not deleted.

因此,这样的写法以后,编译器就会一直为我们保留变量i和a0的对应关系,直到整个asm volatile语句在这个函数中不再执行。

One Response

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

Back to Top