USB CDC类、USB2.0标准与PSTN之间的关系
CDC(Communication Device Class)类是USB2.0标准下的一个子类,定义了通信相关设备的抽象集合。它与USB2.0标准以及其下的子类的相互关系如下图所示:
如上图,USB2.0标准下定义了很多子类,有音频类,CDC类,HID,打印,大容量存储类,HUB,智能卡等等,这些在urb.org官网上有具体的定义,这里我们主要讲的是通信类CDC,CDC类下面,根据具体的应用场合,又有一些子类,这里我们主要讲的是PSTN(Public Switched Telephone Network)。从PSTN官方标准文档来看,PSTN子类是一个与电信相关的子类,而这里,我们只是将它作为一个普通的通信设备使用,并没有使用到它的一些电话特性。
从一个具体的CDC类通信数据说起
如上图,USB CDC类的通信部分主要包含三部分:枚举过程、虚拟串口操作和数据通信。其中虚拟串口操作部分并不一定强制需要,因为若跳过这些虚拟串口的操作,实际上USB依然是可以通信的,这也就是为什么上图中,在操作虚拟串口之前会有两条数据通信的数据。
之所以会有虚拟串口操作,主要是我们通常使用PC作为host端,在PC端使用一个串口工具来与其进行通信,PC端的对应驱动将其虚拟成一个普通串口。这样以来,可以方便PC端软件通过操作串口的方式来与其进行通信。但实际上,Host端与Device端物理上是通过USB总线来进行通信的,与串口没有关系,这一虚拟化过程,起决定性作用的是对应驱动,包含如何将每一条具体的虚拟串口操作对应到实际上的USB的操作等。这里需要注意的是,Host端与Device端的USB通信速率并不受所谓的串口波特率影响,它就是标准的USB2.0全速(12Mbps)速度,实际速率取决于总线的实际使用率,驱动访问USB外设有效速率以及外部环境对通信本身造成的干扰率等因素。
CDC类设备枚举过程
CDC类设备与其他标准USB设备枚举过程并没有什么特殊的地方,在描述符内可以使用DeviceClass=0x00, SubClass=0x00, Protocol=0x00 表示此类信息在接口描述符内给出;或者也可以使用0x02,0x00,0x00 来表明该设备为CDC类设备。或者使用0xef, 0x02,0x01表示当前为复合设备。
CDC类设备在枚举过程中最主要的信息存储在配置描述符内:
如上图所示,CDC类的配置描述符一般包含两个接口(interface0)为控制接口,另外一个是Interface1为数据接口,除此之外,还有一个虚线指向IAD(Interface Association Description)这个是可选的,得根据实际情况来确定其是否真实存在。
控制接口
控制接口侠包含一个类描述符和一个端口(ie:0x82),这个端点(中断传输模式)为异步通知消息的端点,当设备需要向Host端发送异步消息时,可以通过此端点来发送,但平时主机端都是通过端点0来向设备端发送控制消息的,比如哪些虚拟串口的操作指令等。
CDC-ACM
CDC ACM驱动实现以USB设备驱动和tty设备驱动为基础,可将USB设备驱动的实现看作tty驱动和硬件之间数据流转换的桥梁,即整个USB CDC协议的实现均体现在USB部分,USB设备的描述符定义、CD虚拟串口的设定、线路状态设置与读取、串口数据的发送与读取均遵循CDC协议规范。
驱动路径:/drivers/usb/class/cdc-acm.c
注册USB设备驱动&tty设备驱动
/*
* Init / exit.
*/
static int __init acm_init(void)
{
int retval;
acm_tty_driver = alloc_tty_driver(ACM_TTY_MINORS);
if (!acm_tty_driver)
return -ENOMEM;
acm_tty_driver->driver_name = "acm",
acm_tty_driver->name = "ttyACM",
acm_tty_driver->major = ACM_TTY_MAJOR,
acm_tty_driver->minor_start = 0,
acm_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
acm_tty_driver->subtype = SERIAL_TYPE_NORMAL,
acm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
acm_tty_driver->init_termios = tty_std_termios;
acm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD |
HUPCL | CLOCAL;
tty_set_operations(acm_tty_driver, &acm_ops);
retval = tty_register_driver(acm_tty_driver);
if (retval) {
put_tty_driver(acm_tty_driver);
return retval;
}
retval = usb_register(&acm_driver);
if (retval) {
tty_unregister_driver(acm_tty_driver);
put_tty_driver(acm_tty_driver);
return retval;
}
printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n");
return 0;
}
实现USB设备操作
static struct usb_driver acm_driver = {
.name = "cdc_acm",
.probe = acm_probe,
.disconnect = acm_disconnect,
#ifdef CONFIG_PM
.suspend = acm_suspend,
.resume = acm_resume,
.reset_resume = acm_reset_resume,
#endif
.pre_reset = acm_pre_reset,
.id_table = acm_ids,
#ifdef CONFIG_PM
.supports_autosuspend = 1,
#endif
.disable_hub_initiated_lpm = 1,
};
实现TTY设备操作集
/*
* TTY driver structures.
*/
static const struct tty_operations acm_ops = {
.install = acm_tty_install,
.open = acm_tty_open,
.close = acm_tty_close,
.cleanup = acm_tty_cleanup,
.hangup = acm_tty_hangup,
.write = acm_tty_write,
.write_room = acm_tty_write_room,
.ioctl = acm_tty_ioctl,
.throttle = acm_tty_throttle,
.unthrottle = acm_tty_unthrottle,
.chars_in_buffer = acm_tty_chars_in_buffer,
.break_ctl = acm_tty_break_ctl,
.set_termios = acm_tty_set_termios,
.tiocmget = acm_tty_tiocmget,
.tiocmset = acm_tty_tiocmset,
.get_serial = get_serial_info,
.set_serial = set_serial_info,
.get_icount = acm_tty_get_icount,
};
完成USB数据到tty数据流的转换
tty数据发送,相当于将串口数据需要转换为USB数据,再经由USB设备驱动传递给CDC串口硬件。
tty数据接收,相当于硬件接受的数据先经过USB传递给USB设备驱动,在USB数据接收处理中将串口数据传递给TTY驱动程序。
其他总线,如SPI转串口,I2C转串口,蓝牙转串口等,与此同理。
实例分析
以USB转串口芯片CH342为例,介绍USB串口设备在Linux系统中识别过程。
可直接通过lsusb命令查看系统下所有USB设备
通过lsusb -v查看详细信息。通过dmesg内核消息查看设备连接信息及驱动匹配情况。
可以修改内核消息等级查看更多驱动log。Linux支持不同等级的日志输出,可在驱动代码中添加调试信息。系统的日志等级可以通过:/proc/sys/kernel/printk进行设定。