用DDK开发的9054驱动 .

时间:2021-06-30 13:04:18

和S5933比较起来,开发PLX9054比较不幸,可能是第一次开发PCI的缘故吧。因为,很多PCI的例子都是对S5933,就连微软出版的《Programming
the Microsoft Windows Driver Model》都提供了一个完整的S5933的例子。

在这篇有关DDK的开发论文里。我将分两个例子来构建PLX9054的驱动,第一个,是对《Windows2000 设备驱动程序设计指南》里的分段DMA例子的扩充,它的结构比较简单,对理解DDK的运作很有帮助;第二个,我将对《Programming the Microsoft Windows Driver Model》里的S5933进行改造,因为这个例子里,涉及的概念较多,但可与前面《武》用DS开发的驱动媲美。以下,我将一面分析程序,一面将程序中涉及的重要概念进行解释。

例一:

//

//为了编程方便,作者提供了一个CUString类。

// Unicode.h

//

// Copyright (C) 2000 by Jerry Lozano

//

//

#pragma once

class CUString {

public:

CUString() {Init(); } // constructor relies on internal Init function

CUString(const char* pAnsiString);

CUString(PCWSTR pWideString);

~CUString(); // destructor gives back buffer allocation

void Init(); // performs "real" initialization

void Free(); // performs real destruct

CUString(const CUString& orig); // copy constructure (required)

CUString operator=(const CUString& rop); // assignment operator overload (required)

BOOLEAN operator==(const CUString& rop) const; // comparison operator overload

CUString operator+(const CUString& rop) const; // concatenation operator

CUString& operator+=(const CUString& rop); // and a convenient concat

operator PWSTR() const; // cast operator into wchar_t

operator UNICODE_STRING&(); // cast into UNICODE_STRING

operator ULONG() const; // cast operator into ULONG

CUString(ULONG value); // converter: ULONG->CUString

WCHAR& operator[](int idx); // buffer access operator

USHORT Length() {return uStr.Length/2;}

protected:

UNICODE_STRING uStr; // W2K kernel structure for Unicode string

enum ALLOC_TYPE {Empty, FromCode, FromPaged, FromNonPaged};

ALLOC_TYPE aType; // where buffer is allocated

};

//以下是Unicode.cpp

#ifdef WIN32DDK_TEST

#include "DDKTestEnv.h"

#else

extern "C" {

#include

}

#endif

#define max(a,b) ((a>b)?a:b)

#include "Unicode.h"

void CUString::Init() {

uStr.Length = 0;

uStr.MaximumLength = 0;

uStr.Buffer = NULL;

aType = Empty;

}

CUString::CUString(const char* pAnsiString) {

ANSI_STRING str;

RtlInitAnsiString(&str, pAnsiString);

uStr.MaximumLength = (USHORT) max(32, RtlAnsiStringToUnicodeSize(&str) );

uStr.Buffer = (PWSTR)

ExAllocatePoolWithTag(PagedPool, uStr.MaximumLength, 1633);

aType = FromPaged;

RtlAnsiStringToUnicodeString(&uStr, &str, FALSE);

}

CUString::CUString(PCWSTR pWideString) {

RtlInitUnicodeString(&uStr, pWideString);

aType = FromCode;

}

CUString::~CUString() {

Free();

}

void CUString::Free() {

if (aType == FromPaged || aType == FromNonPaged)

ExFreePool(uStr.Buffer);

uStr.Buffer = NULL;

uStr.Length = 0;

uStr.MaximumLength = 0;

}

CUString::CUString(const CUString& orig) { // copy constructor (required)

uStr.Length = 0;

uStr.MaximumLength = orig.uStr.MaximumLength;

uStr.Buffer = (PWSTR)

ExAllocatePoolWithTag(PagedPool, uStr.MaximumLength, 1633);

aType = FromPaged;

RtlCopyUnicodeString(&uStr, (PUNICODE_STRING)&orig.uStr);

uStr.Buffer[uStr.Length/2] = UNICODE_NULL;

}

CUString CUString::operator=(const CUString& rop) { // assignment operator overload (required)

if (&rop != this) { // lop == rop ??? why was I called

if (rop.uStr.Length >= uStr.Length || // does it fit?

(aType != FromPaged && aType != FromNonPaged) ) {

// it doesn't fit - free up existing buffer

if (aType == FromPaged || aType == FromNonPaged)

ExFreePool(uStr.Buffer);

uStr.Length = 0;

uStr.MaximumLength = rop.uStr.MaximumLength;

// and allocate fresh space

uStr.Buffer = (PWSTR)

ExAllocatePoolWithTag(PagedPool, uStr.MaximumLength, 1633);

aType = FromPaged;

}

RtlCopyUnicodeString(&uStr, (PUNICODE_STRING)&rop.uStr);

uStr.Buffer[uStr.Length/2] = UNICODE_NULL;

}

return *this;

}

