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