一、总线速度及主机控制器
1、USB系统架构
USB通信协议采用主从结构,实现主机控制器和外围设备的通信。

1. CPU(中央处理器)
- 功能:CPU是计算机的大脑,负责执行指令和处理数据。它是整个系统的控制中心,所有的计算和逻辑操作都在这里完成。
2. 北桥芯片
- 功能:北桥芯片是主板上连接CPU和其他高速设备的桥梁。它负责管理CPU与内存、显卡等高速设备之间的数据传输。在现代主板中,北桥的功能通常被集成到CPU或芯片组中。
3. 南桥芯片
- 功能:南桥芯片负责管理低速设备的连接,如USB、音频、网络等。它通过PCI总线与北桥芯片通信,将低速设备的数据传输到北桥,再由北桥传输到CPU。
4. PCI总线
- 功能:PCI(Peripheral Component Interconnect)总线是一种扩展总线标准,用于连接主板上的各种扩展卡和设备。在图中,PCI总线连接了南桥芯片和USB主机控制器。
5. USB主机控制器
- 功能:USB主机控制器是管理USB设备的硬件组件。它负责与USB设备进行通信,处理USB数据的传输和控制。在图中,USB主机控制器通过PCI总线与南桥芯片连接。
6. USB主机收发器
- 功能:USB主机收发器是USB主机控制器的一部分,负责将USB数据信号转换为适合在USB线缆上传输的电信号。图中显示了两个USB主机收发器,分别连接到两个A型接口。
7. A型接口
- 功能:A型接口是USB接口的一种标准,通常用于连接USB设备到主机。图中显示了两个A型接口,分别连接到两个USB设备。
8. USB设备
- 功能:USB设备是通过USB接口连接到计算机的外部设备,如U盘、鼠标、键盘等。图中显示了两个USB设备,分别连接到两个A型接口。
USB系统主机端提供为4个引脚的A型接口,USB外围设备 通过4个引脚的B型接口和主机端相连。那4个引脚(一条电压连VBUS、一条地线GND、一条正方向传输数据的D+和一条饭香香传输数据的D-线)。USB主机和USB设备收发器可以采用相同型号的芯片。
USB接口通常有四个引脚,分别是:
VBUS(电源线)
- 功能:提供5V直流电源,用于给USB设备供电。USB设备可以通过VBUS引脚从主机端获取电力,从而不需要额外的电源适配器。
- 电压:通常为5V。
GND(地线)
- 功能:提供接地参考点,用于确保USB设备和主机端之间的信号传输稳定。GND引脚与主机端的接地系统相连,确保信号传输的准确性。
- 电压:0V。
D+(正数据线)
- 功能:用于传输数据的正方向信号。在USB数据传输过程中,D+和D-引脚共同组成差分信号对,用于提高数据传输的抗干扰能力和传输速率。
- 电压:在数据传输过程中,D+引脚的电压会在0V和3.3V之间变化。
D-(负数据线)
- 功能:用于传输数据的负方向信号。与D+引脚共同组成差分信号对,用于提高数据传输的抗干扰能力和传输速率。
- 电压:在数据传输过程中,D-引脚的电压会在0V和3.3V之间变化。
USB主机和设备收发器
在USB系统中,主机端和设备端的收发器可以采用相同型号的芯片,这是因为USB协议规定了统一的数据传输标准和接口规范。无论是在主机端还是设备端,收发器都需要遵循USB协议,确保数据传输的准确性和稳定性。
USB接口的连接方式
USB接口通常有两种类型:A型接口和B型接口。A型接口通常用于主机端,B型接口通常用于设备端。在连接时,A型接口和B型接口通过四根线(VBUS、GND、D+、D-)相连,确保电源和数据的正常传输。
- A型接口:通常位于主机端,用于连接各种USB设备,如U盘、鼠标、键盘等,是下图中右侧的接口。
- B型接口:通常位于设备端,用于连接主机端的A型接口,如打印机、扫描仪、外部硬盘等,是下图中左侧的接口。
2.嵌入式设备的USB架构图
1. 嵌入式SOC(System on a Chip,片上系统)
- CPU核心:这是整个系统的控制中心,负责执行各种指令和任务。
- LCD控制器:负责控制和驱动LCD显示屏,显示信息。
- 内部局域总线:连接CPU核心和各个控制器,实现数据传输。
2. USB控制器
- 主机控制器(总线A和总线B) :负责控制USB主机功能,通过USB主机收发器与外部USB设备通信。
- 设备控制器(总线C) :负责控制USB设备功能,通过USB设备收发器与外部USB主机通信。
- OTG控制器(总线D) :On-The-Go控制器,允许设备在主机和设备模式之间切换,通过USB OTG收发器与外部设备通信。
3. USB收发器
- USB主机收发器:与主机控制器相连,负责数据的发送和接收,通过A型接口与外部USB设备通信。
- USB设备收发器:与设备控制器相连,负责数据的发送和接收,通过B型接口与外部USB主机通信。
- USB OTG收发器:与OTG控制器相连,负责数据的发送和接收,通过迷你AB型接口与外部设备通信。
- USB收发器负责将内部总线上的数据转换为USB接口上的数据信号,并通过USB接口的D-和D+引脚传输到外部设备。同时,USB收发器还负责将外部设备通过USB接口传输的数据信号转换为内部总线上的数据,并传递给相应的控制器。
4. 外部设备和主机
- USB笔相关设备:通过A型接口与USB主机收发器通信。
- 打印机、RF卡、内部集线器等:通过内部USB与USB主机收发器通信。
- 电脑主机PC:通过B型接口与USB设备收发器通信。
- 笔记相关设备驱动或主机PC:通过迷你AB型接口与USB OTG收发器通信。
总线速度:USB1.0标准(速度为1.5MB/s);USB1.1标准(速度为12MB/s);USB2.0标准(速度为480MB/s)。键盘或者鼠标属于低速USB标准,而USB存储器属于全速或者高速USB标准。
二、传输模式与寻址方法
1.USB传输模式为4种:控制传输模式、中断传输模式、等时传输模式、批量传输模式。

