Linux CDC ACM驱动介绍

一、实现原理

CDC ACM驱动实现以USB设备驱动和tty设备驱动为基础,可将USB设备驱动的实现看作tty驱动和硬件之间数据流转换的桥梁。也即,整个USB CDC协议的实现均体现在USB部分,USB设备的描述符定义,CDC虚拟串口的设定,线路状态设置与读取,串口数据的发送与读取均遵循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设备操作与tty操作函数集

    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 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驱动第三节的tty数据发送,相当于将串口数据需要转换为USB数据,再经由USB设备驱动传递给CDC串口硬件。

对应tty数据接收,相当于硬件接收的数据先经过USB传递给USB设备驱动,在USB数据接收处理中将串口数据传递给tty驱动程序。

其他总线,如SPI转串口,I2C转串口,蓝牙转串口,与此同理。

三、实例分析

以USB转串口芯片CH342为例,介绍USB串口设备在Linux系统中识别过程。

可直接通过lsusb命令查看系统下所有usb设备

可以其后加“-v”查看更多设备信息。

通过dmesg内核消息查看设备连接信息及驱动匹配情况

可以修改内核消息等级查看更多驱动log日志。

Linux支持不同等级的日志输出,可在驱动代码中添加调试信息。系统的日志等级可以通过:/proc/sys/kernel/printk进行设定。

posted @ 2022-07-25 09:50  WCH_SoftGroup  阅读(1889)  评论(0编辑  收藏  举报