📄 网络驱动教程.txt
字号:
2.访问这些端口。
3.注销你注册的东西。
首先是注册,你必须在你的Adapter类中包括KNdisIoRange类或者KNdisMemory的数据成员.例子如下:
class MyAdapter : public KNdisMiniAdapter {
...
KNdisIoRange m_Ports;
KNdisMemoryRange m_Memory;
...
}
然后你在adapter的Initialize函数中访问一个KNdisXxxResource类,从中得到你的微端口驱动所拥有的资源。(当然包括io口)。
最后你就可以初始化你的KNdisIoRange以及KNdisMemoryRange成员了。下面有例子:
NDIS_STATUS MyAdapter::Initialize(KNdisMedium& Medium,IN KNdisConfig& Config) { //注意这行代码是向导生成的
...
//现在来设法获取资源
KNdisPnpResourceRequest request(Config);
KNdisResource<CmResourceTypePort> Port(request);
//判断获取结果是否有问题
if(!Port.IsValid())
KNDIS_RETURN_ERROR(NDIS_STATUS_NOT_ACCEPTED;
//注册io端口范围
m_Ports.Initialize();
if(!m_Port.IsValid())
KNDIS_RETURN_ERROR(NDIS_STATUS_RESOURCES);
...
}
就是这样,注册结束。下面看怎么访问。
DriverNetworks提供了四种方法来访问你的m_Ports
1.使用in()和out()成员函数, 它们使用一个ULONG型的偏移量作为参数访问io端口,这个偏移量从io范围的开始地址开始计算。看下面的例子:
UCHAR reg = m_Ports.inb(4);
if(reg|1)
m_Ports.outb(4,reg|1);
else
m_Ports.outb(4,0);
2.使用KNdisIoRegister和KNdisMemoryRegister提供了一种访问这些io范围中一些特殊的寄存器的方法。KNdisIoRegister或者KNdisMemoryRegister的对象可以在对KNdisIoRange或者KNdisMemoryRange对象做[]操作的时候获得。而且KNdisIoRegister和KNdisMemoryRegister对象还可以当作一些基本类型使用,比如它们可以当作ULONG,USHORT,UCHAR等等来访问。看例子:
KNdisIoRegister reg = m_Ports[4];
if(UCHAR(reg)|1)
reg |= UCHAR(reg)|1;
else
reg = 0;
3.使用模板。DriverNetworks提供两个模板KNdisIoRegisterSafe和KNdisMemoryRegisterSafe作为刚刚2中提到的两个类的“safe”版本。怎么说呢,非“safe”版本就是说在编译的时候不会检查读取数据的宽度和实际硬件寄存器数据宽度的不同。比方说你试图往一个只有8位的寄存器中写一个ULONG的数据,象这样 m_Ports.outd(4,1),这编译没问题,但是可能网卡不会象你希望的那么工作。而“safe”版本则可以指定其数据宽度,你可以根据寄存器的实际宽度指定成ULONG,USHORT,UCHAR这样的类型。如果尝试读写不同的数据宽度的类型,你会得到一个编译错误。例子如下:
ULONG newval = 0;
KNdisIoRegisterSafe<UCHAR> reg = m_Ports[4];
if(UCHAR(reg)|1)
reg|=UCHAR(reg)|1;
else
reg = val; //这里你将得到编译错误!
4.最后一种访问io端口的方式是所谓的直接io访问。这是基于NdisImmediateXxx系列函数的。这提供了一种方法让你的驱动可以在硬件资源已经分配,io或者内存范围已经注册之间就访问实际网卡硬件。这种情况下,系统会对每一次访问都要进行
硬件资源转换和映射,所以这种方法访问是很慢的。而且只能用在硬件初始化过程中。举个例子,一个驱动在开始必须读一个网卡上的EEPROM,来获得某种信息(比如版卡类型?),然后才能开始资源分配请求,这种情况下不能不使用这个技术。一般这种方法在即插即用环境中是不推荐的。
DriverNetworks通过KNdisConfig类来支持这种技术。(刚好上一课介绍了这个东西)。这个类有一系列的in()/out()成员函数来访问io口。例子如下:
UCHAR reg = Config.inb(4);
if(reg | 1)
Config.outb(4,reg|1);
else
Config.outb(4.0);
最后是注册的io范围的注销,这很简单,一般在MyAdapter的Halt()函数中做。另一个选择是在Adapter的析构函数中做。只要这样:
m_Ports.Invalidate();
m_Memory.Invalidate();
就可以了。
第四课 关于中断
这也是只用于微端口的。涉及以下几个主题。
1.注册一个中断。
(1).首先,应该在你的工程的Characteristics.h文件中(关于这个文件,你在使用向导生成了一个微端口驱动的框架之后,自然就会看见。)声明你对中断相关的回调函数。这里你要使用一个宏KNDIS_MINPORT_HANDLER.例子如下:
KNDIS_MINIPORT_HANDLER(MyAdapter,DisableInterrupt)
KNDIS_MINIPORT_HANDLER(MyAdapter,EnableInterrupt)
KNDIS_MINIPORT_HANDLER(MyAdapter,HandleInterrupt)
KNDIS_MINIPORT_HANDLER(MyAdapter,Isr)
(2).在你的adapter类中包括一个KNdisInterrupt的数据成员。
class MyAdapter:public KNdisMiniAdapter(
...
KNdisInterrupt m_Interrupt;
...
};
(3).你必须在adapter的Initiazlize()函数中访问一个KNdisXxxResource类来获得系统分配的中断资源。
(4).根据3中得到的信息初始化你的KNdisInterrupt成员。这个过程举例如下:
NDIS_STATUS MyAdapter::Initialize(KNdisMedium& Medium, IN KNdisConfig& Config)
{
. . .
//获得资源信息
KNdisPnpResourceRequest request(Config);
KNdisResource<CmResourceTypeInterrupt> Int(request);
//确保其可用
if (!Int.IsValid())
KNDIS_RETURN_ERROR (NDIS_STATUS_NOT_ACCEPTED);
//注册你的中断
m_Interrupt.Initialize(this, Int, NdisInterruptLatched);
if (!m_Interrupt.IsValid())
KNDIS_RETURN_ERROR (NDIS_STATUS_RESOURCES);
. . .
}
(5).在硬件上使能你的中断。这和网卡有关。以后我们见具体的例子。
2.同步工作
任何微端口驱动函数若和其他同在DIRQL层上运行的函数共享任何资源,都必须处理同步问题。这个问题出现在中断相关的两个回调函数Isr()和DisableInterrupt()上。为了同步这些函数,驱动使用KNdisInterrupt::Synchronize()函数。这个函数将安排指定的函数运行的时候持有一个自旋锁,从来解决同步问题。例子如下:
m_Interrupt.Synchronize(KNDIS_MEMBER_CALLBACK(CardSetMulticast),this);
void MyAdapter::CardSetMulticast()
{
//我们假设这个函数运行于DIRQK,并其中不想被
//Isr()之类的调用打断工作
}
换句话说,使用了Synchronize函数之后,就不必担心CardSetMulicast这个函数会被Isr()或者是DisableInterrupt()这两个函数打断了。
但是在MyAdapter的声明中,还应该有下边的语句:
class MyAdapter:public KNdisMiniAdapter(
...
KNDIS_DECLARE_SYNCHROCALLBACK( MyAdapter,CardSetMulticast);
void CardSetMulticast();
...
);
SYNCHROCALLBACK宏会在adapter类中增加一个静态的成员函数。而系统将会通过这个函数来控制CardSetMulticast()这个函数的运行。
3.最后是注销中断,这样:
m_Interrupt.Invalidate();
关于中断就介绍到这里。具体的使用过程,以后我们看看通过具体的微端口驱动实例就知道了。
下一课介绍如何生成wdm设备对象,以及被动和主动的与应用程序通信。
第五课 Ndis驱动与应用程序交换信息
驱动程序即使可以做界面也是非常困难的。但是往往用户可以通过一些界面来对驱动程序进行一些设置。某些网络驱动提供固定的dll接口来实现设置。这必须阅读windows的规范。我更喜欢使用自己的用户程序来设置我的网络驱动,这样比较自由。
用户程序将信息发送给驱动可以通过WriteFile,或者DeviceIoControl.这些是标准的windowsAPI函数。一个驱动程序注册了WDM设备之后,在windows2000中将被看作一个类似文件的东西。应用可以对它进行读、写和一些其他的控制。
NDIS 5 提供了一个新的函数,NdisMRegisterDevice,允许用户在Ndis驱动中注册一个设备。实际上在98下,无NDIS5的时候,你不得不用另外的代码来做这件事情。假设下面就是自己撰写的一个函数,你可以通过执行这个函数来得到一个设备,你可以在驱动初始化的时候调用这个函数。
--------------示例5.1 ----------------
NDIS_STATUS MyAdapter::CreateMyDevice()
{
NDIS_STATUS Status;
// 如果在2000下编译,我使用NDIS5的方法.DDK表明,2000下
// 的Ndis驱动“不应”使用IoCreateDevice来创建设备,我没
// 试过会有什么后果
#if VDEV_WIN2K
static PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
NdisZeroMemory(MajorFunction, sizeof(MajorFunction));
MajorFunction[IRP_MJ_CREATE] =
MajorFunction[IRP_MJ_CLOSE] =
MajorFunction[IRP_MJ_CLEANUP] =
MajorFunction[IRP_MJ_READ] =
MajorFunction[IRP_MJ_WRITE] =
MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoDispatch;
Status = NdisMRegisterDevice(
*KNdisMiniDriver:riverInstance(),
KNDIS_STRING_CONST("\Device\MyNdisDevice"),
KNDIS_STRING_CONST("\DosDevices\MyNdisDevice"),
MajorFunction,
&m_pDeviceObject,
&m_DeviceHandle);
// 下面的方法用于win98
#else
PDRIVER_OBJECT pDriverObject = KNdisMiniDriver:riverInstance()->DriverObject();
pDriverObject->MajorFunction[IRP_MJ_CREATE] =
pDriverObject->MajorFunction[IRP_MJ_CLOSE] =
pDriverObject->MajorFunction[IRP_MJ_CLEANUP] =
pDriverObject->MajorFunction[IRP_MJ_READ] =
pDriverObject->MajorFunction[IRP_MJ_WRITE] =
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoDispatch;
Status = IoCreateDevice(
pDriverObject,
0,
KNDIS_STRING_CONST("\Device\MyNdisDevice"),
FILE_DEVICE_NETWORK,
0,
FALSE,
&m_pDeviceObject);
if (STATUS_SUCCESS == Status)
// 告诉io系统我们已经准备好了
m_pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
#endif
return Status;
}
为了记住注册的结果,并准备在驱动卸载的时候释放掉,在向导生成的MyAdapter类中,还增加了两个成员:
NDIS_HANDLE m_DeviceHandle;
PDEVICE_OBJECT m_pDeviceObject;
可以看到这两个成员都在上边的函数用到了。其他在卸载的时候应该这样来注销掉这个设备:
--------------示例5.2 ----------------
VOID MyAdapter::Halt(VOID)
{
#if VDEV_WIN2K
// win2000
if (m_DeviceHandle) {
NdisMDeregisterDevice(m_DeviceHandle);
m_DeviceHandle = NULL;
}
#else
// win98
if (m_pDeviceObject)
IoDeleteDevice(m_pDeviceObject);
#endif
m_pDeviceObject=NULL;
}
在这里您依然可以注意到2000与98的不同之处。
此外注意到上边用到一个函数IoDispatch(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp);这个函数是另外写的,专用来处理外部应用程序到这个设备的io请求。你不能把这个函数作为一个C++类的一个普通成员(除非您标明static ),所以只好作为一个全局函数或者static成员。
在上边的注册中我们已经把所有的请求处理函数都指向这一个函数,因此我们可以专心写好这个函数来解决所有的请求问题。
注意DeviceMyNdisDevice中的MyNdisDevice,这是所谓的符号连接名。您的应用程序将通过这个名字来访问这个驱动。如果系统中本来已经有了这个名字,注册将失败。
因此,建议这个符号尽量复杂,不要重名。有更好的使用GUID的注册方式,当时访问起来也更麻烦。这里不讨论了。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -