/home/tnishinaga/TechMEMO

日々行ったこと、面白かったことを書き留めます。

of_property_match_stringを使ってみたメモ

あらすじ

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)