您现在的位置是: 首页 > 驱动程序 驱动程序
硬件驱动程序原代码_硬件驱动程序原代码是什么
ysladmin 2024-05-15 人已围观
简介硬件驱动程序原代码_硬件驱动程序原代码是什么 非常欢迎大家参与这个硬件驱动程序原代码问题集合的探讨。我将以开放的心态回答每个问题,并尽量给出多样化的观点和角度,以期能
非常欢迎大家参与这个硬件驱动程序原代码问题集合的探讨。我将以开放的心态回答每个问题,并尽量给出多样化的观点和角度,以期能够启发大家的思考。
1.电脑驱动程序的工作原理
2.开发板的硬件驱动
3.如何构造一个简单的USB过滤驱动程序
4.如何编写网卡的驱动程序
电脑驱动程序的工作原理
资料来源: /windows-hardware/drivers/develop/getting-started-with-universal-drivers
DCH驱动程序和标准(Standard)驱动程序有什么不同?
虽然基本核心组件文件保持不变,但DCH驱动程序的打包方式与以前的(Standard)标准驱动程序不同。按照 Microsoft 的要求,DCH驱动只含有基础驱动,不包含任何UI和实用工具,也不包含OEM的定制化组件,驱动程序控制面板将不再随驱动程序提供,而是需要通过访问Microsoft Store获取UWP版本的驱动程序控制面板,或是通过Windows Update自动下载安装。
相较于标准驱动程序而言,DCH驱动更干净,而且在同样运行Windows 10 系统的不同硬件设备上,可以使用同一版通用的驱动。
目前电脑硬件厂商中,主要是Intel的核显驱动和Nvidia的独显驱动更新使用了DCH版本。
对于Intel核显驱动来说,用户可以直接从旧版本的标准驱动直接覆盖安装升级至新版本的DCH驱动;而对于Nvidia独显驱动来说,用户无法从旧版本的标准驱动直接覆盖安装升级至新版本的DCH驱动(本文重点)。
以Nvidia显卡驱动为例,如果在原本安装标准驱动程序的机台上下载安装新版本DCH驱动,则会出现如下报错:
如何确认我的驱动程序是标准版还是DCH版?
由于Intel核显驱动可以直接覆盖安装升级为DCH版驱动,我们这里以Nvidia独显驱动为例,右击桌面空白处,打开Nvidia控制面板,在左下角选择系统信息,在弹出的窗口中有一行“Driver Type”,显示DCH即为DCH驱动,显示Standard即为标准驱动:
如何将标准版Nvidia显卡驱动升级为DCH驱动?
如需使用DCH驱动,首先系统必须是Windows 10 1809,Build 17763或更新版本。
在确保系统版本符合要求以及电脑内驱动版本为Standard的前提下,访问Nvidia官网获取新版本DCH驱动下载链接。可以看到在Nvidia官网,对于驱动下载链接有一个下拉菜单进行分类,选择DCH后搜索并下载的便是DCH版驱动:
由于电脑内原先安装的是标准版驱动,无法直接安装DCH驱动,我们需要借助DDU(Display Driver Uninstaller)工具软件来先卸载原有标准版驱动。
获取DDU(Display Driver Uninstaller)
官网:/
下载链接:/DDU/download/DDU%20v18.0.0.4.exe/
在开始操作前确保电脑断网,否则Windows Updete会自动安装驱动
按WIN+R输入msconfig打开系统配置
在“引导”选项卡中勾选“安全引导”,确定后重启进入安全模式。
打开DDU,选择设备类型和设备厂商。
点击清除后重启。待系统自动重启后直接安装之前下载好的DCH版驱动即可。
最后,因为DCH驱动是不包含显卡控制面板的,所以可以自行在Microsoft Store里获取安装NVIDIA Control Panel。
如何构造一个简单的USB过滤驱动程序
大部分嵌入式硬件都需要某种类型的软件进行初始化和管理。直接与一个硬件互相作用并控制这一硬件的软件称为设备驱动程序(device driver)。所有需要软件的嵌入式系统,在它们的系统软件层都需要设备驱动程序软件。设备驱动程序是初始化硬件的软件库,它们管理着高层软件对硬件的访问,它是硬件与操作系统、中间件和应用层之间联络的纽带。具体来说,这类驱动程序包括主处理器体系结构专用的功能性驱动程序、存储器和存储器管理驱动程序、总线初始化和事务驱动程序、还有电路板层和主CPU层次的I/O初始化和控制驱动程序(如用于网络、图形、输入设备、存储设备、调试I/O等)。设备驱动程序通常划分为体系结构专用(architecture-specific)设备驱动程序和通用(generic)设备驱动程序。体系结构专用设备驱动程序管理嵌入到主处理器(体系结构)中的硬件。体系结构专用驱动程序负责初始化主处理器内部的组件,这类驱动程序的具体事例包括片上存储器、集成的存储器管理器(MMU)和浮点硬件的驱动程序。通用设备驱动程序管理电路板上的硬件以及没有集成到主处理器中的硬件。在一个通用设备驱动程序中,通常包含一部分体系结构专用的源代码,因为主处理器是中央控制单元,要访问电路板上的任何组件通常都要经过主处理器。然而,通用驱动程序也可以管理不被特定的处理器所专用的板级硬件,这就意味着一个通用驱动程序可以配置应用到许多体系结构中去,只要该结构中包含该驱动程序对应的硬件。通用驱动程序包含初始化和管理对电路板上剩余主要组件进行访问的代码,这些主要组件包括板级总线(I2C、PCI、PCMCIA等)、片外存储器(控制器、2级以上高速缓存、闪存等)和片外I/O(以太网、RS-232、显示器、鼠标等)。
如何编写网卡的驱动程序
本文分三部分来介绍如何构造一个简单的USB过滤驱动程序,包括“基本原理”、“程序的实现”、“使用INF安装”。此文的目的在于希望读者了解基本原理后,可以使用除DDK以外最流行也最方便的驱动开发工具DriverStudio来实现一个自己的过滤驱动,并正确地安装。
一、基本原理
我们知道,WDM(和KDM)是分层的,在构造设备栈时,IO管理器可以使一个设备对象附加到另外一个初始驱动程序创建的设备对象上。与初始设备对象相关的驱动程序决定的IRP,也将被发送到附加的设备对象相关的驱动程序上。这个被附加的驱动程序便是过滤驱动程序。如右图,过滤驱动可以在设备栈的任何层次中插入。IO管理器发出的IRP将会沿着右图的顺序从上往下传递并返回。因此,我们可以使用过滤驱动程序来检查、修改、完成它接收到的IRP,或者构造自己的IRP。
上面这种文字是很枯燥的,好在“前人”已经写过一些范例以供我们更好地理解这些概念。读过Waltz Oney的《Programming Windows Driver Mode》一书的读者大概都知道Waltz Oney提供的范例中有一个关于USB过滤器(第九章)的例子,而在此基础上,《USB Design By Example》()的作者John Hyde实现了一个USB键盘过滤驱动程序,即给此程序增加了一个“拦截(Intercept)”功能来处理USB键盘的Report以实现特定的功能:当驱动程序在IRP_MJ_INTERNAL_DEVICE_CONTROL设置的完成例程从USB设备拦截到一个Get_Report_Descriptor时,拦截程序将此Descriptor中的USAGE值从“Keyboard”改为“UserDefined”,再返回给系统。
我们可以从这个例子中获得一些灵感,比如,在Win2k下,键盘是由OS独占访问的,我们可以通过这种方式使之可以让用户自由访问;我们也可以拦截其他Report_Descriptor,将部分键重新定义,以满足特殊的要求;如果你愿意再做一个用户态的程序,你还可以将你拦截到的键值传递给你的用户态程序,以实现象联想、实达等国内电脑大厂出品的那些键盘上的各种实用的功能。
二、程序的实现
Waltz Oney和John Hyde的例子已经写得很详细了,读者可以不用修改一个字节便顺利地编译生成一个过滤驱动程序。本文的目的在于使用DriverStudio组件Driverworks来实现同样的功能。
相信读者读到这篇文章时,已经对DriverStudio有了很多的了解。DriverStudio作为一个以C++为基础的“快速”驱动开发工具,它封装了基本上所有的DDK的函数,其集成在VC++中的DriverWizard,可以很方便地引导你完成设备驱动程序开发的全过程,能根据你的硬件种类自动生成设备驱动程序源代码,并提供了很多范例程序。当然,这些例子中便包含一个USB Filter驱动程序的框架。在不侵犯版权的前提下,充分利用现有共享的、免费的、授权的代码是我们的一贯作法。我们下面便以此范例为基础来作修改。
我们的目的是做一个HID小驱动程序hidusb.sys的Lower Filter,它附加在“人机接口设备” ,通过拦截USB的Get_Report_Descriptor来修改其返回值,当它发现该Descriptor的Usage 为“Keyboard”时,将其改为“UserDefined”,如此我们便可以完全控制这只键盘。具体做法是,拦截IRP_MJ_INTERNAL_DEVICE_CONTROL,并检查其IOCTL代码及URB,如果满足IOCTRL功能代码为IOCTL_INTERNAL_USB_SUBMIT_URB以及URB功能代码为URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE的条件,即上层驱动发来Get_Report_Descriptor请求时,设置一个完成例程,在这个完成例程中,我们将判断Usage的值,将Usage由“6(Keyboard)”时,将其改为“0(UserDefined)”。
打开C:\Program Files\NuMega\DriverStudio\DriverWorks\Examples\wdm\usbfilt目录(具体目录依你的DriverStudio所安装的目录不同而不同) ,再打开工程文件usbfilt.dsw,我们先看一下代码。
程序由两个类组成,一个是Driver类,一个是Device类。Driver类包括:
入口函数DriverEntry:
DECLARE_DRIVER_CLASS(UsbFilterDriver, NULL)
/////////////////////////////////////////////////////////////////////
// Driver Entry
//
NTSTATUS UsbFilterDriver::DriverEntry(PUNICODE_STRING RegistryPath)
{
T << "UsbFilterDriver::DriverEntry\n";
m_Unit = 0;
return STATUS_SUCCESS;
// The following macro simply allows compilation at Warning Level 4
// If you reference this parameter in the function simply remove the macro.
UNREFERENCED_PARAMETER(RegistryPath);
}
AddDevice函数
NTSTATUS UsbFilterDriver::AddDevice(PDEVICE_OBJECT Pdo)
{
T << "UsbFilterDriver::AddDevice\n";
UsbFilterDevice * pFilterDevice = new (
static_cast<PCWSTR>(NULL),
FILE_DEVICE_UNKNOWN,
static_cast<PCWSTR>(NULL),
0,
DO_DIRECT_IO
)
UsbFilterDevice(Pdo, m_Unit);
if (pFilterDevice)
{
NTSTATUS status = pFilterDevice->ConstructorStatus();
if ( !NT_SUCCESS(status) )
{
T << "Failed to construct UsbFilterDevice"
<< (ULONG) m_Unit
<< " status = "
<< status
<< "\n";
delete pFilterDevice;
}
else
{
m_Unit++;
}
return status;
}
else
{
T << "Failed to allocate UsbFilterDevice"
<< (ULONG) m_Unit
<< "\n";
return STATUS_INSUFFICIENT_RESOURCES;
}
}
这两段代码基本上和自动生成的代码差不多。AddDevice的作用是构造一个过滤器的实例。
关键的代码在Device类。在这个类里,我们把过滤器插入设备栈,并拦截IRP,用自己的完成例程来实现特定的功能。
Device构造函数
UsbFilterDevice::UsbFilterDevice(PDEVICE_OBJECT Pdo, ULONG Unit) :
KWdmFilterDevice(Pdo, NULL)
{
T << "UsbFilterDevice::UsbFilterDevice\n";
// Check constructor status
if ( ! NT_SUCCESS(m_ConstructorStatus) )
{
return;
}
// Remember our unit number
m_Unit = Unit;
// initialize the USB lower device
m_Usb.Initialize(this, Pdo);
NTSTATUS status = AttachFilter(&m_Usb); //Attach the filter
if(!NT_SUCCESS(status))
{
m_ConstructorStatus = status;
return;
}
SetFilterPowerPolicy();
SetFilterPnpPolicy();
}
在DDK中,我们用IoAttachDevice将设备对象插入设备栈中。DriverStudio封装了这个函数。在DriverStudio中,其他驱动程序需要用Initialize来初始化设备对象和接口,对于过滤驱动,我们关键是需要Attachfilter将其附加在堆栈中。
对于大部分如IRP_MJ_SYSTEM_CONTROL等IRP,我们所做的只需用PassThrough(Irp)将其直接往设备栈下层传递,不需要做任何工作。这些代码我们就不一一列举了。下面的部分才是本文的关键。
我们知道,HIDUSB.SYS是使用内部IOCTRL发出URB给USB类驱动程序(USBD)读取数据的,那么,HIDUSB首先必须构造一个IRP_MJ_INTERNAL_DEVICE_CONTROL,它的IOCTL功能码为IOCTL_INTERNAL_USB_SUBMIT_URB(发出URB的内部IOCTL)。另外,因为我们要检查并修改的是USB键盘某个接口的报告描述,那么这个URB应该是URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE,如下:
NTSTATUS UsbFilterDevice::InternalDeviceControl(KIrp I)
{
T << "UsbFilterDevice::InternalDeviceControl\n";
// Pass through IOCTLs that are not submitting an URB
//不是我们感兴趣的IOCTL不要理它
if (I.IoctlCode() != IOCTL_INTERNAL_USB_SUBMIT_URB)
return DefaultPnp(I);
PURB p = I.Urb(CURRENT); // get URB pointer from IRP
//不是我们感兴趣的URB,也不要理它,
if (p->UrbHeader.Function !=
URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE)
return DefaultPnp(I);
//符合要求的IRP才被设置完成例程
return PassThrough(I, LinkTo(DeviceControlComplete), this);
}
在设置好条件以后,再来实现完成例程。所有的检查、修改等动作都是在完成例程里面完成的。
NTSTATUS UsbFilterDevice::DeviceControlComplete(KIrp I)
{
PURB p = I.Urb(CURRENT);
if(p)
{
//拦截到设备返回的描述表,
char* DescriptorBuffer = (char*)p->UrbControlDescriptorRequest.TransferBuffer;
//指向第三个字节,表示设备Usage属性的值
DescriptorBuffer += 3;
//如果值为6则改成0,6表示hid键盘,0表示未知设备
//在设备管理器里面,原来的hid兼容键盘就不复存在了,取而代之的则是hid兼容设备
if ((*DescriptorBuffer&0xff) == 6)
*DescriptorBuffer = 0;
}
return I.Status();
}
读者可以对照DriverWorks中的例子,直接替换掉(或者修改)上面这两个函数,再编译一下,便可以得到一个完整的键盘过滤器驱动程序。
其实,只要弄清楚了我们需要做些什么动作,在DriverStudio里面只需要写少量的关键代码,便可实现我们的要求,其余的大部分工作,或有范例可供参考,或有Driver Wizard自动生成。
从上面可以看出,我们只需要修改这两个函数,拦截合适的IRP,便可以在完成例程里面实现我们特定的要求。正如开头所说,我们也可以拦截其他的IRP,拦截其他的URB,或者拦截特定键盘的按键键值,将之传递到用户态,以方便实现联想、实达等随机配备的多功能键盘的功能。
三、使用INF安装驱动
在完成了驱动以后,还必须把它安装到系统里面,驱动程序才会起作用。一般来说,我们都必须为我们的驱动程序提供一个inf文件,以便于用户安装或者维护。对于新手来说,过滤驱动程序的inf或许有些棘手。所以,针对本文所描述的驱动,我们提供一个Win98下的安装范例usbkey.inf,范例中“;”后的文字是注解,以方便读者理解。
; usbkey.INF
; Installs Lower Level Filter for a HID keyboard device
; (c) Copyright 2001 SINO Co., Ltd.
;
[Version]
;”CHICAGO”表示Win9x平台
Signature="$CHICAGO$"
;键盘所属类名
Class=HID
ClassGUID={745a17a0-74d3-11d0-b6fe-00a0c90f57da}
;驱动程序提供者,此信息会显示在设备属性的“常规”页
Provider=%USBDBE%
LayoutFile=layout.inf
;显示在驱动程序文件详细资料窗口
DriverVer=11/12/2001,4.10.2222.12
;[ControlFlags]
;ExcludeFromSelect = *
;驱动程序安装目录,inf会将我们的驱动程序安装到如下目录
;记得Destinationdir后面一定要带一个“s”
[DestinationDirs]
DefaultDestDir = 10,system32\drivers
;要增加的注册表项
[ClassInstall]
Addreg=HIDClassReg
[HIDClassReg]
HKR,,,,%HID.ClassName%
HKR,,Icon,,-20
;制造商
[Manufacturer]
%USBDBE%=USBDBE
[USBDBE]
;我们所要附加过滤驱动程序的设备ID。这个ID可以从IC的规范上得来,也可以
;用hidview.exe读出,或者从注册表HKLM\Enum\hid和usb项找出
%HID.DeviceDesc% = Keypad_Inst, USB\VID_05AF&PID_0805&MI_00
;要安装的文件和需要修改的注册表项
;Install usbkey driver
[Keypad_Inst]
CopyFiles=Keypad_Inst.CopyFiles
AddReg=Keypad_Inst.AddReg
[Keypad_Inst.CopyFiles]
hidusb.sys
hidparse.sys
hidclass.sys
usbfilt.sys
[Keypad_Inst.AddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,"hidusb.sys"
[Keypad_Inst.HW]
AddReg=Keypad_Inst.AddReg.HW
;Lowerfilters表示是低层过滤驱动,如果是上层过滤驱动,则必须改为upperfilters
[Keypad_Inst.AddReg.HW]
HKR,,"LowerFilters",0x00010000,"usbfilt.sys"
;HID设备所需要安装的文件和注册表中需要修改的地方
;Install USBHIDDevice
[USBHIDDevice]
CopyFiles=USBHIDDevice.Copy
AddReg=USBHIDDevice.AddReg
[USBHIDDevice.Copy]
hidclass.sys
hidusb.sys
hidparse.sys
[USBHIDDevice.AddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,"hidusb.sys"
;以下定义需要在上面某些地方使用时替换的字符串
[strings]
USBDBE = "SINO Co., Ltd."
HID.DeviceDesc = "SINO USB MultiKeyboard"
HID.HIDDeviceDesc = "Human Interface Devices"
HID.DefaultDevice = "HID Default Device"
HID.ClassName = "Human Input Devices (HID)"
HID.SvcDesc = "Microsoft HID Class Driver"
其实最简单的写inf的方式,是找一些类似设备的inf文件或范例来修改。在不侵权的前提下,充分利用现有资源是我们的一贯原则。
Linux操作系统网络驱动程序编写
一.Linux系统设备驱动程序概述
1.1 Linux设备驱动程序分类
1.2 编写驱动程序的一些基本概念
二.Linux系统网络设备驱动程序
2.1 网络驱动程序的结构
2.2 网络驱动程序的基本方法
2.3 网络驱动程序中用到的数据结构
2.4 常用的系统支持
三.编写Linux网络驱动程序中可能遇到的问题
3.1 中断共享
3.2 硬件发送忙时的处理
3.3 流量控制(flow control)
3.4 调试
四.进一步的阅读
五.杂项
一.Linux系统设备驱动程序概述
1.1 Linux设备驱动程序分类
Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日
益增加,主要是驱动程序的增加。在Linux内核的不断升级过程中,驱动程序的结构
还是相对稳定。在2.0.xx到2.2.xx的变动里,驱动程序的编写做了一些改变,但是
从2.0.xx的驱动到2.2.xx的移植只需做少量的工作。
Linux系统的设备分为字符设备(char device),块设备(block device)和网络
设备(network device)三种。字符设备是指存取时没有缓存的设备。块设备的读写
都有缓存来支持,并且块设备必须能够随机存取(random access),字符设备则没有
这个要求。典型的字符设备包括鼠标,键盘,串行口等。块设备主要包括硬盘软盘
设备,CD-ROM等。一个文件系统要安装进入操作系统必须在块设备上。
网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket
机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系
统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。
1.2 编写驱动程序的一些基本概念
无论是什么操作系统的驱动程序,都有一些通用的概念。操作系统提供给驱动
程序的支持也大致相同。下面简单介绍一下网络设备驱动程序的一些基本要求。
1.2.1 发送和接收
这是一个网络设备最基本的功能。一块网卡所做的无非就是收发工作。所以驱
动程序里要告诉系统你的发送函数在哪里,系统在有数据要发送时就会调用你的发
送程序。还有驱动程序由于是直接操纵硬件的,所以网络硬件有数据收到最先能得
到这个数据的也就是驱动程序,它负责把这些原始数据进行必要的处理然后送给系
统。这里,操作系统必须要提供两个机制,一个是找到驱动程序的发送函数,一个
是驱动程序把收到的数据送给系统。
1.2.2 中断
中断在现代计算机结构中有重要的地位。操作系统必须提供驱动程序响应中断
的能力。一般是把一个中断处理程序注册到系统中去。操作系统在硬件中断发生后
调用驱动程序的处理程序。Linux支持中断的共享,即多个设备共享一个中断。
1.2.3 时钟
在实现驱动程序时,很多地方会用到时钟。如某些协议里的超时处理,没有中
断机制的硬件的轮询等。操作系统应为驱动程序提供定时机制。一般是在预定的时
间过了以后回调注册的时钟函数。在网络驱动程序中,如果硬件没有中断功能,定
时器可以提供轮询(poll)方式对硬件进行存取。或者是实现某些协议时需要的超时
重传等。
二.Linux系统网络设备驱动程序
2.1 网络驱动程序的结构
所有的Linux网络驱动程序遵循通用的接口。设计时采用的是面向对象的方法。
一个设备就是一个对象(device 结构),它内部有自己的数据和方法。每一个设备的
方法被调用时的第一个参数都是这个设备对象本身。这样这个方法就可以存取自身
的数据(类似面向对象程序设计时的this引用)。
一个网络设备最基本的方法有初始化、发送和接收。
------------------- ---------------------
|deliver packets | |receive packets queue|
|(dev_queue_xmit()) | |them(netif_rx()) |
------------------- ---------------------
| | /
/ | |
-------------------------------------------------------
| methods and variables(initialize,open,close,hard_xmit,|
| interrupt handler,config,resources,status...) |
-------------------------------------------------------
| | /
/ | |
----------------- ----------------------
|send to hardware | |receivce from hardware|
----------------- ----------------------
| | /
/ | |
-----------------------------------------------------
| hardware media |
-----------------------------------------------------
初始化程序完成硬件的初始化、device中变量的初始化和系统资源的申请。发送
程序是在驱动程序的上层协议层有数据要发送时自动调用的。一般驱动程序中不对发
送数据进行缓存,而是直接使用硬件的发送功能把数据发送出去。接收数据一般是通
过硬件中断来通知的。在中断处理程序里,把硬件帧信息填入一个skbuff结构中,然
------------------ Linux操作系统网络驱动程序编写 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------
后调用netif_rx()传递给上层处理。
2.2 网络驱动程序的基本方法
网络设备做为一个对象,提供一些方法供系统访问。正是这些有统一接口的方法,
掩蔽了硬件的具体细节,让系统对各种网络设备的访问都采用统一的形式,做到硬件
无关性。
下面解释最基本的方法。
2.2.1 初始化(initialize)
驱动程序必须有一个初始化方法。在把驱动程序载入系统的时候会调用这个初
始化程序。它做以下几方面的工作。检测设备。在初始化程序里你可以根据硬件的
特征检查硬件是否存在,然后决定是否启动这个驱动程序。配置和初始化硬件。在
初始化程序里你可以完成对硬件资源的配置,比如即插即用的硬件就可以在这个时
候进行配置(Linux内核对PnP功能没有很好的支持,可以在驱动程序里完成这个功
能)。配置或协商好硬件占用的资源以后,就可以向系统申请这些资源。有些资源是
可以和别的设备共享的,如中断。有些是不能共享的,如IO、DMA。接下来你要初始
化device结构中的变量。最后,你可以让硬件正式开始工作。
2.2.2 打开(open)
open这个方法在网络设备驱动程序里是网络设备被激活的时候被调用(即设备状
态由down-->up)。所以实际上很多在initialize中的工作可以放到这里来做。比如资
源的申请,硬件的激活。如果dev->open返回非0(error),则硬件的状态还是down。
open方法另一个作用是如果驱动程序做为一个模块被装入,则要防止模块卸载时
设备处于打开状态。在open方法里要调用MOD_INC_USE_COUNT宏。
2.2.3 关闭(stop)
close方法做和open相反的工作。可以释放某些资源以减少系统负担。close是在
设备状态由up转为down时被调用的。另外如果是做为模块装入的驱动程序,close里
应该调用MOD_DEC_USE_COUNT,减少设备被引用的次数,以使驱动程序可以被卸载。
另外close方法必须返回成功(0==success)。
2.2.4 发送(hard_start_xmit)
所有的网络设备驱动程序都必须有这个发送方法。在系统调用驱动程序的xmit
时,发送的数据放在一个sk_buff结构中。一般的驱动程序把数据传给硬件发出去。
也有一些特殊的设备比如loopback把数据组成一个接收数据再回送给系统,或者
dummy设备直接丢弃数据。
如果发送成功,hard_start_xmit方法里释放sk_buff,返回0(发送成功)。如果
设备暂时无法处理,比如硬件忙,则返回1。这时如果dev->tbusy置为非0,则系统
认为硬件忙,要等到dev->tbusy置0以后才会再次发送。tbusy的置0任务一般由中断
完成。硬件在发送结束后产生中断,这时可以把tbusy置0,然后用mark_bh()调用通
知系统可以再次发送。在发送不成功的情况下,也可以不置dev->tbusy为非0,这样
系统会不断尝试重发。如果hard_start_xmit发送不成功,则不要释放sk_buff。
传送下来的sk_buff中的数据已经包含硬件需要的帧头。所以在发送方法里不需
要再填充硬件帧头,数据可以直接提交给硬件发送。sk_buff是被锁住的(locked),
确保其他程序不会存取它。
2.2.5 接收(reception)
驱动程序并不存在一个接收方法。有数据收到应该是驱动程序来通知系统的。
一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块
sk_buff(skb),从硬件读出数据放置到申请好的缓冲区里。接下来填充sk_buff中
的一些信息。skb->dev = dev,判断收到帧的协议类型,填入skb->protocol(多协
议的支持)。把指针skb->mac.raw指向硬件数据然后丢弃硬件帧头(skb_pull)。还要
设置skb->pkt_type,标明第二层(链路层)数据类型。可以是以下类型:
PACKET_BROADCAST : 链路层广播
PACKET_MULTICAST : 链路层组播
PACKET_SELF : 发给自己的帧
PACKET_OTHERHOST : 发给别人的帧(监听模式时会有这种帧)
最后调用netif_rx()把数据传送给协议层。netif_rx()里数据放入处理队列然后返
回,真正的处理是在中断返回以后,这样可以减少中断时间。调用netif_rx()以后,
驱动程序就不能再存取数据缓冲区skb。
2.2.6 硬件帧头(hard_header)
硬件一般都会在上层数据发送之前加上自己的硬件帧头,比如以太网(Ethernet)
就有14字节的帧头。这个帧头是加在上层ip、ipx等数据包的前面的。驱动程序提供
一个hard_header方法,协议层(ip、ipx、arp等)在发送数据之前会调用这段程序。
硬件帧头的长度必须填在dev->hard_header_len,这样协议层回在数据之前保留好
硬件帧头的空间。这样hard_header程序只要调用skb_push然后正确填入硬件帧头就
可以了。
在协议层调用hard_header时,传送的参数包括(2.0.xx):数据的sk_buff,
device指针,protocol,目的地址(daddr),源地址(saddr),数据长度(len)。数据
长度不要使用sk_buff中的参数,因为调用hard_header时数据可能还没完全组织好。
saddr是NULL的话是使用缺省地址(default)。daddr是NULL表明协议层不知道硬件目
的地址。如果hard_header完全填好了硬件帧头,则返回添加的字节数。如果硬件帧
头中的信息还不完全(比如daddr为NULL,但是帧头中需要目的硬件地址。典型的情
况是以太网需要地址解析(arp)),则返回负字节数。hard_header返回负数的情况
下,协议层会做进一步的build header的工作。目前Linux系统里就是做arp
(如果hard_header返回正,dev->arp=1,表明不需要做arp,返回负,dev->arp=0,
做arp)。
对hard_header的调用在每个协议层的处理程序里。如ip_output。
2.2.7 地址解析(xarp)
有些网络有硬件地址(比如Ethernet),并且在发送硬件帧时需要知道目的硬件
地址。这样就需要上层协议地址(ip、ipx)和硬件地址的对应。这个对应是通过地址
解析完成的。需要做arp的的设备在发送之前会调用驱动程序的rebuild_header方
法。调用的主要参数包括指向硬件帧头的指针,协议层地址。如果驱动程序能够解
析硬件地址,就返回1,如果不能,返回0。
对rebuild_header的调用在net/core/dev.c的do_dev_queue_xmit()里。
2.2.8 参数设置和统计数据
在驱动程序里还提供一些方法供系统对设备的参数进行设置和读取信息。一般
只有超级用户(root)权限才能对设备参数进行设置。设置方法有:
dev->set_mac_address()
当用户调用ioctl类型为SIOCSIFHWADDR时是要设置这个设备的mac地址。一般
对mac地址的设置没有太大意义的。
dev->set_config()
------------------ Linux操作系统网络驱动程序编写 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------
当用户调用ioctl时类型为SIOCSIFMAP时,系统会调用驱动程序的set_config
方法。用户会传递一个ifmap结构包含需要的I/O、中断等参数。
dev->do_ioctl()
如果用户调用ioctl时类型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之间,系统
会调用驱动程序的这个方法。一般是设置设备的专用数据。
读取信息也是通过ioctl调用进行。除次之外驱动程序还可以提供一个
dev->get_stats方法,返回一个enet_statistics结构,包含发送接收的统计信息。
ioctl的处理在net/core/dev.c的dev_ioctl()和dev_ifsioc()里。
今天关于“硬件驱动程序原代码”的探讨就到这里了。希望大家能够更深入地了解“硬件驱动程序原代码”,并从我的答案中找到一些灵感。