4. 实现设备内存映射
任务目标
在QNX驱动中实现mmap设备接口,将设备物理内存映射到用户空间,使用户程序可直接读写设备寄存器或内存缓冲区,避免每次访问都经过devctl()系统调用。
实现原理
QNX资源管理器框架中,mmap通过io_mmap消息处理函数实现。驱动在io_funcs表中注册mmap回调,当用户程序调用mmap()时,内核将请求路由到驱动。驱动需要将设备物理地址转换为QNX的uint64_t物理地址格式,并通过mmap_device_io()或mmap_device_memory()建立映射。
核心代码实现
// dev_mmap.c - 设备内存映射驱动示例
#include <sys/iofunc.h>
#include <sys/dispatch.h>
#include <hw/inout.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
// 设备物理地址配置(示例:假设设备寄存器位于0x28000000,长度4KB)
#define DEVICE_PHYS_ADDR 0x28000000ULL
#define DEVICE_MAP_SIZE 0x1000
// 驱动属性结构
typedef struct {
iofunc_attr_t attr;
uintptr_t mapped_addr; // 驱动内部映射地址(用于验证)
} device_attr_t;
// mmap回调:将设备物理内存映射到用户空间
int io_mmap_handler(resmgr_context_t *ctp, io_mmap_t *msg,
iofunc_attr_t *attr, void *extra)
{
device_attr_t *dev_attr = (device_attr_t *)attr;
uint64_t phys_addr = DEVICE_PHYS_ADDR;
uint64_t map_len = DEVICE_MAP_SIZE;
// 检查用户请求的映射范围是否合法
if (msg->offset + msg->len > map_len) {
return EINVAL;
}
// 计算实际物理地址(支持偏移映射)
phys_addr += msg->offset;
// 返回物理地址给内核,由内核完成用户空间映射
msg->phys = phys_addr;
msg->len = msg->len ? msg->len : (map_len - msg->offset);
// 标记为设备内存(非缓存)
msg->flags = IO_MMAP_FLAG_DEVICE_MEMORY;
return EOK;
}
// 设备打开回调
int io_open_handler(resmgr_context_t *ctp, io_open_t *msg,
iofunc_attr_t *attr, void *extra)
{
device_attr_t *dev_attr = (device_attr_t *)attr;
// 驱动内部建立映射,用于验证设备可访问
dev_attr->mapped_addr = (uintptr_t)mmap_device_io(DEVICE_MAP_SIZE,
DEVICE_PHYS_ADDR);
if (dev_attr->mapped_addr == (uintptr_t)MAP_FAILED) {
return errno;
}
return iofunc_open_default(ctp, msg, attr, extra);
}
// 设备关闭回调
int io_close_handler(resmgr_context_t *ctp, void *reserved,
iofunc_attr_t *attr, void *extra)
{
device_attr_t *dev_attr = (device_attr_t *)attr;
if (dev_attr->mapped_addr) {
munmap_device_io(dev_attr->mapped_addr, DEVICE_MAP_SIZE);
dev_attr->mapped_addr = 0;
}
return iofunc_close_default(ctp, reserved, attr, extra);
}
int main(int argc, char *argv[])
{
dispatch_t *dpp;
resmgr_io_funcs_t io_funcs;
resmgr_connect_funcs_t connect_funcs;
device_attr_t dev_attr;
int id;
// 初始化属性
memset(&dev_attr, 0, sizeof(dev_attr));
iofunc_attr_init(&dev_attr.attr, S_IFCHR | 0666, 0, 0);
// 创建分发器
dpp = dispatch_create();
if (dpp == NULL) {
perror("dispatch_create");
return 1;
}
// 注册连接函数
iofunc_funcs_init(&connect_funcs, &io_funcs);
connect_funcs.open = io_open_handler;
connect_funcs.close = io_close_handler;
io_funcs.mmap = io_mmap_handler; // 注册mmap回调
// 创建资源管理器
id = resmgr_attach(dpp, NULL, "/dev/devmap", _FTYPE_ANY, 0,
&connect_funcs, &io_funcs, &dev_attr);
if (id == -1) {
perror("resmgr_attach");
return 1;
}
printf("Device mmap driver started at /dev/devmap\n");
printf("Physical address: 0x%llx, size: 0x%x\n",
DEVICE_PHYS_ADDR, DEVICE_MAP_SIZE);
// 进入消息循环
dispatch_block(dpp);
return 0;
}
编译与运行
# 编译驱动(QNX SDP环境)
qcc -Vgcc_ntox86_64 -o dev_mmap dev_mmap.c -lresmgr -lposix
# 运行驱动(需root权限)
./dev_mmap
# 用户空间测试程序(test_mmap.c)
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdint.h>
int main() {
int fd = open("/dev/devmap", O_RDWR);
if (fd < 0) { perror("open"); return 1; }
// 映射设备内存(映射全部4KB)
volatile uint8_t *map = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (map == MAP_FAILED) { perror("mmap"); return 1; }
// 读取设备寄存器(示例:读取偏移0x00处的值)
uint32_t reg_val = *(volatile uint32_t *)map;
printf("Read register at offset 0x00: 0x%08x\n", reg_val);
// 写入测试(注意:实际设备寄存器可能只读)
// *(volatile uint32_t *)map = 0x12345678;
munmap((void *)map, 0x1000);
close(fd);
return 0;
}
# 编译测试程序
qcc -Vgcc_ntox86_64 -o test_mmap test_mmap.c
# 运行测试
./test_mmap
验证方法
- 驱动启动后,检查
/dev/devmap设备节点是否存在:ls -l /dev/devmap - 运行
test_mmap,若映射成功,程序将打印设备寄存器偏移0x00处的值(具体值取决于硬件);若映射失败,mmap()返回MAP_FAILED并打印错误信息。 - 使用
pidin syspage确认驱动进程存在,使用pidin mem可查看进程内存映射状态。
通过标准:测试程序能成功完成mmap()调用并读取到设备内存值,且驱动无错误输出。若设备物理地址0x28000000在目标硬件上不存在,可替换为实际设备地址后重新编译测试。
关键说明
- 本示例使用
IO_MMAP_FLAG_DEVICE_MEMORY标记设备内存,禁止缓存,确保读写直接访问硬件寄存器。 - 驱动内部通过
mmap_device_io()建立映射用于验证设备可访问性,实际驱动中可根据需要保留或移除。 - 用户空间
mmap()的offset参数对应设备物理地址偏移,本实现支持按页偏移映射。 - 若设备内存大于4KB,需调整
DEVICE_MAP_SIZE并确保物理地址连续。