FreeRTOSを動かしてみる(10)

[前回](https://qiita.com/t-yano-tryhard/items/fe3840011f5b45faa5dc)の続き

# QEMUからFreeRTOSへの送信
以下の<font color="red">赤</font>部分
![images](https://kroki.io/graphviz/svg/eNp9U8tqwzAQvPsrhM7xFxjnFAqFQkObnkIoirWJRWxL1SNNCP73WpIf8iPVwXh3ZmdWWomysyQiRxv0iFCzJKkulEmUot0mcZkKfm0y1dJAEvkUp7BXORGQ4iO_4RVS-l40geSmokDxwdGUOXrxrDBKg_x-Y5W5tUZ2FeQIRYpdGkd9ela3I_IMOigMirXHiBCBgsOZ2Lec123b0VDbY6BzkBXoKYOJeG1pzWHYTeo8INT9X5ZLCtfnnVNTlvflxj0kJM9AKTxiKF5C19_XZrvoPLP6gdIsO1lkbHAaq4_Np8IvEuBj9_45EQ8MOgaeEeQ_Q_DXbWEQaEkmXlvu4TnkB0Wyy8Smnhye_zajRfHanXQ0DNKNvH0HDj-NgsBjhTJe8CaSw3VvtIaaPpo11iHB3XG3zU29fUisujLVTebhXmWqSAmJpSZ200m7kTFILEiWQaufoJPF6j9lIhDo)

## QEMUの割り込み
QEMUが受け取ったARPパケットはFreeRTOS側で受け取れるようにする必要がある。

これには割り込みを使用する。

こんな感じ
```plantuml
participant QEMU as qemu
participant "共有メモリ" as shmem
participant FreeRTOS as frtos

qemu -> shmem : データ書き込み
qemu -> frtos : 割り込み
activate frtos
frtos -> shmem : データ読み込み
frtos -> frtos : 割り込みクリア
frtos --> qemu

deactivate frtos
```

[qemuのコード](https://github.com/qemu/qemu/blob/039afc5ef7367fbc8fb475580c291c2655e856cb/hw/arm/mps2.c#L234)を見ると32個の割り込みが使用出来るようです。
がしかし、実際に登録されているのはUART関連のみのようです。

したがって、割り込みを発生させる前に割り込みデバイスを作成する必要があります。
既存のUARTを借用するというのもあるけど、今回は別で作成しました。

なんか良さげなのがあったので借用してみた。
```c
static void register_ether_irq(DeviceState *armv7m)
{
    DeviceState *dev;

    dev = qdev_new(TYPE_MV88W8618_ETH);
    object_property_set_link(OBJECT(dev), "dma-memory",
                             OBJECT(get_system_memory()), &error_fatal);
    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
    sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,
                       qdev_get_gpio_in(armv7m, IRQ_ETHER));
}
```

で割り込み発生させてみると
```c
static void send_to_freertos(uint8_t *buf, size_t size)
{
    (void)buf;
    CPUState *cpu = CPU(ARM_CPU(first_cpu));

    d("irq fire n:%lu", size);
    cpu->interrupt_request = IRQ_ETHER;
    cpu_interrupt(cpu, CPU_INTERRUPT_VIRQ);
}
```

怒られました
```
mps2.c(829) send_to_freertos irq fire n:42
**
ERROR:../qemu/accel/tcg/tcg-accel-ops.c:91:tcg_handle_interrupt: assertion failed: (qemu_mutex_iothread_locked())
Bail out! ERROR:../qemu/accel/tcg/tcg-accel-ops.c:91:tcg_handle_interrupt: assertion failed: (qemu_mutex_iothread_locked())
Aborted

```

この使い方ではだめらしい

gdb で見てみるとわりかし即abortしてる。
```
(gdb) l
86	}
87	
88	/* mask must never be zero, except for A20 change call */
89	void tcg_handle_interrupt(CPUState *cpu, int mask)
90	{
91	    g_assert(qemu_mutex_iothread_locked());
```
iothreadがlockされていないと割り込みを投げられないらしい。
前はこんなところで引っかからなかったのになぁ

とりあえずコード読んでみると


```c
#define QEMU_DEFINE_STATIC_CO_TLS(type, var)                                 \
    static __thread type co_tls_##var;                                       \
    static __attribute__((noinline, unused))                                 \
    type get_##var(void)                                                     \
    { asm volatile(""); return co_tls_##var; }                               \
    static __attribute__((noinline, unused))                                 \
    void set_##var(type v)                                                   \
    { asm volatile(""); co_tls_##var = v; }                                  \
    static __attribute__((noinline, unused))                                 \
    type *get_ptr_##var(void)                                                \
    { type *ptr = &co_tls_##var; asm volatile("" : "+rm" (ptr)); return ptr; }

QEMU_DEFINE_STATIC_CO_TLS(bool, iothread_locked)

bool qemu_mutex_iothread_locked(void)
{
    return get_iothread_locked();
}

```

`set_iothread_locked()`が事前に必要っぽい
`set_iothread_locked`が使用されているのは[ここ](https://github.com/qemu/qemu/blob/039afc5ef7367fbc8fb475580c291c2655e856cb/system/cpus.c#L486)
system/cpus.c
```c
void qemu_mutex_lock_iothread_impl(const char *file, int line)
{
    QemuMutexLockFunc bql_lock = qatomic_read(&qemu_bql_mutex_lock_func);

    g_assert(!qemu_mutex_iothread_locked());
    bql_lock(&qemu_global_mutex, file, line);
    set_iothread_locked(true);
}
```
include/qemu/main-loop.h  
```c
#define qemu_mutex_lock_iothread()                      \
    qemu_mutex_lock_iothread_impl(__FILE__, __LINE__)
void qemu_mutex_lock_iothread_impl(const char *file, int line);
```
なので事前に`qemu_mutex_lock_iothread`呼んで終わったら`qemu_mutex_unlock_iothread`呼べば良さそう？

一応落ちなくはなったけど？？？
なんかおかしい感じがする
```
mps2.c(829) send_to_freertos irq fire n:42
mps2.c(829) send_to_freertos irq fire n:42
mps2.c(829) send_to_freertos irq fire n:42
```

# 割り込みハンドラとベクタテーブル
qemuから受け取った割り込みはベクタテーブルに記載されている割り込みハンドラへとつながっています。

[FreeRTOS側のコード](https://github.com/FreeRTOS/FreeRTOS/tree/202212.01/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC)も見てみると

こんな感じに32個の割り込みが使用出来ることが見て取れます。

[scripts/mps2_m3.ld](https://github.com/FreeRTOS/FreeRTOS/blob/b4fd629652e936e8c781287a5fed7e90c41c8dfe/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/scripts/mps2_m3.ld#L39)
```armasm

SECTIONS
{

    .isr_vector :
    {
        __vector_table = .;
        KEEP(*(.isr_vector))
        . = ALIGN(4);
    } > FLASH

```

[init/startup.c](https://github.com/FreeRTOS/FreeRTOS/blob/b4fd629652e936e8c781287a5fed7e90c41c8dfe/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c#L170)
```c
const uint32_t * isr_vector[] __attribute__( ( section( ".isr_vector" ) ) ) =
{
    ( uint32_t * ) &_estack,
    ( uint32_t * ) &Reset_Handler,       /* Reset                -15 */
    ( uint32_t * ) &Default_Handler,     /* NMI_Handler          -14 */
    ( uint32_t * ) &Default_Handler2,    /* HardFault_Handler    -13 */
    ( uint32_t * ) &Default_Handler3,    /* MemManage_Handler    -12 */
    ( uint32_t * ) &Default_Handler4,    /* BusFault_Handler     -11 */
    ( uint32_t * ) &Default_Handler5,    /* UsageFault_Handler   -10 */
    0,                                   /* reserved */
    0,                                   /* reserved */
    0,                                   /* reserved */
    0,                                   /* reserved   -6 */
    ( uint32_t * ) &vPortSVCHandler,     /* SVC_Handler              -5 */
    ( uint32_t * ) &Default_Handler6,    /* DebugMon_Handler         -4 */
    0,                                   /* reserved */
    ( uint32_t * ) &xPortPendSVHandler,  /* PendSV handler    -2 */
    ( uint32_t * ) &xPortSysTickHandler, /* SysTick_Handler   -1 */
    0,                                   /* uart0 receive 0 */
    0,                                   /* uart0 transmit */
    0,                                   /* uart1 receive */
    0,                                   /* uart1 transmit */
    0,                                   /* uart 2 receive */
    0,                                   /* uart 2 transmit */
    0,                                   /* GPIO 0 combined interrupt */
    0,                                   /* GPIO 2 combined interrupt */
    0,                                   /* Timer 0 */
    0,                                   /* Timer 1 */
    0,                                   /* Dial Timer */
    0,                                   /* SPI0 SPI1 */
    0,                                   /* uart overflow 1, 2,3 */
    0,                                   /* Ethernet   13 */
};

```

FreeRTOSのコメントより13番がEthernet関連用に使用する割り込みのようなので
そんな感じで実装していく。

```diff
diff --git a/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c b/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c
index 28c657c8..279ecc66 100644
--- a/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c
+++ b/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c
@@ -167,6 +167,8 @@ void Default_Handler6( void )
     }
 }
 
+extern void ether_rx_handler(void);
+
 const uint32_t * isr_vector[] __attribute__( ( section( ".isr_vector" ) ) ) =
 {
     ( uint32_t * ) &_estack,
@@ -198,7 +200,7 @@ const uint32_t * isr_vector[] __attribute__( ( section( ".isr_vector" ) ) ) =
     0,                                   /* Dial Timer */
     0,                                   /* SPI0 SPI1 */
     0,                                   /* uart overflow 1, 2,3 */
-    0,                                   /* Ethernet   13 */
+    (uint32_t*) &ether_rx_handler,       /* Ethernet   13 */
 };
 
 void _start( void )

```

割り込みハンドラの実体はNIC内に

source/portable/NetworkInterface/virtether/NetworkInterface.c
```c
void ether_rx_handler(void)
{
    d("");
}
```

受け取れていないっぽい
```
mps2.c(829) send_to_freertos irq fire n:42
mps2.c(829) send_to_freertos irq fire n:42
mps2.c(829) send_to_freertos irq fire n:42
```

# 割り込みハンドラが受け取れない原因
どうも割り込みの発生させ方が間違っているようで
割り込みハンドラに入ってきません。

割り込みの動作状況を見てみます。
`-d int`のオプションをつけると割り込みの発生が表示されます。
```
sudo /home/voyager/workspace/oss/build_qemu/qemu-system-arm -machine mps2-an385 -m 16 -monitor null -semihosting --semihosting-config enable=on,target=native -kernel ./build/RTOSDemo.axf -serial stdio -nographic  -d int 2>&1 | tee qemu.log


...
Exception return: magic PC fffffffd previous exception 15
...successful exception return
Taking exception 5 [IRQ] on CPU 0
...taking pending nonsecure exception 15
...loading from element 15 of non-secure vector table at 0x3
...

```
他の割り込みは動作しているようです。

なんの割り込みが動作しているか見てみます。
```
fgrep "Taking exception 5 [IRQ] on CPU 0" qemu.log -A4 | grep -E "PC|CPU" | fgrep -v "Taking exception 8"  | sort | uniq -c
...
     32 ...loaded new PC 0x379
   3434 ...loaded new PC 0x3c5
      1 [Taking exception 5 [IRQ] on CPU 0
   3433 Taking exception 5 [IRQ] on CPU 0

```

2つほど動作しているようです。
これを実行ファイルで見てみます。

```
arm-none-eabi-objdump -d build/RTOSDemo.axf | grep -E " (37|3c).:" -B4
...
00000378 <xPortPendSVHandler>:
     378:	f3ef 8009 	mrs	r0, PSP
     37c:	f3bf 8f6f 	isb	sy
--
     3b8:	f3af 8000 	nop.w
     3bc:	f3af 8000 	nop.w

000003c0 <pxCurrentTCBConst>:
     3c0:	20006158 	.word	0x20006158

000003c4 <xPortSysTickHandler>:
     3c4:	b508      	push	{r3, lr}
     3c6:	f04f 03bf 	mov.w	r3, #191	@ 0xbf
     3ca:	f383 8811 	msr	BASEPRI, r3
     3ce:	f3bf 8f6f 	isb	sy

```
`xPortPendSVHandler`と`xPortSysTickHandler`が動作しているようです。
どちらもベクタテーブルに存在しているものです。
```c
const uint32_t * isr_vector[] __attribute__( ( section( ".isr_vector" ) ) ) =
{
...
    ( uint32_t * ) &Default_Handler6,    /* DebugMon_Handler         -4 */    0,                                   /* reserved */
    ( uint32_t * ) &xPortPendSVHandler,  /* PendSV handler    -2 */
    ( uint32_t * ) &xPortSysTickHandler, /* SysTick_Handler   -1 */
```

FreeRTOS側にログ出力を埋め込んで確認してみましょう。

どちらもFreeRTOS/Source/portable/GCC/ARM_CM3/port.cにあります。
```diff
diff --git a/FreeRTOS/Source/portable/GCC/ARM_CM3/port.c b/FreeRTOS/Source/portable/GCC/ARM_CM3/port.c
index 8d07379d..e7291f74 100644
--- a/FreeRTOS/Source/portable/GCC/ARM_CM3/port.c
+++ b/FreeRTOS/Source/portable/GCC/ARM_CM3/port.c
@@ -34,6 +34,8 @@
 #include "FreeRTOS.h"
 #include "task.h"
 
+#include "dlog.h"
+
 /* For backward compatibility, ensure configKERNEL_INTERRUPT_PRIORITY is
  * defined.  The value should also ensure backward compatibility.
  * FreeRTOS.org versions prior to V4.4.0 did not include this definition. */
@@ -400,7 +402,7 @@ void vPortExitCritical( void )
 void xPortPendSVHandler( void )
 {
     /* This is a naked function. */
-
+d("");
     __asm volatile
     (
         "	mrs r0, psp							\n"
@@ -436,6 +438,7 @@ void xPortPendSVHandler( void )
 
 void xPortSysTickHandler( void )
 {
+    d("");
     /* The SysTick runs at the lowest interrupt priority, so when this interrupt
      * executes all interrupts must be unmasked.  There is therefore no need to
      * save and then restore the interrupt mask value as its value is already

```

割り込み内での処理が長すぎて詰まってるっぽいですが
一応入ってはきています。
```
$ sudo /home/voyager/workspace/oss/build_qemu/qemu-system-arm -machine mps2-an385 -m 16 -monitor null -semihosting --semihosting-config enable=on,target=native -kernel ./build/RTOSDemo.axf -serial stdio -nographic 2>&1 | tee -a qemu.log
mps2.c(655) add_extra_mmio_an385 added to 28000000
mps2.c(783) udp_recvfromdummy 
[0000] main_tcp.c(22) main_tcp 09:18:17, Dec 20 2023(51a0cff96d5468ad2355cf219d72f0f524ecdb2b-dirty) fr:0x1c7 
[0000] NetworkInterface.c(139) pxFillInterfaceDescriptor mac:0 fr:0x5583 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(441) xPortSysTickHandler  fr:0xfffffffd 
[01fc] port.c(405) xPortPendSVHandler  fr:0xfffffffd 
```

## qemu内の割り込み発生の仕組み
qemuをデバッグしてこれらの割り込みが発生するときに
どのような動作になっているのか調べてみます。

```shell
$ qemu$ fgrep "Taking exception" . -wnrI
./target/arm/helper.c:10399:        qemu_log_mask(CPU_LOG_INT, "Taking exception %d [%s] on CPU %d\n",
```

```c
10360 void arm_log_exception(CPUState *cs)
10361 {
10362     int idx = cs->exception_index;
```

gdbで見るときはそれ専用のgdbinitを作っておくと楽です。

.gdbinit: FreeRTOSをデバッグする時
```gd
target remote localhost:12345
set pagination off
set logging on
file build/RTOSDemo.axf
set confirm off

define es
 stepi
 disas $pc,+10
end
```

.qemugdbinit: QEMU自体をデバッグするとき
```gdscript
set pagination off
set logging on
file /home/voyager/workspace/oss/build_qemu/qemu-system-arm
#b main
b mps2_common_init
r -machine mps2-an385 -m 16 -monitor null -semihosting --semihosting-config enable=on,target=native -kernel ./build/RTOSDemo.axf -serial stdio -nographic
```

こうすることで以下の手順で別々のデバッグができます。

FreeRTOSをデバッグする時
```
sudo /home/voyager/workspace/oss/build_qemu/qemu-system-arm -machine mps2-an385 -m 16 -monitor null -semihosting --semihosting-config enable=on,target=native -kernel ./build/RTOSDemo.axf -serial stdio -nographic 2>&1 | tee -a qemu.log
```
別ターミナルで
```
gdb
```

QEMUをデバッグする時
```
sudo gdb -ix=.qemugdbinit
```

今回はQEMU自体をデバッグしたいので以下のような感じでデバッグを行います。
```gdscript
$ sudo gdb -ix=.qemugdbinit 
...
(gdb) l arm_log_exception
10356	
10357	    return target_el;
10358	}
10359	
10360	void arm_log_exception(CPUState *cs)
10361	{
10362	    int idx = cs->exception_index;
10363	
10364	    if (qemu_loglevel_mask(CPU_LOG_INT)) {
10365	        const char *exc = NULL;
(gdb) b 10362 if (cs->exception_index == 5)
Breakpoint 2 at 0x555555da3a48: file ../qemu/target/arm/helper.c, line 10362.
(gdb) c
...
Thread 4 "qemu-system-arm" hit Breakpoint 2, arm_log_exception (cs=0x555557945c00) at ../qemu/target/arm/helper.c:10362
10362	    int idx = cs->exception_index;
(gdb) bt 
#0  arm_log_exception (cs=0x555557945c00) at ../qemu/target/arm/helper.c:10362
#1  0x0000555555e4dedc in arm_v7m_cpu_do_interrupt (cs=0x555557945c00) at ../qemu/target/arm/tcg/m_helper.c:2189
#2  0x0000555555dae464 in arm_v7m_cpu_exec_interrupt (cs=0x555557945c00, interrupt_request=2) at ../qemu/target/arm/tcg/cpu32.c:120
#3  0x0000555555f61164 in cpu_handle_interrupt (cpu=0x555557945c00, last_tb=0x7fff6fffe930) at ../qemu/accel/tcg/cpu-exec.c:865
#4  0x0000555555f61677 in cpu_exec_loop (cpu=0x555557945c00, sc=0x7fff6fffe9b0) at ../qemu/accel/tcg/cpu-exec.c:974
#5  0x0000555555f6170f in cpu_exec_setjmp (cpu=0x555557945c00, sc=0x7fff6fffe9b0) at ../qemu/accel/tcg/cpu-exec.c:1058
#6  0x0000555555f61796 in cpu_exec (cpu=0x555557945c00) at ../qemu/accel/tcg/cpu-exec.c:1084
#7  0x0000555555f87dae in tcg_cpus_exec (cpu=0x555557945c00) at ../qemu/accel/tcg/tcg-accel-ops.c:76
#8  0x0000555555f88497 in mttcg_cpu_thread_fn (arg=0x555557945c00) at ../qemu/accel/tcg/tcg-accel-ops-mttcg.c:95
#9  0x000055555618ec4b in qemu_thread_start (args=0x55555799bb70) at ../qemu/util/qemu-thread-posix.c:541
#10 0x00007ffff7884897 in start_thread () at /lib64/libc.so.6
#11 0x00007ffff790b6bc in clone3 () at /lib64/libc.so.6
(gdb) f 2
#2  0x0000555555dae464 in arm_v7m_cpu_exec_interrupt (cs=0x555557945c00, interrupt_request=2) at ../qemu/target/arm/tcg/cpu32.c:120
120	        cc->tcg_ops->do_interrupt(cs);
```

cc->tcg_ops->do_interrupt(cs)がコールされて割り込みが発生しています。
次に割り込みハンドラの決定を見ていきます。
```
qemu$ fgrep "loaded new PC" . -wnrI 
./target/arm/tcg/m_helper.c:712:    qemu_log_mask(CPU_LOG_INT, "...loaded new PC 0x%x\n", *pvec);
```
```c
 655 static bool arm_v7m_load_vector(ARMCPU *cpu, int exc, bool targets_secure,
 656                                 uint32_t *pvec)
...
 712     qemu_log_mask(CPU_LOG_INT, "...loaded new PC 0x%x\n", *pvec); 
```
`arm_v7m_load_vector`で決定されるようです。
ブレークポイントを仕掛けてみると
```gdscript
(gdb) b arm_v7m_load_vector
Breakpoint 3 at 0x555555e4a0bd: file ../qemu/target/arm/tcg/m_helper.c, line 657.
(gdb) c
Continuing.
mps2.c(798) udp_recvfromdummy epoll_wait
[Thread 0x7ffff59ff6c0 (LWP 197519) exited]

Thread 4 "qemu-system-arm" hit Breakpoint 3, arm_v7m_load_vector (cpu=0x555557945c00, exc=15, targets_secure=false, pvec=0x7fff6fffe81c)
    at ../qemu/target/arm/tcg/m_helper.c:657
657	{
```
なにやら怪しい引数があります。
試しに変更してみましょう。
```gdscript
(gdb) set exc=29
(gdb) del
Delete all breakpoints? (y or n) y
(gdb) c
Continuing.
[01fc] NetworkInterface.c(301) ether_rx_handler  fr:0xfffffffd 
```
ビンゴです。
つまり、割り込みが発生し、かつ`arm_v7m_load_vector`の第2引数が29で呼ばれるようにすれば
意図した割り込みハンドラが動作することがわかります。

バックトレースを見て順に調べていきます。
```
#0  arm_v7m_load_vector (cpu=0x555557945c00, exc=15, targets_secure=false, pvec=0x7fff6fffe81c) at ../qemu/target/arm/tcg/m_helper.c:657
#1  0x0000555555e4a9c7 in v7m_exception_taken (cpu=0x555557945c00, lr=4294967293, dotailchain=false, ignore_stackfaults=false)
    at ../qemu/target/arm/tcg/m_helper.c:950
#2  0x0000555555e4e898 in arm_v7m_cpu_do_interrupt (cs=0x555557945c00) at ../qemu/target/arm/tcg/m_helper.c:2420
#3  0x0000555555dae464 in arm_v7m_cpu_exec_interrupt (cs=0x555557945c00, interrupt_request=2) at ../qemu/target/arm/tcg/cpu32.c:120
#4  0x0000555555f61164 in cpu_handle_interrupt (cpu=0x555557945c00, last_tb=0x7fff6fffe930) at ../qemu/accel/tcg/cpu-exec.c:865
#5  0x0000555555f61677 in cpu_exec_loop (cpu=0x555557945c00, sc=0x7fff6fffe9b0) at ../qemu/accel/tcg/cpu-exec.c:974
#6  0x0000555555f6170f in cpu_exec_setjmp (cpu=0x555557945c00, sc=0x7fff6fffe9b0) at ../qemu/accel/tcg/cpu-exec.c:1058
#7  0x0000555555f61796 in cpu_exec (cpu=0x555557945c00) at ../qemu/accel/tcg/cpu-exec.c:1084
#8  0x0000555555f87dae in tcg_cpus_exec (cpu=0x555557945c00) at ../qemu/accel/tcg/tcg-accel-ops.c:76
#9  0x0000555555f88497 in mttcg_cpu_thread_fn (arg=0x555557945c00) at ../qemu/accel/tcg/tcg-accel-ops-mttcg.c:95
#10 0x000055555618ec4b in qemu_thread_start (args=0x55555799bb70) at ../qemu/util/qemu-thread-posix.c:541
#11 0x00007ffff7884897 in start_thread () at /lib64/libc.so.6
#12 0x00007ffff790b6bc in clone3 () at /lib64/libc.so.6
```

該当の関数(`v7m_exception_taken`)でexcの決定は以下のみです。
```c
 843     int exc;          
 844     bool push_failed = false;
 845         
 846     armv7m_nvic_get_pending_irq_info(env->nvic, &exc, &targets_secure);
```

`armv7m_nvic_get_pending_irq_info`
```c
 810 void armv7m_nvic_get_pending_irq_info(NVICState *s,
 811                                       int *pirq, bool *ptargets_secure)
 812 {   
 813     const int pending = s->vectpending;
...
 823     *pirq = pending;
 824 }
```
env->nvic->vectpendingがハンドラを決定する要因のようです。

構造体まるごとコピーとかでなければ普通に`=`で設定しているはずなのでgrepして調べていきます。
```
qemu$ grep vectpending . -wnrI | grep -E "vectpending = "
./hw/intc/armv7m_nvic.c:279:    s->vectpending = pend_irq;
./hw/intc/armv7m_nvic.c:329:    s->vectpending = pend_irq;
```
secureかno secureかの違いでどちらも同じ用途の関数の様です。

どちらが使われているか見ていきます。

```gdscript
(gdb) del
Delete all breakpoints? (y or n) y
(gdb) b nvic_recompute_state
Breakpoint 5 at 0x555555eb050a: file ../qemu/hw/intc/armv7m_nvic.c, line 293.
(gdb) b nvic_recompute_state_secure
Breakpoint 6 at 0x555555eb02d3: file ../qemu/hw/intc/armv7m_nvic.c, line 230.
(gdb) set $bcnt=0
(gdb) commands 5
Type commands for breakpoint(s) 5, one per line.
End with a line saying just "end".
>set $bcnt=$bcnt+1
>if ($bcnt < 100)
 >c
 >end
>end
(gdb) commands 6
Type commands for breakpoint(s) 6, one per line.
End with a line saying just "end".
>set $bcnt=$bcnt+1
>if ($bcnt < 100)
 >c
 >end
>end
(gdb) c
Continuing.
[Switching to Thread 0x7ffff7600ac0 (LWP 197474)]

Thread 1 "qemu-system-arm" hit Breakpoint 5, nvic_recompute_state (s=0x555557815a90) at ../qemu/hw/intc/armv7m_nvic.c:293
293	    int pend_prio = NVIC_NOEXC_PRIO;
[Switching to Thread 0x7fff6ffff6c0 (LWP 197520)]
...
```
nosecureのみしか使われていないようです。
```shell
$ grep nvic_recompute_state gdb.txt| sort  |uniq -c
     34 Thread 1 "qemu-system-arm" hit Breakpoint 5, nvic_recompute_state (s=0x555557815a90) at ../qemu/hw/intc/armv7m_nvic.c:293
     66 Thread 4 "qemu-system-arm" hit Breakpoint 5, nvic_recompute_state (s=0x555557815a90) at ../qemu/hw/intc/armv7m_nvic.c:293
```

`NVICState->vectors[IRQ番号]`に情報が必要なようです。
```c
 290 static void nvic_recompute_state(NVICState *s)
...
 310         VecInfo *vec = &s->vectors[i];
 311 
 312         if (vec->enabled && vec->pending && vec->prio < pend_prio) {
 313             pend_prio = vec->prio;
 314             pend_irq = i;
```

gdbで見てみても
タイマー関連は情報が設定されているようですが
ethernet側は設定されていません。
```gdscript
(gdb) p env->nvic->vectors[29]
$3 = {prio = 0, enabled = 0 '\000', pending = 0 '\000', active = 0 '\000', level = 0 '\000'}
(gdb) p env->nvic->vectors[15]
$4 = {prio = 255, enabled = 1 '\001', pending = 0 '\000', active = 1 '\001', level = 0 '\000'}
```

設定は以下の1箇所だけのようです。
```
qemu$ grep "enabled = " hw/intc/armv7m_nvic.c -n
...
2366:                s->vectors[startvec + i].enabled = setval;...
```

```c
2339 static MemTxResult nvic_sysreg_write(void *opaque, hwaddr addr,
2340                                      uint64_t value, unsigned size,
2341                                      MemTxAttrs attrs)
2342 {
2343     NVICState *s = (NVICState *)opaque;
2344     uint32_t offset = addr;
...
2355     switch (offset) {
2360     case 0x180 ... 0x1bf: /* NVIC Clear enable */
2361         startvec = 8 * (offset - 0x180) + NVIC_FIRST_IRQ;
2362 
2363         for (i = 0, end = size * 8; i < end && startvec + i < s->num_irq; i++) {
2364             if (value & (1 << i) &&
2365                 (attrs.secure || s->itns[startvec + i])) {
2366                 s->vectors[startvec + i].enabled = setval;
2367             }
2368         }
2369         nvic_irq_update(s);

```

2363 にブレークを仕掛けてみるが入ってこない。
自分で設定する必要があるっぽい
ちなみに他の割り込みは以下で設定されているみたい
```c
2578 static void armv7m_nvic_reset(DeviceState *dev)
...
2593     s->vectors[ARMV7M_EXCP_PENDSV].enabled = 1;
2594     s->vectors[ARMV7M_EXCP_SYSTICK].enabled = 1;
```

FreeRTOSから有効にする必要があるっぽい？？？

[これ](https://tekuteku-embedded.xyz/2021/10/04/%E5%89%B2%E3%82%8A%E8%BE%BC%E3%81%BF%E5%87%A6%E7%90%86%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B/)にもそんなふうに書いてあるな

IRQをFreeRTOSから有効にしてみる。
29ではなく13っぽい
```diff
diff --git a/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c b/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c
index 28c657c8..e1c4b1db 100644
--- a/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c
+++ b/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c
@@ -167,6 +167,8 @@ void Default_Handler6( void )
     }
 }
 
+extern void ether_rx_handler(void);
+
 const uint32_t * isr_vector[] __attribute__( ( section( ".isr_vector" ) ) ) =
 {
     ( uint32_t * ) &_estack,
@@ -198,12 +200,13 @@ const uint32_t * isr_vector[] __attribute__( ( section( ".isr_vector" ) ) ) =
     0,                                   /* Dial Timer */
     0,                                   /* SPI0 SPI1 */
     0,                                   /* uart overflow 1, 2,3 */
-    0,                                   /* Ethernet   13 */
+    (uint32_t*) &ether_rx_handler,       /* Ethernet   13 */
 };
 
 void _start( void )
 {
     uart_init();
+    NVIC_EnableIRQ(13);
     main( 0, 0 );
     exit( 0 );
 }
```

書き込み先は0xe000e100らしい
```gdscript
(gdb) b __NVIC_EnableIRQ 
Breakpoint 1 at 0xe4: file /home/voyager/workspace/freertos/freertos_cm3/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/CMSIS/core_cm3.h, line 1510.
(gdb) c
Continuing.
...
Breakpoint 1, __NVIC_EnableIRQ (IRQn=ETHERNET_IRQn) at /home/voyager/workspace/freertos/freertos_cm3/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/CMSIS/core_cm3.h:1510
1510	  if ((int32_t)(IRQn) >= 0)
(gdb) n
1513	    NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
(gdb) p/x &NVIC->ISER[(((uint32_t)IRQn) >> 5UL)]
$3 = 0xe000e100

```
enabledは1になった。
```gdscript
(gdb) bt 
#0  nvic_sysreg_write (opaque=0x555557815a90, addr=256, value=8192, size=4, attrs=...) at ../qemu/hw/intc/armv7m_nvic.c:2363
#1  0x0000555555f1e2fb in memory_region_write_with_attrs_accessor
    (mr=0x555557816c50, addr=256, value=0x7fff6fffdf08, size=4, shift=0, mask=4294967295, attrs=...) at ../qemu/system/memory.c:518
#2  0x0000555555f1e517 in access_with_adjusted_size
    (addr=256, value=0x7fff6fffdf08, size=4, access_size_min=1, access_size_max=4, access_fn=0x555555f1e1ff <memory_region_write_with_attrs_accessor>, mr=0x555557816c50, attrs=...) at ../qemu/system/memory.c:573
...
(gdb) l
2358	        setval = 1;
2359	        /* fall through */
2360	    case 0x180 ... 0x1bf: /* NVIC Clear enable */
2361	        startvec = 8 * (offset - 0x180) + NVIC_FIRST_IRQ;
2362	
2363	        for (i = 0, end = size * 8; i < end && startvec + i < s->num_irq; i++) {
2364	            if (value & (1 << i) &&
2365	                (attrs.secure || s->itns[startvec + i])) {
2366	                s->vectors[startvec + i].enabled = setval;
2367	            }
(gdb) p s
$22 = (NVICState *) 0x555557815a90
(gdb) p s->vectors[29]
$23 = {prio = 0, enabled = 1 '\001', pending = 0 '\000', active = 0 '\000', level = 0 '\000'}
```
ちなみにここの`startvec`が16なので13を設定すると29になる。

次にpendingを設定している箇所を探す。
```shell
qemu$ grep "pending = " -n hw/intc/armv7m_nvic.c  | grep -v ARMV7M_EXCP_ | grep -w pending
519:        vec->pending = 0;
659:        vec->pending = 1;
756:        vec->pending = 1;
773:    const int pending = s->vectpending;
793:    vec->pending = 0;
813:    const int pending = s->vectpending;
901:        vec->pending = 1;
2391:                s->vectors[startvec + i].pending = setval;
```
候補は4箇所なので順に見ていく
```c
 524 static void do_armv7m_nvic_set_pending(void *opaque, int irq, bool secure,
 525                                        bool derived)
...
 674 void armv7m_nvic_set_pending_lazyfp(NVICState *s, int irq, bool secure)
...
 826 int armv7m_nvic_complete_irq(NVICState *s, int irq, bool secure)
...
2339 static MemTxResult nvic_sysreg_write(void *opaque, hwaddr addr,
2340                                      uint64_t value, unsigned size,
2341                                      MemTxAttrs attrs)

```
`nvic_sysreg_write`はソフトからの割り込み発生っぽいので残りにブレークかけてみる

```gdscript
Thread 1 "qemu-system-arm" hit Breakpoint 1, do_armv7m_nvic_set_pending (opaque=0x555557815a90, irq=29, secure=false, derived=false)
    at ../qemu/hw/intc/armv7m_nvic.c:543
543	    NVICState *s = (NVICState *)opaque;
...
(gdb) 
658	    if (!vec->pending) {
659	        vec->pending = 1;
660	        nvic_irq_update(s);
661	    }
(gdb) b 659
Breakpoint 4 at 0x555555eb110a: file ../qemu/hw/intc/armv7m_nvic.c, line 659.
(gdb) c
Continuing.
mps2.c(802) udp_recvfromdummy epoll_wait
[Thread 0x7ffff59ff6c0 (LWP 207444) exited]

Thread 1 "qemu-system-arm" hit Breakpoint 4, do_armv7m_nvic_set_pending (opaque=0x555557815a90, irq=29, secure=false, derived=false)
    at ../qemu/hw/intc/armv7m_nvic.c:659
659	        vec->pending = 1;
(gdb) n
660	        nvic_irq_update(s);
(gdb) p s->vectors[29].pending 
$3 = 1 '\001'
```
何もしなくても勝手に1になったぞ
で、その後0初期化。めっちゃ意味ない
```gdscript
Old value = 1 '\001'
New value = 0 '\000'
0x00007ffff795528a in __memset_avx2_unaligned_erms () from /lib64/libc.so.6
(gdb) bt 
#0  0x00007ffff795528a in __memset_avx2_unaligned_erms () at /lib64/libc.so.6
#1  0x0000555555eb5d84 in armv7m_nvic_reset (dev=0x555557815a90) at ../qemu/hw/intc/armv7m_nvic.c:2583
#2  0x0000555555f92fff in device_transitional_reset (obj=0x555557815a90) at ../qemu/hw/core/qdev.c:780
#3  0x0000555555f94d56 in resettable_phase_hold (obj=0x555557815a90, opaque=0x0, type=RESET_TYPE_COLD) at ../qemu/hw/core/resettable.c:182
#4  0x0000555555f8dc9e in bus_reset_child_foreach (obj=0x55555781b5d0, cb=0x555555f94c1f <resettable_phase_hold>, opaque=0x0, type=RESET_TYPE_COLD)
    at ../qemu/hw/core/bus.c:97
#5  0x0000555555f94a42 in resettable_child_foreach
    (rc=0x5555575d4b10, obj=0x55555781b5d0, cb=0x555555f94c1f <resettable_phase_hold>, opaque=0x0, type=RESET_TYPE_COLD) at ../qemu/hw/core/resettable.c:96
#6  0x0000555555f94cdc in resettable_phase_hold (obj=0x55555781b5d0, opaque=0x0, type=RESET_TYPE_COLD) at ../qemu/hw/core/resettable.c:173
#7  0x0000555555f948e1 in resettable_assert_reset (obj=0x55555781b5d0, type=RESET_TYPE_COLD) at ../qemu/hw/core/resettable.c:60
#8  0x0000555555f94813 in resettable_reset (obj=0x55555781b5d0, type=RESET_TYPE_COLD) at ../qemu/hw/core/resettable.c:45
#9  0x0000555555f950b5 in resettable_cold_reset_fn (opaque=0x55555781b5d0) at ../qemu/hw/core/resettable.c:268
#10 0x0000555555f934ec in qemu_devices_reset (reason=SHUTDOWN_CAUSE_NONE) at ../qemu/hw/core/reset.c:84
#11 0x0000555555c61633 in qemu_system_reset (reason=SHUTDOWN_CAUSE_NONE) at ../qemu/system/runstate.c:494
(gdb) f 1
#1  0x0000555555eb5d84 in armv7m_nvic_reset (dev=0x555557815a90) at ../qemu/hw/intc/armv7m_nvic.c:2583
2583	    memset(s->vectors, 0, sizeof(s->vectors));
(gdb) p s->vectors[29]
$8 = {prio = 0, enabled = 0 '\000', pending = 0 '\000', active = 0 '\000', level = 0 '\000'}


```
他のはその後来た
`vPortSVCHandler`向けはソフトウェア割り込みみたい
```gdscript
Thread 4 "qemu-system-arm" hit Breakpoint 1, do_armv7m_nvic_set_pending (opaque=0x555557815a90, irq=11, secure=false, derived=false)
    at ../qemu/hw/intc/armv7m_nvic.c:543
543	    NVICState *s = (NVICState *)opaque;
(gdb) bt 
#0  do_armv7m_nvic_set_pending (opaque=0x555557815a90, irq=11, secure=false, derived=false) at ../qemu/hw/intc/armv7m_nvic.c:543
#1  0x0000555555eb1153 in armv7m_nvic_set_pending (s=0x555557815a90, irq=11, secure=false) at ../qemu/hw/intc/armv7m_nvic.c:666
#2  0x0000555555e4e286 in arm_v7m_cpu_do_interrupt (cs=0x55555792cc80) at ../qemu/target/arm/tcg/m_helper.c:2241
#3  0x0000555555f60f91 in cpu_handle_exception (cpu=0x55555792cc80, ret=0x7fff6fffe91c) at ../qemu/accel/tcg/cpu-exec.c:753
#4  0x0000555555f6170b in cpu_exec_loop (cpu=0x55555792cc80, sc=0x7fff6fffe9b0) at ../qemu/accel/tcg/cpu-exec.c:970
#5  0x0000555555f61782 in cpu_exec_setjmp (cpu=0x55555792cc80, sc=0x7fff6fffe9b0) at ../qemu/accel/tcg/cpu-exec.c:1058
#6  0x0000555555f61809 in cpu_exec (cpu=0x55555792cc80) at ../qemu/accel/tcg/cpu-exec.c:1084
#7  0x0000555555f87e21 in tcg_cpus_exec (cpu=0x55555792cc80) at ../qemu/accel/tcg/tcg-accel-ops.c:76
#8  0x0000555555f8850a in mttcg_cpu_thread_fn (arg=0x55555792cc80) at ../qemu/accel/tcg/tcg-accel-ops-mttcg.c:95
#9  0x000055555618ecbe in qemu_thread_start (args=0x555557932a10) at ../qemu/util/qemu-thread-posix.c:541
#10 0x00007ffff7884897 in start_thread () at /lib64/libc.so.6
#11 0x00007ffff790b6bc in clone3 () at /lib64/libc.so.6
```
タイマーはqemuから来てる
```gdscript
(gdb) bt 
#0  do_armv7m_nvic_set_pending (opaque=0x555557815a90, irq=15, secure=false, derived=false) at ../qemu/hw/intc/armv7m_nvic.c:553
#1  0x0000555555eb1153 in armv7m_nvic_set_pending (s=0x555557815a90, irq=15, secure=false) at ../qemu/hw/intc/armv7m_nvic.c:666
#2  0x0000555555eb5fc0 in nvic_systick_trigger (opaque=0x555557815a90, n=0, level=1) at ../qemu/hw/intc/armv7m_nvic.c:2664
#3  0x0000555555f9521d in qemu_set_irq (irq=0x5555579294d0, level=1) at ../qemu/hw/core/irq.c:44
#4  0x0000555555b93204 in qemu_irq_pulse (irq=0x5555579294d0) at /home/voyager/workspace/oss/qemu/include/hw/irq.h:22
#5  0x0000555555b9361d in systick_timer_tick (opaque=0x555557817b30) at ../qemu/hw/timer/armv7m_systick.c:55
#6  0x0000555555917b05 in ptimer_trigger (s=0x5555579f1bf0) at ../qemu/hw/core/ptimer.c:48
#7  0x0000555555917eba in ptimer_tick (opaque=0x5555579f1bf0) at ../qemu/hw/core/ptimer.c:195
#8  0x00005555561ac512 in timerlist_run_timers (timer_list=0x555557511840) at ../qemu/util/qemu-timer.c:576
#9  0x00005555561ac5bc in qemu_clock_run_timers (type=QEMU_CLOCK_VIRTUAL) at ../qemu/util/qemu-timer.c:590

```
コードはこんな感じ
```c
(gdb) f 4
#4  0x0000555555b93204 in qemu_irq_pulse (irq=0x5555579294d0) at /home/voyager/workspace/oss/qemu/include/hw/irq.h:22
22	    qemu_set_irq(irq, 1);
(gdb) l
17	    qemu_set_irq(irq, 0);
18	}
19	
20	static inline void qemu_irq_pulse(qemu_irq irq)
21	{
22	    qemu_set_irq(irq, 1);
23	    qemu_set_irq(irq, 0);
24	}
25	
26	/* Returns an array of N IRQs. Each IRQ is assigned the argument handler and
```
のでタイマーと同じような感じでやればできそう？

こんな感じにしてみると
```c
830 static void send_to_freertos(uint8_t *buf, size_t size)
831 {
832     (void)buf;
833     CPUState *cpu = CPU(ARM_CPU(first_cpu));
834     NVICState *nvic;
835     (void)nvic;
836 
837     if (eth_irq)
838     {
839         d("irq fire cpu:%p n:%lu", cpu, size);
840         qemu_mutex_lock_iothread();
841 
842         qemu_set_irq(eth_irq, 1);
843         cpu_interrupt(cpu, CPU_INTERRUPT_HARD);
844         qemu_set_irq(eth_irq, 0);
845         qemu_mutex_unlock_iothread();
846     }
847 }
```

通った
```
mps2.c(839) send_to_freertos irq fire cpu:0x562f1234d880 n:42
[2d50] NetworkInterface.c(301) ether_rx_handler  fr:0xfffffffd 
mps2.c(839) send_to_freertos irq fire cpu:0x562f1234d880 n:42
[2d50] NetworkInterface.c(301) ether_rx_handler  fr:0xfffffffd 
mps2.c(839) send_to_freertos irq fire cpu:0x562f1234d880 n:42
[2d50] NetworkInterface.c(301) ether_rx_handler  fr:0xfffffffd 
```

ここまでのコード
- [FreeRTOSに割り込みハンドラ追加](https://github.com/tyano461/freertos_cm3/commit/840f45c475bd672c003217cc75c6c886b3077693)
- [FreeRTOSのNICドライバ](https://github.com/tyano461/freertos_tcpv4/commit/d59a518586ed7930dbab337684e0306fac61cdc1)
- [改造QEMU](https://github.com/tyano461/qemu_an385_ex/commit/10b437079c71ba9cef690631dbd0511eb68b1c85)

----
# 参考
https://sebastienbourdelin.com/2021/06/16/writing-a-custom-device-for-qemu/
https://tekuteku-embedded.xyz/2021/10/04/%E5%89%B2%E3%82%8A%E8%BE%BC%E3%81%BF%E5%87%A6%E7%90%86%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B/