精华内容
下载资源
问答
  • Virtual Usb Bus Enumerator驱动,有需要的可以下载,
  • Windows 7(64位)操作系统安装驱动错误(代码 52)的解决办法 故障描述Windows 7操作系统设备驱动安装正确但是设备管理器中出现黄色感叹号提示信息无法验证此设备所需驱动程序的数字签名最近的硬件或软件更改安装的文件...
  • 硬件开发板通过USB连接电脑,安装驱动时,选择了驱动文件,但弹出“Windows已找到设备的驱动程序软件,但在试图安装它时遇到错误”,附件提供解决方法。
  • 设备驱动程序是操作系统内核和机器硬件之间的接口,由一组函数和一些私有数据组成,是应用程序和硬件设备之间的桥梁。在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作...
    
    

           设备驱动程序是操作系统内核和机器硬件之间的接口,由一组函数和一些私有数据组成,是应用程序和硬件设备之间的桥梁。在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。

          设备驱动程序是内核的一部分,主要完成以下功能:对设备的初始化和释放把数据从内核传送到硬件设备和从硬件设备读取数据读取应用程序数据传送给设备文件和回送应用程序请求的数据检测和处理硬件设备出现的错误


    一、 Linux USB子系统分析

            在Linux系统中,USB主机驱动程序由3部分组成:USB主机控制器驱动(HCD)USB核心驱动(USBD)不同种类的USB设备类驱动,如下所示。其中HCD和USBD被称为协议软件或者协议栈,这两部分共同处理与协议相关的操作。

           USB设备类驱动可以包含多个,不同的功能接口对应不同的驱动程序,它们不直接与USB设备硬件打交道,而是通过协议软件的抽象处理来完成与设备的不同功能接口之间的通信

           在Linux USB子系统中,HCD是直接和硬件进行交互的软件模块是USB协议栈的最底层部分是USB主机控制器硬件和数据传输的一种抽象

          HCD向上仅对USB总线驱动程序服务,HCD提供了一个软件接口,即HCDI,使得各种USB主机控制器的硬件特性都被软件化,并受USB总线驱动程序的调用和管理。HCD向下则直接管理和检测主控制器硬件的各种行为。HCD提供的功能主要有:主机控制器硬件初始化;为USBD层提供相应的接口函数;提供根HUB(ROOT HUB)设备配置、控制功能;完成4种类型的数据传输等。

          USBD部分是整个USB主机驱动的核心,主要实现的功能有:USB总线管理;USB总线设备管理、USB总线带宽管理、USB的4种类型数据传输、USB HUB驱动、为USB设备驱动提供相关接口、提供应用程序访问USB系统的文件接口等。其中USB HUB作为一类特殊的USB设备,其驱动程序被包含在USBD层。

         在嵌入式Linux系统中,已经包含HCD模块和USB核心驱动USBD,不需要用户重新编写,用户仅仅需要完成USB设备类驱动即可。


    二、Linux系统中USB子系统的主要数据结构

            Linux系统中,USBD通过定义一组宏、数据结构和函数来抽象出所有硬件或是设备具有依赖关系的部分。

    USBD中主要有四个数据结构,分别是:

    1.usb_device保存一个USB设备的信息,包括设备地址,设备描述符,配置描述符等。

    2.usb_bus保存一个USB总线系统的信息,包括总线上设备地址信息,根集线器,带宽使用情况等。一个USB总线系统至少有一个主机控制器一个根集线器,Linux系统支持多USB总线系统。

    3.usb_driver保存客户驱动信息,包括驱动名称,以及驱动提供给USB内核使用的函数指针等。

    4.URB(Universal Request Block)是进行USB通信的数据结构,USBD通过URB在USB设备类驱动和USBD、USBD和HCD间进行数据传输。


    三、Linux系统中USB设备的加载与卸载

           当把一个USB设备插入到一个USB HUB的某个端口时,集中器就会检测到设备的接入,从而在下一次受到主机通过中断交互查询时就会向其报告。集中器的端口在没有设备接入时都处于关闭状态,插入设备之后也不会自动打开,必须由主机通过控制交互发出命令予以打开。所以,在得到集中器的报告之后,主机的USB驱动程序就会为新插入的设备调度若干个控制交互,并向集中器发出打开这个端口的命令,这样新插入的设备就会出现在USB总线上了,并为该设备分配唯一的地址

           HUB驱动程序调用函数usb_connect(struct usb_device *dev)usb_new_device(struct usb_device *dev)解析设备的各种描述符信息,分配资源,并与相应的设备驱动程序建立联系。

    函数usb_new_device主要完成以下工作:

    1.调用usb_set_address把新分配的设备地址传送给设备。

    2.调用usb_get_descriptor获得设备的设备描述符,得到设备端点的包的最大长度,接下来的控制传输按这个数据包最大长度进行。

    3.调用usb_get_configuration得到设备的所有配置描述符、接口描述符和端点描述符信息。

    4.调用usb_set_configuration激活当前的配置作为默认工作配置。

    5.在目录“proc/bus/usb”中为设备创建节点。

    6.在USB子系统中,通过函数usb_find_driversusb_find_interface_driver为设备的每一个接口寻找相应的驱动程序,驱动程序对接口进行配置并为它们分配所需的资源。当每个接口被成功驱动后,此设备就能正常工作了。

          设备拔下时,与之相联的集线器首先检测到设备的拔下信号,通过中断传输将信息传送给集线器的驱动,集线器的驱动先验证设备是否被拔下,如果是则调用usb_disconnect(struct usb_device **pdev)进行处理。设备断开后,USB系统找到设备当前活动配置的每个接口的驱动程序,调用它们提供的disconnect接口函数,中断它们与各个接口的数据传输操作,释放它们为每个接口分配的资源。如果此设备是集线器,则递归调用usb_disconnect来处理它的子设备,释放设备地址,通过usbdevfs_remove_device函数释放给设备创建的文件节点,通过usb_free_dev释放USBD给设备分配的资源。


    四、编写USB驱动程序步骤

    1、所有usb驱动都必须创建主要结构体struct usb_driver

    struct usb_driver

    ->struct module *owner

       (有他可正确对该驱动程序引用计数,应为THIS_MODULE)

    ->const char *name

       (驱动名字,运行时可在查看 /sys/bus/usb/drivers/)

    ->const struct usb_device_id *id_table

       (包含该驱动可支持的所有不同类型的驱动设备,没添探测回调函数不会被调用)

    ->int (*probe)(struct usb_interface *intf,const struct usb_device_id *id)

       (usb驱动探测函数,确认后struct usb_interface 应恰当初始化,然后返0,如果出错则返负值)

    ->void(*disconnect)(struct usb_interface *intf)

       (当struct usb_interface 被从系统中移除或驱动正从usb核心中卸载时,usb核心将调用此函数)

    代码实例:

    1. static struct usb_driver skel_driver={
    2. .owner = THIS_MODULE,
    3. .name = "skeleton",
    4. .id_table = skel_table,
    5. .probe = skel_probe,
    6. .disconnect = skel_disconnect,
    7. };

    2、usb_register()注册将struct usb_driver 注册到usb核心,传统是在usb驱动程序模块初始化代码中完成该工作的

    1. static int __ init usb_skel_init(void)
    2. {
    3. ...
    4. usb_register(&skel_driver);
    5. ...
    6. }

    3、struct usb_device_id usb核心用该表判断哪个设备该使用哪个驱动程序,热插拔脚本使用它来确定当一个特定的设备插入到系统时该自动装载哪个驱动程序

    ->__u16 match_flags(确定设备和结构体中下列字段中哪一个相匹配)
    ->__u16 idVendor(设备的usb制造商id)
    ->__u16 idProduct(设备的usb产品id) 


    4、USB骨架程序的关键几点如下:

    a -- USB驱动的注册和注销 

       Usb驱动程序在注册时会发送一个命令给usb_register,通常在驱动程序的初始化函数里。

       当要从系统卸载驱动程序时,需要注销usb子系统。即需要usb_unregister 函数处理。

    b -- 当usb设备插入时,为了使linux-hotplug(Linux中PCI、USB等设备热插拔支持)系统自动装载驱动程序,你需要创建一个MODULE_DEVICE_TABLE

    代码如下(这个模块仅支持某一特定设备):

    1. static struct usb_device_id skel_table [] = {
    2. { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
    3. { } /* Terminating entry */};
    4. MODULE_DEVICE_TABLE (usb, skel_table);

     USB_DEVICE宏利用厂商ID和产品ID为我们提供了一个设备的唯一标识。当系统插入一个ID匹配的USB设备到USB总线时,驱动会在USB core中注册。驱动程序中probe 函数也就会被调用。usb_device 结构指针、接口号和接口ID都会被传递到函数中。

    c -- static void * skel_probe(struct usb_device *dev,unsigned int ifnum, const struct usb_device_id *id)

           驱动程序需要确认插入的设备是否可以被接受,如果不接受,或者在初始化的过程中发生任何错误,probe函数返回一个NULL值。否则返回一个含有设备驱动程序状态的指针。通过这个指针,就可以访问所有结构中的回调函数。

    d -- 在骨架驱动程序里,最后一点是我们要注册devfs

          我们创建一个缓冲用来保存那些被发送给usb设备的数据和那些从设备上接受的数据,同时USB urb 被初始化,并且我们在devfs子系统中注册设备,允许devfs用户访问我们的设备。注册过程如下:

    1. /* initialize the devfs node for this device and register it */
    2. sprintf(name, "skel%d", skel->;minor);
    3. skel->devfs = devfs_register (usb_devfs_handle, name,DEVFS_FL_DEFAULT, USB_MAJOR,USB_SKEL_MINOR_BASE + skel->minor,
    4. S_IFCHR | S_IRUSR | S_IWUSR |S_IRGRP | S_IWGRP | S_IROTH, &skel_fops, NULL);
    如果devfs_register函数失败,不用担心,devfs子系统会将此情况报告给用户。

    当然最后,如果设备从usb总线拔掉,设备指针会调用disconnect 函数。驱动程序就需要清除那些被分配了的所有私有数据、关闭urbs,并且从devfs上注销调自己。
      /* remove our devfs node */devfs_unregister(skel->;devfs);


    5、其他

    a -- struct usb_host_endpoint(描述usb端点)

    →(包含)struct usb_endpoint_descriptor(含真正端点信息,数据格式,是真正驱动关心的字段)

     端点描述符:

    bEndpointAddress = 81(in)(第8位为1是输入设备)(usb的端点地址,包含端点方向)
    bmAttibutes = 03(interrupt)(端点类型,为中断传输)
    wMaxPacketSize = 0008(每次传8个字节)(端点每次可处理最大字节长度)
    bInterval = 08(8ms)(如端点为中断,该值为轮询间隔)

    b -- usb端点捆绑为接口,usb接口只处理一种usb逻辑连接,如鼠标键盘等

       一个usb设备可有多接口,usb扬声器:一个usb键盘用于按键,一个usb音频流,则需两个不同的驱动程序。

       usb驱动 通常将struct usb_interface 转成 struct usb_device 用函数 interface_to_usbdev转 

    c -- struct usb_interface 描述usb接口

       →struct usb_host_interface * altsetting(接口结构体数组,包含所有可能用于该接口的可选设置)
        →struct usb_host_endpoint
       →unsigned num_altsetting(可选设置的数量)
       →struct usb_host_interface * cur_altsetting(接口当前活动设置)
       →int minor(usb核心分配给接口的次设备号,成功调用usb_register_dev有效) 

    d -- usb设备非常复杂,由许多不同逻辑单元组成,简单关系如下:

       设备通常有一个以上的配置
       配置经常有一个以上接口
       接口通常有一个以上设置
       接口通常有一个以上端点
       设备描述-》配置描述-》接口描述-》端点描述 

    e -- usb sysfs设备命名方案

       根集线器-集线器端口号:配置。接口
       对于usb hub树中层次更高的字树命名方案
       根集线器-集线器端口号-集线器端口号:配置。接口 

    f --  linux内核的代码通过一个成为urb(usb请求块)和所有usb设备通信.  

     用struct urb描述(include/linux/usb.h中定义) 

       ->urb用异步同usb设备特定usb端点发送/接收数据,使用类似网络代码中的struct skbuff
       -> urb 被动态创建,随时可被驱动程序或usb核心取消,内部有引用计数,可被多次调用,使他们可在最后一个使用者释放他们时自动地销毁
       -> urb使得流处理或其他复杂的重叠的通信成为可能,获得高数据传输速度。 
       ->usb_alloc_urb() 创建urb包 usb_free_urb() 释放urb包 
       ->usb_fill_int_urb()正确初始化将发送到usb设备的中断端点urb
         usb_fill_bulk_urb() .. .. .. ... 批量传输端点urb
         usb_fill_control_urb() .. .. .. ... 控制端点urb
         等时urb在提交给核心时必须手动初始化(很不幸,没函数)
       ->usb_submit_urb()urb被usb驱动正确创建和初始化后,就可提交到usb核心,发送到usb设备上了,如果调用成功,函数返0,urb控制权转给usb核心
       ->usb_kill_urb() or usb_unlink_urb()取消已经被提交给核心的urb 


    五、USB驱动开发简单示例

    1、嵌入式Linux系统中USB摄像头驱动程序实现

         通常USB设备类驱动程序需要提供两个数据结构接口,一个针对USBD层,一个针对文件系统。USB摄像头驱动程序需要做的第一件事情就是在USB子系统里注册,并提供一些相关信息,包括该驱动程序支持哪些设备,当被支持的设备从总线插入或拔出时,会有哪些动作等,所有这些信息通过usb_driver的形式传送到USBD中,具体实现如下:

    1. static struct usb_driver cam_driver = {
    2. .name: "cam_video",
    3. .probe: cam_probe,
    4. .disconnect: cam_disconnect,
    5. .id_table: cam_ids,
    6. };

    其中

    cam_video是客户端驱动程序的字符串名称,用于避免驱动程序的重复安装和卸载;

    cam_probe则指向USB驱动程序的探测函数指针,提供给USB内核的函数用于判断驱动程序是否能对设备的某个接口进行驱动

    cam_disconnect指向USB驱动程序中的断开函数的指针,当从系统中被移除或者驱动程序正在从USB核心中卸载时,USB核心将调用该函数;

    cam_ids列表包含了一系列该驱动程序可以支持的所有不同类型的USB设备,如没有设置该列表,则该驱动程序中的探测回调函数不会被调用。

           当一个摄像头连接到USB总线上时,USB内核通过调用camDrive.c中的cam_probe函数判断是否支持该设备,如果支持,为该设备创建设备文件节点,以后应用程序就可以通过标准POSIX函数,把该设备当成普通文件来访问。摄像头驱动程序定义的文件系统接口如下:

    1. struct file_operations cam_fops = {
    2. .owner = THIS_MODULE,
    3. .open = cam_v 4l2_open,
    4. .release = cam_v4l2_release,
    5. .ioctl = cam_v4l2_ioctl,
    6. .llseek = no_llseek,
    7. .read = cam_v4l2_read,
    8. .mmap = cam_v4l2_mmap,
    9. .poll = cam_v4l2_poll,
    10. };
         在USB摄像头驱动程序的初始化函数中,通过usb_register进行设备注册;当从系统卸载驱动程序时,需要通过usb_deregister进行卸载。当驱动程序向USB子系统注册后,插入一个新的USB设备后总是要调用cam_probe函数进行设备驱动程序的查找,以确定新的USB设备硬件中的生产厂商ID和产品自定义ID是否与驱动程序相符,从而确定是否使用该驱动程序。


     2、USB摄像头驱动程序测试

           在嵌入式Linux系统中,USB摄像头被注册为一个标准的视频设备/dev/video,通过影像设备API接口Video4Linux来获取视频和音频数据。

    现有的Video4Linux有两个版本:v4l和v4l2。通过v4l2 API接口获取视频图像的主要操作步骤如下:

    a -- 打开视频设备

    在Linux系统中,摄像头的设备文件为/dev/video0,调用系统函数open打开该设备。

    fd = open (dev_name, O_RDWR);

    b -- 获取视频设备所支持的V4L2特性

          所有的V4L2设备驱动都需要支持VIDIOC_QUERYCAP_ioctl的系统调用。通过该调用,确定该驱动程序是否与V4L2规范相兼容,同时获取该设备所支持的V4L2特性。在摄像头应用程序的开发过程中,需要判定该设备是否支持视频捕获。

    ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);

    c -- 获取视频设备支持的各种特性

          接着,利用ioctl(fd,VIDIOC_QUERYCAP,&cap)函数读取struct v4l2_capability中有关摄像头的信息。该函数成功返回后,这些信息从内核空间拷贝到用户程序空间capability各成员分量中。

    ioctl(device_fd, VIDIOCGCAP, &vidcap);

    d -- 设置视频捕获的图像格式

    memset(&fmt, 0, sizeof(struct v4l2_format));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = vd->width;
    fmt.fmt.pix.height = vd->height;
    fmt.fmt.pix.pixelformat = vd->formatIn;
    ret = ioctl(fd, VIDIOC_S_FMT, &fmt);

    e -- 视频数据帧捕获

    ioctl (fd, VIDIOC_DQBUF, &buf);

    获取到视频数据之后,放到buf缓冲区中,通过QT桌面应用开发系统,显示到LCD显示屏上,通过触摸屏进行交互控制。

    展开全文
  • Linux USB 驱动开发(三)—— 编写USB 驱动程序

    万次阅读 多人点赞 2016-03-26 15:10:13
    Linux的设备驱动都遵循一个惯例——表征驱动程序(用driver更贴切一些,应该称为驱动器比较好吧)的结构体,结构体里面应该包含了驱动程序所需要的所有资源。用术语来说,就是这个驱动器对象所拥有的属性及成员。 ...

        前面学习了USB驱动的一些基础概念与重要的数据结构,那么究竟如何编写一个USB 驱动程序呢?编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否安装了硬件。当然,这些制造商和设备标识需要我们编写进USB 驱动程序中。

      USB 驱动程序依然遵循设备模型 —— 总线、设备、驱动。和I2C 总线设备驱动编写一样,所有的USB驱动程序都必须创建的主要结构体是 struct usb_driver,它们向USB 核心代码描述了USB 驱动程序。但这是个外壳,只是实现设备和总线的挂接,具体的USB 设备是什么样的,如何实现的,比如一个字符设备,我们还需填写相应的文件操作接口 ,下面我们从外到里进行剖析,学习如何搭建这样的一个USB驱动外壳框架:


    一、注册USB驱动程序

      Linux的设备驱动,特别是这种hotplug的USB设备驱动,会被编译成模块,然后在需要时挂在到内核。所以USB驱动和注册与正常的模块注册、卸载是一样的,下面是USB驱动的注册与卸载:
    static int __init usb_skel_init(void)   
    {   
         int result;   
         /* register this driver with the USB subsystem */   
         result = usb_register(&skel_driver);   
         if (result)   
             err("usb_register failed. Error number %d", result);   
      
         return result;   
    }   
      
    static void __exit usb_skel_exit(void)   
    {   
         /* deregister this driver with the USB subsystem */   
         usb_deregister(&skel_driver);   
    }   
      
    module_init (usb_skel_init);   
    module_exit (usb_skel_exit);   
    MODULE_LICENSE("GPL");

          USB设备驱动的模块加载函数通用的方法是在I2C设备驱动的模块加载函数中使用usb_register(struct *usb_driver)函数添加usb_driver的工作,而在模块卸载函数中利用usb_deregister(struct *usb_driver)做相反的工作。 对比I2C设备驱动中的 i2c_add_driver(&i2c_driver)i2c_del_driver(&i2c_driver)

        struct usb_driver是USB设备驱动,我们需要实现其成员函数:

    static struct usb_driver skel_driver = {   
         .owner = THIS_MODULE,    
         .name = "skeleton",  
         .id_table = skel_table,       
         .probe = skel_probe,     
         .disconnect = skel_disconnect,     
    };    
    从代码看来,usb_driver需要初始化五个字段:

    模块的所有者 THIS_MODULE
    模块的名字  skeleton
    probe函数   skel_probe
    disconnect函数skel_disconnect

    id_table

        最重要的当然是probe函数与disconnect函数,这个在后面详细介绍,先谈一下id_table:

        id_table 是struct usb_device_id 类型,包含了一列该驱动程序可以支持的所有不同类型的USB设备。如果没有设置该变量,USB驱动程序中的探测回调该函数将不会被调用。对比I2C中struct i2c_device_id *id_table,一个驱动程序可以对应多个设备,i2c 示例:

    static const struct i2c_device_id mpu6050_id[] = {    
        { "mpu6050", 0},    
        {}    
    };  

        usb子系统通过设备的production ID和vendor ID的组合或者设备的class、subclass跟protocol的组合来识别设备,并调用相关的驱动程序作处理。我们可以看看这个id_table到底是什么东西:

    static struct usb_device_id skel_table [] = {     
         { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },     
         { }                    /* Terminating entry */     
    };     
        
    MODULE_DEVICE_TABLE (usb, skel_table);   

       MODULE_DEVICE_TABLE的第一个参数是 设备的类型,如果是USB设备,那自然是usb。后面一个参数是 设备表这个设备表的最后一个元素是空的,用于标识结束。代码定义了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是说,当有一个设备接到集线器时,usb子系统就会检查这个设备的vendor ID和product ID,如果它们的值是0xfff0时,那么子系统就会调用这个skeleton模块作为设备的驱动。

       当USB设备接到USB控制器接口时,usb_core就检测该设备的一些信息,例如生产厂商ID和产品的ID,或者是设备所属的class、subclass跟protocol,以便确定应该调用哪一个驱动处理该设备
      
     

         我们下面所要做的就是对probe函数与disconnect函数的填充了,但是在对probe函数与disconnect函数填充之前,有必要先学习三个重要的数据结构,这在我们后面probe函数与disconnect函数中有很大的作用:

    二、USB驱动程序中重要数据结构

    1、usb-skeleton

           usb-skeleton 是一个局部结构体,用于与端点进行通信。下面先看一下Linux内核源码中的一个usb-skeleton(就是usb驱动的骨架咯),其定义的设备结构体就叫做usb-skel:

    struct usb_skel {   
         struct usb_device *udev;                 /* the usb device for this device */   
         struct usb_interface  *interface;            /* the interface for this device */   
         struct semaphore limit_sem;         /* limiting the number of writes in progress */   
         unsigned char *bulk_in_buffer;     /* the buffer to receive data */   
         size_t         bulk_in_size;                  /* the size of the receive buffer */   
         __u8          bulk_in_endpointAddr;        /* the address of the bulk in endpoint */   
         __u8          bulk_out_endpointAddr;      /* the address of the bulk out endpoint */   
         struct kref   kref;   
    };  

    他拥有:

    描述usb设备的结构体udev
    一个接口interface
    用于并发访问控制的semaphore(信号量) limit_sem
    用于接收数据的缓冲bulk_in_buffer
    用于接收数据的缓冲尺寸bulk_in_size
    批量输入端口地址bulk_in_endpointAddr
    批量输出端口地址bulk_out_endpointAddr

    内核使用的引用计数器


         从开发人员的角度看,每一个usb设备有若干个配置(configuration)组成,每个配置又可以有多个接口(interface)(我理解就是USB设备的一项功能),每个接口又有多个设置,而接口本身可能没有端点或者多个端点(end point)

    2、USB 接口数据结构 struct usb_interface

    struct usb_interface  
    {           
             struct usb_host_interface *altsetting;   
             struct usb_host_interface *cur_altsetting;        
             unsigned num_altsetting;           
             int minor;                        
             enum usb_interface_condition condition;           
             unsigned is_active:1;             
             unsigned needs_remote_wakeup:1;    
             struct device dev;                
             struct device *usb_dev;           
             int pm_usage_cnt;                 
    };  

           在逻辑上,一个USB设备的功能划分是通过接口来完成的。比如说一个USB扬声器,可能会包括有两个接口:一个用于键盘控制,另外一个用于音频流传输。而事实上,这种设备需要用到不同的两个驱动程序来操作,一个控制键盘,一个控制音频流。但也有例外,比如蓝牙设备,要求有两个接口,第一用于ACL跟EVENT的传输,另外一个用于SCO链路,但两者通过一个驱动控制。在Linux上,接口使用struct usb_interface来描述,以下是该结构体中比较重要的字段:

    a -- struct usb_host_interface *altsetting(注意不是usb_interface)

           其实据我理解,他应该是每个接口的设置,虽然名字上有点奇怪。该字段是一个设置的数组(一个接口可以有多个设置),每个usb_host_interface都包含一套由struct usb_host_endpoint定义的端点配置。但这些配置次序是不定的。

    b -- struct usb_host_interface *cur_altsetting

           当前活动的设置,指向altsetting数组中的一个

    struct usb_host_interface数据结构:

    struct usb_host_interface   
    {  
             struct usb_interface_descriptor desc;//usb描述符,主要有四种usb描述符,设备描述符,配置描述符,接口描述符和端点描述符,协议里规定一个usb设备是必须支持这四大描述符的。  
                                     //usb描述符放在usb设备的eeprom里边  
             /* array of desc.bNumEndpoint endpoints associated with this 
              * interface setting. these will be in no particular order. 
              */  
             struct usb_host_endpoint *endpoint;//这个设置所使用的端点  
      
             char *string;           /* iInterface string, if present */  
             unsigned char *extra;   /* Extra descriptors */关于额外描述符  
             int extralen;  
    };  

    c -- unsigned num_altstting

          可选设置的数量,即altsetting所指数组的元素个数

    d -- int minor

         当捆绑到该接口的USB驱动程序使用USB主设备号时,USB core分配的次设备号。仅在成功调用usb_register_dev之后才有效。  


    3、USB 端点 struct usb_host_endpoint

          Linux中用struct usb_host_endpoint 来描述USB端点

    struct usb_host_endpoint   
    {  
             struct usb_endpoint_descriptor desc;  
             struct list_head                urb_list;//端点要处理的urb队列.urb是usb通信的主角,设备中的每个端点都可以处理一个urb队列.要想和你的usb通信,就得创建一个urb,并且为它赋好值,  
                                       //交给咱们的usb core,它会找到合适的host controller,从而进行具体的数据传输  
             void                            *hcpriv;//这是提供给HCD(host controller driver)用的  
             struct ep_device                *ep_dev;        /* For sysfs info */  
      
             unsigned char *extra;   /* Extra descriptors */  
             int extralen;  
    };  

         每个usb_host_endpoint中包含一个struct usb_endpoint_descriptor结构体,当中包含该端点的信息以及设备自定义的各种信息,这些信息包括:

    a -- bEndpointAddress(b for byte)

           8位端点地址,其地址还隐藏了端点方向的信息(之前说过,端点是单向的),可以用掩码USB_DIR_OUT和USB_DIR_IN来确定。

    b -- bmAttributes

           端点的类型,结合USB_ENDPOINT_XFERTYPE_MASK可以确定端点是USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XFER_BULK(批量)还是USB_ENDPOINT_XFER_INT(中断)。

    c -- wMaxPacketSize

           端点一次处理的最大字节数。发送的BULK包可以大于这个数值,但会被分割传送。

    d -- bInterval

           如果端点是中断类型,该值是端点的间隔设置,以毫秒为单位


    三、探测和断开函数分析

            USB驱动程序指定了两个USB核心在适当时间调用的函数。

    1、探测函数

            当一个设备被安装而USB核心认为该驱动程序应该处理时,探测函数被调用;

            探测函数应该检查传递给他的设备信息确定驱动程序是否真的适合该设备。当驱动程序因为某种原因不应控制设备时,断开函数被调用,它可以做一些清洁的工作。

          系统会传递给探测函数的信息是什么呢?一个usb_interface * 跟一个struct usb_device_id *作为参数。他们分别是该USB设备的接口描述(一般会是该设备的第0号接口,该接口的默认设置也是第0号设置)跟它的设备ID描述(包括Vendor ID、Production ID等)。

           USB驱动程序应该初始化任何可能用于控制USB设备的局部结构体,它还应该把所需的任何设备相关信息保存到局部结构体中。例如,USB驱动程序通常需要探测设备对的端点地址和缓冲区大小,因为需要他们才能和端点通信

           下面具体分析探测函数做了哪些事情:

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

           下面是一个实例代码,他们探测批量类型的IN和OUT端点,把相关信息保存到一个局部设备结构体中

    /* 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) {   
             endpoint = &iface_desc->endpoint[i].desc; 
      
             if ( !dev->bulk_in_endpointAddr &&   
                    ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&   
                 ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {   
                 /* we found a bulk in endpoint */   
                  buffer_size = le16_to_cpu(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_ENDPOINT_DIR_MASK)= =USB_DIR_OUT) &&   
                   ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)= = USB_ENDPOINT_XFER_BULK)) {   
                  /* we found a bulk out endpoint */   
                  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_in_endpointAddr &&   
                    ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&   
                 ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {   

           如果这些都通过了,驱动程序就知道它已经发现了正确的端点类型,可以把该端点相关的信息保存到一个局部结构体中,就是我们前面的usb_skel ,以便稍后使用它和端点进行通信

    /* we found a bulk in endpoint */   
                  buffer_size = le16_to_cpu(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;               
    	      }   


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

          接下来的工作是向系统注册一些以后会用的的信息。首先我们来说明一下usb_set_intfdata()他向内核注册一个data,这个data的结构可以是任意的,这段程序向内核注册了一个usb_skel结构,就是我们刚刚看到的被初始化的那个,这个data可以在以后用usb_get_intfdata来得到

    usb_set_intfdata(interface, dev);


    c -- 注册USB设备

           如果USB驱动程序没有和处理设备与用户交互(例如输入、tty、视频等)的另一种类型的子系统相关联,驱动程序可以使用USB主设备号,以便在用户空间使用传统的字符驱动程序接口。如果要这样做,USB驱动程序必须在探测函数中调用 usb_resgister_dev 函数来把设备注册到USB核心。只要该函数被调用,就要确保设备和驱动陈旭都处于可以处理用户访问设备的要求的恰当状态

    retval = usb_register_dev(interface, &skel_class);

    skel_class结构。这个结构又是什么?我们就来看看这到底是个什么东西:

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

        它其实是一个系统定义的结构,里面包含了一名字、一个文件操作结构体还有一个次设备号的基准值。事实上它才是定义真正完成对设备IO操作的函数。所以他的核心内容应该是skel_fops。

       因为usb设备可以有多个interface,每个interface所定义的IO操作可能不一样,所以向系统注册的usb_class_driver要求注册到某一个interface,而不是device,因此,usb_register_dev的第一个参数才是interface,而第二个参数就是某一个usb_class_driver。

       通常情况下,linux系统用主设备号来识别某类设备的驱动程序,用次设备号管理识别具体的设备,驱动程序可以依照次设备号来区分不同的设备,所以,这里的次设备好其实是用来管理不同的interface的,但由于这个范例只有一个interface,在代码上无法求证这个猜想。

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


    2、断开函数

          当设备被拔出集线器时,usb子系统会自动地调用disconnect,他做的事情不多,最重要的是注销class_driver(交还次设备号)和interface的data:

    dev = usb_get_intfdata(interface);  
    usb_set_intfdata(interface, NULL);  
      
    /* give back our minor */  
    usb_deregister_dev(interface, &skel_class);  


    四、USB请求块

           USB 设备驱动代码通过urb和所有的 USB 设备通讯。urb用 struct urb 结构描述(include/linux/usb.h )。

           urb 以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个 USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个 urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。

     一个 urb 的典型生命循环如下:

     (1)被创建;
     (2)被分配给一个特定 USB 设备的特定端点;
     (3)被提交给 USB 核心;
     (4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动;
     (5)被 USB 主机控制器驱动处理, 并传送到设备;
     (6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。

     
       urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。

    struct urb
    {
    	/* 私有的:只能由usb核心和主机控制器访问的字段 */
    	struct kref kref; /*urb引用计数 */
    	spinlock_t lock; /* urb锁 */
    	void *hcpriv; /* 主机控制器私有数据 */
    	int bandwidth; /* int/iso请求的带宽 */
    	atomic_t use_count; /* 并发传输计数 */
    	u8 reject; /* 传输将失败*/
    	
    	/* 公共的: 可以被驱动使用的字段 */
    	struct list_head urb_list; /* 链表头*/
    	struct usb_device *dev; /* 关联的usb设备 */
    	unsigned int pipe; /* 管道信息 */
    	int status; /* urb的当前状态 */
    	unsigned int transfer_flags; /* urb_short_not_ok | ...*/
    	void *transfer_buffer; /* 发送数据到设备或从设备接收数据的缓冲区 */
    	dma_addr_t transfer_dma; /*用来以dma方式向设备传输数据的缓冲区 */
    	int transfer_buffer_length;/*transfer_buffer或transfer_dma 指向缓冲区的大小 */
    	                     
    	int actual_length; /* urb结束后,发送或接收数据的实际长度 */
    	unsigned char *setup_packet; /* 指向控制urb的设置数据包的指针*/
    	dma_addr_t setup_dma; /*控制urb的设置数据包的dma缓冲区*/
    	int start_frame; /*等时传输中用于设置或返回初始帧*/
    	int number_of_packets; /*等时传输中等时缓冲区数据 */
    	int interval; /* urb被轮询到的时间间隔(对中断和等时urb有效) */
    	int error_count;  /* 等时传输错误数量 */
    	void *context; /* completion函数上下文 */
    	usb_complete_t complete; /* 当urb被完全传输或发生错误时,被调用 */
    	struct usb_iso_packet_descriptor iso_frame_desc[0];
    	/*单个urb一次可定义多个等时传输时,描述各个等时传输 */
    };


    1、创建和注销 urb

          struct urb 结构不能静态创建,必须使用 usb_alloc_urb 函数创建. 函数原型:

    struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
    //int iso_packets : urb 包含等时数据包的数目。如果不使用等时urb,则为0
    //gfp_t mem_flags : 与传递给 kmalloc 函数调用来从内核分配内存的标志类型相同

    //返回值          : 如果成功分配足够内存给 urb , 返回值为指向 urb 的指针. 如果返回值是 NULL, 则在 USB 核心中发生了错误, 且驱动需要进行适当清理


    如果驱动已经对 urb 使用完毕, 必须调用 usb_free_urb 函数,释放urb。函数原型:

    void usb_free_urb(struct urb *urb);
    //struct urb *urb : 要释放的 struct urb 指针


    2、初始化 urb

    static inline void usb_fill_int_urb(struct urb *urb,                                                                                                       
                     struct usb_device *dev,
                     unsigned int pipe,
                     void *transfer_buffer,
                     int buffer_length,
                     usb_complete_t complete_fn,
                     void *context,
                     int interval);
    
    static inline 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_fn,
                     void *context);
    
    static inline 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_fn,
                        void *context);
    
    
    //struct urb *urb :指向要被初始化的 urb 的指针
    //struct usb_device *dev :指向 urb 要发送到的 USB 设备.
    //unsigned int pipe : urb 要被发送到的 USB 设备的特定端点. 必须使用前面提过的 usb_******pipe 函数创建
    //void *transfer_buffer :指向外发数据或接收数据的缓冲区的指针.注意:不能是静态缓冲,必须使用 kmalloc 来创建.
    //int buffer_length :transfer_buffer 指针指向的缓冲区的大小
    //usb_complete_t complete :指向 urb 结束处理例程函数指针
    //void *context :指向一个小数据块的指针, 被添加到 urb 结构中,以便被结束处理例程函数获取使用.
    //int interval :中断 urb 被调度的间隔.
    //函数不设置 urb 中的 transfer_flags 变量, 因此对这个成员的修改必须由驱动手动完成
    
    /*等时 urb 没有初始化函数,必须手动初始化,以下为一个例子*/
    urb->dev = dev;
    urb->context = uvd;
    urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1);
    urb->interval = 1;
    urb->transfer_flags = URB_ISO_ASAP;
    urb->transfer_buffer = cam->sts_buf[i];
    urb->complete = konicawc_isoc_irq;
    urb->number_of_packets = FRAMES_PER_DESC;
    urb->transfer_buffer_length = FRAMES_PER_DESC;
    for (j=0; j < FRAMES_PER_DESC; j++) {
            urb->iso_frame_desc[j].offset = j;
            urb->iso_frame_desc[j].length = 1;
    }


    3、提交 urb

          一旦 urb 被正确地创建并初始化, 它就可以提交给 USB 核心以发送出到 USB 设备. 这通过调用函数 usb_submit_urb 实现:

    int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
    //struct urb *urb :指向被提交的 urb 的指针 
    //gfp_t mem_flags :使用传递给 kmalloc 调用同样的参数, 用来告诉 USB 核心如何及时分配内存缓冲

    /*因为函数 usb_submit_urb 可被在任何时候被调用(包括从一个中断上下文), mem_flags 变量必须正确设置. 根据 usb_submit_urb 被调用的时间,只有 3 个有效值可用:
    GFP_ATOMIC 
    只要满足以下条件,就应当使用此值:
    1.调用者处于一个 urb 结束处理例程,中断处理例程,底半部,tasklet或者一个定时器回调函数.
    2.调用者持有自旋锁或者读写锁. 注意如果正持有一个信号量, 这个值不必要.
    3.current->state 不是 TASK_RUNNING. 除非驱动已自己改变 current 状态,否则状态应该一直是 TASK_RUNNING .

    GFP_NOIO 
    驱动处于块 I/O 处理过程中. 它还应当用在所有的存储类型的错误处理过程中.

    GFP_KERNEL 
    所有不属于之前提到的其他情况
    */


    在 urb 被成功提交给 USB 核心之后, 直到结束处理例程函数被调用前,都不能访问 urb 结构的任何成员.


    4、urb结束处理例程

          如果 usb_submit_urb 被成功调用, 并把对 urb 的控制权传递给 USB 核心, 函数返回 0; 否则返回一个负的错误代码. 如果函数调用成功, 当 urb 被结束的时候结束处理例程会被调用一次.当这个函数被调用时, USB 核心就完成了这个urb, 并将它的控制权返回给设备驱动.

    只有 3 种结束urb并调用结束处理例程的情况:

    (1)urb 被成功发送给设备, 且设备返回正确的确认.如果这样, urb 中的status变量被设置为 0.
    (2)发生错误, 错误值记录在 urb 结构中的 status 变量.
    (3)urb 从 USB 核心unlink. 这发生在要么当驱动通过调用 usb_unlink_urb 或者 usb_kill_urb告知 USB 核心取消一个已提交的 urb,或者在一个 urb 已经被提交给它时设备从系统中去除.

    5、取消 urb

    使用以下函数停止一个已经提交给 USB 核心的 urb:

    void usb_kill_urb(struct urb *urb)
    int usb_unlink_urb(struct urb *urb);

    如果调用usb_kill_urb函数,则 urb 的生命周期将被终止. 这通常在设备从系统移除时,在断开回调函数(disconnect callback)中调用.

    对一些驱动, 应当调用 usb_unlink_urb 函数来使 USB 核心停止 urb. 这个函数不会等待 urb 完全停止才返回. 这对于在中断处理例程中或者持有一个自旋锁时去停止 urb 是很有用的, 因为等待一个 urb 完全停止需要 USB 核心有使调用进程休眠的能力(wait_event()函数).



    展开全文
  • 1、故障现象 在给单位的一台Server2008R2 X64服务器上安装扫描枪的时候发现无法安装扫描枪驱动,打开设备管理器可以看到扫描枪硬件有黄色叹号标记。...如果以错误提示或者错误代码为关键字上网搜索...

    1、故障现象

           在给单位的一台Server2008R2 X64服务器上安装扫描枪的时候发现无法安装扫描枪驱动,打开设备管理器可以看到扫描枪硬件有黄色叹号标记。错误信息如下:

            Windows给出的错误提示很简单,“该设备的驱动程序未被安装。(代码 28)”,就是安装驱动程序有错,但是具体是什么错误,我们从这里是无法得到的。如果以错误提示或者错误代码为关键字上网搜索的话,相信我你一定得不到太多有用的信息。因为之前另外一台win7x64上安装这个扫描枪驱动是没问题的,所以我知道问题不在驱动本身或者硬件设备上。一开始以为是操作系统版本问题,但08r2和win7本来就是差不多的,绕了一些弯路后,还是决定从错误的根源上找原因。

     2、问题分析

          上网搜索后,知道驱动安装会生成系统日志保持在C:\Windows\inf\setupapi.dev.log\setupapi.dev.log中。打开这个日志找到相关日志信息再来分析问题就非常简单了。这是一个非常值得学习的小技巧,log日志远比图形界面提供的错误信息完整并且更有指导意义。如下是setupapi.dev.log中驱动安装时的相关错误信息节选:

         inf:           Opened INF: 'c:\windows\temp\dmiwu\{de4ae465-6949-463b-9822-287a65fb2b68}\nls_vcp_driver.inf' ([strings])
    !    inf:           Could not find include INF file "layout.inf". Error = 0x00000002
    !    inf:           Unable to load INF: 'C:\Windows\System32\DriverStore\FileRepository\mdmcpq.inf_amd64_neutral_b53453733bd795bc\mdmcpq.inf'(00000003)
    !    inf:           Error 3: The system cannot find the path specified.
    !    inf:           Could not find include INF file "mdmcpq.inf". Error = 0x00000003

           相比设备管理器提供的错误信息,这里的日志足够清楚了,安装驱动的时候因为打不开mdmcpq.inf文件所以驱动安装无法继续报错了。上面一行的layout.inf文件经过分析上下文是不影响安装进行的,重要的还是缺少mdmcpq这个东西。找到驱动的安装目录,在这个扫描枪驱动的inf文件中有如下行:

    [VCP_DriverInstall.NT]
    Include=mdmcpq.inf
    CopyFiles=FakeModemCopyFileSection
    AddReg=VCP_DriverInstall.NT.AddReg

    扫描枪的驱动在安装过程中会用到mdmcpq.inf文件,但是安装时候找不到这个文件,所以出错了。

    3、解决方法

           原因找到了缺少依赖驱动,修复就行了,上网直接搜吧,很容易找到一堆关于mdmcpq缺失的问题。同时也会搜到一些无效的方法,比如复制原版的mdmcpq.inf 和 usbser.sys到system32的inf和drivers目录。

           但是从日志很明显知道驱动查找的是C:\Windows\System32\DriverStore\FileRepository\目录。所以最好的方法还是从正常的系统中直接复制C:\Windows\System32\DriverStore\FileRepository\mdmcpq.inf_amd64_neutral_b53453733bd795bc目录到故障系统的同位置目录下,如果复制时提示文件夹没有访问权限,给FileRepository目录增加用户权限就行了。

    我这里出现故障的系统版本是非原版系统,作者也是小有名气的。但在修改系统过程中难免出现一些隐藏的问题,这种隐藏比较深的故障,对于普通用户来说确实是很难解决的问题。这里非常值得注意的是驱动安装日志用于分析问题的方法,图形化界面虽然友好,但在分析故障原因上有时候的提示太过简化反而变得毫无意义。

     

     

     

    展开全文
  • 本节讨论使用DSF设备模拟器测试USB驱动程序的技术。 本节包括: 创建一个模拟USB 2.0 EHCI控制器 在模拟USB 2.0控制器中插入设备 模拟设备PNP枚举 用设备模拟器协调I/O 在计算机上重启模拟设备 使用设备仿真...

    本节讨论使用DSF设备模拟器测试USB驱动程序的技术。

    本节包括:

    创建一个模拟USB 2.0 EHCI控制器

    在模拟USB 2.0控制器中插入设备

    模拟设备PNP枚举

    用设备模拟器协调I/O

    在计算机上重启模拟设备

    使用设备仿真的测试策略


    创建一个模拟USB 2.0 EHCI控制器

    在将模拟设备插入模拟USB 2.0控制器之前,必须在目标系统上创建和枚举控制器。若要创建并枚举控制器,请运行DSF运行时在目标程序系统中安装的Softehcicfg.exe应用程序,该程序文件在\Program Files\dsf\softehci 文件夹中。

    若要安装控制器,请打开命令窗口并运行 softehcicfg /install。这个命令将向DSF根枚举器驱动程序(Dsfroot.sys)发送一条消息,并告诉它枚举模拟EHCI控制器。您必须等待控制器枚举,然后才能将模拟设备插入其中。您可以查看是否通过检查设备管理器和查找“Microsoft USB 2.0 Host Controller Simulator”条目来枚举控制器,或者使用诸如微软 Windows Device Testing Framework (WDTF)之类的工具以编程方式枚举控制器。

    注意,控制器的设备ID是"PCI\VEN_1414&DEV_0004&SUBSYS_00041414&REV_00"。这个ID是微软的一个被正式分配的PCI设备ID,它不应该与任何其他现有的PCI设备冲突。

    您还可以通过使用Softehcicfg.exe意外地从目标系统中移除模拟的EHCI控制器。若要移除模拟的EHCI控制器,请打开命令提示符窗口并运行softehcicfg /remove

    您还可以枚举多个同时模拟的EHCI控制器,将实例参数添加到 softehcicfg /install。例如,您可以运行softehcicfg /install 0,然后运行softehcicfg /install 1

    控制器将由即插即用(PnP)管理器分配唯一的设备实例ID,这些ID以实例号结束,实例号表示为最多三个前导零。使用上面的示例,控制器可以被分配以下设备实例ID:

    Instance 0:
    PCI\VEN_1414&DEV_0004&SUBSYS_00041414&REV_00\1&1222FD94&9&0000
    
    Instance 1:
    PCI\VEN_1414&DEV_0004&SUBSYS_00041414&REV_00\1&1222FD94&9&0001

    若要删除多个实例,请将实例参数追加到softehcicfg /remove。若要移除在较早示例中添加的两个控制器,请运行softehcicfg /remove 0,然后运行softehcicfg /remove 1。 

    原文链接:https://docs.microsoft.com/zh-cn/previous-versions/windows/hardware/dsf/ff538281%28v%3dvs.85%29


    在模拟USB 2.0控制器中插入设备

    有两种方法将设备直接插入模拟USB 2.0控制器:

    调用IDSF::HotPlug,传递设备的DSFDevice对象(IDSFDevice)。

    调用SoftEHCIRootHubPort::HotPlug, 传递设备的SoftUSBDevice 对象(ISoftUSBDevice)。

    通常,测试应用程序将把设备模拟器插入控制器,但是模拟器可以选择性地设计成将自己插入控制器。如果测试应用程序插入设备,则测试应用程序将需要访问其SoftUSBDevice 对象或其DSFDevice对象。每个对象都可以从另一个对象访问。考虑下面的VBScript代码示例。

    Set DSFDevice = SoftUSBDevice.DSFDevice
    
    const IID_ISoftUSBDevice = "{9AC61697-81AE-459A-8629-BF5D5A838519}"
    Set SoftUSBDevice = DSFDevice.Object(IID_ISoftUSBDevice)

    如果模拟设备被实现为COM对象,那么DSFDevice 对象或SoftUSBDevice 对象可以作为属性公开。例如, DSF USB Loopback Device Simulation通过LoopbackDevice::DSFDevice 属性公开DSF设备对象。 

    IDSF::HotPlug方法采用两个参数:总线的名称和模拟设备的DSFDevice对象。EHCI控制器模拟器的总线名称是“USB2.0”。HotPlug返回一个DSFBus 对象(IDSFBus),您可以稍后使用它来拔出设备。(例如,从\Program Files\dsf\USBLoopback文件夹中与DSF运行时一起安装的RunLoopbackSample.wsf loopback示例脚本中),考虑以下代码示例:

    Dim LoopbackDev    : Set LoopbackDev = WScript.CreateObject("SoftUSBLoopback.LoopbackDevice", "LoopbackEvent_")
    Dim LoopbackDSFDev : Set LoopbackDSFDev = LoopbackDev.DSFDevice
    Dim DSF : Set DSF = CreateObject("DSF.DSF")
    Dim Bus : Set Bus = DSF.HotPlug(LoopbackDSFDev, "USB2.0")
    
    ... use the loopback device ...
    Bus.UnPlug LoopbackDSFDev

    为了使用SoftEHCIRootHubPort::HotPlug,你必须访问EHCI控制器模拟器。可以通过为控制器搜索IDSF::Devices 属性来访问此模拟器,如EnumSimulatedDevices例程中的以下示例脚本所示。 

    '/
    ' Function EnumSimulatedDevices
    '
    ' This function searches the collection of simulated devices
    ' referenced by DSF.Devices for a device that exposes an ancillary
    ' object from DSFDevice.Object with the specified GUID. If found it returns the
    ' DSFDevice object otherwise it returns Nothing.
    '/
    const IID_IDSFBus = "{E927C266-5364-449E-AE52-D6A782AFDA9C}"
    Dim CtrlrDev : Set CtrlrDev = EnumSimulatedDevices(IID_IDSFBus)
    ...
    Private Function EnumSimulatedDevices(SearchObjectGUID)
    
        Dim DevSought : Set DevSought = Nothing
        Dim Dev       : Set Dev = Nothing
        Dim DSF       : Set DSF = CreateObject("DSF.DSF")
        Dim ObjSought : Set ObjSought = Nothing
    
        For Each Dev in DSF.Devices
            If Dev.HasObject(SearchObjectGUID) Then
                Set ObjSought = Dev.Object(SearchObjectGUID)
                If Not ObjSought Is Nothing Then
                    Set DevSought = Dev
      Exit For
                End If
            End If
        Next
    
        Set EnumSimulatedDevices = DevSought
    
    End Function

    如果使用多个模拟EHCI控制器并且需要根据设备实例ID查找特定控制器,则可以修改前面的EnumSimulatedDevices示例函数,以通过检查循环中每个设备的InstanceID 属性来检查设备实例ID,如下面是代码示例。

          For Each Dev in DSF.Devices
            If Dev.HasObject(SearchObjectGUID) And (Dev.InstanceID = SearchID) Then
                Set ObjSought = Dev.Object(SearchObjectGUID)
                If Not ObjSought Is Nothing Then
                    Set DevSought = Dev
      Exit For
                End If
            End If
        Next

    根集线器端口对象可从EHCI控制器的端口集合获得。下面的代码示例演示如何获取根集线器端口1的对象。

    Dim CtrlrObj : Set CtrlrObj = CtrlrDev.Object(IID_EHCICtrlrObj)
    Dim RootHubPorts : Set RootHubPorts = CtrlrObj.Ports
    Dim RootHubPort1 : Set RootHubPort1 = RootHubPorts(1)

     第一行从控制器的DSFDevice 对象中提取SoftEHCICtrlr 对象。第二行获取根集线器端口集合,第三行获取端口1的SoftEHCIRootHubPort 对象。

    最后一步是使用设备模拟器的SoftUSBDevice 对象将设备模拟器插入根集线器端口。例如,考虑 loopback设备的以下代码示例。

    const IID_ISoftUSBDevice = "{9AC61697-81AE-459A-8629-BF5D5A838519}"
    RootHubPort1.HotPlug LoopbackDevice.DSFDevice.Object(IID_ISoftUSBDevice)

     要删除设备,运行RootHubPort1.Unplug。

    如果调用SoftEHCIRootHubPort::Unplug 或者 IDSFBus::UnPlug,目标系统将该调用视为意外移除,并且在功能上等同于从根集线器端口移除USB电缆而不首先在软件中启动移除(例如,通过使用安全移除硬件图标)。

    原文链接:https://docs.microsoft.com/zh-cn/previous-versions/windows/hardware/dsf/ff542263(v%3dvs.85)


    模拟设备PNP枚举

    从目标系统的角度来看,模拟USB设备的枚举与真实USB设备的枚举实际上没有什么不同。您可以在设备管理器中看到设备,还可以通过使用工具(如微软 Windows Device Testing Framework (WDTF)或 SetupDiXxx API)以编程方式发现设备。

    在Windows Vista的Windows Driver Kit(WDK)中包含的DSF版本(版本6000)中,在插入第二设备之前,必须等待枚举一个设备。类似地,当您拔出模拟设备时,在移除或插入附加设备之前,必须完成设备移除。

    从WDK for Windows Server 2008(版本6001)中包含的DSF版本开始,可以使用外部集线器模拟器同时枚举多个设备。类似地,可以通过拔出父外部集线器模拟器来拔除多个设备。

    原文链接:https://docs.microsoft.com/zh-cn/previous-versions/windows/hardware/dsf/ff542301(v%3dvs.85)


    用设备模拟器协调I/O

    使用设备模拟器测试驱动程序通常需要使用设备启动I/O,同时在模拟器中运行操作以满足那些I/O请求。测试用例可以是肯定的(期望I/O成功完成)或否定的(期望I/O以特定错误完成)。

    如果测试包括设备的多个实例,则必须将设备模拟器的实例与即插即用(PnP)设备实例匹配。目前,DSF中没有能够将模拟的USB设备与给定设备实例ID积极匹配的工具。

    枚举设备的实例(例如,通过使用微软Windows Device Testing Framework [WDTF]),插入模拟设备,然后重新枚举以找到设备的其他实例。

    通过使用RegisterDeviceNotification 来接收设备到达通知,并在插入模拟设备之后等待通知,从而处理WM_DEVICECHANGE消息或注册设备通知。有关WM_DEVICECHANGE RegisterDeviceNotification的更多信息,请参见MSDN Library 网站。

    在您已经明确地标识了设备并且可以对其发起I/O之后,您必须确保设备模拟器相应地响应I/O请求。例如,考虑DSF USB Loopback Device Simulation,它具有一个具有一个批量IN端点和一个批量OUT端点的单个接口。Rwbulk.exe(您可以在\src\usb\bulkusb\exe文件夹中的WDK安装中找到)调用Microsoft Win32 API函数CreateFile 以打开bulkusb 驱动程序(在\src\usb\bulkusb\sys文件夹中)公开的设备接口,然后调用Win32 API函数ReadFile 和 WriteFile发送I/O请求。WriteFile 调用使控制器向大容量端点发送数据传输。loopback 设备通过将接收到的数据排队到批量IN端点,在CLoopbackDevice::OnWriteTransfer函数(在\src\Test\DSF\USB\SoftUSBLoopback文件夹中)中处理这个I/O事件。来自Rwbulk.exe的后续ReadFile调用导致控制器请求来自大容量IN端点的数据传输,然后大容量IN端点使用排队的数据。

    您可以设计一个确定的肯定测试用例,以便loopback 设备向测试应用程序中触发一个事件,以指示收到批量OUT传输。测试应用程序可以调用WriteFile,等待确认收到批量OUT传输的事件,然后等待WriteFile调用完成。这个过程需要两个线程或使用异步重叠I/O,因为WriteFile调用将被阻塞,直到I/O完成。

    您可以设计一个否定的测试用例,以便对loopback设备进行编程,以在下一个事务上从批量OUT端点返回事务错误。您可以通过向回送设备添加属性来完成这种情况(例如,LoopbackDevice.ErrOnNextBulkOUT = True)。

    loopback设备可以在每次调用CLoopbackDevice::OnWriteTransfer开始时检查该属性,如果属性为真,则返回USB_ERR作为事务状态,并将属性重置为false。在设置此属性之后,测试应用程序可以调用WriteFile ,然后检查它是否返回相应的错误。

    原文链接:https://docs.microsoft.com/zh-cn/previous-versions/windows/hardware/dsf/ff538279(v%3dvs.85)


    在计算机上重启模拟设备

    模拟USB 2.0控制器在重启、关机、休眠和挂起操作之间被持久化,直到通过运行softehcicfg /remove被移除。

    模拟的外部集线器和模拟的USB设备不会在DSF的当前版本中重新启动。

    外部集线器和设备在关机、休眠、暂停时不会被删除,但是,在计算机重新启动时也不会出现。这种情况类似于在系统关闭或暂停USB电缆时物理删除连接到控制器的装置。

    原文链接:https://docs.microsoft.com/zh-cn/previous-versions/windows/hardware/dsf/ff542262(v%3dvs.85)


    使用设备仿真的测试策略

    设备模拟提供了新的机会,以提高您的驱动程序测试策略,是不可用的真实硬件。最明显的好处是您必须手动完成测试的自动化。总有一些测试用例必须手动运行,例如必须知道能够100%工作的通用用户集成场景和涉及无法模拟的潜在电气和时序问题的场景。

    仿真提供了机会来达到代码路径和生成数据值,这在真实硬件不可能做到的。您应该彻底检查您的驱动程序和设备应用程序代码,以确定如何使用仿真覆盖这些情况。

    仿真还允许在驱动程序开发过程中修改,这在真实硬件不可能做到的。在开发过程中,应考虑以下事项以加强过程:

    • 创建一个自动化的构建验证测试(BVT)。
    • 创建一个可以在虚拟机上运行的自动化BVT。使用微软虚拟服务器实现对虚拟机的自动化控制。
    • 创建一个自动回归套件,可以在不到一天的时间内运行。
    • 创建一个较小的自动化回归套件,开发人员可以在他们的计算机或笔记本电脑上运行。
    • 创建一个自动化的压力套件,包括即插即用(PNP)和I/O测试。
    • 使用仿真作为新的驱动程序开发人员的学习工具。
    • 在将硬件提交到硬件原型之前,使用模拟来测试潜在的硬件更改。

    原文链接:https://docs.microsoft.com/zh-cn/previous-versions/windows/hardware/dsf/ff542527(v%3dvs.85)

     

    展开全文
  • USB-Blaster使用的时候会自动安装驱动程序,但是有时候会出现这样一种情况: Altera USB-Blaster驱动程序那里总是显示有黄色感叹号。 按照以往的经验,这种情况一般就是驱动程序有误,重新安装一遍即可,Quartus...
  • USB驱动程序之鼠标用做键盘

    千次阅读 2017-03-20 23:14:24
    我们还是接着来看看我们的例子程序 usbmouse.c 这里它接着判断了他是不是鼠标, 得到它的usb_host_interface,interface=intf->cur_alsetting就是当前接口的设置
  • 参考:https://www.cnblogs.com/benbenfishfish/p/8663434.html
  • 今天记录我在I.MX6Q平台移植RT5370无线网卡驱动程序的过程,加qq1126137994 微信:liu1126137994 共同学习更多技术!!! 两种移植方法:1、移植官方下载的RT5370驱动程序源码 2、在Linux内核配置自带的RT5370驱动...
  • 自己写的禁用USB的小程序,实现很简单,关键是Inf文件的修改
  • 这个设备(服务)的驱动程序已被禁用。另一个驱动程序可以提供这个功能。 (代码 32) 设备 USB\VID_13FE&amp;PID_4100\070C2647DC139778 在启动时出现问题。 驱动程序名称: usbstor.inf 类 GUID: {36FC9E60-...
  • 广州银行网上银行USB Key驱动程序是一款由广州银行官方制作推出的用于广州银行网上交易的Ukey驱动和管理软件。用户在登录网上银行的时候,出现无法输入密码情况就是因为没有安装usbkey驱动认证没有通过。系统唯有...
  • BUS HOUND调试USB驱动遇到的错误代码解析  在使用BUS HOUND调试USB驱动时,在Capture页面会遇到抓取的错误信息,错误代码会在data字段显示,根据不同的显示,含义如下: 顺便记录用于以后代码分析。 ...
  • 本文由Markdown语法编辑器编辑完成。1. 问题提出: 前段时间由于工作需要... 后来虽然从网络上搜索了很多的解决方案,如使用驱动精灵,鲁大师等软件来检测受损的驱动进行修复,或者从电脑的官网上下载了相应的USB的驱
  • Linux驱动开发: USB驱动开发

    千次阅读 多人点赞 2021-08-24 22:03:05
    一、USB简介 1.1 什么是USB? USB是连接计算机系统与外部设备的一种串口总线标准,也是一种输入输出接口的技术规范,被广泛地应用于个人电脑和移动设备等信息通讯产品,USB就是简写,中文叫通用串行总线。最早出现...
  • 在我舍友的电脑上进行同样的操作后,他的电脑就没有Code 39 错误。 Code 39 问题解决方法 最终在Intel的官网上Intel Support找到了问题的解决方案。 下载其中的对应的安装安装包,同样的右击安装操作。如果还是不行...
  • USB驱动程序(较详细)三

    千次阅读 2017-10-31 13:47:19
    (本部分的一些示例源码来自drivers/usb/usb-skeleton.c,它是Linux内核为我们提供的最基础的USB驱动程序,USB骨架程序)   驱动程序把驱动对象注册到 USB 子系统中,之后使用供应商(idVendor)和设备(id...
  • 目前,ST的USB驱动有两套,一套是早期的独立版的驱动,最新版为2.2.0;一套为针对其Cube系列的驱动,根据芯片不同可能有区别,具体见对应芯片的Cube驱动包 。 本文使用的为独立版的USB驱动 本文多出自于ST的官方文档...
  • 开发板USB转串口CH340驱动以及提供了安装失败的解决方案,随后会将自己问题以及相应解决过程以博客形式发出,敬请关注
  • 双击ps/2标准键盘,键盘属性中显示驱动损坏或者找不到2、解决方法选择驱动程序项,选择更新驱动程序,选择可驱动列表中选取(如下4张图所示),重启计算机,键盘问题解决,ps/2兼容鼠标显示错误代码10(无法启动),...
  • USB驱动下载失败的解决

    千次阅读 2018-11-14 20:58:39
    Xilinx-ZYNQ7000系列-学习笔记(1):对USB驱动下载失败的解决 背景 在进行某个项目或是某个工程时,我们在编辑完代码或者是文件之后,需要将其下载到板卡或者一些其他设备中使其工作,这就需要一根下载线,又名...
  • 网络适配器网卡驱动错误代码56解决

    万次阅读 2020-03-26 15:52:06
    win10系统,设备管理器中网络适配器驱动都有黄色感叹号,有线无线usb来连接网络的驱动全部无法使用,网络连接文件夹为空。 解决: 使用“CCleaner”软件来删除修复注册表,重启电脑即可解决! ...
  • win10环境下,安装usb转232的线,但是一直驱动失败。设备无法运行,错误10。 首先网上搜了一堆驱动,好像都不太行得来,最后搜了一个成功了吧,不知对大家有没有用。一会附件上传。 安装后,还是不行。但是第二次...
  • 使用原来的安装包重新安装了虚拟机,得以解决,不明白哪里的问题。
  • USB驱动异常的解决办法

    万次阅读 2016-10-05 22:29:43
     网上查阅相关资料后,发现原来是由于量产工具会更新USB驱动和修改其注册表,这会造成原先的USB驱动异常,并且由于注册表被修改,仅仅通过卸载原驱动重新安装,或者更新驱动是无法解决问题的.最后还是得从注册表入手。
  • 漏洞背景 ...笔者使用他们的代码做了个类似的U盘,用户插入U盘,就会自动执行预置在固件中的恶意代码,下载服务器上恶意文件,执行恶意操作…. 硬件准备 这里我们使用的是: BS Micro pro micro leona...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 22,645
精华内容 9,058
关键字:

usb驱动程序错误代码10