2.寻址:USB设备里面每个寻址单元成为端点。为每个端点分配的地址称为端点地址。每个端点地址都与相关的传输模式建立关系。
3.每个端点可以沿上行方向发送数据,也可以沿下行方向接收数据。沿上行方向从设备收数据叫IN传输(外设→主机);数据沿下行方向到达设备叫OUT传输(主机→外设)。
4.USB驱动整体框架结构以及USB子系统架构视图
a.USB驱动整体框架结构
用户空间
- 使用libusb库访问读写USB:在用户空间中,应用程序通过libusb库与USB设备进行通信。libusb是一个开源的库,它提供了一套API,使得用户空间的程序可以方便地与USB设备进行数据交换。应用程序通过调用libusb库中的函数,可以实现对USB设备的读写操作。
内核空间
- USB设备驱动:在内核空间中,USB设备驱动负责与具体的USB设备进行交互。每个USB设备都有一个对应的设备驱动,这个驱动程序包含了与该设备进行通信的特定协议和方法。当应用程序通过libusb库发起对USB设备的操作时,libusb库会将这些操作请求传递给内核空间的USB设备驱动,由设备驱动来执行具体的硬件操作。
- USB总线驱动:USB总线驱动是内核空间中负责管理USB总线的驱动程序。它负责协调USB设备驱动与USB主机控制器之间的通信。当USB设备驱动需要与USB设备进行数据传输时,它会通过USB总线驱动来与USB主机控制器进行交互,由USB主机控制器来完成实际的数据传输操作。
硬件层面
- USB主机控制器:USB主机控制器是计算机中负责控制USB总线的硬件设备。它负责与USB设备进行实际的数据传输。当USB总线驱动需要与USB设备进行数据传输时,它会通过USB主机控制器来完成实际的数据传输操作。
- USB设备:USB设备是连接到计算机上的外部设备,如键盘、鼠标、U盘等。它们通过USB接口与计算机进行通信。
三、USB驱动总线数据结构分析
1.USB设备描述符结构体

