The WDM model of drivers is built upon structured layers of Physical Device Objects (PDOs) and Functional Device Objects (FDOs). Generally, a PDO exists for each physical piece of hardware attached to a bus and assumes the responsibility for low-level device control common to multiple functions realized by the hardware. An FDO exists for each logical or abstract function presented to higher-level software. As an example, consider a physical disk drive and driver. It can be represented by a PDO that implements bus adapter functions (e.g., adapting an IDE disk bus to a PCI bus). Once the PDO for the disk bus adapter is realized, an FDO assumes responsibility for the functional operations of the disk itself. The FDO may choose to handle a particular disk I/O request directly (e.g., reading a disk sector). It may choose to pass down other requests to its physical device partner (e.g., a power down request). In reality, the role of a PDO can quickly become complicated and recursive. For example, a USB host adapter starts life as a physical device of the bus it adapts (e.g., PCI). The host adapter then takes on the role of a new bus driver, enumerating each USB device it discovers as its collection of PDOs. Each PDO then controls its FDO. The technique is further complicated by allowing FDOs to be surrounded by filter device objects. These upper- and lower-level filter objects may exist in any number to modify or enhance the way that I/O Requests are handled by the resulting stack of device objects. In order to distinguish between those FDOs which implement hardware buses and those that do not, the terms bus FDO and nonbus FDO are used within the DDK. A bus FDO implements the bus driver's responsibility of enumerating the devices attached to a bus. The bus FDO then creates a PDO for each attached device. It is also worth noting that there is only a conceptual difference between a nonbus FDO and a filter device object. From the PnP Manager's perspective, all Device objects position themselves within the device stack, and the fact that some devices choose to consider themselves more than a filter is inconsequential. The arrangement of a device stack is shown in Figure 9.2. The distinction between a bus FDO and a nonbus FDO is depicted in Figure 9.3. Figure 9.2. The device stack.Figure 9.3. Bus FDOs and nonbus FDOs.Understanding the device stack is important in order to describe when the AddDevice function is called for a specific device. The overall algorithm used by Windows 2000 to load drivers and invoke the driver's AddDevice function is described by the following:
The function IoAttachDeviceToDeviceStack is made from AddDevice to place the FDO at the (current) top of the device stack. Its prototype is shown in Table 9.2. For convenience, it is advisable to maintain a relationship of the device stack elements within the Device extension structures of each PDO, FDO, and filter drivers. This is best done by reserving space for a pLowerDevice and pUpperDevice pointer within each Device extension. Unfortunately, while the pLowerDevice pointer initialization is straightforward, the upward pointer can only be initialized safely if the lower device type (filter or function) is known. This is because the return value from IoAttachDeviceToDeviceStack is simply a DEVICE_OBJECT. The extension, extracted from the returned Device object, needs to be explicitly cast so that the pUpperDevice offset is accurate. In a generalized device stack, no driver could be certain, a priori, of its lower device type. Fortunately, the upward pointer is generally unnecessary, so only the lower pointer is routinely maintained.
The final task of the AddDevice function is to create a symbolic link name, if any, for the newly created and enabled device. This technique is exactly as described in chapter 6. A completed AddDevice function might be //++ // 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; // Form the internal Device Name CUString devName("\\Device\\MINPNP"); // for "minimal" dev UlDeviceNumber++; devName += CUString(ulDeviceNumber); // Now create the device status = IoCreateDevice( pDriverObject, sizeof(DEVICE_EXTENSION), &(UNICODE_STRING)devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pfdo ); if (!NT_SUCCESS(status)) return status; // Initialize the Device Extension pDevExt = (PDEVICE_EXTENSION)pfdo->DeviceExtension; pDevExt->pDevice = pfdo; // back pointer pDevExt->DeviceNumber = ulDeviceNumber; pDevExt->ustrDeviceName = devName; // Pile this new fdo on top of the existing lower stack pDevExt->pLowerDevice = // 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("\\??\\MPNP"); symLinkName += CUString(ulDeviceNumber+1); // 1 based pDevExt->ustrSymLinkName = 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; } // Made it return STATUS_SUCCESS; } The device stack (multilayered) approach to Plug and Play drivers is more flexible during the hardware discovery process and better models the actual implementation of hardware (i.e., devices "layer" on a bus).
|