LoginSignup
3
3

More than 3 years have passed since last update.

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

Posted at

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

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

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

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

0. はじめに

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

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

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を指定してあげるとよいっぽい。

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

  • 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に変換して文字出力
  • それ以外 → 文字出力

だと思います。

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を先に解説しておく。

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内で呼ばれている各関数がどこで定義されているのかを確認する。

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するのかの定義に置換される。

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される。

arch/arm/kernel/debug.S

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

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

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

/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のテーブルが定義されていれば使われないような気がしますけどね……

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
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

/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

/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

/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 : 書き出し完了待ち

以上です。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3