我们知道,Linux对于USB设备的支持是十分强大的,USB接口的无线网卡、USB的摄像头、USB键盘、鼠标等等。做些许代码修改,内核选项选中支持后,即可使用这些设备。但是我们要清楚,越是简单的使用,其背后就越是复杂的设计。粗略翻了翻《Linux 那些事儿之我是U盘》,才意识到Linux的USB host驱动有多复杂,不是两三天就能读懂的。( T^T..)。
这篇博文仅分析一下从内核初始化到USB设备插入USB接口到虚拟出tty设备的过程。
一、Linux初始化过程
1、首先在usb.c 中:
/* drivers\usb\core\usb.c */
static int __init usb_init(void)
{
int retval;
... ...
retval = bus_register(&usb_bus_type);
... ...
}
可以看到在usb_init
初始化函数中调用了bus_register
注册了一条USB总线。
还是在这个函数下接着:
/* drivers\usb\core\usb.c */
... ...
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
if (!retval)
goto out;
... ...
将usb_generic_driver
这个驱动程序注册给系统.
/* drivers\usb\core\generic.c */
struct usb_device_driver usb_generic_driver = {
.name = "usb",
.probe = generic_probe,
.disconnect = generic_disconnect,
#ifdef CONFIG_PM
.suspend = generic_suspend,
.resume = generic_resume,
#endif
.supports_autosuspend = 1,
};
里面包含了它自己的probe
函数。
2、在usb_serial_init
中:
/*drivers\usb\serial\usb-serial.c*/
static int __init usb_serial_init(void)
{
... ...
result = tty_register_driver(usb_serial_tty_driver);
if (result) {
printk(KERN_ERR "usb-serial: %s - tty_register_driver failed\n", __func__);
goto exit_reg_driver;
}
... ...
}
usb_serial_init()
函数会调用tty_register_driver(usb_serial_tty_driver)
向内核注册tty类的设备驱动,并在USB转串口总线上添加这个驱动。
3、在option.c
的option_init()
中:
/*drivers\usb\serial\option.c*/
static int __init option_init(void)
{
int retval;
retval = usb_serial_register(&option_1port_device);
if (retval)
goto failed_1port_device_register;
retval = usb_register(&option_driver);
if (retval)
goto failed_driver_register;
printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
DRIVER_DESC "\n");
return 0;
failed_driver_register:
usb_serial_deregister(&option_1port_device);
failed_1port_device_register:
return retval;
}
可以看到:1.调用usb_serial_register(&option_1port_device)
在USB转串口总线上注册驱动option_1port_device
(注意,这仅仅是在总线上注册,并不向内核注册)。
2.调用usb_register(&option_driver)
在USB总线上注册USB驱动,该驱动是接口的驱动。
二、USB设备插入
1.当我们的USB Modem设备插入USB端口时,要调用bus_add_device()
在USB总线上添加一个USB设备。
2.该USB设备由于有USB设备号,会找到刚才注册的usb_generic_driver
中的generic_probe()
函数,在这个函数中经过一系列的函数调用最后会 进入usb_set_configuration()
。
3.usb_set_configuration()
函数会根据HOST和Device沟通的情况,进行总线枚举,我使用的fl2440上有5个USB接口,所以会生成5个interface,该函数会依次将这5个interface添加到USB总线上。
4.每个interface会根据VID和PID找到合适自己的probe函数,这里我们设备的4个接口会依次进入usb_serial_probe()
。
三、虚拟出ttyUSB设备
1.进入usb_serial_probe()
后,首先生成5个usb_serial_port
,port0,port1,port2,port3,port4。接着调用device add()
函数 ,再调用tty_register_device()
。
2.tty_register_device()
函数主要做了三件事:
(1)向系统注册这5个串口设备。
(2)将串口设备,次设备号,串口驱动usb_serial_tty_driver
绑定到一起。
(3)在/dev目录下生成/dev/ttyUSB0,/dev/ttyUSB1,/dev/ttyUSB2,/dev/ttyUSB3,/dev/ttyUSB4
共5个设备。
回想当初在做移植3G无线上网卡时,要让Linux识别出我们得上网卡,只需要在option.c
的option_ids[]
里添加上自己的设备,包括厂家ID、设备ID。再配置内核选项后,即可识别我的3G上网卡。但是背后的工作流程可没这么简单的,这篇博文仍然比较片面,问题多多。这里仅做一个记录吧!
参考的资料:
《Linux 那些事儿之我是U盘》
http://blog.chinaunix.net/uid-20742320-id-4266188.html
http://blog.csdn.net/txxm520/article/details/8934706
http://blog.csdn.net/bingqingsuimeng/article/details/7834878