会员登录
帐号:
密码:
栏目导航
在线客服
 工作时间
周一至周五 :9:00-17:30
周六至周日 :10:00-16:00
 联系方式
屈生:13352775249  QQ:379990634
王小姐:13392806262  QQ:353590641
新闻详情

Linux USB 驱动开发实例(一) —— USB摄像头驱动实现源码分析

Spac5xx的实现是按照标准的USB VIDEO设备的驱动框架编写(其具体的驱动框架可参照/usr/src/linux/drivers/usb/usbvideo.c文件),整个源程序由四个主体部分组成:

设备模块的初始化模块和卸载模块,上层软件接口模块,数据传输模块


具体的模块分析如下:

一、初始化设备模块

       该驱动采用了显式的模块初始化和消除函数,即调用module_init来初始化一个模块,并在卸载时调用moduel-exit函数

       其具体实现如下:

1、模块初始化:

  1. module_init (usb_spca5xx_init);  

  2.  

  3. static int __init  usb_spca5xx_init (void)  

  4. {    

  5. #ifdef CONFIG_PROC_FS                      

  6.    proc_spca50x_create ();//建立PROC设备文件  

  7. #endif /* CONFIG_PROC_FS */      

  8.    if (usb_register (&spca5xx_driver) < 0) //注册USB设备驱动      

  9.        return -1;      

  10.    info ("spca5xx driver %s registered", version);    

  11.    return 0;  

  12. }  



2、模块卸载:


  1. module_exit (usb_spca5xx_exit);  

  2.  

  3. static void __exit  usb_spca5xx_exit (void)  

  4. {      

  5.    usb_deregister (&spca5xx_driver); //注销USB设备驱动    

  6.    info ("driver spca5xx deregistered");  

  7. #ifdef CONFIG_PROC_FS      

  8.    proc_spca50x_destroy ();//撤消PROC设备文件  

  9. #endif /* CONFIG_PROC_FS */  

  10. }  



关键数据结构 USB驱动结构,即插即用功能的实现

  1. static struct usb_driver spca5xx_driver = {          

  2.    "spca5xx",          

  3.    spca5xx_probe, //注册设备自我侦测功能          

  4.    spca5xx_disconnect,//注册设备自我断开功能          

  5.    {NULL,NULL}  

  6. };  


用两个函数调用spca5xx_probe 和spca5xx_disconnect来支持USB设备的即插即用功能:


