专题二:AXI_DMA驱动分析

专题二:AXI_DMA驱动分析

1设备树

Petalinux构建的工程,设备树拥有重写特性,system-user.dtsi可以重写pl.dtsi中的内容

1.1pl.dtsi

/*
 * CAUTION: This file is automatically generated by Xilinx.
 * Version:  
 * Today is: Mon Apr  4 11:11:25 2022
 */


/ {
	amba_pl: amba_pl@0 {
		#address-cells = <2>;
		#size-cells = <2>;
		compatible = "simple-bus";
		ranges ;
		axi_dma_0: dma@80000000 {
			#dma-cells = <1>;
			clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
			clocks = <&zynqmp_clk 71>, <&zynqmp_clk 71>, <&zynqmp_clk 71>;
			compatible = "xlnx,axi-dma-7.1", "xlnx,axi-dma-1.00.a";
			interrupt-names = "mm2s_introut", "s2mm_introut";
			interrupt-parent = <&gic>;
			interrupts = <0 89 4 0 90 4>;
			reg = <0x0 0x80000000 0x0 0x10000>;
			xlnx,addrwidth = <0x20>;
			xlnx,sg-length-width = <0xe>;
			dma-channel@80000000 {
				compatible = "xlnx,axi-dma-mm2s-channel";
				dma-channels = <0x1>;
				interrupts = <0 89 4>;
				xlnx,datawidth = <0x20>;
				xlnx,device-id = <0x0>;
			};
			dma-channel@80000030 {
				compatible = "xlnx,axi-dma-s2mm-channel";
				dma-channels = <0x1>;
				interrupts = <0 90 4>;
				xlnx,datawidth = <0x20>;
				xlnx,device-id = <0x0>;
			};
		};
	};
};

1.2system-user.dtsi

/include/ "system-conf.dtsi"
/ {

};
/* SD */
&sdhci1 {
	disable-wp;
	no-1-8-v;
};
/* USB */
&dwc3_0 {
	status = "okay";
	dr_mode = "host";
};


&amba_pl{
    axidma_chrdev: axidma_chrdev@0 {
            compatible = "xlnx,axidma-chrdev";
            dmas = <&axi_dma_0 0 &axi_dma_0 1>;
            dma-names = "tx_channel", "rx_channel";
    };
};
 
&axi_dma_0{
    dma-channel@80000000 {
        xlnx,device-id = <0x0>;
    };
    dma-channel@80000030 {
        xlnx,device-id = <0x1>;
    };
};

2结构体

2.1struct axidma_device结构体

struct axidma_device {
    int num_devices;                // The number of devices
    unsigned int minor_num;         // The minor number of the device
    dev_t dev_num;                  // The device number of the device
    char *chrdev_name;              // The name of the character device
    struct device *device;          // Device structure for the char device
    struct class *dev_class;        // The device class for the chardevice
    struct cdev chrdev;             // The character device structure

    int num_dma_tx_chans;           // The number of transmit DMA channels
    int num_dma_rx_chans;           // The number of receive DMA channels
    int num_vdma_tx_chans;          // The number of transmit VDMA channels
    int num_vdma_rx_chans;          // The number of receive  VDMA channels
    int num_chans;                  // The total number of DMA channels
    int notify_signal;              // Signal used to notify transfer completion
    struct platform_device *pdev;   // The platofrm device from the device tree
    struct axidma_cb_data *cb_data; // The callback data for each channel
    struct axidma_chan *channels;   // All available channels
    struct list_head dmabuf_list;   // List of allocated DMA buffers
    struct list_head external_dmabufs;  // Buffers allocated in other drivers
};

2.2dev_t结构体

dev_t结构体

在内核中,dev_t结构体用来保存设备编号信息,在linux/type.h中定义,是一个32位的数,12位表示主设备号+20位的次设备号

int MAJOR(dev_t dev)//获得dev的主设备号
int MINOR(dev_t dev)//获得dev的次设备号
dev_t MKDEV(unsignde int major,unsigned int minor)//由主次设备号获得dev_t数据的宏。

2.3device结构体

(5条消息) 阅读Linux设备驱动模型源码之 device结构体成员详解_Qidi_Huang的博客-CSDN博客_device结构体

2.4class结构体

(5条消息) linux class结构体,linux中class_create和device_creat_肖牧之的博客-CSDN博客

2.5cdev结构体

struct cdev {
    struct kobject kobj;                    // 内嵌的kobject对象
    struct module *owner;                   // 所属模块
    const struct file_operations *ops;      // 文件操作结构体
    struct list_head list;                  //linux内核所维护的链表指针
    dev_t dev;                              //设备号
    unsigned int count;                     //设备数目
};

cdev结构体 - bluebluebluesky - 博客园 (cnblogs.com)

2.6platform_device结构体

struct platform_device {
    const char    * name
    u32        id
    struct device    dev
    u32        num_resources
    struct resource    * resource
}

device结构和platform_device结构-xiaohuima_dong-ChinaUnix博客

2.7struct axidma_cb_data结构体

// The data to pass to the DMA transfer completion callback function
struct axidma_cb_data {
    int channel_id;                 // The id of the channel used
    int notify_signal;              // For async, signal to send
    struct task_struct *process;    // The process to send the signal to
    struct completion *comp;        // For sync, the notification to kernel
};

2.7.1struct task_struct结构体

(5条消息) 浅析task_struct结构体_奄奄不息的博客-CSDN博客_task_struct

2.7.2struct completion结构体

(5条消息) 并发控制中的completion_O刺客的博客-CSDN博客_completion

2.8struct axidma_chan结构体

// TODO: Channel really should not be here
struct axidma_chan {
    enum axidma_dir dir;            // The DMA direction of the channel
    enum axidma_type type;          // The DMA type of the channel
    int channel_id;                 // The identifier for the device
    const char *name;               // Name of the channel (ignore)
    struct dma_chan *chan;          // The DMA channel (ignore)
};

2.8.1enum axidma_dir枚举类

enum axidma_dir {
    AXIDMA_WRITE,                   ///< Transmits from memory to a device.
    AXIDMA_READ                     ///< Transmits from a device to memory.
};

2.8.2enum axidma_type枚举类

enum axidma_type {
    AXIDMA_DMA,                     ///< Standard AXI DMA engine
    AXIDMA_VDMA                     ///< Specialized AXI video DMA enginge
};

