メインコンテンツへスキップ

ラズベリーパイ5のGPIOのmmapについて

·361 文字·2 分
技術記事 Raspberry Pi 5 C言語 コードリーディング

ラズベリーパイ 5 で GPIO を mmap するときにハマったので覚え書き。

生成 AI による要約
#

  • 問題の背景
    • mmap 関数を使用して GPIO にアクセスしようとすると、EINVAL エラーが返され、操作が失敗する。
    • 原因としては、mmap の第 2 引数である長さ (len) が適切でない可能性があると考えた。
  • 解決策の探求
    • Raspberry Pi の公式ユーティリティ pinctrl のコードを調査し、どのように mmap を使用しているかを確認。
    • 特定の GPIO_CHIP_T 構造体の size フィールドが mmap の len 引数に使用されていることを突き止めた。
    • Raspberry Pi 5 の RP1 チップの場合、この size フィールドは 0x30000 となっていることを確認。
    • mmap の len 引数に 0x30000 を指定した結果、成功した。
  • 結論
    • Raspberry Pi 5 で mmap を使用する際、/dev/gpiomem ではなく/dev/gpiomemN(N は 0〜4)が使用される。
    • mmap の長さ引数に、適切な値(RP1 の場合は 0x30000)を指定する必要がある。

以下駄文、もとい詳細
#

まず最初は以下の rasbperry-gpio-python のコードを元に作っていたのだが、mmapは EINVAL を返し、動かなかった。 なお、Raspberry Pi 5 環境では/dev/gpiomemではなく/dev/gpiomemN(N は 0 から 4(筆者環境))であった。

reaspberry-gpio-python 2024/8/9 22:00 閲覧

    // try /dev/gpiomem first - this does not require root privs
    if ((mem_fd = open("/dev/gpiomem", O_RDWR|O_SYNC)) > 0)
    {
        if ((gpio_map = (uint32_t *)mmap(NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, 0)) == MAP_FAILED) {
            return SETUP_MMAP_FAIL;
        } else {
            return SETUP_OK;
        }
    }

man によるとmmapEINVALになる条件は以下のようであったので、おそらく長さ(BLOCK_SIZE)があっていないのだろうと推測した。(なお筆者は実際には addr か offset の可能性もいくらか検討した) なおこのコードではBLOCK_SIZE4096であった。

https://ja.manpages.org/mmap/2

EINVAL
addr か length か offset が適切でない (例えば、大きすぎるとか、ページ境界にアラインメントされていない)。
EINVAL
(Linux 2.6.12 以降) length が 0 であった。
EINVAL
flags に MAP_PRIVATE と MAP_SHARED のどちらも含まれていなかった、もしくは その両方が含まれていた。

その後解決策を探したところ フォーラムに respberrypi/utils/pinctrl を参考にしろという情報があったのでそれを見る。

まずmmapをする箇所は以下のようになっていた。 https://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L664

            gpio_map = mmap(
                NULL,                   /* Any address in our space will do */
                chip->size + align,     /* Map length */
                PROT_READ | PROT_WRITE, /* Enable reading & writing */
                MAP_SHARED,             /* Shared with other processes */
                inst->mem_fd,           /* File to map */
                0                       /* Offset to GPIO peripheral */
                );

このうちの第二引数の指定に使われているchip変数は以下のように初期化されている。

https://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L657-L658

        inst = &gpio_chips[i];
        chip = inst->chip;

gpio_chipsgpio_create_instance関数内で初期化が行われており

https://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L51

static GPIO_CHIP_INSTANCE_T *gpio_create_instance(const GPIO_CHIP_T *chip,
                                                  uint64_t phys_addr,
                                                  const char *name,
                                                  const char *dtnode)
{
    GPIO_CHIP_INSTANCE_T *inst = &gpio_chips[num_gpio_chips];

gpio_create_instance関数はgpio_init関数内で呼び出されている。

https://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L481

     inst = gpio_create_instance(chip, phys_addr, NULL, alias);

そしてこのgpio_create_instanceに渡されているchip引数はgpio_find_chip関数で取得されており

https://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L464

        chip = gpio_find_chip(compatible);

そしてgpio_find_chip関数内では以下のように検索されている。

https://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L399

    for (chip = &__start_gpiochips; name && chip < &__stop_gpiochips; chip++) {

そして、__start_gpiochips__stop_gpiochipsgpiochip.hで定義されている。

https://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiochip.h#L39-L40

extern const GPIO_CHIP_T __start_gpiochips;
extern const GPIO_CHIP_T __stop_gpiochips;

GPIO_CHIP_Tは以下の構造体で、mmap箇所ではこれのsizeフィールドが使われていた。

https://github.com/raspberrypi/utils/blob/master/pinctrl/gpiochip.h#L12-L19

typedef struct GPIO_CHIP_
{
    const char *name;
    const char *compatible;
    const GPIO_CHIP_INTERFACE_T *interface;
    int size;
    uintptr_t data;
} GPIO_CHIP_T;

ここで、そのすぐ近くにDECLARE_GPIO_CHIPというマクロがあり、どうやら特定の CHIP ごとの情報を入れた変数が gpiochipsセクションに配置されているらしいとわかる。おそらく、__start_gpiochips__stop_gpiochipsは このセクションを指しているものであろう。

https://github.com/raspberrypi/utils/blob/master/pinctrl/gpiochip.h#L6-L8

#define DECLARE_GPIO_CHIP(name, compatible, iface, size, data) \
    GPIO_CHIP_T name ## _chip __attribute__ ((section ("gpiochips"))) __attribute__ ((used)) = \
    { #name, compatible, iface, size, data }

今回は Raspberry Pi 5 を使っているので Raspberry Pi 5 の peripheral controller である RP1 のコード箇所を見ると、 sizeフィールド部分が0x30000となっていた。

https://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiochip_rp1.c#L503-L504

DECLARE_GPIO_CHIP(rp1, "raspberrypi,rp1-gpio",
                  &rp1_gpio_interface, 0x30000, 0);

そこで試しにmmaplen引数に0x30000を指定して実行したところアドレスを取得できた。

戯言
#

ブログにコード切り貼りは疲れるからなにか自動でコードを挿入する機構を作りたいですね…