写一个Linux UIO的示例代码

以下是一个Linux UIO的示例代码,包含内核模块和用户空间程序:

内核模块代码(my_uio_module.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <linux/module.h>
#include <linux/uio_driver.h>
#include <linux/ioport.h>
#include <linux/io.h>

#define MEM_BASE 0x1000000 // 假设的物理地址(需根据实际情况调整)
#define MEM_SIZE 0x1000 // 内存区域大小(4KB)

static struct uio_info my_uio_info = {
.name = "my_uio_device",
.version = "0.1",
.irq = UIO_IRQ_NONE, // 无中断
};

static int __init my_uio_init(void)
{
// 请求内存区域(确保MEM_BASE未被占用)
if (!request_mem_region(MEM_BASE, MEM_SIZE, "my_uio")) {
pr_err("Failed to request memory region\n");
return -EBUSY;
}

// 设置UIO内存区域信息
my_uio_info.mem[0].name = "my_uio_region";
my_uio_info.mem[0].addr = MEM_BASE; // 物理地址
my_uio_info.mem[0].size = MEM_SIZE;
my_uio_info.mem[0].memtype = UIO_MEM_PHYS;

// 注册UIO设备
if (uio_register_device(NULL, &my_uio_info)) {
pr_err("Failed to register UIO device\n");
release_mem_region(MEM_BASE, MEM_SIZE);
return -ENODEV;
}

pr_info("UIO device registered successfully\n");
return 0;
}

static void __exit my_uio_exit(void)
{
uio_unregister_device(&my_uio_info);
release_mem_region(MEM_BASE, MEM_SIZE);
pr_info("UIO device unregistered\n");
}

module_init(my_uio_init);
module_exit(my_uio_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");

用户空间程序(uio_user.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define UIO_DEV "/dev/uio0"
#define MAP_SIZE 0x1000 // 需与内核模块中的MEM_SIZE一致

int main() {
int uio_fd = open(UIO_DEV, O_RDWR);
if (uio_fd < 0) {
perror("Failed to open UIO device");
return 1;
}

// 映射内存
void *addr = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, uio_fd, 0);
if (addr == MAP_FAILED) {
perror("mmap failed");
close(uio_fd);
return 1;
}

printf("Memory mapped at address: %p\n", addr);

// 示例:读写内存
volatile unsigned int *regs = (volatile unsigned int *)addr;
printf("Read initial value: 0x%x\n", regs[0]);
regs[0] = 0x12345678; // 写入测试值
printf("Wrote 0x12345678, read back: 0x%x\n", regs[0]);

// 清理
munmap(addr, MAP_SIZE);
close(uio_fd);
return 0;
}

编译与运行步骤

  1. 编译内核模块

    1
    2
    obj-m += my_uio_module.o
    make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
  2. 插入内核模块(需要root权限):

    1
    2
    3
    4
    5
    sudo insmod my_uio_module.o
    # 检查是否注册成功
    dmesg | tail
    # 确认设备节点存在
    ls /dev/uio*
  3. 编译用户空间程序

    1
    gcc uio_user.c -o uio_user
  4. 运行用户程序(需要root权限):

    1
    sudo ./uio_user

注意事项

  1. 物理地址调整
    示例中的MEM_BASE0x1000000)是假设值,实际使用时需根据硬件手册调整。错误地址可能导致系统崩溃。

  2. 内存冲突
    使用request_mem_region前,需确保目标地址未被其他驱动占用。可通过/proc/iomem查看已注册的内存区域。

  3. 用户权限
    确保用户有权限访问/dev/uio*设备节点(通常需要root权限)。

  4. 中断处理
    如需处理中断,需在my_uio_info中设置.irq.handler,并在用户空间通过read等待中断。

  5. 动态内存分配
    更安全的方式是使用dma_alloc_coherent动态分配物理内存,避免硬编码地址。


此示例演示了UIO的基本用法,实际开发中需结合具体硬件调整参数。