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