タグ「Olimex CS-E9302日記」が付けられているもの

結局、menuconfig を色々いじらないといけないらしい。

親切な人々のページを参考にして menuconfig をやり直す。

CPUのタイプをちゃんと選びましょう。

vgacon.o でコンパイルエラーになるのですが、、、の対策

↑ こんなエラー ↓

drivers/video/console/vgacon.c: In function `vgacon_startup':
drivers/video/console/vgacon.c:...: error: `PCIMEM_BASE' undeclared (first use in this function)
drivers/video/console/vgacon.c:...: error: (Each undeclared identifier is reported only 
...
make[4]: *** [drivers/video/console/vgacon.o] エラー 1
make[3]: *** [drivers/video/console] エラー 2
make[2]: *** [drivers/video] エラー 2
make[1]: *** [drivers] エラー 2

あと、シリアルドライバの選択と、ext2 ファイルシステムの選択も必要であった。

さらに、浮動小数のエミュレータも忘れずに。これがないと、ユーザプログラム中で double を使った瞬間に

Illegal instraction

となってプログラムが動かなくなる。最初原因が全く分からなかった。

まとめると、こんな感じ。

$ make ARCH=arm CROSS_COMPILE=arm-linux- distclean
$ make ARCH=arm CROSS_COMPILE=arm-linux- menuconfig
...
System-type --->
    ARM system type ---> 
        (X) EP93xx-based  ← CPUをちゃんと選ばないとカーネルのロード直後に落ちる
    Cirrus EP93xx Implementation Options ---> 
        [*] Support Cirrus Logic EDB9302
    [*] Support ARM920T processor
    [*] Support Thumb user binaries
Floating point emulation --->
    [*] NWFPE math emulation ← Illegal instruction 対策
Device Drivers --->
    Character devices --->
        Serial drivers --->
            <*> ARM AMBA PL010 serial port support ←開発PCとの通信用 /dev/ttyAM のドライバ
            [*]    Support for console on AMBA serial port
            < > EP93xx serial port support ← 注意 こっち選ぶとコンパイルエラーになる
    Graphics support --->
        Console display driver support --->
            [ ]VGA text console  ← 選択なしに変える (vgacon.oのコンパイルエラー対策)
File systems --->
    <*> Second extended fs support ← モジュールドライバからビルトインドライバに変える
                                   (ramdisk のフォーマットが ext2 なので)
    [*]     Ext2 extended attributes
    [*]       Ext2 POSIX Access Control Lists
    [*]       Ext2 Security Labels
...

という感じで、もう一度 menuconfig をやりなおした。で、コンパイル。

$ make ARCH=arm CROSS_COMPILE=arm-linux- zImage
...
Kernel: arch/arm/boot/zImage is ready

いぇ~。今度は成功。前回と同じ手順で、zImage をボードに転送して、FLASHに書き込む。

... 前回と同じなので省略

リセット。

RedBoot> reset
...
RedBoot> exec -r 0x800000 -x 0x800000 -c "console=ttyAM root=/dev/ram"
Using base address 0x00080000 and length 0x0013f000
Uncopressing Linux..... done. booting the kernel.
[    0.000000] Linux version 2.6.20.4 ...
...
[    3.680000] Freeint init memory: 88K
init started: BusyBox v1.1.3 (2006.08.22-13:03+0000) multi-call binary

Please press Enter to activate this console. ← リターンを押す

BusyBox v1.1.3 (2006.08.22-13:03+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

~ # ls
bin  etc  lib ...
~ # hello
hello
~ #

はぁ。やっと動いた。

カーネルのリコンパイルにチャレンジ

まず、uclibc 用に変更した gcc を元の gcc に戻した。

$ su
# mv /usr/local/arm /usr/local/arm-uclib
# cd /
# tar jxf /mnt/cd-rom/arm-linux-gcc-3.4.1.tar.bz2

ソースは、CDに入っていたLinux 2.6.20.4 を使う

$ mkdir kernel
$ cp /mnt/cd-rom/linux\ sources/linux-2.6.20.4.tar.gz kernel/.
$ cp /mnt/cd-rom/linux\ sources/linux-2.6.20.4-2db93xx.patch kernel/.
$ cd kernel
$ tar zxf linux-2.6.20.4.tar.gz
$ cd linux-2.6.20.4
$ cat ../linux-2.6.20.4-2db93xx.patch | patch -p1

コンパイルしてみる。(参考)「組み込みLinuxシステム構築」(オライリー)

$ make ARCH=arm CROSS_COMPILE=arm-linux- menuconfig
... → 変更なし
$ make  ARCH=arm CROSS_COMPILE=arm-linux- zImage
...

一時間くらいでコンパイル終了。意外と速い。RedBoot でボードのRAMにアップロード

$ kermit
Kermit> c
...  ← 電源ON & Ctrl+C
RedBoot> load -r -b 0x00080000 -m xmodem  ← ボードを受信待ちにする
CC...

別の端末から

$ sx ./kernel/linux-2.6.20.4/arch/arm/boot/zImage < /dev/ttyS0 > /dev/ttyS0  ← PCからファイル送信する
...

転送は10分くらいで終わる。次にRAMからFLASHに書き込む。再びRedBootの画面から

RedBoot> fis write -f 0x60D40000(*注) -b 0x00080000 -l 0x140000 
...

(*注) -f のアドレスは事前に RedBoot>fis list で確認した zImage のアドレス。間違えると面倒なので注意。

書き込み終了したので、リセット

RedBoot> reset
...
RedBoot> exec -r 0x800000 -x 0x800000 -c "console=ttyAM root=/dev/ram"
Using base address 0x00080000 and length 0x0013f000
← 停まった

orz. 動かなかった。

OpenOCD を使ってJTAG経由でFLASHのバックアップをとる。 要するに、FLASHメモリのダンプを開発PCのファイルに落とすのである。 RedBootだとFLASHに書き込むことはできるが、FLASHの中身を吸い出すことはできない。

openocd のマニュアルを見ると、dump_image というコマンドがある。これでFLASHの番地からデータを吸い出すことができる。

作業の前に、RedBootでFLASHのアドレスを確認しておく。

RedBoot> fis list
Name              FLASH addr  Mem addr    Length      Entry point
RedBoot           0x60000000  0x60000000  0x00040000  0x00000000
RedBoot config    0x60FC0000  0x60FC0000  0x00001000  0x00000000
FIS directory     0x60FE0000  0x60FE0000  0x00020000  0x00000000
netbsd            0x60040000  0x00200000  0x00500000  0x00200000
netbsd_install    0x60540000  0x00200000  0x00500000  0x00200000
ramdisk.gz        0x60A40000  0x00800000  0x00300000  0x00800000
zImage            0x60D40000  0x00080000  0x00140000  0x00080000

ついでにRedBootの設定も確認しておく。

RedBoot> fconfig -l
Run script at boot: true
Boot script: 
.. fis load ramdisk.gz
.. fis load zImage
.. exec -r 0x800000 -s 0x800000 -c "console=ttyAM root=/dev/ram"

Boot script timeout (1000ms resolution): 5
Use BOOTP for network configuration: false
Gateway IP address: 0.0.0.0
Local IP address: 192.168.0.93
Local IP address mask: 255.255.255.0
Default server IP address: 192.168.0.59
DNS server IP address: 0.0.0.0
Set eth0 network hardware address [MAC]: true
eth0 network hardware address [MAC]: 0x00:0x00:0x00:0x00:0x54:0x33
GDB connection port: 9000
Force console for special debug messages: false
Network debug at boot time: false

前回と同じ手順で、openocd を起動しtelnetで接続する。

$ openocd -f openocd.cfg

別の端末で telnet 実行

$ telnet 0.0.0.0 4444
...
Escape character is '^]'.
Open On-Chip Debugger
>

FLASHの全体を dump_image する前に、少しだけダンプを取ってうまくいくかどうかを確認する。試しに上の ramdisk.gz の領域の最初の20byte だけダンプしてみる。

> dump_image aaa.img 0x60a40000 20  ← 4の倍数でないと怒られる
dumped 20 byte in 0s 539us
>

ダンプしたファイルと ramdisk.gz の原本を比較する。

$ od aaa.img
0000000 067074 137671 067207 137671 107074 137671 106450 000024
0000020 106450 000024
0000024
$ od -N 20 ramdisk.gz
0000000 105437 004010 044775 044174 001400 060562 062155 071551
0000020 000153 136754
0000024

残念ながら全然違っている。

原因は、MMUがONになっているせいだと思われる。openocd が接続する前にボードのLinuxが起動して、仮想メモリが実行されて、MMUがONになっているのである。おそらく上で見たaaa.imgは、initプロセスのプロセス空間のダンプになっているのであろう。

MMUをOFFにする方法 → openocd.cfg を変更する。リセットした直後のMMUがOFFの状態で止まるようにする。

$ vi openocd.cfg
telnet 4444
gdb_port 3333
interface parport
parport_cable wiggler
jtag_device 4 0x1 0xf 0xe
daemon_startup reset   ← 変更
target arm920t little reset_init 0 ← 変更
flash bank cfi 0x60000000 0x10000000 2 2 0

再度 openocd 起動

$ openocd -f openocd.cfg ← openocd 起動

もう一度 telnetで接続して、ramdisk.gz 領域のダンプを取ってみる。

$ telnet 0.0.0.0 4444
...
Open On-Chip Debugger
>
> dump_image aaa.img 0x60a40000 20
dumped 20 byte in 0s 49548us

ダンプしたファイルと ramdisk.gz の原本を比較する。

$ od aaa.img
0000000 105437 004010 044775 044174 001400 060562 062155 071551
0000020 000153 136754
0000024
$ od -N 20 ramdisk.gz
0000000 105437 004010 044775 044174 001400 060562 062155 071551
0000020 000153 136754
0000024

今回はばっちり一致している。

(メモ:soft_reset_halt コマンドはうまくいかなかった。ダンプが全部 0 になる。)

全FLASH領域をダンプしてバックアップする。

↓ telnet 端末
> dump_image backup.img 0x60000000 0x1000000
dumped 16777216 byte in 3535s 89682us ← たったの16Mbyteだが、一時間くらいかかる

これでFLASHのバックアップがとれた。

backup.img から ramdisk.gz の部分を抜き出して原本と一致することを確認する。

ダンプイメージの中で、ramdisk.gz の開始位置は、FLASH上のアドレスからオフセット0x60000000 を差っぴいて、0xA40000=10747904である。

また、ファイルサイズは

$ ls -al ramdisk.gz
-r-xr-xr-x 1 homerun homerun 2889696 2008-07-15 15:55 ramdisk.gz

ダンプイメージから ramdisk.gz の部分を抜き出す。

$ tail -c+10747905 backup.img | head -c 2889696 > aaa.img

ここで tail コマンドの -c+ オプションは序数指定(例えば先頭は -c+1)。なので10747904+1 を指定している。

原本と比較する

$ diff aaa.img ramdisk.gz
$

OK、一致している。

(メモ) 16進数→10進変換コマンド $printf とか $((0xAA)) とか

Olimex CS-E9302日記 - JTAG 接続

JTAG接続に挑戦。今回のJTAG接続の目的は、ボードの Debug ではない。FLASHの吸い上げである。バックアップしときたいので。

JTAG接続ケーブル購入。パラレルポート用のやつ。

ARM-JTAG [ARM-JTAG] 購入先
(参考) ケーブル説明

開発PCに OpenOCD を導入。OpenOCDと関連パッケージをダウンロードしてインストール。

openocd (0.0+r211-1)

開発PCのパラレルポートとボードのJTAG端子をJTAGケーブルで接続する。ボードのジャンパは買ったときのままでOK。接続した後、ボードの電源を入れる。

開発PCで openocd 起動する。

$ su
# chmod 666 /dev/parport0
# exit
$ vi openocd.cfg ← コンフィグファイルを作る
telnet 4444
gdb_port 3333
interface parport
parport_cable wiggler
jtag_device 4 0x1 0xf 0xe
daemon_startup attach
target arm920t little reset_run 0
flash bank cfi 0x60000000 0x10000000 2 2 0
$ openocd -f openocd.cfg
Info: openocd.c:93 main(): Open On-Chip Debugger (2007-09-05 09:00 CEST)
     ←  openocd はデーモンとして起動される。

別のX端末から telnet で openocd デーモンに接続する。

↓ 別のX端末
$ telnet 0.0.0.0 4444
Trying 0.0.0.0...
Connected to 0.0.0
Escape character is '^]^.
Open On-Chip Debugger
>

これでJTAG経由でボードに接続できた。(らしい)

(参考)
OpenOCDの説明
openocd.cfg の説明
openocd.cfg のサンプル
openocd.cfg のサンプルは、開発PCの /usr/share/doc/openocd/examples にもある。
OpenOCDのコマンドの説明
OpenOCDのクイックリファレンス

ボードに lrzsz をインストールしたので使ってみる。

まず開発PCで hello2 プログラムを作成する。

$ vi hello2.c
#include &lt;stdio.h&gt;
int main(){
  prntf("hello2\n");
  return 0;
}
$ arm-linux-gcc -o hello2 hello2.c

ボードを起動してKermitで接続する。

← ボードの電源入れる
$ kermit
C-Kermit> c
Please press Enter to activate this console. ← リターンを押す
~ #  

ファイルの転送の実行

~ # cd bin
/bin # rx -b hello2   ← 受信コマンド
rx: ready to receive hello2  ← 受信待ちになっている

ここで別のX端末から送信コマンドを発行する

↓ 別のX端末
$ sx hello2 < /dev/ttyS0 > /dev/ttyS0  ←  hello2 の送信
Sending hello2, 36 blocks: Give your local XMODEM receive command now.
Byte Sent: 4736 BPS:300
Transfer complete ← 転送終了

もとのボードに接続してる端末をみると転送が終了している。

/bin # 
/bin # ls -al hello2
-rw------  1 root root 4736 Jan 1 00:07 hello2
/bin # chmod a+x hello2
/bin # hello2
hello2

うまく転送できている。転送時間は数秒である。

また、X端末を2画面使ったが、Kermitエスケープすれば1画面で送信できる。

~ # cd bin
/bin # rx -b hello2 
rx: ready to receive hello2
Ctrl+\c   ←  Kermitのエスケープシーケンスを入力。
...
C-Kermit> !sx hello2 < /dev/ttyS0 > /dev/ttyS0  ←  hello2 の送信
...Byte Sent: 4736 BPS 2186
Transfer complete
C-Kermit> c  ← 再接続
/bin # ls -al hello2
-rw------  1 root root 4736 Jan 1 00:07 hello2
/bin # chmod a+x hello2
/bin # hello2
hello2

としてもよい。Kermitエスケープする方は、手順がややこしいが転送失敗が少ないような気がする。

以上でファイル転送はうまくいくことを確認した。しかし、ひとつ注意がある。この転送先の /bin はRAMディスクなので、ボードをリセットすると消えてしまう。

リセットして再接続

~ # ls -al /bin/hello2
ls: /bin/hello2: No such file or directory ← 消えている

ということで、次はボードのLinux上でRAMディスクをFLASHへ書き込む方法が欲しくなる。が、これには、もう少し工夫がいる。

しかし、RAMディスクへの転送だけであっても、プログラムのデバッグのためには随分ありがたいと思う。

hello プログラムを転送するのに、ルートファイルシステムを作って、RedBoot 経由で転送という手順だと10分くらいかかる。 RedBoot経由せずに、ボードのLinuxと開発PCとの間で直接ファイル転送できると作業が効率的である。

そこで、ボードに xmodem 転送コマンドを導入することにする。ソースは、lrzsz パッケージを使う。(というか、これしかない)

まずこちらから↓ lrzsz のソースをダウンロードする。
lrzsz-0.12.20.tar.gz

これを uClibc の環境でコンパイルする。

解凍してコンフィグ

$ tar zxf lrzsz-0.12.20.tar.gz
$ cd lrzsz-0.12.20
$ CC=arm-linux-gcc ./configure --prfix=/bin
creating cache ./config.cache
checking for a BSD comptatible install... /usr/bin/install -c
...

CC=arm-linux-gccとしているが、わたしの環境では、これが glibc ではなく uClibc をリンクするようにしている。(普通の環境だと、arm-linux-uclibc-gcc のこと)

コンパイル

$ make
make all-recursive
make[1]: ...
...
make[2]: ディレクトリ `/home/homerun/arm-dev/lrzsz-0.12.20/src' に入ります
arm-linux-gcc -g -O2  -o lrz  lrz.o timing.o zperr.o zreadline.o crctab.o rbsb.o zm.o
 protname.o tcp.o lsyslog.o canit.o ../lib/libzmodem.a ../intl/libintl.a -lnsl
lrz.o(.text+0x264): In function `wcgetsec':
/usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/../../../../arm-linux/sys-include/bits/stdio.h:68:
 undefined reference to `_IO_putc'
lrz.o(.text+0x49c):/usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/../../../../arm-linux/sys-include/bits/stdio.h:68:
 undefined reference to `_IO_putc'
...
collect2: ld returned 1 exit status
make[2]: *** [lrz] エラー 1
make[2]: ディレクトリ `/home/homerun/arm-dev/lrzsz-0.12.20/src' から出ます
make[1]: *** [all-recursive] エラー 1
make[1]: ディレクトリ `/home/homerun/arm-dev/lrzsz-0.12.20' から出ます
make: *** [all-recursive-am] エラー 2

なんかエラーになった。 _IO_putc がないというエラーが延々と出ている。
uClibc のincludeディレクトリ /usr/local/arm/3.4.1/arm-linux/include を確認したが、_IO_putc は定義されていない。grep で探すと /usr/local/arm/3.4.1/arm-linux/sys-include/stdio.h で使用されている。

どうやら arm-linux-gcc のデフォルトインクルードパスがおかしいらしい。

デフォルトインクルードパスを確認する。

$ arm-linux-cpp -v  ← gcc でなく cpp
...
#include "..." search starts here:
#include <...> search starts here:
 /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/include
 /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/../../../../arm-linux/sys-include ← ここの順番が不味い
 /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/../../../../arm-linux/include ← ここの順番が不味い
End of search list.
^C

.../arm-linux/sys-include が先になっている。ほんとうは、uClibc をインストールした .../arm-linux/include を先に見て欲しいのである。もう少し確認してみよう。

$ arm-linux-gcc -H hello.c
. /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/../../../../arm-linux/sys-include/stdio.h ← NG
...

完全にsys-include/stdio.h を見に行っている。

回避方法は、-isystem オプションを使って以下のようにすればよい。

$ arm-linux-gcc -H -isystem /usr/local/arm/3.4.1/arm-linux/include hello.c
. /usr/local/arm/3.4.1/arm-linux/include/stdio.h ← OK
...

なお -I オプションではうまくいかない。.../arm-linux/include が元々デフォルトのインクルードパスに含まれているので -I オプションでは順番が変わらないからだ。 (因みに-nostdinc を使うとデフォルトインクルードをなしにもできる)
参考 Cプリプロセッサのman

lrzsz のコンフィグではこうする

$ CC=arm-linux-gcc CFLAGS="-isystem /usr/local/arm/3.4.1/arm-linux/include" ./configure --prfix=/bin
...

この後 make すると正常に終了することを確認した。

しかし、コンパイルの度に -isystem を指定するのは面倒なので、今後のことも考えて、arm-linux-gcc の specs を変更しておく。

 
$ su
# vi /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/specs
...
*cpp:
%(cpp_cpu_arch) %(subtarget_cpp_spec) 			← これらの行は1行です
%{mapcs-32:%{mapcs-26:							← これらの行は1行です
%e-mapcs-26 and -mapcs-32 may not be used together}}		← これらの行は1行です
%{msoft-float:%{mhard-float:							← これらの行は1行です
%e-msoft-float and -mhard_float may not be used together}}	← これらの行は1行です
%{mbig-endian:%{mlittle-endian:							← これらの行は1行です
%e-mbig-endian and -mlittle-endian may not be used together}} ← これらの行は1行です
-isystem /usr/local/arm/3.4.1/arm-linux/include ← これらの行は1行です
...

表示のために改行しているが、実際は1行です。-isystem xxx オプションを cpp に追加した。

インクルードパスが変わっていることの確認。

$ arm-linux-cpp -v
...
#include "..." search starts here:
#include <...> search starts here:
 /usr/local/arm/3.4.1/arm-linux/include ← 先頭に追加されている。OK
 /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/include
 /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/../../../../arm-linux/sys-include
 /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/../../../../arm-linux/include
End of search list.

うまくいっている。

あらためて、lrzsz のコンフィグ&コンパイル

$ CC=arm-linux-gcc ./configure --prfix=/bin
...
$ make
...
make[1]: ディレクトリ `/home/homerun/arm-dev/lrzsz-0.12.20`から出ます

うまくコンパイルできた。

ボードへのインストール

コンパイルしてできた lsz, lrz コマンドをボードのルートファイルシステムにコピーする。ルートファイルシステムの作り方は前回と同じ。再掲する。

$ cp /mnt/cdrom/linux-2.6\ images/ramdisk.gz .   ← 付属CDのルートファイルシステムを流用
$ gunzip ramdisk.gz
$ mkdir initrd
$ su
# mount -o loop ramdisk initrd  ← ループバックでマウント
# rm initrd/Noise.wav  ← 不要ファイル消す
# rm initrd/funcky.mp3
# cp lrzsz-0.12.20/src/lsz initrd/bin/.  ← lsz のコピー
# cp lrzsz-0.12.20/src/lrz initrd/bin/.  ← lrz のコピー
# cd initrd/bin/.
# ln lsz lsx  ← ハードリンク作成
# ln lrz lrx
# ln lsz lsy
# ln lrz lry
# ln -s lsz sz ← ソフトリンク作成
# ln -s lrz rz
# ln -s lsx sx
# ln -s lrx rx
# ln -s lsy sy
# ln -s lry ry
# cd ../..
# umount initrd
# gzip ramdisk

ramdisk.gz をRedBoot経由でボードに転送して、FLASHに書き込む。やり方は前回と同じ。

ここでは、lrzsz を使ったが、gkermit という転送コマンドも便利である。(ただしKermitとしか通信できない)

前回 uClibc を開発ツールチェーンにインストールしたので、これを使って hello をコンパイルする。

$ vi hello.c
#include <stdio.h>
int main(){
  printf("hello\n");
  return 1;
}
$ arm-linux-gcc -o hello hello.c
homerun@homerun-laptop:~/arm-dev$ arm-linux-gcc -o hello hello.c
/usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/../../../../arm-linux/bin/ld: warning: libc.so.6, 
needed by /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/../../../../arm-linux/lib/libgcc_s.so, 
may conflict with libc.so.0

なにか警告が出ている。 リンカのデフォルトライブラリがおかしいらしい。 回避方法は2つ

(回避1) libgcc_s.soのリンク先を変える

$ su
# cd /usr/local/arm/3.4.1/arm-linux/lib
# rm libgcc_s.so
# ln -s libuClibc-0.9.28.so libgcc_s.so

これでもうまくいくことは確認した。

(回避2) specs というファイルでデフォルトライブラリを変更する。
specs ファイルの場所の確認。

$ arm-linux-gcc -v
Reading specs from /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/specs
Configured with: 云々... 

次のように変更する。

$ su
# vi /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/specs
...
*libgcc:
%{msoft-float:-lfloat} %{static|static-libgcc:-lgcc -lgcc_eh} ← この3行は本来1行です
%{!static:%{!static-libgcc:%{!shared-libgcc:-lgcc} ← この3行は本来1行です
%{shared-libgcc:%{!shared: -lgcc}}}} ← この3行は本来1行です
...

表示のため改行しているが、上の3行は1行である。元々あった -lgcc_s の指定を全部消した。

再度 hello をコンパイル

$ arm-linux-gcc -o hello hello.c
$

今度はOK。中身を確認する

$ arm-linux-readelf -a hello
ELF Header:
...
      [Requesting program interpreter: /lib/ld-linux.so.2] ← これは不味い
...
 0x00000001 (NEEDED)                     Shared library: [libc.so.0] ← uClibcである
...
     8: 000083a4   496 FUNC    GLOBAL DEFAULT  UND __uClibc_main

共有ライブラリはuClibc が指定されている。これはOK。

しかし、ローダの指定が/lib/ld-linux.so.2 になっている。これは /lib/ld-uClibc.so.0 になってて欲しい。

回避策は2つ

(回避1) ルートファイルシステムで ld-linux.so.2 のリンク先をld-uClibc-0.9.28.soにする。

# cd ...../lib ← ボード用のルートファイルシステムの下の lib に移動する
# ln -s ld-uClibc-0.9.28.so ld-linux.so.2 ← ローダの調整

これでもうまくいくことを確認した。

(回避2) 若しくは specs ファイルを変更する。

$ su
# vi /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/specs
*link:
%{!static:--eh-frame-hdr} %{h*} %{version:-v}    ← 元々1行です
%{b} %{Wl,*:%*}    %{static:-Bstatic}    %{shared:-shared}    ← 元々1行です
%{symbolic:-Bsymbolic}    %{rdynamic:-export-dynamic}    ← 元々1行です
%{!dynamic-linker:-dynamic-linker /lib/ld-uClibc.so.0}← 元々1行です
    -X    %{mbig-endian:-EB} -m armelf_linux -p← 元々1行です

表示のため改行しているが、上の5行は元々1行のもの。ローダを ld-uClibc.so.0 に変えた。

(メモ:リンカオプション --dynamic-linker も使える?リンカのman)

再々度 hello をコンパイル

$ arm-linux-gcc -o hello hello.c

中身を確認する

$ arm-linux-readelf -a hello
ELF Header:
...
      [Requesting program interpreter: /lib/ld-uClibc.so.0]
...

ローダの指定もOK。

helloコマンドをルートファイルシステムにコピーする。やり方は前回と同じ。
上の(回避1)を採用した場合はローダのリンクも行う。

なお、ルートファイルシステムの libuClibc-0.9.28.so や ld-uClibc-0.9.28.so はプレインストールのままにしておく。本当はリコンパイルした uClibc で入れ替えるべきだが、既存のBusyBoxが動かなくなる恐れが大なので止めておく。このテストではhelloがちらっと動いてくれればOKなので、、、

出来た ramdisk.gz をボードへ転送する。方法は前回と全く同じ。
転送して、再起動して、ログインする。

~ # ls -al /bin/hello
-rwxr-xr-x   1 root root  4730 Jul 15 2008 /bin/hello
~ # hello
hello

ということで、uClibc にリンクした hello も動く。

ユーザプログラムは、この uClibc 環境でコンパイルすることにする。
カーネル、ドライバについては、uClibc なしのノーマルgcc 環境を別途用意し、そこでコンパイルする予定。
c++, java は使わない方向で。(もし使うなら、 glibc を入れて、uClibc, BusyBox は放棄する)

参考 specs の説明書
specs の説明が少し出てくる

開発ツールチェーンにuClibcを導入

ボードにプレインストールされているuClibcのバージョンの確認

~ # ls -al /lib/libuClibc*
-rw------- 1 root root 329972 Aug 22 2006 /lib/libuClibc-0.9.28.so

0.9.28をダウンロードする。ここから↓
uClibc-0.9.28.3.tar.bz2

コンパイル
と思ったら、コンフィグのために、開発PCにncurses を導入する必要があるらしい。
ncurses-devや依存パッケージを導入。ここから↓
libncurses5-dev
libc6-dev
linux-libc-dev

あらためて、uClibc のコンフィグ&コンパイル&インストール。

$ tar jxf uClibc-0.9.28.tar.bz2
$ cd uCibc-0.9.28
$ make menuconfig
Target Architecture → arm
Target Architecture Feature and Options →
  /Target Processor Type → Arm 920T
  /Linux kernel header location → /usr/local/arm/3.4.1/arm-linux/
General Library Settings
  /Shared library loader naming prefix → ld-uClibc.so
Library Instllation Optins →
  /Shared library loader path → /lib/
  /uClibc runtime library directory → /usr/local/arm/3.4.1/arm-linux/
  /uClibc development environment directory → /usr/local/arm/3.4.1/arm-linux/
uClibc development/debugging options → 
  /Cross-compiling toolchain prefix → arm-linux-

他はデフォルトのまま。セーブして終了。Linuxヘッダのバージョンが少しズレているが、とりあえずこのまま。

$ make
$ su
# make install

インストールの結果。
/usr/local/arm/3.4.1/arm-linux/include/ が変更された。
/usr/local/arm/3.4.1/arm-linux/lib/ が変更された。
また、arm-linux-gcc などコンパイラ、リンカ自体は変更をされていない。ラッパー(arm-uclibc-gccなど)も作成されない。

インストールディレクトリを/usr/local/arm/3.4.1/arm-linux/uclibc というのも試したが、helloのリンクエラーを回避できなかったので没。

(参考) 「組み込みLINUXシステム構築」4.3.1章

Olimex CS-E9302日記 - hello 作成

とりあえず hello を作ってみる。

全体の手順
わたしの環境では、LANが使えない。従ってプログラムの転送が少し面倒になる。全体の手順は以下の通り。
(1)hello のクロスコンパイル
(2)hello コマンドを入れてルートファイルシステムを作る。
(3)RedBoot 経由でルートファイルシステムをボードのFlashに書き込む。
(4)ボード再起動
(5)ログインしてhello 実行

まずクロスコンパイラの導入
ボード付属CDのarm-linux-gcc-3.4.1.tar.bz2 を使う。arm用にコンパイル済みのgccパッケージである。(なお付属CDには gcc-4.0.2 も入っている。3.4.1にしたのはなんとなく。)

$ cd /
$ su
# tar jxf arm-linux-gcc-3.4.1.tar.bz2

これで /usr/local/arm 以下に gcc その他が展開される。gccコンパイラなどの名前は、arm-linux-gcc。パスを通しておく。

$ export PATH=/usr/local/arm:$PATH

hello の作成
(注)共有ライブラリが使えないのでstaticリンクする。

$ vi hello.c
#include <stdio.h>
int main(){
  printf("hello\n");
  return 0;
}
$ arm-linux-gcc -static -o hello hello.c

ルートファイルシステムの作成
付属CDに入っているルートファイルシステムを流用する。

$ cp /mnt/cdrom/linux-2.6\ images/ramdisk.gz .
$ gunzip ramdisk.gz

これをループバックでマウントする。

$ mkdir initrd
$ su
# mount -o loop ramdisk initrd

次に、hello をルートファイルシステムにコピーする。
その前に、要らないファイルを消す。(サイズが厳しいので)

# rm initrd/Noise.wav
# rm initrd/funcky.mp3
# cp hello initrd/bin
# chmod 755 initrd/bin/hello

最後にアンマウントして圧縮。

# umount initrd
# gzip ramdisk

(参考)「組み込みLINUXシステム構築」8.6章

続いてルートファイルシステムをボードに書き込む。そのためにRedBootのコマンドモードに入る。
コマンドモードの入り方
(1)ボードの電源を入れる。
(2)起動中にCtrl-Cを押す。

...(起動中)
== Executing boot script in 5.00 seconds - enter ^C to abort
^C
RedBoot>

ルートファイルシステムの書き込み先のアドレスを確認する。

RedBoot> fis list 
Name        FLASH addr  Mem addr    Length    Entry point
...
ramdisk.gz  0x60A40000  0x00800000  0x00300000  0x00800000
zImge       0x60D40000  0x00080000  0x00140000  0x00080000

ramdisk.gz 領域のサイズは、0x300000=3145728バイト。
一方、先ほど作った ramdisk.gz のサイズは 2746991バイトだったで、上書きしても大丈夫。<br>

(参考) RedBoot のマニュアル fis の説明

ルートファイルシステムをFLASHに書き込む。
めちゃややこしいが、こうやるらしい。
まず、先ほど作った ramdisk.gz をボードのRAMにアップロードする。

RedBoot> load -r -b 0x00800000  -m xmodem  
CCC      ← ボード側RedBootが受信待ちになっている。

別のX端末で送信コマンドを発行する。

↓ Kermit を実行してるのとは別のX端末
$ sx /home/homerun/ramdisk.gz < /dev/ttyS0 > /dev/ttyS0 
Sending ramdisk.gz, 22575 blocks: Give your local XMODEM receive command now.
Xmodem sectors/kbyte sent: 0/0kRetry 0: NAK on sector   
...
Xmodem sectors/kbyte sent: 22161/2770kRetry 0: NAK on sector ← ここまで10分くらい
Bytes Sent:2747008  BPS:5571

Transfer complete   ← 転送成功

もとのKermitの画面のほうを見ると転送が終了している。

... Row file loaded 0x00800000-0x00a9ea6e, assumed entry at 0x00800000  云々...
RedBoot>

転送終了。

(参考) RedBoot のマニュアル loadの説明
kermitとsxの使い方

次に、RAM上の ramdisk.gz を FLASH に書き込む。

RedBoot> fis write -f 0x60A40000(*注) -b 0x00800000 -l 0x300000  ← -f アドレスに注意
* CAUTION * about to program FLASH
   at 0x60a40000..0x60d3ffff from 0x00800000 - continue (y/n) ? y ← アドレスが間違ってないことを確認してyを入力
... Erase from 0x60a40000-0x60d40000: .....
... Program from 0x00800000-0x00b00000 at 0x60a40000: .....
RedBoot>

(*注) fis write の -f アドレスは、fis list で確認した各ボードの値入力すること。間違えないこと。間違って、他の領域をつぶすとボードが起動しなくなる。(たとえは、RedBootを破壊したり、FLASHの情報領域を破壊したりとか、、、→ こうなるとJTAG接続でFLASHを再構築しないといけない)

(参考) RedBoot のマニュアル fis writeの説明

以上で書き込み終了。再起動する。
再起動は、ボードのリセットボタン(RSTNと刻印のあるボタン)を押すか、

RedBoot> reset
とする。

起動後ログイン。hello が入っていることを確認。

~ # ls -al /bin/hello
-rwxr-xr-x  1 root root  510927 Jul 14 2008 /bin/hello
~ #

hello の実行

~ # /bin/hello
hello
~ #

上の転送では、Kermitと別のX端末の2つの端末を使ったが、Kermitをエスケープすると、1つの端末でも送信可能である。

RedBoot> load -r -b 0x00800000  -m xmodem  
Ctrl+\c   ← Kermitのエスケープを入力
C-Kermit>  ← カーミットのコマンドモードに戻る。
C-Kermit> !sx /home/homerun/ramdisk.gz < /dev/ttyS0 > /dev/ttyS0 ← 送信コマンド発行
Sending ramdisk.gz ...
...
Bytes Sent:2747008  BPS:5571
Transfer complete 
C-Kermit> c  ← 再度ボードに接続する
Row file loaded 0x00800000-0x00a9ea6e, assumed entry at 0x00800000  云々...
RedBoot>
組み込みLinuxの調査

Olimex CS-E9302 をテスト
こちらから購入

とりあえず電源投入。
ガラクタ箱から見つけた、5V 500mA のACアダプタ接続。→ 反応無し
付属CDの説明書見ると、5V 1A の電源が必要らしい。
5.5V 900mAの電源でリトライ → LED点灯。

開発用ノートPCの準備。
スペック: LANなし。シリアルあり。パラレルあり。メモリ192Mbyte。HD30Gbyte。Cerelon 490MHz。
→シリアル経由で開発することにする。
→パラレル経由でJTAG接続は未確認
OSは、UbuntuのライブCDをインストール
カーネルバージョンは 2.6.24-14-generic
あと、kermit をダウンロードしてインストール
ここから ckermit
その他 kermit に必要な関連パッケージもダウンロードして導入。
それから xmodem 転送コマンド sx もインストール
ここから lrzszパッケージ

ボードと開発PCとの接続。
開発PCとCS-E9302をRS-232Cのクロス線で接続。
開発PC上でのkermit の設定。 ~/.kermrc をこのように編集

set line /dev/ttyS0
set speed 57600
set flow-control none
set carrier-watch off
set parity none
接続速度などは、ボード付属CDの説明書の通り。

開発PCでkermit 起動。ボードにログインする。

$ kermit
C-kermit 云々、、、
(/home/homerun/) C-kermit> c
Connecting to 云々 ,,,
------------------------------------------
ここでボードの電源投入
...
Redboot 云々、、、
...
Please press Enter to activate this console.
リターンを押すと
~ # 
ルートでログイン成功

Linuxバージョンの確認

~ # uname -a
Linux (none) 2.6.21 #17 PREEMPT Wed May 2 10:42:16 EEST 2007 armv4tl unknown

付属CDのカーネルソースの確認

linux-2.6.17-xxx
linux-2.6.20.xxx
どうやらプレインストールされているカーネルに対応するソースは提供されていないっぽい。(T‐T)

開発ツールチェーンの確認

~ # ls -al /lib
...
-rwx------ 1 root root 329972 Aug 22 2006 libuClibc-0.9.28.so
...
ボードにはuClibc がプレインストールされているようである。
しかし、付属CDのツールチェーン ( gcc4.0.1 と gcc3.4.1 の2つが提供されている )には、uClibcは入っていなかった。
ということで、ボードにプレインストールされているライブラリが、開発ツールとして提供されていない。(各コマンド、ライブラリのソースも一切ない)。自分でなんとかしろということか。面倒くさい雰囲気。他のにしといた方が良かったかな、、、安いJTAGケーブルに惹かれてこっちにしたのだが、、、