ラズベリーパイ 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 によるとmmap
でEINVAL
になる条件は以下のようであったので、おそらく長さ(BLOCK_SIZE
)があっていないのだろうと推測した。(なお筆者は実際には addr か offset の可能性もいくらか検討した)
なおこのコードではBLOCK_SIZE
は4096
であった。
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
変数は以下のように初期化されている。
inst = &gpio_chips[i];
chip = inst->chip;
gpio_chips
はgpio_create_instance
関数内で初期化が行われており
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
関数内で呼び出されている。
inst = gpio_create_instance(chip, phys_addr, NULL, alias);
そしてこのgpio_create_instance
に渡されているchip
引数はgpio_find_chip
関数で取得されており
chip = gpio_find_chip(compatible);
そしてgpio_find_chip
関数内では以下のように検索されている。
for (chip = &__start_gpiochips; name && chip < &__stop_gpiochips; chip++) {
そして、__start_gpiochips
と__stop_gpiochips
はgpiochip.h
で定義されている。
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
となっていた。
DECLARE_GPIO_CHIP(rp1, "raspberrypi,rp1-gpio",
&rp1_gpio_interface, 0x30000, 0);
そこで試しにmmap
のlen
引数に0x30000
を指定して実行したところアドレスを取得できた。
戯言#
ブログにコード切り貼りは疲れるからなにか自動でコードを挿入する機構を作りたいですね…