a -- spca5xx_probe具体实现如下:
  1. static void * spca5xx_probe (struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id)  

  2. {      

  3.    struct usb_interface_descriptor *interface;          //USB设备接口描述符  

  4.    struct usb_spca50x *spca50x;                    //物理设备数据结构    

  5.    int err_probe;    

  6.    int i;      

  7.    if (dev->descriptor.bNumConfigurations != 1)        //探测设备是不是可配置      

  8.        goto nodevice;    

  9.    if (ifnum > 0)      

  10.        goto nodevice;      

  11.    interface = &dev->actconfig->interface[ifnum].altsetting[0];    

  12.    MOD_INC_USE_COUNT;      

  13.    interface = &intf->altsetting[0].desc;    

  14.    if (interface->bInterfaceNumber > 0)      

  15.        goto nodevice;      

  16.    if ((spca50x = kmalloc (sizeof (struct usb_spca50x), GFP_KERNEL)) == NULL) //分配物理地址空间      

  17.    {          

  18.        err ("couldn't kmalloc spca50x struct");        

  19.        goto error;      

  20.    }      

  21.  

  22.    memset (spca50x, 0, sizeof (struct usb_spca50x));    

  23.    spca50x->dev = dev;      

  24.    spca50x->iface = interface->bInterfaceNumber;      

  25.    if ((err_probe = spcaDetectCamera (spca50x)) < 0)       //具体物理设备查找,匹配厂商号,设备号(在子程序中)        

  26.    {          

  27.        err (" Devices not found !! ");        

  28.        goto error;      

  29.    }      

  30.    PDEBUG (0, "Camera type %s ", Plist[spca50x->cameratype].name)  

  31.    for (i = 0; i < SPCA50X_NUMFRAMES; i++)        

  32.        init_waitqueue_head (&spca50x->frame[i].wq);     //初始化帧等待队列      

  33.        init_waitqueue_head (&spca50x->wq);            //初始化驱动等待队列    

  34.  

  35.    if (!spca50x_configure (spca50x))                  //物理设备配置(主要完成传感器侦测和图形参数配置),主要思想是给控制寄存器写值,读回其返回值,以此判断具体的传感器型号      

  36.    {          

  37.        spca50x->user = 0;          

  38.        init_MUTEX (&spca50x->lock);                  //信号量初始化        

  39.        init_MUTEX (&spca50x->buf_lock);          

  40.        spca50x->v4l_lock = SPIN_LOCK_UNLOCKED;        

  41.        spca50x->buf_state = BUF_NOT_ALLOCATED;      

  42.    }                                      

  43.    else      

  44.    {  

  45.        err ("Failed to configure camera");        

  46.            goto error;      

  47.    }  

  48.    

  49.  

  50.    /* Init video stuff */  

  51.    

  52.    spca50x->vdev = video_device_alloc ();           //设备控制块内存分配    

  53.    if (!spca50x->vdev)      

  54.        goto error;  

  55.    memcpy (spca50x->vdev, &spca50x_template, sizeof (spca50x_template));    

  56.    //系统调用的挂接,在此将驱动实现的系统调用,挂到内核中    

  57.    video_set_drvdata (spca50x->vdev, spca50x);  

  58.    

  59.        if (video_register_device (spca50x->vdev, VFL_TYPE_GRABBER, video_nr) < 0)      

  60.    {                                              

  61.    //video设备注册        

  62.        err ("video_register_device failed");        

  63.        goto error;      

  64.    }  

  65.    

  66.    spca50x->present = 1;    

  67.    if (spca50x->force_rgb)  

  68.      

  69.        info ("data format set to RGB");    

  70.    spca50x->task.sync = 0;  

  71.    

  72.    spca50x->task.routine = auto_bh;    

  73.    spca50x->task.data = spca50x;    

  74.    spca50x->bh_requested = 0;  

  75.              

  76.    MOD_DEC_USE_COUNT; //增加模块使用数    

  77.    return spca50x; //返回数剧结构  

  78. error://错误处理    

  79.    if (spca50x->vdev)      

  80.    {  

  81.        

  82.        if (spca50x->vdev->minor == -1)  

  83.        

  84.            video_device_release (spca50x->vdev);        

  85.        else  

  86.        

  87.            video_unregister_device (spca50x->vdev);        

  88.        spca50x->vdev = NULL;      

  89.    }  

  90.    

  91.    if (spca50x)      

  92.    {  

  93.        

  94.        kfree (spca50x);        

  95.        spca50x = NULL;      

  96.    }  

  97.    

  98.    MOD_DEC_USE_COUNT;    

  99.    return NULL;  

  100. nodevice:  

  101.    

  102.    return NULL;  

  103. }  


