linux 驱动流程,linux驱动开发教程

大家好,今天来为大家解答linux 驱动流程这个问题的一些问题点,包括linux驱动开发教程也一样很多人还不知道,因此呢,今天就来为大家分析分析,现在让我们一起来看看吧!如果解决了您的问题,还望您关注下本站哦,谢谢~

DPDK网卡驱动流程总结

本文基于DPDK-16.07.2、Linux 4.4.2进行分析总结,旨在详细阐述DPDK网卡驱动流程及UIO技术的应用与实现原理。以下为DPDK网卡驱动流程的深入解析。

### UIO技术概述

UIO(Userspace I/O)是一种运行在用户空间的I/O技术。在Linux系统中,大部分驱动程序运行在内核空间,而UIO技术则允许将驱动程序的一部分放在内核空间运行,其余部分则在用户空间实现。这样设计的主要优点是可以避免设备驱动程序随内核更新而频繁变动的问题,提高驱动程序的维护性和移植性。

###为何需要UIO技术

硬件设备根据功能和连接方式被划分为多个类别,如网络设备、块设备、字符设备等,并由不同的内核子系统支持。标准设备的驱动编写相对简单且易于维护,容易融入主内核源码。然而,存在一些难以归类为标准设备的非标准硬件,如I/O卡、现场总线接口或定制的FPGA等。这些设备的驱动程序通常被实现为字符驱动,与内核内部函数和宏的频繁变动相冲突,导致驱动程序的编写和维护成本高。UIO框架为这些设备提供了一种运行在用户空间的解决方案,避免了驱动程序随内核更新的困扰。

### UIO技术的工作原理

设备驱动程序的主要任务是管理硬件资源和处理设备事件。UIO框架通过三层结构实现这一目标:内核UIO框架、内核驱动(如igb_uio)和用户态驱动(如dpdk实例程序)。内核UIO框架主要负责核心管理,如创建设备描述符和注册设备。用户态驱动则通过特定接口与内核驱动交互,实现与硬件的直接通信和控制。

### DPDK网卡驱动流程

在DPDK网卡驱动流程中,首先配置内核选项以启用UIO功能,内核编译相关代码并在启动时初始化UIO内核核心框架。接着,通过特定的内核驱动(如igb_uio)注册设备,实现设备的配置和管理。用户态驱动在DPDK实例程序初始化时与设备进行匹配和加载,实现对网卡的控制。

####内核UIO框架总结

内核UIO框架通过一系列步骤完成设备的注册、初始化和管理。主要包括设备的注册、初始化函数的挂载、设备驱动的配置以及设备中断的注册等。

#### UIO内核驱动部分总结

在DPDK中,内核驱动(如igb_uio)主要负责与硬件交互的低级操作,包括设备注册、初始化以及中断处理。驱动通过特定接口与UIO框架交互,实现设备的管理。

#### UIO用户驱动加载

用户态驱动在DPDK实例程序初始化时与设备进行匹配和加载,完成对网卡的控制。通过DPDK提供的工具(如dpdk-devbind.py)绑定网卡设备,实现驱动的加载和初始化。

### DPDK学习资源

如需深入学习DPDK及相关技术,如网络协议栈、VPP、OvS、DDoS防护、NFV和虚拟化等,可参考相关学习资料、教学视频和学习路线图。这些资源通常可在特定的在线平台或社区中找到,如官方文档、教程网站或专业论坛。

Linux 实现原理 — 网卡驱动程序初始化流程

Linux内核启动流程始于main.c start_kernel(),这是内核初始化的主入口,执行一系列初始化步骤,包括CPU初始化、主内存初始化、中断初始化、进程调度初始化、TCP/IP栈初始化等,最终形成基本的Linux内核环境。

在内核初始化过程中,rest_init()函数将启动0号进程,并进入永循环。

协议栈初始化流程在start_kernel()调用linux/net/socket.c sock_init()开始。

网卡驱动程序注册流程始于驱动程序调用module_init()向内核注册初始化函数。在dev_init过程中初始化设备和驱动程序时,内核会调用这些注册的函数。以Intel I350网卡的IGB驱动为例,其初始化函数位于linux/drivers/net/ethernet/intel/igb/igb_main.c igb_init_module()。

IGB驱动程序的核心初始化过程通过pci_register_driver()实现。此函数管理pci_device_id映射表,通过读取设备PCI配置空间中的厂商ID和设备ID来识别设备型号和对应驱动程序。例如Intel I350的主板型号为board_82575。

注册过程还包含了IGB驱动程序的各种回调函数注册到pci_driver结构体实例中,如igb_driver_name()和igb_probe()等函数。这样,IGB驱动就被注册到了内核的设备接口层。

