DAY 29
2
Software Development

## 予焦啦！附錄：詭異的時間中斷（timer interrupt）擱置位元（pending bit）

3.0 那一篇，筆者介紹時間中斷與簡單的實作。也許也有讀者注意到了不太順暢的部分，那就是直接跳躍到作業系統模式去控制，卻沒有說明機器模式的中斷相關控制暫存器的機制。

## 問題：收斂之後

``````        /* Clear Time Compare */
mt->time_wr(true, -1ULL,
&mt_time_cmp[target_hart - mt->first_hartid]);

return 0;
``````

### 探索規格書

``````A machine timer interrupt becomes pending whenever mtime
contains a value greater than or equal to mtimecmp, treating
the values as unsigned integers.
``````

``````(gdb) x/i \$pc
=> 0x80011db6:  sd      a1,0(a2)
(gdb) display/x \$mip
3: /x \$mip = 0x0
(gdb) display/x \$a1
4: /x \$a1 = 0xffffffffffffffff
``````

``````(gdb) si
0x0000000080011db8 in ?? ()
3: /x \$mip = 0x80
4: /x \$a1 = 0xffffffffffffffff
``````

``````(gdb) set \$pc=0x0000000080011db6
(gdb) set \$a1=0x7fffffffffffffff
(gdb) si
0x0000000080011db8 in ?? ()
3: /x \$mip = 0x80
4: /x \$a1 = 0x7fffffffffffffff
``````

``````(gdb) set \$pc=0x0000000080011db6
(gdb) set \$a1=0x0000000000070000
(gdb) si
0x0000000080011db8 in ?? ()
3: /x \$mip = 0x0
4: /x \$a1 = 0x70000
7: x/xg 0x200bff8  0x200bff8:   0x00000000000629bd
``````

``````(gdb) set \$pc=0x0000000080011db6
(gdb) set \$a1=0x1fffffffffffffbd
(gdb) si
0x0000000080011db8 in ?? ()
3: /x \$mip = 0x80
4: /x \$a1 = 0x1fffffffffffffbd
7: x/xg 0x200bff8  0x200bff8:   0x0000000000067388
8: x/xg 0x2004018  0x2004018:   0x1fffffffffffffbd
(gdb) set \$pc=0x0000000080011db6
(gdb) set \$a1=0x1fffffffffffffbc
(gdb) si
0x0000000080011db8 in ?? ()
3: /x \$mip = 0x0
4: /x \$a1 = 0x1fffffffffffffbc
7: x/xg 0x200bff8  0x200bff8:   0x000000000006781c
8: x/xg 0x2004018  0x2004018:   0x1fffffffffffffbc
``````

`0x1fffffffffffffbd` 以上的值，都會有擱置的效果。

### 追查 QEMU 實作

``````...
if (cpu->env.timecmp <= rtc_r) {
/* if we're setting an MTIMECMP value in the "past",
immediately raise the timer interrupt */
return;
}
...
``````

### 第一種回溯

``````#0  riscv_cpu_update_mip
at ../target/riscv/cpu_helper.c:231
#1  0x0000555555baf3c0 in sifive_clint_write_timecmp
(cpu=0x7ffff7fb2830, value=4294967295, timebase_freq=10000000)
at ../hw/intc/sifive_clint.c:60
#2  0x0000555555b79840 in memory_region_write_accessor
=<optimized out>, access_size_max=<optimized out>, access_fn=
0x555555b797c0 <memory_region_write_accessor>, mr=0x5555567eb0f0, attrs=...)
at ../softmmu/memory.c:554
#4  0x0000555555b7b149 in memory_region_dispatch_write
data@entry=18446744073709551615, op=op@entry=MO_64, attrs=...) at ../softmmu/memory.c:1511
#5  0x0000555555c417e9 in io_writex
(env=env@entry=0x7ffff7fbb9a0, iotlbentry=iotlbentry@entry=0x5555567c0bd0, mmu_idx=mmu_idx@e
36213287170, op=MO_64) at ../accel/tcg/cputlb.c:1420
#6  0x0000555555c49ea7 in store_helper
env=0x7ffff7fbb9a0) at ../accel/tcg/cputlb.c:2463
#7  helper_le_stq_mmu
0736213287170) at ../accel/tcg/cputlb.c:2541
#8  0x00007fffb4000102 in code_gen_buffer ()
#9  0x0000555555c36e0e in cpu_tb_exec
(cpu=0x7ffff7fb2830, itb=<optimized out>, tb_exit=0x7fffa7ffd8e8)
at ../accel/tcg/cpu-exec.c:353
#10 0x0000555555c382be in cpu_loop_exec_tb
(tb_exit=0x7fffa7ffd8e8, last_tb=<synthetic pointer>, tb=0x7fffb4000000 <code_gen_buffer+671
08819>, cpu=0x7ffff7fb2830) at ../accel/tcg/cpu-exec.c:812
#11 cpu_exec (cpu=cpu@entry=0x7ffff7fb2830) at ../accel/tcg/cpu-exec.c:970
#12 0x0000555555c546e0 in tcg_cpus_exec (cpu=cpu@entry=0x7ffff7fb2830)
at ../accel/tcg/tcg-accel-ops.c:67
at ../accel/tcg/tcg-accel-ops-mttcg.c:70
#16 0x00007ffff71585e3 in clone () at /usr/lib/libc.so.6
``````

