Qualcomm usb modem驱动小结

Qualcomm usb modem驱动小结

前段时间再为我们公司的模块产品sim5210linux下的驱动, sim5210使用的是qualcomm6280芯片, 该平台提供了USB功能, 并再USB之上提供了modem, diag, nmea等设备接口, 即再usb之上我们的模块实现了modem功能, diag(诊断)功能, nmea(gps)功能, 由于在linux下有现成的标准CDC modem的驱动, 所以我们以此为参考实现了我们的非标准modem, 和用于收发at commanddiag的驱动. 下面我们重点总结一下modem驱动的编写方法,  diag类似.

我们的modem驱动是tty+usb的层次关系, 即上层跟用户交互的接口使用tty驱动类型, 初始化时先注册一个设备文件, 以后用户使用时直接通过这个设备文件与设备打交道, 当用户的操作传递到我们的tty驱动层后, 在我们的驱动中在把这些操作(read, write)转化为对usb设备的操作, 最后通过usb总线与usb设备交互, 整个驱动结构如下:

 

下面详细讲解这个驱动的整个流程:

1 通过module_init() module_exit()注册我们驱动的initexit函数, init函数将会在驱动加载时被调用, exit函数将会在驱动卸载时被调用.

2 我们的init函数是

static int __init qcmdm_init()

{

    /*1 调用alloc_tty_driver()分配一个tty_driver对象: qcmdm_tty_driver */

   

    /*2 初始化qcmdm_tty_driver 的相关field*/

 

    /*3 调用tty_set_operations qcmdm_tty_driver 设好操作函数表, tty驱动的函数表*/

 

    /*4 调用tty_register_driver()qcmdm_tty_driver注册到系统中去*/

    /*调用usb_register()注册我们的usb驱动对象 qcmdm_usb_driver(静态分配并初始化好的)*/

 

    return 0;

}

通过这一步, 我们注册了usb驱动对象, tty驱动对象, 但这时设备文件还没创建好,因此这个tty驱动还没法用, modem查上后linux内核会调用我们先前注册的usb驱动对象中的probe函数(整个调用流程可以参考我写的另一篇文章<设备probe全过程>>)

我们的exit函数是:

static void __exit qcmdm_exit()

{

    /*1 调用tty_unregister_driver() 来卸载qcmdm_tty_driver*/

 

    /*2 调用put_tty_driver() 来释放qcmdm_tty_driver占用的内存*/

  

    /*3 调用 usb_deregister()来下载qcmdm_usb_driver*/

}

3 我们qcmdm_usb_driverprobe函数:

static int qcmdm_probe(struct usb_interface *intf,  /*匹配的接口*/

const struct usb_device_id *id /*驱动支持的设备表项*/)

{

                  /*

1 根据设备的配置判断这个interface是否就是我们驱动支持的接口,

一般到了这步说明这个接口匹配了我们的device_id列表(这是我们驱动

提供的用于判断一个接口是否是我们驱动支持的这么一个列表), 但由于我们

的设备是个多接口的设备,所以这里还要进一步判断是否匹配,

*/

 

           /*

            2  检测设备的其他信息是否和我们的真是设备的信息匹配(如端点数量,端点类型等)

            */

 

            /*

             * 3 一般每个驱动都会定义一个记录我们设备信息的私有数据结构,我的驱动中定义的是

             * strcut qcmdm,  这里我们分配一个qcmdm的对象mdm,并初始化好它的各个字段(如保存各个端点信息等)

             */

 

             /*4 我们在mdm中保存了一个读和写的urb列表, 这里我们为每个urb分配内存(usb_alloc_urb), 并为每个每个urb分配DMA缓冲(usb_buffer_alloc). 并初始化好每个urb(usb_fill_xxx_urb())*/

 

             /*5 调用tty_register_device() 来把qcmdm_tty_driver和这个interface绑定起来,并创建设备文件, 以后对这个设备文件的操作就会映射到qcmdm_tty_driver的操作函数.*/

}

 此外,我们还可以在probe中保存额外的一些信息, 如调用usb_set_intfdata(intf, mdm)来将mdm保存在intf的相关字段中, 这样在以后使用中可以直接通过usb_get_intfdata()来获取mdm指针.

4我们qcmdm_usb_driverdisconnect函数(在设备拔出时调用): 这个函数主要做一些和probe相反的工作

static void qcmdm_disconnect(struct usb_interface *intf)

{

   /*1 删除urb  (usb_kill_urb())*/

   /*2  reset 读写list*/

   /*3 释放相关资源, urb memory, usb dma memory*/

   /*4 调用tty_unregister_device()来释放驱动和设备的绑定,并删除设备文件*/

   /*5 其他资源的释放或reset*/  

}

总之在probe中申请了什么资源应该在disconnect中释放掉, 因为下次设备连上后,又会调用probe, 此时又会去分配资源.

5 打开函数: 这个函数是tty driver的一部分, 当打开我们创建的设备文件时会调用这个函数, 它的工作主要也是做一些初始化工作, 并调用usb_submit_urb()提交一个中断类型的urb(因为我们的设备上有个interrupt的端点, 用于设备上报一些自身状态的改变信息, 所以这里我们应该准备好接收这些状态信息)

6 write函数: 这也是tty driver的一部分, 在我们的驱动中保存了一个用于写usb的缓冲链表, 每次要写时就去获取一个空闲表项, 并把端点,数据等信息填充号这个表项中的urb, 特别的一般我们还会填充一个我们自己的用于完成后的回调函数. 然后调用usb_submit_urb提交这个写操作, usb写完或错误后会调用我们的回调函数(注意这是运行在中断上下文的), 这样我们就知道成功与否.

7 read函数:  这也是tty driver的一部分跟write相似,也有一个用于读的链表, 每个读操作也是先填充urb, 然后在提交,在完成后调用我们的回调函数, 此时我们得到了数据, 可以通过tty_insert_flip_string()tty_flip_buffer_push()把数据提交到tty核心,在由tty核心交给用户. 由于在usb核心收到数据后应该快速的返回给用户, 因此在读的部分, 我们使用了tasklet, 以加快数据读出,又可避免直接在中断上下文读数据.

8 其他函数相对简单,在此不作介绍

你可能感兴趣的:(linux,driver)