return Completelrp(Irp,status,BytesTxd);

}

The shared memory buffer is implemented using these three global variables.

KSPIN_LOCK BufferLock;

PUCHAR Buffer = NULL;

ULONG BufferSize = 0;

If the buffer size is greater than zero, Buffer points to some nonpaged memory of this size. As mentioned earlier in this chapter, there must be some mechanism to protect access to such global variables in a multiprocessor environment (e.g., to prevent one dispatch routine from changing BufferSize while another, or even the same, routine tries to access or change it simultaneously).

Spin Locks

A kernel spin lock called BufferLock provides this protection. A spin lock can be used where code needs access to a resource of some sort for a short time.

The spin lock is initialized in the Wdm1 DriverEntry routine as follows.

KeInitializeSpinLock(&BufferLock);

Use the KeAcquireSpinLock function to acquire a spin lock and KeReleaseSpinLock to release it. Only one instance of a piece of code can acquire a spin lock at the same time. Other attempts to acquire the spin lock will 'spin' until the resource becomes available. 'Spinning' means that KeAcquireSpinLock keeps looking continuously. For this reason, make sure that you only hold a spin lock for a short time. The DDK recommends that you never hold a spin lock for more than 25 microseconds.

As shown in the code example, you must provide a pointer to a KIRQL variable in the call to KeAcquireSpinLock. This stores the original IRQL level before it is raised (if necessary) to DISPATCH_LEVEL The call to KeReleaseSpinLock lowers the IRQL if necessary. If you are certain that your code is working at DISPATCH_LEVEL, you can use the KeAcquireSpinLockAtDpcLevel and KeReleaseSpinLockFromDpcLevel routines for better performance.

The Wdm1 driver acquires the BufferLock spin lock for the duration of any accesses to the Buffer and BufferSize variables. Do not access paged code or data while holding a spin lock, as the system will almost certainly crash. Definitely do not exit a main dispatch routine while holding a spin lock.

Write Algorithm

The write dispatch stores the write data in the shared memory buffer, starting from the given file pointer. It extends the buffer, if necessary.

If there is no buffer at all, or the buffer needs to be extended, ExAllocatePool is called to allocate some nonpaged memory. Notice that the algorithm checks for a NULL return value and copes as best as it can.

A new memory buffer is zeroed using RtlZeroMemory. If an old shorter buffer exists, it is copied to the start of the new buffer using RtlCopyMemory. RtlMoveMemory can be used if the source and destination pointers overlap. The old buffer is removed with ExFreePool.

Finally, Wdm1Write copies the data from the user buffer using RtlCopyMemory. As Wdm1 uses Buffered I/O, it can simply copy the data from Irp- >AssociatedIrp.SystemBuffer.

This algorithm is fairly crude, because the buffer may have to be reallocated often. A much-enhanced version of Wdm1 could implement a RAM disk.

The driver unload routine frees any shared memory buffer.

if (Buffer!=NULL) ExFreePool(Buffer);

Read

The read dispatch routine for Wdm1, Wdm1Read, is simpler than the write handler. It acquires the spin lock while it accesses the global variables. The required number of bytes are copied to the user's buffer at Irp->AssociatedIrp.SystemBuffer. If the user requests more data than is in the buffer, the request is truncated.

IOCTL

The Wdm1DeviceControl dispatch routine handles the four IOCTLs defined for Wdm1 devices: Zero the buffer, Remove the Buffer, Get the buffer size, and Get the buffer.

All these IOCTLs use Buffered I/O, so any input and output data is found at Irp->Associ-atedIrp.SystemBuffer. As usual, the routine acquires the shared buffer spin lock for the duration of the call. The actual implementation of each IOCTL is straightforward. The Get buffer size and Get buffer handlers check that the output buffer is large enough; if not, they return STATUS INVALID_PARAMETER.

Defining IOCTLs

An IOCTL code is a 32-bit value formed using the CTL_CODE macro shown in Table 7.4. The Wdm1 example defines its IOCTL codes in Ioctl.h, as shown in this example.

//define IOCTL_WDM1_ZERO_BUFFER CTL_CODE(

 FILE_DEVICE_UNKNOWN,

 0x801,

 METHOD_BUFFERED,

 FILE_ANY_ACCESS)

Table 7.4 CTL_CODE macro parameters

Parameter Description
DeviceType FILE_DEVICE_XXX value given to IoCreateDevice.
Control Code IOCTL Function Code 0x000–0x7FF Reserved for Microsoft 0x800-0xFFF Private codes
TransferType METHOD_BUFFERED METHOD_IN_DIRECT METHOD_OUT_DIRECT METHOD_NEITHER
RequiredAccess FILE_ANY_ACCESS FILE READ_DATA FILE_WRITE_DATA FILE READ_DATA|FILE_WRITE_DATA

Ioctl.h is also included in the Wdm1Test project. It includes the standard winioctl.h header file first to get the definition of CTL_CODE.

System Control

Dispatch.cpp also contains a handler for the Windows Management Instrumentation IRP, IRP_MJ_SYSTEM_CONTROL. Wdm1SystemControl simply passes the IRP down to the next driver in the stack. A full explanation of this process is given later.

IoSkipCurrentIrpStackLocation(Irp);

PWDM1_DEVICE_EXTENSION dx = (PWDMl_DEVICE_EXTENSION)fdo->DeviceExtension;

return IoCallDriver(dx->NextStackDevice, Irp);

Conclusion

This chapter has looked in detail at I/O Request Packets (IRPs) and how to write dispatch routines to handle common IRPs. In addition, it has covered how to define IOCTL codes, and how to use spin locks. The Wdm1 dispatch routines implement a shared memory buffer, protected by a spin lock.

The next chapter looks at Plug and Play in detail, enhancing Wdm1 to support PnP correctly.

Chapter 8

Plug and Play and Device Stacks

This chapter looks at the design of the Plug and Play (PnP) system and how PnP drivers fit into a device stack. A device stack is the means by which several layers of drivers can work together

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату
×