[U-boot] SPIドライバが認識されない謎

U-boot で SPI ドライバを作成してたときのこと。
ドライバモデルに準拠して作成したにも関わらずドライバの関数が一切呼ばれず、どうしようもなくてソース解読を進めた結果、想定外の結末だったという話です。

SPI ドライバの作成例

最近の U-boot のデバイス対応は DM(DeviceModel) に準拠して作るようになっています。ドライバの作成はだいたい以下のような流れです。

  • DMに準拠した他の登録済ドライバを見てスケルトンを作る
  • 名前を決めて関数名などを合わせる
  • 関数の中身を実装する
  • 使った API に合わせた #include を追加する
  • DeviceTree のノードを作成する
  • Makefile と Kconfig を定義する
  • config 定義とビルド
  • 実機に投入して起動

スケルトンは U_BOOT_DRIVER() で定義した情報で probe(開始)、remove(終了)、ops(各機能)の各関数を定義します。また of_match で DeviceTree の定義も行います。SPI の場合はだいたい以下のようになります。

static const struct dm_spi_ops HOGE_spi_ops = {
	.claim_bus	= HOGE_spi_claim_bus,
	.release_bus	= HOGE_spi_release_bus,
	.xfer		= HOGE_spi_xfer,
	.set_speed	= HOGE_spi_set_speed,
	.set_mode	= HOGE_spi_set_mode,
};

static const struct udevice_id HOGE_spi_ids[] = {
	{ .compatible = "FOOBAR,HOGE-spi" },
	{ }
};

U_BOOT_DRIVER(HOGE_spi) = {
	.name	= "HOGE-spi",
	.id	= UCLASS_SPI,
	.of_match = HOGE_spi_ids,
	.ops	= &HOGE_spi_ops,
	.ofdata_to_platdata = HOGE_spi_ofdata_to_platdata,
	.probe	= HOGE_spi_probe,
	.remove	= HOGE_spi_remove,
};

実際の SPI ドライバの作り方は以下に結構いい説明がありました。他のドライバでも作り方の流れは参考になります。
https://github.com/qemu/u-boot/blob/master/doc/driver-model/spi-howto.txt

drivers/spi/Makefile と drivers/spi/Kconfig にそれぞれ情報を追加してドライバの実装は完了ですが、この DM では DeviceTree を読むので、”FOOBAR,HOGE-spi”を持つノードを DeviceTree に追加します。例えば以下のような感じ(だいぶ適当)。

&soc {
	spi0: spi@12000000 {
		compatible = "FOOBAR,HOGE-spi";
		status = "okay";
		reg = <0x12000000 0x100>;
		pinctrl = <&pinctrl_spi>;
	};
};

“CONFIG_DM_SPI” と Kconfig に作成したこのドライバの定義(例えば “CONFIG_HOGE_SPI”)を menuconfig などで定義しビルドします。ついでにコマンドから確認できるように CONFIG_CMD_SPI も定義します。

そして起動ROMに投入して U-boot 起動。”sspi” が SPI の送信コマンドで、ドライバの probe もこのコマンドがきっかけで走ります。

=> sspi 0:0 8 A
Invalid bus 0 (err=-19)

ん?ドライバ起動時に走る probe 関数も ofdata_to_platdata 関数も走っていないようです。compatible は合ってるし、”dm tree” でドライバ自体が定義されていることは確認できています。しかし起動しない。

訳が分からないので解読

メッセージ “Invalid bus” から追ってみます。簡易表記で関数呼び出し関係を示します。

spi_get_bus_and_cs()
	uclass_get_device_by_seq()
		uclass_find_device_by_seq() ⇒ -ENODEV
	"Invalid bus %d (err=%d)\n"

uclass_find_device_by_seq() は SPI デバイスを変数 dev で巡回し、dev->req_seq が与えられたシーケンシャル番号(0) と一致すれば OK、見つからなければ -ENODEV を返します。つまり、登録された SPI デバイスに番号がない(実際 dev->req_seq == -1)。

dev->req_seq はデバイス定義の初期段階 device_bind_common() で付与されるようです。

device_bind_common()
	dev_read_alias_seq(dev, &dev->req_seq);

alias ?? もしかして… DeviceTree には SPI のエイリアスは無し。spi0 を追加してみると…

	aliases {
		serial0 = &serial0;
		serial1 = &serial1;
		spi0 = &spi0;
	};
=> sspi 0:0 8 A
FF

値はともかくエラーがなくなりました!どうも 「シーケンシャル番号を持つデバイスは alias 定義があるものだけが有効になる」ということのようです。Linux ではこの定義はなくても有効にできるのですが、U-boot の場合は SPI だけでなく serial や I2C もこの罠にハマることになりそうです。

[Linux] kprobes

kernel や module をデバッグしたいと思ったときにいろんな方法がありますが、真っ先に思いつくのが printk() でメッセージや観測値を表示することだと思います。しかしいちいちビルドし直さなければならないこと、ソースを改変する必要があること、などあまりいい方法ではありません。

Linux にはデバッグのための手段がたくさん用意されています。kernel 内蔵の kdb/kgdb、kernel 動作の追跡を行う ftrace、プロセス動作に手を入れる ptrace、syscall やシグナルを監視する strace、実行状況や実行回数を監視する perf など。これらはプログラム動作を外から監視するものですが、動作そのものに割り込む機能として kprobes があります。

