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 もこの罠にハマることになりそうです。
コメント