2.8.3struct dma_chan结构体

(5条消息) DMA 相关的一些结构体_lamdoc的博客-CSDN博客

/**
 * struct dma_chan - devices supply DMA channels, clients use them
 * @device: ptr to the dma device who supplies this channel, always !%NULL
 * @cookie: last cookie value returned to client
 * @chan_id: channel ID for sysfs
 * @dev: class device for sysfs
 * @device_node: used to add this to the device chan list
 * @local: per-cpu pointer to a struct dma_chan_percpu
 * @client-count: how many clients are using this channel
 * @table_count: number of appearances in the mem-to-mem allocation table
 * @private: private data for certain client-channel associations
 */
struct dma_chan {
	struct dma_device *device;
	dma_cookie_t cookie;
 
	/* sysfs */
	int chan_id;
	struct dma_chan_dev *dev;
 
	struct list_head device_node;
	struct dma_chan_percpu __percpu *local;
	int client_count;
	int table_count;
	void *private;
};

2.9struct list_head结构体

Linux内核中的双向链表struct list_head - Cqlismy - 博客园 (cnblogs.com)

在内核源码中,list_head结构体的定义在文件source_code/include/linux/types.h文件中,结构体定义如下:

struct list_head {
    struct list_head *next, *prev;
};
通过这个结构体开始构建链表,然后插入、删除节点,遍历整个链表等,内核已经提供好了现成的调用接口。

2.10struct axidma_dma_allocation结构体

// A structure that represents a DMA buffer allocation
struct axidma_dma_allocation {
    size_t size;                // Size of the buffer
    void *user_addr;            // User virtual address of the buffer
    void *kern_addr;            // Kernel virtual address of the buffer
    dma_addr_t dma_addr;        // DMA bus address of the buffer
    struct list_head list;      // List node pointers for allocation list
};

2.11struct axidma_num_channels结构体

struct axidma_num_channels {
    int num_channels;               // Total DMA channels in the system
    int num_dma_tx_channels;        // DMA transmit channels available
    int num_dma_rx_channels;        // DMA receive channels available
    int num_vdma_tx_channels;       // VDMA transmit channels available
    int num_vdma_rx_channels;       // VDMA receive channels available
};

2.12struct axidma_channel_info结构体

struct axidma_channel_info {
    struct axidma_chan *channels;   // Metadata about all available channels
};

2.13struct axidma_register_buffer结构体

struct axidma_register_buffer {
    int fd;                         // Anonymous file descritpor for DMA buffer
    size_t size;                    // The size of the external DMA buffer
    void *user_addr;                // User virtual address of the buffer
};

2.14struct axidma_transaction结构体

struct axidma_transaction {
    bool wait;                      // Indicates if the call is blocking
    int channel_id;                 // The id of the DMA channel to use
    void *buf;                      // The buffer used for the transaction
    size_t buf_len;                 // The length of the buffer
};

2.15struct axidma_inout_transaction结构体

struct axidma_inout_transaction {
    bool wait;                      // Indicates if the call is blocking
    int tx_channel_id;              // The id of the transmit DMA channel
    void *tx_buf;                   // The buffer containing the data to send
    size_t tx_buf_len;              // The length of the transmit buffer
    int rx_channel_id;              // The id of the receive DMA channel
    void *rx_buf;                   // The buffer to place the data in
    size_t rx_buf_len;              // The length of the receive buffer
};

2.16struct axidma_video_transaction结构体

struct axidma_video_transaction {
    int channel_id;             // The id of the DMA channel to transmit video
    int num_frame_buffers;      // The number of frame buffers to use.
    void **frame_buffers;       // The frame buffer addresses to use for video
    size_t width;               // The width of the image in pixels
    size_t height;              // The height of the image in lines
    size_t depth;               // The size of each pixel in bytes
};

2.17struct axidma_transfer结构体

// A convenient structure to pass between prep and start transfer functions
struct axidma_transfer {
    int sg_len;                     // The length of the BD array
    struct scatterlist *sg_list;    // List of buffer descriptors
    bool wait;                      // Indicates if we should wait
    dma_cookie_t cookie;            // The DMA cookie for the transfer
    struct completion comp;         // A completion to use for waiting
    enum axidma_dir dir;            // The direction of the transfer
    enum axidma_type type;          // The type of the transfer (VDMA/DMA)
    int channel_id;                 // The ID of the channel
    int notify_signal;              // The signal to use for async transfers
    struct task_struct *process;    // The process requesting the transfer
    struct axidma_cb_data *cb_data; // The callback data struct

    // VDMA specific fields (kept as union for extensability)
    union {
        struct {
            int width;              // Width of the image in pixels
            int height;             // Height of the image in lines
            int depth;              // Size of each pixel in bytes
        } vdma_tfr;
    };
};

2.18struct scatterlist结构体

(5条消息) struct scatterlist 使用_咸稀饭的博客-CSDN博客_scatterlist结构体声明

struct scatterlist {
     ...
     /* User input members */
    unsigned long   page_link;// pointer to a page, but the bit0 and bit1 have special info.[1]
    unsigned int    offset; // Offset of data buffer in page referred by @page_link
    unsigned int    length; //Length of data
    /* Output value */
    dma_addr_t  dma_address; // this address can be used by device to do DMA 
     ...
};

3分析

axi_dma.c模块完成内核驱动的初始化
/*----------------------------------------------------------------------------
 * Module Initialization and Exit
 *----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------
 * 初始化调用平台总线进行注册platform_driver_register(&axidma_driver);
 *----------------------------------------------------------------------------*/
static int __init axidma_init(void)
{
    return platform_driver_register(&axidma_driver);
}

static void __exit axidma_exit(void)
{
    return platform_driver_unregister(&axidma_driver);
}

module_init(axidma_init);
module_exit(axidma_exit);

MODULE_AUTHOR("Brandon Perez");
MODULE_AUTHOR("Jared Choi");

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Module to provide a userspace interface for transferring "
                   "data from the processor to the logic fabric via AXI DMA.");

axi_dma.c模块中的axidma_driver结构体
static const struct of_device_id axidma_compatible_of_ids[] = {
    { .compatible = "xlnx,axidma-chrdev" },
    {}
};