b -- Spca5xx_disconnect 的具体实现如下:
  1. static void  spca5xx_disconnect (struct usb_device *dev, void *ptr)  

  2. {      

  3.    struct usb_spca50x *spca50x = (struct usb_spca50x *) ptr;    

  4.    int n;      

  5.    MOD_INC_USE_COUNT; //增加模块使用数    

  6.    if (!spca50x)      

  7.        return;      

  8.    down (&spca50x->lock); //减少信号量    

  9.    spca50x->present = 0;  //驱动卸载置0      

  10.    for (n = 0; n < SPCA50X_NUMFRAMES; n++)       //标示所有帧ABORTING状态      

  11.    {  

  12.        spca50x->frame[n].grabstate = FRAME_ABORTING;      

  13.        spca50x->curframe = -1;    

  14.      

  15.    }  

  16.     for (n = 0; n < SPCA50X_NUMFRAMES; n++)       //唤醒所有等待进程      

  17.    {  

  18.        if (waitqueue_active (&spca50x->frame[n].wq))        

  19.            wake_up_interruptible (&spca50x->frame[n].wq);    

  20.          

  21.        if (waitqueue_active (&spca50x->wq))          

  22.            wake_up_interruptible (&spca50x->wq);      

  23.    }  

  24.    spca5xx_kill_isoc(spca50x);  //子函数终止URB包的传输    

  25.    PDEBUG (3,"Disconnect Kill isoc done");      

  26.    up (&spca50x->lock);  //增加信号量      

  27.    while(spca50x->user) /如果还有进程在使用,进程切换        

  28.        schedule();        

  29.    down (&spca50x->lock);      

  30.    if (spca50x->vdev)    

  31.    {        

  32.        video_unregister_device (spca50x->vdev);   //注销video设备      

  33.        usb_driver_release_interface (&spca5xx_driver,&spca50x->dev->actconfig->interface[spca50x->iface]); //端口释放        

  34.        spca50x->dev = NULL;        

  35.    }  

  36.    up (&spca50x->lock);  

  37. #ifdef CONFIG_PROC_FS          

  38.    destroy_proc_spca50x_cam (spca50x); //注销PROC文件  

  39. #endif /* CONFIG_PROC_FS */  

  40.        

  41.    if (spca50x && !spca50x->user)                      //释放内存空间          

  42.    {            

  43.        spca5xx_dealloc (spca50x);            

  44.        kfree (spca50x);            

  45.        spca50x = NULL;          

  46.    }      

  47.    MOD_DEC_USE_COUNT;                              //减少模块记数    

  48.    PDEBUG (3, "Disconnect complete");  

  49. }  



二、上层软件接口模块:

       该模块通过file_operations数据结构,依据V4L协议规范,实现设备的关键系统调用,实现设备文件化的UNIX系统设计特点。作为摄相头驱动,其功能在于数据采集,而没有向摄相头输出的功能,因此在源码中没有实现write系统调用。

其关键的数据结构如下:

  1. static struct video_device spca50x_template = {    

  2.    .owner = THIS_MODULE,    

  3.    .name = "SPCA5XX USB Camera",    

  4.    .type = VID_TYPE_CAPTURE,    

  5.    .hardware = VID_HARDWARE_SPCA5XX,    

  6.    .fops = &spca5xx_fops,          

  7. };  

  8.  

  9.  

  10. static struct file_operations spca5xx_fops = {    

  11.    .owner = THIS_MODULE,    

  12.    .open = spca5xx_open, //open功能    

  13.    .release = spca5xx_close,//close功能    

  14.    .read = spca5xx_read, //read功能    

  15.    .mmap = spca5xx_mmap, //内存映射功能    

  16.    .ioctl = spca5xx_ioctl, //文件信息获取    

  17.    .llseek = no_llseek,//文件定位功能未实现  

  18. };  

1. Open功能

