Kernel USB驱动框架

一,USB基本概念

    USB协议是非常复杂的,好在Linux内核为我们做了太多的工作,使得USB驱动的开发相对容易很多。

    USB基于树形的拓扑结构设计,支持多种传输模式和传输速率。硬件上,USB分为USB控制器和USB设备,USB控制器负责同USB设备通信和协议协商,以便USB控制器能很好的控制和管理挂接在其下的USB设备。USB控制器逻辑复杂,不在本篇分析,本篇主要分析USB设备驱动架构。

    1,USB控制器

    USB控制器通常是USB模块的核心,通过根集线器连接各外接USB设备。USB控制器类型分为EHCI, UHCI和OHCI等,支持各种不同的usb总线协议和总线速率标准。各种USB控制器类型在kernel中都有实现,usb控制器驱动在加载过程中,会枚举/轮询下挂在该usb控制器下的所有usb设备,并提供操作下挂usb设备的接口。

    2,USB设备

    USB设备分为hub和功能部件。一个hub可以下挂多个设备或者hub,从而形成一个树状的拓扑结构。

    3,USB传输模式

    USB控制器支持4种传输模式:批量传输模式(bulk),控制传输模式(control),中断传输模式(interrupt),等时传输模式(iso),每种模式的适用场景不尽相同。

    批量传输模式:USB控制器使用批量传输模式用于数据量大,非实时数据传输。常用于U盘,打印机等块设备数据传输。

    控制传输模式:USB控制器使用控制传输模式同设备交换设备描述符、ID、Product等信息,并发送各种控制命令。这是USB控制器必须支持的传输模式,每个usb控制器都有一个0号端点,工作在控制传输模式,用来传输控制信息,传输数据量小。

    中断传输模式:USB控制器定时查看是否有中断数据传输请求,传输数据量小。常用于鼠标,键盘,游戏控制器等。

    等时传输模式:USB控制器使用等时传输模式来定时传输数据量大,实时性高的数据。常用于音频,mic等设备。

二,USB设备驱动框架

    以Linux内核的usb skel驱动为例,分析usb设备驱动框架。首先看一下skel驱动的框图:

    USB驱动框架遵循Linux设备驱动模型框架(usb_bus, usb_device 和 usb_driver),skel_driver使用module_usb_driver(skel_driver);注册进usb总线,调用usb总线的match方法usb_device_match(),如果匹配完成,调用usb_probe_interface(),最终调用到skel_driver的probe方法skel_probe()。这个过程太常见,就不再分析。

    我们要分析的是skel_driver的驱动框架,从而掌握通用usb驱动的编写。我们知道,usb控制器驱动提供了设备的枚举和通信的通道,要实现一个基于USB的设备驱动,我们需要完成两个方面的事情:

    (1)如何通过usb找到特定的数据传输端点,以便跟实际硬件设备通信?

    (2)如何向上层提供接口,以便用户可以访问该usb设备?

    解决上述两点,我们就完成了一个usb设备驱动的功能。

    1,USB子系统驱动初始化:

        我们先从用户接口说起。USB设备在初始化的过程中,通过在/dev下创建字符设备节点,给用户提供操作usb设备的接口:

int usb_major_init(void)
{
    int error;

    error = register_chrdev(USB_MAJOR, "usb", &usb_fops);    // 注册/dev/usb*设备驱动
    if (error)
        printk(KERN_ERR "Unable to get major %d for usb devices\n",
               USB_MAJOR);

    return error;
}

#define MAX_USB_MINORS    256    // 最大支持256个usb子设备
static const struct file_operations *usb_minors[MAX_USB_MINORS];    // 

static const struct file_operations usb_fops = {
    .owner =    THIS_MODULE,
    .open =        usb_open,
    .llseek =    noop_llseek,
};

