614de937770ad7db4ba105c20a5c97e5f8c9972b
[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 BOOLEAN 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 WvlBusInit(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  * 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 WvBusAddWorkItem_(
119     WVL_SP_BUS_T bus,
120     WV_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 WV_SP_BUS_WORK_ITEM_    The work item, or NULL for an empty queue.
136  */
137 static WV_SP_BUS_WORK_ITEM_ WvBusGetWorkItem_(
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, WV_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("Adding PDO to bus...\n");
165     ObReferenceObject(new_node->BusPrivate_.Pdo);
166     bus->BusPrivate_.NodeCount++;
167     /* It's too bad about having both linked list and bus ref. */
168     new_node->BusPrivate_.Bus = bus;
169
170     /* Find a slot for the new child. */
171     walker = &bus->BusPrivate_.Nodes;
172     new_node->BusPrivate_.Num = 0;
173     while ((walker = walker->Flink) != &bus->BusPrivate_.Nodes) {
174         WVL_SP_BUS_NODE node = CONTAINING_RECORD(
175             walker,
176             WVL_S_BUS_NODE,
177             BusPrivate_.Link
178           );
179
180         if (
181             node->BusPrivate_.Num &&
182             (node->BusPrivate_.Link.Blink == &bus->BusPrivate_.Nodes)
183           ) {
184             /* The first node's unit number is != 0.  Insert here. */
185             break;
186           }
187         if (node->BusPrivate_.Num > new_node->BusPrivate_.Num) {
188             /* There is a gap so insert here. */
189             break;
190           }
191         /* Continue trying to find a slot. */
192         new_node->BusPrivate_.Num++;
193       } /* while */
194     /* Insert before walker. */
195     InsertTailList(walker, &new_node->BusPrivate_.Link);
196     return;
197   }
198
199 /**
200  * Remove a PDO node from a bus.  Internal.
201  *
202  * @v bus             The bus to remove the node from.
203  * @v node            The PDO node to remove from its parent bus.
204  *
205  * Don't call this function yourself.  It expects to have exclusive
206  * access to the bus' list of children.
207  */
208 static VOID STDCALL WvlBusRemoveNode_(
209     WVL_SP_BUS_T bus,
210     WVL_SP_BUS_NODE node
211   ) {
212     DBG("Removing PDO from bus...\n");
213     RemoveEntryList(&node->BusPrivate_.Link);
214     ObDereferenceObject(node->BusPrivate_.Pdo);
215     bus->BusPrivate_.NodeCount--;
216     return;    
217   }
218
219 /**
220  * Process work items for a bus.
221  *
222  * @v Bus               The bus to process its work items.
223  */
224 WVL_M_LIB VOID WvlBusProcessWorkItems(WVL_SP_BUS_T Bus) {
225     WV_SP_BUS_WORK_ITEM_ work_item;
226     WVL_SP_BUS_NODE node;
227     PIRP irp;
228     PIO_STACK_LOCATION io_stack_loc;
229     PDEVICE_OBJECT dev_obj;
230     PDRIVER_OBJECT driver_obj;
231     BOOLEAN nodes_changed;
232
233     while (work_item = WvBusGetWorkItem_(Bus)) {
234         switch (work_item->Cmd) {
235             case WvBusWorkItemCmdAddPdo_:
236               node = work_item->Context.Node;
237               WvlBusAddNode_(Bus, node);
238               nodes_changed = TRUE;
239               break;
240
241             case WvBusWorkItemCmdRemovePdo_:
242               node = work_item->Context.Node;
243               WvlBusRemoveNode_(Bus, node);
244               nodes_changed = TRUE;
245               break;
246
247             case WvBusWorkItemCmdProcessIrp_:
248               irp = work_item->Context.Irp;
249               io_stack_loc = IoGetCurrentIrpStackLocation(irp);
250               dev_obj = Bus->Fdo;
251               driver_obj = dev_obj->DriverObject;
252               driver_obj->MajorFunction[io_stack_loc->MajorFunction](
253                   dev_obj,
254                   irp
255                 );
256               break;
257
258             case WvBusWorkItemCmdCustom_:
259               DBG("Custom work item.\n");
260               work_item->Context.Custom->Func(
261                   work_item->Context.Custom->Context
262                 );
263               break;
264
265             default:
266               DBG("Unknown work item type!\n");
267           }
268         wv_free(work_item);
269       }
270     if (nodes_changed && Bus->Pdo) {
271         nodes_changed = FALSE;
272         IoInvalidateDeviceRelations(
273             Bus->Pdo,
274             BusRelations
275           );
276       }
277     return;
278   }
279
280 /**
281  * Cancel pending work items for a bus.
282  *
283  * @v Bus       The bus to cancel pending work items for.
284  */
285 WVL_M_LIB VOID WvlBusCancelWorkItems(WVL_SP_BUS_T Bus) {
286     WV_SP_BUS_WORK_ITEM_ work_item;
287
288     DBG("Canceling work items.\n");
289     while (work_item = WvBusGetWorkItem_(Bus))
290       wv_free(work_item);
291     return;
292   }
293
294 /**
295  * The bus thread wrapper.
296  *
297  * @v context           The thread context.  In our case, it points to
298  *                      the bus that the thread should use in processing.
299  *
300  * Note that we do not attempt to free the bus data; this is a bus
301  * implementor's responsibility.  We do, however, set the ThreadStopped
302  * signal which should mean that resources can be freed, from a completed
303  * thread's perspective.
304  */
305 static VOID STDCALL WvBusThread_(IN PVOID context) {
306     WVL_SP_BUS_T bus = context;
307
308     if (!bus || !bus->Thread) {
309         DBG("No bus or no thread!\n");
310         return;
311       }
312
313     bus->Thread(bus);
314     DBG("Exiting.\n");
315     PsTerminateSystemThread(STATUS_SUCCESS);
316     return;
317   }
318
319 /**
320  * The default bus thread routine.
321  *
322  * @v bus       Points to the bus device for the thread to work with.
323  *
324  * Note that if you implement your own bus type using this library,
325  * you can override the thread routine with your own.  If you do so,
326  * your thread routine should call WvlBusProcessWorkItems() within
327  * its loop.  To start a bus thread, use WvlBusStartThread()
328  * If you implement your own thread routine, you are also responsible
329  * for calling WvlBusCancelWorkItems() and freeing the bus.
330  */
331 static VOID STDCALL WvBusDefaultThread_(IN WVL_SP_BUS_T bus) {
332     LARGE_INTEGER timeout;
333
334     /* Wake up at least every 30 seconds. */
335     timeout.QuadPart = -300000000LL;
336
337     /* When WVL_S_BUS_T::Stop is set, we shut down. */
338     while (!bus->Stop) {
339         DBG("Alive.\n");
340
341         /* Wait for the work signal or the timeout. */
342         KeWaitForSingleObject(
343             &bus->ThreadSignal,
344             Executive,
345             KernelMode,
346             FALSE,
347             &timeout
348           );
349         /* Reset the work signal. */
350         KeResetEvent(&bus->ThreadSignal);
351
352         WvlBusProcessWorkItems(bus);
353       } /* while !bus->Stop */
354
355     WvlBusCancelWorkItems(bus);
356     return;
357   }
358
359 /**
360  * Start a bus thread.
361  *
362  * @v Bus               The bus to start a thread for.
363  * @v Thread            A PETHREAD to be filled to reference the thread.
364  * @ret NTSTATUS        The status of the thread creation operation.
365  *
366  * Also see WVL_F_BUS_THREAD in the header for details about the prototype
367  * for implementing your own bus thread routine.  You set WVL_S_BUS_T::Thread
368  * to specify your own thread routine, then call this function to start it.
369  * When stopping the thread, you can wait on the thread handle.
370  */
371 WVL_M_LIB NTSTATUS WvlBusStartThread(
372     IN WVL_SP_BUS_T Bus,
373     OUT PETHREAD * Thread
374   ) {
375     OBJECT_ATTRIBUTES obj_attrs;
376     HANDLE thread_handle;
377     NTSTATUS status;
378
379     if (!Bus) {
380         DBG("No bus specified!\n");
381         return STATUS_INVALID_PARAMETER;
382       }
383
384     InitializeObjectAttributes(
385         &obj_attrs,
386         NULL,
387         OBJ_KERNEL_HANDLE,
388         NULL,
389         NULL
390       );
391     status = PsCreateSystemThread(
392         &thread_handle,
393         THREAD_ALL_ACCESS,
394         &obj_attrs,
395         NULL,
396         NULL,
397         WvBusThread_,
398         Bus
399       );
400     if (!NT_SUCCESS(status))
401       return status;
402     return ObReferenceObjectByHandle(
403         thread_handle,
404         THREAD_ALL_ACCESS,
405         *PsThreadType,
406         KernelMode,
407         Thread,
408         NULL
409       );
410   }
411
412 /**
413  * Initialize a bus node with an associated PDO.
414  *
415  * @v Node              The node to initialize.
416  * @v Pdo               The PDO to associate the node with.
417  * @ret BOOLEAN FALSE for a NULL argument, otherwise TRUE
418  */
419 WVL_M_LIB BOOLEAN STDCALL WvlBusInitNode(
420     OUT WVL_SP_BUS_NODE Node,
421     IN PDEVICE_OBJECT Pdo
422   ) {
423     if (!Node || !Pdo)
424       return FALSE;
425
426     RtlZeroMemory(Node, sizeof *Node);
427     Node->BusPrivate_.Pdo = Pdo;
428     return TRUE;
429   }
430
431 /**
432  * Add a PDO node to a bus' list of children.
433  *
434  * @v Bus               The bus to add the node to.
435  * @v Node              The PDO node to add to the bus.
436  * @ret NTSTATUS        The status of the operation.
437  *
438  * Do not attempt to add the same node to more than one bus.
439  * When WvlBusProcessWorkItems() is called for the bus, the
440  * node will be added.  This is usually from the bus' thread.
441  */
442 WVL_M_LIB NTSTATUS STDCALL WvlBusAddNode(
443     WVL_SP_BUS_T Bus,
444     WVL_SP_BUS_NODE Node
445   ) {
446     WV_SP_BUS_WORK_ITEM_ work_item;
447
448     if (
449         !Bus ||
450         !Node ||
451         Bus->Fdo->DriverObject != Node->BusPrivate_.Pdo->DriverObject
452       )
453       return STATUS_INVALID_PARAMETER;
454
455     if (Bus->Stop)
456       return STATUS_NO_SUCH_DEVICE;
457
458     if (!(work_item = wv_malloc(sizeof *work_item)))
459       return STATUS_INSUFFICIENT_RESOURCES;
460
461     work_item->Cmd = WvBusWorkItemCmdAddPdo_;
462     work_item->Context.Node = Node;
463     if (!WvBusAddWorkItem_(Bus, work_item)) {
464         wv_free(work_item);
465         return STATUS_UNSUCCESSFUL;
466       }
467     /* Fire and forget. */
468     KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
469     return STATUS_SUCCESS;
470   }
471
472 /**
473  * Remove a PDO node from a bus.
474  *
475  * @v Node              The PDO node to remove from its parent bus.
476  * @ret NTSTATUS        The status of the operation.
477  *
478  * When WvlBusProcessWorkItems() is called for the bus, it will
479  * then remove the node.  This is usually from the bus' thread.
480  */
481 WVL_M_LIB NTSTATUS STDCALL WvlBusRemoveNode(
482     WVL_SP_BUS_NODE Node
483   ) {
484     WVL_SP_BUS_T bus;
485     WV_SP_BUS_WORK_ITEM_ work_item;
486
487     if (!Node || !(bus = Node->BusPrivate_.Bus))
488       return STATUS_INVALID_PARAMETER;
489
490     if (bus->Stop)
491       return STATUS_NO_SUCH_DEVICE;
492
493     if (!(work_item = wv_malloc(sizeof *work_item)))
494       return STATUS_INSUFFICIENT_RESOURCES;
495
496     work_item->Cmd = WvBusWorkItemCmdRemovePdo_;
497     work_item->Context.Node = Node;
498     if (!WvBusAddWorkItem_(bus, work_item)) {
499         wv_free(work_item);
500         return STATUS_UNSUCCESSFUL;
501       }
502     /* Fire and forget. */
503     KeSetEvent(&bus->ThreadSignal, 0, FALSE);
504     return STATUS_SUCCESS;
505   }
506
507 /**
508  * Enqueue an IRP for a bus' thread to process.
509  *
510  * @v Bus               The bus for the IRP.
511  * @v Irp               The IRP for the bus.
512  * @ret NTSTATUS        The status of the operation.  Returns STATUS_PENDING
513  *                      if the IRP is successfully added to the queue.
514  */
515 WVL_M_LIB NTSTATUS STDCALL WvlBusEnqueueIrp(
516     WVL_SP_BUS_T Bus,
517     PIRP Irp
518   ) {
519     WV_SP_BUS_WORK_ITEM_ work_item;
520
521     if (!Bus || !Irp)
522       return STATUS_INVALID_PARAMETER;
523
524     if (Bus->Stop)
525       return STATUS_NO_SUCH_DEVICE;
526
527     if (!(work_item = wv_malloc(sizeof *work_item)))
528       return STATUS_INSUFFICIENT_RESOURCES;
529
530     work_item->Cmd = WvBusWorkItemCmdProcessIrp_;
531     work_item->Context.Irp = Irp;
532     IoMarkIrpPending(Irp);
533     if (!WvBusAddWorkItem_(Bus, work_item)) {
534         wv_free(work_item);
535         return STATUS_UNSUCCESSFUL;
536       }
537     /* Fire and forget. */
538     KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
539     return STATUS_PENDING;
540   }
541
542 /**
543  * Enqueue a custom work item for a bus' thread to process.
544  *
545  * @v Bus               The bus for the IRP.
546  * @v CustomWorkItem    The custom work item for the bus' thread to process.
547  * @ret NTSTATUS        The status of the operation.
548  */
549 WVL_M_LIB NTSTATUS STDCALL WvBusEnqueueCustomWorkItem(
550     WVL_SP_BUS_T Bus,
551     WVL_SP_BUS_CUSTOM_WORK_ITEM CustomWorkItem
552   ) {
553     WV_SP_BUS_WORK_ITEM_ work_item;
554
555     if (!Bus || !CustomWorkItem)
556       return STATUS_INVALID_PARAMETER;
557
558     if (Bus->Stop)
559       return STATUS_NO_SUCH_DEVICE;
560
561     if (!(work_item = wv_malloc(sizeof *work_item)))
562       return STATUS_INSUFFICIENT_RESOURCES;
563
564     work_item->Cmd = WvBusWorkItemCmdCustom_;
565     work_item->Context.Custom = CustomWorkItem;
566     if (!WvBusAddWorkItem_(Bus, work_item)) {
567         wv_free(work_item);
568         return STATUS_UNSUCCESSFUL;
569       }
570     /* Fire and forget. */
571     KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
572     return STATUS_SUCCESS;
573   }
574
575 /**
576  * Get the unit number for a child node on a bus.
577  *
578  * @v Node              The node whose unit number we request.
579  * @ret UINT32          The unit number for the node.
580  */
581 WVL_M_LIB UINT32 STDCALL WvBusGetNodeNum(
582     IN WVL_SP_BUS_NODE Node
583   ) {
584     return Node->BusPrivate_.Num;
585   }
586
587 /**
588  * Get the next child node on a bus.
589  *
590  * @v Bus               The bus whose nodes are fetched.
591  * @v PrevNode          The previous node.  Pass NULL to begin.
592  * @ret WVL_SP_BUS_NODE  Returns NULL when there are no more nodes.
593  *
594  * This function should only be called within the thread context of
595  * whichever thread calls WvlBusProcessWorkItems() because it expects
596  * the list of child nodes to remain static between calls.
597  */
598 WVL_M_LIB WVL_SP_BUS_NODE STDCALL WvBusGetNextNode(
599     IN WVL_SP_BUS_T Bus,
600     IN WVL_SP_BUS_NODE PrevNode
601   ) {
602     PLIST_ENTRY link;
603
604     if (!PrevNode)
605       link = &Bus->BusPrivate_.Nodes;
606       else
607       link = &PrevNode->BusPrivate_.Link;
608     link = link->Flink;
609     if (link == &Bus->BusPrivate_.Nodes)
610       return NULL;
611     return CONTAINING_RECORD(link, WVL_S_BUS_NODE, BusPrivate_.Link);
612   }
613
614 /**
615  * Get a child node's PDO.
616  *
617  * @v Node              The node whose PDO will be returned.
618  * @ret PDEVICE_OBJECT  The PDO for the node.
619  */
620 WVL_M_LIB PDEVICE_OBJECT STDCALL WvBusGetNodePdo(
621     IN WVL_SP_BUS_NODE Node
622   ) {
623     return Node->BusPrivate_.Pdo;
624   }
625
626 /**
627  * Get the count of child nodes on a bus.
628  *
629  * @v Bus               The bus whose node-count will be returned.
630  * @v UINT32            The count of nodes on the bus.
631  *
632  * In order for this function to yield a race-free, useful result, it
633  * should be used by whatever thread calls WvlBusProcessWorkItems()
634  */
635 WVL_M_LIB UINT32 STDCALL WvBusGetNodeCount(
636     WVL_SP_BUS_T Bus
637   ) {
638     return Bus->BusPrivate_.NodeCount;
639   }