完成设备的打开和初始化,并初始化解码器模块。其具体实现如下:

  1. static int  spca5xx_open(struct video_device *vdev, int flags)  

  2. {      

  3.    struct usb_spca50x *spca50x = video_get_drvdata (vdev);    

  4.    int err;      

  5.    MOD_INC_USE_COUNT;                         //增加模块记数    

  6.    down (&spca50x->lock);                                

  7.    err = -ENODEV;      

  8.  

  9.    if (!spca50x->present)                 //检查设备是不是存在,有不有驱动,是不是忙      

  10.        goto out;    

  11.    err = -EBUSY;    

  12.    if (spca50x->user)      

  13.        goto out;      

  14.    err = -ENOMEM;      

  15.    if (spca50x_alloc (spca50x))    

  16.        goto out;                      

  17.      

  18.    err = spca50x_init_source (spca50x);           //初始化传感器和解码模块,在此函数的实现中,对每一款DSP芯片的初始化都不一样,对中星微301P的DSP芯片的初始化在子函数zc3xx_init,其实现方法为寄存器填值。    

  19.    if (err != 0)  

  20.    {          

  21.        PDEBUG (0, "DEALLOC error on spca50x_init_source\n");        

  22.        up (&spca50x->lock);          

  23.        spca5xx_dealloc (spca50x);        

  24.        goto out2;      

  25.    }  

  26.  

  27.    spca5xx_initDecoder(spca50x);                  //解码模块初始化,其模块的具体实现采用的是huffman算法      spca5xx_setFrameDecoder(spca50x);    

  28.    spca50x->user++;      

  29.      

  30.    err = spca50x_init_isoc (spca50x);              //初始化URB(usb request block) 包,启动摄相头,采用同步传输的方式传送数据    

  31.    if (err)      

  32.    {          

  33.        PDEBUG (0, " DEALLOC error on init_Isoc\n");        

  34.        spca50x->user--;          

  35.        spca5xx_kill_isoc (spca50x);        

  36.        up (&spca50x->lock);          

  37.        spca5xx_dealloc (spca50x);        

  38.        goto out2;  

  39.    }      

  40.  

  41.    spca50x->brightness = spca50x_get_brghtness (spca50x) << 8;    

  42.    spca50x->whiteness = 0;  

  43.  

  44. out:      

  45.    up (&spca50x->lock);  

  46. out2:    

  47.    if (err)      

  48.        MOD_DEC_USE_COUNT;    

  49.    if (err)          

  50.    {          

  51.        PDEBUG (2, "Open failed");      

  52.    }    

  53.    else      

  54.    {          

  55.        PDEBUG (2, "Open done");      

  56.    }      

  57.    return err;  

  58. }  

2.Close功能

完成设备的关闭,其具体过程是:

  1. static void spca5xx_close(struct video_device *vdev)  

  2. {    

  3.    struct usb_spca50x *spca50x =vdev->priv;    

  4.    int i;    

  5.    PDEBUG (2, "spca50x_close");    

  6.    down (&spca50x->lock);  //参数设置    

  7.    spca50x->user--;    

  8.    spca50x->curframe = -1;  

  9.    

  10.    if(spca50x->present)//present:是或有驱动加载      

  11.    {        

  12.        spca50x_stop_isoc (spca50x);//停止摄相头工作和数据包送        

  13.        spcaCameraShutDown (spca50x);//关闭摄相头,由子函数spca50x_stop_iso完成    

  14.        for (i = 0; i < SPCA50X_NUMFRAMES; i++)    //唤醒所有等待进程      

  15.        {        

  16.            if(waitqueue_active(&spca50x->frame[i].wq))  

  17.                wake_up_interruptible (&spca50x->frame[i].wq);      

  18.        }  

  19.        if(waitqueue_active (&spca50x->wq))          

  20.            wake_up_interruptible (&spca50x->wq);    

  21.    }    

  22.      

  23.    up(&spca50x->lock);  

  24.    spca5xx_dealloc (spca50x);//回收内存空间    

  25.      

  26.    PDEBUG(2,"Release ressources done");    

  27.    MOD_DEC_USE_COUNT;  

  28. }  


3、 Read功能

