Arm嵌入式开发之USB主机与设备驱动

2023-10-29

2013-03-05 00:37  25人阅读  评论(0)  收藏  举报

USB驱动层次结构

由上到下:USB设备驱动-USB核心-USB主机控制器驱动-USB控制器硬件

USB核心为USB驱动程序提供了一个用于访问和控制USB硬件的接口,而不必考虑系统当前存在的各种不同类型的USB硬件控制器。

USB设备包括配置(configuration)、接口(interface)和端点(endpoint)USB设备绑定到接口上,而不是整个USB设备。如下图所示:

对应的结构体:usb_device_descriptor, usb_config_descriptor, usb_interface_descriptor, usb_endpoint_descriptor, usb_string_descriptor.

Lsusb命令可以得到U盘相关的描述符。

 

USB主机驱动

主机驱动整体结构

用结构体usb_hcd描述,包括USB主机控制器的家务信息,硬件资源,状态描述,和用于操作主机控制器的hc_driver等。

IHCI主机控制器数据结构体ohci_hcd,可由内联函数实现usb_hcdohci_hcd之间的转换:

Struct ohci_hcd * usb_hcd(struct usb_hcd *hcd);

Struct usb_hcd * ohci_hcd(const struct ohci_hcd *ohci);

初始化OHCI主机控制器

Int ohci_init (struct ohci_hcd *ohci);

开启,停止及复位OHCI控制器

Int ohci_run(struct ohci_hcd *ohci);

void ohci_stop (struct usb_hcd *hcd);

void ohci_usb_reset (struct ohci_hcd *ohci);

 实例s3c2410USB主机驱动

s3c2410内部集成了USB主机控制器,从基址0x49000000开始分别提供了OHCI的寄存器。s3c2410主机控制器驱动hc_driver机构体中大多成员是通用的Ohci_xxx()函数,start(),hub_status_data(),hub_control()是针对s3c2410而编写的。

 

static const struct hc_driver ohci_s3c2410_hc_driver = {

     .description =         hcd_name,

     .product_desc =        "S3C24XX OHCI",

     .hcd_priv_size =   sizeof(struct ohci_hcd),

 

     /*

      * generic hardware linkage

      */

     .irq =             ohci_irq,

     .flags =      HCD_USB11 | HCD_MEMORY,

 

     /*

      * basic lifecycle operations

      */

     .start =      ohci_s3c2410_start,

     .stop =            ohci_stop,

 

     /*

      * managing i/o requests and associated device resources

      */

     .urb_enqueue =         ohci_urb_enqueue,

     .urb_dequeue =         ohci_urb_dequeue,

     .endpoint_disable =    ohci_endpoint_disable,

 

     /*

      * scheduling support

      */

     .get_frame_number =    ohci_get_frame,

 

     /*

      * root hub support

      */

     .hub_status_data = ohci_s3c2410_hub_status_data,

     .hub_control =         ohci_s3c2410_hub_control,

#ifdef   CONFIG_PM

     .bus_suspend =         ohci_bus_suspend,

     .bus_resume =      ohci_bus_resume,

#endif

     .start_port_reset =    ohci_start_port_reset,

};

 

/* device driver */

static int ohci_hcd_s3c2410_drv_probe(struct platform_device *pdev)

{

     return usb_hcd_s3c2410_probe(&ohci_s3c2410_hc_driver, pdev);

}

 

static int ohci_hcd_s3c2410_drv_remove(struct platform_device *pdev)

{

     struct usb_hcd *hcd = platform_get_drvdata(pdev);

 

     usb_hcd_s3c2410_remove(hcd, pdev);

     return 0;

}

 

static struct platform_driver ohci_hcd_s3c2410_driver = {

     .probe        = ohci_hcd_s3c2410_drv_probe,

     .remove       = ohci_hcd_s3c2410_drv_remove,

     /*.suspend    = ohci_hcd_s3c2410_drv_suspend, */

     /*.resume = ohci_hcd_s3c2410_drv_resume, */

     .driver       = {

         .owner   = THIS_MODULE,

         .name    = "s3c2410-ohci",

     },

};

 

static int __init ohci_hcd_s3c2410_init (void)

{

     return platform_driver_register(&ohci_hcd_s3c2410_driver);

}

 

static void __exit ohci_hcd_s3c2410_cleanup (void)

{

     platform_driver_unregister(&ohci_hcd_s3c2410_driver);

}

 

module_init (ohci_hcd_s3c2410_init);

module_exit (ohci_hcd_s3c2410_cleanup);

 

USB主机侧具体设备驱动

对于内核驱动无法满足要求的Usb设备,我们要自己编写它们的驱动程序,可划分为几个设备类:

音频,通信,HID(人机接口),显示,存储设备,电源,打印,集线器等

Linux为各类USB设备分配了相应的设备号,如ACMUSB调制解调器主设备号166,设备名/dev/ttyACMn,USB打印机主设备号180,次设备号0-15,名/dev/lpn,USB串口主设备号188,名/dev/ttyUSBn等

