UIO mechanism of Linux device driver

Source: Internet
Author: User
Tags goto



There are two main tasks for a device driver:
1. Accessing the device's memory
2. Interruption of processing equipment



For the first task. The UIO core implements the Mmap () capable of handling physical memory (physical memory), logical (logical
Virtual memory. The UiO drive is written without the need to consider these tedious details.






For the second task, the answer to the device interrupt must be in kernel space. So there's a small piece of code in the kernel space
Used to answer interrupts and prohibit interrupts, but the rest of the work is left to the user for space processing.



Assuming that the user space waits for a device to break, it simply needs to be plugged in to the read () operation on the/dev/uiox.
The read () operation returns immediately when the device generates an interrupt.



UIO also implements the poll () system call. You can use
Select () to wait for the interrupt to occur. Select () has a timeout parameter that can be used to allow for a limited time to wait for interrupts.



The control of the device can also be completed by reading and writing the various files under the/sys/class/uio. Your UiO device will appear under this folder today.




If your UiO device is uio0 then the mapped device memory file is now/SYS/CLASS/UIO/UIO0/MAPS/MAPX. Reading and writing to this file is
Read and write to device memory.




For example, the following diagram describes the kernel part of the UiO drive. The User space section. The relationship between the UiO framework and the kernel intrinsic functions.






II: UiO Driver Registration



First look at a simple UiO driver code, code from the Internet, non-original, designed to learn



Kernel part:


/ *
* This is simple demon of uio driver.
* Version 1
*Compile:
*    Save this file name it simple.c
*    #echo "obj -m := simple.o" > Makefile
*    #make -Wall -C /lib/modules/‘uname -r‘/build M=‘pwd‘ modules
*Load the module:
*    #modprobe uio
*    #insmod simple.ko
* /
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/uio_driver.h>
#include <linux/slab.h>
/*struct uio_info {
Struct UIO device * UIO device; / / initialize in UIO register device
Const char * name; / / must be initialized before calling \
Const char * version; / / must be initialized before calling "UIO" register "device
struct uio_mem      mem[MAX_UIO_MAPS];
struct uio_port     port[MAX_UIO_PORT_REGIONS];
Long IRQ; / / the interrupt number assigned to the UIO device. It must be initialized before calling the UIO register device
Unsigned long irq_flags; / / must be initialized before calling \
void            *priv; //
Irqreturn_t (*handler) (int IRQ, struct uio_info *dev_info); //uio_interrupt is called for interrupt handling.
//Must be initialized before calling UIO register device
Int (* MMAP) (struct UIO ﹣ info * info, struct VM ﹣ area ﹣ struct * VMA); / / called in UIO ﹣ MMAP,
//Perform device open specific operations
Int (* open) (struct uio_info * info, struct inode * inode); / / called in uio_open to perform specific operation of device opening
Int (* release) (struct uio_info * info, struct inode * inode); / / called in uio_device to perform device opening specific operations
Int (* irqcontrol) (struct UIO ﹣ info * info, S32 IRQ ﹣ on); / / called in the UIO ﹣ write method to execute user driven
//Specific operations.
}; * /
struct uio_info kpart_info = {
.name = "kpart",
.version = "0.1",
.irq = UIO_IRQ_NONE,
}
static int drv_kpart_probe(struct device *dev);
static int drv_kpart_remove(struct device *dev);
static struct device_driver uio_dummy_driver = {
.name = "kpart",
.bus = &amp;platform_bus_type,
.probe = drv_kpart_probe,
.remove = drv_kpart_remove,
}
static int drv_kpart_probe(struct device *dev)
{
printk("drv_kpart_probe(%p)\n",dev);
kpart_info.mem[0].addr = (unsigned long)kmalloc(1024,GFP_KERNEL);
if(kpart_info.mem[0].addr == 0)
return -ENOMEM;
kpart_info.mem[0].memtype = UIO_MEM_LOGICAL;
kpart_info.mem[0].size = 1024;
if(uio_register_device(dev,&amp;kpart_info))
return -ENODEV;
Return 0;
}
static int drv_kpart_remove(struct device *dev)
{
uio_unregister_device(&amp;kpart_info);
Return 0;
}
static struct platform_device * uio_dummy_device;
static int __init uio_kpart_init(void)
{
uio_dummy_device = platform_device_register_simple("kpart",-1,NULL,0);
return driver_register(&amp;uio_dummy_driver);
}
static void __exit uio_kpart_exit(void)
{
platform_device_unregister(uio_dummy_device);
driver_unregister(&amp;uio_dummy_driver);
}
module_init(uio_kpart_init);
module_exit(uio_kpart_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("IGB_UIO_TEST");
MODULE_DESCRIPTION("UIO dummy driver");


UiO driver Registration is similar to other drivers, by invoking the Linux provided by the UiO API interface to register, before registering, the main work is to populate the UIO_INFO structure of information, mainly including memory size, type and other information filling. After filling, call the Uio_register_device () function to register the uio_info with the kernel. After registering, in/sys/class/uio/uiox, where x is our registered first few UiO devices, such as Uio0, the map/map0 under that folder will have some of the information we just populated, including addr, name, size, offset, Where addr is the physical address of the device, the size of the address is stored in the user state will be read out, and mmap to the user state process space, so that the user state can directly manipulate the memory space of the device.



User state:


#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>

#define UIO_DEV "/dev/uio0"
#define UIO_ADDR "/sys/class/uio/uio0/maps/map0/addr"
#define UIO_SIZE "/sys/class/uio/uio0/maps/map0/size"

static char uio_addr_buf[16]={0};
static char uio_size_buf[16]={0};

int main(void)
{
    int uio_fd,addr_fd,size_fd;
    int uio_size;
    void *uio_addr, *access_address;
    int n=0;
    uio_fd = open(UIO_DEV,O_RDWR);
    addr_fd = open(UIO_ADDR,O_RDONLY);
    size_fd = open(UIO_SIZE,O_RDONLY);
    if(addr_fd < 0 || size_fd < 0 || uio_fd < 0){
        fprintf(stderr,"mmap:%s\n",strerror(errno));
        exit(-1);
    }

    n=read(addr_fd,uio_addr_buf,sizeof(uio_addr_buf));
    if(n<0){
        fprintf(stderr, "%s\n", strerror(errno));
        exit(-1);
    }
    n=read(size_fd,uio_size_buf,sizeof(uio_size_buf));
    if(n<0){
        fprintf(stderr, "%s\n", strerror(errno));
        exit(-1);
    }
    uio_addr = (void*)strtoul(uio_addr_buf,NULL,0);
    uio_size = (int)strtol(uio_size_buf,NULL,0);

    access_address = mmap(NULL,uio_size,PROT_READ | PROT_WRITE,
                            MAP_SHARED,uio_fd,0);
    if(access_address == (void*)-1){
        fprintf(stderr,"mmap:%s\n",strerror(errno));
        exit(-1);
    }

    printf("The device address %p (lenth %d)\n"
        "can be accessed over\n"
        "logical address %p\n",uio_addr,uio_size,access_address);
/*
    access_address = (void*)(long)mremap(access_address, getpagesize(),uio_size + getpagesize()+ 11111, MAP_SHARED);

    if(access_address == (void*)-1){
        fprintf(stderr,"mremap: %s\n",strerror(errno));
        exit(-1);
    }

    printf(">>>AFTER REMAP:""logical address %p\n",access_address);
*/
    return 0;
}


