Fedoraでもarm
何を調べていたか思い出せないんだけど、気が付いたら 計測ミュージアムなんてのが表示されてた。これも ご縁と思って、4~20mA物語なんてのを 読んでみたよ。
オイラーも、この方面はちょいと首を突っ込んだ前歴があるんで、うなづける点が多々ある。 ゼロ付近の信号を扱うの難しいから、シフトしちゃえ。そうすれば、断線した結果として ゼロになってるのか、本当にゼロなのかの判定が容易って嬉しいおまけがある。交流より 直流ってのも、現場主義の賜物。
20mAで思い出すのは、TTYの駆動が20mAのカレント(電流)ループだったな。これも、ノイズ避けに 最適。RS232Cなんかだと、マーク・スペース信号に、プラス・マイナス5V以上は出力される 事。入力の判定レベルはプラス・マイナス3Vで行われる事。これでノイズマージンが2Vは 保障されますってのが決まってたな。
電流の流れる向きを、デジタル信号の1と0に対応させましょってのが最近再注目を浴びて いる。ノイズで電流の向きを反転させる、なんて事は、雷様でも難しかろうってのが、その 理由。
なんてのは、半分嘘で半分当たっている。電圧の変化で1と0を判定しようとすると、遷移 時間が問題になって、高速で信号を遅れないんだ。その点、電流モードだと、そういう事に 気を使わなくても良い。HDDと本体間の信号のやり取りがシリアル信号になってるね。
ああ、TTYと言うと、その後継でも思い出があるな。PDPに繋がっていたVT100って名前のVKT。 ビデオ・キーボード・ターミナルの略称。TTYのガタガタ言いながら印字されるのから、 輝点で文字が表示されるのを見て、ぶったまげたものだ。
今でも、そのVKTが形を変えて使われているね。
RaspberryPiクラスタ製作記 第4回「BareMetal並列計算」 が、面白いぞ。
fedoraでもarm
したいな。そんなの前回やったように、ウブで実現出来てるじゃん。そんなのは承知ですが、 ウブにだけ頼るのはどうかと思いまして。。。ウブがこけたら皆こけたはイヤですから。
で、探してみると、 arm for Fedora とか piでベアメタルプログラミング なんてのが引っかかってきた。
どうも、Fedoraではユーザーランドなアプリは作成出来ないようだ。膨大なライブラリー どうすんのってのが最大も問題なんだろうな。
で、裸の石で、自前で何とかしてよって言う、組み込み技術者養成環境なんですな。よし、 ウブみたいに生温い環境を飛び出して、何とかしたるわい。
armのクロス開発用ツールを入れる。次に貧乏人はラズパイを買えないので、qemu-system-armで 我慢するって寸法です。便利な世の中になり過ぎているな。昔なら、gccをコンパイルする だけで丸半日は優にかかってたもんなあ。それが、dnf install gcc-arm-linux-gnu 一発だからね。
そして、qemu-system-armが仮想のVKTの役目をしてくれる。わざわざ、LCDディスプレーを 用意する必要が無いばかりかUSB接続のキーボードも不要。でもマウスは必要でしょ。 昔は、マウスなんて無かったよ。それでやってこれたんだから、オイラーには必要無し。
そして、資料も買う必要無し。 組込み技術者向け「初めてのC言語」 も参考に。
英国の大学 とか、 Bare Metal Programming in C Pt1 も、参考になる。
arm開発用のbusybox
最初はMakefile。俺様の開発用busyboxです。変にGUIに凝るなんて、クリック猿のやる事です。 使い難い所が有ったら、どんどん改変しましょう。なお、途中で出て来る、arm-gdbは、Fedoraの コンパイル環境チェックの目的で入れてみた、gdb-7.9.1です。本当は、sim/runが欲しかったってのは、 秘密ですよ。
[sakae@fedora arm]$ cat Makefile CC = arm-linux-gnu-gcc CFLAGS= -mcpu=arm926ej-s -marm -nostdlib --no-builtin -Wall -g LD = arm-linux-gnu-ld LDFLAGS = -T smpl.ld kernel = app.img objs = main.o bio.o head.o all: $(objs) $(LD) $(LDFLAGS) $(objs) -o $(kernel) .c.o: $(CC) -c $(CFLAGS) $< -c .S.o: $(CC) $(CFLAGS) $< -c clean: rm -f *.o $(kernel) run: qemu-system-arm -M versatilepb -nographic -kernel $(kernel) # stop qemu for CTL-a c quit or kill qemu-system-arm debug: qemu-system-arm -s -M versatilepb -nographic -kernel $(kernel) gdb: arm-gdb -q $(kernel) # need target remote localhost:1234 in .gdbinit .PHONY: clean all
リンカーへ伝えるプログラム内のデータブロック配置情報。ENTRYって所に宣言されてるのが、 プログラムの実行開始番地になる。後は、.textでいわゆる実行コード部分を配置。続いて、.dataで、 初期値がある変数部分、.bssで初期値の無い変数部分(エリアだけ確保)、最後は、スタックエリアの 割り当てだな。
[sakae@fedora arm]$ cat smpl.ld ENTRY(_start) SECTIONS { . = 0x10000; .text : { *(.entry); *(.text, rodata); } .data : { *(.data); } .bss : { *(.bss); } .stack : { . = . + 0x1000; . = ALIGN(4); stack_top = .; . = . + 0x1000; . = ALIGN(4); irq_stack_top = .; } }
プログラムが起動した時に、真っ先に動き出す部分。俗にCRTと呼ばれる部分だ。 スタックを設定して、mymainをサブルーチンコール してる。本当はmainとしたかったんだけど、そうすると警告が出てきて鬱陶しいので、 独自な関数名にしてる。
[sakae@fedora arm]$ cat head.S .text .code 32 .globl _start _start: ldr sp, =stack_top bl mymain halt: b halt
UARTを使った基本入出力です。manによると、getsはバッファー溢れの恐れがあるんで使うなと、 強く警告してます。そこでちと仕様を変えて、最大受付文字数を与えるようにした亜流に なってます。
石の内部に存在するUART(シリアル入出力)を使って、1文字の入出力関数を組み立て、それを 応用して、1行の入出力ルーチンを実現してます。1文字入力時にはechoしません。
UARTは、FIFO(馴染みな言い方だとパイプだな)が2本内蔵。それぞれの端に、パラレルーシリアル変換器が 接続されてて、RS232Cラインを駆動するようになっている。2本のFIFOのもう一方の端は、 共通なレジスタ(UART0)に接続されてる。このレジスタに書き込むと、データが出力(表示)され、 読み出すと、キーボードからの入力が得られる。
読み書き出来るタイミングは、FIFOの充填具合によるので(FIFOが満杯の時は書き込めない等)、 その状態を得るフラグレジスタが用意されてる。他にも制御用のレジスタが多数有る。 フラグレジスタは、基点になるUART0から6バイト変位した所にあるので、それをUARTFRで 宣言してる。
このUARTによるシリアルラインは3本用意されるようで、そのうちの一番最初のやつを利用 してる。説明書を見ると、赤外線によるコントロールとかも出来るようになってて、そんなの 今じゃ青歯だろうにと思うけど、基本はお大事にって事で、内蔵してるんだろうね。
[sakae@fedora arm]$ cat bio.c // Basic I/O using UART #define UART0 ((volatile unsigned int *) 0x101f1000) // data reg. (address) #define UARTFR 0x06 // flag reg. (offset) #define UARTFR_TXFF 0x20 // tx FIFO full #define UARTFR_RXFF 0x10 // rx FIFO empty void putc(const char c) { while (*(UART0 + UARTFR) & UARTFR_TXFF) ; *UART0 = c; } void puts(const char *s) { while (*s) { while (*(UART0 + UARTFR) & UARTFR_TXFF) ; *UART0 = *s; s++; } } int getc(void) { while (*(UART0 + UARTFR) & UARTFR_RXFF) ; return *UART0; } char *gets(char * const buf, const int size) { int ch; int n = 0; while (n < size - 1) { ch = getc(); if (ch == '\r') { puts("\r\n"); break; } putc(ch); buf[n++] = ch; } buf[n] = 0; return buf; }
ヘッダーファイルです。普通は引数の型だけ宣言しますが、それだと警告が出てきたので、 引数名も含めています。どうやるのが正解なんでしょうかね?
[sakae@fedora arm]$ cat bio.h // Basic IO void putc(const char c); void puts(const char *s); int getc(void); char *gets(char * const buf, const int size);
最後は、お待ちかねのメインです。mainじゃなくてmymainってのがちとあれっぽいな。(謎)
[sakae@fedora arm]$ cat main.c #include "bio.h" void mymain(void) { char buff[128]; puts("hello, ARM on Fedora\n"); gets(buff, 128); puts(buff); puts("\n"); }
動作試験
まずは、コンパイルして、app.imgと言う偽物カーネルを作ります。
[sakae@fedora arm]$ make arm-linux-gnu-gcc -c -mcpu=arm926ej-s -marm -nostdlib --no-builtin -Wall -g main.c -c arm-linux-gnu-gcc -c -mcpu=arm926ej-s -marm -nostdlib --no-builtin -Wall -g bio.c -c arm-linux-gnu-gcc -mcpu=arm926ej-s -marm -nostdlib --no-builtin -Wall -g head.S -c arm-linux-gnu-ld -T smpl.ld main.o bio.o head.o -o app.img
ちゃんと出来上がったか確認。
[sakae@fedora arm]$ file app.img app.img: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped [sakae@fedora arm]$ arm-linux-gnu-size app.img text data bss dec hex filename 556 0 8192 8748 222c app.img
bssがやけに大きいな。何故? そうか、MMUの制御の関係かな? textエリアは、読み出しと実行が 可能領域に設定。bssは、読み書き可能で割り付けるから、ページが(勿論)別になり、管理の 都合で、切りの良い値にした。でも、bssなら初期値は無いはずだから、容量喰うのはおかしい。 あれ? それ以前に、bssに配置されるような変数を設定した覚え無いんだけどな。 静的ファイルって特殊性が反映された結果だろうね。そういう事にしておこう。
そんじゃ、実行。
[sakae@fedora arm]$ make run qemu-system-arm -M versatilepb -nographic -kernel app.img xcb_connection_has_error() returned true pulseaudio: set_sink_input_volume() failed pulseaudio: Reason: Invalid argument pulseaudio: set_sink_input_mute() failed pulseaudio: Reason: Invalid argument hello, ARM on Fedora ABCDEFG ABCDEFG QEMU 2.3.0 monitor - type 'help' for more information (qemu) quit
helloの前に見苦しいのが出てるけど、これを消そうと思ったら、自前でqemuをコンパイル しないと駄目かな。
キーボード入力も出来てる。終了は、qemuを強制的にkillするか、ソフトに、CTL-a c して、 qemuのモニターモードに落ちてからquitだ。
オラクル提供のvboxで動かしてる時は、ゲストOSの終了で、qemuも自動終了するけど、何か 特殊なパッチでも当たっているの? それとも、インテルの石と言うかウィンテル陣営得意の、 パワーマネジメントで、ゲストOS終了を検出して、qemuを終了させてるの?
次は、qemu内蔵の通信機能を使ってリモートデバッグ。
[sakae@fedora arm]$ make debug qemu-system-arm -s -M versatilepb -nographic -kernel app.img xcb_connection_has_error() returned true pulseaudio: set_sink_input_volume() failed pulseaudio: Reason: Invalid argument pulseaudio: set_sink_input_mute() failed pulseaudio: Reason: Invalid argument hello, ARM on Fedora
キー入力待ちになった時、別端末から
[sakae@fedora arm]$ make gdb arm-gdb -q app.img Reading symbols from app.img...done. warning: Can not parse XML target description; XML support was disabled at compile time 0x00010118 in getc () at bio.c:22 22 while (*(UART0 + UARTFR) & UARTFR_RXFF) ; (gdb) bt #0 0x00010118 in getc () at bio.c:22 #1 0x00010170 in gets (buf=0x111a4 "", size=128) at bio.c:30 #2 0x00010024 in mymain () at main.c:7 #3 0x00010204 in _start () at head.S:8 Backtrace stopped: previous frame identical to this frame (corrupt stack?) (gdb)
btした時、全部のフレームが見られるって、初めての体験ですよ。超気持ちイイーーー! この気持ちを手軽に味わうには、.gdbinitを用意しておいてね。
(gdb) up 2 #2 0x00010024 in mymain () at main.c:7 7 gets(buff, 128); (gdb) b 8 Breakpoint 1 at 0x10024: file main.c, line 8. (gdb) c Continuing. Breakpoint 1, mymain () at main.c:8 8 puts(buff); (gdb) p buff $1 = "1234\177\177\177", '\000' <repeats 120 times>
クライアント側で、1234に続いて数回バックスペースした後RET。で、buffを見ると、 バックスペースが0xFFになってる。これって、qemuさんの仕業? 0xFFって表示時には 無視された。ちゃんとクックモードを導入しろよ。
何故に太ってる
上で気になったbss領域の肥大化、どうなってるの?こういう時は、取り合えず
[sakae@fedora arm]$ arm-linux-gnu-nm app.img 000101fc T _start 0001010c T getc 0001014c T gets 00010204 t halt 0001222c B irq_stack_top 00010000 T mymain 0001004c T putc 0001009c T puts 0001122c B stack_top
うーむ、スタック領域がbssエリアに喰いこんでいるな。2本のスタックに、それぞれ4kづつ 割り当てているから、合計で8kになってるけど、そんなものなんですか? >偉い人。
今度は、objdumpを使って
[sakae@fedora arm]$ arm-linux-gnu-objdump -x app.img app.img: ファイル形式 elf32-littlearm app.img アーキテクチャ: arm, フラグ 0x00000112: EXEC_P, HAS_SYMS, D_PAGED 開始アドレス 0x000101fc プログラムヘッダ: LOAD off 0x00010000 vaddr 0x00010000 paddr 0x00010000 align 2**16 filesz 0x0000022c memsz 0x0000222c flags rwx STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4 filesz 0x00000000 memsz 0x00000000 flags rwx private フラグ = 5000202: [バージョン 5 EABI] [soft-float ABI] [エントリポイントを持っています] セクション: 索引名 サイズ VMA LMA File off Algn 0 .stack 00002000 0001022c 0001022c 0001022c 2**0 ALLOC 1 .text 0000020c 00010000 00010000 00010000 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 2 .rodata 00000020 0001020c 0001020c 0001020c 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA :
スタックアリアは確かに8k割り当ててあるな。objdump --help したら、面白いスイッチが 見つかったぞ。
[sakae@fedora arm]$ arm-linux-gnu-objdump -S app.img app.img: ファイル形式 elf32-littlearm セクション .text の逆アセンブル: 00010000 <mymain>: #include "bio.h" void mymain(void) { 10000: e92d4800 push {fp, lr} 10004: e28db004 add fp, sp, #4 10008: e24dd080 sub sp, sp, #128 ; 0x80 char buff[128]; puts("hello, ARM on Fedora\n"); 1000c: e59f0030 ldr r0, [pc, #48] ; 10044 <mymain+0x44> 10010: eb000021 bl 1009c <puts> gets(buff, 128); 10014: e24b3084 sub r3, fp, #132 ; 0x84 10018: e3a01080 mov r1, #128 ; 0x80 1001c: e1a00003 mov r0, r3 10020: eb000049 bl 1014c <gets> puts(buff); 10024: e24b3084 sub r3, fp, #132 ; 0x84 10028: e1a00003 mov r0, r3 1002c: eb00001a bl 1009c <puts> puts("\n"); 10030: e59f0010 ldr r0, [pc, #16] ; 10048 <mymain+0x48> 10034: eb000018 bl 1009c <puts> } 10038: e1a00000 nop ; (mov r0, r0) 1003c: e24bd004 sub sp, fp, #4 10040: e8bd8800 pop {fp, pc} 10044: 0001020c .word 0x0001020c 10048: 00010224 .word 0x00010224
これが、mymainの正体とな。アセンブラーの勉強にはうってつけ。って、はるか昔にやった ような記憶が蘇ってきた。我ながら進歩が無いな。
折角、実験用コードが有るんで、インテルの石用に移植して太り具合が一緒か、確認してみる。 変更すべき所は、Makefileとhead.Sだ。head.Sだけを載せておく。
sakae@uB:~/src/i386$ cat head.S .text // .code 32 .globl _start _start: mov $stack_top, %esp call mymain halt: jmp halt
.codeってアセンブラの偽命令は無いのね。近頃は、64Bitマシンも当たり前のように有るけど、 インテル得意の32Bit仮想マシンは利用出来ないの? armは32Bitと16Bitの切り替えが、一 命令で出来るんだから、天下のインテルさんだってやればいいのに。
sakae@uB:~/src/i386$ size app.img text data bss dec hex filename 533 0 8192 8725 2215 app.img sakae@uB:~/src/i386$ nm app.img 000100b0 T getc 000100cb T gets 00010140 t halt 00012218 B irq_stack_top 00010000 T mymain 00010054 T putc 0001007c T puts 00011218 B stack_top 00010136 T _start
無謀にも走らせてみる。
sakae@uB:~/src/i386$ gdb -q app.img (gdb) b mymain Breakpoint 1 at 0x10009: file main.c, line 6. (gdb) run Starting program: /home/sakae/src/i386/app.img Breakpoint 1, mymain () at main.c:6 6 puts("hello, ARM on Fedora\n"); (gdb) c (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x00010087 in puts (s=0x10142 "hello, ARM on Fedora\n") at bio.c:15 15 while (*(UART0 + UARTFR) & UARTFR_TXFF) ; (gdb) bt #0 0x00010087 in puts (s=0x10142 "hello, ARM on Fedora\n") at bio.c:15 #1 0x00010016 in mymain () at main.c:6 #2 0x00010140 in _start () at head.S:8
コードは適当な所に置いて実行出来るようだけど、変な所にアクセスするとSEGVになるのね。 だから、管理されたエリアに、UART0を移してしまえば、文句を言われないだろう。 どうするか? smpl.ldの.stackの前に、そのアドレスを書いておけば良い。
.bss : { *(.bss); } . = 0x101f1000; .stack : {
これでstackの端っこがUART0のレジスタにかかってくる。
[sakae@fedora i386]$ nm app.img 00010139 T _start 000100b3 T getc 000100ce T gets 00010143 t halt 101f3000 B irq_stack_top 00010000 T mymain 00010055 T putc 0001007e T puts 101f2000 B stack_top
gdbから実行してみると
Breakpoint 1, mymain () at main.c:6 6 puts("hello, ARM on Fedora\n"); (gdb) c Continuing. 1234 ^C Program received signal SIGINT, Interrupt. halt () at head.S:10 10 jmp halt
今度はSEGVに落ちる事なく、mymainを無事に走り終えた。勿論、プログラム中のputsからの 表示は無いけどね。
objdump -xの結果も
プログラムヘッダ: LOAD off 0x00001000 vaddr 0x00010000 paddr 0x00010000 align 2**12 filesz 0x00000218 memsz 0x00000218 flags r-x LOAD off 0x00002000 vaddr 0x101f1000 paddr 0x101f1000 align 2**12 filesz 0x00000000 memsz 0x00002000 flags rw- STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4 filesz 0x00000000 memsz 0x00000000 flags rwx
エリアが飛んで配置されている。あれ? 今気が付いたんだけど、stackエリアの属性が 読み書き実行になってるぞ。OpenBSDの親分さんのTeoが、顔を真っ赤ににして怒りそうな 設定だな。何でも、大型コンピュータでは、スタック上でプログラムを実行可能なんて もってのほかだそうですよ。
qemuのsource探検
日頃からお世話になってるqemuのsourceがどうなってるか、探検してみたい。何ってたって、 こいつはCPUの博物館ですから、効率よく見て回る事が出来るでしょう。
なんてのは嘘です。昔、台湾にある故宮博物館を訪れた事があるけど、ツアーに組み込まれた 数時間ではとても回れるものではない。目的を持って回らないと、何を見てきたの?って事に なりきれませんから。あの時は、ツアーと言っても、おいら夫婦だけのツアーで、親日の ガイドさんが案内してくれたから、融通も利いたし、見所を的確に回ってくれた。もう一度、 行ってみたいな。
そんな訳なんで、今回はARMに絞ります。目に付くtarget-armの方じゃなくて、hw/armの 下が目的な展示室。ここには、各種ボード(-M ? で列挙されるやつ)が陳列されてる。 冒頭に、ボードの主が書いてあるんで、まずそれを確かめよう。
キヤノンさん有り、シャープさんあり、TIさんあり、サムスンさんあり、色々な派生品が置いてある。 それから、カーネルのローダーなんてのもね。上でやった -kernel app.img で、読み込みが 行われるのは、このコードが使われているんだな。納得。
ああ、先に展示を眺めちゃったけど、sourceは下記のようにして取得する。
git clone git://git.qemu-project.org/qemu.git
142Mぐらいな、大量ソースでした。
更に絞り込んで、UART系のソースを探してみた。
[sakae@fedora hw]$ grep Arm `find . -type f -print` ./gpio/pl061.c: * Arm PrimeCell PL061 General Purpose IO with additional ./dma/pl080.c: * Arm PrimeCell PL080/PL081 DMA controller ./ssi/pl022.c: * Arm PrimeCell PL022 Synchronous Serial Port ./sd/pl181.c: * Arm PrimeCell PL181 MultiMedia Card Interface ./intc/pl190.c: * Arm PrimeCell PL190 Vector Interrupt Controller ./i386/smbios.c: * Markus Armbruster <armbru@redhat.com> ./usb/quirks-ftdi-ids.h: * Armin Laeuger originally sent the PID for the UM 100 module. ./misc/mst_fpga.c: * Copyright (c) 2007 by Armin Kuster <akuster@kama-aina.net> or ./audio/pl041.c: * Arm PrimeCell PL041 Advanced Audio Codec Interface ./audio/pl041.hx: * Arm PrimeCell PL041 Advanced Audio Codec Interface ./audio/pl041.h: * Arm PrimeCell PL041 Advanced Audio Codec Interface ./input/pl050.c: * Arm PrimeCell PL050 Keyboard / Mouse Interface ./input/pxa2xx_keypad.c: * Written by Armin Kuster <akuster@kama-aina.net> ./display/pl110.c: * Arm PrimeCell PL110 Color LCD Controller ./display/pl110_template.h: * Arm PrimeCell PL110 Color LCD Controller ./display/xenfb.c: * Markus Armbruster <armbru@redhat.com>, ./char/pl011.c: * Arm PrimeCell PL011 UART ./arm/mainstone.c: * Copyright (c) 2007 by Armin Kuster <akuster@kama-aina.net> or
ARMな石は英国の設計会社が、各メーカーに設計図を売って儲けている。その時に、オプションで、 音関係は必要ですか? キーボードやマウスコントロールは必要ですか? シリアルコントロールは は必要ですかってな具合に、追加出来るようになってるんだな。で、その追加部品が、PLxxxxって 具合になってる。PLってのはプロダクトの略だろう。多分。PrimeCellって事は、叩き台で、 メーカーは、コピペした後、更に改造可能だったり思想。
UART系は、PL1011なんだな。char/ の下に置いてあるんで、unix流の分類だと、キャラクターデバイスって事 になるな。
折角なので、ちょいとchar/p1011.cを観賞
static void pl011_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { PL011State *s = (PL011State *)opaque; unsigned char ch; switch (offset >> 2) { case 0: /* UARTDR */ /* ??? Check if transmitter is enabled. */ ch = value; if (s->chr) qemu_chr_fe_write(s->chr, &ch, 1); s->int_level |= PL011_INT_TX; pl011_update(s); break; :
ベースアドレスと、レジスタのオフセット、送出データ、サイズ(使ってないぞ)を 受け取って、データを送出してる。色々なケースがこのルーチンで処理されてて、UARTの くせにDMA転送までやってる。一体、最大ボーレートはどのぐらいなの? そんなの、説明書を 調べろよ。色々分かって面白いな。