static struct platform_driver axidma_driver = {
    .driver = {
        .name = MODULE_NAME,
        .owner = THIS_MODULE,
        .of_match_table = axidma_compatible_of_ids,
    },
    .probe = axidma_probe,
    .remove = axidma_remove,
};
当匹配后,回调axi_dma.c模块中的axidma_probe函数

3.1axidma_probe()函数分析

/*----------------------------------------------------------------------------
 * Platform Device Functions
 *----------------------------------------------------------------------------*/

static int axidma_probe(struct platform_device *pdev)
{
/*----------------------------------------------------------------------------
 * 1.变量初始化
 *----------------------------------------------------------------------------*/
    int rc;
    struct axidma_device *axidma_dev;

    // Allocate a AXI DMA device structure to hold metadata about the DMA
    axidma_dev = kmalloc(sizeof(*axidma_dev), GFP_KERNEL);
    if (axidma_dev == NULL) {
        axidma_err("Unable to allocate the AXI DMA device structure.\n");
        return -ENOMEM;
    }
    axidma_dev->pdev = pdev;
/*----------------------------------------------------------------------------
 * 2.AXIdma初始化
 *----------------------------------------------------------------------------*/
    // Initialize the DMA interface
    rc = axidma_dma_init(pdev, axidma_dev);
    if (rc < 0) {
        goto free_axidma_dev;
    }
/*----------------------------------------------------------------------------
 * 3.字符设备初始化
// Default name of the character of the device
#define CHRDEV_NAME                 AXIDMA_DEV_NAME
// Default minor number for the device
#define MINOR_NUMBER                0
// The default number of character devices for DMA
#define NUM_DEVICES                 1

static char *chrdev_name = CHRDEV_NAME;
module_param(chrdev_name, charp, S_IRUGO);

static int minor_num = MINOR_NUMBER;
module_param(minor_num, int, S_IRUGO);
*----------------------------------------------------------------------------*/
    // Assign the character device name, minor number, and number of devices
    axidma_dev->chrdev_name = chrdev_name;
    axidma_dev->minor_num = minor_num;
    axidma_dev->num_devices = NUM_DEVICES;

    // Initialize the character device for the module.
    rc = axidma_chrdev_init(axidma_dev);
    if (rc < 0) {
        goto destroy_dma_dev;
    }
/*----------------------------------------------------------------------------
 * 4.将axidma_dev结构存入平台设备pdev的私有数据区
*----------------------------------------------------------------------------*/
    // Set the private data in the device to the AXI DMA device structure
    dev_set_drvdata(&pdev->dev, axidma_dev);
    return 0;

destroy_dma_dev:
    axidma_dma_exit(axidma_dev);
free_axidma_dev:
    kfree(axidma_dev);
    return -ENOSYS;
}

3.1.2axidma_dma_init(pdev, axidma_dev)函数分析

int axidma_dma_init(struct platform_device *pdev, struct axidma_device *dev)
{
    int rc;
    size_t elem_size;
/*----------------------------------------------------------------------------
 * 1.解析设备树,获取总通道个数
 *	为每个通道的struct axidma_chan *channels结构体分配内存;
 *	为每个通道的struct axidma_cb_data *cb_data结构体分配内存;
 *----------------------------------------------------------------------------*/
    // Get the number of DMA channels listed in the device tree
    dev->num_chans = axidma_of_num_channels(pdev);
    if (dev->num_chans < 0) {
        return dev->num_chans;
    }

    // Allocate an array to store all the channel metdata structures
    elem_size = sizeof(dev->channels[0]);
    dev->channels = kmalloc(dev->num_chans * elem_size, GFP_KERNEL);
    if (dev->channels == NULL) {
        axidma_err("Unable to allocate memory for channel structures.\n");
        return -ENOMEM;
    }

    // Allocate an array to store all callback structures, for async
    elem_size = sizeof(dev->cb_data[0]);
    dev->cb_data = kmalloc(dev->num_chans * elem_size, GFP_KERNEL);
    if (dev->cb_data == NULL) {
        axidma_err("Unable to allocate memory for callback structures.\n");
        rc = -ENOMEM;
        goto free_channels;
    }
/*----------------------------------------------------------------------------
 * 2.解析设备树,获取各个通道的类型、方向,并请求dma_chan结构体
 *----------------------------------------------------------------------------*/
    // Parse the type and direction of each DMA channel from the device tree
    rc = axidma_of_parse_dma_nodes(pdev, dev);
    if (rc < 0) {
        return rc;
    }

    // Exclusively request all of the channels in the device tree entry
    rc = axidma_request_channels(pdev, dev);
    if (rc < 0) {
        goto free_callback_data;
    }

    axidma_info("DMA: Found %d transmit channels and %d receive channels.\n",
                dev->num_dma_tx_chans, dev->num_dma_rx_chans);
    axidma_info("VDMA: Found %d transmit channels and %d receive channels.\n",
                dev->num_vdma_tx_chans, dev->num_vdma_rx_chans);
    return 0;

free_callback_data:
    kfree(dev->cb_data);
free_channels:
    kfree(dev->channels);
    return rc;
}

3.1.2axidma_chrdev_init函数分析

/*----------------------------------------------------------------------------
 * Initialization and Cleanup
 *----------------------------------------------------------------------------*/

int axidma_chrdev_init(struct axidma_device *dev)
{
    int rc;
/*----------------------------------------------------------------------------
 * 1.创建字符设备
 *----------------------------------------------------------------------------*/
    // Store a global pointer to the axidma device
    axidma_dev = dev;

    // Allocate a major and minor number region for the character device
    rc = alloc_chrdev_region(&dev->dev_num, dev->minor_num, dev->num_devices,
                             dev->chrdev_name);
    if (rc < 0) {
        axidma_err("Unable to allocate character device region.\n");
        goto ret;
    }

    // Create a device class for our device
    dev->dev_class = class_create(THIS_MODULE, dev->chrdev_name);
    if (IS_ERR(dev->dev_class)) {
        axidma_err("Unable to create a device class.\n");
        rc = PTR_ERR(dev->dev_class);
        goto free_chrdev_region;
    }

    /* Create a device for our module. This will create a file on the
     * filesystem, under "/dev/dev->chrdev_name". */
    dev->device = device_create(dev->dev_class, NULL, dev->dev_num, NULL,
                                dev->chrdev_name);
    if (IS_ERR(dev->device)) {
        axidma_err("Unable to create a device.\n");
        rc = PTR_ERR(dev->device);
        goto class_cleanup;
    }
/*----------------------------------------------------------------------------
 * 2.注册字符设备和ops结构体
 *----------------------------------------------------------------------------*/
    // Register our character device with the kernel
    cdev_init(&dev->chrdev, &axidma_fops);
    rc = cdev_add(&dev->chrdev, dev->dev_num, dev->num_devices);
    if (rc < 0) {
        axidma_err("Unable to add a character device.\n");
        goto device_cleanup;
    }
/*----------------------------------------------------------------------------
 * 3.创建双向链表
 *----------------------------------------------------------------------------*/
    // Initialize the list for DMA mmap'ed allocations
    INIT_LIST_HEAD(&dev->dmabuf_list);
    INIT_LIST_HEAD(&dev->external_dmabufs);

    return 0;

device_cleanup:
    device_destroy(dev->dev_class, dev->dev_num);
class_cleanup:
    class_destroy(dev->dev_class);
free_chrdev_region:
    unregister_chrdev_region(dev->dev_num, dev->num_devices);
ret:
    return rc;
}

