[bus] Rename bus__create_pdo_ to WvBusCreatePdo_
[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 device__dev_ctl_func WvBusDevCtlDispatch;
40 /* IRP_MJ_PNP dispatcher from bus/pnp.c */
41 extern device__pnp_func WvBusPnpDispatch;
42
43 /* Types. */
44 typedef enum WV_BUS_WORK_ITEM_CMD_ {
45     WvBusWorkItemCmdAddPdo_,
46     WvBusWorkItemCmdRemovePdo_,
47     WvBusWorkItemCmds_
48   } WV_E_BUS_WORK_ITEM_CMD_, * WV_EP_BUS_WORK_ITEM_CMD_;
49
50 typedef struct WV_BUS_WORK_ITEM_ {
51     LIST_ENTRY Link;
52     WV_E_BUS_WORK_ITEM_CMD_ Cmd;
53     union {
54         WV_SP_BUS_NODE Node;
55       } Context;
56   } WV_S_BUS_WORK_ITEM_, * WV_SP_BUS_WORK_ITEM_;
57
58 /* Forward declarations. */
59 static device__free_func WvBusFree_;
60 static device__create_pdo_func WvBusCreatePdo_;
61 static device__dispatch_func bus__power_;
62 static device__dispatch_func bus__sys_ctl_;
63 static device__pnp_func bus__pnp_dispatch_;
64 static WV_F_BUS_THREAD bus__default_thread_;
65 static winvblock__bool bus__add_work_item_(
66     WV_SP_BUS_T,
67     WV_SP_BUS_WORK_ITEM_
68   );
69 static WV_SP_BUS_WORK_ITEM_ bus__get_work_item_(WV_SP_BUS_T);
70
71 /* Globals. */
72 struct device__irp_mj bus__irp_mj_ = {
73     bus__power_,
74     bus__sys_ctl_,
75     WvBusDevCtlDispatch,
76     (device__scsi_func *) 0,
77     WvBusPnpDispatch,
78   };
79
80 /**
81  * Add a child node to the bus.
82  *
83  * @v bus_ptr           Points to the bus receiving the child.
84  * @v dev_ptr           Points to the child device to add.
85  * @ret                 TRUE for success, FALSE for failure.
86  */
87 winvblock__lib_func winvblock__bool STDCALL WvBusAddChild(
88     IN OUT WV_SP_BUS_T bus_ptr,
89     IN OUT struct device__type * dev_ptr
90   ) {
91     /* The new node's device object. */
92     PDEVICE_OBJECT dev_obj_ptr;
93     /* Walks the child nodes. */
94     struct device__type * walker;
95     winvblock__uint32 dev_num;
96
97     DBG("Entry\n");
98     if ((bus_ptr == NULL) || (dev_ptr == NULL)) {
99         DBG("No bus or no device!\n");
100         return FALSE;
101       }
102     /* Create the child device. */
103     dev_obj_ptr = device__create_pdo(dev_ptr);
104     if (dev_obj_ptr == NULL) {
105         DBG("PDO creation failed!\n");
106         device__free(dev_ptr);
107         return FALSE;
108       }
109
110     dev_ptr->Parent = bus_ptr->Dev->Self;
111     /*
112      * Initialize the device.  For disks, this routine is responsible for
113      * determining the disk's geometry appropriately for AoE/RAM/file disks.
114      */
115     dev_ptr->ops.init(dev_ptr);
116     dev_obj_ptr->Flags &= ~DO_DEVICE_INITIALIZING;
117     /* Add the new device's extension to the bus' list of children. */
118     dev_num = 0;
119     if (bus_ptr->first_child == NULL) {
120         bus_ptr->first_child = dev_ptr;
121       } else {
122         walker = bus_ptr->first_child;
123         /* If the first child device number isn't 0... */
124         if (walker->dev_num) {
125             /* We insert before. */
126             dev_ptr->next_sibling_ptr = walker;
127             bus_ptr->first_child = dev_ptr;
128           } else {
129             while (walker->next_sibling_ptr != NULL) {
130                 /* If there's a gap in the device numbers for the bus... */
131                 if (walker->dev_num < walker->next_sibling_ptr->dev_num - 1) {
132                     /* Insert here, instead of at the end. */
133                     dev_num = walker->dev_num + 1;
134                     dev_ptr->next_sibling_ptr = walker->next_sibling_ptr;
135                     walker->next_sibling_ptr = dev_ptr;
136                     break;
137                   }
138                 walker = walker->next_sibling_ptr;
139                 dev_num = walker->dev_num + 1;
140               }
141             /* If we haven't already inserted the device... */
142             if (!dev_ptr->next_sibling_ptr) {
143                 walker->next_sibling_ptr = dev_ptr;
144                 dev_num = walker->dev_num + 1;
145               }
146           }
147       }
148     dev_ptr->dev_num = dev_num;
149     bus_ptr->Children++;
150     if (bus_ptr->PhysicalDeviceObject != NULL) {
151         IoInvalidateDeviceRelations(
152             bus_ptr->PhysicalDeviceObject,
153             BusRelations
154           );
155       }
156     DBG("Exit\n");
157     return TRUE;
158   }
159
160 static NTSTATUS STDCALL bus__sys_ctl_(
161     IN struct device__type * dev,
162     IN PIRP irp
163   ) {
164     WV_SP_BUS_T bus = WvBusFromDev(dev);
165     PDEVICE_OBJECT lower = bus->LowerDeviceObject;
166
167     if (lower) {
168         DBG("Passing IRP_MJ_SYSTEM_CONTROL down\n");
169         IoSkipCurrentIrpStackLocation(irp);
170         return IoCallDriver(lower, irp);
171       }
172     return driver__complete_irp(irp, 0, STATUS_SUCCESS);
173   }
174
175 static NTSTATUS STDCALL bus__power_(
176     IN struct device__type * dev,
177     IN PIRP irp
178   ) {
179     WV_SP_BUS_T bus = WvBusFromDev(dev);
180     PDEVICE_OBJECT lower = bus->LowerDeviceObject;
181
182     PoStartNextPowerIrp(irp);
183     if (lower) {
184         IoSkipCurrentIrpStackLocation(irp);
185         return PoCallDriver(lower, irp);
186       }
187     return driver__complete_irp(irp, 0, STATUS_SUCCESS);
188   }
189
190 NTSTATUS STDCALL WvBusGetDevCapabilities(
191     IN PDEVICE_OBJECT DeviceObject,
192     IN PDEVICE_CAPABILITIES DeviceCapabilities
193   ) {
194     IO_STATUS_BLOCK ioStatus;
195     KEVENT pnpEvent;
196     NTSTATUS status;
197     PDEVICE_OBJECT targetObject;
198     PIO_STACK_LOCATION irpStack;
199     PIRP pnpIrp;
200
201     RtlZeroMemory(DeviceCapabilities, sizeof (DEVICE_CAPABILITIES));
202     DeviceCapabilities->Size = sizeof (DEVICE_CAPABILITIES);
203     DeviceCapabilities->Version = 1;
204     DeviceCapabilities->Address = -1;
205     DeviceCapabilities->UINumber = -1;
206
207     KeInitializeEvent(&pnpEvent, NotificationEvent, FALSE);
208     targetObject = IoGetAttachedDeviceReference(DeviceObject);
209     pnpIrp = IoBuildSynchronousFsdRequest(
210         IRP_MJ_PNP,
211         targetObject,
212         NULL,
213         0,
214         NULL,
215         &pnpEvent,
216         &ioStatus
217       );
218     if (pnpIrp == NULL) {
219         status = STATUS_INSUFFICIENT_RESOURCES;
220       } else {
221         pnpIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
222         irpStack = IoGetNextIrpStackLocation(pnpIrp);
223         RtlZeroMemory(irpStack, sizeof (IO_STACK_LOCATION));
224         irpStack->MajorFunction = IRP_MJ_PNP;
225         irpStack->MinorFunction = IRP_MN_QUERY_CAPABILITIES;
226         irpStack->Parameters.DeviceCapabilities.Capabilities =
227           DeviceCapabilities;
228         status = IoCallDriver(targetObject, pnpIrp);
229         if (status == STATUS_PENDING) {
230             KeWaitForSingleObject(
231                 &pnpEvent,
232                 Executive,
233                 KernelMode,
234                 FALSE,
235                 NULL
236               );
237             status = ioStatus.Status;
238           }
239       }
240     ObDereferenceObject(targetObject);
241     return status;
242   }
243
244 /* Initialize a bus. */
245 static winvblock__bool STDCALL bus__init_(IN struct device__type * dev) {
246     return TRUE;
247   }
248
249 /**
250  * Initialize bus defaults.
251  *
252  * @v bus               Points to the bus to initialize with defaults.
253  */
254 winvblock__lib_func void WvBusInit(WV_SP_BUS_T bus) {
255     struct device__type * dev = bus->Dev;
256
257     RtlZeroMemory(bus, sizeof *bus);
258     /* Populate non-zero bus device defaults. */
259     bus->Dev = dev;
260     bus->BusPrivate_.PrevFree = dev->ops.free;
261     bus->Thread = bus__default_thread_;
262     KeInitializeSpinLock(&bus->BusPrivate_.WorkItemsLock);
263     InitializeListHead(&bus->BusPrivate_.WorkItems);
264     KeInitializeEvent(&bus->ThreadSignal, SynchronizationEvent, FALSE);
265     dev->ops.create_pdo = WvBusCreatePdo_;
266     dev->ops.init = bus__init_;
267     dev->ops.free = WvBusFree_;
268     dev->ext = bus;
269     dev->irp_mj = &bus__irp_mj_;
270     dev->IsBus = TRUE;
271   }
272
273 /**
274  * Create a new bus.
275  *
276  * @ret bus_ptr         The address of a new bus, or NULL for failure.
277  *
278  * This function should not be confused with a PDO creation routine, which is
279  * actually implemented for each device type.  This routine will allocate a
280  * WV_S_BUS_T, track it in a global list, as well as populate the bus
281  * with default values.
282  */
283 winvblock__lib_func WV_SP_BUS_T WvBusCreate(void) {
284     struct device__type * dev;
285     WV_SP_BUS_T bus;
286
287     /* Try to create a device. */
288     dev = device__create();
289     if (dev == NULL)
290       goto err_no_dev;
291     /*
292      * Bus devices might be used for booting and should
293      * not be allocated from a paged memory pool.
294      */
295     bus = wv_malloc(sizeof *bus);
296     if (bus == NULL)
297       goto err_no_bus;
298
299     bus->Dev = dev;
300     WvBusInit(bus);
301     return bus;
302
303     err_no_bus:
304
305     device__free(dev);
306     err_no_dev:
307
308     return NULL;
309   }
310
311 /**
312  * Create a bus PDO.
313  *
314  * @v dev               Populate PDO dev. ext. space from these details.
315  * @ret pdo             Points to the new PDO, or is NULL upon failure.
316  *
317  * Returns a Physical Device Object pointer on success, NULL for failure.
318  */
319 static PDEVICE_OBJECT STDCALL WvBusCreatePdo_(IN struct device__type * dev) {
320     PDEVICE_OBJECT pdo = NULL;
321     WV_SP_BUS_T bus;
322     NTSTATUS status;
323
324     /* Note the bus device needing a PDO. */
325     if (dev == NULL) {
326         DBG("No device passed\n");
327         return NULL;
328       }
329     bus = WvBusFromDev(dev);
330     /* Create the PDO. */
331     status = IoCreateDevice(
332         dev->DriverObject,
333         sizeof (driver__dev_ext),
334         NULL,
335         FILE_DEVICE_CONTROLLER,
336         FILE_DEVICE_SECURE_OPEN,
337         FALSE,
338         &pdo
339       );
340     if (pdo == NULL) {
341         DBG("IoCreateDevice() failed!\n");
342         goto err_pdo;
343       }
344
345     /* Set associations for the bus, device, PDO. */
346     device__set(pdo, dev);
347     dev->Self = bus->PhysicalDeviceObject = pdo;
348
349     /* Set some DEVICE_OBJECT status. */
350     pdo->Flags |= DO_DIRECT_IO;         /* FIXME? */
351     pdo->Flags |= DO_POWER_INRUSH;      /* FIXME? */
352     pdo->Flags &= ~DO_DEVICE_INITIALIZING;
353     #ifdef RIS
354     dev->State = Started;
355     #endif
356
357     return pdo;
358
359     IoDeleteDevice(pdo);
360     err_pdo:
361
362     return NULL;
363   }
364
365 /**
366  * Default bus deletion operation.
367  *
368  * @v dev_ptr           Points to the bus device to delete.
369  */
370 static void STDCALL WvBusFree_(IN struct device__type * dev_ptr) {
371     WV_SP_BUS_T bus_ptr = WvBusFromDev(dev_ptr);
372     /* Free the "inherited class". */
373     bus_ptr->BusPrivate_.PrevFree(dev_ptr);
374
375     wv_free(bus_ptr);
376   }
377
378 /**
379  * Get a bus from a device.
380  *
381  * @v dev       A pointer to a device.
382  * @ret         A pointer to the device's associated bus.
383  */
384 extern winvblock__lib_func WV_SP_BUS_T WvBusFromDev(
385     struct device__type * dev
386   ) {
387     return dev->ext;
388   }
389
390 /**
391  * Add a work item for a bus to process.
392  *
393  * @v bus                       The bus to process the work item.
394  * @v work_item                 The work item to add.
395  * @ret winvblock__bool         TRUE if added, else FALSE
396  *
397  * Note that this function will initialize the work item's completion signal.
398  */
399 static winvblock__bool bus__add_work_item_(
400     WV_SP_BUS_T bus,
401     WV_SP_BUS_WORK_ITEM_ work_item
402   ) {
403     ExInterlockedInsertTailList(
404         &bus->BusPrivate_.WorkItems,
405         &work_item->Link,
406         &bus->BusPrivate_.WorkItemsLock
407       );
408
409     return TRUE;
410   }
411
412 /**
413  * Get (and dequeue) a work item from a bus' queue.
414  *
415  * @v bus                       The bus processing the work item.
416  * @ret bus__work_item_         The work item, or NULL for an empty queue.
417  */
418 static WV_SP_BUS_WORK_ITEM_ bus__get_work_item_(
419     WV_SP_BUS_T bus
420   ) {
421     PLIST_ENTRY list_entry;
422
423     list_entry = ExInterlockedRemoveHeadList(
424         &bus->BusPrivate_.WorkItems,
425         &bus->BusPrivate_.WorkItemsLock
426       );
427     if (!list_entry)
428       return NULL;
429
430     return CONTAINING_RECORD(list_entry, WV_S_BUS_WORK_ITEM_, Link);
431   }
432
433 /**
434  * Process work items for a bus.
435  *
436  * @v bus               The bus to process its work items.
437  */
438 winvblock__lib_func void WvBusProcessWorkItems(WV_SP_BUS_T bus) {
439     WV_SP_BUS_WORK_ITEM_ work_item;
440     WV_SP_BUS_NODE node;
441
442     while (work_item = bus__get_work_item_(bus)) {
443         switch (work_item->Cmd) {
444             case WvBusWorkItemCmdAddPdo_:
445               DBG("Adding PDO to bus...\n");
446
447               node = work_item->Context.Node;
448               /* It's too bad about having both linked list and bus ref. */
449               node->BusPrivate_.Bus = bus;
450               ObReferenceObject(node->BusPrivate_.Pdo);
451               InsertTailList(&bus->BusPrivate_.Nodes, &node->BusPrivate_.Link);
452               bus->BusPrivate_.NodeCount++;
453               break;
454
455             case WvBusWorkItemCmdRemovePdo_:
456               DBG("Removing PDO from bus...\n");
457
458               node = work_item->Context.Node;
459               RemoveEntryList(&node->BusPrivate_.Link);
460               ObDereferenceObject(node->BusPrivate_.Pdo);
461               bus->BusPrivate_.NodeCount--;
462               break;
463
464             default:
465               DBG("Unknown work item type!\n");
466           }
467         wv_free(work_item);
468       }
469     return;
470   }
471
472 /**
473  * Cancel pending work items for a bus.
474  *
475  * @v bus       The bus to cancel pending work items for.
476  */
477 winvblock__lib_func void WvBusCancelWorkItems(WV_SP_BUS_T bus) {
478     WV_SP_BUS_WORK_ITEM_ work_item;
479
480     DBG("Canceling work items.\n");
481     while (work_item = bus__get_work_item_(bus))
482       wv_free(work_item);
483     return;
484   }
485
486 /* The device__type::ops.free implementation for a threaded bus. */
487 static void STDCALL bus__thread_free_(IN struct device__type * dev) {
488     WV_SP_BUS_T bus = WvBusFromDev(dev);
489
490     bus->Stop = TRUE;
491     KeSetEvent(&bus->ThreadSignal, 0, FALSE);
492     return;
493   }
494
495 /**
496  * The bus thread wrapper.
497  *
498  * @v context           The thread context.  In our case, it points to
499  *                      the bus that the thread should use in processing.
500  */
501 static void STDCALL bus__thread_(IN void * context) {
502     WV_SP_BUS_T bus = context;
503
504     if (!bus || !bus->Thread) {
505         DBG("No bus or no thread!\n");
506         return;
507       }
508
509     bus->Thread(bus);
510     return;
511   }
512
513 /**
514  * The default bus thread routine.
515  *
516  * @v bus       Points to the bus device for the thread to work with.
517  *
518  * Note that if you implement your own bus type using this library,
519  * you can override the thread routine with your own.  If you do so,
520  * your thread routine should call WvBusProcessWorkItems() within
521  * its loop.  To start a bus thread, use WvBusStartThread()
522  * If you implement your own thread routine, you are also responsible
523  * for calling WvBusCancelWorkItems() and freeing the bus.
524  */
525 static void STDCALL bus__default_thread_(IN WV_SP_BUS_T bus) {
526     LARGE_INTEGER timeout;
527
528     /* Wake up at least every 30 seconds. */
529     timeout.QuadPart = -300000000LL;
530
531     /* Hook device__type::ops.free() */
532     bus->Dev->ops.free = bus__thread_free_;
533
534     /* When bus::Stop is set, we shut down. */
535     while (bus->Stop) {
536         DBG("Alive.\n");
537
538         /* Wait for the work signal or the timeout. */
539         KeWaitForSingleObject(
540             &bus->ThreadSignal,
541             Executive,
542             KernelMode,
543             FALSE,
544             &timeout
545           );
546         /* Reset the work signal. */
547         KeResetEvent(&bus->ThreadSignal);
548
549         WvBusProcessWorkItems(bus);
550       } /* while bus->alive */
551
552     WvBusCancelWorkItems(bus);
553     WvBusFree_(bus->Dev);
554     return;
555   }
556
557 /**
558  * Start a bus thread.
559  *
560  * @v bus               The bus to start a thread for.
561  * @ret NTSTATUS        The status of the thread creation operation.
562  *
563  * Also see WV_F_BUS_THREAD in the header for details about the prototype
564  * for implementing your own bus thread routine.  You set bus::Thread to
565  * specify your own thread routine, then call this function to start it.
566  */
567 winvblock__lib_func NTSTATUS WvBusStartThread(
568     WV_SP_BUS_T bus
569   ) {
570     OBJECT_ATTRIBUTES obj_attrs;
571     HANDLE thread_handle;
572
573     if (!bus) {
574         DBG("No bus specified!\n");
575         return STATUS_INVALID_PARAMETER;
576       }
577
578     InitializeObjectAttributes(
579         &obj_attrs,
580         NULL,
581         OBJ_KERNEL_HANDLE,
582         NULL,
583         NULL
584       );
585     return PsCreateSystemThread(
586         &thread_handle,
587         THREAD_ALL_ACCESS,
588         &obj_attrs,
589         NULL,
590         NULL,
591         bus__thread_,
592         bus
593       );
594   }
595
596 /**
597  * Initialize a bus node with an associated PDO.
598  *
599  * @v Node              The node to initialize.
600  * @v Pdo               The PDO to associate the node with.
601  * @ret winvblock__bool FALSE for a NULL argument, otherwise TRUE
602  */
603 winvblock__lib_func winvblock__bool STDCALL WvBusInitNode(
604     OUT WV_SP_BUS_NODE Node,
605     IN PDEVICE_OBJECT Pdo
606   ) {
607     if (!Node || !Pdo)
608       return FALSE;
609
610     RtlZeroMemory(Node, sizeof *Node);
611     Node->BusPrivate_.Pdo = Pdo;
612     return TRUE;
613   }
614
615 /**
616  * Add a PDO node to a bus' list of children.
617  *
618  * @v Bus               The bus to add the node to.
619  * @v Node              The PDO node to add to the bus.
620  * @ret NTSTATUS        The status of the operation.
621  *
622  * Do not attempt to add the same node to more than one bus.
623  * When WvBusProcessWorkItems() is called for the bus, the
624  * node will be added.  This is usually from the bus' thread.
625  */
626 winvblock__lib_func NTSTATUS STDCALL WvBusAddNode(
627     WV_SP_BUS_T Bus,
628     WV_SP_BUS_NODE Node
629   ) {
630     WV_SP_BUS_WORK_ITEM_ work_item;
631
632     if (
633         !Bus ||
634         !Node ||
635         Bus->Dev->Self->DriverObject != Node->BusPrivate_.Pdo->DriverObject
636       )
637       return STATUS_INVALID_PARAMETER;
638
639     if (Bus->Stop)
640       return STATUS_NO_SUCH_DEVICE;
641
642     if (!(work_item = wv_malloc(sizeof *work_item)))
643       return STATUS_INSUFFICIENT_RESOURCES;
644
645     work_item->Cmd = WvBusWorkItemCmdAddPdo_;
646     work_item->Context.Node = Node;
647     if (!bus__add_work_item_(Bus, work_item)) {
648         wv_free(work_item);
649         return STATUS_UNSUCCESSFUL;
650       }
651     /* Fire and forget. */
652     KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
653     return STATUS_SUCCESS;
654   }
655
656 /**
657  * Remove a PDO node from a bus.
658  *
659  * @v Node              The PDO node to remove from its parent bus.
660  * @ret NTSTATUS        The status of the operation.
661  *
662  * When WvBusProcessWorkItems() is called for the bus, it will
663  * then remove the node.  This is usually from the bus' thread.
664  */
665 winvblock__lib_func NTSTATUS STDCALL WvBusRemoveNode(
666     WV_SP_BUS_NODE Node
667   ) {
668     WV_SP_BUS_T bus;
669     WV_SP_BUS_WORK_ITEM_ work_item;
670
671     if (!Node || !(bus = Node->BusPrivate_.Bus))
672       return STATUS_INVALID_PARAMETER;
673
674     if (bus->Stop)
675       return STATUS_NO_SUCH_DEVICE;
676
677     if (!(work_item = wv_malloc(sizeof *work_item)))
678       return STATUS_INSUFFICIENT_RESOURCES;
679
680     work_item->Cmd = WvBusWorkItemCmdRemovePdo_;
681     work_item->Context.Node = Node;
682     if (!bus__add_work_item_(bus, work_item)) {
683         wv_free(work_item);
684         return STATUS_UNSUCCESSFUL;
685       }
686     /* Fire and forget. */
687     KeSetEvent(&bus->ThreadSignal, 0, FALSE);
688     return STATUS_SUCCESS;
689   }