BOOLEAN CUString::operator ==(const CUString& rop) const {

return RtlEqualUnicodeString(&this->uStr, &rop.uStr, FALSE); // case matters

}

CUString::operator PWSTR() const {

return uStr.Buffer;

}

CUString::operator UNICODE_STRING &() {

return uStr;

}

CUString CUString::operator+(const CUString& rop) const {

CUString retVal;

retVal.uStr.Length = this->uStr.Length + rop.uStr.Length;

retVal.uStr.MaximumLength = max(32, retVal.uStr.Length+2);

retVal.uStr.Buffer = (PWSTR)

ExAllocatePoolWithTag(PagedPool, retVal.uStr.MaximumLength, 1633);

RtlCopyUnicodeString(&retVal.uStr, (PUNICODE_STRING)&this->uStr);

RtlAppendUnicodeStringToString(&retVal.uStr, (PUNICODE_STRING)&rop.uStr);

retVal.uStr.Buffer[retVal.uStr.Length/2] = UNICODE_NULL;

return retVal;

}

CUString& CUString::operator+=(const CUString& rop) {

*this = *this + rop;

return *this;

}

CUString::operator ULONG() const {

ULONG retVal;

RtlUnicodeStringToInteger((PUNICODE_STRING)&uStr, 0, &retVal);

return retVal;

}

CUString::CUString(ULONG value) {

// Converts from a ULONG into a CUString

uStr.Length = 0;

uStr.MaximumLength = 32;

uStr.Buffer = (PWSTR)

ExAllocatePoolWithTag(PagedPool, uStr.MaximumLength, 1633);

aType = FromPaged;

RtlIntegerToUnicodeString(value, 0, &uStr);

}

WCHAR& CUString::operator[](int idx) {

// accesses an individual WCHAR in CUString buffer

if (idx >= 0 && idx < uStr.MaximumLength/2)

return uStr.Buffer[idx];

else

return uStr.Buffer[0]; // got to return something

}

//有了CUString整个UNICODE_STRING处理起来就非常方便。

//我喜欢抄程序,真的用手抄,可以感觉一个好的程序的细节之美。

//以下是两个字符串的解释:

// typedef struct _STRING {

// USHORT Length;

// USHORT MaximumLength;

// PCHAR Buffer;

// } ANSI_STRING *PANSI_STRING;

//这是用于ANSI的字符串。

// typedef struct _UNICODE_STRING {

// USHORT Length;

// USHORT MaximumLength;

// PWSTR Buffer; //Pointer to a buffer used to contain a string of wide characters

// } UNICODE_STRING *PUNICODE_STRING;

//这是驱动用的字符串。使用RtlInitUnicodeString()进行初始化。还可以用以下例程:

//RtlAnsiStringToUnicodeSize, RtlAnsiStringToUnicodeString, RtlFreeUnicodeString, //RtlInitUnicodeString, RtlUnicodeStringToAnsiSize, RtlUnicodeStringToAnsiString

接下来,我想通过比较两个DDK驱动,然后,得出自己的驱动。这两个驱动分别是前两经典的例子,为行文方便,我用《WDM》表示取自《Programming the Microsoft Windows Driver Model》的例子(Chap7PKTDMA),用《2000》表示取自《Windows2000 设备驱动程序设计指南》的例子(Chap12 DMASlave)。

一. DEVICE_EXTENSION的比较

《2000》:

enum DRIVER_STATE {Stopped, Started, Removed};