内核提供USB文件系统usbfs,和/proc一样动态生成,通过在/etc/fstab中增加如下行:

none /proc/bus/usb usbfs defaults

USB设备和接口在sysfs中命名规则:

根集线器—集线器端口号:配置.接口

/sys/bus/usb目录下的多数文件是链接到/sys/devices及/sys/drivers中的相关文件的

 

USB请求块(URB

类似与网络设备驱动sk_buff,是用来描述与USB设备通信所用的基本载体和核心数据结构。

Urb处理流程:

1.创建

Struct urb *usb_alloc_urb(int iso_packets, int mem_flags);

iso_packets等时数据包的数目。mem_flags分配内存的标志

/* 创建一个urb,并且给它分配一个缓存*/
    urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!urb) {
        retval = -ENOMEM;
        goto error;
    }

2.初始化

urb的初始化问题,如果你只写简单的USB驱动,这块不用多加考虑,框架程序里的东西已经够用了,这里我们简单介绍三个初始化urb的辅助函数:
usb_fill_int_urb :它的函数原型是这样的:
void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buff,
int buffer_length,usb_complete_t complete,
void *context,int interval);
这个函数用来正确的初始化即将被发送到USB设备的中断端点的urb
usb_fill_bulk_urb :它的函数原型是这样的:
void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buffer,
int buffer_length,usb_complete_t complete)
这个函数是用来正确的初始化批量urb端点的。
usb_fill_control_urb :它的函数原型是这样的:
void usb_fill_control_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,unsigned char *setup_packet,void *transfer_buffer,int buffer_length,usb_complete_t complete,void *context);
这个函数是用来正确初始化控制urb端点的。setup_packet参数指向即将被发送到端口的设置数据包。

还有一个初始化等时urb的,它现在还没有初始化函数,所以它们在被提交到USB核心前,必须在驱动程序中手工地进行初始化,可以参考内核源代码树下的/usr/src/~/drivers/usb/media下的usbvideo.c文件。

3.USB设备驱动提交到USB核心

Int usb_submit_urb(struct urb *urb, int mem_flags); mem_flags告知USB核心如何分配内存缓存区

4.提交由USB核心指定的USB主机控制器驱动

5.USB主机控制器处理,进行一次到USB设备的传送。

 

