ARMで起動する前にコンソールに文字を出す方法とは

# 起動する前にコンソールに文字を出すにはどうしたらいい？

linux が起動した後であれば、`console=ttyS0,115200` などのオプションで指定する事で、コンソール接続のデバイスを指定することができる。

だが、起動する前のログを表示したかったらどうすればいいの？どうやって表示するの？というあたりを整理してみる。

調査ベースはこのあたり。

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm

# 0. はじめに

MM problemやprintkが動かない場合に有効なdebugging routineではあるが、
あくまでもデバッグ用であり、製品用カーネルでは取り外す事、となっている。

取り扱いは注意が必要ですね！

```c:arch/arm/kernel/debug.S

/*
 * Some debugging routines (useful if you've got MM problems and
 * printk isn't working).  For DEBUGGING ONLY!!!  Do not leave
 * references to these in a production kernel!
 */
```


# 1.起動時にearlyprintk を指定する。

linuxの起動オプションに、`earlyprintk`を指定してあげるとよいっぽい。

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/Documentation/admin-guide/kernel-parameters.txt#n1135

これを指定する事で何が起きるのか？

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm/kernel/early_printk.c

- early_param("earlyprintk", setup_early_printk);
- → setup_early_printk() 
- →→ register_console(&early_console_dev);
- →→→ early_console_write()
- →→→→ printascii()

という流れで、表示する文字のアドレスが、printascii()関数に渡される。

# 2. printasciiで文字を出力しよう

printasciiの定義は、こちら。
なお、ARMのアセンブラの場合、引数はr0に入っている。
今回のケースでは、r0には文字列のポインタ。

- \0 → 処理終了。
- \n → \r\nに変換して文字出力
- それ以外 → 文字出力

だと思います。

```c:arch/arm/kernel/debug.S

#ifndef CONFIG_DEBUG_SEMIHOSTING

ENTRY(printascii)
        addruart_current r3, r1, r2 // 2.1で説明
1:      teq r0, #0
        ldrbne  r1, [r0], #1
        teqne   r1, #0
        reteq   lr
2:      teq     r1, #'\n'
        bne 3f
        mov r1, #'\r'
        waituart r2, r3 // 4.2で説明
        senduart r1, r3 // 4.3で説明
        busyuart r2, r3 // 4.4で説明
        mov r1, #'\n'
3:      waituart r2, r3
        senduart r1, r3
        busyuart r2, r3
        b   1b
ENDPROC(printascii)

＜略＞

#else

ENTRY(printascii)
        mov r1, r0
        mov r0, #0x04       @ SYS_WRITE0
    ARM(    svc #0x123456   )
#ifdef CONFIG_CPU_V7M
    THUMB(  bkpt    #0xab       )
#else
    THUMB(  svc #0xab       )
#endif
        ret lr
ENDPROC(printascii)

```

DEBUG_SEMIHOSTINGが有効か否かで振る舞いが変わっている。

無効である場合には、adduart_current / waituart/ senduart などを駆使して文字を出力しようとしている。これは製品固有の実装になる（後述）。

有効である場合は？ r0レジスタに0x04, r1 レジスタに当該表示しようとしている文字をセットして、ARMモードであれば0x123456 をSVCしている。これは機種問わず利用できる機能で、JTAGなどのデバッガ経由で見ることができる（と思われる）

## 2.1 adduart_currentを確認しておく

全機種共通部の処理となる、adduart_currentを先に解説しておく。

```c:arch/arm/kernel/debug.S
#ifdef CONFIG_MMU
        .macro  addruart_current, rx, tmp1, tmp2
        addruart    \tmp1, \tmp2, \rx // 4.1で説明
        mrc     p15, 0, \rx, c1, c0
        tst     \rx, #1
        moveq       \rx, \tmp1
        movne       \rx, \tmp2
        .endm

#else /* !CONFIG_MMU */
        .macro  addruart_current, rx, tmp1, tmp2
        addruart    \rx, \tmp1, \tmp2
        .endm

#endif /* CONFIG_MMU */

```
 rx [out] tmp1/tmp2のうちアクセス可能なアドレス
 temp1 [tmp] : テンポラリに利用
 temp2 [tmp] : テンポラリに利用

MMUが有効であれば、MMUの状態に応じてPHYS/VIRTを切り替え、
MMUが無効であれば、PHYSとする。

つまり、先頭のレジスタに、UARTデバイスを制御するのに適切なアドレスが代入されてくることになる(r3)

# 3. printasciiでどこの機種のアセンブラを読むか決定する

それでは、printascii内で呼ばれている各関数がどこで定義されているのかを確認する。