The code is very simple, that is to say the few files read out, and re-mmap out, and finally print it out. So we can simply see, want to operate the UiO device, only need to re-mmap, and then we can operate the memory of the same as the general RAM, then the implementation of DPDK is similar, but more complex.



DPDK's UiO implementation of the kernel code is mainly in igb_uio.c, tidy up the main code:


static struct pci_driver igbuio_pci_driver = {
    .name = "igb_uio",
    .id_table = NULL,
    .probe = igbuio_pci_probe,
    .remove = igbuio_pci_remove,
};

module_init(igbuio_pci_init_module);

static int __init
igbuio_pci_init_module(void)
{
    int ret;

    ret = igbuio_config_intr_mode(intr_mode);
    if (ret < 0)
        return ret;

    return pci_register_driver(&igbuio_pci_driver);
}



#if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0)
static int __devinit
#else
static int
#endif
igbuio_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
    struct rte_uio_pci_dev *udev;

    udev = kzalloc(sizeof(struct rte_uio_pci_dev), GFP_KERNEL);
    if (!udev)
        return -ENOMEM;

    /*
     * enable device: ask low-level code to enable I/O and
     * memory
     */
    if (pci_enable_device(dev)) {
        printk(KERN_ERR "Cannot enable PCI device\n");
        goto fail_free;
    }

    /*
     * reserve device‘s PCI memory regions for use by this
     * module
     */
    if (pci_request_regions(dev, "igb_uio")) {
        printk(KERN_ERR "Cannot request regions\n");
        goto fail_disable;
    }

    /* enable bus mastering on the device */
    pci_set_master(dev);

    /* remap IO memory */
    if (igbuio_setup_bars(dev, &udev->info))
        goto fail_release_iomem;

    /* set 64-bit DMA mask */
    if (pci_set_dma_mask(dev,  DMA_BIT_MASK(64))) {
        printk(KERN_ERR "Cannot set DMA mask\n");
        goto fail_release_iomem;
    } else if (pci_set_consistent_dma_mask(dev, DMA_BIT_MASK(64))) {
        printk(KERN_ERR "Cannot set consistent DMA mask\n");
        goto fail_release_iomem;
    }

    /* fill uio infos */
    udev->info.name = "Intel IGB UIO";
    udev->info.version = "0.1";
    udev->info.handler = igbuio_pci_irqhandler;
    udev->info.irqcontrol = igbuio_pci_irqcontrol;