typedef struct _DEVICE_EXTENSION {

PDEVICE_OBJECT pDevice;

PDEVICE_OBJECT pLowerDevice;

ULONG DeviceNumber;

CUString ustrDeviceName; // internal name

CUString ustrSymLinkName; // external name

PUCHAR portBase; // I/O register address

ULONG portLength;

KIRQL IRQL; // Irq for parallel port

ULONG Vector;

KAFFINITY Affinity;

PKINTERRUPT pIntObj; // the interrupt object

BOOLEAN bInterruptExpected; // TRUE iff this driver is expecting interrupt

DRIVER_STATE state; // current state of driver

PDMA_ADAPTER pDmaAdapter;

ULONG mapRegisterCount;

ULONG dmaChannel;

// This is the "handle" assigned to the map registers

// when the AdapterControl routine is called back

PVOID mapRegisterBase;

ULONG bytesRequested;

ULONG bytesRemaining;

ULONG transferSize;

PUCHAR transferVA;

// This flag is TRUE if writing, FALSE if reading

BOOLEAN bWriting;

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

////////////////////////////////////////////////////////////////////

//《WDM》:

typedef struct tagDEVICE_EXTENSION {

PDEVICE_OBJECT DeviceObject; // device object this extension belongs to,同

PDEVICE_OBJECT LowerDeviceObject; // next lower driver in same stack,同

PDEVICE_OBJECT Pdo; // the PDO

IO_REMOVE_LOCK RemoveLock; // removal control locking structure,使用了IO_REMOVE_LOCK.

UNICODE_STRING devname; //内部名称

PGENERIC_EXTENSION pgx; // device extension for GENERIC.SYS

//在《WDM》的构建过程中,大量相同的工作被放在了GENERIC.SYS。

DEVQUEUE dqReadWrite; // queue for reads and writes

//这是用于IRP串行化处理的一个类,在GENERIC.SYS里定义

LONG handles; // # open handles

PKINTERRUPT InterruptObject; // address of interrupt object,同

PUCHAR portbase; // I/O port base address,同

ULONG nports; // number of assigned ports,同

PADAPTER_OBJECT AdapterObject; // DMA adapter object,同

ULONG nMapRegisters; // maximum # mapping registers

ULONG xfer; // # bytes to transfer in this stage,同前bytesRequested;

ULONG numxfer; // # bytes transferred so far,同前transferSize;

ULONG nbytes; // # bytes remaining to transfer,同前bytesRemaining;

ULONG nMapRegistersAllocated; // # map registers allocated for this transfer

PVOID vaddr; // virtual addr for this stage of transfer,同前transferVA;

PVOID regbase; // handle for base of mapping register set,同前mapRegisterBase;

ULONG intcsr; // accumulated interrupt flags

BOOLEAN mappedport; // true if we mapped port addr in StartDevice

BOOLEAN busy; // true if device busy with a request,同前state.

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

/////////////////////////////////////////////////

//我的DEVICE_EXTENSION,参考了前两个定义,并以《WDM》为蓝本。

enum DRIVER_STATE{ Stopped, Started, Removed};

typedef struct tagDEVICE_EXTENSION {

PDEVICE_OBJECT DeviceObject; // device object this extension belongs to,同

PDEVICE_OBJECT LowerDeviceObject; // next lower driver in same stack,同

PDEVICE_OBJECT Pdo; // the PDO,《WDM》

// IO_REMOVE_LOCK RemoveLock; // removal control locking structure,使用了IO_REMOVE_LOCK.

//因为想简单些,所以没用,其实不用也是可以的。

ULONG DeviceNumber; //来自《2000》,用于DeviceObject计数

UNICODE_STRING devname; //内部名称

UNICODE_STRING SymLinkName; //根据《2000》加的。用于外部程序引用。

// PGENERIC_EXTENSION pgx; // device extension for GENERIC.SYS

//在《WDM》的构建过程中,大量相同的工作被放在了GENERIC.SYS。

//因为本驱动,不需要GENERIC.SYS,其中的许多工作将在程序自身中完成。

// DEVQUEUE dqReadWrite; // queue for reads and writes

//这是用于IRP串行化处理的一个类,在GENERIC.SYS里定义

//因为简化程序的控制,本驱动程序不用IRP串行处理。

// LONG handles; // # open handles

PKINTERRUPT InterruptObject; // address of interrupt object,同

PUCHAR portbase; // I/O port base address,同

ULONG nports; // number of assigned ports,同

PADAPTER_OBJECT AdapterObject; // DMA adapter object,同

ULONG nMapRegisters; // maximum # mapping registers

ULONG xfer; // # bytes to transfer in this stage,同前bytesRequested;

ULONG numxfer; // # bytes transferred so far,同前transferSize;

ULONG nbytes; // # bytes remaining to transfer,同前bytesRemaining;

ULONG nMapRegistersAllocated; // # map registers allocated for this transfer

PVOID vaddr; // virtual addr for this stage of transfer,同前transferVA;

PVOID regbase; // handle for base of mapping register set,同前mapRegisterBase;

ULONG intcsr; // accumulated interrupt flags

BOOLEAN mappedport; // true if we mapped port addr in StartDevice

DRIVER_STATE state; // current state of driver.来自《2000》

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

///////////////////////////////////////////////////////////////////////////////////////

二. DriverEntery的比较

《2000》:

//++

// Function: DriverEntry

//

// Description:

// Initializes the driver.

//

// Arguments:

// pDriverObject - Passed from I/O Manager

// pRegistryPath - UNICODE_STRING pointer to

// registry info (service key)

// for this driver

//本例子中没有对注册表进行操作,此pRegistryPath对应的是INF在注册表中设置的驱动注册目录

// Return value:

// NTSTATUS signaling success or failure

//--

extern "C" NTSTATUS DriverEntry (

IN PDRIVER_OBJECT pDriverObject,

IN PUNICODE_STRING pRegistryPath ) {

ULONG ulDeviceNumber = 0;

NTSTATUS status = STATUS_SUCCESS;

// Announce other driver entry points

pDriverObject->DriverUnload = DriverUnload;

// Announce the PNP AddDevice entry point

pDriverObject->DriverExtension->AddDevice = AddDevice;

// Announce the PNP Major Function entry point

pDriverObject->MajorFunction[IRP_MJ_PNP] = DispPnp;

// This includes Dispatch routines for Create, Write & Read

pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;

pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;

pDriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchReadWrite;

pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchReadWrite;

pDriverObject->DriverStartIo = StartIo;

// Notice that no device objects are created by DriverEntry.

// Instead, we await the PnP call to AddDevice

return status;

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//《WDM》

extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,

IN PUNICODE_STRING RegistryPath)

{ // DriverEntry

KdPrint((DRIVERNAME " - Entering DriverEntry: DriverObject %8.8lXn", DriverObject));

// Insist that OS support at least the WDM level of the DDK we use

if (!IoIsWdmVersionAvailable(1, 0))

{

KdPrint((DRIVERNAME " - Expected version of WDM (%d.%2.2d) not availablen", 1, 0));

return STATUS_UNSUCCESSFUL;

}

// See if we're running under Win98 or NT:

win98 = IsWin98();

#if DBG

if (win98)

KdPrint((DRIVERNAME " - Running under Windows 98n"));

else

KdPrint((DRIVERNAME " - Running under NTn"));

#endif

// Save the name of the service key

servkey.Buffer = (PWSTR) ExAllocatePool(PagedPool, RegistryPath->Length + sizeof(WCHAR));

if (!servkey.Buffer)

{

KdPrint((DRIVERNAME " - Unable to allocate %d bytes for copy of service key namen", RegistryPath->Length + sizeof(WCHAR)));

return STATUS_INSUFFICIENT_RESOURCES;

}

servkey.MaximumLength = RegistryPath->Length + sizeof(WCHAR);

RtlCopyUnicodeString(&servkey, RegistryPath);

// Initialize function pointers

DriverObject->DriverUnload = DriverUnload;

DriverObject->DriverExtension->AddDevice = AddDevice;

DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;

DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;

DriverObject->MajorFunction[IRP_MJ_READ] = DispatchReadWrite;

DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchReadWrite;

DriverObject->MajorFunction[IRP_MJ_CLEANUP] = DispatchCleanup;

DriverObject->MajorFunction[IRP_MJ_POWER] = DispatchPower;

DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp;

return STATUS_SUCCESS;

} // DriverEntry

//比较这两个DriverEntry,基本是相同的。以下就以《2000》为蓝本来构建我的DriverEntery,因其简洁

extern "C" NTSTATUS DriverEntry (

IN PDRIVER_OBJECT pDriverObject,

IN PUNICODE_STRING pRegistryPath ) {

ULONG ulDeviceNumber = 0;

NTSTATUS status = STATUS_SUCCESS;

// Announce other driver entry points

pDriverObject->DriverUnload = DriverUnload;

// Announce the PNP AddDevice entry point

pDriverObject->DriverExtension->AddDevice = AddDevice;

// Announce the PNP Major Function entry point

pDriverObject->MajorFunction[IRP_MJ_PNP] = DispPnp;

// This includes Dispatch routines for Create, Write & Read

pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;

pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;

pDriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchReadWrite;

pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchReadWrite;

pDriverObject->DriverStartIo = StartIo;

// Notice that no device objects are created by DriverEntry.

// Instead, we await the PnP call to AddDevice

return status;

}

三. AddDevice的实现

由于《WDM》的AddDevice用的是GENERIC.SYS来初始化,与《2000》有很多不同,较繁琐,而我的驱动是以《2000》为蓝本来写的,此处就不贴《WDM》的。由于我们的DEVICE_EXTENSION改了许多,所以要重新改写此AddDevice。以下就是改写后的部分:

//++

// Function: AddDevice

//

// Description:

// Called by the PNP Manager when a new device is

// detected on a bus. The responsibilities include

// creating an FDO, device name, and symbolic link.

//

// Arguments:

// pDriverObject - Passed from PNP Manager

// pdo - pointer to Physcial Device Object

// passed from PNP Manager

//

// Return value:

// NTSTATUS signaling success or failure

//--

NTSTATUS AddDevice (

IN PDRIVER_OBJECT pDriverObject,

IN PDEVICE_OBJECT pdo ) {

NTSTATUS status;

PDEVICE_OBJECT pfdo;

PDEVICE_EXTENSION pDevExt;

static int ulDeviceNumber = 0;



// Form the internal Device Name

CUString devName("\Device\9054DMA"); // for "9054 DMA" dev

devName += CUString(ulDeviceNumber);



// Now create the device

status =

IoCreateDevice( pDriverObject,

sizeof(DEVICE_EXTENSION),

&(UNICODE_STRING)devName,

FILE_DEVICE_UNKNOWN,

0, FALSE,

&pfdo );

if (!NT_SUCCESS(status))

return status;



// Choose to use DIRECT_IO (typical for DMA)

pfdo->Flags |= DO_DIRECT_IO;



// Initialize the Device Extension

//根据DEVICE_EXTENSION完成初始化

pDevExt = (PDEVICE_EXTENSION)pfdo->DeviceExtension;

pDevExt->DeviceObject = pfdo; // back pointer

pDevExt->DeviceNumber = ulDeviceNumber;

pDevExt->DevName = devName;

pDevExt->Pdo = pdo; //来自例程的输入参数

pDevExt->state = Stopped;



// Pile this new fdo on top of the existing lower stack

pDevExt->LowerDeviceObject = // downward pointer

IoAttachDeviceToDeviceStack( pfdo, pdo);



// This is where the upper pointer would be initialized.

// Notice how the cast of the lower device's extension

// must be known in order to find the offset pUpperDevice.

// PLOWER_DEVEXT pLowerDevExt = (PLOWER_DEVEXT)

// pDevExt->pLowerDevice->DeviceExtension;

// pLowerDevExt->pUpperDevice = pfdo;



// Form the symbolic link name

CUString symLinkName("\??\DMAS");

symLinkName += CUString(ulDeviceNumber+1); // 1 based

pDevExt->SymLinkName = symLinkName;



// Now create the link name

status =

IoCreateSymbolicLink( &(UNICODE_STRING)symLinkName,

&(UNICODE_STRING)devName );

if (!NT_SUCCESS(status)) {

// if it fails now, must delete Device object

IoDeleteDevice( pfdo );

return status;

}



// We need a DpcForIsr registration

IoInitializeDpcRequest(

pfdo,

DpcForIsr );



// Clear the Device Initializing bit since the FDO was created

// outside of DriverEntry.

pfdo->Flags &= ~DO_DEVICE_INITIALIZING;



// Made it

ulDeviceNumber++;

return STATUS_SUCCESS;

}

四. DispPnp的实现

当总线驱动器扫描到硬件时,就会发出一个IRP_MJ_PNP的IRP,它是实现PNP类型的驱动初始化硬件资源的地方。

我采用了《2000》的代码,因其清楚明了。

NTSTATUS DispPnp( IN PDEVICE_OBJECT pDO,

IN PIRP pIrp ) {

// obtain current IRP stack location

PIO_STACK_LOCATION pIrpStack;

pIrpStack = IoGetCurrentIrpStackLocation( pIrp );



switch (pIrpStack->MinorFunction) {

case IRP_MN_START_DEVICE:

return HandleStartDevice(pDO, pIrp );

case IRP_MN_STOP_DEVICE:

return HandleStopDevice( pDO, pIrp );

case IRP_MN_REMOVE_DEVICE:

return HandleRemoveDevice( pDO, pIrp );

default:

// if not supported here, just pass it down

return PassDownPnP(pDO, pIrp);

}



// all paths from the switch statement will "return"

// the results of the handler invoked

}



//这是不处理的IRP,将它传到下一层。

NTSTATUS PassDownPnP( IN PDEVICE_OBJECT pDO,

IN PIRP pIrp ) {

IoSkipCurrentIrpStackLocation( pIrp );

PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)

pDO->DeviceExtension;

return IoCallDriver(pDevExt->pLowerDevice, pIrp);

}



//上面的程序,我基本没有改动。

//接下来是要改动的部分

//这里可以跟《武》例子中的NTSTATUS PCI9054Device::OnStartDevice(Kirp I)进行比较了。



NTSTATUS HandleStartDevice( IN PDEVICE_OBJECT pDO,IN PIRP pIrp )

{

// The stack location contains the Parameter info



PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );

PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDO->DeviceExtension;



PCM_RESOURCE_LIST pResourceList;

PCM_FULL_RESOURCE_DESCRIPTOR pFullDescriptor;

PCM_PARTIAL_RESOURCE_LIST pPartialList;

PCM_PARTIAL_RESOURCE_DESCRIPTOR pPartialDescriptor;

int i;

NTSTATUS status;



ULONG vector;

KIRQL irql;

KINTERRUPT_MODE mode;

KAFFINITY affinity;

BOOLEAN irqshare;

BOOLEAN gotinterrupt = FALSE;

PHYSICAL_ADDRESS portbase;

BOOLEAN gotport = FALSE;



pResourceList = pIrpStack->Parameters.StartDevice.AllocatedResourcesTranslated;

pFullDescriptor = pResourceList->List;

pPartialList = &pFullDescriptor->PartialResourceList;

for (i=0; i<(int)pPartialList->Count; i++)

{

pPartialDescriptor = &pPartialList->PartialDescriptors[i];

switch (pPartialDescriptor->Type) {

case CmResourceTypeInterrupt:

irql = (KIRQL)pPartialDescriptor->u.Interrupt.Level;

vector = pPartialDescriptor->u.Interrupt.Vector;

affinity = pPartialDescriptor->u.Interrupt.Affinity;

mode = (resource->Flags == CM_RESOURCE_INTERRUPT_LATCHED)?

Latched : LevelSensitive;

Irqshare = resource->ShareDisposition == CmResourceShareShared;

Gotinterrupt = TRUE;

break;



case CmResourceTypePort:

portbase = pPartialDescriptor->u.Port.Start;

pDevExt->nports = pPartialDescriptor->u.Port.Length;

gotport = TRUE;

break;



case default:

// We don't do memory usage

break;

}

}







// Make sure we got our interrupt (and port) resources

// Fail this IRP if we didn't.

// Most likely cause for no interrupt:

// Failure to request an interrupt resource for the

// printer port from the Device Manager.

// Be SURE to use Control Panel...

// Administrative Tools...

// Computer Management...

// Device Manager...

// Then select Ports...Printer Port (LPT1)

// From the Port Settings tab,

// select "Use any interrupt assigned to the port"

if (!(gotport && gotinterrupt))

return STATUS_DEVICE_CONFIGURATION_ERROR;



INTERFACE_TYPE bustype; //获得总线类型

ULONG junk;

status = IoGetDeviceProperty( pdx->Pdo, DeviePropertyLegacyBusType,

sizeof(bustype), &bustype, &junk);

if(!NT_SUCCESS(status))

return status;



pDevExt ->portbase = (PUCHAR)portbase.QuadPart;



//构建一个DMA ADAPTER对象

DEVICE_DESCRIPTION dd;

RtlZeroMemory(&dd, sizeof(dd));

dd.Version = DEVICE_DESCRIPTION_VERSION;

dd.Master = TRUE;

dd.ScatterGather = FALSE;

dd.DemondMode = TRUE;

dd.AutoInitialize = FALSE;

dd.Dma32BitAddress = TRUE;

dd.IgnoreCount = FALSE;

dd.DmaChannel = 0;

dd.InterfaceType = bustype; //《武》中是 PCIBus;

dd.DmaWidth = Width32Bits; // PCI default width

dd.DmaSpeed = Compatible;

dd.MaximumLength = 0x1000;

//仔细比较,发现与《武》的DEVICE_DESCRIPTION的初始化完全一致。

//以下是完整的DEVICE_DESCRIPTOR的定义

//typedef struct _DEVICE_DESCRIPTION {

// ULONG Version;

// BOOLEAN Master;

// BOOLEAN ScatterGather;

// BOOLEAN DemandMode;

// BOOLEAN AutoInitialize;

// BOOLEAN Dma32BitAddresses;

// BOOLEAN IgnoreCount;

// BOOLEAN Reserved1;

// BOOLEAN Dma64BitAddresses;

// ULONG BusNumber;

// ULONG DmaChannel;

// INTERFACE_TYPE InterfaceType;

// DMA_WIDTH DmaWidth;

// DMA_SPEED DmaSpeed;

// ULONG MaximumLength;

// ULONG DmaPort;

// } DEVICE_DESCRIPTION, *PDEVICE_DESCRIPTION;

//现在是将全部都设定了初值。



pdx->AdapterObject = IoGetDmaAdapter(pDevExt ->Pdo, &dd, & pDevExt ->nMapRegisters);

//最后一个参数是指最大的可以映射的寄存器组个数。

if (!pDevExt ->AdapterObject)

{ // can't create adapter object

KdPrint((DRIVERNAME " - Unable to create DMA adapter object\n"));

pDevExt ->portbase = NULL;

return STATUS_UNSUCCESSFUL;

} // can't create adapter object



// Create & connect to an Interrupt object

status =

IoConnectInterrupt(

&pDevExt->InterruptObject, // the Interrupt object

Isr, // our ISR

pDevExt, // Service Context

NULL, // no spin lock

vector, // vector

irql, // DIRQL

irql, // DIRQL

mode, // Latched or LevelSensitive

irqshare, // Shared?

affinity, // processors in an MP set

FALSE ); // save FP registers?

if (!NT_SUCCESS(status)) {

return status;

}



//允许PCI中断和DMA通道0中断

WRITE_PORT_ULONG((PULONG)( pDevExt->portbase + INTCSR), 0x40100);

//其中INTCSR是有9054 DATASHEET中得到的是0x68。

pDevExt->state = Started;



return PassDownPnP(pDO, pIrp);

}

NTSTATUS HandleStopDevice( IN PDEVICE_OBJECT pDO,

IN PIRP pIrp ) {

PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDO->DeviceExtension;

//禁止PCI中断和DMA通道0中断

WRITE_PORT_ULONG((PULONG)( pDevExt->portbase + INTCSR), 0);



// Delete our Interrupt object

if (pDevExt->InterruptObject)

IoDisconnectInterrupt( pDevExt-> InterruptObject);

pDevExt-> InterruptObject = NULL;



// Delete the DMA Adapter object

pDevExt->AdapterObject->DmaOperations->

FreeAdapterChannel( pDevExt->AdapterObject );

pDevExt-> AdapterObject = NULL;





pDevExt->state = Stopped;



return PassDownPnP(pDO, pIrp);

}





以下是 DMA 传输的步骤:

(1)在IRP_MJ_START_DEVICE的处理过程中,使用IoGetDmaAdapter,结合DEVICE_DESCRIPTION,构建AdapterObject。

(2)在StartIo中,通过AllocateAdapterChannel启动回调例程AdapterControl(这个名字可不同)。

(3) 在AdapterControl中,使用MapTransfer,映射连续内存,并开始进行第一次传输

(4) 当DMA传输完,将产生一个中断,由ISR处理程序处理。

(5) ISR服务子程序,将实际处理任务交给DPC服务例程。

(6) DPC检测是否完成传输任务,如果没有,则再调用MapTransfer,映射连续内存,然后再启动下一次传输。如果完成,则结束此IRP.

其过程和前面《DS》处理DMA的流程很相似。

当用户程序发出读、写请求时,驱动程序将收到IRP_MJ_READ,或IRP_MJ_WRITE的IRP,然后,启动StartIo实现DMA传输。但DMA传输结束,9054会发出中断请求,驱动程序会启动ISR服务程序,为将IRQL的级别降低,ISR会将实际的工作交给DPC来完成。以下是9054的DMA所需代码。包括:StartIo, Isr, Dpc,及此例程中用到的子函数。



一. StartIo



VOID StartIo(IN PDEVICE_OBJECT fdo, IN PIRP Irp)

{ // StartIo

PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);



PMDL mdl = Irp->MdlAddress;



pdx->numxfer = 0;

pdx->xfer = pdx->nbytes = MmGetMdlByteCount(mdl);

pdx->vaddr = MmGetMdlVirtualAddress(mdl);



ULONG nregs = ADDRESS_AND_SIZE_TO_SPAN_PAGES(pdx->vaddr, pdx->nbytes);

if (nregs > pdx->nMapRegisters)

{ // staged transfer needed

nregs = pdx->nMapRegisters;

pdx->xfer = nregs * PAGE_SIZE - MmGetMdlByteOffset(mdl);

} // staged transfer needed

//注意,在这里完成了数据包的第一次分割。

//nregs中保存着整个DMA要传输的数据块所需的页面数。

//而nMapRegisters是在IoGetDmaAdapter中返回的系统可以映射的最大页面数量。

//当nregs > pdx->nMapRegisters,就必须减少nregs。



pdx->nMapRegistersAllocated = nregs; // save for deallocation later



status = (*pdx->AdapterObject->DmaOperations->AllocateAdapterChannel)

(pdx->AdapterObject, fdo, nregs, (PDRIVER_CONTROL) AdapterControl, pdx);

//此处设置了AdapterControl,

//将在AdapterControl里完成实际的DMA硬件控制。



if (!NT_SUCCESS(status))

{

pIrp->IoStatus.Status = status;

pIrp->IoStatus.Information = 0;

IoCompleteRequest(pIrp, IO_NO_INCREMENT);

IoStartNextPacket(fdo, FALSE);

}

} // StartIo