3.2axidma_fops结构

// The file operations for the AXI DMA device
static const struct file_operations axidma_fops = {
    .owner = THIS_MODULE,
    .open = axidma_open,
    .release = axidma_release,
    .mmap = axidma_mmap,
    .unlocked_ioctl = axidma_ioctl,
};

3.2.1axidma_open()函数

static int axidma_open(struct inode *inode, struct file *file)
{
    // Only the root user can open this device, and it must be exclusive
    if (!capable(CAP_SYS_ADMIN)) {
        axidma_err("Only root can open this device.");
        return -EACCES;
    } else if (!(file->f_flags & O_EXCL)) {
        axidma_err("O_EXCL must be specified for open()\n");
        return -EINVAL;
    }
/*----------------------------------------------------------------------------
 * 1.将axidma_dev结构存入文件结构体file的私有数据区
*----------------------------------------------------------------------------*/
    // Place the axidma structure in the private data of the file
    file->private_data = (void *)axidma_dev;
    return 0;
}

3.2.2axidma_release()函数

static int axidma_release(struct inode *inode, struct file *file)
{
    file->private_data = NULL;
    return 0;
}

3.2.3axidma_mmap()函数

static int axidma_mmap(struct file *file, struct vm_area_struct *vma)
{
/*----------------------------------------------------------------------------
 * 1.变量初始化
 *	获取file结构体中设置的私有数据
 *	获取系统分配的vma起始地址和种植地址
*----------------------------------------------------------------------------*/
    int rc;
    struct axidma_device *dev;
    struct axidma_dma_allocation *dma_alloc;

    // Get the axidma device structure
    dev = file->private_data;

    // Allocate a structure to store data about the DMA mapping
    dma_alloc = kmalloc(sizeof(*dma_alloc), GFP_KERNEL);
    if (dma_alloc == NULL) {
        axidma_err("Unable to allocate VMA data structure.");
        rc = -ENOMEM;
        goto ret;
    }

    // Set the user virtual address and the size
    dma_alloc->size = vma->vm_end - vma->vm_start;
    dma_alloc->user_addr = (void *)vma->vm_start;
/*----------------------------------------------------------------------------
 * 2.调用of_dma_configure初始化
*----------------------------------------------------------------------------*/
    // Configure the DMA device
    of_dma_configure(dev->device, NULL);
/*----------------------------------------------------------------------------
 * 3.申请关闭cache
*----------------------------------------------------------------------------*/
    // Allocate the requested region a contiguous and uncached for DMA
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
/*----------------------------------------------------------------------------
 * 4.申请连续内存
*----------------------------------------------------------------------------*/
    dma_alloc->kern_addr = dma_alloc_coherent(dev->device, dma_alloc->size,
                                              &dma_alloc->dma_addr, GFP_KERNEL);
    if (dma_alloc->kern_addr == NULL) {
        axidma_err("Unable to allocate contiguous DMA memory region of size "
                   "%zu.\n", dma_alloc->size);
        axidma_err("Please make sure that you specified cma= on the "
                   "kernel command line, and the size is large enough.\n");
        rc = -ENOMEM;
        goto free_vma_data;
    }
/*----------------------------------------------------------------------------
 * 5.Map the region into userspace
*----------------------------------------------------------------------------*/
    // Map the region into userspace
    rc = dma_mmap_coherent(dev->device, vma, dma_alloc->kern_addr,
                           dma_alloc->dma_addr, dma_alloc->size);
    if (rc < 0) {
        axidma_err("Unable to remap address %p to userspace address %p, size "
                   "%zu.\n", dma_alloc->kern_addr, dma_alloc->user_addr,
                   dma_alloc->size);
        goto free_dma_region;
    }
/*----------------------------------------------------------------------------
 * 6.Override the VMA close with our call, so that we can free the DMA region
     * when the memory region is closed. Pass in the data to do so
*----------------------------------------------------------------------------*/
    vma->vm_ops = &axidma_vm_ops;
    vma->vm_private_data = dma_alloc;

    // Add the allocation to the driver's list of DMA buffers
    list_add(&dma_alloc->list, &dev->dmabuf_list);
    return 0;

free_dma_region:
    dma_free_coherent(dev->device, dma_alloc->size, dma_alloc->kern_addr,
                      dma_alloc->dma_addr);
free_vma_data:
    kfree(dma_alloc);
ret:
    return rc;
}

3.2.3.1axidma_vm_ops结构重写
static void axidma_vma_close(struct vm_area_struct *vma)
{
    struct axidma_device *dev;
    struct axidma_dma_allocation *dma_alloc;

    // Get the AXI DMA allocation data and free the DMA buffer
    dev = axidma_dev;
    dma_alloc = vma->vm_private_data;
    dma_free_coherent(dev->device, dma_alloc->size, dma_alloc->kern_addr,
                      dma_alloc->dma_addr);

    // Remove the allocation from the list, and free the structure
    list_del(&dma_alloc->list);
    kfree(dma_alloc);

    return;
}

// The VMA operations for the AXI DMA device
static const struct vm_operations_struct axidma_vm_ops = {
    .close = axidma_vma_close,
};

3.2.4axidma_ioctl()函数

