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