///////////////////////////////////////////////////////////////////////////////



#pragma LOCKEDCODE



IO_ALLOCATION_ACTION AdapterControl(PDEVICE_OBJECT fdo, PIRP junk, PVOID regbase, PDEVICE_EXTENSION pdx)

{ // AdapterControl

PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);

PMDL mdl = Irp->MdlAddress;

BOOLEAN isread = IoGetCurrentIrpStackLocation(Irp)->MajorFunction == IRP_MJ_READ;

pdx->regbase = regbase;

KeFlushIoBuffers(mdl, isread, TRUE);

PHYSICAL_ADDRESS address = (*pdx->AdapterObject->DmaOperations->MapTransfer)

(pdx->AdapterObject, mdl, regbase, pdx->vaddr, &pdx->xfer, !isread);

//注意:只有在MapTransfer中映射的区域才是实际一次DMA传输的区域。

//这里的pdx->xfer是IN OUT类型,输出时是实际的传输量,可能与输入时的值不一样

//这里完成了数据的第二次截断。



//实际的硬件寄存器操作在下面进行。

StartTransfer(pdx, address, isread);



return DeallocateObjectKeepRegisters;

} // AdapterControl



//////////////////////////////////////////////////////////////////////////////////////////

#pragma LOCKEDCODE



