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