[aoe] Add explicit IRP dispatch, AddDevice routines
authorShao Miller <Shao.Miller@yrdsb.edu.on.ca>
Fri, 31 Dec 2010 04:45:17 +0000 (23:45 -0500)
committerShao Miller <Shao.Miller@yrdsb.edu.on.ca>
Fri, 31 Dec 2010 04:51:20 +0000 (23:51 -0500)
Lots of copying and pasting in this commit.  The idea is
to make the AoE driver slightly less dependent on the
WinVBlock driver.  We will request a PDO of the WinVBlock
bus, then when our driver's AddDevice callback is invoked
on that PDO, we establish the FDO and our AoE bus.

We will handle IRPs with our own IRP dispatch functions.
For bus IRPs, we use the WinVBlock bus library or our own
IRP_MJ_DEVICE_CONTROL handling.  For disk IRPs, we continue
to use the WinVBlock device and disk structures.

We aren't quite functional yet, as the PnpId logic needs to
be revisited.  If the AoE driver is unloaded after it has
created a PDO on the WinVBlock bus, a PnP ID query for that
PDO will attempt to call the PnpId routine in the unloaded
AoE driver (which is a bad thing).

src/aoe/bus.c
src/aoe/driver.c

index e758095..6ffa06f 100644 (file)
@@ -27,6 +27,7 @@
 #include <ntddk.h>
 
 #include "winvblock.h"
+#include "wv_stdlib.h"
 #include "portable.h"
 #include "driver.h"
 #include "bus.h"
 #define AOE_M_BUS_DOSNAME_ (L"\\DosDevices\\AoE")
 
 /* TODO: Remove this pull from aoe/driver.c */
-extern WV_F_DEV_DISPATCH aoe__scan;
-extern WV_F_DEV_DISPATCH AoeBusDevCtlShow;
-extern WV_F_DEV_DISPATCH aoe__mount;
+extern NTSTATUS STDCALL AoeBusDevCtlScan(IN PIRP);
+extern NTSTATUS STDCALL AoeBusDevCtlShow(IN PIRP);
+extern NTSTATUS STDCALL AoeBusDevCtlMount(IN PIRP);
 
 /* Forward declarations. */
-static WV_F_DEV_CTL AoeBusDevCtlDispatch_;
 static WV_F_DEV_PNP_ID AoeBusPnpId_;
 winvblock__bool AoeBusCreate(void);
 void AoeBusFree(void);
 
 /* Globals. */
 WV_S_BUS_T AoeBusMain = {0};
