[project] Rename winvblock__uint32 back to UINT32
[people/sha0/winvblock.git] / src / winvblock / bus / bus.c
1 /**
2  * Copyright (C) 2009-2011, 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         WVL_SP_BUS_NODE Node;
51         PIRP Irp;
52         WVL_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 WVL_F_BUS_THREAD WvBusDefaultThread_;
58 static winvblock__bool WvBusAddWorkItem_(
59     WVL_SP_BUS_T,
60     WV_SP_BUS_WORK_ITEM_
61   );
62 static WV_SP_BUS_WORK_ITEM_ WvBusGetWorkItem_(WVL_SP_BUS_T);
63
64 /* Handle an IRP_MJ_SYSTEM_CONTROL IRP. */
65 WVL_M_LIB NTSTATUS STDCALL WvBusSysCtl(
66     IN WVL_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 WVL_M_LIB NTSTATUS STDCALL WvBusPower(
81     IN WVL_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 WVL_M_LIB void WvBusInit(WVL_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   }
108
109 /**
110  * Create a new bus.
111  *
112  * @ret WVL_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  * WVL_S_BUS_T as well as populate the bus with default values.
117  */
118 WVL_M_LIB WVL_SP_BUS_T WvBusCreate(void) {
119     WVL_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     WVL_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     WVL_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_(WVL_SP_BUS_T bus, WVL_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         WVL_SP_BUS_NODE node = CONTAINING_RECORD(
204             walker,
205             WVL_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     WVL_SP_BUS_T bus,
239     WVL_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 WVL_M_LIB void WvBusProcessWorkItems(WVL_SP_BUS_T Bus) {
254     WV_SP_BUS_WORK_ITEM_ work_item;
255     WVL_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             case WvBusWorkItemCmdCustom_:
288               DBG("Custom work item.\n");
289               work_item->Context.Custom->Func(
290                   work_item->Context.Custom->Context
291                 );
292               break;
293
294             default:
295               DBG("Unknown work item type!\n");
296           }
297         wv_free(work_item);
298       }
299     if (nodes_changed && Bus->Pdo) {
300         nodes_changed = FALSE;
301         IoInvalidateDeviceRelations(
302             Bus->Pdo,
303             BusRelations
304           );
305       }
306     return;
307   }
308
309 /**
310  * Cancel pending work items for a bus.
311  *
312  * @v Bus       The bus to cancel pending work items for.
313  */
314 WVL_M_LIB void WvBusCancelWorkItems(WVL_SP_BUS_T Bus) {
315     WV_SP_BUS_WORK_ITEM_ work_item;
316
317     DBG("Canceling work items.\n");
318     while (work_item = WvBusGetWorkItem_(Bus))
319       wv_free(work_item);
320     return;
321   }
322
323 /**
324  * The bus thread wrapper.
325  *
326  * @v context           The thread context.  In our case, it points to
327  *                      the bus that the thread should use in processing.
328  *
329  * Note that we do not attempt to free the bus data; this is a bus
330  * implementor's responsibility.  We do, however, set the ThreadStopped
331  * signal which should mean that resources can be freed, from a completed
332  * thread's perspective.
333  */
334 static void STDCALL WvBusThread_(IN void * context) {
335     WVL_SP_BUS_T bus = context;
336
337     if (!bus || !bus->Thread) {
338         DBG("No bus or no thread!\n");
339         return;
340       }
341
342     bus->Thread(bus);
343     DBG("Exiting.\n");
344     PsTerminateSystemThread(STATUS_SUCCESS);
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 WVL_SP_BUS_T bus) {
361     LARGE_INTEGER timeout;
362
363     /* Wake up at least every 30 seconds. */
364     timeout.QuadPart = -300000000LL;
365
366     /* When WVL_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  * @v Thread            A PETHREAD to be filled to reference the thread.
393  * @ret NTSTATUS        The status of the thread creation operation.
394  *
395  * Also see WVL_F_BUS_THREAD in the header for details about the prototype
396  * for implementing your own bus thread routine.  You set WVL_S_BUS_T::Thread
397  * to specify your own thread routine, then call this function to start it.
398  * When stopping the thread, you can wait on the thread handle.
399  */
400 WVL_M_LIB NTSTATUS WvBusStartThread(
401     IN WVL_SP_BUS_T Bus,
402     OUT PETHREAD * Thread
403   ) {
404     OBJECT_ATTRIBUTES obj_attrs;
405     HANDLE thread_handle;
406     NTSTATUS status;
407
408     if (!Bus) {
409         DBG("No bus specified!\n");
410         return STATUS_INVALID_PARAMETER;
411       }
412
413     InitializeObjectAttributes(
414         &obj_attrs,
415         NULL,
416         OBJ_KERNEL_HANDLE,
417         NULL,
418         NULL
419       );
420     status = PsCreateSystemThread(
421         &thread_handle,
422         THREAD_ALL_ACCESS,
423         &obj_attrs,
424         NULL,
425         NULL,
426         WvBusThread_,
427         Bus
428       );
429     if (!NT_SUCCESS(status))
430       return status;
431     return ObReferenceObjectByHandle(
432         thread_handle,
433         THREAD_ALL_ACCESS,
434         *PsThreadType,
435         KernelMode,
436         Thread,
437         NULL
438       );
439   }
440
441 /**
442  * Initialize a bus node with an associated PDO.
443  *
444  * @v Node              The node to initialize.
445  * @v Pdo               The PDO to associate the node with.
446  * @ret winvblock__bool FALSE for a NULL argument, otherwise TRUE
447  */
448 WVL_M_LIB winvblock__bool STDCALL WvBusInitNode(
449     OUT WVL_SP_BUS_NODE Node,
450     IN PDEVICE_OBJECT Pdo
451   ) {
452     if (!Node || !Pdo)
453       return FALSE;
454
455     RtlZeroMemory(Node, sizeof *Node);
456     Node->BusPrivate_.Pdo = Pdo;
457     return TRUE;
458   }
459
460 /**
461  * Add a PDO node to a bus' list of children.
462  *
463  * @v Bus               The bus to add the node to.
464  * @v Node              The PDO node to add to the bus.
465  * @ret NTSTATUS        The status of the operation.
466  *
467  * Do not attempt to add the same node to more than one bus.
468  * When WvBusProcessWorkItems() is called for the bus, the
469  * node will be added.  This is usually from the bus' thread.
470  */
471 WVL_M_LIB NTSTATUS STDCALL WvBusAddNode(
472     WVL_SP_BUS_T Bus,
473     WVL_SP_BUS_NODE Node
474   ) {
475     WV_SP_BUS_WORK_ITEM_ work_item;
476
477     if (
478         !Bus ||
479         !Node ||
480         Bus->Fdo->DriverObject != Node->BusPrivate_.Pdo->DriverObject
481       )
482       return STATUS_INVALID_PARAMETER;
483
484     if (Bus->Stop)
485       return STATUS_NO_SUCH_DEVICE;
486
487     if (!(work_item = wv_malloc(sizeof *work_item)))
488       return STATUS_INSUFFICIENT_RESOURCES;
489
490     work_item->Cmd = WvBusWorkItemCmdAddPdo_;
491     work_item->Context.Node = Node;
492     if (!WvBusAddWorkItem_(Bus, work_item)) {
493         wv_free(work_item);
494         return STATUS_UNSUCCESSFUL;
495       }
496     /* Fire and forget. */
497     KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
498     return STATUS_SUCCESS;
499   }
500
501 /**
502  * Remove a PDO node from a bus.
503  *
504  * @v Node              The PDO node to remove from its parent bus.
505  * @ret NTSTATUS        The status of the operation.
506  *
507  * When WvBusProcessWorkItems() is called for the bus, it will
508  * then remove the node.  This is usually from the bus' thread.
509  */
510 WVL_M_LIB NTSTATUS STDCALL WvBusRemoveNode(
511     WVL_SP_BUS_NODE Node
512   ) {
513     WVL_SP_BUS_T bus;
514     WV_SP_BUS_WORK_ITEM_ work_item;
515
516     if (!Node || !(bus = Node->BusPrivate_.Bus))
517       return STATUS_INVALID_PARAMETER;
518
519     if (bus->Stop)
520       return STATUS_NO_SUCH_DEVICE;
521
522     if (!(work_item = wv_malloc(sizeof *work_item)))
523       return STATUS_INSUFFICIENT_RESOURCES;
524
525     work_item->Cmd = WvBusWorkItemCmdRemovePdo_;
526     work_item->Context.Node = Node;
527     if (!WvBusAddWorkItem_(bus, work_item)) {
528         wv_free(work_item);
529         return STATUS_UNSUCCESSFUL;
530       }
531     /* Fire and forget. */
532     KeSetEvent(&bus->ThreadSignal, 0, FALSE);
533     return STATUS_SUCCESS;
534   }
535
536 /**
537  * Enqueue an IRP for a bus' thread to process.
538  *
539  * @v Bus               The bus for the IRP.
540  * @v Irp               The IRP for the bus.
541  * @ret NTSTATUS        The status of the operation.  Returns STATUS_PENDING
542  *                      if the IRP is successfully added to the queue.
543  */
544 WVL_M_LIB NTSTATUS STDCALL WvBusEnqueueIrp(
545     WVL_SP_BUS_T Bus,
546     PIRP Irp
547   ) {
548     WV_SP_BUS_WORK_ITEM_ work_item;
549
550     if (!Bus || !Irp)
551       return STATUS_INVALID_PARAMETER;
552
553     if (Bus->Stop)
554       return STATUS_NO_SUCH_DEVICE;
555
556     if (!(work_item = wv_malloc(sizeof *work_item)))
557       return STATUS_INSUFFICIENT_RESOURCES;
558
559     work_item->Cmd = WvBusWorkItemCmdProcessIrp_;
560     work_item->Context.Irp = Irp;
561     IoMarkIrpPending(Irp);
562     if (!WvBusAddWorkItem_(Bus, work_item)) {
563         wv_free(work_item);
564         return STATUS_UNSUCCESSFUL;
565       }
566     /* Fire and forget. */
567     KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
568     return STATUS_PENDING;
569   }
570
571 /**
572  * Enqueue a custom work item for a bus' thread to process.
573  *
574  * @v Bus               The bus for the IRP.
575  * @v CustomWorkItem    The custom work item for the bus' thread to process.
576  * @ret NTSTATUS        The status of the operation.
577  */
578 WVL_M_LIB NTSTATUS STDCALL WvBusEnqueueCustomWorkItem(
579     WVL_SP_BUS_T Bus,
580     WVL_SP_BUS_CUSTOM_WORK_ITEM CustomWorkItem
581   ) {
582     WV_SP_BUS_WORK_ITEM_ work_item;
583
584     if (!Bus || !CustomWorkItem)
585       return STATUS_INVALID_PARAMETER;
586
587     if (Bus->Stop)
588       return STATUS_NO_SUCH_DEVICE;
589
590     if (!(work_item = wv_malloc(sizeof *work_item)))
591       return STATUS_INSUFFICIENT_RESOURCES;
592
593     work_item->Cmd = WvBusWorkItemCmdCustom_;
594     work_item->Context.Custom = CustomWorkItem;
595     if (!WvBusAddWorkItem_(Bus, work_item)) {
596         wv_free(work_item);
597         return STATUS_UNSUCCESSFUL;
598       }
599     /* Fire and forget. */
600     KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
601     return STATUS_SUCCESS;
602   }
603
604 /**
605  * Get the unit number for a child node on a bus.
606  *
607  * @v Node              The node whose unit number we request.
608  * @ret UINT32          The unit number for the node.
609  */
610 WVL_M_LIB UINT32 STDCALL WvBusGetNodeNum(
611     IN WVL_SP_BUS_NODE Node
612   ) {
613     return Node->BusPrivate_.Num;
614   }
615
616 /**
617  * Get the next child node on a bus.
618  *
619  * @v Bus               The bus whose nodes are fetched.
620  * @v PrevNode          The previous node.  Pass NULL to begin.
621  * @ret WVL_SP_BUS_NODE  Returns NULL when there are no more nodes.
622  *
623  * This function should only be called within the thread context of
624  * whichever thread calls WvBusProcessWorkItems() because it expects
625  * the list of child nodes to remain static between calls.
626  */
627 WVL_M_LIB WVL_SP_BUS_NODE STDCALL WvBusGetNextNode(
628     IN WVL_SP_BUS_T Bus,
629     IN WVL_SP_BUS_NODE PrevNode
630   ) {
631     PLIST_ENTRY link;
632
633     if (!PrevNode)
634       link = &Bus->BusPrivate_.Nodes;
635       else
636       link = &PrevNode->BusPrivate_.Link;
637     link = link->Flink;
638     if (link == &Bus->BusPrivate_.Nodes)
639       return NULL;
640     return CONTAINING_RECORD(link, WVL_S_BUS_NODE, BusPrivate_.Link);
641   }
642
643 /**
644  * Get a child node's PDO.
645  *
646  * @v Node              The node whose PDO will be returned.
647  * @ret PDEVICE_OBJECT  The PDO for the node.
648  */
649 WVL_M_LIB PDEVICE_OBJECT STDCALL WvBusGetNodePdo(
650     IN WVL_SP_BUS_NODE Node
651   ) {
652     return Node->BusPrivate_.Pdo;
653   }
654
655 /**
656  * Get the count of child nodes on a bus.
657  *
658  * @v Bus               The bus whose node-count will be returned.
659  * @v UINT32            The count of nodes on the bus.
660  *
661  * In order for this function to yield a race-free, useful result, it
662  * should be used by whatever thread calls WvBusProcessWorkItems()
663  */
664 WVL_M_LIB UINT32 STDCALL WvBusGetNodeCount(
665     WVL_SP_BUS_T Bus
666   ) {
667     return Bus->BusPrivate_.NodeCount;
668   }