VOID StartTransfer(PDEVICE_EXTENSION pdx, PHYSICAL_ADDRESS address, BOOLEAN isread)

{ // StartTransfer



// Setup read or write transfer registers. Note that the 9054 calls a transfer

// from memory to the device a "read"

//Channel0 interrupt to the PCI Bus interrupt,Done Interrupt Enable,FIFO

WRITE_PORT_ULONG((PULONG) (pdx->portbase + DMAMODE0), 0x20C00);



//DMA Channel0 PCI Address

WRITE_PORT_ULONG((PULONG) (pdx->portbase + DMAPADR0), address);



//DMA Channel0 Local Address,自己设计的FIFO地址

WRITE_PORT_ULONG((PULONG) (pdx->portbase + DMALADR0), 0x8);



//DMA Channel0 Transfer Size(Bytes)

WRITE_PORT_ULONG((PULONG) (pdx->portbase + DMASIZ0), pdx->xfer);



if (isread)

{ // read from device

//from the Local Bus to the PCI Bus

WRITE_PORT_ULONG((PULONG) (pdx->portbase + DMADPR0), 0x8);

} // read from device



else

{ // write to device

//from the PCI Bus to the Local Bus

WRITE_PORT_ULONG((PULONG) (pdx->portbase + DMADPR0), 0x0);

} // write to device



// start the transfer



pdx->state = Started;



//Channel0 Enable,Start

WRITE_PORT_ULONG((PULONG) (pdx->portbase + DMACSR0), 0x3);

} // StartTransfer