static int usb_open(struct inode *inode, struct file *file)
{
    int err = -ENODEV;
    const struct file_operations *new_fops;

    down_read(&minor_rwsem);
    new_fops = fops_get(usb_minors[iminor(inode)]);    // 根据从设备号,获取从设备的文件操作

    if (!new_fops)
        goto done;

    replace_fops(file, new_fops);    // 替换文件操作
    /* Curiouser and curiouser... NULL ->open() as "no device" ? */
    if (file->f_op->open)
        err = file->f_op->open(inode, file);    // 调用实际从设备的open()方法

 done:
    up_read(&minor_rwsem);
    return err;
}

    从上面可以看出,usb skel 驱动预先定义了 usb_minors[] 数组用于存储usb从设备的文件操作符。针对usb驱动,注册文件操作是 usb_fops。当用户通过/dev/usb*设备节点打开usb字符设备时,通过def_chr_fops调用到usb_fops->open()方法,即usb_open()方法,该方法从usb_minors[]数组中获取相应的文件操作,最终完成特定设备的打开操作。

    那么,usb_minors[]是何时初始化的?是usb skel驱动注册时初始化的。

    2,USB skel驱动注册

static int skel_probe(struct usb_interface *interface,
              const struct usb_device_id *id)
{
    struct usb_skel *dev;
    struct usb_host_interface *iface_desc;
    struct usb_endpoint_descriptor *endpoint;
    size_t buffer_size;
    int i;
    int retval = -ENOMEM;

    /* allocate memory for our device state and initialize it */
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);    // 分配 usb_skel 实例
    if (!dev) {
        dev_err(&interface->dev, "Out of memory\n");
        goto error;
    }
    kref_init(&dev->kref);
    sema_init(&dev->limit_sem, WRITES_IN_FLIGHT);
    mutex_init(&dev->io_mutex);
    spin_lock_init(&dev->err_lock);
    init_usb_anchor(&dev->submitted);
    init_waitqueue_head(&dev->bulk_in_wait);

    dev->udev = usb_get_dev(interface_to_usbdev(interface));    // 指向 usb_device
    dev->interface = interface;    // 指向 usb_interface

    /* set up the endpoint information */
    /* use only the first bulk-in and bulk-out endpoints */
    iface_desc = interface->cur_altsetting;
    for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {    // 轮询当前altsetting下的所有端点

        endpoint = &iface_desc->endpoint[i].desc;

        if (!dev->bulk_in_endpointAddr &&
            usb_endpoint_is_bulk_in(endpoint)) {    // 找到bulk in 端点

            /* we found a bulk in endpoint */
            buffer_size = usb_endpoint_maxp(endpoint);    // 端点支持的最大分组长度
            dev->bulk_in_size = buffer_size;    // 缓存大小
            dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;    // 端点号
            dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);    // 分配缓存
            if (!dev->bulk_in_buffer) {
                dev_err(&interface->dev,
                    "Could not allocate bulk_in_buffer\n");
                goto error;
            }
            dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL);    // 分配urb缓存
            if (!dev->bulk_in_urb) {
                dev_err(&interface->dev,
                    "Could not allocate bulk_in_urb\n");
                goto error;
            }
        }

        if (!dev->bulk_out_endpointAddr &&
            usb_endpoint_is_bulk_out(endpoint)) {
            /* we found a bulk out endpoint */
            dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
        }    // 找到 bulk out 端点

    }
    if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
        dev_err(&interface->dev,
            "Could not find both bulk-in and bulk-out endpoints\n");
        goto error;
    }

    /* save our data pointer in this interface device */
    usb_set_intfdata(interface, dev);    //interface->dev.driver_data指向dev

    /* we can register the device now, as it is ready */
    retval = usb_register_dev(interface, &skel_class);    // 注册该usb接口设备,关联相关的 fops
    if (retval) {
        /* something prevented us from registering this driver */
        dev_err(&interface->dev,
            "Not able to get a minor for this device.\n");
        usb_set_intfdata(interface, NULL);
        goto error;
    }

    /* let the user know what node this device is now attached to */
    dev_info(&interface->dev,
         "USB Skeleton device now attached to USBSkel-%d",
         interface->minor);
    return 0;

error:
    if (dev)
        /* this frees allocated memory */
        kref_put(&dev->kref, skel_delete);
    return retval;
}