USB驱动程序实践
了解了上述理论后,我们就可以动手写驱动程序,如果你基本功好,而且写过linux下的硬件驱动,USB的硬件驱动和pci_driver很类似,那么写USB的驱动就比较简单了,如果你只是大体了解了linux的硬件驱动,那也不要紧,因为在linux的内核源码中有一个框架程序可以拿来借用一下,这个框架程序在/usr/src/~(你的内核版本,以下同)/drivers/usb下,文件名为usb-skeleton.c
写一个
USB的驱动程序最基本的要做四件事:驱动程序要支持的设备、注册USB驱动程序、探测和断开、提交和控制urbUSB请求块)(当然也可以不用urb来传输数据,下文我们会说到)。
驱动程序支持的设备:有一个结构体struct usb_device_id,这个结构体提供了一列不同类型的该驱动程序支持的USB设备,对于一个只控制一个特定的USB设备的驱动程序来说,struct usb_device_id表被定义为:
/* 驱动程序支持的设备列表 */
static struct usb_device_id skel_table [] = {
    { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
    { }                    /* 
终止入口 */
};
MODULE_DEVICE_TABLE (usb, skel_table);
对于PC驱动程序,MODULE_DEVICE_TABLE是必需的,而且usb必需为该宏的第一个值,而USB_SKEL_VENDOR_IDUSB_SKEL_PRODUCT_ID就是这个特殊设备的制造商和产品的ID了,我们在程序中把定义的值改为我们这款USB的,如:
/* 定义制造商和产品的ID */
#define USB_SKEL_VENDOR_ID    0x1234
#define USB_SKEL_PRODUCT_ID    0x2345
这两个值可以通过命令lsusb,当然你得先把USB设备先插到主机上了。或者查看厂商的USB设备的手册也能得到,在我机器上运行lsusb是这样的结果:
Bus 004 Device 001: ID 0000:0000  
Bus 003 Device 002: ID 1234:2345  Abc  Corp. 
Bus 002 Device 001: ID 0000:0000  
Bus 001 Device 001: ID 0000:0000
得到这两个值后把它定义到程序里就可以了。
USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID//该宏根据制造商ID和产品ID生成一个usb_device_id
USB_DEVICE_INFO(class,subclass,protocol)//该宏用于创建一个匹配指定类型的usb_device_id
USB_DEVICE_INFO(class,subclass,protocol)//该宏该宏用于创建一个匹配接口指定类型的usb_device_id

注册USB驱动程序:
所有的USB驱动程序都必须创建的结构体是struct usb_driver。这个结构体必须由USB驱动程序来填写,包括许多回调函数和变量,它们向USB核心代码描述USB驱动程序。
创建一个有效的
struct usb_driver结构体,只须要初始化五个字段就可以了,在框架程序中是这样的:
static struct usb_driver skel_driver = {
    .owner =    THIS_MODULE,
    .name =        "skeleton",
    .probe =    skel_probe,
    .disconnect =    skel_disconnect,
    .id_table =    skel_table,
};
struct module *owner 
指向该驱动程序的模块所有者的指针。USB核心使用它来正确地对该USB驱动程序进行引用计数,使它不会在不合适的时刻被卸载掉,这个变量应该被设置为THIS_MODULE宏。
const char *name指向驱动程序名字的指针,在内核的所有USB驱动程序中它必须是唯一的,通常被设置为和驱动程序模块名相同的名字。
int (*probe) (struct usb_interface *intf,const struct usb_device_id *id):这个是指向USB驱动程序中的探测函数的指针。USB核心认为它有一个接口(usb_interface)可以由该驱动程序处理时,这个函数被调用。
void (disconnect)(struct usb_interface *intf)指向USB驱动程序中的断开函数的指针,当一个USB接口(usb_interface)被从系统中移除或者驱动程序正在从USB核心中卸载时,USB核心将调用这个函数。
const struct usb_device_id *id_table指向ID设备表的指针,这个表包含了一列该驱动程序可以支持的USB设备,如果没有设置这个变量,USB驱动程序中的探测回调函数就不会被调用。
在这个结构体中还有其它的几个回调函数不是很常用,这里就不一一说明了。以struct usb_driver 指针为参数的usb_register_driver函数调用把struct usb_driver注册到USB核心。一般是在USB驱动程序的模块初始化代码中完成这个工作的:
static int __init usb_skel_init(void)
{
    int result;

    /* 
驱动程序注册到USB子系统中*/
    result = usb_register(&skel_driver);
    if (result)
        err("usb_register failed. Error number %d", result);

    return result;
}
USB驱动程序将要被卸开时,需要把struct usb_driver从内核中注销。通过调用usb_deregister_driver来完成这个工作,当调用发生时,当前绑定到该驱动程序上的任何USB接口都被断开,断开函数将被调用:
static void __exit usb_skel_exit(void)
{
    /* 
从子系统注销驱动程序 */
    usb_deregister(&skel_driver);
}

探测和断开函数USB设备驱动usb_driver结构体的探测函数应完成入下工作:

1.探测设备端点地址,缓冲区大小,初始化任何可能用于控制USB设备的数据结构

2.把已经初始化的数据结构的指针保存到接口设备中

Usb_set_intfdata()函数设置USB_interface的私有数据

Usb_get_intfdata()获取usb_interface的私有数据

3.注册USB设备

如果是字符设备,调用usb_register_dev()

Int usb_register_dev(struct usb_interface *intf, struct usb_class_driver *class_driver);

第二个参数为usb_class_driver结构体

Struct usb_class_driver{

Char *name;文件系统中来描述设备名

Struct file_operations *fops;文件操作结构体指针

Int minor_base;开始次设备号

}

其中fops成员函数中的write(),read(),ioctl()等函数完全等同与字符设备的驱动;若为类型的设备则调用对应设备的注册函数

4.释放所有为设备分配的资源

当一个USB设备被断开时,和该设备相关联的所有资源都应该被尽可能的清理掉,在此时,如果已在在探测函数中调用了注册函数来为该USB设备分配了一个次设备号话,必须调用usb_deregister_dev函数来把次设备号交还给USB核心。

5.设置接口设备的数据指针为NULL

在断开函数中,从接口获取之前调用usb_set_intfdata设置的任何数据也是很重要的。然后设置struct usb_interface结构体中的数据指针为NULL,以防任何不适当的对该数据的错误访问。

6.注销USB设备

下面是探测函数的部分源码,我们加以分析。
    /* 设置端点信息 */
    /* 
只使用第一个批量IN和批量OUT端点 */
    iface_desc = interface->cur_altsetting;
    for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
        endpoint = &iface_desc->endpoint[i].desc;

        if (!dev->bulk_in_endpointAddr &&
            (endpoint->bEndpointAddress & USB_DIR_IN) &&
            ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
                    == USB_ENDPOINT_XFER_BULK)) {
            /
找到一个批量IN端点 */
            buffer_size = endpoint->wMaxPacketSize;
            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) {
                err("Could not allocate bulk_in_buffer");
                goto error;
            }
        }

        if (!dev->bulk_out_endpointAddr &&
            !(endpoint->bEndpointAddress & USB_DIR_IN) &&
            ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
                    == USB_ENDPOINT_XFER_BULK)) {
            /* 
找到一个批量OUT端点 */
            dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
        }
    }
    if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
        err("Could not find both bulk-in and bulk-out endpoints");
        goto error;
    }