static long axidma_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    long rc;
    size_t size;
    void *__user arg_ptr;
    struct axidma_device *dev;
    struct axidma_num_channels num_chans;
    struct axidma_channel_info usr_chans, kern_chans;
    struct axidma_register_buffer ext_buf;
    struct axidma_transaction trans;
    struct axidma_inout_transaction inout_trans;
    struct axidma_video_transaction video_trans, *__user user_video_trans;
    struct axidma_chan chan_info;
/*----------------------------------------------------------------------------
 * 1.(void __user *)arg 指的是arg值是一个用户空间的地址,不能直接进行拷贝等,要使用例如copy_from_user,copy_to_user等函数。
*----------------------------------------------------------------------------*/
    // Coerce the arguement as a userspace pointer
    arg_ptr = (void __user *)arg;
/*----------------------------------------------------------------------------
 * 2.验证命令的合法性
 
 // The magic number used to distinguish IOCTL's for our device
#define AXIDMA_IOCTL_MAGIC              'W'

// The number of IOCTL's implemented, used for verification
#define AXIDMA_NUM_IOCTLS               10
*----------------------------------------------------------------------------*/
    // Verify that this IOCTL is intended for our device, and is in range
    if (_IOC_TYPE(cmd) != AXIDMA_IOCTL_MAGIC) {
        axidma_err("IOCTL command magic number does not match.\n");
        return -ENOTTY;
    } else if (_IOC_NR(cmd) >= AXIDMA_NUM_IOCTLS) {
        axidma_err("IOCTL command is out of range for this device.\n");
        return -ENOTTY;
    }

    // Verify the input argument
    if ((_IOC_DIR(cmd) & _IOC_READ)) {
        if (!axidma_access_ok(arg_ptr, _IOC_SIZE(cmd), false)) {
            return -EFAULT;
        }
    } else if (_IOC_DIR(cmd) & _IOC_WRITE) {
        if (!axidma_access_ok(arg_ptr, _IOC_SIZE(cmd), true)) {
            return -EFAULT;
        }
    }

    // Get the axidma device from the file
    dev = file->private_data;
/*----------------------------------------------------------------------------
 * 2.开始解析命令
*----------------------------------------------------------------------------*/
    // Perform the specified command
    switch (cmd) {
        case AXIDMA_GET_NUM_DMA_CHANNELS:
            axidma_get_num_channels(dev, &num_chans);
            if (copy_to_user(arg_ptr, &num_chans, sizeof(num_chans)) != 0) {
                axidma_err("Unable to copy channel info to userspace for "
                           "AXIDMA_GET_NUM_DMA_CHANNELS.\n");
                return -EFAULT;
            }
            rc = 0;
            break;

        case AXIDMA_GET_DMA_CHANNELS:
            if (copy_from_user(&usr_chans, arg_ptr, sizeof(usr_chans)) != 0) {
                axidma_err("Unable to copy channel buffer address from "
                           "userspace for AXIDMA_GET_DMA_CHANNELS.\n");
                return -EFAULT;
            }

            // Copy the channels array to userspace
            axidma_get_num_channels(dev, &num_chans);
            axidma_get_channel_info(dev, &kern_chans);
            size = num_chans.num_channels * sizeof(kern_chans.channels[0]);
            if (copy_to_user(usr_chans.channels, kern_chans.channels, size)) {
                axidma_err("Unable to copy channel ids to userspace for "
                           "AXIDMA_GET_DMA_CHANNELS.\n");
                return -EFAULT;
            }

            rc = 0;
            break;

        case AXIDMA_SET_DMA_SIGNAL:
            rc = axidma_set_signal(dev, arg);
            break;

        case AXIDMA_REGISTER_BUFFER:
            if (copy_from_user(&ext_buf, arg_ptr, sizeof(ext_buf)) != 0) {
                axidma_err("Unable to copy external buffer info from userspace "
                           "for AXIDMA_REGISTER_BUFFER.\n");
                return -EFAULT;
            }
            rc = axidma_get_external(dev, &ext_buf);
            break;

        case AXIDMA_DMA_READ:
            if (copy_from_user(&trans, arg_ptr, sizeof(trans)) != 0) {
                axidma_err("Unable to copy transfer info from userspace for "
                           "AXIDMA_DMA_READ.\n");
                return -EFAULT;
            }
            rc = axidma_read_transfer(dev, &trans);
            break;

        case AXIDMA_DMA_WRITE:
            if (copy_from_user(&trans, arg_ptr, sizeof(trans)) != 0) {
                axidma_err("Unable to copy transfer info from userspace for "
                           "AXIDMA_DMA_WRITE.\n");
                return -EFAULT;
            }
            rc = axidma_write_transfer(dev, &trans);
            break;

        case AXIDMA_DMA_READWRITE:
            if (copy_from_user(&inout_trans, arg_ptr,
                               sizeof(inout_trans)) != 0) {
                axidma_err("Unable to copy transfer info from userspace for "
                           "AXIDMA_DMA_READWRITE.\n");
                return -EFAULT;
            }
            rc = axidma_rw_transfer(dev, &inout_trans);
            break;

        case AXIDMA_DMA_VIDEO_READ:
            if (copy_from_user(&video_trans, arg_ptr,
                               sizeof(video_trans)) != 0) {
                axidma_err("Unable to copy transfer info from userspace for "
                           "AXIDMA_DMA_VIDEO_READ.\n");
                return -EFAULT;
            }

            // Allocate a kernel-space array for the frame buffers
            size = video_trans.num_frame_buffers *
                   sizeof(video_trans.frame_buffers[0]);
            video_trans.frame_buffers = kmalloc(size, GFP_KERNEL);
            if (video_trans.frame_buffers == NULL) {
                axidma_err("Unable to allocate array for the frame buffers.\n");
                return -ENOMEM;
            }

            // Copy the frame buffer array from user space to kernel space
            user_video_trans = (struct axidma_video_transaction *__user)arg_ptr;
            if (!copy_from_user(video_trans.frame_buffers,
                        user_video_trans->frame_buffers, size) != 0) {
                axidma_err("Unable to copy the frame buffer array from "
                        "userspace for AXIDMA_VIDEO_READ.\n");
                kfree(video_trans.frame_buffers);
                return -EFAULT;
            }

            rc = axidma_video_transfer(dev, &video_trans, AXIDMA_READ);
            kfree(video_trans.frame_buffers);
            break;

        case AXIDMA_DMA_VIDEO_WRITE:
            if (copy_from_user(&video_trans, arg_ptr,
                               sizeof(video_trans)) != 0) {
                axidma_err("Unable to copy transfer info from userspace for "
                           "AXIDMA_VIDEO_WRITE.\n");
                return -EFAULT;
            }

            // Allocate a kernel-space array for the frame buffers
            size = video_trans.num_frame_buffers *
                   sizeof(video_trans.frame_buffers[0]);
            video_trans.frame_buffers = kmalloc(size, GFP_KERNEL);
            if (video_trans.frame_buffers == NULL) {
                axidma_err("Unable to allocate array for the frame buffers.\n");
                return -ENOMEM;
            }

            // Copy the frame buffer array from user space to kernel space
            user_video_trans = (struct axidma_video_transaction *__user)arg_ptr;
            if (!copy_from_user(video_trans.frame_buffers,
                        user_video_trans->frame_buffers, size) != 0) {
                axidma_err("Unable to copy the frame buffer array from "
                        "userspace for AXIDMA_VIDEO_READ.\n");
                kfree(video_trans.frame_buffers);
                return -EFAULT;
            }

            rc = axidma_video_transfer(dev, &video_trans, AXIDMA_WRITE);
            kfree(video_trans.frame_buffers);
            break;

        case AXIDMA_STOP_DMA_CHANNEL:
            if (copy_from_user(&chan_info, arg_ptr, sizeof(chan_info)) != 0) {
                axidma_err("Unable to channel info from userspace for "
                           "AXIDMA_STOP_DMA_CHANNEL.\n");
            }
            rc = axidma_stop_channel(dev, &chan_info);
            break;

        case AXIDMA_UNREGISTER_BUFFER:
            rc = axidma_put_external(dev, (void *)arg);
            break;

        // Invalid command (already handled in preamble)
        default:
            return -ENOTTY;
    }

    return rc;
}
3.2.4.1命令定义
/**
 * Returns the number of available DMA channels in the system.
 *
 * This writes to the structure given as input by the user, telling the numbers for all DMA channels in the system.
 *
 * Outputs:
 *  - num_channels - The total number of DMA channels in the system.
 *  - num_dma_tx_channels - The number of transmit AXI DMA channels
 *  - num_dma_rx_channels - The number of receive AXI DMA channels
 *  - num_vdma_tx_channels - The number of transmit AXI VDMA channels
 *  - num_vdma_rx_channels - The number of receive AXI VDMA channels
 **/