int usb_register_dev(struct usb_interface *intf,
             struct usb_class_driver *class_driver)
{
    int retval;
    int minor_base = class_driver->minor_base; 
    int minor;
    char name[20];
    char *temp;

#ifdef CONFIG_USB_DYNAMIC_MINORS
    /*
     * We don't care what the device tries to start at, we want to start
     * at zero to pack the devices into the smallest available space with
     * no holes in the minor range.
     */
    minor_base = 0;
#endif

    if (class_driver->fops == NULL)
        return -EINVAL;
    if (intf->minor >= 0)
        return -EADDRINUSE;

    retval = init_usb_class();
    if (retval)
        return retval;

    dev_dbg(&intf->dev, "looking for a minor, starting at %d\n", minor_base);

    down_write(&minor_rwsem);
    for (minor = minor_base; minor < MAX_USB_MINORS; ++minor) {
        if (usb_minors[minor])
            continue;

        usb_minors[minor] = class_driver->fops;
        intf->minor = minor;
        break;
    }    // 从 usb_minors[] 数组寻找空位,分配一个从设备号并注册相应的文件操作符

    up_write(&minor_rwsem);
    if (intf->minor < 0)
        return -EXFULL;

    /* create a usb class device for this usb interface */
    snprintf(name, sizeof(name), class_driver->name, minor - minor_base);
    temp = strrchr(name, '/');
    if (temp && (temp[1] != '\0'))
        ++temp;
    else
        temp = name;
    intf->usb_dev = device_create(usb_class->class, &intf->dev,
                      MKDEV(USB_MAJOR, minor), class_driver,
                      "%s", temp);    // 在设备模型中注册该设备

    if (IS_ERR(intf->usb_dev)) {
        down_write(&minor_rwsem);
        usb_minors[minor] = NULL;
        intf->minor = -1;
        up_write(&minor_rwsem);
        retval = PTR_ERR(intf->usb_dev);
    }
    return retval;
}

    从上面的注册流程可以看出,usb skel首先从usb interface中找到相应的端点,并分配数据接收缓存,然后从usb_minors[]数组中分配一个从设备号,并注册相应的设备文件操作。最后将该设备注册进设备模型。

    这里需要注意一点,设备注册到设备模型时(device_create),会通过devtmpfs_create_node(dev) 在 /dev下注册相应的设备节点,这样用户就可以通过操作 /dev/usb* 设备节点访问usb设备了。

    到此为止,usb skel 驱动框架成型:usb_skel驱动注册时,匹配usb interface,探测interface的过程中,找到bulk in/ bulk out 端点信息,分配数据交换缓存,注册fops到usb_minors[]数组,设备注册进Linux设备模型,并创建 /dev/usb*设备节点。当用户通过设备节点 /dev/usb*打开usb设备时,查找usb_minors[]数组,找到相应的fops,替换到file结构中,最终通过注册的fops操作usb设备。

    3,USB skel驱动操作分析

    USB skel驱动注册的操作结构如下:

static struct usb_class_driver skel_class = {
    .name =        "skel%d",
    .fops =        &skel_fops,
    .minor_base =    USB_SKEL_MINOR_BASE,
};

static const struct file_operations skel_fops = {
    .owner =    THIS_MODULE,
    .read =        skel_read,
    .write =    skel_write,
    .open =        skel_open,
    .release =    skel_release,
    .flush =    skel_flush,
    .llseek =    noop_llseek,
};

    (1)skel_open():

static int skel_open(struct inode *inode, struct file *file)
{
    struct usb_skel *dev;
    struct usb_interface *interface;
    int subminor;
    int retval = 0;

    subminor = iminor(inode);

    interface = usb_find_interface(&skel_driver, subminor);    // 根据minor查找interface
    if (!interface) {
        pr_err("%s - error, can't find device for minor %d\n",
            __func__, subminor);
        retval = -ENODEV;
        goto exit;
    }

    dev = usb_get_intfdata(interface);    // 获取 usb_skel
    if (!dev) {
        retval = -ENODEV;
        goto exit;
    }

    retval = usb_autopm_get_interface(interface);
    if (retval)
        goto exit;

    /* increment our usage count for the device */
    kref_get(&dev->kref);

    /* save our object in the file's private structure */
    file->private_data = dev;    // file->private_data初始化为 usb_skel

exit:
    return retval;
}

    (2)skel_read()