在探测函数里,这个循环首先访问该接口中存在的每一个端点,给该端点一个局部指针以便以后访问:
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
        endpoint = &iface_desc->endpoint[i].desc;
在一轮探测过后,我们就有了一个端点,在还没有发现批量IN类型的端点时,探测该端点方向是否为IN,这可以通过检查USB_DIR_IN是否包含在bEndpointAddress端点变量有确定,如果是的话,我们在探测该端点类型是否为批量,先用USB_ENDPOINT_XFERTYPE_MASK位掩码来取bmAttributes变量的值,然后探测它是否和USB_ENDPOINT_XFER_BULK值匹配:
        if (!dev->bulk_out_endpointAddr &&
            !(endpoint->bEndpointAddress & USB_DIR_IN) &&
            ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
                    == USB_ENDPOINT_XFER_BULK))
如果所有这些探测都通过了,驱动程序就知道它已经发现了正确的端点类型,可以把该端点的相关信息保存到一个局部结构体中以便稍后用它来和端点进行通信:
            /* 找到一个批量IN类型的端点 */
            buffer_size = endpoint->wMaxPacketSize;
            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) {
                err("Could not allocate bulk_in_buffer");
                goto error;
            }
因为USB驱动程序要在设备的生命周期的稍后时间获取和接口相关联的局部数据结构体,所以调用了usb_set_intfdata函数,把它保存到struct usb_interface结构体中以便后面的访问
    /* 把数据指针保存到这个接口设备中 */
    usb_set_intfdata(interface, dev);
我们以后调用usb_set_intfdata函数来获取数据。当这一切都完成后,USB驱动程序必须在探测函数中调用usb_register_dev函数来把该设备注册到USB核心里:
    /* 注册设备到USB核心 */
    retval = usb_register_dev(interface, &skel_class);
    if (retval) {
        /* 
有些情况下是不允许注册驱动程序的 */
        err("Not able to get a minor for this device.");
        usb_set_intfdata(interface, NULL);
        goto error;
    }
当一个USB设备被断开时,和该设备相关联的所有资源都应该被尽可能的清理掉,在此时,如果已在在探测函数中调用了注册函数来为该USB设备分配了一个次设备号话,必须调用usb_deregister_dev函数来把次设备号交还给USB核心。在断开函数中,从接口获取之前调用usb_set_intfdata设置的任何数据也是很重要的。然后设置struct usb_interface结构体中的数据指针为NULL,以防任何不适当的对该数据的错误访问。
在探测函数中会对每一个接口进行一次探测,所以我们在写USB驱动程序的时候,只要做好第一个端点,其它的端点就会自动完成探测。在探测函数中我们要注意的是在内核中用结构体struct usb_host_endpoint来描述USB端点,这个结构体在另一个名为struct usb_endpoint_descriptor的结构体中包含了真正的端点信息,struct usb_endpoint_descriptor结构体包含了所有的USB特定的数据,该结构体中我们要关心的几个字段是:
bEndpointAddress:这个是特定的USB地址,可以结合USB_DIR_INUSB_DIR_OUT来使用,以确定该端点的数据是传向设备还是主机。
bmAttributes:这个是端点的类型,这个值可以结合位掩码USB_ENDPOINT_XFERTYPE_MASK来使用,以确定此端点的类型是USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XFER_BULK(批量)、USB_ENDPOINT_XFER_INT的哪一种。
wMaxPacketSize:这个是端点一次可以处理的最大字节数,驱动程序可以发送数量大于此值的数据到端点,在实际传输中,数据量如果大于此值会被分割。
bInterval:这个值只有在端点类型是中断类型时才起作用,它是端点中断请求的间隔时间,以毫秒为单位。

提交和控制urb:当驱动程序有数据要发送到USB设备时(大多数情况是在驱动程序的写函数中),要分配一个urb来把数据传输给设备:
    /* 创建一个urb,并且给它分配一个缓存*/
    urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!urb) {
        retval = -ENOMEM;
        goto error;
    }
urb被成功分配后,还要创建一个DMA缓冲区来以高效的方式发送数据到设备,传递给驱动程序的数据要复制到这块缓冲中去:
    buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);
    if (!buf) {
        retval = -ENOMEM;
        goto error;
    }

    if (copy_from_user(buf, user_buffer, count)) {
        retval = -EFAULT;
        goto error;
    }
当数据从用户空间正确复制到局部缓冲区后,urb必须在可以被提交给USB核心之前被正确初始化:
    /* 初始化urb */
    usb_fill_bulk_urb(urb, dev->udev,
              usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
              buf, count, skel_write_bulk_callback, dev);
    urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
然后urb就可以被提交给USB核心以传输到设备了:
    /* 把数据从批量OUT端口发出 */
    retval = usb_submit_urb(urb, GFP_KERNEL);
    if (retval) {
        err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);
        goto error;
    }
