RV32I/RV32E的寄存器是32位的,而mtime和mtimecmp总是64位的,RV32I/RV32E读写mtime和mtimecmp就需要分两次Load/Store,而且mtime不停地在变化,这就需要一些技巧处理这两个寄存器的访问。
mingdu.zheng at gmail dot com
http://blog.csdn.net/zoomdy/article/details/79361553
64位的mtime和mtimecmp
mtime和mtimecmp总是64位的,无论是RV32I/RV32E或者是RV64I还是RV128I。对用作计数的这两个寄存器而言,64位已经是天文数字,在产品的整个生命周期内都不会产生溢出。假设驱动mtime的时钟频率是10GHz(目前还没有哪个CPU主频能到10GHz的吧),那么要让64位的mtime溢出,那么需要0x10000000000000000 / 10000000000 / 60 / 60 / 24 / 365
年,也就是58年。HiFive1开发板驱动mtime的时钟频率是32768Hz,那么需要17851025年mtime才会溢出。
RV64I或RV128I访问mtime或mtimecmp只需要一条指令,不会有什么问题。RV32I/RV32E访问mtime或mtimecmp需要把这两个寄存器拆成两个32位寄存器来访问,这需要两条指令,如果真好碰上低32位要进位的情况,那么就会产生问题,访问的结果会产生巨大的偏差。
读mtime
读mtime的代码如下:
static uint32_t mtime_lo(void)
{
return *(volatile uint32_t *)(CLINT_CTRL_ADDR + CLINT_MTIME);
}
static uint32_t mtime_hi(void)
{
return *(volatile uint32_t *)(CLINT_CTRL_ADDR + CLINT_MTIME + 4);
}
uint64_t get_timer_value()
{
uint32_t hi = mtime_hi();
uint32_t lo = mtime_lo();
return ((uint64_t)hi << 32) | lo;
}
假设进入get_timer_value
函数时,mtime当前值为0x00000050_FFFFFFFF,假如在hi = mtime_hi();
和lo = mtime_lo();
两条语句之间正好产生进位,那么 hi = 0x00000050,lo = 0x00000000。 返回结果是0x00000050_00000000,预期结果应该是0x00000051_00000000,不仅产生极大的偏差,而且时光倒流了。
改进的get_timer_value
:
// 出自 HiFive1 示例程序 demo_gpio/bsp/env/freedom-e300-hifive1/init.c
uint64_t get_timer_value()
{
while (1) {
uint32_t hi = mtime_hi();
uint32_t lo = mtime_lo();
if (hi == mtime_hi())
return ((uint64_t)hi << 32) | lo;
}
}
改进后的get_timer_value
在读取了mtime的两部分后再次读取高32位,并判断有没有进位产生,如果产生进位那么就丢弃本次错误的结果重新读一次,如果没有进位那么结果是可靠的,返回读到的mtime值。
写mtimecmp
如何正确地写mtimecmp的值在 The RISC-V Instruction Set Manual Volume II: Privileged Architecture 3.1.15 Machine Timer Registers (mtime and mtimecmp) 中有明确的说明:
In RV32, memory-mapped writes to mtimecmp modify only one 32-bit part of the register. The following code
sequence sets a 64-bit mtimecmp value without spuriously generating a timer interrupt due to the intermediate
# New comparand is in a1:a0.
li t0, -1
sw t0, mtimecmp # No smaller than old value.
sw a1, mtimecmp+4 # No smaller than new value.
sw a0, mtimecmp # New value.
用C语言来实现:
static void mtimecmp_lo(uint32_t v)
{
*(volatile uint32_t *)(CLINT_CTRL_ADDR + CLINT_MTIMECMP) = v;
}
static void mtimecmp_hi(uint32_t v)
{
*(volatile uint32_t *)(CLINT_CTRL_ADDR + CLINT_MTIMECMP + 4) = v;
}
void set_timecmp_value(uint64_t v)
{
uint32_t hi = (v >> 32) & 0xffffffff;
uint32_t lo = v & 0xffffffff;
mtimecmp_lo(0xffffffff); // No smaller than old value.
mtimecmp_hi(hi); // No smaller than new value.
mtimecmp_lo(lo); // New value.
}
如果没有按照这个要求写mtimecmp,那可能会多产生一个额外的中断,这不是程序所期望的。