完成数据的读取,其主要的工作就是将数据由内核空间传送到进程用户空间

  1. static long spca5xx_rea(struct video_device *dev, char * buf, unsigned long count,int noblock)  

  2. {    

  3.    struct usb_spca50x *spca50x = video_get_drvdata (dev);    

  4.    int i;    

  5.    int frmx = -1;    

  6.    int rc;    

  7.    volatile struct spca50x_frame *frame;  

  8.      

  9.        if(down_interruptible(&spca50x->lock))  //获取信号量        

  10.        return -EINTR;  

  11.      

  12.        if(!dev || !buf){//判断设备情况      

  13.        up(&spca50x->lock);      

  14.        return -EFAULT;  

  15.    }    

  16.    

  17.    if(!spca50x->dev){      

  18.        up(&spca50x->lock);      

  19.        return -EIO;  

  20.    }  

  21.      

  22.    if (!spca50x->streaming){      

  23.        up(&spca50x->lock);      

  24.        return -EIO;  

  25.    }  

  26.      

  27.        if((rc = wait_event_interruptible(spca50x->wq,     //在指定的队列上睡眠,直到参数2的条件为真  

  28.              spca50x->frame[0].grabstate == FRAME_DONE ||  

  29.              spca50x->frame[1].grabstate == FRAME_DONE ||                

  30.          spca50x->frame[2].grabstate == FRAME_DONE ||                

  31.              spca50x->frame[3].grabstate == FRAME_DONE )))    

  32.        {                

  33.        up(&spca50x->lock);                

  34.        return rc;          

  35.    }    

  36.    

  37.  

  38.    for (i = 0; i < SPCA50X_NUMFRAMES; i++)         //当数据到来      

  39.        if (spca50x->frame[i].grabstate == FRAME_DONE)   //标识数已到        

  40.            frmx = i;    

  41.    if (frmx < 0)      

  42.    {  

  43.        

  44.        PDEBUG(2, "Couldnt find a frame ready to be read.");        

  45.        up(&spca50x->lock);        

  46.        return -EFAULT;      

  47.    }  

  48.    

  49.    frame = &spca50x->frame[frmx];  

  50.    PDEBUG (2, "count asked: %d available: %d", (int) count,(int) frame->scanlength);    

  51.    if (count > frame->scanlength)      

  52.        count = frame->scanlength;  

  53.    

  54.    if ((i = copy_to_user (buf, frame->data, count)))   //实现用户空间和内核空间的数据贝      

  55.    {  

  56.        

  57.        PDEBUG (2, "Copy failed! %d bytes not copied", i);            

  58.        up(&spca50x->lock);        

  59.        return -EFAULT;      

  60.    }  

  61.    

  62.    /* Release the frame */    

  63.    frame->grabstate = FRAME_READY; //标识数据已空  

  64.    up(&spca50x->lock);                            

  65.    return count;//返回拷贝的数据数  

  66. }  


4、Mmap功能

实现将设备内存映射到用户进程的地址空间的功能,其关键函数是remap_page_range,其具体实现如下:

  1. static int spca5xx_mmap(struct video_device *dev,const char *adr, unsigned long size)  

  2. {  

  3.    

  4.    unsigned long start = (unsigned long) adr;    

  5.    struct usb_spca50x *spca50x = dev->priv;    

  6.    unsigned long page, pos;    

  7.    if (spca50x->dev == NULL)      

  8.        return -EIO;  

  9.      

  10.    PDEBUG (4, "mmap: %ld (%lX) bytes", size, size);    

  11.    if(size > (((SPCA50X_NUMFRAMES * MAX_DATA_SIZE) + PAGE_SIZE - 1) & ~(PAGE_SIZE -1)))      

  12.        return -EINVAL;  

  13.        

  14.    if (down_interruptible(&spca50x->lock))  //获取信号量                

  15.        return -EINTR;  

  16.    

  17.    pos = (unsigned long) spca50x->fbuf;  

  18.    while (size > 0)  //循环实现内存映射      

  19.    {                  

  20.        page = kvirt_to_pa (pos);  

  21.        if (remap_page_range (start, page, PAGE_SIZE, PAGE_SHARED)){  //实现内存映射  

  22.            up(&spca50x->lock);  

  23.            return -EAGAIN;      

  24.        }        

  25.      

  26.        start += PAGE_SIZE;        

  27.        pos += PAGE_SIZE;        

  28.        if (size > PAGE_SIZE)          

  29.            size -= PAGE_SIZE;        

  30.        else      

  31.            size = 0;      

  32.    }  

  33.  

  34.    up(&spca50x->lock); //释放信号量    

  35.    return 0;  

  36. }  


