8c8683afbab922de20b2895d312c14c23aee1323
[people/sha0/winvblock.git] / src / winvblock / libbus / libbus.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 "portable.h"
31 #include "winvblock.h"
32 #include "wv_stdlib.h"
33 #include "irp.h"
34 #include "bus.h"
35 #include "debug.h"
36
37 /* Types. */
38 typedef enum WVL_BUS_WORK_ITEM_CMD {
39     WvlBusWorkItemCmdAddPdo_,
40     WvlBusWorkItemCmdRemovePdo_,
41     WvlBusWorkItemCmdProcessIrp_,
42     WvlBusWorkItemCmdCustom_,
43     WvlBusWorkItemCmds_
44   } WVL_E_BUS_WORK_ITEM_CMD, * WVL_EP_BUS_WORK_ITEM_CMD;
45
46 typedef struct WVL_BUS_WORK_ITEM {
47     LIST_ENTRY Link;
48     WVL_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   } WVL_S_BUS_WORK_ITEM, * WVL_SP_BUS_WORK_ITEM;
55
56 /* Forward declarations. */
57 static WVL_F_BUS_THREAD WvlBusDefaultThread;
58 static BOOLEAN WvlBusAddWorkItem(
59     WVL_SP_BUS_T,
60     WVL_SP_BUS_WORK_ITEM
61   );
62 static WVL_SP_BUS_WORK_ITEM WvlBusGetWorkItem(WVL_SP_BUS_T);
63
64 /* Handle an IRP_MJ_SYSTEM_CONTROL IRP. */
65 WVL_M_LIB NTSTATUS STDCALL WvlBusSysCtl(
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 WvlIrpComplete(Irp, 0, STATUS_SUCCESS);
77   }
78
79 /* Handle a power IRP. */
80 WVL_M_LIB NTSTATUS STDCALL WvlBusPower(
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 WvlIrpComplete(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 WvlBusInit(WVL_SP_BUS_T Bus) {
100     RtlZeroMemory(Bus, sizeof *Bus);
101     /* Populate non-zero bus device defaults. */
102     Bus->Thread = WvlBusDefaultThread;
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  * Add a work item for a bus to process.
111  *
112  * @v bus                       The bus to process the work item.
113  * @v work_item                 The work item to add.
114  * @ret BOOLEAN         TRUE if added, else FALSE
115  *
116  * Note that this function will initialize the work item's completion signal.
117  */
118 static BOOLEAN WvlBusAddWorkItem(
119     WVL_SP_BUS_T bus,
120     WVL_SP_BUS_WORK_ITEM work_item
121   ) {
122     ExInterlockedInsertTailList(
123         &bus->BusPrivate_.WorkItems,
124         &work_item->Link,
125         &bus->BusPrivate_.WorkItemsLock
126       );
127
128     return TRUE;
129   }
130
131 /**
132  * Get (and dequeue) a work item from a bus' queue.
133  *
134  * @v bus                       The bus processing the work item.
135  * @ret WVL_SP_BUS_WORK_ITEM    The work item, or NULL for an empty queue.
136  */
137 static WVL_SP_BUS_WORK_ITEM WvlBusGetWorkItem(
138     WVL_SP_BUS_T bus
139   ) {
140     PLIST_ENTRY list_entry;
141
142     list_entry = ExInterlockedRemoveHeadList(
143         &bus->BusPrivate_.WorkItems,
144         &bus->BusPrivate_.WorkItemsLock
145       );
146     if (!list_entry)
147       return NULL;
148
149     return CONTAINING_RECORD(list_entry, WVL_S_BUS_WORK_ITEM, Link);
150   }
151
152 /**
153  * Add a PDO node to a bus' list of children.  Internal.
154  *
155  * @v bus               The bus to add the node to.
156  * @v new_node          The PDO node to add to the bus.
157  *
158  * Don't call this function yourself.  It expects to have exclusive
159  * access to the bus' list of children.
160  */
161 static VOID STDCALL WvlBusAddNode_(WVL_SP_BUS_T bus, WVL_SP_BUS_NODE new_node) {
162     PLIST_ENTRY walker;
163
164     DBG(
165         "Adding PDO 0x%08X to bus 0x%08X.\n",
166         (PVOID) new_node->BusPrivate_.Pdo,
167         (PVOID) bus
168       );
169     ObReferenceObject(new_node->BusPrivate_.Pdo);
170     bus->BusPrivate_.NodeCount++;
171     /* It's too bad about having both linked list and bus ref. */
172     new_node->BusPrivate_.Bus = bus;
173
174     /* Find a slot for the new child. */
175     walker = &bus->BusPrivate_.Nodes;
176     new_node->BusPrivate_.Num = 0;
177     while ((walker = walker->Flink) != &bus->BusPrivate_.Nodes) {
178         WVL_SP_BUS_NODE node = CONTAINING_RECORD(
179             walker,
180             WVL_S_BUS_NODE,
181             BusPrivate_.Link
182           );
183
184         if (
185             node->BusPrivate_.Num &&
186             (node->BusPrivate_.Link.Blink == &bus->BusPrivate_.Nodes)
187           ) {
188             /* The first node's unit number is != 0.  Insert here. */
189             break;
190           }
191         if (node->BusPrivate_.Num > new_node->BusPrivate_.Num) {
192             /* There is a gap so insert here. */
193             break;
194           }
195         /* Continue trying to find a slot. */
196         new_node->BusPrivate_.Num++;
197       } /* while */
198     /* Insert before walker. */
199     InsertTailList(walker, &new_node->BusPrivate_.Link);
200     return;
201   }
202
203 /**
204  * Remove a PDO node from a bus.  Internal.
205  *
206  * @v bus             The bus to remove the node from.
207  * @v node            The PDO node to remove from its parent bus.
208  *
209  * Don't call this function yourself.  It expects to have exclusive
210  * access to the bus' list of children.
211  */
212 static VOID STDCALL WvlBusRemoveNode_(
213     WVL_SP_BUS_T bus,
214     WVL_SP_BUS_NODE node
215   ) {
216     DBG(
217         "Removing PDO 0x%08X from bus 0x%08X.\n",
218         (PVOID) node->BusPrivate_.Pdo,
219         (PVOID) bus
220       );
221     RemoveEntryList(&node->BusPrivate_.Link);
222     ObDereferenceObject(node->BusPrivate_.Pdo);
223     bus->BusPrivate_.NodeCount--;
224     return;    
225   }
226
227 /**
228  * Process work items for a bus.
229  *
230  * @v Bus               The bus to process its work items.
231  */
232 WVL_M_LIB VOID WvlBusProcessWorkItems(WVL_SP_BUS_T Bus) {
233     WVL_SP_BUS_WORK_ITEM work_item;
234     WVL_SP_BUS_NODE node;
235     PIRP irp;
236     PIO_STACK_LOCATION io_stack_loc;
237     PDEVICE_OBJECT dev_obj;
238     PDRIVER_OBJECT driver_obj;
239     BOOLEAN nodes_changed;
240
241     while (work_item = WvlBusGetWorkItem(Bus)) {
242         switch (work_item->Cmd) {
243             case WvlBusWorkItemCmdAddPdo_:
244               node = work_item->Context.Node;
245               WvlBusAddNode_(Bus, node);
246               nodes_changed = TRUE;
247               break;
248
249             case WvlBusWorkItemCmdRemovePdo_:
250               node = work_item->Context.Node;
251               WvlBusRemoveNode_(Bus, node);
252               nodes_changed = TRUE;
253               break;
254
255             case WvlBusWorkItemCmdProcessIrp_:
256               irp = work_item->Context.Irp;
257               io_stack_loc = IoGetCurrentIrpStackLocation(irp);
258               dev_obj = Bus->Fdo;
259               if (!dev_obj)
260                 WvlIrpComplete(irp, 0, STATUS_NO_SUCH_DEVICE);
261               driver_obj = dev_obj->DriverObject;
262               driver_obj->MajorFunction[io_stack_loc->MajorFunction](
263                   dev_obj,
264                   irp
265                 );
266               break;
267
268             case WvlBusWorkItemCmdCustom_:
269               DBG("Custom work item.\n");
270               work_item->Context.Custom->Func(
271                   work_item->Context.Custom->Context
272                 );
273               break;
274
275             default:
276               DBG("Unknown work item type!\n");
277           }
278         wv_free(work_item);
279       }
280     if (nodes_changed && Bus->Pdo) {
281         nodes_changed = FALSE;
282         IoInvalidateDeviceRelations(
283             Bus->Pdo,
284             BusRelations
285           );
286       }
287     return;
288   }
289
290 /**
291  * Cancel pending work items for a bus.
292  *
293  * @v Bus       The bus to cancel pending work items for.
294  */
295 WVL_M_LIB VOID WvlBusCancelWorkItems(WVL_SP_BUS_T Bus) {
296     WVL_SP_BUS_WORK_ITEM work_item;
297
298     DBG("Canceling work items.\n");
299     while (work_item = WvlBusGetWorkItem(Bus))
300       wv_free(work_item);
301     return;
302   }
303
304 /**
305  * The bus thread wrapper.
306  *
307  * @v context           The thread context.  In our case, it points to
308  *                      the bus that the thread should use in processing.
309  *
310  * Note that we do not attempt to free the bus data; this is a bus
311  * implementor's responsibility.  We do, however, set the ThreadStopped
312  * signal which should mean that resources can be freed, from a completed
313  * thread's perspective.
314  */
315 static VOID STDCALL WvlBusThread(IN PVOID context) {
316     WVL_SP_BUS_T bus = context;
317
318     if (!bus || !bus->Thread) {
319         DBG("No bus or no thread!\n");
320         return;
321       }
322
323     bus->Thread(bus);
324     DBG("Exiting.\n");
325     PsTerminateSystemThread(STATUS_SUCCESS);
326     return;
327   }
328
329 /**
330  * The default bus thread routine.
331  *
332  * @v bus       Points to the bus device for the thread to work with.
333  *
334  * Note that if you implement your own bus type using this library,
335  * you can override the thread routine with your own.  If you do so,
336  * your thread routine should call WvlBusProcessWorkItems() within
337  * its loop.  To start a bus thread, use WvlBusStartThread()
338  * If you implement your own thread routine, you are also responsible
339  * for calling WvlBusCancelWorkItems() and freeing the bus.
340  */
341 static VOID STDCALL WvlBusDefaultThread(IN WVL_SP_BUS_T bus) {
342     LARGE_INTEGER timeout;
343
344     /* Wake up at least every 30 seconds. */
345     timeout.QuadPart = -300000000LL;
346
347     /* When WVL_S_BUS_T::Stop is set, we shut down. */
348     while (!bus->Stop) {
349         DBG("Alive.\n");
350
351         /* Wait for the work signal or the timeout. */
352         KeWaitForSingleObject(
353             &bus->ThreadSignal,
354             Executive,
355             KernelMode,
356             FALSE,
357             &timeout
358           );
359         /* Reset the work signal. */
360         KeResetEvent(&bus->ThreadSignal);
361
362         WvlBusProcessWorkItems(bus);
363       } /* while !bus->Stop */
364
365     WvlBusCancelWorkItems(bus);
366     return;
367   }
368
369 /**
370  * Start a bus thread.
371  *
372  * @v Bus               The bus to start a thread for.
373  * @v Thread            A PETHREAD to be filled to reference the thread.
374  * @ret NTSTATUS        The status of the thread creation operation.
375  *
376  * Also see WVL_F_BUS_THREAD in the header for details about the prototype
377  * for implementing your own bus thread routine.  You set WVL_S_BUS_T::Thread
378  * to specify your own thread routine, then call this function to start it.
379  * When stopping the thread, you can wait on the thread handle.
380  */
381 WVL_M_LIB NTSTATUS WvlBusStartThread(
382     IN WVL_SP_BUS_T Bus,
383     OUT PETHREAD * Thread
384   ) {
385     OBJECT_ATTRIBUTES obj_attrs;
386     HANDLE thread_handle;
387     NTSTATUS status;
388
389     if (!Bus) {
390         DBG("No bus specified!\n");
391         return STATUS_INVALID_PARAMETER;
392       }
393
394     InitializeObjectAttributes(
395         &obj_attrs,
396         NULL,
397         OBJ_KERNEL_HANDLE,
398         NULL,
399         NULL
400       );
401     status = PsCreateSystemThread(
402         &thread_handle,
403         THREAD_ALL_ACCESS,
404         &obj_attrs,
405         NULL,
406         NULL,
407         WvlBusThread,
408         Bus
409       );
410     if (!NT_SUCCESS(status))
411       return status;
412     return ObReferenceObjectByHandle(
413         thread_handle,
414         THREAD_ALL_ACCESS,
415         *PsThreadType,
416         KernelMode,
417         Thread,
418         NULL
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 BOOLEAN FALSE for a NULL argument, otherwise TRUE
428  */
429 WVL_M_LIB BOOLEAN STDCALL WvlBusInitNode(
430     OUT WVL_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 WvlBusProcessWorkItems() is called for the bus, the
450  * node will be added.  This is usually from the bus' thread.
451  */
452 WVL_M_LIB NTSTATUS STDCALL WvlBusAddNode(
453     WVL_SP_BUS_T Bus,
454     WVL_SP_BUS_NODE Node
455   ) {
456     WVL_SP_BUS_WORK_ITEM work_item;
457
458     if (
459         !Bus ||
460         !Node ||
461         Bus->Fdo->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 = WvlBusWorkItemCmdAddPdo_;
472     work_item->Context.Node = Node;
473     if (!WvlBusAddWorkItem(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 WvlBusProcessWorkItems() is called for the bus, it will
489  * then remove the node.  This is usually from the bus' thread.
490  */
491 WVL_M_LIB NTSTATUS STDCALL WvlBusRemoveNode(
492     WVL_SP_BUS_NODE Node
493   ) {
494     WVL_SP_BUS_T bus;
495     WVL_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 = WvlBusWorkItemCmdRemovePdo_;
507     work_item->Context.Node = Node;
508     if (!WvlBusAddWorkItem(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 WVL_M_LIB NTSTATUS STDCALL WvlBusEnqueueIrp(
526     WVL_SP_BUS_T Bus,
527     PIRP Irp
528   ) {
529     WVL_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 = WvlBusWorkItemCmdProcessIrp_;
541     work_item->Context.Irp = Irp;
542     IoMarkIrpPending(Irp);
543     if (!WvlBusAddWorkItem(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   }
551
552 /**
553  * Enqueue a custom work item for a bus' thread to process.
554  *
555  * @v Bus               The bus for the IRP.
556  * @v CustomWorkItem    The custom work item for the bus' thread to process.
557  * @ret NTSTATUS        The status of the operation.
558  */
559 WVL_M_LIB NTSTATUS STDCALL WvlBusEnqueueCustomWorkItem(
560     WVL_SP_BUS_T Bus,
561     WVL_SP_BUS_CUSTOM_WORK_ITEM CustomWorkItem
562   ) {
563     WVL_SP_BUS_WORK_ITEM work_item;
564
565     if (!Bus || !CustomWorkItem)
566       return STATUS_INVALID_PARAMETER;
567
568     if (Bus->Stop)
569       return STATUS_NO_SUCH_DEVICE;
570
571     if (!(work_item = wv_malloc(sizeof *work_item)))
572       return STATUS_INSUFFICIENT_RESOURCES;
573
574     work_item->Cmd = WvlBusWorkItemCmdCustom_;
575     work_item->Context.Custom = CustomWorkItem;
576     if (!WvlBusAddWorkItem(Bus, work_item)) {
577         wv_free(work_item);
578         return STATUS_UNSUCCESSFUL;
579       }
580     /* Fire and forget. */
581     KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
582     return STATUS_SUCCESS;
583   }
584
585 /**
586  * Get the unit number for a child node on a bus.
587  *
588  * @v Node              The node whose unit number we request.
589  * @ret UINT32          The unit number for the node.
590  */
591 WVL_M_LIB UINT32 STDCALL WvlBusGetNodeNum(
592     IN WVL_SP_BUS_NODE Node
593   ) {
594     return Node->BusPrivate_.Num;
595   }
596
597 /**
598  * Get the next child node on a bus.
599  *
600  * @v Bus               The bus whose nodes are fetched.
601  * @v PrevNode          The previous node.  Pass NULL to begin.
602  * @ret WVL_SP_BUS_NODE  Returns NULL when there are no more nodes.
603  *
604  * This function should only be called within the thread context of
605  * whichever thread calls WvlBusProcessWorkItems() because it expects
606  * the list of child nodes to remain static between calls.
607  */
608 WVL_M_LIB WVL_SP_BUS_NODE STDCALL WvlBusGetNextNode(
609     IN WVL_SP_BUS_T Bus,
610     IN WVL_SP_BUS_NODE PrevNode
611   ) {
612     PLIST_ENTRY link;
613
614     if (!PrevNode)
615       link = &Bus->BusPrivate_.Nodes;
616       else
617       link = &PrevNode->BusPrivate_.Link;
618     link = link->Flink;
619     if (link == &Bus->BusPrivate_.Nodes)
620       return NULL;
621     return CONTAINING_RECORD(link, WVL_S_BUS_NODE, BusPrivate_.Link);
622   }
623
624 /**
625  * Get a child node's PDO.
626  *
627  * @v Node              The node whose PDO will be returned.
628  * @ret PDEVICE_OBJECT  The PDO for the node.
629  */
630 WVL_M_LIB PDEVICE_OBJECT STDCALL WvlBusGetNodePdo(
631     IN WVL_SP_BUS_NODE Node
632   ) {
633     return Node->BusPrivate_.Pdo;
634   }
635
636 /**
637  * Get the count of child nodes on a bus.
638  *
639  * @v Bus               The bus whose node-count will be returned.
640  * @v UINT32            The count of nodes on the bus.
641  *
642  * In order for this function to yield a race-free, useful result, it
643  * should be used by whatever thread calls WvlBusProcessWorkItems()
644  */
645 WVL_M_LIB UINT32 STDCALL WvlBusGetNodeCount(
646     WVL_SP_BUS_T Bus
647   ) {
648     return Bus->BusPrivate_.NodeCount;
649   }