插入Intel I350 PCIe网卡后,内核通过PCI设备的厂商ID和设备ID匹配到对应的igb_probe()函数,进入IGB驱动的初始化流程。

IGB驱动初始化流程从igb_probe()函数开始,获取PCI设备的详细信息及各类操作函数入口,完成设备控制。

网络接口创建在igb_probe()后期调用register_netdev()函数,将net_device实例注册到内核中,并创建对应的网络接口。此时,Shell中能看见对应的网卡设备。

register_netdev()读取net_device实例提供的信息,并使用IGB驱动设置的网络接口名称前缀,生成唯一的接口名称,如:ethX。

使用ifconfig指令可以查看到net_device实例提供的名称、MAC、掩码、MTU等信息。这是通过调用Socket I/O SCI实现的。

使用ethtool命令行工具可以查看并交互net_device结构体的配置信息。此命令通过调用ioctl I/O SCI与Net device注册的ethtool函数进行交互实现。

- END-

Linux系统中USB驱动程序的工作流程详解

1.USB主机

在Linux驱动中,USB驱动处于最底层的是USB主机控制器硬件,在其之上运行的是USB主机控制器驱动,主机控制器之上为USB核心层,再上层为USB设备驱动层(插入主机上的U盘、鼠标、USB转串口等设备驱动)。

因此,在主机侧的层次结构中,要实现的USB驱动包括两类:USB主机控制器驱动和USB设备驱动,前者控制插入其中的USB设备,后者控制USB设备如何与主机通信。Linux内核USB核心负责USB驱动管理和协议处理的主要工作。主机控制器驱动和设备驱动之间的USB核心非常重要,其功能包括:通过定义一些数据结构、宏和功能函数,向上为设备驱动提供编程接口,向下为USB主机控制器驱动提供编程接口;通过全局变量维护整个系统的USB设备信息;完成设备热插拔控制、总线数据传输控制等。

2.USB设备

Linux内核中USB设备侧驱动程序分为3个层次:UDC驱动程序、Gadget API和Gadget驱动程序。UDC驱动程序直接访问硬件,控制USB设备和主机间的底层通信,向上层提供与硬件相关操作的回调函数。当前Gadget API是UDC驱动程序回调函数的简单包装。Gadget驱动程序具体控制USB设备功能的实现,使设备表现出网络连接、打印机或USB Mass Storage等特性,它使用Gadget API控制UDC实现上述功能。Gadget API把下层的UDC驱动程序和上层的Gadget驱动程序隔离开,使得在Linux系统中编写USB设备侧驱动程序时能够把功能的实现和底层通信分离。

3.层次

在USB设备组织结构中,从上到下分为设备(device)、配置(config)、接口(interface)和端点(endpoint)四个层次。USB设备程序绑定到接口上。

对于这四个层次的简单描述如下:

(1)设备通常具有一个或多个的配置

(2)配置经常具有一个或多个的接口

(3)接口没有或具有一个以上的端点

4.端点

USB通信最基本的形式是通过端点(USB端点分中断(Interrupt)、批量(Bulk)、等时(ISO)、控制(Control)四种,每种用途不同),USB端点只能往一个方向传送数据,从主机到设备或者从设备到主机,端点可以看作是单向的管道(pipe)。驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否已经安装了硬件。USB核心使用一个列表(是一个包含制造商ID和设备号ID的一个结构体)来判断对于一个设备该使用哪一个驱动程序,热插拨脚本使用它来确定当一个特定的设备插入到系统时该自动执行哪一个驱动程序的Probe。

5.数据结构

(1)USB设备:对应数据结构struct usb_device

(2)配置:struct usb_host_config(任一时刻,只能有一个配置生效)

(3)USB接口:struct usb_interface(USB核心将其传递给USB设备驱动,并由USB设备驱动负责后续的控制。一个USB接口代表一个基本功能,每个USB驱动控制一个接口。所以一个物理上的硬件设备可能需要一个以上的驱动程序。)

(4)端点: struct usb_host_endpoint,它所包含的真实端点信息在另一个结构中:struct usb_endpoint_descriptor(端点描述符,包含所有的USB特定数据)。

6. USB端点分类

USB通讯的最基本形式是通过一个称为端点的东西。一个USB端点只能向一个方向传输数据(从主机到设备(称为输出端点)或者从设备到主机(称为输入端点))。端点可被看作一个单向的管道。

USB端点有 4种不同类型,分别具有不同的数据传送方式:

(1)控制CONTROL

控制端点被用来控制对USB设备的不同部分访问.通常用作配置设备、获取设备信息、发送命令到设备或获取设备状态报告。这些端点通常较小。每个 USB设备都有一个控制端点称为端点 0,被 USB核心用来在插入时配置设备。USB协议保证总有足够的带宽留给控制端点传送数据到设备.

(2)中断INTERRUPT

每当 USB主机向设备请求数据时,中断端点以固定的速率传送小量的数据。此为USB键盘和鼠标的主要的数据传送方法。它还用以传送数据到USB设备来控制设备。通常不用来传送大量数据。USB协议保证总有足够的带宽留给中断端点传送数据到设备.

(3)批量BULK

批量端点用以传送大量数据。这些端点通常比中断端点大得多.它们普遍用于不能有任何数据丢失的情况。USB协议不保证传输在特定时间范围内完成。如果总线上没有足够的空间来发送整个BULK包,它被分为多个包进行传输。这些端点普遍用于打印机、USB Mass Storage和USB网络设备上。

(4)等时ISOCHRONOUS

等时端点也批量传送大量数据,但是这个数据不被保证能送达。这些端点用在可以处理数据丢失的设备中,并且更多依赖于保持持续的数据流。如音频和视频设备等等。

控制和批量端点用于异步数据传送,而中断和等时端点是周期性的。这意味着这些端点被设置来在固定的时间连续传送数据,USB核心为它们保留了相应的带宽。

7. endpoint

C/C++ Code复制内容到剪贴板structusb_host_endpoint{structusb_endpoint_descriptordesc;//端点描述符structlist_headurb_list;//此端点的URB对列,由USB核心维护void*hcpriv;structep_device*ep_dev;/*Forsysfsinfo*/unsignedchar*extra;/*Extradescriptors*/intextralen;intenabled;};

当调用USB设备驱动调用usb_submit_urb提交urb请求时,将调用int usb_hcd_link_urb_to_ep(struct usb_hcd*hcd, struct urb*urb)把此urb增加到urb_list的尾巴上。(hcd: Host Controller Driver,对应数据结构struct usb_hcd)

8. urb

所有USB通讯均为请求--响应模式,USB设备不会主动向Host发送数据。写数据:USB设备驱动发送urb请求给USB设备,USB设备不需要回数据。读数据:USB设备驱动发送urb请求给USB设备,USB设备需要回数据。

USB设备驱动通过urb和所有的 USB设备通讯。urb用 struct urb结构描述(include/linux/usb.h)。

urb以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个 USB设备驱动可根据驱动的需要,分配多个 urb给一个端点或重用单个 urb给多个不同的端点。设备中的每个端点都处理一个 urb队列,所以多个 urb可在队列清空之前被发送到相同的端点。

一个 urb的典型生命循环如下:

(1)被创建;

(2)被分配给一个特定 USB设备的特定端点;

(3)被提交给 USB核心;

(4)被 USB核心提交给特定设备的特定 USB主机控制器驱动;

(5)被 USB主机控制器驱动处理,并传送到设备;

(6)以上操作完成后,USB主机控制器驱动通知 USB设备驱动。

urb也可被提交它的驱动在任何时间取消;如果设备被移除,urb可以被USB核心取消。urb被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。

8.1提交 urb

一旦 urb被正确地创建并初始化,它就可以提交给 USB核心以发送出到 USB设备.这通过调用函数sb_submit_urb实现.

int usb_submit_urb(struct urb*urb, gfp_t mem_flags);

参数:

struct urb*urb:指向被提交的 urb的指针

gfp_t mem_flags:使用传递给 kmalloc调用同样的参数,用来告诉 USB核心如何及时分配内存缓冲

因为函数 usb_submit_urb可被在任何时候被调用(包括从一个中断上下文), mem_flags变量必须正确设置.根据 usb_submit_urb被调用的时间,只有 3个有效值可用:

GFP_ATOMIC

只要满足以下条件,就应当使用此值:

1)调用者处于一个 urb结束处理例程,中断处理例程,底半部,tasklet或者一个定时器回调函数.

2)调用者持有自旋锁或者读写锁.注意如果正持有一个信号量,这个值不必要.

3) current-state不是 TASK_RUNNING.除非驱动已自己改变 current状态,否则状态应该一直是TASK_RUNNING.

GFP_NOIO

驱动处于块 I/O处理过程中.它还应当用在所有的存储类型的错误处理过程中.

GFP_KERNEL

所有不属于之前提到的其他情况

在 urb被成功提交给 USB核心之后,直到结束处理例程函数被调用前,都不能访问 urb结构的任何成员

8.2 urb结束处理例程

