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 也提供了访问对应计数器的伪指令:
+
+
+
+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