RV32I/RV32E在访问mtime和mtimecmp的注意事项

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,那可能会多产生一个额外的中断,这不是程序所期望的。

你可能感兴趣的:(嵌入式硬件,RISC-V)