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