```c:arch/arm/Kconfig.debug

# These options are only for real kernel hackers who want to get their hands dirty.
config DEBUG_LL
	bool "Kernel low-level debugging functions (read help!)"
	depends on DEBUG_KERNEL
	help
	  Say Y here to include definitions of printascii, printch, printhex
	  in the kernel.  This is helpful if you are debugging code that
	  executes before the console is initialized.

	  Note that selecting this option will limit the kernel to a single
	  UART definition, as specified below. Attempting to boot the kernel
	  image on a different platform *will not work*, so this option should
	  not be enabled for kernels that are intended to be portable.

choice
	prompt "Kernel low-level debugging port"
	depends on DEBUG_LL

	config DEBUG_ALPINE_UART0
		bool "Kernel low-level debugging messages via Alpine UART0"
		depends on ARCH_ALPINE
		select DEBUG_UART_8250
		help
		  Say Y here if you want kernel low-level debugging support
		  on Alpine based platforms.
```

> これらのオプションは、手を汚したい本物のカーネルハッカーのためのものです
>
> Yを選択する事でprintascii, printch, printhexがカーネル内に定義されます。これは、consoleが初期化されてる前に実行するcodeのデバッグをする場合に役に立つでしょう
>
> このオプションを選択すると、以下に示すように、カーネルが単一のUART定義に制限されることに注意してください。 別のプラットフォームでカーネルイメージを起動しようとすると*動作しません*ので、このオプションは、移植を目的としたカーネルでは有効にしないでください。

ということで、ここは本当にべた書きされているので、移植性がなくなる修正になります。

上記の設定で、DEBUG_ALPINE_UART0などが定義されると、DEBUG_UART_8250などが指定され、更に、どのアセンブラコードをincludeするのかの定義に置換される。

```c:arch/arm/Kconfig.debug
config DEBUG_LL_INCLUDE
    string
    default "debug/sa1100.S" if DEBUG_SA1100
    default "debug/palmchip.S" if DEBUG_UART_8250_PALMCHIP
    default "debug/8250.S" if DEBUG_LL_UART_8250 || DEBUG_UART_8250
    default "debug/at91.S" if DEBUG_AT91_UART
    default "debug/asm9260.S" if DEBUG_ASM9260_UART
    default "debug/clps711x.S" if DEBUG_CLPS711X_UART1 || DEBUG_CLPS711X_UART2
    default "debug/dc21285.S" if DEBUG_DC21285_PORT
    default "debug/meson.S" if DEBUG_MESON_UARTAO
    default "debug/pl01x.S" if DEBUG_LL_UART_PL01X || DEBUG_UART_PL01X
    default "debug/exynos.S" if DEBUG_EXYNOS_UART
    default "debug/efm32.S" if DEBUG_LL_UART_EFM32
    default "debug/icedcc.S" if DEBUG_ICEDCC
```

上記によって、CONFIG_DEBUG_LL_INCLUDEは、`"debug/*.S"`という文字列になって、これが#includeされる。

```c:arch/arm/kernel/debug.S

#if !defined(CONFIG_DEBUG_SEMIHOSTING)
#include CONFIG_DEBUG_LL_INCLUDE
#endif

```

なお、すぐ4.1で使うCONFOG_DEBUG_UART_PHTSなどの定義もこちら。

```c:arch/arm/Kconfig.debug
config DEBUG_UART_PHYS
    hex "Physical base address of debug UART"
    default 0x01c20000 if DEBUG_DAVINCI_DMx_UART0
    default 0x01c28000 if DEBUG_SUNXI_UART0
    default 0x01c28400 if DEBUG_SUNXI_UART1
    default 0x01d0c000 if DEBUG_DAVINCI_DA8XX_UART1
    default 0x01d0d000 if DEBUG_DAVINCI_DA8XX_UART2
    default 0x01f02800 if DEBUG_SUNXI_R_UART
    default 0x02530c00 if DEBUG_KEYSTONE_UART0
    default 0x02531000 if DEBUG_KEYSTONE_UART1
    default 0x03010fe0 if ARCH_RPC
    :

config DEBUG_UART_VIRT
    hex "Virtual base address of debug UART"
    default 0xc881f000 if DEBUG_RV1108_UART2
    default 0xc8821000 if DEBUG_RV1108_UART1
    default 0xc8912000 if DEBUG_RV1108_UART0
    default 0xe0010fe0 if ARCH_RPC
    default 0xf0000be0 if ARCH_EBSA110
    default 0xf0010000 if DEBUG_ASM9260_UART
    default 0xf0100000 if DEBUG_DIGICOLOR_UA0
    default 0xf01fb000 if DEBUG_NOMADIK_UART
    default 0xf0201000 if DEBUG_BCM2835 || DEBUG_BCM2836
    default 0xf1000300 if DEBUG_BCM_5301X
    default 0xf1000400 if DEBUG_BCM_HR2
    default 0xf1002000 if DEBUG_MT8127_UART0
    default 0xf1006000 if DEBUG_MT6589_UART0
    default 0xf1009000 if DEBUG_MT8135_UART3
    default 0xf1023000 if DEBUG_BCM_IPROC_UART3
    default 0xf11f1000 if DEBUG_VERSATILE
    default 0xf1600000 if DEBUG_INTEGRATOR
    :
    :
```

