帝国为聘老婆你要乖:多种开发工具开发的9054驱动源程序比较(下2)转

来源:百度文库 编辑:中财网 时间:2024/05/09 07:03:50

多种开发工具开发的9054驱动源程序比较(下2)转

(2008-09-30 22:06:46) 转载标签:

driver

杂谈

分类: 驱动开发,嵌入式开发 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
三. 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的起始地址。