-static WV_S_DEV_T AoeBusMainDev_ = {0};
 static UNICODE_STRING AoeBusName_ = {
     sizeof AOE_M_BUS_NAME_,
     sizeof AOE_M_BUS_NAME_,
@@ -64,28 +63,70 @@ static UNICODE_STRING AoeBusDosname_ = {
     AOE_M_BUS_DOSNAME_
   };
 
-static NTSTATUS STDCALL AoeBusDevCtlDispatch_(
-    IN WV_SP_DEV_T dev,
+static NTSTATUS STDCALL AoeBusDevCtlDetach_(IN PIRP irp) {
+    PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
+    winvblock__uint32 unit_num;
+    WV_SP_BUS_NODE walker;
+
+    if (!(io_stack_loc->Control & SL_PENDING_RETURNED)) {
+        NTSTATUS status;
+
+        /* Enqueue the IRP. */
+        status = WvBusEnqueueIrp(&AoeBusMain, irp);
+        if (status != STATUS_PENDING)
+          /* Problem. */
+          return driver__complete_irp(irp, 0, status);
+        /* Ok. */
+        return status;
+      }
+    /* If we get here, we should be called by WvBusProcessWorkItems() */
+    unit_num = *((winvblock__uint32_ptr) irp->AssociatedIrp.SystemBuffer);
+    DBG("Request to detach unit: %d\n", unit_num);
+
+    walker = NULL;
+    /* For each node on the bus... */
+    while (walker = WvBusGetNextNode(&AoeBusMain, walker)) {
+        WV_SP_DEV_T dev = WvDevFromDevObj(WvBusGetNodePdo(walker));
+
+        /* If the unit number matches... */
+        if (WvBusGetNodeNum(walker) == unit_num) {
+            /* If it's not a boot-time device... */
+            if (dev->Boot) {
+                DBG("Cannot detach a boot-time device.\n");
+                /* Signal error. */
+                walker = NULL;
+                break;
+              }
+            /* Detach the node and free it. */
+            DBG("Removing unit %d\n", unit_num);
+            WvBusRemoveNode(walker);
+            WvDevClose(dev);
+            IoDeleteDevice(dev->Self);
+            WvDevFree(dev);
+            break;
+          }
+      }
+    if (!walker)
+      return driver__complete_irp(irp, 0, STATUS_INVALID_PARAMETER);
+    return driver__complete_irp(irp, 0, STATUS_SUCCESS);
+  }
+
+NTSTATUS STDCALL AoeBusDevCtl(
     IN PIRP irp,
     IN ULONG POINTER_ALIGNMENT code
   ) {
     switch(code) {
         case IOCTL_AOE_SCAN:
-          return aoe__scan(dev, irp);
+          return AoeBusDevCtlScan(irp);
 
         case IOCTL_AOE_SHOW:
-          return AoeBusDevCtlShow(dev, irp);
+          return AoeBusDevCtlShow(irp);
 
         case IOCTL_AOE_MOUNT:
-          return aoe__mount(dev, irp);
+          return AoeBusDevCtlMount(irp);
 
         case IOCTL_AOE_UMOUNT:
-          /* Pretend it's an IOCTL_FILE_DETACH. */
-          return AoeBusMainDev_.IrpMj->DevCtl(
-              &AoeBusMainDev_,
-              irp,
-              IOCTL_FILE_DETACH
-            );
+          return AoeBusDevCtlDetach_(irp);
 
         default:
           DBG("Unsupported IOCTL\n");
@@ -101,9 +142,6 @@ static NTSTATUS STDCALL AoeBusDevCtlDispatch_(
 winvblock__bool AoeBusCreate(void) {
     NTSTATUS status;
 
-    /* Initialize the AoE bus. */
-    WvBusInit(&AoeBusMain);
-    WvDevInit(&AoeBusMainDev_);
     /* Create the PDO for the sub-bus on the WinVBlock bus. */
     status = WvDriverAddDummy(
         AoeBusPnpId_,
@@ -112,34 +150,17 @@ winvblock__bool AoeBusCreate(void) {
       );
     if (!NT_SUCCESS(status)) {
         DBG("Couldn't add AoE bus to WinVBlock bus!\n");
-        goto err_add_child;
-      }
-    /* DosDevice symlink. */
-    status = IoCreateSymbolicLink(
-        &AoeBusDosname_,
-        &AoeBusName_
-      );
-    if (!NT_SUCCESS(status)) {
-        DBG("IoCreateSymbolicLink() failed!\n");
-        goto err_dos_symlink;
+        return FALSE;
       }
     /* All done. */
     return TRUE;
-
-    IoDeleteSymbolicLink(&AoeBusDosname_);
-    err_dos_symlink:
-
-    IoDeleteDevice(AoeBusMain.Fdo);
-    err_add_child:
-
-    return FALSE;
   }
 
 /* Destroy the AoE bus. */
 void AoeBusFree(void) {
     IoDeleteSymbolicLink(&AoeBusDosname_);
-    IoDeleteDevice(AoeBusMain.Fdo);
-    WvBusRemoveNode(&AoeBusMainDev_.BusNode);
+    if (AoeBusMain.Fdo)
+      IoDeleteDevice(AoeBusMain.Fdo);
     return;
   }
 
@@ -165,3 +186,148 @@ static winvblock__uint32 STDCALL AoeBusPnpId_(
           return 0;
       }
   }
+
+static NTSTATUS STDCALL AoeBusPnpQueryDevText_(
+    IN WV_SP_BUS_T bus,
+    IN PIRP irp
+  ) {
+    WCHAR (*str)[512];
+    PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
+    NTSTATUS status;
+    winvblock__uint32 str_len;
+
+    /* Allocate a string buffer. */
+    str = wv_mallocz(sizeof *str);
+    if (str == NULL) {
+        DBG("wv_malloc IRP_MN_QUERY_DEVICE_TEXT\n");
+        status = STATUS_INSUFFICIENT_RESOURCES;
+        goto alloc_str;
+      }
+    /* Determine the query type. */
+    switch (io_stack_loc->Parameters.QueryDeviceText.DeviceTextType) {
+        case DeviceTextDescription:
+          str_len = swprintf(*str, L"AoE Bus") + 1;
+          irp->IoStatus.Information =
+            (ULONG_PTR) wv_palloc(str_len * sizeof *str);
+          if (irp->IoStatus.Information == 0) {
+              DBG("wv_palloc DeviceTextDescription\n");
+              status = STATUS_INSUFFICIENT_RESOURCES;
+              goto alloc_info;
+            }
+          RtlCopyMemory(
+              (PWCHAR) irp->IoStatus.Information,
+              str,
+              str_len * sizeof (WCHAR)
+            );
+          status = STATUS_SUCCESS;
+          goto alloc_info;
+
+        case DeviceTextLocationInformation:
+          str_len = AoeBusPnpId_(
+              NULL,
+              BusQueryInstanceID,
+              str
+            );
+          irp->IoStatus.Information =
+            (ULONG_PTR) wv_palloc(str_len * sizeof *str);
+          if (irp->IoStatus.Information == 0) {
+              DBG("wv_palloc DeviceTextLocationInformation\n");
+              status = STATUS_INSUFFICIENT_RESOURCES;
+              goto alloc_info;
+            }
+          RtlCopyMemory(
+              (PWCHAR) irp->IoStatus.Information,
+              str,
+              str_len * sizeof (WCHAR)
+            );
+          status = STATUS_SUCCESS;
+          goto alloc_info;
+
+        default:
+          irp->IoStatus.Information = 0;
+          status = STATUS_NOT_SUPPORTED;
+      }
+    /* irp->IoStatus.Information not freed. */
+    alloc_info:
+
+    wv_free(str);
+    alloc_str:
+
+    return driver__complete_irp(irp, irp->IoStatus.Information, status);
+  }
+
+NTSTATUS STDCALL AoeBusAttachFdo(
+    IN PDRIVER_OBJECT driver_obj,
+    IN PDEVICE_OBJECT pdo
+  ) {
+    KIRQL irql;
+    NTSTATUS status;
+    PLIST_ENTRY walker;
+    PDEVICE_OBJECT fdo = NULL;
+
+    DBG("Entry\n");
+    /* Do we already have our main bus? */
+    if (AoeBusMain.Fdo) {
+        DBG("Already have the main bus.  Refusing.\n");
+        status = STATUS_NOT_SUPPORTED;
+        goto err_already_established;
+      }
+    /* Initialize the bus. */
+    WvBusInit(&AoeBusMain);
+    /* Create the bus FDO. */
+    status = IoCreateDevice(
+        driver_obj,
+        0,
+        &AoeBusName_,
+        FILE_DEVICE_CONTROLLER,
+        FILE_DEVICE_SECURE_OPEN,
+        FALSE,
+        &fdo
+      );
+    if (!NT_SUCCESS(status)) {
+        DBG("IoCreateDevice() failed!\n");
+        goto err_fdo;
+      }
+    /* DosDevice symlink. */
+    status = IoCreateSymbolicLink(
+        &AoeBusDosname_,
+        &AoeBusName_
+      );
+    if (!NT_SUCCESS(status)) {
+        DBG("IoCreateSymbolicLink() failed!\n");
+        goto err_dos_symlink;
+      }
+    /* Set associations for the bus, FDO, PDO. */
+    AoeBusMain.Fdo = fdo;
+    AoeBusMain.QueryDevText = AoeBusPnpQueryDevText_;
+    AoeBusMain.PhysicalDeviceObject = pdo;
+    fdo->Flags |= DO_DIRECT_IO;         /* FIXME? */
+    fdo->Flags |= DO_POWER_INRUSH;      /* FIXME? */
+    /* Attach the FDO to the PDO. */
+    AoeBusMain.LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo, pdo);
+    if (AoeBusMain.LowerDeviceObject == NULL) {
+        status = STATUS_NO_SUCH_DEVICE;
+        DBG("IoAttachDeviceToDeviceStack() failed!\n");
+        goto err_attach;
+      }
+    /* Ok! */
+    fdo->Flags &= ~DO_DEVICE_INITIALIZING;
+    #ifdef RIS
+    AoeBusMain.State = WvBusStateStarted;
+    #endif
+    DBG("Exit\n");
+    return STATUS_SUCCESS;
+
+    err_attach:
+
+    IoDeleteSymbolicLink(&AoeBusDosname_);
+    err_dos_symlink:
+
+    IoDeleteDevice(fdo);
+    err_fdo:
+
+    err_already_established:
+
+    DBG("Exit with failure\n");
+    return status;
+  }
index fd152c7..2e460f2 100644 (file)
@@ -27,6 +27,7 @@
 
 #include <stdio.h>
 #include <ntddk.h>
+#include <scsi.h>
 
 #include "winvblock.h"
 #include "wv_stdlib.h"
@@ -54,6 +55,11 @@ extern NTSTATUS STDCALL ZwWaitForSingleObject(
 extern WV_S_BUS_T AoeBusMain;
 extern winvblock__bool AoeBusCreate(void);
 extern void AoeBusFree(void);
+extern NTSTATUS STDCALL AoeBusDevCtl(IN PIRP, IN ULONG POINTER_ALIGNMENT);
+extern NTSTATUS STDCALL AoeBusAttachFdo(
+    IN PDRIVER_OBJECT,
+    IN PDEVICE_OBJECT
+  );
 /* From aoe/registry.c */
 extern winvblock__bool STDCALL AoeRegSetup(OUT PNTSTATUS);
 
@@ -68,6 +74,14 @@ static WV_F_DISK_IO AoeDiskIo_;
 static WV_F_DISK_MAX_XFER_LEN AoeDiskMaxXferLen_;
 static WV_F_DISK_INIT AoeDiskInit_;
 static WV_F_DISK_CLOSE AoeDiskClose_;
+static driver__dispatch_func AoeDriverIrpNotSupported_;
+static driver__dispatch_func AoeDriverIrpPower_;
+static driver__dispatch_func AoeDriverIrpCreateClose_;
+static driver__dispatch_func AoeDriverIrpSysCtl_;
+static driver__dispatch_func AoeDriverIrpDevCtl_;
+static driver__dispatch_func AoeDriverIrpScsi_;
+static driver__dispatch_func AoeDriverIrpPnp_;
+static void STDCALL AoeDriverUnload_(IN PDRIVER_OBJECT);
 
 /** Tag types. */
 typedef enum AOE_TAG_TYPE_ {
@@ -222,6 +236,7 @@ NTSTATUS STDCALL DriverEntry(
     OBJECT_ATTRIBUTES ObjectAttributes;
     void * ThreadObject;
     WV_SP_BUS_T bus_ptr;
+    int i;
 
     DBG("Entry\n");
 
@@ -310,7 +325,19 @@ NTSTATUS STDCALL DriverEntry(
         KeSetEvent(&AoeSignal_, 0, FALSE);
       }
 
+    for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
+      DriverObject->MajorFunction[i] = AoeDriverIrpNotSupported_;
+    DriverObject->MajorFunction[IRP_MJ_PNP] = AoeDriverIrpPnp_;
+    DriverObject->MajorFunction[IRP_MJ_POWER] = AoeDriverIrpPower_;
+    DriverObject->MajorFunction[IRP_MJ_CREATE] = AoeDriverIrpCreateClose_;
+    DriverObject->MajorFunction[IRP_MJ_CLOSE] = AoeDriverIrpCreateClose_;
+    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = AoeDriverIrpSysCtl_;
+    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = AoeDriverIrpDevCtl_;
+    DriverObject->MajorFunction[IRP_MJ_SCSI] = AoeDriverIrpScsi_;
+    /* Set the driver Unload callback. */
     DriverObject->DriverUnload = AoeUnload_;
+    /* Set the driver AddDevice callback. */
+    DriverObject->DriverExtension->AddDevice = AoeBusAttachFdo;
     AoeStarted_ = TRUE;
     if (!AoeBusCreate()) {
         DBG("Unable to create AoE bus!\n");
@@ -1597,10 +1624,7 @@ static void AoeProcessAbft_(void) {
     return;
   }
 
-NTSTATUS STDCALL aoe__scan(
-    IN WV_SP_DEV_T dev,
-    IN PIRP irp
-  ) {
+NTSTATUS STDCALL AoeBusDevCtlScan(IN PIRP irp) {
     KIRQL irql;
     winvblock__uint32 count;
     AOE_SP_TARGET_LIST_ target_walker;
@@ -1656,10 +1680,7 @@ NTSTATUS STDCALL aoe__scan(
     return driver__complete_irp(irp, irp->IoStatus.Information, STATUS_SUCCESS);
   }
 
-NTSTATUS STDCALL AoeBusDevCtlShow(
-    IN WV_SP_DEV_T dev,
-    IN PIRP irp
-  ) {
+NTSTATUS STDCALL AoeBusDevCtlShow(IN PIRP irp) {
     winvblock__uint32 count;
     WV_SP_BUS_NODE walker;
     aoe__mount_disks_ptr disks;
@@ -1732,10 +1753,7 @@ NTSTATUS STDCALL AoeBusDevCtlShow(
     return driver__complete_irp(irp, irp->IoStatus.Information, STATUS_SUCCESS);
   }
 
-NTSTATUS STDCALL aoe__mount(
-    IN WV_SP_DEV_T dev,
-    IN PIRP irp
-  ) {
+NTSTATUS STDCALL AoeBusDevCtlMount(IN PIRP irp) {
     winvblock__uint8_ptr buffer = irp->AssociatedIrp.SystemBuffer;
     AOE_SP_DISK_ aoe_disk;
 
@@ -1846,3 +1864,183 @@ static void STDCALL AoeDiskFree_(IN WV_SP_DEV_T dev) {
 
     wv_free(aoe_disk);
   }
+
+static NTSTATUS STDCALL AoeDriverIrpNotSupported_(
+    IN PDEVICE_OBJECT dev_obj,
+    IN PIRP irp
+  ) {
+    return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
+  }
+
+/* Handle a power IRP. */
+static NTSTATUS AoeDriverIrpPower_(
+    IN PDEVICE_OBJECT dev_obj,
+    IN PIRP irp
+  ) {
+    WV_SP_DEV_T dev;
+
+    #ifdef DEBUGIRPS
+    Debug_IrpStart(dev_obj, irp);
+    #endif
+    /* Check for a bus IRP. */
+    if (dev_obj == AoeBusMain.Fdo)
+      return WvBusPower(&AoeBusMain, irp);
+    /* WvDevFromDevObj() checks for a NULL dev_obj */
+    dev = WvDevFromDevObj(dev_obj);
+    /* Check that the device exists. */
+    if (!dev || dev->State == WvDevStateDeleted) {
+        /* Even if it doesn't, a power IRP is important! */
+        PoStartNextPowerIrp(irp);
+        return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
+      }
+    /* Call the particular device's power handler. */
+    if (dev->IrpMj && dev->IrpMj->Power)
+      return dev->IrpMj->Power(dev, irp);
+    /* Otherwise, we don't support the IRP. */
+    return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
+  }
+
+/* Handle an IRP_MJ_CREATE or IRP_MJ_CLOSE IRP. */
+static NTSTATUS AoeDriverIrpCreateClose_(
+    IN PDEVICE_OBJECT dev_obj,
+    IN PIRP irp
+  ) {
+    WV_SP_DEV_T dev;
+
+    #ifdef DEBUGIRPS
+    Debug_IrpStart(dev_obj, irp);
+    #endif
+    /* Check for a bus IRP. */
+    if (dev_obj == AoeBusMain.Fdo)
+      return driver__complete_irp(irp, 0, STATUS_SUCCESS);
+    /* WvDevFromDevObj() checks for a NULL dev_obj */
+    dev = WvDevFromDevObj(dev_obj);
+    /* Check that the device exists. */
+    if (!dev || dev->State == WvDevStateDeleted)
+      return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
+    /* Always succeed with nothing to do. */
+    return driver__complete_irp(irp, 0, STATUS_SUCCESS);
+  }
+
+/* Handle an IRP_MJ_SYSTEM_CONTROL IRP. */
+static NTSTATUS AoeDriverIrpSysCtl_(
+    IN PDEVICE_OBJECT dev_obj,
+    IN PIRP irp
+  ) {
+    WV_SP_DEV_T dev;
+
+    #ifdef DEBUGIRPS
+    Debug_IrpStart(dev_obj, irp);
+    #endif
+    /* Check for a bus IRP. */
+    if (dev_obj == AoeBusMain.Fdo)
+      return WvBusSysCtl(&AoeBusMain, irp);
+    /* WvDevFromDevObj() checks for a NULL dev_obj */
+    dev = WvDevFromDevObj(dev_obj);
+    /* Check that the device exists. */
+    if (!dev || dev->State == WvDevStateDeleted)
+      return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
+    /* Call the particular device's power handler. */
+    if (dev->IrpMj && dev->IrpMj->SysCtl)
+      return dev->IrpMj->SysCtl(dev, irp);
+    /* Otherwise, we don't support the IRP. */
+    return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
+  }
+
+/* Handle an IRP_MJ_DEVICE_CONTROL IRP. */
+static NTSTATUS AoeDriverIrpDevCtl_(
+    IN PDEVICE_OBJECT dev_obj,
+    IN PIRP irp
+  ) {
+    WV_SP_DEV_T dev;
+    PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
+
+    #ifdef DEBUGIRPS
+    Debug_IrpStart(dev_obj, irp);
+    #endif
+    /* Check for a bus IRP. */
+    if (dev_obj == AoeBusMain.Fdo) {
+        return AoeBusDevCtl(
+            irp,
+            io_stack_loc->Parameters.DeviceIoControl.IoControlCode
+          );
+      }
+    /* WvDevFromDevObj() checks for a NULL dev_obj */
+    dev = WvDevFromDevObj(dev_obj);
+    /* Check that the device exists. */
+    if (!dev || dev->State == WvDevStateDeleted)
+      return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
+    /* Call the particular device's power handler. */
+    if (dev->IrpMj && dev->IrpMj->DevCtl) {
+        return dev->IrpMj->DevCtl(
+            dev,
+            irp,
+            io_stack_loc->Parameters.DeviceIoControl.IoControlCode
+          );
+      }
+    /* Otherwise, we don't support the IRP. */
+    return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
+  }
+
+/* Handle an IRP_MJ_SCSI IRP. */
+static NTSTATUS AoeDriverIrpScsi_(
+    IN PDEVICE_OBJECT dev_obj,
+    IN PIRP irp
+  ) {
+    WV_SP_DEV_T dev;
+    PIO_STACK_LOCATION io_stack_loc;
+
+    #ifdef DEBUGIRPS
+    Debug_IrpStart(dev_obj, irp);
+    #endif
+    /* Check for a bus IRP. */
+    if (dev_obj == AoeBusMain.Fdo)
+      return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
+    /* WvDevFromDevObj() checks for a NULL dev_obj */
+    dev = WvDevFromDevObj(dev_obj);
+    io_stack_loc = IoGetCurrentIrpStackLocation(irp);
+    /* Check that the device exists. */
+    if (!dev || dev->State == WvDevStateDeleted)
+      return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
+    /* Call the particular device's power handler. */
+    if (dev->IrpMj && dev->IrpMj->Scsi) {
+        return dev->IrpMj->Scsi(
+            dev,
+            irp,
+            io_stack_loc->Parameters.Scsi.Srb->Function
+          );
+      }
+    /* Otherwise, we don't support the IRP. */
+    return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
+  }
+
+/* Handle an IRP_MJ_PNP IRP. */
+static NTSTATUS AoeDriverIrpPnp_(
+    IN PDEVICE_OBJECT dev_obj,
+    IN PIRP irp
+  ) {
+    WV_SP_DEV_T dev;
+    PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
+
+    #ifdef DEBUGIRPS
+    Debug_IrpStart(dev_obj, irp);
+    #endif
+    /* Check for a bus IRP. */
+    if (dev_obj == AoeBusMain.Fdo)
+      return WvBusPnp(&AoeBusMain, irp, io_stack_loc->MinorFunction);
+    /* WvDevFromDevObj() checks for a NULL dev_obj */
+    dev = WvDevFromDevObj(dev_obj);
+    /* Check that the device exists. */
+    if (!dev || dev->State == WvDevStateDeleted)
+      return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
+    /* Call the particular device's power handler. */
+    if (dev->IrpMj && dev->IrpMj->Pnp) {
+        return dev->IrpMj->Pnp(
+            dev,
+            irp,
+            io_stack_loc->MinorFunction
+          );
+      }
+    /* Otherwise, we don't support the IRP. */
+    return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
+  }