PCI设备DMA内存分配与UIO映射:如何获取/dev/uioX并传递给用户空间
问题描述
我正在开展一个项目,需要为特定PCI设备分配内核空间DMA内存,目标是通过ioctl调用从用户空间触发内核完成内存分配(类似常规malloc,但每次关联不同PCI设备),同时使用Userspace I/O(UIO)框架实现用户空间对该内存的访问。
由于系统中有多个进程使用UIO框架,我不想让用户空间维护当前/dev/uioX的编号并在DMA_ALLOC和DMA_FREE后更新。请问如何获取注册生成的/dev/uioX并传递给用户空间,或者让用户空间指定后续可用于mmap的文件名(可不同于uio文件)?
现有代码
ioctl头文件
#define DMA_ALLOC _IOR('A', 1, u64) struct DMA_mesg { u64 pci_dev; u64 size; };
内核空间代码
struct uio_info kpart_info = { .name = "DMA_region", .version = "0.1", .irq = UIO_IRQ_NONE, }; static long my_ioctl(struct file *file, unsigned int cmd, u64 arg) { dma_addr_t dma_handle; gfp_t gfp=GFP_KERNEL | GFP_DMA | __GFP_ZERO; struct pci_dev *pPci_dev; struct DMA_mesg *msg = kzalloc(sizeof(struct DMA_mesg), GFP_DMA); struct uio_info *info; if (copy_from_user(msg, (void *)arg, sizeof(struct DMA_mesg))) return -EFAULT; pPci_dev=(struct pci_dev *)(msg->pci_dev); switch (cmd) { case DMA_ALLOC: { info = kzalloc(sizeof(struct uio_info), GFP_KERNEL); if (IS_ERR(info)) { ret = PTR_ERR(info); pr_err("can't malloc info "); } /* Initialize the info structure. */ *info = kpart_info; info->mem[0].size = msg->size; mem = dma_alloc_coherent(pPci_dev->dev, msg->size, &dma_handle, gfp); info->mem[0].addr = dma_handle; info->mem[0].memtype = UIO_MEM_PHYS; //where does it registered? if (uio_register_device(pPci_dev->dev, info)) { pr_err("canot register uio device "); } return 0; } default: return -ENOTTY; // Unsupported command } }
解决方案
1. 向用户空间返回/dev/uioX路径或minor号
核心思路是利用uio_register_device成功后内核填充的uio_info->minor字段,构造设备路径并通过修改后的ioctl消息结构体传回用户空间。
步骤:
- 更新ioctl定义和消息结构体:将ioctl改为双向传输类型
_IOWR,并在结构体中添加返回字段:
// 用户态头文件修改 #define DMA_ALLOC _IOWR('A', 1, struct DMA_mesg) struct DMA_mesg { u64 pci_dev; u64 size; char uio_path[64]; // 用于接收内核返回的/dev/uioX路径 };
- 内核中填充返回信息并回传:注册UIO设备成功后,构造路径并通过
copy_to_user传回用户空间,同时完善错误处理:
static long my_ioctl(struct file *file, unsigned int cmd, u64 arg) { dma_addr_t dma_handle; gfp_t gfp = GFP_KERNEL | __GFP_ZERO; // 除非老设备需求,否则GFP_DMA可移除 struct pci_dev *pPci_dev; struct DMA_mesg *msg = kzalloc(sizeof(struct DMA_mesg), GFP_KERNEL); struct uio_info *info = NULL; void *mem = NULL; int ret = 0; if (!msg) return -ENOMEM; if (copy_from_user(msg, (void *)arg, sizeof(struct DMA_mesg))) { ret = -EFAULT; goto cleanup_msg; } pPci_dev = (struct pci_dev *)(msg->pci_dev); switch (cmd) { case DMA_ALLOC: info = kzalloc(sizeof(struct uio_info), GFP_KERNEL); if (!info) { ret = -ENOMEM; pr_err("无法分配uio_info"); goto cleanup_msg; } *info = kpart_info; info->mem[0].size = msg->size; mem = dma_alloc_coherent(&pPci_dev->dev, msg->size, &dma_handle, gfp); if (!mem) { ret = -ENOMEM; pr_err("无法分配DMA内存"); goto cleanup_info; } info->mem[0].addr = dma_handle; info->mem[0].memtype = UIO_MEM_PHYS; if (uio_register_device(&pPci_dev->dev, info)) { ret = -EIO; pr_err("无法注册UIO设备"); goto cleanup_mem; } // 构造/dev/uioX路径并回传 snprintf(msg->uio_path, sizeof(msg->uio_path), "/dev/uio%d", info->minor); if (copy_to_user((void *)arg, msg, sizeof(struct DMA_mesg))) { ret = -EFAULT; pr_err("回传信息失败"); uio_unregister_device(info); goto cleanup_mem; } break; default: ret = -ENOTTY; goto cleanup_msg; } cleanup_mem: if (mem) dma_free_coherent(&pPci_dev->dev, msg->size, mem, dma_handle); cleanup_info: if (info) kfree(info); cleanup_msg: kfree(msg); return ret; }
2. 创建自定义命名的符号链接
如果希望用户空间使用固定路径而非动态的uioX编号,可以在内核中为UIO设备创建基于PCI设备唯一标识的符号链接,比如使用PCI设备的BDF地址命名。
实现示例:
在UIO设备注册成功后,添加以下代码:
// 生成基于PCI设备BDF的自定义路径 char link_path[128]; snprintf(link_path, sizeof(link_path), "/dev/dma_pci_%s", pci_name(pPci_dev)); // 创建符号链接指向/dev/uioX dev_t uio_devt = MKDEV(UIO_MAJOR, info->minor); if (mknod(link_path, S_IFCHR | 0666, uio_devt) == 0) { pr_info("创建自定义设备链接:%s", link_path); } else { pr_err("无法创建自定义设备链接"); }
注意:内核态调用mknod需要确保/dev挂载了devtmpfs,或者可以通过sysfs创建链接后配合udev规则自动生成/dev下的节点,这种方式更稳妥:
// 在PCI设备的sysfs目录下创建指向UIO设备的链接 if (sysfs_create_link(&pPci_dev->dev.kobj, &info->dev.kobj, "dma_uio") == 0) { pr_info("sysfs链接创建成功"); }
用户空间可以通过/sys/bus/pci/devices/[BDF]/dma_uio访问该链接,或者配置udev规则自动在/dev下生成自定义命名的节点。
内容的提问来源于stack exchange,提问作者nerya
相关产品推荐
相关产品推荐