5、Ioctl功能:  

实现文件信息的获取功能

  1. static int spca5xx_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)  

  2. {        

  3.    struct video_device *vdev = file->private_data;          

  4.    struct usb_spca50x *spca50x = vdev->priv;          

  5.    int rc;  

  6.        

  7.    if(down_interruptible(&spca50x->lock))       //获取信号量                

  8.        return -EINTR;  

  9.        

  10.    rc = video_usercopy (inode, file, cmd, arg, spca5xx_do_ioctl);  //将信息传送到用户进程,其关键函数实现spca5xx_do_ioctl  

  11.    up(&spca50x->lock);    

  12.    return rc;  

  13. }  


spca5xx_do_ioctl函数的实现依赖于不同的硬件,本驱动为了支持多种芯片,实现程序过于烦琐,其主要思想是通过copy_to_user(arg,b,sizeof(struct video_capability)函数将设备信息传递给用户进程。


三、数据传输模块

     源程序采用tasklet来实现同步快速传递数据,并通过spcadecode.c上的软件解码模块实现图形信息的解码。此模块的入口点挂节在spca_open函数中,其具体的函数为spca50x_init_isoc。当设备被打开时,同步传输数据也已经开始,并通过spca50x_move_data函数将数据传递给驱动程序,驱动程序通过轮询的办法实现对数据的访问。

  1. Void outpict_do_tasklet (unsigned long ptr)  

  2. {    

  3.    int err;    

  4.    struct spca50x_frame *taskletframe = (struct spca50x_frame *) ptr;    

  5.    taskletframe->scanlength = taskletframe->highwater - taskletframe->data;  

  6.      PDEBUG (2, "Tasklet ask spcadecoder hdrwidth %d hdrheight %d method %d",          

  7.        taskletframe->hdrwidth, taskletframe->hdrheight,            

  8.        taskletframe->method);  

  9.    

  10.    err = spca50x_outpicture (taskletframe);  //输出处理过的图片数据    

  11.    if (err != 0)      

  12.    {        

  13.        PDEBUG (0, "frame decoder failed (%d)", err);        

  14.        taskletframe->grabstate = FRAME_ERROR;      

  15.    }    

  16.    else      

  17.    {        

  18.        taskletframe->grabstate = FRAME_DONE;      

  19.    }  

  20.    

  21.    if (waitqueue_active (&taskletframe->wq))//如果有进程等待,唤醒等待进程      

  22.        wake_up_interruptible (&taskletframe->wq);  

  23. }  


值得一提的是spcadecode.c上解码模块将原始压缩图形数据流yyuyv,yuvy, jpeg411,jpeg422解码为RGB图形,但此部分解压缩算法的实现也依赖于压缩的格式,归根结底依赖于DSP(数字处理芯片)中的硬件压缩算法。


四.USB CORE的支持

      LINUX下的USB设备对下层硬件的操作依靠系统实现的USB CORE层,USB CORE对上层驱动提供了众多函数接口如:usb_control_msg,usb_sndctrlpipe等,其中最典型的使用为源码中对USB端点寄存器的读写函数spca50x_reg_write和spca50x_reg_read等,具体实现如下:(举spca50x_reg_write的实现,其他类似)


  1. static int spca50x_reg_write(struct usb_device *dev,__u16 reg,__u16 index,__u16 value)  

  2. {        

  3.    int rc;          

  4.    rc = usb_control_msg(dev,          //通过USB CORE提供的接口函数设置寄存器的值  

  5.                usb_sndctrlpipe(dev, 0),  

  6.                        reg,  

  7.                        USB_TYPE_VENDOR | USB_RECIP_DEVICE,  

  8.                        value, index, NULL, 0, TimeOut);  

  9.        

  10.    PDEBUG(5, "reg write: 0x%02X,0x%02X:0x%02X, 0x%x", reg, index, value, rc);          

  11.    if (rc < 0)                

  12.        err("reg write: error %d", rc);          

  13.    return rc;  

  14. }