あらすじ
Google Summer of Code(GSoC)でやることの一つとして、STM32F7マイコンでUART(シリアル)を使うためのLinuxドライバを書いています。 STM32F7とSTM32F4のシリアルペリフェラルはほとんど同じ*1なので、STM32F4のものをベースにSTM32F7向けのコードを追記して、STM32F7で利用する際は追記したコードに処理を切り替えるということを行えば、既存のドライバのコードを再利用してSTM32F7でもシリアルが利用できるようになります。
この切り替えを行うためにDevice Tree Blobに書かれた情報からSTM32F4で動いているのかSTM32F7で動いているのかを判断するようなコードを書いていましたが、メンターの方と相談した結果、差分もほとんどない上、#define
部も切り替える必要がありコードがわかりづらくなるなどの問題もあり、#if
ディレクティブを用いて書いてみることになりました。
以上より、Device Tree Blobに書かれた情報からSTM32F4で動いているのかSTM32F7で動いているのかを判断するようなコードはひとまず不要となったので、そのやり方についてをこちらで供養したいと思います。
コードを切り替えるには
ランタイム時、ドライバ内でコードの切り替えを行うためには、STM32F4で動いているのかSTM32F7で動いているのかを判断する必要があります。
この判断については、カーネルがDeviceTreeBlob(DTB)に従ってどういう名前のserialドライバを読み込もうとするかを見ることで行うことにしました。例えば、stm32-usartというドライバ名で読み込もうとする場合はSTM32F4として、stm32f7-usartというドライバ名で読み込もうとする場合はSTM32F7として判断するという感じです。
実際の arch/arm/boot/dts/stm32f429.dtsi のserialに関する記述は、以下のようになっています。
usart6: serial@40011400 { compatible = "st,stm32-usart", "st,stm32-uart"; reg = <0x40011400 0x400>; interrupts = <71>; clocks = <&rcc 0 165>; status = "disabled"; };
このように書かれているとき、stm32f429.dtsiをincludeしているDTBを読み込んだカーネルは、stm32-usartをcompatibleとして設定しているドライバを読み込んで使用します。
ドライバ側のコード(drivers/tty/serial/stm32-usart.c)には次のような構造体が定義されており、ここに追記を行うとcompatibleとして扱う名前を増やすことができます。
static const struct of_device_id stm32_match[] = { { .compatible = "st,stm32-usart", }, { .compatible = "st,stm32-uart", }, {}, };
ここにcompatibleとしてstm32f7-usartとstm32f7-uartの記述を増やすと、次のような感じになります。
static const struct of_device_id stm32_match[] = { { .compatible = "st,stm32-usart", }, { .compatible = "st,stm32-uart", }, { .compatible = "st,stm32f7-usart", }, { .compatible = "st,stm32f7-uart", }, {}, };
ドライバ内でDTBのcompatibleに設定されている名前が「st,stm32f7-usart」であるかを確認するには、of_property_match_string関数が使えます*2。 この関数は第3引数に設定した文字列と一致する文字列がcompatibleに設定されている場合、compatibleの何番目に設定されているかのインデックスを返します。そうでない場合はマイナスの値が返ってくるようです。 よって、次に示すstm32_of_is_stm32f7関数のようなコードを作ってドライバ内のprobe関数で呼び出すと、そのドライバがSTM32F4向けとして呼び出されたのか、STM32F7向けとして呼び出されたのかを知ることができます。
static inline bool stm32_of_is_stm32f7(struct platform_device *pdev){ struct device_node *np = pdev->dev.of_node; if (of_property_match_string(np, "compatible", "st,stm32f7-uart") >= 0 || of_property_match_string(np, "compatible", "st,stm32f7-usart") >= 0) return true; else return false; }
後は、この情報をどこかで覚えておいて、ifを使ってコードを切り替えれば良いと思います。
以上です。
おまけ。
ドライバ内ではpr_info関数をprintfデバッグに使えるっぽい(?)
pr_info("Hogehoge\n");
of_property_read_string関数を用いると、DTBから特定の要素に設定された文字列を読み出せる。
const char *comp; of_property_read_string(pdev->dev.of_node, "compatible", &comp); pr_info("%s\n",comp);
*1:詳細はSTマイクロエレクトロニクス社の資料(Migration of microcontroller applications from STM32F42xxx/STM32F43xxx to STM32F74xxx/STM32F75xxx) を参照してください。
*2:この関数の詳しい仕様について、日本語では以下の記事がとてもわかりやすいです。とあるエンジニアの備忘log: Device Tree アクセス関数まとめ (Linux Kernel)