📄 document.html
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0052)http://www.vu.union.edu/~dixonw/driver/document.html -->
<HTML><HEAD><TITLE>Sample Windows NT Device Driver Documentation</TITLE>
<META content="text/html; charset=gb2312" http-equiv=Content-Type>
<META content="MSHTML 5.00.2614.3500" name=GENERATOR></HEAD>
<BODY>
<CENTER>
<H1>Walt Dixon's<BR>Sample Windows NT Device Driver<BR>Documentation </H1><A
href="mailto:dixonw@virtual.union.edu">dixonw@virtual.union.edu</A> </CENTER>
<HR>
<H2>Why I wrote it</H2>This driver provides a gateway between the physical
protected sub-system and the user mode process. Since the DT2851 has both a port
I/O and memory mapped interface, the device driver must be responsible for both
channels of communication.
<HR>
<H2>Driver Entry</H2>Each kernel mode driver has an initialization routine
called the DriverEntry. This routine is responsible for allocating the necessary
resources, setting up the dispatch entry points that handle the I/O request
packets, and optionally defining an unload routine. This unload routine is
necessary if the driver is designed to be loaded and or replaced dynamically,
where unloading the driver requires that claimed resources be freed.
<P><PRE><B>
DriverEntry( IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING registryPath );
</B></PRE>
<P>The DriverEntry routine is passed two arguments: a pointer to the driver
object, which is created by the system, and a pointer to the registry entry. The
Registry is a small database which contains information about the installed
services and hardware configuration. For the DT2851 driver, the registry
contains the base port and memory address for the image processing card. These
fields can be configured using NT's registry editor, and are based on the card's
jumper settings. If for some reason the configuration could not be found in the
registry, the driver is set up to use default values for the base port and
memory addresses.
<P><PRE><B>
if (!NT_SUCCESS(RtlQueryRegistryValues(
RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL,
paramPath.Buffer,
¶mTable[0],
NULL,
NULL)))
{
physicalPortAddress32 = defaultportBase;
physicalMemoryAddress32 = defaultphysicalMemoryAddress32;
}
</B></PRE>
<P>NT's protected subsystem insulate user applications from having to know
anything about kernel mode components. NT's I/O manager works in a similar
fashion, where the I/O manager insulates the protected subsystems from having to
know anything about machine specific device configurations, or about the drivers
implementation. It is for this reason the driver must set itself up to handle
the calls from the I/O manager. Depending on the type of interaction that the
driver is intended to have with the system and with other drivers, determines
what type of I/O requests the driver must support. Since the DT2851 device
driver is relatively simple, the driver only needs to support opening and
closing of a device object, read and write I/O requests to the port and memory
mapped interface, and dynamic unloading to free the system resources when the
driver is stopped. In order to provide this functionality the DriverEntry
routine is responsible for setting up a function table to handle supported I/O
interaction.
<P><PRE><B>
driverObject->MajorFunction[IRP_MJ_CREATE] = DT2851Dispatch;
driverObject->MajorFunction[IRP_MJ_CLOSE] = DT2851Dispatch;
driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DT2851Dispatch;
driverObject->DriverUnload = DT2851Unload;
</B></PRE>
<P>The Driver Entry routine must also create a device object, which represents a
physical, logical or virtual device being loaded into the system. A call is made
to IoCreateDevice to allocate memory and initialize the object. The device
object also has a device extension that provides the driver with space to store
non-pageable information needing to be accessed outside the Driver Entry
routine. The DT2851's device extension is used to store the base address of the
port and mapped memory interface.
<P><PRE><B>
IoCreateDevice (driverObject,
sizeof(DT2851_EXTENSION),
&deviceNameUnicodeString,
DT2851_TYPE,
0,
TRUE,
&deviceObject);
</B></PRE>
<P>After creating the device object, the driver now needs to map the port, and
memory mapped interface to physical address space. A call is made to
HalTranslateBusAddress, converting these base physical addresses to a virtual
addresses in system space. On the Intel architecture, where the port interface
is separate from the memory address space, HalTranslateBusAddress is the only
call that needs to be made to map a port address into physical space.
<P><PRE><B>
HalTranslateBusAddress ( Isa, // device on ISA bus
0, // bus number 0
physicalAddress64, // physical address
&memType, // address space
&mappedAddress64); // translated address
</B></PRE>
<P>For the memory mapped interface; however, the translated system address is
then used in the call to MmMapIoSpace, which maps a range of addresses into
system space.
<P><PRE><B>
MmMapIoSpace ( mappedAddress64,
addressSpace,
FALSE); // don't cache
</B></PRE>
<P>After preserving the translated base port and memory addresses in the driver
extension, the Driver Entry routine is complete. The routine allocated a driver
object, set up the required dispatch function tables, and mapped the physical
addresses of the port and memory mapped interface into system address space.
<HR>
<H2>Driver Dispatch</H2>The DriverDispatch routine is called by the I/O manager
with a requested operation. It is this routine's responsibility to service the
I/O request packet, by either processing the IRP itself or by calling the
appropriate routine to handle the operation.
<P><PRE><B>
DT2851Dispatch( IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIORequestPacket );
</B></PRE>
<P>When the I/O manager calls a driver dispatch routine, it uses the function
table set up in the driver entry routine. The I/O manager also passes a pointer
to the current I/O request packet (IRP), which is a data structure passed to
device drivers. This structure is created by the I/O manager, or subsystem that
is responsible for high level I/O. The dispatch routine must then locate the
current IRP stack frame, with a call to IoGetCurrentIrpStackLocation.
<P><PRE><B>
pIORequestPacket = IoGetCurrentIrpStackLocation(pIORequestPacket);
</B></PRE>
<P>In general the a device driver consists of several layers, where the driver
must be aware of the bracketed layers, continually setting up the stack location
for the next driver layer. Once the dispatch routine has access to the current
IRP stack location, the driver must determine what operation is being requested.
The DT2851Dispatch routine simply switches on
pIORequestPacketStack->MajorFunction.
<P><PRE><B>
switch (pIORequestPacketStack->MajorFunction)
{
case IRP_MJ_CREATE:
case IRP_MJ_CLOSE:
case IRP_MJ_DEVICE_CONTROL: // Dispatch on IOCTL
switch (pIORequestPacketStack
->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_DT2851_READ_PORT:
case IOCTL_DT2851_WRITE_PORT:
case IOCTL_DT2851_READ_MEMORY:
case IOCTL_DT2851_WRITE_MEMORY:
case IOCTL_DT2851_FILL_MEMORY:
}
}
</B></PRE>
<P>The IRP_MJ_CREATE and IRP_MJ_CLOSE cases do nothing special in the
DT2851Dispatch routine. Upon decoding these requests the dispatch routine simply
return SUCCESS. The major responsibility of the dispatch routine is to handle
the case of IRP_MJ_DEVICE_CONTROL. Upon receiving this request, the driver must
then determine what type of I/O request to process. The type of I/O request is
based on the driver defined IOCTL operations. The driver then calls the
appropriate function to process the IOCTL request, and control returns to the
user process.
<P>Separate routines have been written to handle the supported IOCTL requests.
Each receive a pointer to the DT2851's device extension, a pointer to the
current I/O request packet, and a pointer to the IRP stack. By accessing the
AssociatedIrp.SystemBuffer member of the IRP structure, the kernel mode driver
has access to copies of the user supplied I/O buffers.
<P><PRE><B>
DT2851ReadPort( IN PDT2851_EXTENSION pLDI,
IN PIRP pIORequestPacket,
IN PIO_STACK_LOCATION pIrpStack );
DT2851WritePort( IN PDT2851_EXTENSION pLDI,
IN PIRP pIORequestPacket,
IN PIO_STACK_LOCATION pIrpStack );
DT2851ReadMemory( IN PDT2851_EXTENSION pLDI,
IN PIRP pIORequestPacket,
IN PIO_STACK_LOCATION pIrpStack );
DT2851WriteMemory( IN PDT2851_EXTENSION pLDI,
IN PIRP pIORequestPacket,
IN PIO_STACK_LOCATION pIrpStack );
DT2851FillMemory( IN PDT2851_EXTENSION pLDI,
IN PIRP pIORequestPacket,
IN PIO_STACK_LOCATION pIrpStack );
</B></PRE>
<P>The I/O manager supports both buffered and direct I/O operations between a
user process and device driver. Using Buffered I/O, the driver reads and writes
to an address space allocated by the I/O manager, rather than directly to the
buffer supplied to the driver by the user mode process. The main advantage that
buffered I/O has over direct I/O is that in NT, there is no guarantee that an
I/O request will complete while the user process is still in context. Buffering
the I/O ensures that the device driver has access to information regardless of
process context. The system allocated buffers are automatically locked down, and
are available to the driver for the duration of the driver entry routine. Before
entering the driver dispatch routine, the I/O manager copies the user supplied
input buffer into a system allocated buffer, and after servicing the driver
dispatch routine, the I/O manager copies the system output buffer into the
supplied user buffer.
<P>Reading and writing to the port using buffered I/O works well. The buffers
are small, so the overhead of buffering the I/O is negligible. However, the
mapped memory interface uses a 256 Kbytes buffer to transfer information between
the user mode process and the driver. Although not unreasonable to replicate a
buffer of this size in system space, the driver can be written to avoid the
extra copying. The memory mapped interface still uses buffered I/O, however the
buffer supplied to the driver dispatch routine contains a pointer to the
user-supplied video image buffer. Because this driver is not layered, the user
supplied buffer is guaranteed not to switch out of context. The only danger that
exists is the chance that the user-supplied buffer is invalid. To eliminate the
possibility of the kernel mode process from accessing an illegal memory address,
a call to MmProbeAndLockPages is made. This call verifies the integrity of the
supplied buffer, and also locks the pages down. Layers could be added to the
driver, without worrying about process context switching.
<P><PRE><B>
MmProbeAndLockPages( IN memoryDescriptorList,
IN accessMode,
IN operation);
</B></PRE>
<P>The driver must call MmUlockPages before returning control to the user
process.
<P><PRE><B>
MmProbeAndLockPages(IN memoryDescriptorList);
</B></PRE>
<P>
<HR>
<H2>Driver Unload</H2>Every driver that has registry configurable entries, or
that can be stopped once started, needs to have an unload routine. This routine
is responsible for deallocating any driver allocated resources, and deleting the
device object. The DT2851Unload routine un-maps the memory mapped interface from
system space by calling MmUnmapIoSpcae.
<P><PRE><B>
MmUnmapIoSpace(extension->baseAddress, MEMORY_SIZE);
</B></PRE>
<P>The unload routine then deletes the device object. With all resources
un-mapped, and the device object destroyed, the I/O manager can then delete the
driver object without leaving dangling resources. </P></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -