QEMUのRaspberry Pi 3モデルでタスク切り替え

QEMUのRaspbery Pi 3モデルを使って、タスクの切り替えを確認してみました。

今度はAArch64でタスクの切り替えをやってみました。

ソースコードです。
* https://github.com/eggman/raspberrypi/tree/master/qemu-raspi3/task01
* https://github.com/eggman/raspberrypi/tree/master/qemu-raspi3/task02

# タスクAからタスクBを起動するだけ

一番シンプルにタスクBを起動してみます。

タスクBを起動するは、「PCとスタックポインタとレジスタ」をタスクBに切り替えれば良いです。

タスクB

```
void task_b(void)
{
    uart_puts("task B ");
    uart_puthex(get_sp()); uart_putc('\n');
    switch_flg = 0;
    for (;;) {
        io_halt();
    }
}
```

タスクBのスタック領域を作る
タスクBのスタックは0x40000の固定値としています
aarch64ではx30が関数から戻りアドレスです、ここに、task_b()のアドレスを設定します。
戻り値として使用済みスタックの先頭を返す。

レジスタ番号と対応している数値を代入しているのは、デバッグのためです。

```
uint64_t *setup_task_b_stack(void)
{
    uint64_t *p =  (uint64_t *) 0x40000;

    uart_puthex((uint64_t) p);
    uart_putc('\n');

    /* prepare data in stack */
    p--;
    *p = 0x0101010101010101ULL; /* x1 */
    p--;
    *p = 0x00000000000000000LL; /* x0 */
    p--;
    *p = 0x0303030303030303ULL; /* x3 */
    p--;
    *p = 0x0202020202020202ULL; /* x2 */
    p--;
    *p = 0x0505050505050505ULL; /* x5 */
    p--;
    *p = 0x0404040404040404ULL; /* x4 */
    p--;
    *p = 0x0707070707070707ULL; /* x7 */
    p--;
    *p = 0x0606060606060606ULL; /* x6 */
    p--;
    *p = 0x0909090909090909ULL; /* x9 */
    p--;
    *p = 0x0808080808080808ULL; /* x8 */
    p--;
    *p = 0x1111111111111111ULL; /* x11 */
    p--;
    *p = 0x1010101010101010ULL; /* x10 */
    p--;
    *p = 0x1313131313131313ULL; /* x13 */
    p--;
    *p = 0x1212121212121212ULL; /* x12 */
    p--;
    *p = 0x1515151515151515ULL; /* x15 */
    p--;
    *p = 0x1414141414141414ULL; /* x14 */
    p--;
    *p = 0x1717171717171717ULL; /* x17 */
    p--;
    *p = 0x1616161616161616ULL; /* x16 */
    p--;
    *p = 0x1919191919191919ULL; /* x19 */
    p--;
    *p = 0x1818181818181818ULL; /* x18 */
    p--;
    *p = 0x2121212121212121ULL; /* x21 */
    p--;
    *p = 0x2020202020202020ULL; /* x20 */
    p--;
    *p = 0x2323232323232323ULL; /* x23 */
    p--;
    *p = 0x2222222222222222ULL; /* x22 */
    p--;
    *p = 0x2525252525252525ULL; /* x25 */
    p--;
    *p = 0x2424242424242424ULL; /* x24 */
    p--;
    *p = 0x2727272727272727ULL; /* x27 */
    p--;
    *p = 0x2626262626262626ULL; /* x26 */
    p--;
    *p = 0x2929292929292929ULL; /* x29 */
    p--;
    *p = 0x2828282828282828ULL; /* x28 */
    p--;
    *p = ( uint64_t ) 0x00;     /* xzr - has no effect, used so there are an even number of registers. */
    p--;
    *p = ( uint64_t ) task_b;   /* x30 - procedure call link register. */

    uart_dump(p);
    uart_puthex((uint64_t) p);
    uart_putc('\n');
    return p;
}

```

タスクを切り替える関数をアセンブラで記述します。

引数で起動するスタックの先頭を渡す。
スタックポインタを切り替える。
スタックからレジスタをリストアする。
x30がtaskb()になっているので、ret命令でtask_b()が起動する


```
void switch_task(uint64_t *next_task_top_of_stack);
```

```
.globl switch_task
switch_task:

    /* switch sp */
    mov    sp, x0

    /* restore context */
    ldp   x30, xzr, [sp], #16
    ldp   x28, x29, [sp], #16
    ldp   x26, x27, [sp], #16
    ldp   x24, x25, [sp], #16
    ldp   x22, x23, [sp], #16
    ldp   x20, x21, [sp], #16
    ldp   x18, x19, [sp], #16
    ldp   x16, x17, [sp], #16
    ldp   x14, x15, [sp], #16
    ldp   x12, x13, [sp], #16
    ldp   x10, x11, [sp], #16
    ldp   x8,  x9,  [sp], #16
    ldp   x6,  x7,  [sp], #16
    ldp   x4,  x5,  [sp], #16
    ldp   x2,  x3,  [sp], #16
    ldp   x0,  x1,  [sp], #16

    ret
```

kernel_main()関数
setup_task_b_stack()でスタックを生成する。
UART入力の割り込みハンドラでswitch_flgを１になったら、switch_task()でタスクAを起動する。

```
void kernel_main(void)
{
    uint64_t *task_b_top_of_stack;
    uart_puts("int01\n");

    // enable UART RX interrupt.
    *UART0_IMSC = 1 << 4;

    // UART interrupt routing.
    *IRQ_ENABLE2 = 1 << 25;

    // IRQ routeing to CORE0.
    *GPU_INTERRUPTS_ROUTING = 0x00;

    enable_irq();

    task_b_top_of_stack = setup_task_b_stack();

    uart_puts("task A ");
    uart_puthex(get_sp()); uart_putc('\n');
    while (1) {
        if(switch_flg) {
            switch_task(task_b_top_of_stack);
        }
        io_halt();
    }
}
```

動作例

```
qemu-system-aarch64 -M raspi3 -m 128 -serial mon:stdio -nographic -kernel kernel.elf
int01
0000000000040000
000000000003FF00: 80 19 08 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000000003FF10: 28 28 28 28  28 28 28 28  29 29 29 29  29 29 29 29  (((((((())))))))
000000000003FF20: 26 26 26 26  26 26 26 26  27 27 27 27  27 27 27 27  &&&&&&&&''''''''
000000000003FF30: 24 24 24 24  24 24 24 24  25 25 25 25  25 25 25 25  $$$$$$$$%%%%%%%%
000000000003FF40: 22 22 22 22  22 22 22 22  23 23 23 23  23 23 23 23  """"""""########
000000000003FF50: 20 20 20 20  20 20 20 20  21 21 21 21  21 21 21 21          !!!!!!!!
000000000003FF60: 18 18 18 18  18 18 18 18  19 19 19 19  19 19 19 19  ................
000000000003FF70: 16 16 16 16  16 16 16 16  17 17 17 17  17 17 17 17  ................
000000000003FF80: 14 14 14 14  14 14 14 14  15 15 15 15  15 15 15 15  ................
000000000003FF90: 12 12 12 12  12 12 12 12  13 13 13 13  13 13 13 13  ................
000000000003FFA0: 10 10 10 10  10 10 10 10  11 11 11 11  11 11 11 11  ................
000000000003FFB0: 08 08 08 08  08 08 08 08  09 09 09 09  09 09 09 09  ................
000000000003FFS0: 06 06 06 06  06 06 06 06  07 07 07 07  07 07 07 07  ................
000000000003FFD0: 04 04 04 04  04 04 04 04  05 05 05 05  05 05 05 05  ................
000000000003FFE0: 02 02 02 02  02 02 02 02  03 03 03 03  03 03 03 03  ................
000000000003FFF0: 00 00 00 00  00 00 00 00  01 01 01 01  01 01 01 01  ................
0000000000040000: 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000000003FF00
task A 000000000007FFE0
 c_irq_handler
000000000007FF20
task B 000000000003FFF0
```

割り込みを契機に、タスクBが起動して、スタックポインタが0x3FFF0になっていることが確認できます。

# タスクAとタスクBを行ったり来たりする。

次にタスクAとタスクBを行ったり来たりしてみます。
タスクAとタスクBを行ったり来たりするには、コンテキストの保持する処理を追加すれば良いです。

switch_task()でSPを切り替える前に、コンテキストを保持するためにレジスタを保存する処理を入れます。
コンテキストを保持したスタックポインタの値をポインタ変数に保存するために、引数を追加し、引数の型をポインタのポインタにします。

```
void switch_task(uint64_t **prev_task_top_of_stack, uint64_t **next_task_top_of_stack);
```

```
.globl switch_task
switch_task:

    /* save context */
    stp   x0,  x1,  [sp, #-16]!
    stp   x2,  x3,  [sp, #-16]!
    stp   x4,  x5,  [sp, #-16]!
    stp   x6,  x7,  [sp, #-16]!
    stp   x8,  x9,  [sp, #-16]!
    stp   x10, x11, [sp, #-16]!
    stp   x12, x13, [sp, #-16]!
    stp   x14, x15, [sp, #-16]!
    stp   x16, x17, [sp, #-16]!
    stp   x18, x19, [sp, #-16]!
    stp   x20, x21, [sp, #-16]!
    stp   x22, x23, [sp, #-16]!
    stp   x24, x25, [sp, #-16]!
    stp   x26, x27, [sp, #-16]!
    stp   x28, x29, [sp, #-16]!
    stp   x30, xzr, [sp, #-16]!

    /* store sp */
    mov   x19, sp
    str   x19, [x0]

    /* switch sp */
    ldr   x19, [x1]
    mov    sp, x19

    /* restore context */
    ldp   x30, xzr, [sp], #16
    ldp   x28, x29, [sp], #16
    ldp   x26, x27, [sp], #16
    ldp   x24, x25, [sp], #16
    ldp   x22, x23, [sp], #16
    ldp   x20, x21, [sp], #16
    ldp   x18, x19, [sp], #16
    ldp   x16, x17, [sp], #16
    ldp   x14, x15, [sp], #16
    ldp   x12, x13, [sp], #16
    ldp   x10, x11, [sp], #16
    ldp   x8,  x9,  [sp], #16
    ldp   x6,  x7,  [sp], #16
    ldp   x4,  x5,  [sp], #16
    ldp   x2,  x3,  [sp], #16
    ldp   x0,  x1,  [sp], #16

    ret
```

これで、タスクを行ったり来たりできます。


task_b()に、タスクAに戻るswitch_task()を追加します。
switch_flgの状態を追加します。

```

void task_b(void)
{   
    for (;;) {
        if (switch_flg == 1 ) {
            uart_puts("task B ");
            uart_puthex(get_sp()); uart_putc('\n');
            switch_flg = 2;
        } else if(switch_flg == 3) {
            switch_task(&task_b_top_of_stack, &task_a_top_of_stack);
        } else {
            io_halt();
        }
    }
}

void c_irq_handler(void)
{   
    char c;
    
    // check inteerupt source
    if (*CORE0_INTERRUPT_SOURCE & (1 << 8)) {
        if (*IRQ_PEND2 & (1 << 25)) {
            if (*UART0_MIS & (1 << 4)) { 
                c = (unsigned char) *UART0_DR; // read for clear tx interrupt.
                uart_putc(c);
                uart_puts(" c_irq_handler ");
                uart_puthex(get_sp()); uart_putc('\n');
                if (switch_flg == 0) {
                    switch_flg = 1;
                } else if (switch_flg == 2 ) {
                    switch_flg = 3;
                }
                return;
            }
        }
    }
    return;
}


void kernel_main(void)
{

    uart_puts("task01\n");

    // enable UART RX interrupt.
    *UART0_IMSC = 1 << 4;

    // UART interrupt routing.
    *IRQ_ENABLE2 = 1 << 25;

    // IRQ routeing to CORE0.
    *GPU_INTERRUPTS_ROUTING = 0x00;

    enable_irq();

    task_b_top_of_stack = setup_task_b_stack();

    uart_puts("task A ");
    uart_puthex(get_sp()); uart_putc('\n');
    while (1) {
        if(switch_flg == 1) {
            switch_task(&task_a_top_of_stack, &task_b_top_of_stack);
        } else if (switch_flg == 3 ) {
            uart_puts("task A ");
            uart_puthex(get_sp()); uart_putc('\n');
            switch_flg = 0;
        } else {
            io_halt();
        }
    }
}  
```

実行例
UARTに一文字入力するごとにタスクAとタスクBを行ったり来たりしています。

```
qemu-system-aarch64 -M raspi3 -m 128 -serial mon:stdio -nographic -kernel kernel.elf
task01
0000000000040000
000000000003FF00: 80 19 08 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000000003FF10: 28 28 28 28  28 28 28 28  29 29 29 29  29 29 29 29  (((((((())))))))
000000000003FF20: 26 26 26 26  26 26 26 26  27 27 27 27  27 27 27 27  &&&&&&&&''''''''
000000000003FF30: 24 24 24 24  24 24 24 24  25 25 25 25  25 25 25 25  $$$$$$$$%%%%%%%%
000000000003FF40: 22 22 22 22  22 22 22 22  23 23 23 23  23 23 23 23  """"""""########
000000000003FF50: 20 20 20 20  20 20 20 20  21 21 21 21  21 21 21 21          !!!!!!!!
000000000003FF60: 18 18 18 18  18 18 18 18  19 19 19 19  19 19 19 19  ................
000000000003FF70: 16 16 16 16  16 16 16 16  17 17 17 17  17 17 17 17  ................
000000000003FF80: 14 14 14 14  14 14 14 14  15 15 15 15  15 15 15 15  ................
000000000003FF90: 12 12 12 12  12 12 12 12  13 13 13 13  13 13 13 13  ................
000000000003FFA0: 10 10 10 10  10 10 10 10  11 11 11 11  11 11 11 11  ................
000000000003FFB0: 08 08 08 08  08 08 08 08  09 09 09 09  09 09 09 09  ................
000000000003FFS0: 06 06 06 06  06 06 06 06  07 07 07 07  07 07 07 07  ................
000000000003FFD0: 04 04 04 04  04 04 04 04  05 05 05 05  05 05 05 05  ................
000000000003FFE0: 02 02 02 02  02 02 02 02  03 03 03 03  03 03 03 03  ................
000000000003FFF0: 00 00 00 00  00 00 00 00  01 01 01 01  01 01 01 01  ................
0000000000040000: 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000000003FF00
task A 000000000007FFD0
 c_irq_handler 000000000006FF50
task B 000000000003FFD0
 c_irq_handler 000000000006FF50
task A 000000000007FFD0
 c_irq_handler 000000000006FF50
task B 000000000003FFD0
 c_irq_handler 000000000006FF50
task A 000000000007FFD0
```

# 参考情報
* [OSのタスク切り替え処理とAArch64(ARM64)の関数呼び出し規約](https://qiita.com/tkato/items/82f095a8f8c55be07cb5)
　今回の記事は、この記事のコードを単体で動作するようにしただけです。