kprobes は特定の場所に breakpoint 命令を設定し、実行時に命令をフックして予め用意した関数を実行することができます。特定の場所の設定と関数は module として用意することで、kernel が起動した後でも module をロードして設定することができます。

kprobes は kernel config の以下を有効にします。使い方を見るために下の2行にてサンプルも有効にしておきます。

CONFIG_KPROBES=y
CONFIG_SAMPLES=y
CONFIG_SAMPLE_KPROBES=m

サンプルは samples/kprobes/kprobe_sample.c にあります。make modules でビルドすると、samples/kprobes/kprobe_sample.ko ができあがるので、以下のように割り込みたい関数ラベルを指定してロードします。

# insmod kprobe_sample.ko symbol=_do_fork
Planted kprobe at 0xffff000080102050

このサンプルでは、struct kprobe の .symbol に関数ラベルの文字列、.pre_handler に割り込む箇所を実行する前に実行したい関数、.post_handler に割り込む箇所を実行した後に実行したい関数をそれぞれ設定しています。この関数に表示したい情報や実行したい操作を呼び出すことで任意の場所に割り込んで操作することができます。割り込む箇所が関数ラベルから離れている場合は .offset に関数ラベルからのオフセットを記載します。

しかしながら、kprobes では任意の箇所に割り込むため、この割り込ませる関数はレジスタレベルで情報を取得することになります。引数の struct pt_regs はアーキテクチャ依存のレジスタセットを表しています。

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
#ifdef CONFIG_X86
	pr_info("<%s> pre_handler: p->addr = 0x%p, ip = %lx, flags = 0x%lx\n",
		p->symbol_name, p->addr, regs->ip, regs->flags);
#endif
	...
	/* A dump_stack() here will give a stack backtrace */
	return 0;
}

struct kprobes を設定したら、register_kprobe() で登録、unregister_kprobe() で登録を解除するだけです。module の probe 関数に必要なだけ登録すれば、複数箇所に割り込むことができます。

レジスタレベルで情報を扱うため、c言語レベルのデバッグにはやや不向きなところもありますが、ビルドが不要でどこにでも割り込めるため強力なデバッグツールと言えます。

CONTINUE 展示 in 城陽

城陽市歴史民俗資料館にて、特別展示として表題のイベントが9/2まで実施されていました。
内容としては主に1970~2000年代のゲーム機の展示ですが、いろんな方面からさまざまな所蔵品が集められていて、これだけの数のゲーム機が揃うのはなかなか見られない機会でした。

ただ、コンパクトな展示場に凝縮されて並べている展示方法だったため、一つ一つの機材について解説を見たり思い返したりするというよりも、これだけの数の圧巻さを提示している感じでちょっともったいなかった気もします。

続きを読む

ALL ABOUT マイコン BASIC マガジン II SP in 大阪

今年初めに「ALL ABOUT マイコンBASICマガジンII」が有楽町のよみうりホールで1000人規模で開催され参加したのですが,その半年後にまさかの大阪でのスペシャル版として南港ATCホールにて開催されました.これまでの内容を踏襲しながらも,300人規模とあってより近い感じ,より踏み込んだ形でトークを聞くことができました.

続きを読む

[RPi][Linux] Raspberry Pi で SPI 通信

SPI通信が使える環境がないか探していて、Raspberry Pi なら 40pin ヘッダに出ていて直接使えるのでこれで何かモジュールを繋げば通信できそうということで試してみます。
Raspbian 環境なら手順がいろいろ書かれているので、ここでは敢えて mainline kernel 環境で SPI を有効にしてみることにします。SPI はシリアル通信の一つでI2Cと同様に周辺デバイスのアクセス手段として利用されています。

SPI 通信の動作を確認するため、対向デバイスとして温度センサモジュールを接続して温度を取得してみることにします。温度センサは ADT7310 を使いますが、モジュール化された “MDK001” が便利です。

続きを読む

GPD Pocket

Cloud Funding の “Indiegogo” で募集され 3,182,160 USD も集めた “GPD Pocket” ですが、やはり気になる機種ということで出資してたところ、無事出荷され入手できました。

Ubuntu 版は同価格ながら Windows ライセンスが付属しないのであまり選択する旨味はないのですが、BIOS update と OS の自力入れ替えが必要でハマると文鎮ループのリスクも考えられるので、敢えてこちらを選択したのでした。

続きを読む

[RPi] Pi Zero W

Raspberry Pi 3 を入手して間もないですが、海外通販にて Pi Zero W を入手しました。PIROMONI にて注文掛けていたのですが6日で無事到着しました。Anniversary 期間に間に合ったので £10 で入手でき(通常は £12)、せっかくなので Pibow ケースや Speaker pHAT も追加しました。送料は £5.5 です。

SoC は RPi 無印と同じ BCM2835 で ARM1176 コア、RAMは 512MB の Elpida チップが SoC 上にボンディングされています。外部端子は micro USB(OTG)、mini HDMI、micro SD カード、GPIO ピン、と RPi の互換を保ちながら最小構成でできています。Pi Zero W ではさらに Wi-Fi と Bluetooth が内蔵されています。

続きを読む

[RPi][Linux] boot upstream Linux from U-boot on Raspberry Pi 3

U-boot を Raspberry Pi 3 に導入して、ビルドした mainline Linux kernel を起動してみました。

Raspberry Pi は 既存の bootloader が提供されているので、SDカードの FATパーティションに kernel image を置けば起動するのですが、ビルドした kernel を直接投入するには network 経由で起動したくなるものです。

続きを読む