#ifdef CONFIG_XEN_DOM0
    /* check if the driver run on Xen Dom0 */
    if (xen_initial_domain())
        udev->info.mmap = igbuio_dom0_pci_mmap;
#endif
    udev->info.priv = udev;
    udev->pdev = dev;
    udev->mode = RTE_INTR_MODE_LEGACY;
    spin_lock_init(&udev->lock);

    /* check if it need to try msix first */
    if (igbuio_intr_mode_preferred == RTE_INTR_MODE_MSIX) {
        int vector;

        for (vector = 0; vector < IGBUIO_NUM_MSI_VECTORS; vector ++)
            udev->msix_entries[vector].entry = vector;

        if (pci_enable_msix(udev->pdev, udev->msix_entries, IGBUIO_NUM_MSI_VECTORS) == 0) {
            udev->mode = RTE_INTR_MODE_MSIX;
        }
        else {
            pci_disable_msix(udev->pdev);
            printk(KERN_INFO "fail to enable pci msix, or not enough msix entries\n");
        }
    }
    switch (udev->mode) {
    case RTE_INTR_MODE_MSIX:
        udev->info.irq_flags = 0;
        udev->info.irq = udev->msix_entries[0].vector;
        break;
    case RTE_INTR_MODE_MSI:
        break;
    case RTE_INTR_MODE_LEGACY:
        udev->info.irq_flags = IRQF_SHARED;
        udev->info.irq = dev->irq;
        break;
    default:
        break;
    }

    pci_set_drvdata(dev, udev);
    igbuio_pci_irqcontrol(&udev->info, 0);

    if (sysfs_create_group(&dev->dev.kobj, &dev_attr_grp))
        goto fail_release_iomem;

    /* register uio driver */
    if (uio_register_device(&dev->dev, &udev->info))
        goto fail_release_iomem;

    printk(KERN_INFO "uio device registered with irq %lx\n", udev->info.irq);

    return 0;

fail_release_iomem:
    sysfs_remove_group(&dev->dev.kobj, &dev_attr_grp);
    igbuio_pci_release_iomem(&udev->info);
    if (udev->mode == RTE_INTR_MODE_MSIX)
        pci_disable_msix(udev->pdev);
    pci_release_regions(dev);
fail_disable:
    pci_disable_device(dev);
fail_free:
    kfree(udev);

    return -ENODEV;
}


After the code has been collated, compared to the above simple UiO driver implementation, DPDK UiO implementation is also the first to initialize a pci_driver structure, in the Igbuio_pci_init_module () function directly call Linux provided PCI registration API,PCI_ Register_driver (&igbuio_pci_driver), then jumps to Igbuio_pci_probe (struct Pci_dev *dev, const struct PCI_DEVICE_ID *id) function, this function is similar to the kernel code in the example above, the RTE_UIO_PCI_DEV structure is DPDK itself encapsulated, as follows:


//In IGB GUI
struct rte_uio_pci_dev {
struct uio_info info;
struct pci_dev *pdev;
spinlock_t lock; /* spinlock for accessing PCI config space or msix data in multi tasks/isr */
enum igbuio_intr_mode mode;
struct msix_entry         msix_entries[IGBUIO_NUM_MSI_VECTORS]; /* pointer to the msix vectors to be allocated later */
} 


As you can see, there is uio_info this structure, from the igbuio_pci_probe (struct Pci_dev *dev, const struct PCI_DEVICE_ID *id) function code can be seen, mainly in the fill Uio_ Info structure information, and also around the physical address and size of the PCI device, and finally call the Linux provided by the UiO Registration interface Uio_register_device (&dev->dev, &udev->info), Complete the UiO registration.



UIO mechanism of Linux device driver


Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.