如果 usb_submit_urb被成功调用,并把对 urb的控制权传递给 USB核心,函数返回 0;否则返回一个负的错误代码.如果函数调用成功,当 urb被结束的时候结束处理例程会被调用一次.当这个函数被调用时, USB核心就完成了这个urb,并将它的控制权返回给设备驱动.

只有3种结束urb并调用结束处理例程的情况:

(1)urb被成功发送给设备,且设备返回正确的确认.如果这样, urb中的status变量被设置为 0.

(2)发生错误,错误值记录在 urb结构中的 status变量.

(3)urb从 USB核心unlink.这发生在要么当驱动通过调用 usb_unlink_urb或者 usb_kill_urb告知 USB核心取消一个已提交的 urb,或者在一个 urb已经被提交给它时设备从系统中去除.

9.探测和断开

在 struct usb_driver结构中,有 2个 USB核心在适当的时候调用的函数:

(1)当设备插入时,如果 USB核心认为这个驱动可以处理(USB核心使用一个列表(是一个包含制造商ID和设备号ID的一个结构体)来判断对于一个设备该使用哪一个驱动程序),则调用探测(probe)函数,探测函数检查传递给它的设备信息,并判断驱动是否真正合适这个设备.

(2)由于某些原因,设备被移除或驱动不再控制设备时,调用断开(disconnect)函数,做适当清理.

探测和断开回调函数都在USB集线器内核线程上下文中被调用,因此它们休眠是合法的.为了缩短 USB探测时间,大部分工作尽可能在设备打开时完成.这是因为 USB核心是在一个线程中处理 USB设备的添加和移除,因此任何慢设备驱动都可能使 USB设备探测时间变长。

9.1探测函数分析

在探测回调函数中, USB设备驱动应当初始化它可能用来管理 USB设备的所有本地结构并保存所有需要的设备信息到本地结构,因为在此时做这些通常更容易.为了和设备通讯,USB驱动通常要探测设备的端点地址和缓冲大小.

PS:Linux USB驱动相关细节知识补充

1.在usb_fill_bulk_urb,usb_fill_int_urb,usb_fill_control_urb都需要指定回调函数,当此URB请求完成时,usb core回调用此函数。

注意:urb回调函数是在中断上下文运行,因此它不应做任何内存分配,持有任何信号量,或任何可导致进程休眠的事情.如果从回调中提交 urb并需要分配新内存块,需使用 GFP_ATOMIC标志来告知 USB核心不要休眠.

2. urb封装函数:

(1)int usb_bulk_msg(struct usb_device*usb_dev,unsigned int pipe,void*data, int len, int*actual_length,int timeout)

功能:创建批量 urb并发送到指定的设备,接着在返回之前等待完成.

参数:

struct usb_device*usb_dev:目标 USB设备指针

unsigned int pipe:目标 USB设备的特定端点.必须使用特定的宏创建.

void*data:如果是 OUT端点,指向要发送到设备的数据的指针.如果是 IN端点,这是从设备读取的数据的缓冲区指针.

int len: data参数指向的缓冲的长度

int*actual_length:指向函数放置真实字节数的指针,根据端点方向,这些字节要么是被发送到设备的,要么是从设备中读取的.

int timeout:时钟嘀哒数,应等待的时间.如果为 0,函数永远等待操作完成.

返回值:成功返回0,actual_length参数包含被传送或从设备中读取的字节数.否则返回负的错误值.

(2)int usb_control_msg(struct usb_device*dev, unsigned int pipe, __u8 request,__u8 requesttype, __u16 value, __u16 index,void*data, __u16 size,int timeout)

功能:创建控制 urb并发送到指定的设备,接着在返回之前等待完成.

参数:

struct usb_device*usb_dev:目标 USB设备指针

unsigned int pipe:目标 USB设备的特定端点.必须使用特定的宏创建.

__u8 request:控制消息的 USB请求值.

__u8 requesttype:控制消息的 USB请求类型.

__u16 value:控制消息的 USB消息值.

__u16 index:控制消息的 USB消息索引值.

void*data:如果是 OUT端点,指向要发送到设备的数据的指针.如果是 IN端点,这是从设备读取的数据的缓冲区指针.

__u16 size: data参数指向的缓冲的长度

int timeout:时钟嘀哒数,应等待的时间.如果为 0,函数永远等待操作完成.

返回值:成功返回被传送到或从设备读取的字节数.否则返回负的错误值.

(3)int usb_interrupt_msg(struct usb_device*usb_dev, unsigned int pipe,void*data,int len, int*actual_length,int timeout)

功能:创建中断 urb并发送到指定的设备,接着在返回之前等待完成.其实就是usb_bulk_msg的包装,所有参数和usb_bulk_msg一样使用

阅读剩余
THE END