#define AXIDMA_GET_NUM_DMA_CHANNELS     _IOW(AXIDMA_IOCTL_MAGIC, 0, \
                                             struct axidma_num_channels)

/**
 * Returns information on all DMA channels in the system.
 *
 * This function writes to the array specified in the pointer given to the
 * struct structures representing all data about a given DMA channel (device id,
 * direction, etc.). The array must be able to hold at least the number of
 * elements returned by AXIDMA_GET_NUM_DMA_CHANNELS.
 *
 * The important piece of information returned in the id for the channels.
 * This, along with the direction and type, uniquely identifies a DMA channel
 * in the system, and this is how you refer to a channel in later calls.
 *
 * Inputs:
 *  - channels - A pointer to a region of memory that can hold at least
 *               num_channels * sizeof(struct axidma_chan) bytes.
 *
 * Outputs:
 *  - channels - An array of structures of the following format:
 *  - An array of structures with the following fields:
 *       - dir - The direction of the channel (either read or write).
 *       - type - The type of the channel (either normal DMA or video DMA).
 *       - channel_id - The integer id for the channel.
 *       - chan - This field has no meaning and can be safely ignored.
 **/
#define AXIDMA_GET_DMA_CHANNELS         _IOR(AXIDMA_IOCTL_MAGIC, 1, \
                                             struct axidma_channel_info)

/**
 * Register the given signal to be sent when DMA transactions complete.
 *
 * This function sets up an asynchronous signal to be delivered to the invoking
 * process any DMA subsystem completes a transaction. If the user dispatches
 * an asynchronous transaction, and wants to know when it completes, they must
 * register a signal to be delivered.
 *
 * The signal must be one of the POSIX real time signals. So, it must be
 * between the signals SIGRTMIN and SIGRTMAX. The kernel will deliver the
 * channel id back to the userspace signal handler.
 *
 * This can be used to have a user callback function, effectively emulating an
 * interrupt in userspace. The user must register their signal handler for
 * the specified signal for this to happen.
 *
 * Inputs:
 *  - signal - The signal to send upon transaction completion.
 **/
#define AXIDMA_SET_DMA_SIGNAL           _IO(AXIDMA_IOCTL_MAGIC, 2)

/**
 * Registers the external DMA buffer with the driver, making it available to be
 * used in DMA transfers.
 *
 * Sometimes, it may be useful to use a DMA buffer that was allocated by another
 * driver. The best example of this is if you want to interact with a
 * frame buffer allocated by a DRM driver.
 *
 * However, the driver cannot simply access this DMA buffer as is. The user must
 * register the buffer with the driver, so that it can get the information from
 * the driver that originally allocated it.
 *
 * This uses the kernel's DMA buffer sharing API. Thus, the user must first tell
 * the other driver to export the DMA buffer for sharing, typically done through
 * an IOCTL. This will return a file descriptor, which the user must pass into
 * this function, along with the virtual address in userspace.
 *
 * Inputs:
 *  - fd - File descriptor corresponding to the DMA buffer share.
 *  - size - The size of the DMA buffer in bytes.
 *  - user_addr - The user virtual address of the buffer.
 **/
#define AXIDMA_REGISTER_BUFFER          _IOR(AXIDMA_IOCTL_MAGIC, 3, \
                                             struct axidma_register_buffer)

/**
 * Receives the data from the logic fabric into the processing system.
 *
 * This function receives data from a device on the PL fabric through
 * AXI DMA into memory. The device id should be an id that is returned by the
 * get dma channels ioctl. The user can specify if the call should wait for the
 * transfer to complete, or if it should return immediately.
 *
 * The specified buffer must be within an address range that was allocated by a
 * call to mmap with the AXI DMA device. Also, the buffer must be able to hold
 * at least `buf_len` bytes.
 *
 * Inputs:
 *  - wait - Indicates if the call should be blocking or non-blocking
 *  - channel_id - The id for the channel you want receive data over.
 *  - buf - The address of the buffer you want to receive the data in.
 *  - buf_len - The number of bytes to receive.
 **/
