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