以下是《武》的StartDma

VOID PCI9054Device::StartDMA(ULONG PAddress,ULONG NBytes)

{

//下面几条语句设置DMA通道0寄存器,启动块传输方式,从FIFO读数据

//Channel0 interrupt to the PCI Bus interrupt,Done Interrupt Enable,FIFO

m_IoPortRange0.outd(DMAMODE0,0x20C00);

//DMA Channel0 PCI Address

m_IoPortRange0.outd(DMAPADR0,PAddress);

//DMA Channel0 Local Address,自己设计的FIFO地址

m_IoPortRange0.outd(DMALADR0,0x8);

//DMA Channel0 Transfer Size(Bytes)

m_IoPortRange0.outd(DMASIZ0,NBytes);

//from the Local Bus to the PCI Bus

m_IoPortRange0.outd(DMADPR0,0x8);

//Channel0 Enable,Start

m_IoPortRange0.outb(DMACSR0,0x3);

}



对DMA操作的步骤:

1. 设置DMA MODE 以适应BLOCK方式

2. 写入PCI Bus的起始地址 ,用于初始化DMAPADR

3. 写入LOCAL Bus 的起始地址,用于初始化DMALADR

4. 写入DMA传输数据块的大小,用于初始化DMASIZE

5. 确定传输方向,DMADPR[3],1是L到P,0是P到L。

6. 启动DMA,DMACSR的【1:0】,[0]是Channel0 Enable,【1】是Channel0 Start.



注意MapTransfer返回了映射后的地址,9054将使用它作为PCI Bus的起始地址。《转自网易博友》