linux下usb转串口驱动分析

最近要做一个关于LTE的项目,模块厂家提供的驱动里面有usb转串口驱动,usb网卡驱动,项目还没立项所以比较空,闲的蛋疼把usb转串口驱动研究了一遍,本文尽量用画图把事情说明白,献给各位纠结的童鞋。

首先说一下linux的风格,个人理解

1. linux大小结构体其实是面向对象的方法,(如果把struct 比作类,kmalloc就是类的实例化,结构体里面的函数指针就是方法,还有重构,多态)

2. 在linux里面,设备是对象,驱动也是对象,并且这两个是分开的

现在我们来看驱动的总体架构

linux下usb转串口驱动分析

并不用太在意这个图,对用户来说usb_serial设备就是普通的串口设备

我们可以看驱动里面几个主要的源代码文件

usb-serial.c 模块的主要实现

bus.c usb_serial总线驱动,驱动和设备都要注册到这条总线上

generic.c通用的用户驱动,用户如果写自己的驱动只需拿自己的实现代替generic.c的函数,一般这个驱动已经能适应大部分设备了

现在我们来看usb_serial模块的初始化过程

  1. staticint__initusb_serial_init(void)
  2. {
  3. inti;
  4. intresult;
  5. usb_serial_tty_driver=alloc_tty_driver(SERIAL_TTY_MINORS);
  6. if(!usb_serial_tty_driver)
  7. return-ENOMEM;
  8. /*Initializeourglobaldata*/
  9. for(i=0;i<SERIAL_TTY_MINORS;++i)
  10. serial_table[i]=NULL;
  11. result=bus_register(&usb_serial_bus_type);
  12. if(result){
  13. printk(KERN_ERR"usb-serial:%s-registeringbusdriver"
  14. "failed\n",__func__);
  15. gotoexit_bus;
  16. }
  17. usb_serial_tty_driver->owner=THIS_MODULE;
  18. usb_serial_tty_driver->driver_name="usbserial";
  19. usb_serial_tty_driver->name="ttyUSB";
  20. usb_serial_tty_driver->major=SERIAL_TTY_MAJOR;
  21. usb_serial_tty_driver->minor_start=0;
  22. usb_serial_tty_driver->type=TTY_DRIVER_TYPE_SERIAL;
  23. usb_serial_tty_driver->subtype=SERIAL_TYPE_NORMAL;
  24. usb_serial_tty_driver->flags=TTY_DRIVER_REAL_RAW|
  25. TTY_DRIVER_DYNAMIC_DEV;
  26. usb_serial_tty_driver->init_termios=tty_std_termios;
  27. usb_serial_tty_driver->init_termios.c_cflag=B9600|CS8|CREAD
  28. |HUPCL|CLOCAL;
  29. usb_serial_tty_driver->init_termios.c_ispeed=9600;
  30. usb_serial_tty_driver->init_termios.c_ospeed=9600;
  31. tty_set_operations(usb_serial_tty_driver,&serial_ops);
  32. result=tty_register_driver(usb_serial_tty_driver);
  33. if(result){
  34. printk(KERN_ERR"usb-serial:%s-tty_register_driverfailed\n",
  35. __func__);
  36. gotoexit_reg_driver;
  37. }
  38. /*registertheUSBdriver*/
  39. result=usb_register(&usb_serial_driver);
  40. if(result<0){
  41. printk(KERN_ERR"usb-serial:%s-usb_registerfailed\n",
  42. __func__);
  43. gotoexit_tty;
  44. }
  45. /*registerthegenericdriver,ifweshould*/
  46. result=usb_serial_generic_register(debug);
  47. if(result<0){
  48. printk(KERN_ERR"usb-serial:%s-registeringgeneric"
  49. "driverfailed\n",__func__);
  50. gotoexit_generic;
  51. }
  52. printk(KERN_INFOKBUILD_MODNAME":"DRIVER_DESC"\n");
  53. returnresult;
  54. exit_generic:
  55. usb_deregister(&usb_serial_driver);
  56. exit_tty:
  57. tty_unregister_driver(usb_serial_tty_driver);
  58. exit_reg_driver:
  59. bus_unregister(&usb_serial_bus_type);
  60. exit_bus:
  61. printk(KERN_ERR"usb-serial:%s-returningwitherror%d\n",
  62. __func__,result);
  63. put_tty_driver(usb_serial_tty_driver);
  64. returnresult;
  65. }
  1. staticint__initusb_serial_init(void)
  2. {
  3. inti;
  4. intresult;
  5. usb_serial_tty_driver=alloc_tty_driver(SERIAL_TTY_MINORS);
  6. if(!usb_serial_tty_driver)
  7. return-ENOMEM;
  8. /*Initializeourglobaldata*/
  9. for(i=0;i<SERIAL_TTY_MINORS;++i)
  10. serial_table[i]=NULL;
  11. result=bus_register(&usb_serial_bus_type);
  12. if(result){
  13. printk(KERN_ERR"usb-serial:%s-registeringbusdriver"
  14. "failed\n",__func__);
  15. gotoexit_bus;
  16. }
  17. usb_serial_tty_driver->owner=THIS_MODULE;
  18. usb_serial_tty_driver->driver_name="usbserial";
  19. usb_serial_tty_driver->name="ttyUSB";
  20. usb_serial_tty_driver->major=SERIAL_TTY_MAJOR;
  21. usb_serial_tty_driver->minor_start=0;
  22. usb_serial_tty_driver->type=TTY_DRIVER_TYPE_SERIAL;
  23. usb_serial_tty_driver->subtype=SERIAL_TYPE_NORMAL;
  24. usb_serial_tty_driver->flags=TTY_DRIVER_REAL_RAW|
  25. TTY_DRIVER_DYNAMIC_DEV;
  26. usb_serial_tty_driver->init_termios=tty_std_termios;
  27. usb_serial_tty_driver->init_termios.c_cflag=B9600|CS8|CREAD
  28. |HUPCL|CLOCAL;
  29. usb_serial_tty_driver->init_termios.c_ispeed=9600;
  30. usb_serial_tty_driver->init_termios.c_ospeed=9600;
  31. tty_set_operations(usb_serial_tty_driver,&serial_ops);
  32. result=tty_register_driver(usb_serial_tty_driver);
  33. if(result){
  34. printk(KERN_ERR"usb-serial:%s-tty_register_driverfailed\n",
  35. __func__);
  36. gotoexit_reg_driver;
  37. }
  38. /*registertheUSBdriver*/
  39. result=usb_register(&usb_serial_driver);
  40. if(result<0){
  41. printk(KERN_ERR"usb-serial:%s-usb_registerfailed\n",
  42. __func__);
  43. gotoexit_tty;
  44. }
  45. /*registerthegenericdriver,ifweshould*/
  46. result=usb_serial_generic_register(debug);
  47. if(result<0){
  48. printk(KERN_ERR"usb-serial:%s-registeringgeneric"
  49. "driverfailed\n",__func__);
  50. gotoexit_generic;
  51. }
  52. printk(KERN_INFOKBUILD_MODNAME":"DRIVER_DESC"\n");
  53. returnresult;
  54. exit_generic:
  55. usb_deregister(&usb_serial_driver);
  56. exit_tty:
  57. tty_unregister_driver(usb_serial_tty_driver);
  58. exit_reg_driver:
  59. bus_unregister(&usb_serial_bus_type);
  60. exit_bus:
  61. printk(KERN_ERR"usb-serial:%s-returningwitherror%d\n",
  62. __func__,result);
  63. put_tty_driver(usb_serial_tty_driver);
  64. returnresult;
  65. }

