天天看点

Qualcomm usb modem驱动小结

Qualcomm usb modem驱动小结

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

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

Qualcomm usb modem驱动小结

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

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

2 我们的init函数是

static int __init qcmdm_init()

{

    return 0;

}

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

我们的exit函数是:

static void __exit qcmdm_exit()

{

}

3 我们qcmdm_usb_driver的probe函数:

static int qcmdm_probe(struct usb_interface *intf, 

const struct usb_device_id *id )

{

}

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

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

static void qcmdm_disconnect(struct usb_interface *intf)

{

}

总之在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 其他函数相对简单,在此不作介绍

继续阅读