static ssize_t skel_read(struct file *file, char *buffer, size_t count,
             loff_t *ppos)
{
    struct usb_skel *dev;
    int rv;
    bool ongoing_io;

    dev = file->private_data;    // 获取 usb_skel

    /* if we cannot read at all, return EOF */
    if (!dev->bulk_in_urb || !count)
        return 0;

    /* no concurrent readers */
    rv = mutex_lock_interruptible(&dev->io_mutex);
    if (rv < 0)
        return rv;

    if (!dev->interface) {        /* disconnect() was called */
        rv = -ENODEV;
        goto exit;
    }

    /* if IO is under way, we must not touch things */
retry:
    spin_lock_irq(&dev->err_lock);
    ongoing_io = dev->ongoing_read;
    spin_unlock_irq(&dev->err_lock);

    if (ongoing_io) {    // 设备busy
        /* nonblocking IO shall not wait */
        if (file->f_flags & O_NONBLOCK) {
            rv = -EAGAIN;
            goto exit;
        }    // 非阻塞,直接退出

        /*
         * IO may take forever
         * hence wait in an interruptible state
         */
        rv = wait_event_interruptible(dev->bulk_in_wait, (!dev->ongoing_read));    // 等待设备读空闲
        if (rv < 0)
            goto exit;
    }

    /* errors must be reported */
    rv = dev->errors;
    if (rv < 0) {
        /* any error is reported once */
        dev->errors = 0;
        /* to preserve notifications about reset */
        rv = (rv == -EPIPE) ? rv : -EIO;
        /* report it */
        goto exit;
    }

    /*
     * if the buffer is filled we may satisfy the read
     * else we need to start IO
     */

    if (dev->bulk_in_filled) {    // 设备数据已经读取进驱动缓存中
        /* we had read data */
        size_t available = dev->bulk_in_filled - dev->bulk_in_copied;
        size_t chunk = min(available, count);    // 计算驱动中缓存的数据大小

        if (!available) {
            /*
             * all data has been used
             * actual IO needs to be done
             */
            rv = skel_do_read_io(dev, count);    // 缓存中没有可读的数据,调度usb控制器从设备读取数据到缓存
            if (rv < 0)
                goto exit;
            else
                goto retry;
        }

        /*
         * data is available
         * chunk tells us how much shall be copied
         */

        if (copy_to_user(buffer,
                 dev->bulk_in_buffer + dev->bulk_in_copied,
                 chunk))    // 缓存数据返回到用户态

            rv = -EFAULT;
        else
            rv = chunk;

        dev->bulk_in_copied += chunk;    // 更新缓存

        /*
         * if we are asked for more than we have,
         * we start IO but don't wait
         */
        if (available < count)
            skel_do_read_io(dev, count - chunk);    // 数据没读完,调度usb控制器读取实际数据

    } else {
        /* no data in the buffer */
        rv = skel_do_read_io(dev, count);
        if (rv < 0)
            goto exit;
        else if (!(file->f_flags & O_NONBLOCK))
            goto retry;
        rv = -EAGAIN;
    }    // 缓存中没有数据,调度usb控制器读取实际数据

exit:
    mutex_unlock(&dev->io_mutex);
    return rv;
}

