物理アドレス直接操作によるLinux LEDドライバ開発

LEDドライバ実験(レジスタ直接操作)

Linuxのユーザースペースでは仮想アドレスが使用されますが、実際のハードウェアの制御には物理アドレスが必要です。データシートを参照して、GPIOピンの物理アドレスを特定します。

Linuxのデバイスドライバは、主に初期化(登録)と終了処理(アンインストール)で構成されます。文字デバイスドライバの場合、file_operations構造体にopenwritereleaseなどの関数ポインタを定義します。

カーネルスペースでのGPIO制御

カーネルドライバ内では、ioremap関数を使用して物理アドレスをカーネルの仮想アドレス空間にマッピングします。

// GPIOコントローラの物理アドレス定義
#define GPIO_CONTROLLER_BASE       (0x12345678)
#define PIN_MUX_CONFIG_BASE        (0x1234567C)
#define PIN_PAD_CONFIG_BASE        (0x12345680)
#define GPIO_DATA_REGISTER         (0x12345684)
#define GPIO_DIRECTION_REGISTER    (0x12345688)

// マッピング後の仮想アドレスポインタ
static void __iomem *gpio_controller;
static void __iomem *pin_mux_config;
static void __iomem *pin_pad_config;
static void __iomem *gpio_data_reg;
static void __iomem *gpio_dir_reg;

// ドライバの初期化関数
static int __init gpio_led_init(void)
{
    int result = 0;
    unsigned int value = 0;

    // 1. LEDピンをGPIOモードに設定し、アドレスをマッピング
    gpio_controller = ioremap(GPIO_CONTROLLER_BASE, 4);
    pin_mux_config = ioremap(PIN_MUX_CONFIG_BASE, 4);
    pin_pad_config = ioremap(PIN_PAD_CONFIG_BASE, 4);
    gpio_data_reg = ioremap(GPIO_DATA_REGISTER, 4);
    gpio_dir_reg = ioremap(GPIO_DIRECTION_REGISTER, 4);

    // GPIOピンを出力モードに設定
    value = readl(gpio_dir_reg);
    value |= (1 << 5); // ピン5を出力に設定
    writel(value, gpio_dir_reg);

    // 初期状態でLEDを消灯
    value = readl(gpio_data_reg);
    value &= ~(1 << 5); // ピン5をLOWに設定
    writel(value, gpio_data_reg);

    return result;
}

// ドライバの終了処理関数
static void __exit gpio_led_exit(void)
{
    unsigned int value = 0;

    // LEDを点灯して終了
    value = readl(gpio_data_reg);
    value |= (1 << 5); // ピン5をHIGHに設定
    writel(value, gpio_data_reg);

    // 1. アドレスマッピングを解除
    iounmap(gpio_controller);
    iounmap(pin_mux_config);
    iounmap(pin_pad_config);
    iounmap(gpio_data_reg);
    iounmap(gpio_dir_reg);

    printk("gpio_led_exit
");
}

カーネルには、レジスタの読み書きを行うための関数が用意されています。

// 8ビット、16ビット、32ビットの読み取り操作
u8 readb(const volatile void __iomem *addr);
u16 readw(const volatile void __iomem *addr);
u32 readl(const volatile void __iomem *addr);

// 8ビット、16ビット、32ビットの書き込み操作
void writeb(u8 value, volatile void __iomem *addr);
void writew(u16 value, volatile void __iomem *addr);
void writel(u32 value, volatile void __iomem *addr);

ユーザースペースからの物理メモリアクセス

Linuxは、/dev/memデバイスファイルを通じてユーザースペースから物理メモリにアクセスする機能を提供しています。このデバイスファイルをopenし、Cライブラリのmmap関数でメモリ領域にマッピングすることで、ユーザーアプリケーションから直接物理アドレスのレジスタを操作できます。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

// XYZボードのGPIOレジスタ定義
#define XYZ_BOARD_GPIO_BASE 0x40000000
#define XYZ_GPIO_DIR_OFFSET 0x00    // ピン方向レジスタ
#define XYZ_GPIO_OUT_OFFSET 0x04    // 出力データレジスタ
#define XYZ_GPIO_SET_OFFSET 0x08    // 出力セットレジスタ
#define XYZ_GPIO_CLR_OFFSET 0x0C    // 出力クリアレジスタ

// 32ビットレジスタの読み書きマクロ
#define read_reg(addr) (*(volatile uint32_t *)(addr))
#define write_reg(val, addr) (*(volatile uint32_t *)(addr) = (val))

// GPIOピンをLOWに設定する関数
int set_gpio_pin_low(void)
{
    int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (mem_fd < 0) {
        perror("Failed to open /dev/mem");
        return -1;
    }

    // GPIOレジスタをユーザースペースにマッピング
    volatile uint32_t *gpio_base = (volatile uint32_t *)mmap(NULL, 4096,
        PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, XYZ_BOARD_GPIO_BASE);
    if (gpio_base == MAP_FAILED) {
        perror("mmap failed");
        close(mem_fd);
        return -1;
    }

    printf("GPIO base mapped successfully: 0x%p
", gpio_base);

    uint32_t reg_value;
    // ピン7を出力モードに設定
    reg_value = read_reg(gpio_base + (XYZ_GPIO_DIR_OFFSET / sizeof(uint32_t)));
    reg_value |= (1 << 7);
    write_reg(reg_value, gpio_base + (XYZ_GPIO_DIR_OFFSET / sizeof(uint32_t)));

    // ピン7をLOWに設定
    write_reg((1 << 7), gpio_base + (XYZ_GPIO_CLR_OFFSET / sizeof(uint32_t)));

    // メモリマッピングを解除してファイルを閉じる
    munmap((void *)gpio_base, 4096);
    close(mem_fd);

    return 0;
}

// GPIOピンをHIGHに設定する関数
int set_gpio_pin_high(void)
{
    int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (mem_fd < 0) {
        perror("Failed to open /dev/mem");
        return -1;
    }

    // GPIOレジスタをユーザースペースにマッピング
    volatile uint32_t *gpio_base = (volatile uint32_t *)mmap(NULL, 4096,
        PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, XYZ_BOARD_GPIO_BASE);
    if (gpio_base == MAP_FAILED) {
        perror("mmap failed");
        close(mem_fd);
        return -1;
    }

    printf("GPIO base mapped successfully: 0x%p
", gpio_base);

    uint32_t reg_value;
    // ピン7を出力モードに設定
    reg_value = read_reg(gpio_base + (XYZ_GPIO_DIR_OFFSET / sizeof(uint32_t)));
    reg_value |= (1 << 7);
    write_reg(reg_value, gpio_base + (XYZ_GPIO_DIR_OFFSET / sizeof(uint32_t)));

    // ピン7をHIGHに設定
    write_reg((1 << 7), gpio_base + (XYZ_GPIO_SET_OFFSET / sizeof(uint32_t)));

    // メモリマッピングを解除してファイルを閉じる
    munmap((void *)gpio_base, 4096);
    close(mem_fd);

    return 0;
}

int main()
{
    while (1) {
        set_gpio_pin_low();
        sleep(1);
        set_gpio_pin_high();
        sleep(1);
    }
    return 0;
}

タグ: linux device-driver GPIO ioremap mmap

8月15日 01:01 投稿