[bus,driver] Put bus IRP_MJ_POWER handler in driver
[people/sha0/winvblock.git] / src / winvblock / bus / bus.c
1 /**
2  * Copyright (C) 2009-2010, Shao Miller <shao.miller@yrdsb.edu.on.ca>.
3  * Copyright 2006-2008, V.
4  * For WinAoE contact information, see http://winaoe.org/
5  *
6  * This file is part of WinVBlock, derived from WinAoE.
7  *
8  * WinVBlock is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * WinVBlock is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with WinVBlock.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22 /**
23  * @file
24  *
25  * Bus specifics.
26  */
27
28 #include <ntddk.h>
29
30 #include "winvblock.h"
31 #include "wv_stdlib.h"
32 #include "portable.h"
33 #include "driver.h"
34 #include "device.h"
35 #include "bus.h"
36 #include "debug.h"
37
38 /* IRP_MJ_DEVICE_CONTROL dispatcher from bus/dev_ctl.c */
39 extern WV_F_DEV_CTL WvBusDevCtlDispatch;
40 /* IRP_MJ_PNP dispatcher from bus/pnp.c */
41 extern WV_F_DEV_PNP WvBusPnpDispatch;
42
43 /* Types. */
44 typedef enum WV_BUS_WORK_ITEM_CMD_ {
45     WvBusWorkItemCmdAddPdo_,
46     WvBusWorkItemCmdRemovePdo_,
47     WvBusWorkItemCmdProcessIrp_,
48     WvBusWorkItemCmds_
49   } WV_E_BUS_WORK_ITEM_CMD_, * WV_EP_BUS_WORK_ITEM_CMD_;
50
51 typedef struct WV_BUS_WORK_ITEM_ {
52     LIST_ENTRY Link;
53     WV_E_BUS_WORK_ITEM_CMD_ Cmd;
54     union {
55         WV_SP_BUS_NODE Node;
56         PIRP Irp;
57       } Context;
58   } WV_S_BUS_WORK_ITEM_, * WV_SP_BUS_WORK_ITEM_;
59
60 /* Forward declarations. */
61 static WV_F_DEV_FREE WvBusFree_;
62 static WV_F_DEV_CREATE_PDO WvBusCreatePdo_;
63 static WV_F_BUS_THREAD WvBusDefaultThread_;
64 static winvblock__bool WvBusAddWorkItem_(
65     WV_SP_BUS_T,
66     WV_SP_BUS_WORK_ITEM_
67   );
68 static WV_SP_BUS_WORK_ITEM_ WvBusGetWorkItem_(WV_SP_BUS_T);
69
70 /* Globals. */
71 WV_S_DEV_IRP_MJ WvBusIrpMj_ = {
72     (WV_FP_DEV_DISPATCH) 0,
73     (WV_FP_DEV_DISPATCH) 0,
74     WvBusDevCtlDispatch,
75     (WV_FP_DEV_SCSI) 0,
76     WvBusPnpDispatch,
77   };
78
79 /**
80  * Add a child node to the bus.
81  *
82  * @v Bus               Points to the bus receiving the child.
83  * @v Dev               Points to the child device to add.
84  * @ret                 TRUE for success, FALSE for failure.
85  */
86 winvblock__lib_func winvblock__bool STDCALL WvBusAddChild(
87     IN OUT WV_SP_BUS_T Bus,
88     IN OUT WV_SP_DEV_T Dev
89   ) {
90     /* The new node's device object. */
91     PDEVICE_OBJECT dev_obj;
92     /* Walks the child nodes. */
93     WV_SP_DEV_T walker;
94     winvblock__uint32 dev_num;
95
96     DBG("Entry\n");
97     if ((Bus == NULL) || (Dev == NULL)) {
98         DBG("No bus or no device!\n");
99         return FALSE;
100       }
101     /* Create the child device. */
102     dev_obj = WvDevCreatePdo(Dev);
103     if (dev_obj == NULL) {
104         DBG("PDO creation failed!\n");
105         WvDevFree(Dev);
106         return FALSE;
107       }
108
109     Dev->Parent = Bus->Dev.Self;
110     /*
111      * Initialize the device.  For disks, this routine is responsible for
112      * determining the disk's geometry appropriately for AoE/RAM/file disks.
113      */
114     Dev->Ops.Init(Dev);
115     dev_obj->Flags &= ~DO_DEVICE_INITIALIZING;
116     /* Add the new device's extension to the bus' list of children. */
117     dev_num = 0;
118     if (Bus->first_child == NULL) {
119         Bus->first_child = Dev;
120       } else {
121         walker = Bus->first_child;
122         /* If the first child device number isn't 0... */
123         if (walker->DevNum) {
124             /* We insert before. */
125             Dev->next_sibling_ptr = walker;
126             Bus->first_child = Dev;
127           } else {
128             while (walker->next_sibling_ptr != NULL) {
129                 /* If there's a gap in the device numbers for the bus... */
130                 if (walker->DevNum < walker->next_sibling_ptr->DevNum - 1) {
131                     /* Insert here, instead of at the end. */
132                     dev_num = walker->DevNum + 1;
133                     Dev->next_sibling_ptr = walker->next_sibling_ptr;
134                     walker->next_sibling_ptr = Dev;
135                     break;
136                   }
137                 walker = walker->next_sibling_ptr;
138                 dev_num = walker->DevNum + 1;
139               }
140             /* If we haven't already inserted the device... */
141             if (!Dev->next_sibling_ptr) {
142                 walker->next_sibling_ptr = Dev;
143                 dev_num = walker->DevNum + 1;
144               }
145           }
146       }
147     Dev->DevNum = dev_num;
148     Bus->Children++;
149     if (Bus->PhysicalDeviceObject != NULL) {
150         IoInvalidateDeviceRelations(
151             Bus->PhysicalDeviceObject,
152             BusRelations
153           );
154       }
155     DBG("Exit\n");
156     return TRUE;
157   }
158
159 /* Handle an IRP_MJ_SYSTEM_CONTROL IRP. */
160 winvblock__lib_func NTSTATUS STDCALL WvBusSysCtl(
161     IN WV_SP_BUS_T Bus,
162     IN PIRP Irp
163   ) {
164     PDEVICE_OBJECT lower = Bus->LowerDeviceObject;
165
166     if (lower) {
167         DBG("Passing IRP_MJ_SYSTEM_CONTROL down\n");
168         IoSkipCurrentIrpStackLocation(Irp);
169         return IoCallDriver(lower, Irp);
170       }
171     return driver__complete_irp(Irp, 0, STATUS_SUCCESS);
172   }
173
174 /* Handle a power IRP. */
175 winvblock__lib_func NTSTATUS STDCALL WvBusPower(
176     IN WV_SP_BUS_T Bus,
177     IN PIRP Irp
178   ) {
179     PDEVICE_OBJECT lower = Bus->LowerDeviceObject;
180
181     PoStartNextPowerIrp(Irp);
182     if (lower) {
183         IoSkipCurrentIrpStackLocation(Irp);
184         return PoCallDriver(lower, Irp);
185       }
186     return driver__complete_irp(Irp, 0, STATUS_SUCCESS);
187   }
188
189 NTSTATUS STDCALL WvBusGetDevCapabilities(
190     IN PDEVICE_OBJECT DevObj,
191     IN PDEVICE_CAPABILITIES DevCapabilities
192   ) {
193     IO_STATUS_BLOCK io_status;
194     KEVENT pnp_event;
195     NTSTATUS status;
196     PDEVICE_OBJECT target_obj;
197     PIO_STACK_LOCATION io_stack_loc;
198     PIRP pnp_irp;
199
200     RtlZeroMemory(DevCapabilities, sizeof *DevCapabilities);
201     DevCapabilities->Size = sizeof *DevCapabilities;
202     DevCapabilities->Version = 1;
203     DevCapabilities->Address = -1;
204     DevCapabilities->UINumber = -1;
205
206     KeInitializeEvent(&pnp_event, NotificationEvent, FALSE);
207     target_obj = IoGetAttachedDeviceReference(DevObj);
208     pnp_irp = IoBuildSynchronousFsdRequest(
209         IRP_MJ_PNP,
210         target_obj,
211         NULL,
212         0,
213         NULL,
214         &pnp_event,
215         &io_status
216       );
217     if (pnp_irp == NULL) {
218         status = STATUS_INSUFFICIENT_RESOURCES;
219       } else {
220         pnp_irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
221         io_stack_loc = IoGetNextIrpStackLocation(pnp_irp);
222         RtlZeroMemory(io_stack_loc, sizeof *io_stack_loc);
223         io_stack_loc->MajorFunction = IRP_MJ_PNP;
224         io_stack_loc->MinorFunction = IRP_MN_QUERY_CAPABILITIES;
225         io_stack_loc->Parameters.DeviceCapabilities.Capabilities =
226           DevCapabilities;
227         status = IoCallDriver(target_obj, pnp_irp);
228         if (status == STATUS_PENDING) {
229             KeWaitForSingleObject(
230                 &pnp_event,
231                 Executive,
232                 KernelMode,
233                 FALSE,
234                 NULL
235               );
236             status = io_status.Status;
237           }
238       }
239     ObDereferenceObject(target_obj);
240     return status;
241   }
242
243 /* Initialize a bus. */
244 static winvblock__bool STDCALL WvBusDevInit_(IN WV_SP_DEV_T dev) {
245     return TRUE;
246   }
247
248 /**
249  * Initialize bus defaults.
250  *
251  * @v Bus               Points to the bus to initialize with defaults.
252  */
253 winvblock__lib_func void WvBusInit(WV_SP_BUS_T Bus) {
254     RtlZeroMemory(Bus, sizeof *Bus);
255     /* Populate non-zero bus device defaults. */
256     WvDevInit(&Bus->Dev);
257     Bus->Thread = WvBusDefaultThread_;
258     KeInitializeSpinLock(&Bus->BusPrivate_.WorkItemsLock);
259     InitializeListHead(&Bus->BusPrivate_.WorkItems);
260     KeInitializeEvent(&Bus->ThreadSignal, SynchronizationEvent, FALSE);
261     KeInitializeEvent(&Bus->ThreadStopped, SynchronizationEvent, FALSE);
262     Bus->Dev.Ops.CreatePdo = WvBusCreatePdo_;
263     Bus->Dev.Ops.Init = WvBusDevInit_;
264     Bus->Dev.Ops.Free = WvBusFree_;
265     Bus->Dev.ext = Bus;
266     Bus->Dev.IrpMj = &WvBusIrpMj_;
267     Bus->Dev.IsBus = TRUE;
268   }
269
270 /**
271  * Create a new bus.
272  *
273  * @ret WV_SP_BUS_T     The address of a new bus, or NULL for failure.
274  *
275  * This function should not be confused with a PDO creation routine, which is
276  * actually implemented for each device type.  This routine will allocate a
277  * WV_S_BUS_T as well as populate the bus with default values.
278  */
279 winvblock__lib_func WV_SP_BUS_T WvBusCreate(void) {
280     WV_SP_BUS_T bus;
281
282     /*
283      * Bus devices might be used for booting and should
284      * not be allocated from a paged memory pool.
285      */
286     bus = wv_malloc(sizeof *bus);
287     if (bus == NULL)
288       goto err_no_bus;
289
290     WvBusInit(bus);
291     return bus;
292
293     wv_free(bus);
294     err_no_bus:
295
296     return NULL;
297   }
298
299 /**
300  * Create a bus PDO.
301  *
302  * @v dev               Populate PDO dev. ext. space from these details.
303  * @ret PDEVICE_OBJECT  Points to the new PDO, or is NULL upon failure.
304  *
305  * Returns a Physical Device Object pointer on success, NULL for failure.
306  */
307 static PDEVICE_OBJECT STDCALL WvBusCreatePdo_(IN WV_SP_DEV_T dev) {
308     PDEVICE_OBJECT pdo = NULL;
309     WV_SP_BUS_T bus;
310     NTSTATUS status;
311
312     /* Note the bus device needing a PDO. */
313     if (dev == NULL) {
314         DBG("No device passed\n");
315         return NULL;
316       }
317     bus = WvBusFromDev(dev);
318     /* Create the PDO. */
319     status = IoCreateDevice(
320         dev->DriverObject,
321         sizeof (driver__dev_ext),
322         NULL,
323         FILE_DEVICE_CONTROLLER,
324         FILE_DEVICE_SECURE_OPEN,
325         FALSE,
326         &pdo
327       );
328     if (pdo == NULL) {
329         DBG("IoCreateDevice() failed!\n");
330         goto err_pdo;
331       }
332
333     /* Set associations for the bus, device, PDO. */
334     WvDevForDevObj(pdo, dev);
335     dev->Self = bus->PhysicalDeviceObject = pdo;
336
337     /* Set some DEVICE_OBJECT status. */
338     pdo->Flags |= DO_DIRECT_IO;         /* FIXME? */
339     pdo->Flags |= DO_POWER_INRUSH;      /* FIXME? */
340     pdo->Flags &= ~DO_DEVICE_INITIALIZING;
341     #ifdef RIS
342     dev->State = Started;
343     #endif
344
345     return pdo;
346
347     IoDeleteDevice(pdo);
348     err_pdo:
349
350     return NULL;
351   }
352
353 /**
354  * Default bus deletion operation.
355  *
356  * @v dev               Points to the bus device to delete.
357  */
358 static void STDCALL WvBusFree_(IN WV_SP_DEV_T dev) {
359     WV_SP_BUS_T bus = WvBusFromDev(dev);
360
361     wv_free(bus);
362   }
363
364 /**
365  * Get a bus from a device.
366  *
367  * @v dev       A pointer to a device.
368  * @ret         A pointer to the device's associated bus.
369  */
370 extern winvblock__lib_func WV_SP_BUS_T WvBusFromDev(WV_SP_DEV_T Dev) {
371     return Dev->ext;
372   }
373
374 /**
375  * Add a work item for a bus to process.
376  *
377  * @v bus                       The bus to process the work item.
378  * @v work_item                 The work item to add.
379  * @ret winvblock__bool         TRUE if added, else FALSE
380  *
381  * Note that this function will initialize the work item's completion signal.
382  */
383 static winvblock__bool WvBusAddWorkItem_(
384     WV_SP_BUS_T bus,
385     WV_SP_BUS_WORK_ITEM_ work_item
386   ) {
387     ExInterlockedInsertTailList(
388         &bus->BusPrivate_.WorkItems,
389         &work_item->Link,
390         &bus->BusPrivate_.WorkItemsLock
391       );
392
393     return TRUE;
394   }
395
396 /**
397  * Get (and dequeue) a work item from a bus' queue.
398  *
399  * @v bus                       The bus processing the work item.
400  * @ret WV_SP_BUS_WORK_ITEM_    The work item, or NULL for an empty queue.
401  */
402 static WV_SP_BUS_WORK_ITEM_ WvBusGetWorkItem_(
403     WV_SP_BUS_T bus
404   ) {
405     PLIST_ENTRY list_entry;
406
407     list_entry = ExInterlockedRemoveHeadList(
408         &bus->BusPrivate_.WorkItems,
409         &bus->BusPrivate_.WorkItemsLock
410       );
411     if (!list_entry)
412       return NULL;
413
414     return CONTAINING_RECORD(list_entry, WV_S_BUS_WORK_ITEM_, Link);
415   }
416
417 /**
418  * Add a PDO node to a bus' list of children.  Internal.
419  *
420  * @v bus               The bus to add the node to.
421  * @v new_node          The PDO node to add to the bus.
422  *
423  * Don't call this function yourself.  It expects to have exclusive
424  * access to the bus' list of children.
425  */
426 static void STDCALL WvBusAddNode_(WV_SP_BUS_T bus, WV_SP_BUS_NODE new_node) {
427     PLIST_ENTRY walker;
428
429     DBG("Adding PDO to bus...\n");
430     ObReferenceObject(new_node->BusPrivate_.Pdo);
431     bus->BusPrivate_.NodeCount++;
432     /* It's too bad about having both linked list and bus ref. */
433     new_node->BusPrivate_.Bus = bus;
434
435     /* Find a slot for the new child. */
436     walker = &bus->BusPrivate_.Nodes;
437     new_node->BusPrivate_.Num = 0;
438     while ((walker = walker->Flink) != &bus->BusPrivate_.Nodes) {
439         WV_SP_BUS_NODE node = CONTAINING_RECORD(
440             walker,
441             WV_S_BUS_NODE,
442             BusPrivate_.Link
443           );
444
445         if (
446             node->BusPrivate_.Num &&
447             (node->BusPrivate_.Link.Blink == &bus->BusPrivate_.Nodes)
448           ) {
449             /* The first node's unit number is != 0.  Insert here. */
450             break;
451           }
452         if (node->BusPrivate_.Num > new_node->BusPrivate_.Num) {
453             /* There is a gap so insert here. */
454             break;
455           }
456         /* Continue trying to find a slot. */
457         new_node->BusPrivate_.Num++;
458       } /* while */
459     /* Insert before walker. */
460     InsertTailList(walker, &new_node->BusPrivate_.Link);
461     return;
462   }
463
464 /**
465  * Process work items for a bus.
466  *
467  * @v Bus               The bus to process its work items.
468  */
469 winvblock__lib_func void WvBusProcessWorkItems(WV_SP_BUS_T Bus) {
470     WV_SP_BUS_WORK_ITEM_ work_item;
471     WV_SP_BUS_NODE node;
472     PIRP irp;
473     PIO_STACK_LOCATION io_stack_loc;
474     PDEVICE_OBJECT dev_obj;
475     PDRIVER_OBJECT driver_obj;
476
477     while (work_item = WvBusGetWorkItem_(Bus)) {
478         switch (work_item->Cmd) {
479             case WvBusWorkItemCmdAddPdo_:
480               node = work_item->Context.Node;
481               WvBusAddNode_(Bus, node);
482               break;
483
484             case WvBusWorkItemCmdRemovePdo_:
485               DBG("Removing PDO from bus...\n");
486
487               node = work_item->Context.Node;
488               RemoveEntryList(&node->BusPrivate_.Link);
489               ObDereferenceObject(node->BusPrivate_.Pdo);
490               Bus->BusPrivate_.NodeCount--;
491               break;
492
493             case WvBusWorkItemCmdProcessIrp_:
494               irp = work_item->Context.Irp;
495               io_stack_loc = IoGetCurrentIrpStackLocation(irp);
496               dev_obj = Bus->Dev.Self;
497               driver_obj = dev_obj->DriverObject;
498               driver_obj->MajorFunction[io_stack_loc->MajorFunction](
499                   dev_obj,
500                   irp
501                 );
502               break;
503
504             default:
505               DBG("Unknown work item type!\n");
506           }
507         wv_free(work_item);
508       }
509     return;
510   }
511
512 /**
513  * Cancel pending work items for a bus.
514  *
515  * @v Bus       The bus to cancel pending work items for.
516  */
517 winvblock__lib_func void WvBusCancelWorkItems(WV_SP_BUS_T Bus) {
518     WV_SP_BUS_WORK_ITEM_ work_item;
519
520     DBG("Canceling work items.\n");
521     while (work_item = WvBusGetWorkItem_(Bus))
522       wv_free(work_item);
523     return;
524   }
525
526 /* The WV_S_DEV_T::Ops.Free implementation for a threaded bus. */
527 static void STDCALL WvBusThreadFree_(IN WV_SP_DEV_T dev) {
528     WV_SP_BUS_T bus = WvBusFromDev(dev);
529
530     bus->Stop = TRUE;
531     KeSetEvent(&bus->ThreadSignal, 0, FALSE);
532     return;
533   }
534
535 /**
536  * The bus thread wrapper.
537  *
538  * @v context           The thread context.  In our case, it points to
539  *                      the bus that the thread should use in processing.
540  *
541  * Note that we do not attempt to free the bus data; this is a bus
542  * implementor's responsibility.  We do, however, set the ThreadStopped
543  * signal which should mean that resources can be freed, from a completed
544  * thread's perspective.
545  */
546 static void STDCALL WvBusThread_(IN void * context) {
547     WV_SP_BUS_T bus = context;
548
549     if (!bus || !bus->Thread) {
550         DBG("No bus or no thread!\n");
551         return;
552       }
553
554     bus->Thread(bus);
555     KeSetEvent(&bus->ThreadStopped, 0, FALSE);
556     return;
557   }
558
559 /**
560  * The default bus thread routine.
561  *
562  * @v bus       Points to the bus device for the thread to work with.
563  *
564  * Note that if you implement your own bus type using this library,
565  * you can override the thread routine with your own.  If you do so,
566  * your thread routine should call WvBusProcessWorkItems() within
567  * its loop.  To start a bus thread, use WvBusStartThread()
568  * If you implement your own thread routine, you are also responsible
569  * for calling WvBusCancelWorkItems() and freeing the bus.
570  */
571 static void STDCALL WvBusDefaultThread_(IN WV_SP_BUS_T bus) {
572     LARGE_INTEGER timeout;
573
574     /* Wake up at least every 30 seconds. */
575     timeout.QuadPart = -300000000LL;
576
577     /* Hook WV_S_DEV_T::Ops.Free() */
578     bus->Dev.Ops.Free = WvBusThreadFree_;
579
580     /* When WV_S_BUS_T::Stop is set, we shut down. */
581     while (!bus->Stop) {
582         DBG("Alive.\n");
583
584         /* Wait for the work signal or the timeout. */
585         KeWaitForSingleObject(
586             &bus->ThreadSignal,
587             Executive,
588             KernelMode,
589             FALSE,
590             &timeout
591           );
592         /* Reset the work signal. */
593         KeResetEvent(&bus->ThreadSignal);
594
595         WvBusProcessWorkItems(bus);
596       } /* while !bus->Stop */
597
598     WvBusCancelWorkItems(bus);
599     return;
600   }
601
602 /**
603  * Start a bus thread.
604  *
605  * @v Bus               The bus to start a thread for.
606  * @ret NTSTATUS        The status of the thread creation operation.
607  *
608  * Also see WV_F_BUS_THREAD in the header for details about the prototype
609  * for implementing your own bus thread routine.  You set WV_S_BUS_T::Thread
610  * to specify your own thread routine, then call this function to start it.
611  */
612 winvblock__lib_func NTSTATUS WvBusStartThread(
613     WV_SP_BUS_T Bus
614   ) {
615     OBJECT_ATTRIBUTES obj_attrs;
616     HANDLE thread_handle;
617
618     if (!Bus) {
619         DBG("No bus specified!\n");
620         return STATUS_INVALID_PARAMETER;
621       }
622
623     InitializeObjectAttributes(
624         &obj_attrs,
625         NULL,
626         OBJ_KERNEL_HANDLE,
627         NULL,
628         NULL
629       );
630     return PsCreateSystemThread(
631         &thread_handle,
632         THREAD_ALL_ACCESS,
633         &obj_attrs,
634         NULL,
635         NULL,
636         WvBusThread_,
637         Bus
638       );
639   }
640
641 /**
642  * Initialize a bus node with an associated PDO.
643  *
644  * @v Node              The node to initialize.
645  * @v Pdo               The PDO to associate the node with.
646  * @ret winvblock__bool FALSE for a NULL argument, otherwise TRUE
647  */
648 winvblock__lib_func winvblock__bool STDCALL WvBusInitNode(
649     OUT WV_SP_BUS_NODE Node,
650     IN PDEVICE_OBJECT Pdo
651   ) {
652     if (!Node || !Pdo)
653       return FALSE;
654
655     RtlZeroMemory(Node, sizeof *Node);
656     Node->BusPrivate_.Pdo = Pdo;
657     return TRUE;
658   }
659
660 /**
661  * Add a PDO node to a bus' list of children.
662  *
663  * @v Bus               The bus to add the node to.
664  * @v Node              The PDO node to add to the bus.
665  * @ret NTSTATUS        The status of the operation.
666  *
667  * Do not attempt to add the same node to more than one bus.
668  * When WvBusProcessWorkItems() is called for the bus, the
669  * node will be added.  This is usually from the bus' thread.
670  */
671 winvblock__lib_func NTSTATUS STDCALL WvBusAddNode(
672     WV_SP_BUS_T Bus,
673     WV_SP_BUS_NODE Node
674   ) {
675     WV_SP_BUS_WORK_ITEM_ work_item;
676
677     if (
678         !Bus ||
679         !Node ||
680         Bus->Dev.Self->DriverObject != Node->BusPrivate_.Pdo->DriverObject
681       )
682       return STATUS_INVALID_PARAMETER;
683
684     if (Bus->Stop)
685       return STATUS_NO_SUCH_DEVICE;
686
687     if (!(work_item = wv_malloc(sizeof *work_item)))
688       return STATUS_INSUFFICIENT_RESOURCES;
689
690     work_item->Cmd = WvBusWorkItemCmdAddPdo_;
691     work_item->Context.Node = Node;
692     if (!WvBusAddWorkItem_(Bus, work_item)) {
693         wv_free(work_item);
694         return STATUS_UNSUCCESSFUL;
695       }
696     /* Fire and forget. */
697     KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
698     return STATUS_SUCCESS;
699   }
700
701 /**
702  * Remove a PDO node from a bus.
703  *
704  * @v Node              The PDO node to remove from its parent bus.
705  * @ret NTSTATUS        The status of the operation.
706  *
707  * When WvBusProcessWorkItems() is called for the bus, it will
708  * then remove the node.  This is usually from the bus' thread.
709  */
710 winvblock__lib_func NTSTATUS STDCALL WvBusRemoveNode(
711     WV_SP_BUS_NODE Node
712   ) {
713     WV_SP_BUS_T bus;
714     WV_SP_BUS_WORK_ITEM_ work_item;
715
716     if (!Node || !(bus = Node->BusPrivate_.Bus))
717       return STATUS_INVALID_PARAMETER;
718
719     if (bus->Stop)
720       return STATUS_NO_SUCH_DEVICE;
721
722     if (!(work_item = wv_malloc(sizeof *work_item)))
723       return STATUS_INSUFFICIENT_RESOURCES;
724
725     work_item->Cmd = WvBusWorkItemCmdRemovePdo_;
726     work_item->Context.Node = Node;
727     if (!WvBusAddWorkItem_(bus, work_item)) {
728         wv_free(work_item);
729         return STATUS_UNSUCCESSFUL;
730       }
731     /* Fire and forget. */
732     KeSetEvent(&bus->ThreadSignal, 0, FALSE);
733     return STATUS_SUCCESS;
734   }
735
736 /**
737  * Enqueue an IRP for a bus' thread to process.
738  *
739  * @v Bus               The bus for the IRP.
740  * @v Irp               The IRP for the bus.
741  * @ret NTSTATUS        The status of the operation.  Returns STATUS_PENDING
742  *                      if the IRP is successfully added to the queue.
743  */
744 winvblock__lib_func NTSTATUS STDCALL WvBusEnqueueIrp(
745     WV_SP_BUS_T Bus,
746     PIRP Irp
747   ) {
748     WV_SP_BUS_WORK_ITEM_ work_item;
749
750     if (!Bus || !Irp)
751       return STATUS_INVALID_PARAMETER;
752
753     if (Bus->Stop)
754       return STATUS_NO_SUCH_DEVICE;
755
756     if (!(work_item = wv_malloc(sizeof *work_item)))
757       return STATUS_INSUFFICIENT_RESOURCES;
758
759     work_item->Cmd = WvBusWorkItemCmdProcessIrp_;
760     work_item->Context.Irp = Irp;
761     IoMarkIrpPending(Irp);
762     if (!WvBusAddWorkItem_(Bus, work_item)) {
763         wv_free(work_item);
764         return STATUS_UNSUCCESSFUL;
765       }
766     /* Fire and forget. */
767     KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
768     return STATUS_PENDING;
769   }