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