很简单

第一步 将usb_seria的TTY驱动注册进TTY驱动列表里面,以后调用open,write,read首先会调用tty驱动里面的函数,然后函数指针会指到用户自己定义的驱动里面,这应该是多态的一种应用吧,个人理解求指正

第二步 将usb_seria驱动注册进usb_core里面的驱动列表

只有usb_serial模块驱动,设备还不能正常工作,linux很好的把它分成了

分层一: usb_serial驱动,设备的大部分实现都在此

分层二: 用户驱动,不需要知道太多的细节,实现几个回调函数就能实现整个驱动功能。

generic.c 就是个通用的用户驱动模型,并且大部分设备都能兼容。

下面generic.c的模块初始化函数

  1. intusb_serial_generic_register(int_debug)
  2. {
  3. intretval=0;
  4. debug=_debug;
  5. #ifdefCONFIG_USB_SERIAL_GENERIC
  6. generic_device_ids[0].idVendor=vendor;
  7. generic_device_ids[0].idProduct=product;
  8. generic_device_ids[0].match_flags=
  9. USB_DEVICE_ID_MATCH_VENDOR|USB_DEVICE_ID_MATCH_PRODUCT;
  10. /*registerourgenericdriverwithourselves*/
  11. retval=usb_serial_register(&usb_serial_generic_device);
  12. if(retval)
  13. gotoexit;
  14. retval=usb_register(&generic_driver);
  15. if(retval)
  16. usb_serial_deregister(&usb_serial_generic_device);
  17. exit:
  18. #endif
  19. returnretval;
  20. }
  1. intusb_serial_generic_register(int_debug)
  2. {
  3. intretval=0;
  4. debug=_debug;
  5. #ifdefCONFIG_USB_SERIAL_GENERIC
  6. generic_device_ids[0].idVendor=vendor;
  7. generic_device_ids[0].idProduct=product;
  8. generic_device_ids[0].match_flags=
  9. USB_DEVICE_ID_MATCH_VENDOR|USB_DEVICE_ID_MATCH_PRODUCT;
  10. /*registerourgenericdriverwithourselves*/
  11. retval=usb_serial_register(&usb_serial_generic_device);
  12. if(retval)
  13. gotoexit;
  14. retval=usb_register(&generic_driver);
  15. if(retval)
  16. usb_serial_deregister(&usb_serial_generic_device);
  17. exit:
  18. #endif
  19. returnretval;
  20. }


