diff --git a/articles/20220405-riscv-linux-timer.md b/articles/20220405-riscv-linux-timer.md new file mode 100644 index 0000000000000000000000000000000000000000..7c6a4415fc3adce4dce06a36dfdb3f6ef9fa3b87 --- /dev/null +++ b/articles/20220405-riscv-linux-timer.md @@ -0,0 +1,128 @@ +> Author: Yu Liao
+> Date: 2022/04/5
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux) + +# RISCV timer在Linux中的实现 + +## 简介 +Linux 将底层时钟硬件抽象为两类设备: clockevent 和 clocksource,前者用来在未来指定的时间产生中断,通常用作定时器;后者则用于维护自系统启动以来所经过的时间。 +当前 Linux 时间子系统为 RISCV 实现了两套驱动,S-mode 的 timer-riscv.c 和 M-mode 的 timer-clint.c。 + +## S-mode timer + +S-mode timer 驱动适用于有MMU的场景。RISCV CSR 中提供了 3 个 64 位非特权计数器:`cycle`、`time`、`instret`。 +`cycle` 是 CPU 复位以来经过的周期数;`time` 是 CPU 复位以来经过的时间,由固定频率的 clock 驱动; +`instret` 值代表复位以来执行的指令数量。RISCV 也提供了访问对应计数器的伪指令: + +![](images/riscv_timer/riscv-counter.png) + +clocksource 通过直接读取 CSR 的`time`计数器来实现。 + +```c +static unsigned long long riscv_clocksource_rdtime(struct clocksource *cs) +{ + return get_cycles64(); +} +``` +64 位的RISCV中直接调用 `get_cycles` 读取 CSR time 计数器,32 位需要分两次读取并考虑进位的情况。 +```c +#ifdef CONFIG_64BIT +static inline u64 get_cycles64(void) +{ + return get_cycles(); +} +#else /* CONFIG_64BIT */ +static inline u64 get_cycles64(void) +{ + u32 hi, lo; + + do { + hi = get_cycles_hi(); + lo = get_cycles(); + } while (hi != get_cycles_hi()); + + return ((u64)hi << 32) | lo; +} +#endif /* CONFIG_64BIT */ + +static inline cycles_t get_cycles(void) +{ + return csr_read(CSR_TIME); +} +static inline u32 get_cycles_hi(void) +{ + return csr_read(CSR_TIMEH); +} + +``` +clockevent 则通过 SBI Timer 调用来实现时钟事件。 +```c +static int riscv_clock_next_event(unsigned long delta, + struct clock_event_device *ce) +{ + csr_set(CSR_IE, IE_TIE); + sbi_set_timer(get_cycles64() + delta); + return 0; +} +``` + +## M-mode timer +M-mode timer 驱动主要适用于无 MMU 场景,大多数 RISCV 系统还会提供 CLINT MMIO timer,我们可以通过 `mtime` 和 `mtimecmp` 这两个 64 位的寄存器来访问它。`mtime` 以一个大于 10MHz 的固定的频率递增,设置 `mie` 寄存器中的 `MTIE` 使能中断后,当 `mtime` 大于或等于 `mtimecmp` 时,timer 中断就会触发。 + +clocksource通过读取 `mtime` 寄存器来实现。 +```c +static u64 clint_rdtime(struct clocksource *cs) +{ + return clint_get_cycles64(); +} +``` +64 位情况下直接读取 `mtime` 值就行了,32 位还需要分两次读取,且需要考虑产生进位的情况。 +```c +#ifdef CONFIG_64BIT +static u64 notrace clint_get_cycles64(void) +{ + return clint_get_cycles(); +} +#else /* CONFIG_64BIT */ +static u64 notrace clint_get_cycles64(void) +{ + u32 hi, lo; + + do { + hi = clint_get_cycles_hi(); + lo = clint_get_cycles(); + } while (hi != clint_get_cycles_hi()); + + return ((u64)hi << 32) | lo; +} +#endif /* CONFIG_64BIT */ +``` +`clint_get_cycles/clint_get_cycles_hi` 则直接通过内存访问寄存器。 +```c +#ifdef CONFIG_64BIT +#define clint_get_cycles() readq_relaxed(clint_timer_val) +#else +#define clint_get_cycles() readl_relaxed(clint_timer_val) +#define clint_get_cycles_hi() readl_relaxed(((u32 *)clint_timer_val) + 1) +#endif + +``` + +clockevent 是通过打开 `MIE` 的 `TIMER` 中断,并在 `mtimecmp` 寄存器写入计数值实现。 +```c +static int clint_clock_next_event(unsigned long delta, + struct clock_event_device *ce) +{ + void __iomem *r = clint_timer_cmp + + cpuid_to_hartid_map(smp_processor_id()); + + csr_set(CSR_IE, IE_TIE); + writeq_relaxed(clint_get_cycles64() + delta, r); + return 0; +} +``` + +## 参考文档 + +1. [RISC-V Platform](https://github.com/riscv/riscv-platform-specs/blob/main/riscv-platform-spec.adoc/) +2. [RISC-V ISA Specification](https://riscv.org/technical/specifications/) diff --git a/articles/images/riscv_timer/riscv-counter.png b/articles/images/riscv_timer/riscv-counter.png new file mode 100644 index 0000000000000000000000000000000000000000..9a66b9e713ac603d4a4d07476902cf45a3cd9650 Binary files /dev/null and b/articles/images/riscv_timer/riscv-counter.png differ