Linux内核驱动开发入门:我是如何给一个虚拟CDC ACM设备写“Hello World”驱动的
Linux内核驱动开发入门手把手实现虚拟CDC ACM设备驱动第一次接触Linux内核驱动开发时面对复杂的代码结构和晦涩的概念我完全摸不着头脑。直到导师扔给我一个USB转串口设备试试看能不能让它在Linux上工作。经过两周的挣扎我终于理解了CDC ACM驱动如何作为USB与TTY子系统之间的桥梁。今天我们就从零开始用QEMU模拟一个最简单的虚拟CDC ACM设备实现一个能打印Hello World的基础驱动。1. 环境准备与基础概念在开始编码前我们需要搭建一个安全的开发环境。推荐使用Ubuntu 22.04 LTS作为开发主机配合QEMU虚拟机进行测试。这样即使驱动崩溃也不会影响主系统稳定性。必备工具链安装sudo apt install build-essential libncurses-dev flex bison libssl-dev qemu-system-x86CDC ACMCommunication Device Class Abstract Control Model是USB协议中定义的一种设备类别常用于USB转串口设备。它的核心架构包含三个关键部分USB设备驱动层处理USB协议通信和设备枚举TTY子系统接口提供标准的字符设备操作数据转换层在USB数据包和串行数据流之间转换理解这个分层架构非常重要——我们的驱动本质上是在实现这两层之间的翻译器。2. 创建最简单的内核模块框架我们先从标准的内核模块模板开始。创建一个新的目录结构~/cdc_hello/ ├── Makefile └── cdc_hello.cMakefile内容obj-m : cdc_hello.o KDIR : /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M$(PWD) modules clean: make -C $(KDIR) M$(PWD) clean基础模块代码cdc_hello.c#include linux/module.h #include linux/kernel.h static int __init cdc_hello_init(void) { printk(KERN_INFO CDC Hello: module loaded\n); return 0; } static void __exit cdc_hello_exit(void) { printk(KERN_INFO CDC Hello: module unloaded\n); } module_init(cdc_hello_init); module_exit(cdc_hello_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(Simple CDC ACM Hello World Driver);编译并测试这个空模块make sudo insmod cdc_hello.ko dmesg | tail -n 1 # 应该看到加载消息 sudo rmmod cdc_hello dmesg | tail -n 1 # 应该看到卸载消息3. 实现USB设备驱动基础现在我们来添加USB支持。CDC ACM驱动需要两个关键结构体usb_driver和usb_device_id。首先扩展头文件包含#include linux/usb.h #include linux/tty.h #include linux/tty_driver.h定义我们的虚拟USB设备IDstatic struct usb_device_id cdc_hello_table[] { { USB_DEVICE(0x1234, 0x5678) }, // 自定义的厂商ID和产品ID { } /* 终止项 */ }; MODULE_DEVICE_TABLE(usb, cdc_hello_table);实现probe和disconnect函数static int cdc_hello_probe(struct usb_interface *interface, const struct usb_device_id *id) { printk(KERN_INFO CDC Hello: Device connected\n); return 0; } static void cdc_hello_disconnect(struct usb_interface *interface) { printk(KERN_INFO CDC Hello: Device disconnected\n); }定义usb_driver结构体static struct usb_driver cdc_hello_driver { .name cdc_hello, .probe cdc_hello_probe, .disconnect cdc_hello_disconnect, .id_table cdc_hello_table, };修改模块初始化和退出函数static int __init cdc_hello_init(void) { int retval; printk(KERN_INFO CDC Hello: initializing\n); retval usb_register(cdc_hello_driver); if (retval) { printk(KERN_ERR USB register failed: %d\n, retval); return retval; } return 0; } static void __exit cdc_hello_exit(void) { usb_deregister(cdc_hello_driver); printk(KERN_INFO CDC Hello: driver unregistered\n); }现在我们已经有了一个能识别特定USB设备的基础框架。可以用QEMU测试这个驱动是否能正确识别虚拟设备。4. 集成TTY子系统真正的CDC ACM驱动需要在USB和TTY之间建立桥梁。让我们实现最基础的TTY操作。首先定义tty_driver和tty_operations结构体static struct tty_driver *cdc_hello_tty_driver; static int cdc_hello_tty_open(struct tty_struct *tty, struct file *file) { printk(KERN_INFO CDC Hello: tty opened\n); return 0; } static void cdc_hello_tty_close(struct tty_struct *tty, struct file *file) { printk(KERN_INFO CDC Hello: tty closed\n); } static int cdc_hello_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) { printk(KERN_INFO CDC Hello: writing %d bytes\n, count); return count; } static const struct tty_operations cdc_hello_tty_ops { .open cdc_hello_tty_open, .close cdc_hello_tty_close, .write cdc_hello_tty_write, };更新probe函数来注册TTY设备static int cdc_hello_probe(struct usb_interface *interface, const struct usb_device_id *id) { int retval; printk(KERN_INFO CDC Hello: Device connected\n); // 分配TTY驱动 cdc_hello_tty_driver alloc_tty_driver(1); if (!cdc_hello_tty_driver) return -ENOMEM; // 设置TTY驱动属性 cdc_hello_tty_driver-driver_name cdc_hello; cdc_hello_tty_driver-name ttyHELLO; cdc_hello_tty_driver-type TTY_DRIVER_TYPE_SERIAL; cdc_hello_tty_driver-subtype SERIAL_TYPE_NORMAL; cdc_hello_tty_driver-flags TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; tty_set_operations(cdc_hello_tty_driver, cdc_hello_tty_ops); // 注册TTY驱动 retval tty_register_driver(cdc_hello_tty_driver); if (retval) { printk(KERN_ERR Failed to register TTY driver\n); put_tty_driver(cdc_hello_tty_driver); return retval; } return 0; }同时更新disconnect函数static void cdc_hello_disconnect(struct usb_interface *interface) { printk(KERN_INFO CDC Hello: Device disconnected\n); tty_unregister_driver(cdc_hello_tty_driver); put_tty_driver(cdc_hello_tty_driver); }现在我们的驱动已经能够创建/dev/ttyHELLO设备节点了。虽然还不能真正通信但已经完成了基本的框架搭建。5. 实现数据通路与测试最后我们来实现最简单的数据回显功能。首先在probe函数中添加一个urbUSB Request Block用于接收数据static void cdc_hello_rx_complete(struct urb *urb) { struct tty_struct *tty urb-context; if (urb-status) { printk(KERN_ERR CDC Hello: RX error %d\n, urb-status); return; } tty_insert_flip_string(tty, urb-transfer_buffer, urb-actual_length); tty_flip_buffer_push(tty); // 重新提交URB继续接收 usb_submit_urb(urb, GFP_ATOMIC); }更新probe函数设置USB通信static int cdc_hello_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_device *udev interface_to_usbdev(interface); struct usb_endpoint_descriptor *ep_in, *ep_out; // ...之前的TTY初始化代码... // 查找批量传输端点 if (!usb_find_common_endpoints(interface-cur_altsetting, ep_in, ep_out, NULL, NULL)) { printk(KERN_ERR CDC Hello: no bulk endpoints found\n); return -ENODEV; } // 创建接收URB cdc_hello_rx_urb usb_alloc_urb(0, GFP_KERNEL); if (!cdc_hello_rx_urb) return -ENOMEM; // 设置URB参数 usb_fill_bulk_urb(cdc_hello_rx_urb, udev, usb_rcvbulkpipe(udev, ep_in-bEndpointAddress), cdc_hello_rx_buffer, sizeof(cdc_hello_rx_buffer), cdc_hello_rx_complete, tty); // 提交URB开始接收数据 usb_submit_urb(cdc_hello_rx_urb, GFP_KERNEL); return 0; }更新write操作实现真实的数据发送static int cdc_hello_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) { struct urb *urb; int retval; urb usb_alloc_urb(0, GFP_KERNEL); if (!urb) return -ENOMEM; usb_fill_bulk_urb(urb, cdc_hello_udev, usb_sndbulkpipe(cdc_hello_udev, ep_out-bEndpointAddress), (void *)buf, count, cdc_hello_tx_complete, NULL); retval usb_submit_urb(urb, GFP_KERNEL); if (retval) { usb_free_urb(urb); return retval; } return count; }现在你可以编译加载这个模块然后在QEMU虚拟机中测试echo Hello World /dev/ttyHELLO通过dmesg应该能看到数据发送和接收的日志。虽然这个实现非常基础但它展示了CDC ACM驱动最核心的工作原理。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2478260.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!