### 第二種回溯

``````#0  riscv_cpu_update_mip (cpu=0x7ffff7fb2830, mask=128, value=4294967295)
at ../target/riscv/cpu_helper.c:231
#1  0x0000555555dc7659 in timerlist_run_timers (timer_list=0x55555659f770)
at ../util/qemu-timer.c:573
#2  0x0000555555dc7895 in timerlist_run_timers (timer_list=<optimized out>)
at ../util/qemu-timer.c:506
#3  qemu_clock_run_timers (type=<optimized out>) at ../util/qemu-timer.c:587
#4  qemu_clock_run_all_timers () at ../util/qemu-timer.c:669
#5  0x0000555555dc383b in main_loop_wait (nonblocking=nonblocking@entry=0)
at ../util/main-loop.c:542
#6  0x0000555555b76ca3 in qemu_main_loop () at ../softmmu/runstate.c:726
#7  0x0000555555903bfe in main
``````

``````...
timer_list->active_timers = ts->next;
ts->next = NULL;
ts->expire_time = -1;
cb = ts->cb;
opaque = ts->opaque;

/* run the callback (the timer list can be modified) */
qemu_mutex_unlock(&timer_list->active_timers_lock);
cb(opaque);
qemu_mutex_lock(&timer_list->active_timers_lock);
...
``````

``````static void sifive_clint_timer_cb(void *opaque)
{
RISCVCPU *cpu = opaque;
}
``````

### 追查 timerlist

``````    /* otherwise, set up the future timer interrupt */
diff = cpu->env.timecmp - rtc_r;
/* back to ns (note args switched in muldiv64) */
next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
timer_mod(cpu->env.timer, next);
``````

``````void timer_mod(QEMUTimer *ts, int64_t expire_time)
{
timer_mod_ns(ts, expire_time * ts->scale);
}
``````

``````/* modify the current timer so that it will be fired when current_time >= expire_time. The corresponding callback will be called. */
void timer_mod_ns(QEMUTimer *ts, int64_t expire_time)
{
QEMUTimerList *timer_list = ts->timer_list;
bool rearm;

qemu_mutex_lock(&timer_list->active_timers_lock);
timer_del_locked(timer_list, ts);
rearm = timer_mod_ns_locked(timer_list, ts, expire_time);
...
``````

### 揪出錯誤

``````next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
``````

``````void timer_mod(QEMUTimer *ts, int64_t expire_time)
``````

### 解決方案

``````diff --git a/hw/intc/sifive_clint.c b/hw/intc/sifive_clint.c
index 0f41e5ea1c..24307ed59c 100644
--- a/hw/intc/sifive_clint.c
+++ b/hw/intc/sifive_clint.c
@@ -44,6 +44,7 @@ static void sifive_clint_write_timecmp(RISCVCPU *cpu, uint64_t value,
{
uint64_t next;
uint64_t diff;
+    uint64_t now;

@@ -59,9 +60,9 @@ static void sifive_clint_write_timecmp(RISCVCPU *cpu, uint64_t value,
diff = cpu->env.timecmp - rtc_r;
/* back to ns (note args switched in muldiv64) */
-    next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
-        muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
-    timer_mod(cpu->env.timer, next);
+    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    next = now + muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
+    timer_mod(cpu->env.timer, (next <= now) ? (int64_t)((1ULL<<63) - 1) : next);
}

/*
``````

## 小結

``````Hello,

Thanks for the patch!

As David's was sent first I am going to apply that instead of this

Please feel free to send more QEMU patches in the future or to review
other people's patches.

Hopefully we will see you again :)

Alistair
``````