LEDドライバ実験(レジスタ直接操作)
Linuxのユーザースペースでは仮想アドレスが使用されますが、実際のハードウェアの制御には物理アドレスが必要です。データシートを参照して、GPIOピンの物理アドレスを特定します。
Linuxのデバイスドライバは、主に初期化(登録)と終了処理(アンインストール)で構成されます。文字デバイスドライバの場合、file_operations構造体にopen、write、releaseなどの関数ポインタを定義します。
カーネルスペースでの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;
}