第一步 首先确定自己的特征码,这里通过vendor 和 product表明自己是哪种设备的驱动

第二步 将用户驱动注册进usb_serial_bus总线,linux很喜欢这么搞……,通过总线来管理一类设备

第三步 将usb驱动注册进usb_core的驱动列表

有人会问,为什么usb会自动发现对应的驱动,这其实是个匹配的过程,linux里面叫probe

前面第一步是确定了驱动的匹配值,第二步和第三步都把驱动的匹配值注册进去

linux下usb转串口驱动分析

事实上前面第三步代码retval = usb_register(&generic_driver);就是为了把generic驱动注册进usb的驱动列表,当有设备插入时,会轮询到它,然后会调用此驱动的probe,就是下面的函数

  1. staticintgeneric_probe(structusb_interface*interface,
  2. conststructusb_device_id*id)
  3. {
  4. conststructusb_device_id*id_pattern;
  5. id_pattern=usb_match_id(interface,generic_device_ids);
  6. if(id_pattern!=NULL)
  7. returnusb_serial_probe(interface,id);
  8. return-ENODEV;
  9. }
  1. staticintgeneric_probe(structusb_interface*interface,
  2. conststructusb_device_id*id)
  3. {
  4. conststructusb_device_id*id_pattern;
  5. id_pattern=usb_match_id(interface,generic_device_ids);
  6. if(id_pattern!=NULL)
  7. returnusb_serial_probe(interface,id);
  8. return-ENODEV;
  9. }

最主要的还是调用usb_serial_probe(interface, id)函数

现在我们来看usb_serial_probe()的匹配过程
linux下usb转串口驱动分析

通过probe我们最终将/dev/ttySn设备和usb_serial_port对象绑定起来,我们对/dev/ttySn设备进行操作,对应tty驱动里面的

  1. staticconststructtty_operationsserial_ops={
  2. .open=serial_open,
  3. .close=serial_close,
  4. .write=serial_write,
  5. .hangup=serial_hangup,
  6. .write_room=serial_write_room,
  7. .ioctl=serial_ioctl,
  8. .set_termios=serial_set_termios,
  9. .throttle=serial_throttle,
  10. .unthrottle=serial_unthrottle,
  11. .break_ctl=serial_break,
  12. .chars_in_buffer=serial_chars_in_buffer,
  13. .tiocmget=serial_tiocmget,
  14. .tiocmset=serial_tiocmset,
  15. .cleanup=serial_cleanup,
  16. .install=serial_install,
  17. .proc_fops=&serial_proc_fops,
  18. };
  1. staticconststructtty_operationsserial_ops={
  2. .open=serial_open,
  3. .close=serial_close,
  4. .write=serial_write,
  5. .hangup=serial_hangup,
  6. .write_room=serial_write_room,
  7. .ioctl=serial_ioctl,
  8. .set_termios=serial_set_termios,
  9. .throttle=serial_throttle,
  10. .unthrottle=serial_unthrottle,
  11. .break_ctl=serial_break,
  12. .chars_in_buffer=serial_chars_in_buffer,
  13. .tiocmget=serial_tiocmget,
  14. .tiocmset=serial_tiocmset,
  15. .cleanup=serial_cleanup,
  16. .install=serial_install,
  17. .proc_fops=&serial_proc_fops,
  18. };

比如我们对/dev/ttySn进行写操作,write->serial_write

  1. staticintserial_write(structtty_struct*tty,constunsignedchar*buf,
  2. intcount)
  3. {
  4. structusb_serial_port*port=tty->driver_data;
  5. intretval=-ENODEV;
  6. if(port->serial->dev->state==USB_STATE_NOTATTACHED)
  7. gotoexit;
  8. dbg("%s-port%d,%dbyte(s)",__func__,port->number,count);
  9. /*passontothedriverspecificversionofthisfunction*/
  10. retval=port->serial->type->write(tty,port,buf,count);
  11. exit:
  12. returnretval;
  13. }
  1. staticintserial_write(structtty_struct*tty,constunsignedchar*buf,
  2. intcount)
  3. {
  4. structusb_serial_port*port=tty->driver_data;
  5. intretval=-ENODEV;
  6. if(port->serial->dev->state==USB_STATE_NOTATTACHED)
  7. gotoexit;
  8. dbg("%s-port%d,%dbyte(s)",__func__,port->number,count);
  9. /*passontothedriverspecificversionofthisfunction*/
  10. retval=port->serial->type->write(tty,port,buf,count);
  11. exit:
  12. returnretval;
  13. }


struct usb_serial_port *port = tty->driver_data;找到了这个tty对象所对应的port对象,

port对象里面有驱动信息,urb的缓冲区信息,最终调用的是我们写的用户驱动里面的write方法。

用户驱动通过信使URB将想要发送的数据发送出去。

总结:

通过分析usb 串口驱动可以推测出其他usb设备大致的工作方式,下一步将分析usb网卡的驱动。

linux复杂的代码结构其实是有面向对象的思想