# 4. printasciiで実際に利用されるアセンブラ

実際のアセンブラコードは、include/debug以下にそれぞれ実装されている。
ここでは、ソースコードが一番短そうな、bcmのコードを用いて、
用意されている関数の定義を簡単に確認する。
## 4.1 adduart, rp,rv,tmp

```c:/arch/arm/include/debug/bcm63xx.S
	.macro	addruart, rp, rv, tmp
	ldr	\rp, =CONFIG_DEBUG_UART_PHYS
	ldr	\rv, =CONFIG_DEBUG_UART_VIRT
	.endm
```

 rp [out] : CONFIG_DEBUG_UART_PHYS
 rv [out] : CONFIG_DEBUG_UART_VIRT
 tmp[out] :  waituart/busyuart へ渡されるデータ

なお、この関数は mmu制御のところからも間接的に呼ばれる。
多分これは、iomapのテーブルが定義されていれば使われないような気がしますけどね……

```c:arch/arm/mm/mmu.c

    /*
     * Ask the machine support to map in the statically mapped devices.
     */
    if (mdesc->map_io)
        mdesc->map_io();
    else
        debug_ll_io_init();

：
：


#ifdef CONFIG_DEBUG_LL
void __init debug_ll_io_init(void)
{
    struct map_desc map;

    debug_ll_addr(&map.pfn, &map.virtual);
    if (!map.pfn || !map.virtual)
        return;
    map.pfn = __phys_to_pfn(map.pfn);
    map.virtual &= PAGE_MASK;
    map.length = PAGE_SIZE;
    map.type = MT_DEVICE;
    iotable_init(&map, 1);
}
#endif
```

```c:arch/arm/kernel/debug.S

#ifdef CONFIG_MMU
ENTRY(debug_ll_addr)
        addruart r2, r3, ip
        str r2, [r0]
        str r3, [r1]
        ret lr
ENDPROC(debug_ll_addr)
#endif

```

## 4.2 waituart, rd,rx

```c:/arch/arm/include/debug/bcm63xx.S

	.macro	waituart, rd, rx
1001:	ldr	\rd, [\rx, #UART_IR_REG]
	tst	\rd, #(1 << UART_IR_TXEMPTY)
	beq	1001b
	.endm

```

 rd [tmp] : テンポラリに利用
 rx [in] : UARTデバイスのポート

 UARTデバイスポートの、UART_IR_REGのOFFSETにあるメモリを読み出して、#(1 << UART_IR_TXEMPTY)と一致するまでループ。

## 4.3 senduart, rd,rx

```c:/arch/arm/include/debug/bcm63xx.S

	.macro	senduart, rd, rx
	/* word access do not work */
	strb	\rd, [\rx, #UART_FIFO_REG]
	.endm
```

 rd [in] : 書き出す文字列
 rx [in] : UARTデバイスのポート


## 4.4 busyuart, rd,rx

```c:/arch/arm/include/debug/bcm63xx.S

	.macro	busyuart, rd, rx
1002:	ldr	\rd, [\rx, #UART_IR_REG]
	tst	\rd, #(1 << UART_IR_TXTRESH)
	beq	1002b
	.endm
```

 rd [tmp] : テンポラリに利用
 rx [in] : UARTデバイスのポート

 UARTデバイスポートの、UART_IR_REGのOFFSETにあるメモリを読み出して、#(1 << UART_IR_TXTRESH)と一致するまでループ。

# まとめると

- 起動時のboot parameterにearlyprintkを追加
- KERNEL CONFIGで、DEBUG_LL を有効化
- arch/arm/Kconfig.debug にデバッグ用の設定を追加。
- arch/arm/Kconfig.debug に対応するアセンブラを追加
- arch/arm/include 以下に、対応するアセンブラを追加
 - waituart : 書き出せる状態待ち
 - senduart : 実際にFIFOなりに書き出す
 - busyuart : 書き出し完了待ち

以上です。