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