urb被成功传输到USB设备之后,urb回调函数将被USB核心调用,在我们的例子中,我们初始化urb,使它指向skel_write_bulk_callback函数,以下就是该函数:
static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
{
    struct usb_skel *dev;

    dev = (struct usb_skel *)urb->context;

    if (urb->status && 
        !(urb->status == -ENOENT || 
          urb->status == -ECONNRESET ||
          urb->status == -ESHUTDOWN)) {
        dbg("%s - nonzero write bulk status received: %d",
            __FUNCTION__, urb->status);
    }

    /* 
释放已分配的缓冲区 */
    usb_buffer_free(urb->dev, urb->transfer_buffer_length, 
            urb->transfer_buffer, urb->transfer_dma);
}
有时候USB驱动程序只是要发送或者接收一些简单的数据,驱动程序也可以不用urb来进行数据的传输,这是里涉及到两个简单的接口函数:usb_bulk_msgusb_control_msg ,在这个USB框架程序里读操作就是这样的一个应用:
/* 进行阻塞的批量读以从设备获取数据 */
    retval = usb_bulk_msg(dev->udev,
                  usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
                  dev->bulk_in_buffer,
                  min(dev->bulk_in_size, count),
                  &count, HZ*10);

    /*
如果读成功,复制到用户空间 */
    if (!retval) {
        if (copy_to_user(buffer, dev->bulk_in_buffer, count))
            retval = -EFAULT;
        else
            retval = count;
    }
usb_bulk_msg
接口函数的定义如下:
int usb_bulk_msg(struct usb_device *usb_dev,unsigned int pipe,
void *data,int len,int *actual_length,int timeout);
其参数为:
struct usb_device *usb_dev:指向批量消息所发送的目标USB设备指针。
unsigned int pipe:批量消息所发送目标USB设备的特定端点,此值是调用usb_sndbulkpipe或者usb_rcvbulkpipe来创建的。
void *data:如果是一个OUT端点,它是指向即将发送到设备的数据的指针。如果是IN端点,它是指向从设备读取的数据应该存放的位置的指针。
int lendata参数所指缓冲区的大小。
int *actual_length:指向保存实际传输字节数的位置的指针,至于是传输到设备还是从设备接收取决于端点的方向。
int timeout:以Jiffies为单位的等待的超时时间,如果该值为0,该函数一直等待消息的结束。
如果该接口函数调用成功,返回值为0,否则返回一个负的错误值。
usb_control_msg接口函数定义如下:
int usb_control_msg(struct usb_device *dev,unsigned int pipe,__u8    request,__u8requesttype,__u16 value,__u16 index,void *data,__u16 size,int timeout)
除了允许驱动程序发送和接收USB控制消息之外,usb_control_msg函数的运作和usb_bulk_msg函数类似,其参数和usb_bulk_msg的参数有几个重要区别:
struct usb_device *dev:指向控制消息所发送的目标USB设备的指针。
unsigned int pipe:控制消息所发送的目标USB设备的特定端点,该值是调用usb_sndctrlpipeusb_rcvctrlpipe来创建的。
__u8 request:控制消息的USB请求值。
__u8 requesttype:控制消息的USB请求类型值。
__u16 value:控制消息的USB消息值。
__u16 index:控制消息的USB消息索引值。
void *data:如果是一个OUT端点,它是指身即将发送到设备的数据的指针。如果是一个IN端点,它是指向从设备读取的数据应该存放的位置的指针。
__u16 sizedata参数所指缓冲区的大小。
int timeout:以Jiffies为单位的应该等待的超时时间,如果为0,该函数将一直等待消息结束。
如果该接口函数调用成功,返回传输到设备或者从设备读取的字节数;如果不成功它返回一个负的错误值。
这两个接口函数都不能在一个中断上下文中或者持有自旋锁的情况下调用,同样,该函数也不能被任何其它函数取消,使用时要谨慎。
我们要给未知的USB设备写驱动程序,只需要把这个框架程序稍做修改就可以用了,前面我们已经说过要修改制造商和产品的ID号,把0xfff0这两个值改为未知USBID号。
 #define USB_SKEL_VENDOR_ID      0xfff0
     #define USB_SKEL_PRODUCT_ID     0xfff0
还有就是在探测函数中把需要探测的接口端点类型写好,在这个框架程序中只探测了批量(USB_ENDPOINT_XFER_BULKINOUT端点,可以在此处使用掩码(USB_ENDPOINT_XFERTYPE_MASK)让其探测其它的端点类型,驱动程序会对USB设备的每一个接口进行一次探测,当探测成功后,驱动程序就被绑定到这个接口上。再有就是urb的初始化问题,如果你只写简单的USB驱动,这块不用多加考虑,框架程序里的东西已经够用了,这里我们简单介绍三个初始化urb的辅助函数:
usb_fill_int_urb :它的函数原型是这样的:
void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buff,
int buffer_length,usb_complete_t complete,
void *context,int interval);
这个函数用来正确的初始化即将被发送到USB设备的中断端点的urb
usb_fill_bulk_urb :它的函数原型是这样的:
void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buffer,
int buffer_length,usb_complete_t complete)
这个函数是用来正确的初始化批量urb端点的。
usb_fill_control_urb :它的函数原型是这样的:
void usb_fill_control_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,unsigned char *setup_packet,void *transfer_buffer,int buffer_length,usb_complete_t complete,void *context);
这个函数是用来正确初始化控制urb端点的。
还有一个初始化等时urb的,它现在还没有初始化函数,所以它们在被提交到USB核心前,必须在驱动程序中手工地进行初始化,可以参考内核源代码树下的/usr/src/~/drivers/usb/media下的konicawc.c文件。