2.USB设备结构体类型

3.USB配置描述符结构体

4.接口描述符类型(逻辑设备)

5.功能以及关系
1. usb_device_descriptor(设备描述符)
- 功能:描述USB设备的全局信息,包括厂商ID、产品ID、USB版本、支持的配置数量等。这是主机在枚举设备时首先获取的描述符。
- 关键字段:
bcdUSB:设备遵循的USB规范版本(如USB 2.0为0x0200)。idVendor和idProduct:厂商和产品的唯一标识符,用于驱动匹配。bNumConfigurations:设备支持的配置数量。- 作用:提供设备的基本身份和功能信息,是驱动加载和设备识别的核心依据。
2. usb_device(设备结构体)
- 功能:表示一个完整的USB设备实例,包含设备的状态、配置、端点等运行时信息。
- 关键字段:
descriptor:嵌入的usb_device_descriptor结构体,存储设备描述符。actconfig:当前激活的配置(指向usb_host_config,内含usb_config_descriptor)。ep_in/ep_out:输入/输出端点数组,用于数据传输。- 作用:管理设备的生命周期、配置切换和数据通信。
3. usb_config_descriptor(配置描述符)
- 功能:描述设备的某一具体配置,如接口数量、电源需求等。一个设备可能有多个配置。
- 关键字段:
bNumInterfaces:该配置包含的接口数量。bConfigurationValue:配置的标识值,用于激活此配置。bMaxPower:设备在配置下的最大功耗(单位:2mA)。- 作用:定义设备在特定工作模式下的资源分配和功能组合。
4. usb_interface_descriptor(接口描述符)
- 功能:描述USB接口的属性,如类(Class)、子类(SubClass)、协议(Protocol)和端点数量。
- 关键字段:
bInterfaceNumber:接口的唯一编号。bNumEndpoints:接口包含的端点数量(不包括端点0)。bInterfaceClass:接口的功能类别(如HID、Mass Storage等)。- 作用:定义接口的功能特性,驱动通常绑定到特定接口而非整个设备。
5.关系图
四、 设备驱动开发
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
//Linux 内核中用于表示输入设备的结构体,它包含了输入设备的各种属性和操作方法。
//通过这个指针,代码可以对输入设备进行初始化、设置、注册以及事件的处理和发送等操作。
static struct input_dev *uk_dev;
//这是一个字符指针,用于指向一个缓冲区,该缓冲区用于存储从 USB 设备(这里是 USB 鼠标)接收到的数据
static char *usb_buf;
//dma_addr_t 是一个用于表示物理地址的数据类型。这个变量用于存储 usb_buf 缓冲区的物理地址。在使用直接内存访问(DMA)进行数据传输时,
//需要知道缓冲区的物理地址,以便硬件设备(如 USB 控制器)能够直接访问该缓冲区
static dma_addr_t usb_buf_phys;
//这是一个整型变量,用于存储从 USB 设备读取数据的长度
static int len;
//struct urb 是 USB 请求块(USB Request Block)的结构体,它是 USB 设备与内核之间进行数据传输的基本单位。
//这个指针用于指向一个分配好的 URB 结构体,通过这个 URB 可以向 USB 设备发送请求或接收数据
static struct urb *uk_urb;
//指定 USB 驱动程序能够匹配的 USB 设备类型
/*
在 Linux 内核的 USB 驱动框架中,当有新的 USB 设备插入系统时,内核会遍历已注册的 USB 驱动程序,
检查每个驱动程序的 id_table 来判断该驱动是否能够处理这个新设备。usbmouse_as_key_id_table 数组就是用于存储该驱动能够匹配的 USB 设备的相关信息,
这样当符合条件的 USB 设备插入时,对应的驱动程序就会被调用进行处理。
*/
static struct usb_device_id usbmouse_as_key_id_table [] = {
/*
USB_INTERFACE_CLASS_HID:表示 USB 设备的接口类为人体接口设备(Human Interface Device,HID)。HID 类设备是一类常见的 USB 设备,包括鼠标、键盘、游戏手柄等,它们用于实现人机交互功能。
USB_INTERFACE_SUBCLASS_BOOT:表示接口子类为引导子类。引导子类通常用于在系统启动阶段提供基本的输入功能,例如在 BIOS 阶段使用的鼠标和键盘。
USB_INTERFACE_PROTOCOL_MOUSE:表示接口协议为鼠标协议。这表明该设备遵循 USB 鼠标的通信协议,用于传输鼠标的移动、按键等信息。
*/
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },{ }
};
//一个 USB 请求块(URB)的中断处理函数,当 USB 设备(这里是 USB 鼠标)完成一次数据传输后,内核会调用这个函数来处理接收到的数据
/*
usbmouse_as_key_irq 函数通过比较前后两次读取的鼠标按键状态,检测鼠标按键的按下和松开事件,并将这些事件转换为对应的键盘按键事件发送出去。
最后,重新提交 URB 以保持数据的持续接收,从而实现了将 USB 鼠标模拟成键盘按键的功能。
*/
static void usbmouse_as_key_irq(struct urb *urb)
{
//用于保存上一次读取的鼠标按键状态
static unsigned char pre_val;
/*
usb_buf[0] 存储了从 USB 鼠标接收到的第一个字节的数据,该字节的最低位(第 0 位)表示鼠标左键的状态。1<<0 是一个位掩码,用于提取该位的值。
通过比较 pre_val 和 usb_buf[0] 中对应位的值,可以判断左键状态是否发生了变化。
如果左键状态发生了变化,调用 input_event 函数产生一个键盘按键事件.input_event 函数的参数分别为:
uk_dev:指向输入设备结构体的指针,表示要发送事件的输入设备。
EV_KEY:表示产生的事件类型为按键事件。
KEY_L:表示具体的按键代码,这里将鼠标左键映射为键盘的 L 键。
(usb_buf[0] & (1<<0)) ? 1 : 0:根据当前左键的状态(按下为 1,松开为 0)来确定按键事件的状态。
*/
if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
{
/* 左键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
input_sync(uk_dev);
}
/*
原理与检测左键状态变化类似,只不过这里提取的是 usb_buf[0] 的第 1 位,该位表示鼠标右键的状态,并将鼠标右键映射为键盘的 S 键
*/
if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
{
/* 右键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
input_sync(uk_dev);
}
/*
同样的原理,提取 usb_buf[0] 的第 2 位,该位表示鼠标中键的状态,并将鼠标中键映射为键盘的 ENTER 键
*/
if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
{
/* 中键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
input_sync(uk_dev);
}
//将当前读取的鼠标按键状态保存到 pre_val 中,以便下一次比较
pre_val = usb_buf[0];
/* 重新提交urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
}
/*
当有符合 usbmouse_as_key_id_table 中定义的 USB 设备插入系统时,内核会调用该函数对设备进行初始化和配置。
其主要功能是将 USB 鼠标设备抽象为一个输入设备,并设置相关的数据传输通道,使得可以接收鼠标数据并将其转换为键盘事件
*/
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
//当有符合 usbmouse_as_key_id_table 中定义的 USB 设备插入系统时,内核会调用该函数对设备进行初始化和配置。其主要功能是将 USB 鼠标设备抽象为一个输入设备,并设置相关的数据传输通道,使得可以接收鼠标数据并将其转换为键盘事件
struct usb_device *dev = interface_to_usbdev(intf);
//定义一个指向 USB 主机接口结构体的指针,用于存储当前 USB 接口的设置信息
struct usb_host_interface *interface;
//定义一个指向 USB 端点描述符结构体的指针,用于获取 USB 设备端点的相关信息,如端点地址、最大数据包大小等
struct usb_endpoint_descriptor *endpoint;
//用于存储 USB 数据传输的管道信息
int pipe;
//获取当前 USB 接口的活动设置,cur_altsetting 是 struct usb_interface 结构体中的一个成员,指向当前使用的接口设置
interface = intf->cur_altsetting;
//获取当前接口设置中的第一个端点的描述符,endpoint[0] 表示第一个端点,.desc 表示该端点的描述符结构体
endpoint = &interface->endpoint[0].desc;
/* a.调用 input_allocate_device 函数分配一个输入设备结构体 struct input_dev,并将其指针赋值给全局变量 uk_dev,后续将对该输入设备进行设置和注册*/
uk_dev = input_allocate_device();
/* b. 设置 */
/* b.1 能产生哪类事件 */
//这行代码表示该输入设备支持按键事件
set_bit(EV_KEY, uk_dev->evbit);
//EV_REP 表示重复事件类型,这行代码表示该输入设备支持重复事件,例如长按按键时会持续产生按键事件
set_bit(EV_REP, uk_dev->evbit);
/* b.2 能产生哪些事件 */
//这行代码表示该输入设备可以产生 L 键的按键事件
set_bit(KEY_L, uk_dev->keybit);
//这行代码表示该输入设备可以产生 S 键的按键事件
set_bit(KEY_S, uk_dev->keybit);
//表示该输入设备可以产生 ENTER 键的按键事件
set_bit(KEY_ENTER, uk_dev->keybit);
/* c. 注册 */
input_register_device(uk_dev);
/* d. 硬件相关操作 */
/* 数据传输3要素: 源,目的,长度 */
/* 源: USB设备的某个端点 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 长度: */
len = endpoint->wMaxPacketSize;
/* 目的: */
//usb_alloc_coherent 是一个内核函数,用于分配一块连续的物理内存作为数据缓冲区,以支持 DMA(直接内存访问)操作
usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);
/* 使用"3要素" */
/* 分配usb request block */
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
/* 使用"3要素设置urb" */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
//将之前分配的缓冲区的物理地址赋值给 URB 结构体的 transfer_dma 成员,以便 USB 控制器可以直接访问该缓冲区进行数据传输
uk_urb->transfer_dma = usb_buf_phys;
//设置 URB 的传输标志,URB_NO_TRANSFER_DMA_MAP 表示不需要内核进行额外的 DMA 映射,因为已经使用 usb_alloc_coherent 分配了连续的物理内存
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* usb_submit_urb 是一个内核函数,用于将填充好的 URB 提交给 USB 子系统,开始进行数据传输*/
usb_submit_urb(uk_urb, GFP_KERNEL);
return 0;
}
//这个函数是当 USB 设备(这里是 USB 鼠标)从系统中移除时被调用的,其主要作用是进行资源的清理工作,确保在设备断开连接后,内核不会再使用该设备相关的资源,避免资源泄漏和潜在的错误
static void usbmouse_as_key_disconnect(struct usb_interface *intf){
//通过 USB 接口结构体 struct usb_interface 指针获取对应的 USB 设备结构体 struct usb_device 指针
struct usb_device *dev = interface_to_usbdev(intf);
//停止并释放usb请求块
usb_kill_urb(uk_urb);
usb_free_urb(uk_urb);
//释放usb数据缓冲区
usb_free_coherent(dev,len,usb_buf,usb_buf_phys);
//注销并释放输入设备
input_unregister_device(uk_dev);
input_free_device(uk_dev);
}
// Linux 内核中用于表示 USB 驱动程序的结构体,通过对这个结构体进行初始化和设置,可以定义一个 USB 驱动程序的基本属性和行为
/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
.name = "usbmouse_as_key_",
.probe = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};
static int usbmouse_as_key_init(void)
{
/* 2. 注册 */
usb_register(&usbmouse_as_key_driver);
return 0;
}
static void usbmouse_as_key_exit(void)
{
usb_deregister(&usbmouse_as_key_driver);
}
MODULE_LICENSE("GPL");
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);




900

被折叠的 条评论
为什么被折叠?



