Pylone Blog
年末年始休業のお知らせ
誠に勝手ではございますが、株式会社パイロンは2009年12月27日から2010年1月4日の間を休業とさせていただきます。ご迷惑をおかけいたしますが、よろしくお願いいたします。
PowerPCボードADS512101へのLinuxカーネルの移植
Freescale社製PowerPCボードADS512101へLinuxカーネルを移植する手順を紹介します。
Freescale社ではADS512101用のLinuxBSPを公開しており、ソースコードから環境を構築することが可能ですが、Linuxカーネルのバージョンが2.6.24と若干古いため、比較的新しいバージョンを移植することが目的です。
予め
- PowerPC向けクロス開発ツール
- tftpサーバ
をインストールした環境を前提とします。
尚、使用するU-Boot、及びLinuxカーネルのバージョンは以下のとおりです。
- U-Boot 2009.1
- Linuxカーネル 2.6.28
U-Bootのビルド
オフィシャルサイトからソースコードを取得し、展開します。
% wget ftp://ftp.denx.de/pub/u-boot/u-boot-2009.01.tar.bz2 % tar xjfv u-boot-2009.01.tar.bz2
ターゲットにADS5121を指定してビルドします。
% cd u-boot-2009.01 % make ads5121_config % CROSS_COMPILE=powerpc-linux-gnu- make
Linuxカーネルのビルド
オフィシャルサイトからソースコードを取得し、展開します。
% wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.28.tar.bz2 % tar xjfv linux-2.6.28.tar.bz2
ADS5121ボード用(v2.6.28向け)のパイロン製パッチを取得・適用します
% wget http://code.pylone.jp/hg/linux-2.6-ads5121-mq/raw-file/tip/ads5121 % cd linux-2.6.28 % patch -p1 < ../ads5121
カーネル設定ファイルを設置後、必要に応じて設定を変更しビルドします(デフォルトでU-Boot形式が生成されます)。
% cp arch/powerpc/configs/ads5121_defconfig .config % ARCH=powerpc CROSS_COMPILE=powerpc-linux-gnu- make menuconfig % ARCH=powerpc CROSS_COMPILE=powerpc-linux-gnu- make
デバイスツリーのビルド
デバイスツリーとは簡単に説明すると、
- PowerPC/Linux固有の仕組みで、ボード依存のデバイス情報(I/Oアドレス等)を定義したもの
- デバイスツリーを定義したソースファイル(*.dts)をコンパイラ(dtc)にてコンパイルしバイナリファイル(*.dtb)を得る
- *.dtbファイルはLinuxカーネルイメージとは別にメモリ上に置かれ、Linuxカーネルはその内容からボードに関する各デバイス情報を把握する
といったものです。
デバイスツリーコンパイラはLinuxカーネルに含まれているため、以下の様にしてADS5121向けデバイスツリーソースをコンパイルします。
% cd (Linuxカーネルのディレクトリ)/arch/powerpc/boot % ./dtc -O dtb -o ads5121.dtb dts/mpc5121ads.dts
Flash更新
tftpに備えて、これまで生成したバイナリファイルを、tftpサーバのディレクトリ(/srv/tftp/とします)にコピーしておきます。
# cp (U-Bootのディレクトリ)/u-boot.bin /srv/tftp/ # cp (Linuxカーネルのディレクトリ)/arch/powerpc/boot/uImage /srv/tftp/ # cp (Linuxカーネルのディレクトリ)/arch/powerpc/boot/ads5121.dtb /srv/tftp/
シリアルコンソールとしてシリアルクロスケーブルをHostPCとボードを接続します。通信設定は以下のとおりです。
| ボーレート | 115200 |
|---|---|
| ビット長 | 8 |
| フロー制御 | なし |
| ストップビット | 1 |
ボードの電源を投入後、SW1を押下しボード標準のU-Bootを起動します。このとき、
Hit any key to stop autoboot:
のカウントダウンが開始されたらキャンセルします。
tftpに備え、必要に応じて環境変数を設定します。 (ここでは、tftpサーバとなるHostPCのIPアドレスを192.168.0.26、ボードのIPアドレスを192.168.0.120とします)
=> setenv netmask=255.255.255.0 => setenv ipaddr=192.168.0.120 => setenv serverip=192.168.0.26
先ほどビルドしたU-BootをFlashに書き込みます。FlashのI/O開始アドレスは 0xFC000000 で、内容は以下の様になっています。
| アドレス | セクタ番号 | 内容 |
|---|---|---|
| FC000000 | 0 | Protected |
| FC040000 | 1 | File system |
| FFC40000 | 241 | Linux カーネル |
| FFEC0000 | 251 | Device tree |
| FFF00000 | 252 | U-Boot |
| FFF40000 | 253 | U-Boot 環境変数 |
まず、更新するU-BootをRAM上のダウンロード領域(0x20000000)へダウンロードします。
=> tftp 2000000 u-boot.bin
Using FEC ETHERNET device
TFTP from server 192.168.0.26; our IP address is 192.168.0.120
Filename 'u-boot.bin'.
Load address: 0x2000000
Loading: ################################################
done
Bytes transferred = 241680 (3b010 hex)
そして、Flashのライトプロテクトを解除、該当のセクタを消去し、書き込みます。
=> protect off bank 1 => erase 1:252-252 => cp.b 2000000 fff00000 3b010
上記cpコマンドのサイズ(0x3b010)はtftpコマンドで実際に転送されたサイズを指定しています
Linuxカーネルを更新する場合も同様の手順になります。
=> tftp 2000000 uImage => protect off bank 1 => erase 1:241-250 => cp.b 2000000 FFC40000 <size>
FlashのライトプロテクトはU-Boot更新時に解除していますので続けて更新する場合は不要です。
デバイスファイルも同様の手順です。
=> tftp 2000000 mpc5121ads.dtb => protect off bank 1 => erase 1:251-251 => cp.b 2000000 FFEC0000 <size>
起動
ボード標準のU-Boot起動コマンドでFlash上のrootfsから起動するには以下の様にします。
=> run jffs2boot
カーネルコマンドラインを指定して起動する場合は以下の様にします
=> set bootargs console=ttyPSC0,115200 root=/dev/mtdblock1 rw rootfstype=jffs2 mem=256M => bootm ffc40000 - ffec0000
bootmコマンドの引数はそれぞれ、
- ffc40000 : カーネルのアドレス
- - : initial ramdiskのアドレス(未使用なので省略を示す'-')
- ffec0000 : デバイスツリーのアドレス
と言う意味になります。
Flashを更新せずにRAMから起動する場合
デバッグ段階などで、Linuxカーネルやデバイスツリーを頻繁に更新するようなケースで、毎回Flashに書き込みをしていては非効率です。そこで両ファイルをFlashに書き込まずにRAMから起動する場合は以下の様にします。
=> tftp 2000000 uImage => tftp 3000000 ads5121.dtb => set bootargs console=ttyPSC0,115200 root=/dev/mtdblock1 rw rootfstype=jffs2 mem=256M => bootm 2000000 - 3000000
または、Flash上のデバイスツリーファイルを使用する場合は、最後のbootmコマンドのデバイスツリーファイルのアドレスを、
=> bootm 2000000 - ffec0000
とします。
jffs2イメージをloopback mount
block2mtdドライバを使えば、jffs2イメージファイルをloopbackでmountできます。
erasesize 128K (131072) のrootfs.jffs2を/mntにmountする場合
mount:
# modprobe jffs2 # modprobe mtdblock # losetup /dev/loop0 rootfs.jffs2 # modprobe block2mtd block2mtd=/dev/loop0,131072 # mount -t jffs2 -o ro /dev/mtdblock0 /mnt
umount:
# umount /mnt # rmmod block2mtd # losetup -d /dev/loop0
TOPPERS/JSPカーネル for Bishop
組込みLinux開発用CPUボードBishop向けに移植したTOPPERS/JSPカーネルを公開します。使用したTOPPERS/JSPカーネルのバージョンは 1.4.3 です。
TOPPERS/JSPカーネルとは、μITRON4.0仕様に準拠したリアルタイムカーネルで、TOPPERSプロジェクトの開発成果です。 TOPPERS/JSPカーネルの詳細についてはTOPPERSプロジェクトサイトを参照してください。
株式会社パイロンが独自に配布する非公式なバージョンです。本ソフトウェアについてTOPPERSプロジェクトへの質問はお控えください。
今回の移植は実験的なものです。実用レベルに達していないことを予めご了承ください。
| TOPPERS/JSP カーネル for Bishop | |||
| toppers-jsp-bishop-1.4.3-pylone0.tar.bz2 ソースコード | 1.4.3-pylone0 | ダウンロード | 996.4KB |
| toppers-jsp-bishop-1.4.3-pylone0.bin ROMバイナリ | 1.4.3-pylone0 | ダウンロード | 28.0KB |
移植概要
ゼロからの移植ではなく、標準TOPPERS/JSPカーネルに既に含まれる同一のCPUコアを持つシステムをベースにして移植を行いました。 BishopのCPUコアはARM920Tであるため、ベースにするシステムはIntegrator/AP+CM920Tとしました。
また、サポートしたデバイスは、TOPPERS/JSPカーネルの動作が最低限確認出来るものとして、
- インターバルタイマ
- UART
のみとしました。
Bishopエミュレータによる実行
Bishopエミュレータにて実行する手順です。 予めBishopエミュレータをインストールしたLinux環境を想定しています。
Windows上のBishopエミュレータでの実行手順、及びBishopボードでの実行手順に関しては後述の関連リンクを参照してください。
前述のTOPPERS/JSPカーネルのROMバイナリファイルをダウンロードして、ダウンロードしたディレクトリに移動後、
$ mv toppers-jsp-bishop-1.4.3-pylone0.bin u-boot.bin
としてファイル名称を変更して、
$ qemu-bishop -M pe201a -serial stdio -kernel dummy -mtdblock /dev/null -nographic
としてqemu-bishopを起動します。この時、
failed to open: led.img failed to open: led.img failed to open: led.img failed to open: led.img failed to open: pe201a.img bishop_reset: splash image splash480.bmp.gz was not found bishop_reset: failed to load a kernel image file
と、qemu-bishopがメッセージを出力しますがTOPPERS/JSPカーネルの動作に影響はありません。
次いで、
TOPPERS/JSP Kernel Release 1.4 (patchlevel = 3) for ARM - Pylone Bishop board (Feb 24 2009, 15:21:58)
Copyright (C) 2000-2003 by Embedded and Real-Time Systems Laboratory
Toyohashi Univ. of Technology, JAPAN
Copyright (C) 2004-2006 by Embedded and Real-Time Systems Laboratory
Graduate School of Information Science, Nagoya Univ., JAPAN
System logging task is started on port 1.
Sample program starts (exinf = 0).
task1 is running (001). |
task1 is running (002). |
task1 is running (003). |
とTOPPERS/JSPカーネルのバナー、及びTOPPERS/JSP付属のサンプルプログラムからのシリアル出力が表示されると思います。
サンプルプログラムは起動中の3つのタスクに関する指示を与えられるようになっており、 例えばレディーキューを回転する'r'を入力すると、task1からtask2へと実行状態のタスクが切り替わります。
Sample program starts (exinf = 0). task1 is running (001). | task1 is running (002). | task1 is running (003). | #rot_rdq(three priorities) task2 is running (001). + task2 is running (002). + task2 is running (003). +
サンプルプログラムの詳細に関しては、TOPPERS/JSPカーネルソースコード内の sample/sample1.c 冒頭のコメント文を参照してください。
TOPPERS/JSPカーネルに標準で付属するWindows上で動作するシミュレータでもタスクの状態を検証する事が出来ると思いますが、今回のBishopエミュレータによる実行も併用すれば、実機によるデバッグをさらに軽減することが出来ると思います。
関連リンク
seq_file の使い方
Linuxカーネルが持つ、疑似ファイルの実装を補助する機構を紹介します。
カーネルからユーザ空間へデータを渡す手段としては、 /proc や /sys 以下に作成した疑似ファイルを使うことが多いでしょう。
渡したい値が単純な型の場合はカーネル組込みのヘルパ関数を使えば十分(sysfs 経由でモジュールパラメータにアクセス、sysfs 経由でモジュールパラメータにアクセス (2))ですが、 より複雑なデータを渡すならば:
- 疑似ファイルの内容となるテキスト量が大きい場合、カーネル空間に全体を保持たくない。
- データ構造を操作に時間がかかる場合、ユーザ空間から要求された部分だけを処理することが望ましい。
- 同時に複数の読み手が存在したり、読み出し途中でデータが変更される場合の排他が必要。
などの点を考慮するべきです。
本記事では、シーケンシャルな疑似ファイルを実装するために用意されている補助関数群について解説します。 カーネル内でも広く使われている機構なので、知っておくとコードを読む際にも役立つでしょう。
seq_file
VFS の層ではファイル中の位置はバイト単位で扱われます。固定長のバッファで扱える程度のデータ量ならよいのですが、 疑似ファイル内容を一括生成・保持できない場合、自力で出力済のバイト数管理やバイト単位でのシークを実装するのは手間がかかります。
fs/seq_file.c には出力したいデータが
- 通し番号が付けられる(配列や木などの)要素の集合として表現できる
- 要素の番号がわかれば、その要素の文字列表現が得られる
という条件を満たすとき、各要素へのアクセスをイテレータ操作として抽象化する仕組みが用意されています。
これを使うと、所定のアクセサを作成するだけで、 VFS に登録する struct file_operations のメンバのほとんどをカーネルから提供される汎用関数でまかなうことができます。
使用例
サンプルとして、全 tty についてdebugfs 上の 疑似ファイル経由として termios 状態を一覧するカーネルモジュールのソースを用意しましたtermios_dumper.c。
個別のttyの状態取得なら tcgetattr() でも十分なのですが、システム上で 現在 active な全てのttyをリストするために、カーネル内部のデータ構造を直接読んでいます。
疑似ファイルの登録
seq_file を使用する場合でも、ファイルシステムへの疑似ファイルの登録は通常のまま
- struct file_operations を作成
- ファイルシステム毎の登録関数の呼び出し
という手順で行います。
ただし、VFS に渡す struct file_operations のうち、読みこみ可能な疑似ファイルを実装するために必要な
- open
- read
- llseek
- release
のうち read, llseek, release としては、seq_file で実装されている seq_read, seq_lseek, seq_release をそのまま使用できます。
.open に登録する関数では、
- open() されたファイルに対して、seq_open() を呼んでハンドラを登録。
- 必要ならリソースの確保/ロック
を行ないます。
サンプルでは登録先のファイルシステムを debugfs としたので、以下のようにしています。
static const struct file_operations fops = {
.open = termios_dumper_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
...
static int __init termios_dumper_init(void)
{
...
if(IS_ERR(fs_root = debugfs_create_dir(KBUILD_BASENAME, NULL)))
return PTR_ERR(fs_root);
if(IS_ERR(fs_file = debugfs_create_file("state", 0444, fs_root, NULL,
&fops)))
return PTR_ERR(fs_file);
この例では、特にリソースの初期化が必要ないため、.open に登録した test_seqfile_open の 実装は seq_open() の呼び出しだけです。
static int test_seqfile_open(struct inode *inode, struct file *file)
{
return seq_open(file, &seqfile_ops);
}
seq_operations
読み出し用のイテレータに必要な操作:
- 初期化
- 終了処理
- 「次」の要素に移動
- 値(文字列表現)の取得
は、
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};
の各メンバとして登録されます。
start
seq_file 用のファイルが open() された時に呼ばれます。
- 使用するリソースの確保
- イテレータの初期化
を行ない、成功した場合には初期化済みのイテレータ、失敗した場合は NULL を返すことになるでしょう。
引数 *pos が 0 より大きい場合には、その位置を指すようにしたイテレータが返却されるべきです。
※ イテレータの更新処理は、(pos) ではなく、(*pos)を使います。
イテレータを進める処理は、next() からの処理でも必要となるため、共通化してもよいでしょう。サンプルでは update_iterator()としています。
next
seq_file が、現在のイテレータからのデータを消費し終えた時、 現在のイテレータと次に指すべき位置を引数として呼ばれます。
読み手の read() に対応していると考えてよいでしょう。 ただし、カーネルによる先読み処理の対象となるため、read() の度にかならず呼ばれるわけではありあせん。 また、読み手が小量のデータしが要求していなくても、さらに先まで要求されることがあります。
指定位置を指すイテレータを用意して、それへのポインタを返します。 この場合返却したポインタは後述する show() への引数として使われます。
処理に失敗した場合は NULL を返却します。このとき、続いて stop() が呼ばれますが、 stop() の引数にはNULLしか渡らないため、リソースの開放にイテレータが必要なら、next() 内で済ませておく必要があります。
stop
読み手に十分なテキストが渡ったか、next() からデータがもうないことを通知した(NULL を返した)後に呼びだされます。
リソースの開放を行なうべきですが、引数として渡されるポインタは NULL の場合があることに注意が必要です。
show
引数として渡されたイテレータから、読み手に渡す(疑似ファイルの内容となる)文字列を構築します。
構築には seq_printf(), seq_puts() などのヘルパ関数を使用します。これらのヘルパは繰替えし呼ぶこともでき(追記されます)、 最終的な長さは自動的に管理されるので、成功時には 0 を返せば適切に処理されます。
ただし、一度に構築できる文字列の全長は PAGE_SIZE 程度なので、長くなりすぎる場合はイテレータの構造を変える必要があるでしょう。
サンプルコードでは、現在のイテレータが指している termios の情報を、seq_printf() 経由でgrep しやすい書式に変換しています。
サンプル termios_dumper.c についての補足
サンプルとして使用したコード termios_dumper.c では、カーネルから公開されていはい tty ドライバのリストの先頭要素のアドレスを知るために、 struct tty_driver の magic 要素を用いて検索していますが、これは低い確率ですが (メモリ中の値が偶然TTY_MAGICと一致したり、get_current_tty() が呼べない場合に)失敗する可能性のある処理です。 確実を期すなら、カーネルの公開シンボルに tty core の tty_drivers を追加するか、専用のアクセサを追加するべきでしょう。
また、tty サブシステム内の構造体を辿る処理は tty の mutex を取得した上で行うべきなのですが、
- tty_mutex を占有すると副作用が大きい(ロック中に新規に tty を開こうとした他のプロセス全てが停止するため)
- 改造して失敗したときにシステムをロックしてしまう
- 今回のサンプルでは読み出ししかおこなわないため、整合性が崩れても大きな問題にならない
ため、デモ目的では不要と判断してコメントアウトしてあります。 このため、疑似ファイルからの読出し途中で tty が増減すると、表示の一貫性が崩れることがあるでしょう。 同様の理由で、kobjectの参照数カウントについても省略しています。