#define AXIDMA_DMA_READ                 _IOR(AXIDMA_IOCTL_MAGIC, 4, \
                                             struct axidma_transaction)

/**
 * Sends the given data from the processing system to the logic fabric.
 *
 * This function sends data from memory to a device on the PL fabric through
 * AXI DMA. The device id should be an id that is returned by the get dma
 * channels ioctl. The user can specify if the call should wait for the transfer
 * to complete, or if it should return immediately.
 *
 * The specified buffer must be within an address range that was allocated by a
 * call to mmap with the AXI DMA device. Also, the buffer must be able to hold
 * at least `buf_len` bytes.
 *
 * Inputs:
 *  - wait - Indicates if the call should be blocking or non-blocking
 *  - channel_id - The id for the channel you want to send data over.
 *  - buf - The address of the data you want to send.
 *  - buf_len - The number of bytes to send.
 **/
#define AXIDMA_DMA_WRITE                _IOR(AXIDMA_IOCTL_MAGIC, 5, \
                                             struct axidma_transaction)

/**
 * Performs a two-way transfer between the logic fabric and processing system.
 *
 * This function both sends data to the PL and receives data from the PL fabric.
 * It is intended for DMA transfers that are tightly coupled together
 * (e.g. converting an image to grayscale on the PL fabric). The device id's for
 * both channels should be ones that are returned by the get dma ioctl. The user
 * can specify if the call should block. If it blocks, it will wait until the
 * receive transaction completes.
 *
 * The specified buffers must be within an address range that was allocated by a
 * call to mmap with the AXI DMA device. Also, each buffer must be able to hold
 * at least the number of bytes that are being transfered.
 *
 * Inputs:
 *  - wait - Indicates if the call should be blocking or non-blocking
 *  - tx_channel_id - The id for the channel you want transmit data on.
 *  - tx_buf - The address of the data you want to send.
 *  - tx_buf_len - The number of bytes you want to send.
 *  - rx_buf - The address of the buffer you want to receive data in.
 *  - rx_buf_len - The number of bytes you want to receive.
 **/
#define AXIDMA_DMA_READWRITE            _IOR(AXIDMA_IOCTL_MAGIC, 6, \
                                             struct axidma_inout_transaction)

/**
 * Performs frame-buffer based transfers from a camera on the fabric.
 *
 * This function performs a video transfer from the logic fabric. It receives
 * the given buffers from logic fabric (intended for a camera pipeline). When it
 * reaches the end of the buffers, it loops back and receives the data again in
 * the first buffer. This is used for frame-buffer based cameras.
 *
 * All of the frame buffers must be within an address range that was allocated
 * by a call to mmap with the AXI DMA device. Also, each buffer must
 * be able to hold a frame of (width * height * depth) bytes. The input array of
 * buffers must be a memory location that holds `num_frame_buffers` addresses.
 *
 * This call is always non-blocking as the VDMA engine will run forever. In
 * order to end the transaction, you must make a call to the stop dma channel
 * ioctl.
 *
 * Inputs:
 *  - channel_id - The id for the channel you want to send data over.
 *  - num_frame_buffers - The number of frame buffers you're using.
 *  - frame_buffers - An array of the frame buffer addresses.
 *  - width - The width of the frame (image) in pixels.
 *  - height - The height of the frame in lines.
 *  - depth - The size of each pixel in the frame in bytes.
 **/
#define AXIDMA_DMA_VIDEO_READ           _IOR(AXIDMA_IOCTL_MAGIC, 7, \
                                             struct axidma_video_transaction)

/**
 * Performs frame-buffer based transfers to a display on the logic fabric.
 *
 * This function performs a video transfer to the logic fabric. It sends
 * the given buffers to logic fabric (intended for a display pipeline). When it
 * reaches the end of the buffers, it loops back and re-sends the first buffer.
 * This is used for frame-buffer based graphics.
 *
 * All of the frame buffers must be within an address range that was allocated
 * by a call to mmap with the AXI DMA device. Also, each buffer must
 * be able to hold a frame of (width * height * depth) bytes. The input array of
 * buffers must be a memory location that holds `num_frame_buffers` addresses.
 *
 * This call is always non-blocking as the VDMA engine will run forever. In
 * order to end the transaction, you must make a call to the stop dma channel
 * ioctl.
 *
 * Inputs:
 *  - channel_id - The id for the channel you want to send data over.
 *  - num_frame_buffers - The number of frame buffers you're using.
 *  - frame_buffers - An array of the frame buffer addresses.
 *  - width - The width of the frame (image) in pixels.
 *  - height - The height of the frame in lines.
 *  - depth - The size of each pixel in the frame in bytes.
 **/
#define AXIDMA_DMA_VIDEO_WRITE          _IOR(AXIDMA_IOCTL_MAGIC, 8, \
                                             struct axidma_video_transaction)

/**
 * Stops all transactions on the given DMA channel.
 *
 * This function flushes all in-progress transactions, and discards all pending
 * transactions on the given DMA channel. The specified id should be one that
 * was returned by the get dma channels ioctl.
 *
 * Inputs:
 *  - dir - The direction of the channel (either read or write).
 *  - type - The type of the channel (either normal DMA or video DMA).
 *  - channel_id - The integer id for the channel.
 *  - chan - This field is unused an can be safely left uninitialized.
 */
#define AXIDMA_STOP_DMA_CHANNEL         _IOR(AXIDMA_IOCTL_MAGIC, 9, \
                                             struct axidma_chan)

/**
 * Unregisters and external DMA buffer previously registered through an
 * AXIDMA_REGISTER_BUFFER IOCTL
 *
 * All external buffers that are registered by the user must be freed in order
 * to ensure that all kernel data structures are properly cleaned up. This
 * removes the external DMA buffer from the driver, so it can no longer be
 * used in DMA transfers after this call.
 *
 * Inputs:
 *  - user_addr - The user virtual address of the external DMA buffer.
 **/
#define AXIDMA_UNREGISTER_BUFFER        _IO(AXIDMA_IOCTL_MAGIC, 10)

4分析示例

4.1示例