驱动模块的编译、配置和使用
现在我们的驱动程序已经大体写好了,然后在linux下把它编译成模块就可以把驱动模块插入到内核中运行了,编译的Makefile文件可以这样来写:
ifneq ($(KERNELRELEASE),)
    obj-m := xxx.o
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
    rm -rf *.mod.* *.o *.ko .*.ko.* .tmp* .*.mod.o.* .*.o.*
其中xxx是源文件的文件名,在linux下直接执行make就可以生成驱动模块(xxx.ko)了。生成驱动模块后使用insmod xxx.ko就可以插入到内核中运行了,用lsmod可以看到你插入到内核中的模块,也可以从系统中用命令rmmod xxx把模块卸载掉;如果把编译出来的驱动模块拷贝到/lib/modules/~/kernel/drivers/usb/下,然后depmod一下,那么你在插入USB设备的时候,系统就会自动为你加载驱动模块的;当然这个得有hotplug的支持;加载驱动模块成功后就会在/dev/下生成设备文件了,如果用命令cat /proc/bus/usb/devices,我们可以看到驱动程序已经绑定到接口上了:
T:  Bus=03 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#=  2 Spd=12  MxCh= 0
D:  Ver= 1.10 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1
P:  Vendor=1234 ProdID=2345 Rev= 
1.10
C:* #Ifs= 1 Cfg#= 1 Atr=c0 MxPwr=  0mA
I:  If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=test_usb_driver /*
我们的驱动*/
E:  Ad=01(O) Atr=02(Bulk) MxPS=  64 Ivl=0ms
E:  Ad=82(I) Atr=02(Bulk) MxPS=  64 Ivl=0ms
此框架程序生成的是skel0(可以自由修改)的设备文件,现在就可以对这个设备文件进行打开、读写、关闭等的操作了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Arm嵌入式开发之USB主机与设备驱动 的相关文章

  • 发现多个 USB-IrDA 设备,打开并连接每个设备的套接字

    对于任何优秀的套接字程序员 是否可以使用套接字枚举通过 USB 端口连接到 PC 的多个活动 IrDA 设备 如果是这样 怎么办 这确实是我的主要问题 帖子的其余部分完善了细节并描述了我所尝试的内容 我正在使用 Microsoft SDK
  • 如何在没有窗口句柄的情况下使用 RegisterDeviceNotification?

    我正在编写一个 DLL 库 该库使用 VB NET 中的 winusb 连接到 USB 设备 由于它是一个 DLL 所以我没有窗口 而且我的库的用户也可能没有窗口 例如命令行应用程序 我见过的检测设备连接和分离的示例都使用RegisterD
  • libv4l2:打开流时出错:设备上没有剩余空间

    我尝试为 opencv 获取立体声对 我将 Logitech B910 和 Logitech C910 网络摄像头连接到 USB 但有这个错误 我玩弄了怪癖参数并设置outfmt mjpeg在mplayer中 但又出现此错误 在哪里可以找到
  • 如何枚举 USB 设备*并*对其进行读/写?

    我需要与一个自定义 USB 设备进行通信 它没有特定的驱动程序 该设备不显示为 dev tty I 发现 http www signal11 us oss udev 我该如何使用libudev枚举 USB 设备 假设我设法获得相应的stru
  • Silverlight 能否访问可访问 USB 端口的 .Net 库?

    我们有一个 Net 库 可以通过 USB 端口访问一些自定义硬件 我读到 Silverlight 包含 Net 运行时的子集 所以 我的问题是 这个子集足以让 Net 库运行吗 不可以 您不能使用从 Silverlight 内部访问 USB
  • 从网络浏览器中检测和访问 USB 设备 - 即使用插件

    是否可以通过插件 activeX Java 或 Flex 编写一个能够检测从 USB 设备通道读取数据流的浏览器插件 我做了一些研究 但没有找到答案 http support microsoft com kb 832678 http sup
  • USBInterfaceOpen总是报kIOReturnExclusiveAccess错误

    最近我遇到了这个问题 很头疼 我已经在这个问题上花了一个星期了 但仍然失败 希望您能帮我把这块石头踢开 非常感谢 我的问题 我们公司为iPhone生产USB存储设备 实际上这个存储设备中有一个SDCard 现在 我们想要开发一个 Mac 应
  • USB接口条码扫描器

    不完全是一个编程问题 但很接近 无论如何我都会试试运气 The 键盘楔子 扫码机插入 读者之间的翻译装置 和键盘 数据发送通过 楔形看起来就像是被输入的 电脑 而键盘 本身仍保持完整功能 因为电脑使用键盘 楔子无法区分 在输入的数据之间 扫
  • c#.NET USB设备持久标识符

    我正在寻找一种在 C 中为 USB 插槽 更具体地说是 SD 卡读卡器 提供持久标识符 在最终用户拔下集线器 重新启动计算机时 的方法 驱动器号并不总是分配给同一个插槽 但我需要一种方法来物理识别插槽 A 一旦我能够识别它 我就可以连接到驱
  • LibUsb 声明接口访问被拒绝 Java

    我希望能够从 USB 计步器读取数据 我正在 Java 中尝试此操作 并且使用 LibUsb 和 Usb4Java 库 我似乎无法认领 USB 管道或类似的东西 我正在使用的代码 final Context context new Cont
  • STM32 传输结束时,循环 DMA 外设到存储器的行为如何?

    我想问一下 在以下情况下 STM32 中的 DMA SPI rx 会如何表现 我有一个指定的 例如 96 字节数组 名为 A 用于存储从 SPI 接收到的数据 我打开循环 SPI DMA 它对每个字节进行操作 配置为 96 字节 是否有可能
  • 我可以将 SQL Server 安装在 USB 驱动器上吗?

    我是一名开发人员 如果可以的话 我经常远程工作 而不是通勤上班 我希望能够随身携带开发数据库环境 我知道我可以远程访问数据库 但速度很慢 我有时在没有无线连接的地方工作 有任何想法吗 是的 这是可能的 看this http social m
  • 虚拟USB设备的安装与仿真

    我已经读过创建虚拟USB设备 https stackoverflow com questions 5016363 creating a virtual usb device and 虚拟USB设备 https stackoverflow c
  • BeagleBone Black 如何用作大容量存储设备?

    是否可以使用 BB 作为大容量存储设备 我希望将其连接到可以从 USB 连接 例如 USB 闪存驱动器 读取文件的音频播放器并充当包含一个特定文件夹的数据存储设备 及其子文件夹 从文件系统 如果可能 在连接到开发板的闪存驱动器上 正如设备规
  • 作为附件的 Android 设备

    我有 2 个 Android 设备 我想用 USB OTG 电缆连接它们 并在两个设备之间进行来回通信 据我了解 一台 Android 设备将充当 USB 主机 运行 4 4 的 Nexus 7 另一台 Android 设备将充当 USB
  • Linux中根据USB VID:PID获取设备路径

    如果我插入一个设备 比如说 dev ttyUSB0我想知道号码0基于其 VID PID 通过lsusb 我怎样才能在 C Linux 中做到这一点 我有这个代码来查找一台打印机设备 如果有帮助的话 int printer open void
  • 未找到 Chrome 应用 USB DigitalPersona 指纹识别器

    我正在尝试开发 Google Chrome 应用程序 或扩展程序 不确定 以在 Windows 上使用 DigitalPersona 指纹识别器 下列的 https developer chrome com apps usb https d
  • Vb中读取USB存储设备的序列号

    是否可以使用VB读取USB驱动器的序列号 这将为您提供 net 中驱动器的信息 包括 USB 设备 只需导入这些 导入脚本 导入系统 IO Private Class USBsn Private Sub Button1 Click ByVa
  • cdc_acm:无法设置 dtr/rts - 无法与 USB cdc 设备通信

    我试图使用 pic24fj128gb206 枚举 usb cdc 设备 设备似乎已正确枚举 但是当我将设备连接到 Linux PC 时 我从内核收到以下警告消息 cdc acm 1 8 1 6 7 1 0 failed to set dtr
  • 启用 USB 设备进行远程调试在 Firefox 上不起作用

    我使用的firefox版本是106 0 2 我正在尝试连接 oculus quest 2 来调试网站项目 问题是我按下启用 USB 设备按钮 它显示更新状态 然后再次显示相同的按钮 状态保持禁用 有任何想法吗 chrome 的开发工具也不起

