You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 15:35:04