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