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