/*----------------------------------------------------------------------------
 * 在Axi_transfer中存在如下用法:2.调用DMA驱动中的ioctl完成数据搬移
 *----------------------------------------------------------------------------*/
    // Perform the read-write transfer
    rc = ioctl(dev->fd, AXIDMA_DMA_READWRITE, &trans);
    if (rc < 0) {
        perror("Failed to perform the AXI DMA read-write transfer");
    }

4.2命令参数说明

参数说明:
/**
 * Performs a two-way transfer between the logic fabric and processing system.
 *
 * This function both sends data to the PL and receives data from the PL fabric.
 * It is intended for DMA transfers that are tightly coupled together
 * (e.g. converting an image to grayscale on the PL fabric). The device id's for
 * both channels should be ones that are returned by the get dma ioctl. The user
 * can specify if the call should block. If it blocks, it will wait until the
 * receive transaction completes.
 *
 * The specified buffers must be within an address range that was allocated by a
 * call to mmap with the AXI DMA device. Also, each buffer must be able to hold
 * at least the number of bytes that are being transfered.
 *
 * Inputs:
 *  - wait - Indicates if the call should be blocking or non-blocking
 *  - tx_channel_id - The id for the channel you want transmit data on.
 *  - tx_buf - The address of the data you want to send.
 *  - tx_buf_len - The number of bytes you want to send.
 *  - rx_buf - The address of the buffer you want to receive data in.
 *  - rx_buf_len - The number of bytes you want to receive.
 **/
#define AXIDMA_DMA_READWRITE            _IOR(AXIDMA_IOCTL_MAGIC, 6, \
                                             struct axidma_inout_transaction)

4.3结构体说明

struct axidma_inout_transaction {
    bool wait;                      // Indicates if the call is blocking
    int tx_channel_id;              // The id of the transmit DMA channel
    void *tx_buf;                   // The buffer containing the data to send
    size_t tx_buf_len;              // The length of the transmit buffer
    int rx_channel_id;              // The id of the receive DMA channel
    void *rx_buf;                   // The buffer to place the data in
    size_t rx_buf_len;              // The length of the receive buffer
}inout_trans;

4.4命令解析说明

实现方式:     
		case AXIDMA_DMA_READWRITE:
            if (copy_from_user(&inout_trans, arg_ptr,
                               sizeof(inout_trans)) != 0) {
                axidma_err("Unable to copy transfer info from userspace for "
                           "AXIDMA_DMA_READWRITE.\n");
                return -EFAULT;
            }
            rc = axidma_rw_transfer(dev, &inout_trans);
            break;

4.5axidma_rw_transfer()函数分析

/* Transfers data from the given source buffer out to the AXI DMA device, and
 * places the data received into the receive buffer. */
int axidma_rw_transfer(struct axidma_device *dev,
                       struct axidma_inout_transaction *trans)
{
/*----------------------------------------------------------------------------
 * 1.初始化相关变量
 *----------------------------------------------------------------------------*/
    int rc;
    struct axidma_chan *tx_chan, *rx_chan;
    struct scatterlist tx_sg_list, rx_sg_list;
    struct axidma_transfer tx_tfr, rx_tfr;

    // Get the transmit and receive channels with the given ids.
    tx_chan = axidma_get_chan(dev, trans->tx_channel_id);
    if (tx_chan == NULL || tx_chan->dir != AXIDMA_WRITE) {
        axidma_err("Invalid device id %d for DMA transmit channel.\n",
                   trans->tx_channel_id);
        return -ENODEV;
    }

    rx_chan = axidma_get_chan(dev, trans->rx_channel_id);
    if (rx_chan == NULL || rx_chan->dir != AXIDMA_READ) {
        axidma_err("Invalid device id %d for DMA receive channel.\n",
                   trans->rx_channel_id);
        return -ENODEV;
    }

    // Setup the scatter-gather list for the transfers (only one entry)
    sg_init_table(&tx_sg_list, 1);
    rc = axidma_init_sg_entry(dev, &tx_sg_list, 0, trans->tx_buf,
                              trans->tx_buf_len);
    if (rc < 0) {
        return rc;
    }
    sg_init_table(&rx_sg_list, 1);
    rc = axidma_init_sg_entry(dev, &rx_sg_list, 0, trans->rx_buf,
                              trans->rx_buf_len);
    if (rc < 0) {
        return rc;
    }

    // Setup receive and trasmit transfer structures for DMA
    tx_tfr.sg_list = &tx_sg_list,
    tx_tfr.sg_len = 1,
    tx_tfr.dir = tx_chan->dir,
    tx_tfr.type = tx_chan->type,
    tx_tfr.wait = false,
    tx_tfr.channel_id = trans->tx_channel_id,
    tx_tfr.notify_signal = dev->notify_signal,
    tx_tfr.process = get_current(),
    tx_tfr.cb_data = &dev->cb_data[trans->tx_channel_id];
    // FIXME: FIXME: FIXME: Temporary
    tx_tfr.vdma_tfr.height = 1080;
    tx_tfr.vdma_tfr.width = 1920;
    tx_tfr.vdma_tfr.depth = 4;

    rx_tfr.sg_list = &rx_sg_list,
    rx_tfr.sg_len = 1,
    rx_tfr.dir = rx_chan->dir,
    rx_tfr.type = rx_chan->type,
    rx_tfr.wait = trans->wait,
    rx_tfr.channel_id = trans->rx_channel_id,
    rx_tfr.notify_signal = dev->notify_signal,
    rx_tfr.process = get_current(),
    rx_tfr.cb_data = &dev->cb_data[trans->rx_channel_id];
    rx_tfr.vdma_tfr.height = 1080;
    rx_tfr.vdma_tfr.width = 1920;
    rx_tfr.vdma_tfr.depth = 4;
/*----------------------------------------------------------------------------
 * 2.待分析.......
 *----------------------------------------------------------------------------*/
    // Prep both the receive and transmit transfers
    rc = axidma_prep_transfer(tx_chan, &tx_tfr);
    if (rc < 0) {
        return rc;
    }
    rc = axidma_prep_transfer(rx_chan, &rx_tfr);
    if (rc < 0) {
        return rc;
    }

    // Submit both transfers to the DMA engine, and wait on the receive transfer
    rc = axidma_start_transfer(tx_chan, &tx_tfr);
    if (rc < 0) {
        return rc;
    }
    rc = axidma_start_transfer(rx_chan, &rx_tfr);
    if (rc < 0) {
        return rc;
    }

    return 0;
}

你可能感兴趣的:(linux)