随机推荐

  • 微服务整合knife4j springboot2.6.14

    业务层 springboot集成knife4j 引入jar包依赖
  • Qt.ui文件是怎么生成相应的.h文件

    ui文件在编译文件时通过uic o ui h ui 命令自动生成ui头文件
  • SIMetrix教程-001.SIMetrix软件简介与安装

    由于某些原因需要用到SIMetrix仿真软件 然而网上的资料并不是特别多 故在此记录一下这款仿真软件的学习过程 也给有需要的人提供一些参考 免走弯路 如果使用过Pspice或者LTspice软件 学习SIMetrix会很容易入门 仿真软件都
  • bpe分词算法的原理以及在机器翻译中的应用

    概述 bpe byte pair encoding 是一种根据字节对进行编码的算法 主要目的是为了数据压缩 算法描述为字符串里频率最常见的一对字符被一个没有在这个字符中出现的字符代替的层层迭代过程 该算法在论文 https arxiv or
  • 分布式系统failover测试之拔盘插盘操作

    分布式系统failover测试之拔盘插盘操作 拔盘 echo scsi remove single device 1 0 0 0 gt proc scsi scsi echo scsi remove single device 2 0 0
  • CVPR2023论文及代码合集来啦~

    以下内容由马拉AI整理汇总 下载 点我跳转 狂肝200小时的良心制作 529篇最新CVPR2023论文及其Code 汇总成册 制作成 CVPR 2023论文代码检索目录 包括以下方向 1 2D目标检测 2 视频目标检测 3 3D目标检测 4
  • 图形化OpenGL调试器 BuGLe

    图形化OpenGL调试器 BuGLe 转 BuGLe 结合图形化的OpenGL调试与选择的过滤器上的OpenGL命令流 调试器可以查看状态 纹理 framebuffers 着色器 而过滤器允许日志 错误检查 自由相机控制 视频捕捉等 主页
  • 数羊

    H题数羊 第八届 图灵杯 NEUQ ACM程序设计竞赛个人赛 题目描述 憨憨小杨晚上睡不着觉 就开始数羊 她觉得一只一只数太慢了 突发奇想出了一种新的数羊方式 羊羊数量A n m 由两个整形变量n和m决定 计算方式如下 现在给出n和m的值
  • c#学习笔记

    1 输入prop 再按两次TAB会自动添加一个属性 set get方法 2 统一命名空间内 类可以跨多个文件 需要用partial e g partial class XXXX 3 类型 ulong 64无符号整数 decimal 有效数字
  • 阿里云服务器安装WordPress网站教程基于CentOS系统

    阿里云百科分享使用阿里云服务器安装WordPress博客网站教程 WordPress是使用PHP语言开发的博客平台 在支持PHP和MySQL数据库的服务器上 您可以用WordPress架设自己的网站 也可以用作内容管理系统 CMS 本教程介
  • linux---我电脑把给ubuntu分区删了开机就是什么grub rescue怎么办

    装了ubuntu后 会默认从Ubuntu的Grub菜单引导系统 Win8的MBR被替换为Grub来引导 于是 你删除了Ubuntu 也就是删除了Grub的文件 但是 你的引导扇区的启动参数还是指向了Grub 于是就grun rescue了
  • QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。

    QT多线程5种用法 第一种 主线程 GUI 第二种 子线程1继承自QThread 头文件 movetothread4 h 源文件 movetothread4 cpp 子线程1对象的创建 第二种 子线程2继承自QThread 头文件 源文件
  • Cannot find QEMU binary /usr/bin/kvm: Permission denied

    kvm报错 信息如下 Cannot find QEMU binary usr bin kvm Permission denied 解决办法 find name qemu kvm 或者 find name qemu 然后加软连接 ln s u
  • emacs verilog-mode方式实现verilog实例化集成

    文章目录 背景介绍 AUTOINST和AUTOWIRE的应用 推荐使用方法 auto template命令总结 中括号 里面没内容 表示auto inst时 会显示 3 0 类似内容 常用于相同module 多次实例化情况 我不常用 这里仅
  • docker:基本架构

    docker是典型的CS架构 包括客户端和服务端两大核心组件 同时通过镜像仓库来存储镜像 客户端和服务器既可以运行在同一个机器上 也可以通过socket或者RESTful API来进行通信 服务端 docker的服务端一般在宿主主机后台运行
  • 将li标签元素内容排在一行

  • esp8266学习

    在网上学习AT指令 学习esp8266的相关内容 正点原子官方AT指令集 https img blog csdnimg cn 50f65a5a0b724815b82b56d7bb57c113 png x oss process image
  • 跟我一起开启 linux 的学习吧

    跟我学 CentOS 的安装 一 安装 VMware 二 创建虚拟机 三 安装 CentOS 7 四 linux 的登录 一 安装 VMware VMware 计算机虚拟化软件 从官网 https www vmware com cn htm
  • 在VMware虚拟机中安装CentOS 7(图文教程)

    一 VMware简介 VMware是一个虚拟PC的软件 可以在现有的操纵系统上虚拟出一个新的硬件环境 相当于模拟出一台新的PC 以此来实现在一台机器上真正同时运行两个独立的操作系统 VMware的主要特点 不需要区分或重新开机就能在同一台P
  • Arm嵌入式开发之USB主机与设备驱动

    Arm嵌入式开发之USB主机与设备驱动 2013 03 05 00 37 25人阅读 评论 0 收藏 举报 USB驱动层次结构 由上到下 USB设备驱动 USB核心 USB主机控制器驱动 USB控制器硬件 USB核心为USB驱动程序提供了一