static int skel_do_read_io(struct usb_skel *dev, size_t count)
{
    int rv;

    /* prepare a read */
    usb_fill_bulk_urb(dev->bulk_in_urb,
            dev->udev,
            usb_rcvbulkpipe(dev->udev,
                dev->bulk_in_endpointAddr),
            dev->bulk_in_buffer,
            min(dev->bulk_in_size, count),
            skel_read_bulk_callback,
            dev);    // 填充 urb

    /* tell everybody to leave the URB alone */
    spin_lock_irq(&dev->err_lock);
    dev->ongoing_read = 1;
    spin_unlock_irq(&dev->err_lock);

    /* submit bulk in urb, which means no data to deliver */
    dev->bulk_in_filled = 0;
    dev->bulk_in_copied = 0;

    /* do it */
    rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL);    // 提交urb读请求,调用usb控制器异步读取数据
    if (rv < 0) {
        dev_err(&dev->interface->dev,
            "%s - failed submitting read urb, error %d\n",
            __func__, rv);
        rv = (rv == -ENOMEM) ? rv : -EIO;
        spin_lock_irq(&dev->err_lock);
        dev->ongoing_read = 0;
        spin_unlock_irq(&dev->err_lock);
    }

    return rv;
}
 

    (3)skel_write()

static ssize_t skel_write(struct file *file, const char *user_buffer,
              size_t count, loff_t *ppos)
{
    struct usb_skel *dev;
    int retval = 0;
    struct urb *urb = NULL;
    char *buf = NULL;
    size_t writesize = min(count, (size_t)MAX_TRANSFER);    // 计算要传输的数据大小

    dev = file->private_data;    // 获取usb_skel

    /* verify that we actually have some data to write */
    if (count == 0)
        goto exit;

    /*
     * limit the number of URBs in flight to stop a user from using up all
     * RAM
     */
    if (!(file->f_flags & O_NONBLOCK)) {
        if (down_interruptible(&dev->limit_sem)) {
            retval = -ERESTARTSYS;
            goto exit;
        }
    } else {
        if (down_trylock(&dev->limit_sem)) {
            retval = -EAGAIN;
            goto exit;
        }
    }

    spin_lock_irq(&dev->err_lock);
    retval = dev->errors;
    if (retval < 0) {
        /* any error is reported once */
        dev->errors = 0;
        /* to preserve notifications about reset */
        retval = (retval == -EPIPE) ? retval : -EIO;
    }
    spin_unlock_irq(&dev->err_lock);
    if (retval < 0)
        goto error;

    /* create a urb, and a buffer for it, and copy the data to the urb */
    urb = usb_alloc_urb(0, GFP_KERNEL);    // 分配urb
    if (!urb) {
        retval = -ENOMEM;
        goto error;
    }

    buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL,
                 &urb->transfer_dma);    // 分配一致性DMS缓存

    if (!buf) {
        retval = -ENOMEM;
        goto error;
    }

    if (copy_from_user(buf, user_buffer, writesize)) {
        retval = -EFAULT;
        goto error;
    }    // 用户态数据拷贝到dms缓存中

    /* this lock makes sure we don't submit URBs to gone devices */
    mutex_lock(&dev->io_mutex);
    if (!dev->interface) {        /* disconnect() was called */
        mutex_unlock(&dev->io_mutex);
        retval = -ENODEV;
        goto error;
    }

    /* initialize the urb properly */
    usb_fill_bulk_urb(urb, dev->udev,
              usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
              buf, writesize, skel_write_bulk_callback, dev);    // 填充 bulk out urb

    urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
    usb_anchor_urb(urb, &dev->submitted); 

    /* send the data out the bulk port */
    retval = usb_submit_urb(urb, GFP_KERNEL);    // 提交urb请求, 调用usb控制器异步写数据
    mutex_unlock(&dev->io_mutex);
    if (retval) {
        dev_err(&dev->interface->dev,
            "%s - failed submitting write urb, error %d\n",
            __func__, retval);
        goto error_unanchor;
    }

    /*
     * release our reference to this urb, the USB core will eventually free
     * it entirely
     */
    usb_free_urb(urb);


    return writesize;

error_unanchor:
    usb_unanchor_urb(urb);
error:
    if (urb) {
        usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma);
        usb_free_urb(urb);
    }
    up(&dev->limit_sem);

exit:
    return retval;
}

转载于:https://my.oschina.net/yepanl/blog/3068419

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值