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