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