Linux内核USB设备驱动架构分析

一、总线速度及主机控制器

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接口通常有四个引脚,分别是:

  1. VBUS(电源线)

    • 功能:提供5V直流电源,用于给USB设备供电。USB设备可以通过VBUS引脚从主机端获取电力,从而不需要额外的电源适配器。
    • 电压:通常为5V。
  2. GND(地线)

    • 功能:提供接地参考点,用于确保USB设备和主机端之间的信号传输稳定。GND引脚与主机端的接地系统相连,确保信号传输的准确性。
    • 电压:0V。
  3. D+(正数据线)

    • 功能:用于传输数据的正方向信号。在USB数据传输过程中,D+和D-引脚共同组成差分信号对,用于提高数据传输的抗干扰能力和传输速率。
    • 电压:在数据传输过程中,D+引脚的电压会在0V和3.3V之间变化。
  4. 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)。
    • idVendoridProduct:厂商和产品的唯一标识符,用于驱动匹配。
    • 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);

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值