简介:Linux USB驱动是连接外部USB设备与系统内核的桥梁,包括主机控制器驱动、通用USB驱动堆栈和特定设备驱动。本文将详细介绍Linux USB驱动的组成部分和核心概念,如设备枚举、USB设备类、传输类型、设备描述符、设备驱动模型、API、用户空间访问、设备热插拔、调试工具和内核模块编译。通过理解这些概念,开发者可以编写适用于各种USB设备的驱动程序,实现设备与Linux系统的有效通信。
1. Linux USB驱动架构组成
Linux操作系统通过一种分层的架构来管理USB设备,这个架构允许开发者和用户高效地利用USB设备。Linux的USB驱动架构主要由以下几个部分组成:
首先,位于最底层的是USB核心(USB Core),它提供了USB协议的实现以及设备管理的基本框架。USB核心负责处理通用的USB操作,如设备的枚举、数据传输等。
接着是USB主机控制器驱动(USB Host Controller Driver),它负责与计算机硬件直接交互,例如通过USB端口发送和接收数据。这个层面上的驱动需要根据不同的硬件(如Intel, AMD等)单独编写。
紧接着是USB设备驱动(USB Device Driver),它们是针对特定USB设备编写的,将USB核心提供的功能应用到具体设备上,比如打印机、键盘、存储设备等。这些驱动通常由设备制造商提供或由社区开发。
最后,为了让用户能够访问这些设备,Linux还提供了用户空间的接口,如udev系统和libusb库,它们为设备文件的创建、权限管理以及用户空间程序访问USB设备提供了支持。
graph TD
A[用户空间] -->|使用udev和libusb| B(USB设备驱动)
B -->|操作| C(USB核心)
C -->|管理| D(USB主机控制器驱动)
D -->|与硬件交互| E[USB硬件]
了解这些基础架构有助于开发者深入理解Linux中USB设备的工作方式,同时为后续的开发和调试工作打下坚实的基础。在后续章节中,我们将逐步探索Linux USB驱动架构的每一个组成部分,并深入解析其工作原理。
2. 设备枚举过程与USB设备分类
在深入探讨USB驱动开发之前,必须对设备枚举过程和USB设备分类有一个清晰的认识。这些基础知识是编写USB驱动程序的基础,理解了这些概念才能进一步探索USB驱动的高级主题。
2.1 设备枚举过程
枚举是USB设备连接到计算机后,计算机识别并初始化设备的过程。它包括多个步骤,每一步都至关重要,以确保设备可以正确地与系统通信。
2.1.1 枚举步骤概述
枚举过程从识别设备开始,继续到配置设备,最后完成设备的使用。以下是枚举过程的基本步骤:
- 设备连接:USB设备通过其物理接口连接到主机。
- 地址分配:主机通过默认地址0给新连接的设备发送一系列的控制请求来识别它。
- 设备描述符获取:通过一系列控制传输获取设备描述符以获取设备的相关信息。
- 配置:主机获取设备的配置描述符,决定如何配置设备,可能涉及选择接口和设置端点。
- 接口激活:主机激活选定的接口并开始数据传输。
每个步骤都是自检和验证的,确保设备是识别并能够按预期工作。
2.1.2 枚举过程中的关键交互
在枚举过程中,USB主机和设备之间有一系列关键的交互。这些交互通过USB协议的定义进行。
- 描述符请求:通过控制端点0发送,请求设备或配置的描述符信息。
- 设备响应:设备响应请求,返回相关的描述符信息。
- 接口和端点配置:主机根据得到的描述符信息配置接口和端点。
- 设备状态确认:主机发送状态请求确认设备是否成功配置。
这些关键的交互确保了设备正确配置并且系统了解如何与之通信。
2.2 USB设备分类
USB设备根据它们的功能和协议版本进行分类。分类有助于理解设备的行为和限制。
2.2.1 按USB版本分类
USB的不同版本有不同的特性,包括数据传输速度和电源管理能力。
- USB 1.x:提供低速(1.5 Mbps)和全速(12 Mbps)传输。
- USB 2.0:增加了高速(480 Mbps)传输能力。
- USB 3.x:进一步增加了超高速(5 Gbps)和超高速+ (10 Gbps)传输速度。
不同版本的设备和主机端口需要支持相应的协议来保证兼容性和性能。
2.2.2 按USB设备类型分类
USB设备按其功能主要分为以下几类:
- 集线器(Hub):提供额外的USB端口,允许连接更多的USB设备。
- 人机接口设备(HID):如键盘、鼠标、游戏控制器等。
- 存储设备:如U盘、移动硬盘、SSD。
- 打印机和其他影像设备。
- 音频设备:如麦克风、扬声器。
- 网络设备:提供USB接口的网络连接。
每种设备类型都遵循特定的类规范,这些规范在USB规范中有所描述,定义了设备特定的传输和协议要求。
graph TD
A[开始枚举] --> B[设备连接]
B --> C[地址分配]
C --> D[设备描述符获取]
D --> E[配置]
E --> F[接口激活]
F --> G[设备枚举完成]
以上代码块展示了枚举过程的步骤以及步骤之间的依赖关系,使用mermaid格式的流程图,以可视化的方式对USB设备的枚举步骤进行描述。
总结本章节的内容,对设备枚举过程和USB设备分类有了全面的了解。这为深入理解USB驱动架构以及更进一步的开发工作打下了坚实的基础。在本章的后续部分,我们将深入了解USB传输类型和设备描述符的解析过程。
3. USB传输类型与设备描述符解析
3.1 USB传输类型详解
在USB通信协议中,传输类型指的是数据在USB总线上传输的方式。不同类型的传输有不同的特点,适用于不同的使用场景,确保了USB通信的多样性和效率。接下来我们将深入了解四种传输类型:控制传输、批量传输、中断传输和实时传输。
3.1.1 控制传输
控制传输是用于USB设备初始化和配置的传输方式。它用于传输设备的描述符信息、设定地址、配置设备、发送命令以及获取状态信息。控制传输保证了在数据传输前,USB设备和主机之间能够达成一致的理解。
特点和使用场景: - 保证可靠传输: 由于控制传输包含了数据传输的校验机制,因此在设备初始化和重要信息交换时优先考虑。 - 优先级较高: 在USB总线管理中,控制传输通常有较高的优先级。 - 传输少量数据: 控制传输通常用于传输少量、重要的数据。
3.1.2 批量传输
批量传输主要用于不需实时性、数据量较大的场合,如数据的读写操作。它在保证传输正确性的同时,提供比控制传输更高的传输效率。
特点和使用场景: - 传输大量数据: 批量传输适用于大批量数据的传输,如打印机、扫描仪等设备。 - 无实时性要求: 在传输大量数据时,并不关注传输的实时性。 - 保证数据完整性: 尽管传输效率较高,但批量传输仍然具有一定的错误检测和重传机制,确保数据的完整性。
3.1.3 中断传输
中断传输是一种低带宽、实时性要求高的传输方式。它通常用于键盘、鼠标等输入设备,这些设备对响应时间非常敏感。
特点和使用场景: - 低延迟: 中断传输专为需要快速响应的设备设计,比如人机交互设备。 - 定期检查: 由于是周期性检查设备状态,所以称为“中断传输”。 - 小包数据: 每次传输的数据量不大,但需要保证传输的及时性。
3.1.4 实时传输
实时传输是为满足连续数据流传输需求而设计的。在音频、视频等多媒体传输场景中尤为重要。
特点和使用场景: - 连续数据流: 实时传输适合于连续的、对时间敏感的数据流,如USB音频设备。 - 带宽保证: 它提供了相对固定的带宽保证,确保数据流的平稳传输。 - 音频视频流: 最适合传输音频和视频数据,因为这些数据对时间的敏感性要求非常高。
3.2 设备描述符解析
USB设备描述符是一系列预定义的数据结构,用于描述USB设备的属性和功能。正确解析和理解这些描述符对于USB驱动开发至关重要。
3.2.1 描述符结构与内容
每个USB设备都有一个设备描述符,以及其他特定类型的描述符,如配置描述符、接口描述符和端点描述符。
- 设备描述符(Device Descriptor): 提供了设备的全局信息,如厂商ID、产品ID和设备类等。
- 配置描述符(Configuration Descriptor): 描述设备的一组功能,包括需要的电源量和该配置支持的接口数量。
- 接口描述符(Interface Descriptor): 定义了与特定接口相关的属性,例如接口类、子类和协议。
- 端点描述符(Endpoint Descriptor): 描述了特定端点的传输类型和速度等信息。
3.2.2 描述符的编程读取方法
了解描述符的结构之后,我们需要通过编程的方式读取这些信息。通常在Linux内核中,我们使用urb(USB Request Blocks)API来读取描述符。
#include <linux/usb.h>
int usb_get_descriptor(struct usb_device *dev, unsigned char type, unsigned char index, void *buf, int size);
- 参数说明:
usb_get_descriptor
函数用于读取指定USB设备的描述符。 - 参数解释:
-
dev
指向目标USB设备的指针。 -
type
描述符类型,比如USB_DT_DEVICE等。 -
index
描述符索引,对于设备描述符,此值为0。 -
buf
用于存储读取的数据的缓冲区。 -
size
缓冲区大小。
以下是一个示例代码,演示如何读取设备描述符:
struct usb_device *usb_dev;
char buf[256];
int result;
usb_dev = usb_get_dev(...); // 获取USB设备结构体指针
result = usb_get_descriptor(usb_dev, USB_DT_DEVICE, 0, buf, sizeof(buf));
if (result < 0) {
// 处理错误情况
} else {
// buf中存储了设备描述符信息
}
usb_put_dev(usb_dev); // 释放设备结构体指针
通过上述方法,我们可以获得USB设备的相关信息,并基于这些信息进行进一步的驱动开发和优化。接下来,我们将深入了解USB设备驱动模型,以及如何有效地使用USB子系统提供的API。
4. USB设备驱动模型与子系统API使用
4.1 USB设备驱动模型
4.1.1 驱动模型架构
USB设备驱动模型是Linux内核中管理USB设备的核心组件。它的主要目的是为开发者提供一个通用的框架,以便于编写适用于所有USB设备的驱动代码。USB驱动模型架构可以分为几个关键部分:USB核心、USB驱动程序和USB设备。USB核心是USB子系统的核心,负责实现USB协议的大部分细节,包括设备枚举、数据传输、设备管理等。USB驱动程序是针对特定USB设备编写的代码,它实现了与USB核心的交互,处理特定设备的特定事务。USB设备是硬件设备,当它连接到计算机时,USB核心会检测到设备并加载相应的USB驱动程序。
USB设备驱动模型的关键组件之一是USB设备的表示。在Linux内核中,每个USB设备都用一个 struct usb_device
结构体表示,该结构体包含了设备的各种属性,如厂商ID、产品ID、设备的配置等。而USB驱动程序则使用 struct usb_driver
结构体来表示,它定义了驱动程序的基本操作,如probe(探测设备)、disconnect(断开连接)等。
4.1.2 URBs(USB请求块)的概念
USB请求块(USB Request Blocks,URBs)是USB驱动程序与USB核心之间交换数据的基本机制。URBs是用于描述USB传输请求的数据结构,它们被提交给USB核心以发起数据传输或控制事务。每个URB代表一个USB事务,它可以包含一个或多个事务包(TPs),每个TP对应于USB协议栈中的一个数据包。
在Linux内核中,URBs由 struct urb
结构体表示,它包含了传输的详细信息,例如缓冲区、设备句柄、端点地址、传输类型、传输标志等。驱动程序可以通过提交URBs来与USB设备进行通信,比如读取或写入数据。
在Linux内核中提交URB的函数原型如下:
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
该函数的参数包括指向 struct urb
的指针和内存分配标志。如果传输成功提交,则返回0,否则返回错误码。
代码逻辑分析
以下是一个简单的例子,展示了如何初始化一个URB并提交给USB核心以执行一个简单的批量读取操作:
// 初始化urb结构体
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
return -ENOMEM;
// 分配缓冲区
buf = usb_alloc_coherent(usb_dev, size, GFP_ATOMIC, &urb->transfer_dma);
urb->transfer_buffer = buf;
urb->transfer_buffer_length = size;
urb->pipe = usb_rcvbulkpipe(usb_dev, endpoint);
// 设置完成句柄
urb->transfer_flags = URB_NOtransfer;
urb->transfer_buffer_length = size;
urb->complete = my_complete;
urb->context = my_context;
// 提交urb
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
usb_free_coherent(usb_dev, size, buf, urb->transfer_dma);
usb_free_urb(urb);
return ret;
}
// 完成句柄函数
void my_complete(struct urb *urb)
{
// 传输完成后的处理
}
// 清理urb
usb_free_urb(urb);
usb_free_coherent(usb_dev, size, buf, urb->transfer_dma);
在这个例子中,我们首先分配了一个URB,并为其分配了一个缓冲区用于数据传输。我们指定了端点并设置了传输标志。然后,我们定义了一个完成句柄 my_complete
,它将在传输完成后被内核调用。提交URB后,USB核心负责执行实际的数据传输。
4.2 USB子系统API使用
4.2.1 常用API函数介绍
Linux USB子系统提供了一套丰富的API来简化USB设备驱动程序的编写。下面是一些常用的API函数及其用途:
-
usb_register_driver(struct usb_driver *new_driver)
: 注册一个USB驱动程序,使其与USB核心关联,开始监听与该驱动匹配的设备。 -
usb_submit_urb(struct urb *urb, gfp_t mem_flags)
: 如之前所述,提交一个URB以执行USB传输。 -
usb_kill_urb(struct urb *urb)
: 终止一个URB的当前传输。 -
usb_free_urb(struct urb *urb)
: 释放一个URB。 -
usb_alloc_coherent(struct usb_device *dev, size_t size, gfp_t mem_flags, dma_addr_t *dma)
: 分配一个与USB设备通信的连续缓冲区,并将其映射到DMA。
4.2.2 API函数的高级用法
在复杂的驱动程序中,我们常常需要在多个地方使用URBs,例如,当一个驱动程序需要管理多个并发传输时。这时,我们需要更加灵活地控制URB的生命周期。高级API的使用包括异步传输、中断传输和控制传输的处理,以及URB的取消、同步和错误处理。
异步传输
在异步传输中,驱动程序可以提交一个URB,并继续执行其他任务。传输完成时,会调用之前设置的完成句柄。这种方式特别适用于不能阻塞当前执行路径的情况。
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
中断传输
USB中断传输用于处理定时的、低带宽的数据交换。与批量传输类似,但通常用于更小的数据块。
int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length, int timeout);
控制传输
控制传输是用于设备控制请求的传输,例如获取设备描述符或设置配置。
int usb_control_msg(struct usb_device *usb_dev, unsigned int pipe, __u8 request,
__u8 requesttype, __u16 value, __u16 index,
void *data, __u16 size, int timeout);
在使用这些API时,开发者需要提供正确的参数,以及当传输完成时合适的处理逻辑。这些API都是构建可靠和高效的USB驱动程序的基础。
通过以上章节内容,读者应能够理解Linux USB驱动模型的基本构成,以及如何使用USB子系统API进行设备通信和管理。这种理解是开发稳定和高性能USB设备驱动所必需的。
5. 用户空间对USB设备的访问与设备热插拔机制
5.1 用户空间对USB设备的访问方法
在Linux操作系统中,用户空间访问USB设备通常有几种方法,这里将深入分析设备文件系统(sysfs)以及libusb库的使用,并详细阐述它们的工作原理和操作方法。
5.1.1 设备文件系统(sysfs)
sysfs文件系统是一个虚拟的文件系统,它提供了内核对象的结构化视图。在USB设备管理中,sysfs暴露了许多设备相关的信息,允许用户空间程序读取和设置设备属性。
sysfs的组织结构
sysfs中的设备文件通常位于 /sys
目录下。USB设备的信息被组织在以下路径:
/sys/bus/usb/devices/
每个USB设备都有一个对应的目录,例如 2-2
,在这个目录下,有几个关键的子目录:
-
device/
:包含设备的通用信息。 -
driver/
:显示设备绑定的驱动程序。 -
power/
:包含设备的电源管理信息。 -
interface/
:USB接口的具体信息。
使用sysfs访问设备属性
要访问USB设备的属性,可以通过读写对应文件系统中的文件来完成。例如,查看设备的速度可以通过读取设备目录下的 speed
文件。
cat /sys/bus/usb/devices/2-2/device/speed
输出可能类似于:
480
表明该设备是高速(High-Speed)设备。
5.1.2 libusb库的使用
libusb是一个跨平台的库,它提供了一个简化的接口来访问USB设备。libusb不依赖于特定的驱动程序,可以用来在用户空间与USB设备进行通信。
libusb的安装与初始化
首先需要安装libusb库,然后在应用中进行初始化:
#include <stdio.h>
#include <libusb-1.0/libusb.h>
int main() {
libusb_device_handle *handle;
libusb_init(NULL);
// 获取设备句柄,这里只是示例,具体设备应该使用libusb_get_device_list获取
handle = libusb_open_device_with_vid_pid(NULL, 0x1234, 0x5678);
if (handle == NULL) {
printf("无法打开设备\n");
libusb_exit(NULL);
return -1;
}
// 在这里可以调用libusb的API进行读写操作
// 释放设备句柄
libusb_close(handle);
// 清理libusb环境
libusb_exit(NULL);
return 0;
}
libusb提供了一系列API函数,可以用来对USB设备进行操作,如读取、写入和设置配置。不过,使用libusb编程需要充分理解USB协议以及特定USB设备的协议细节。
编写libusb程序的操作步骤
- 初始化libusb环境。
- 识别并打开USB设备。
- 对设备进行配置和通信。
- 完成操作后关闭设备并释放资源。
请注意,使用libusb时,需要确保你有足够的权限来访问USB设备,否则可能需要设置相应的udev规则。
通过sysfs和libusb,用户空间程序能够以不同层次和方式访问USB设备,对于需要在用户空间进行调试和开发的场景非常有用。
5.2 设备热插拔机制
热插拔(Hotplug)是USB设备的特性之一,它允许用户在不关闭系统电源的情况下连接和移除设备。本小节将探讨Linux内核对热插拔的支持以及用户空间的通知机制。
5.2.1 热插拔的内核支持
Linux内核中,USB子系统对热插拔提供了全面的支持。核心组件之一是USB核心,它负责处理所有USB设备的通用部分。当USB设备连接时,USB核心会为设备创建一个设备节点,并调用相应的驱动程序模块。
USB核心还负责管理设备的枚举过程,并将设备事件广播给感兴趣的用户空间程序。
5.2.2 用户空间通知机制
用户空间的程序可以通过监听系统消息来获得USB设备的热插拔事件。有两种主要的通知机制:sysfs通知和netlink套接字。
sysfs通知
sysfs提供了设备连接和断开的通知,可以通过轮询特定文件(如 /sys/kernel/debug/usb/devices
)来检查设备变化,或者通过创建设备文件的软链接到一个可监控的目录来实现。
netlink套接字
netlink是一种特殊的网络套接字,允许内核空间和用户空间程序之间进行双向通信。netlink套接字可以用来实现USB设备的热插拔事件通知。
下面是一个创建netlink socket并注册监听USB热插拔事件的简单例子:
#include <asm/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/usb_device.h>
#define NETLINK_USER 31
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl src_addr, dest_addr;
struct nlmsgerr *err;
struct msghdr msg;
int sock_fd;
int result;
int error;
int seq = 0;
int pid = getpid();
sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = pid;
bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr));
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // for Linux Kernel
dest_addr.nl_groups = 1; // for multicast subscription
// Join multicast group with id 1 to receive messages
setsockopt(sock_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &dest_addr, sizeof(dest_addr));
// Prepare message header
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = (struct iovec *)&nlh;
msg.msg_iovlen = 1;
while (1) {
memset(nlh, 0, sizeof(*nlh));
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct usb_device));
nlh->nlmsg_pid = pid;
nlh->nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
nlh->nlmsg_type = NETLINK_KOBJECT_UEVENT;
result = sendmsg(sock_fd, &msg, 0);
while ((nlh = (struct nlmsghdr *)recvmsg(sock_fd, &msg, 0)) != NULL && NLMSG_OK(nlh, result)) {
if (nlh->nlmsg_type == NLMSG_DONE)
break;
if (nlh->nlmsg_type == NLMSG_ERROR) {
err = (struct nlmsgerr *)NLMSG_DATA(nlh);
error = -err->error;
printf("Error: %d\n", error);
exit(1);
}
printf("Received message payload: %s\n", NLMSG_DATA(nlh));
nlh = NLMSG.Next(nlh);
}
}
这段代码创建了一个netlink socket并监听了USB设备事件。当然,在真实使用中,会需要更多的错误处理和事件解析逻辑。
热插拔机制使Linux系统更加友好和高效,开发者可以利用上述机制,为USB设备开发自动化的工具或应用程序,例如自动安装驱动程序或者启动特定的服务。
6. USB驱动开发调试工具与内核模块编译管理
6.1 USB驱动开发调试工具
6.1.1 常见的调试工具介绍
在Linux系统中,调试USB驱动时有多种工具可供使用。其中最为常见的有 dmesg
命令、 usbmon
、 gdb
和 SystemTap
等。
-
dmesg
命令用于输出和控制内核环形缓冲区的内容,它能够显示启动信息以及设备驱动加载的信息,非常适合于查看内核启动过程中的USB事件和错误信息。 -
usbmon
是一个用于捕获USB事件的工具,它提供了USB设备的详细I/O数据,有助于分析USB通信过程中的性能瓶颈。 -
gdb
(GNU Debugger)是一个功能强大的调试器,可以用来调试运行中的程序,包括内核模块。配合特定的插件,如内核模块的kgdb,可以用于USB驱动的调试。 -
SystemTap
允许开发者在不需要重新编译内核的情况下,收集内核运行时的信息。它通过脚本提供动态的调试和性能分析。
6.1.2 调试工具的使用技巧
使用 dmesg
来追踪USB设备的加载信息,可以运行如下命令:
dmesg | grep usb
此命令会筛选出包含 "usb" 字符串的所有内核消息,这些消息通常包括设备的注册和移除事件。
usbmon
工具的使用涉及到 pcap
接口,查看USB传输的命令如下:
sudo cat /sys/kernel/debug/usb/usbmon/1t
其中 1
表示USB总线编号, t
表示传输(transaction)日志, p
表示数据包(packet)日志。
使用 gdb
调试USB驱动时,首先需要确保内核配置中启用了kgdb支持,然后在加载驱动之前启动 gdb
并附加到内核:
sudo gdb /path/to/vmlinux
(gdb) target remote /dev/ttyUSB0
其中 /path/to/vmlinux
是内核映像的路径, /dev/ttyUSB0
是与目标机器连接的串行控制台。
最后, SystemTap
的使用比前几个工具更为复杂,需要编写脚本来定义探针点和数据收集逻辑。一个基本的脚本示例如下:
probe kernel.function("usb_submit_urb")
{
printf("urb submitted with length %d\n", $urb->transfer_buffer_length);
}
该脚本会在内核函数 usb_submit_urb
被调用时输出所提交的urb的长度。
6.2 内核模块编译与管理
6.2.1 内核模块编译过程
内核模块的编译通常涉及到使用 make
工具。首先需要一个正确的Makefile,它定义了如何编译模块。以下是一个简单的Makefile示例:
obj-m += usb_driver.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
在这里, usb_driver.c
是你的驱动源文件, obj-m
指定了要编译的模块。 make
命令会编译指定的模块,并生成 usb_driver.ko
。
6.2.2 内核模块的加载与卸载
编译完成后,可以使用 insmod
命令加载模块:
sudo insmod usb_driver.ko
使用 rmmod
卸载模块:
sudo rmmod usb_driver
加载模块后,系统会自动打印出模块初始化的信息。而卸载模块时,确保没有任何用户空间的程序正在使用该模块,否则会出现错误。
6.2.3 模块参数的使用与管理
模块参数允许你在加载模块时设置模块的行为。模块源代码中通过 module_param
宏定义参数,然后通过 /sys/module/<module_name>/parameters/
目录来访问和修改这些参数。
例如,假设你的模块中定义了一个 debug
参数:
static int debug = 0;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "Enable debug messages");
加载模块时,可以通过命令行来设置这个参数:
sudo insmod usb_driver.ko debug=1
如果模块已经加载,可以通过写入 sysfs
文件系统来设置参数:
echo 1 | sudo tee /sys/module/usb_driver/parameters/debug
以上内容介绍了调试USB驱动的常见工具及其使用技巧,并详细说明了如何编译内核模块,以及如何管理这些模块及其参数。这些知识对于开发和维护USB驱动来说是必不可少的。
简介:Linux USB驱动是连接外部USB设备与系统内核的桥梁,包括主机控制器驱动、通用USB驱动堆栈和特定设备驱动。本文将详细介绍Linux USB驱动的组成部分和核心概念,如设备枚举、USB设备类、传输类型、设备描述符、设备驱动模型、API、用户空间访问、设备热插拔、调试工具和内核模块编译。通过理解这些概念,开发者可以编写适用于各种USB设备的驱动程序,实现设备与Linux系统的有效通信。