PDO and FDO in Windows Drivers

In the Windows driver model, understanding the roles of the Physical Device Object (PDO) and Functional Device Object (FDO) is fundamental for developing robust and efficient kernel-mode drivers.

What are PDO and FDO?

Physical Device Object (PDO)

A Physical Device Object (PDO) represents the physical hardware device itself. It is typically created by the bus driver (e.g., PCI, USB, I2C) that enumerates the hardware. The PDO acts as a proxy to the underlying hardware and is the initial point of contact for the system when it discovers a new device.

Key characteristics of a PDO:

  • Represents a physical piece of hardware.
  • Created by the bus driver during device enumeration.
  • Used by the Plug and Play (PnP) manager to identify and manage hardware.
  • Often has a device description associated with it.

Functional Device Object (FDO)

A Functional Device Object (FDO) represents a logical function or a specific device interface provided by a driver. For a given hardware device (represented by a PDO), there can be one or more FDOs stacked on top of it, each providing a different set of functionalities or interfaces. The topmost FDO in a stack is usually the one that directly interacts with the user-mode applications or services.

Key characteristics of an FDO:

  • Represents a logical device or a driver's functionality.
  • Created by a function driver for a PDO.
  • Handles I/O Request Packets (IRPs) for its specific functions.
  • Can be part of a device object stack.

The Device Object Stack

Device objects in Windows are often organized in a stack. The stack typically starts with the PDO at the bottom, representing the hardware. Above the PDO, one or more FDOs can be stacked. Each FDO corresponds to a driver that provides services for the device.

For example, a USB device might have the following stack:

  1. USB Bus Driver: Creates the PDO for the USB device.
  2. USBport Driver: Manages the USB port and creates an FDO for the USB device function.
  3. Device Class Driver (e.g., HID, Mass Storage): Creates another FDO on top of the USBport driver's FDO to provide specific class functionality.
  4. Filter Drivers: Optional drivers that can sit anywhere in the stack to filter or modify IRPs.
Note: The Plug and Play (PnP) manager traverses the device object stack to send IRPs to the appropriate driver.

PDO vs. FDO - When to Use Which

The choice between creating a PDO or an FDO depends on the role of the driver:

  • Bus Drivers: These drivers are responsible for enumerating devices on a bus. They create PDOs for the discovered hardware.
  • Function Drivers: These drivers provide the primary functionality for a device. They typically create an FDO that attaches to a PDO.
  • Filter Drivers: These drivers can be either lower-level (attaching to a PDO or another FDO) or higher-level (attaching to an FDO) to intercept and process IRPs.

Example: Creating a PDO (Conceptual)

A bus driver might use a function like IoCreateDevice to create a PDO. The name of the PDO is often related to the bus and the device's enumeration path.


// Conceptual example within a bus driver's AddDevice routine
NTSTATUS
MyBusDriver_AddDevice(
    _In_ PDRIVER_OBJECT DriverObject,
    _In_ PDEVICE_OBJECT PhysicalDeviceObject // PDO passed by PnP Manager
)
{
    PDEVICE_OBJECT pdo;
    // In a real bus driver, you'd get the PnP Device ID and create a PDO name.
    // This PDO is typically provided by the PnP manager to the bus driver.
    pdo = PhysicalDeviceObject;

    // Configure PDO flags, etc.
    // ...

    // Report success to PnP manager
    return STATUS_SUCCESS;
}
                

Example: Creating an FDO (Conceptual)

A function driver would typically create an FDO after receiving an IRP_MN_START_DEVICE request from the PnP manager, referencing the PDO it's associated with.


// Conceptual example within a function driver's AddDevice routine
NTSTATUS
MyFunctionDriver_AddDevice(
    _In_ PDRIVER_OBJECT DriverObject,
    _In_ PDEVICE_OBJECT Pdo // The PDO for the hardware
)
{
    PDEVICE_OBJECT fdo;
    NTSTATUS status;
    UNICODE_STRING deviceName;

    // Create a symbolic link name for user-mode access (optional)
    RtlInitUnicodeString(&deviceName, L"\\DosDevices\\MyDevice");

    // Create the Functional Device Object
    status = IoCreateDevice(
        DriverObject,
        sizeof(MY_DEVICE_EXTENSION), // Your custom device extension size
        &deviceName,                 // Device name
        FILE_DEVICE_UNKNOWN,         // Device characteristics
        0,                           // Device flags
        FALSE,                       // Not exclusive
        &fdo                         // Pointer to the new FDO
    );

    if (!NT_SUCCESS(status)) {
        return status;
    }

    // Attach the FDO to the PDO. This is crucial for the device stack.
    fdo->StackSize = Pdo->StackSize + 1;
    status = IoAttachDeviceToDeviceStack(fdo, Pdo);
    if (!NT_SUCCESS(status)) {
        IoDeleteDevice(fdo);
        return status;
    }

    // Initialize device extension
    MY_DEVICE_EXTENSION* devExt = (MY_DEVICE_EXTENSION*)fdo->DeviceExtension;
    devExt->Pdo = Pdo; // Store reference to PDO
    devExt->TargetDeviceObject = IoGetAttachedDeviceReference(fdo); // Reference for lower drivers

    // Set device characteristics and flags
    fdo->Flags |= DO_BUFFERED_IO; // Example: Enable buffered I/O
    fdo->Characteristics |= FILE_REMOVABLE_MEDIA; // Example: Device characteristics

    // Complete device initialization...

    return STATUS_SUCCESS;
}
                
Important: The `IoAttachDeviceToDeviceStack` function is key to forming the device object stack. The function driver's FDO is attached to the PDO (or the top of an existing stack).

Conclusion

PDOs and FDOs are fundamental building blocks for Windows kernel-mode drivers. The PDO represents the hardware, while the FDO represents a driver's functional interface. Their proper handling and arrangement in a device stack are essential for the Plug and Play manager and the I/O manager to route IRPs correctly.

Further Reading: