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/
6 * This file is part of WinVBlock, derived from WinAoE.
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.
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.
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/>.
30 #include "winvblock.h"
31 #include "wv_stdlib.h"
38 /* IRP_MJ_DEVICE_CONTROL dispatcher from bus/dev_ctl.c */
39 extern device__dev_ctl_func bus_dev_ctl__dispatch;
40 /* IRP_MJ_PNP dispatcher from bus/pnp.c */
41 extern device__pnp_func bus_pnp__dispatch;
44 typedef enum WV_BUS_WORK_ITEM_CMD_ {
45 WvBusWorkItemCmdAddPdo_,
46 WvBusWorkItemCmdRemovePdo_,
48 } WV_E_BUS_WORK_ITEM_CMD_, * WV_EP_BUS_WORK_ITEM_CMD_;
50 typedef struct WV_BUS_WORK_ITEM_ {
52 WV_E_BUS_WORK_ITEM_CMD_ Cmd;
56 } WV_S_BUS_WORK_ITEM_, * WV_SP_BUS_WORK_ITEM_;
58 /* Forward declarations. */
59 static device__free_func bus__free_;
60 static device__create_pdo_func bus__create_pdo_;
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_(
69 static WV_SP_BUS_WORK_ITEM_ bus__get_work_item_(WV_SP_BUS_T);
72 struct device__irp_mj bus__irp_mj_ = {
75 bus_dev_ctl__dispatch,
76 (device__scsi_func *) 0,
81 * Add a child node to the bus.
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.
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
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;
98 if ((bus_ptr == NULL) || (dev_ptr == NULL)) {
99 DBG("No bus or no device!\n");
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);
110 dev_ptr->Parent = bus_ptr->Dev->Self;
112 * Initialize the device. For disks, this routine is responsible for
113 * determining the disk's geometry appropriately for AoE/RAM/file disks.
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. */
119 if (bus_ptr->first_child == NULL) {
120 bus_ptr->first_child = dev_ptr;
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;
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;
138 walker = walker->next_sibling_ptr;
139 dev_num = walker->dev_num + 1;
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;
148 dev_ptr->dev_num = dev_num;
150 if (bus_ptr->PhysicalDeviceObject != NULL) {
151 IoInvalidateDeviceRelations(
152 bus_ptr->PhysicalDeviceObject,
160 static NTSTATUS STDCALL bus__sys_ctl_(
161 IN struct device__type * dev,
164 WV_SP_BUS_T bus = WvBusFromDev(dev);
165 PDEVICE_OBJECT lower = bus->LowerDeviceObject;
168 DBG("Passing IRP_MJ_SYSTEM_CONTROL down\n");
169 IoSkipCurrentIrpStackLocation(irp);
170 return IoCallDriver(lower, irp);
172 return driver__complete_irp(irp, 0, STATUS_SUCCESS);
175 static NTSTATUS STDCALL bus__power_(
176 IN struct device__type * dev,
179 WV_SP_BUS_T bus = WvBusFromDev(dev);
180 PDEVICE_OBJECT lower = bus->LowerDeviceObject;
182 PoStartNextPowerIrp(irp);
184 IoSkipCurrentIrpStackLocation(irp);
185 return PoCallDriver(lower, irp);
187 return driver__complete_irp(irp, 0, STATUS_SUCCESS);
190 NTSTATUS STDCALL WvBusGetDevCapabilities(
191 IN PDEVICE_OBJECT DeviceObject,
192 IN PDEVICE_CAPABILITIES DeviceCapabilities
194 IO_STATUS_BLOCK ioStatus;
197 PDEVICE_OBJECT targetObject;
198 PIO_STACK_LOCATION irpStack;
201 RtlZeroMemory(DeviceCapabilities, sizeof (DEVICE_CAPABILITIES));
202 DeviceCapabilities->Size = sizeof (DEVICE_CAPABILITIES);
203 DeviceCapabilities->Version = 1;
204 DeviceCapabilities->Address = -1;
205 DeviceCapabilities->UINumber = -1;
207 KeInitializeEvent(&pnpEvent, NotificationEvent, FALSE);
208 targetObject = IoGetAttachedDeviceReference(DeviceObject);
209 pnpIrp = IoBuildSynchronousFsdRequest(
218 if (pnpIrp == NULL) {
219 status = STATUS_INSUFFICIENT_RESOURCES;
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 =
228 status = IoCallDriver(targetObject, pnpIrp);
229 if (status == STATUS_PENDING) {
230 KeWaitForSingleObject(
237 status = ioStatus.Status;
240 ObDereferenceObject(targetObject);
244 /* Initialize a bus. */
245 static winvblock__bool STDCALL bus__init_(IN struct device__type * dev) {
250 * Initialize bus defaults.
252 * @v bus Points to the bus to initialize with defaults.
254 winvblock__lib_func void WvBusInit(WV_SP_BUS_T bus) {
255 struct device__type * dev = bus->Dev;
257 RtlZeroMemory(bus, sizeof *bus);
258 /* Populate non-zero bus device defaults. */
260 bus->BusPrivate_.PrevFree = dev->ops.free;
261 bus->Thread = bus__default_thread_;
262 KeInitializeSpinLock(&bus->SpinLock);
263 KeInitializeSpinLock(&bus->BusPrivate_.WorkItemsLock);
264 InitializeListHead(&bus->BusPrivate_.WorkItems);
265 KeInitializeEvent(&bus->ThreadSignal, SynchronizationEvent, FALSE);
266 dev->ops.create_pdo = bus__create_pdo_;
267 dev->ops.init = bus__init_;
268 dev->ops.free = bus__free_;
270 dev->irp_mj = &bus__irp_mj_;
277 * @ret bus_ptr The address of a new bus, or NULL for failure.
279 * This function should not be confused with a PDO creation routine, which is
280 * actually implemented for each device type. This routine will allocate a
281 * WV_S_BUS_T, track it in a global list, as well as populate the bus
282 * with default values.
284 winvblock__lib_func WV_SP_BUS_T WvBusCreate(void) {
285 struct device__type * dev;
288 /* Try to create a device. */
289 dev = device__create();
293 * Bus devices might be used for booting and should
294 * not be allocated from a paged memory pool.
296 bus = wv_malloc(sizeof *bus);
315 * @v dev Populate PDO dev. ext. space from these details.
316 * @ret pdo Points to the new PDO, or is NULL upon failure.
318 * Returns a Physical Device Object pointer on success, NULL for failure.
320 static PDEVICE_OBJECT STDCALL bus__create_pdo_(IN struct device__type * dev) {
321 PDEVICE_OBJECT pdo = NULL;
325 /* Note the bus device needing a PDO. */
327 DBG("No device passed\n");
330 bus = WvBusFromDev(dev);
331 /* Create the PDO. */
332 status = IoCreateDevice(
334 sizeof (driver__dev_ext),
336 FILE_DEVICE_CONTROLLER,
337 FILE_DEVICE_SECURE_OPEN,
342 DBG("IoCreateDevice() failed!\n");
346 /* Set associations for the bus, device, PDO. */
347 device__set(pdo, dev);
348 dev->Self = bus->PhysicalDeviceObject = pdo;
350 /* Set some DEVICE_OBJECT status. */
351 pdo->Flags |= DO_DIRECT_IO; /* FIXME? */
352 pdo->Flags |= DO_POWER_INRUSH; /* FIXME? */
353 pdo->Flags &= ~DO_DEVICE_INITIALIZING;
355 dev->State = Started;
367 * Default bus deletion operation.
369 * @v dev_ptr Points to the bus device to delete.
371 static void STDCALL bus__free_(IN struct device__type * dev_ptr) {
372 WV_SP_BUS_T bus_ptr = WvBusFromDev(dev_ptr);
373 /* Free the "inherited class". */
374 bus_ptr->BusPrivate_.PrevFree(dev_ptr);
380 * Get a bus from a device.
382 * @v dev A pointer to a device.
383 * @ret A pointer to the device's associated bus.
385 extern winvblock__lib_func WV_SP_BUS_T WvBusFromDev(
386 struct device__type * dev
392 * Add a work item for a bus to process.
394 * @v bus The bus to process the work item.
395 * @v work_item The work item to add.
396 * @ret winvblock__bool TRUE if added, else FALSE
398 * Note that this function will initialize the work item's completion signal.
400 static winvblock__bool bus__add_work_item_(
402 WV_SP_BUS_WORK_ITEM_ work_item
404 ExInterlockedInsertTailList(
405 &bus->BusPrivate_.WorkItems,
407 &bus->BusPrivate_.WorkItemsLock
414 * Get (and dequeue) a work item from a bus' queue.
416 * @v bus The bus processing the work item.
417 * @ret bus__work_item_ The work item, or NULL for an empty queue.
419 static WV_SP_BUS_WORK_ITEM_ bus__get_work_item_(
422 PLIST_ENTRY list_entry;
424 list_entry = ExInterlockedRemoveHeadList(
425 &bus->BusPrivate_.WorkItems,
426 &bus->BusPrivate_.WorkItemsLock
431 return CONTAINING_RECORD(list_entry, WV_S_BUS_WORK_ITEM_, Link);
435 * Process work items for a bus.
437 * @v bus The bus to process its work items.
439 winvblock__lib_func void WvBusProcessWorkItems(WV_SP_BUS_T bus) {
440 WV_SP_BUS_WORK_ITEM_ work_item;
443 while (work_item = bus__get_work_item_(bus)) {
444 switch (work_item->Cmd) {
445 case WvBusWorkItemCmdAddPdo_:
446 DBG("Adding PDO to bus...\n");
448 node = work_item->Context.Node;
449 /* It's too bad about having both linked list and bus ref. */
450 node->BusPrivate_.Bus = bus;
451 ObReferenceObject(node->BusPrivate_.Pdo);
452 InsertTailList(&bus->BusPrivate_.Nodes, &node->BusPrivate_.Link);
453 bus->BusPrivate_.NodeCount++;
456 case WvBusWorkItemCmdRemovePdo_:
457 DBG("Removing PDO from bus...\n");
459 node = work_item->Context.Node;
460 RemoveEntryList(&node->BusPrivate_.Link);
461 ObDereferenceObject(node->BusPrivate_.Pdo);
462 bus->BusPrivate_.NodeCount--;
466 DBG("Unknown work item type!\n");
474 * Cancel pending work items for a bus.
476 * @v bus The bus to cancel pending work items for.
478 winvblock__lib_func void WvBusCancelWorkItems(WV_SP_BUS_T bus) {
479 WV_SP_BUS_WORK_ITEM_ work_item;
481 DBG("Canceling work items.\n");
482 while (work_item = bus__get_work_item_(bus))
487 /* The device__type::ops.free implementation for a threaded bus. */
488 static void STDCALL bus__thread_free_(IN struct device__type * dev) {
489 WV_SP_BUS_T bus = WvBusFromDev(dev);
492 KeSetEvent(&bus->ThreadSignal, 0, FALSE);
497 * The bus thread wrapper.
499 * @v context The thread context. In our case, it points to
500 * the bus that the thread should use in processing.
502 static void STDCALL bus__thread_(IN void * context) {
503 WV_SP_BUS_T bus = context;
505 if (!bus || !bus->Thread) {
506 DBG("No bus or no thread!\n");
515 * The default bus thread routine.
517 * @v bus Points to the bus device for the thread to work with.
519 * Note that if you implement your own bus type using this library,
520 * you can override the thread routine with your own. If you do so,
521 * your thread routine should call WvBusProcessWorkItems() within
522 * its loop. To start a bus thread, use WvBusStartThread()
523 * If you implement your own thread routine, you are also responsible
524 * for calling WvBusCancelWorkItems() and freeing the bus.
526 static void STDCALL bus__default_thread_(IN WV_SP_BUS_T bus) {
527 LARGE_INTEGER timeout;
529 /* Wake up at least every 30 seconds. */
530 timeout.QuadPart = -300000000LL;
532 /* Hook device__type::ops.free() */
533 bus->Dev->ops.free = bus__thread_free_;
535 /* When bus::Stop is set, we shut down. */
539 /* Wait for the work signal or the timeout. */
540 KeWaitForSingleObject(
547 /* Reset the work signal. */
548 KeResetEvent(&bus->ThreadSignal);
550 WvBusProcessWorkItems(bus);
551 } /* while bus->alive */
553 WvBusCancelWorkItems(bus);
554 bus__free_(bus->Dev);
559 * Start a bus thread.
561 * @v bus The bus to start a thread for.
562 * @ret NTSTATUS The status of the thread creation operation.
564 * Also see WV_F_BUS_THREAD in the header for details about the prototype
565 * for implementing your own bus thread routine. You set bus::Thread to
566 * specify your own thread routine, then call this function to start it.
568 winvblock__lib_func NTSTATUS WvBusStartThread(
571 OBJECT_ATTRIBUTES obj_attrs;
572 HANDLE thread_handle;
575 DBG("No bus specified!\n");
576 return STATUS_INVALID_PARAMETER;
579 InitializeObjectAttributes(
586 return PsCreateSystemThread(
598 * Initialize a bus node with an associated PDO.
600 * @v Node The node to initialize.
601 * @v Pdo The PDO to associate the node with.
602 * @ret winvblock__bool FALSE for a NULL argument, otherwise TRUE
604 winvblock__lib_func winvblock__bool STDCALL WvBusInitNode(
605 OUT WV_SP_BUS_NODE Node,
606 IN PDEVICE_OBJECT Pdo
611 RtlZeroMemory(Node, sizeof *Node);
612 Node->BusPrivate_.Pdo = Pdo;
617 * Add a PDO node to a bus' list of children.
619 * @v Bus The bus to add the node to.
620 * @v Node The PDO node to add to the bus.
621 * @ret NTSTATUS The status of the operation.
623 * Do not attempt to add the same node to more than one bus.
624 * When WvBusProcessWorkItems() is called for the bus, the
625 * node will be added. This is usually from the bus' thread.
627 winvblock__lib_func NTSTATUS STDCALL WvBusAddNode(
631 WV_SP_BUS_WORK_ITEM_ work_item;
636 Bus->Dev->Self->DriverObject != Node->BusPrivate_.Pdo->DriverObject
638 return STATUS_INVALID_PARAMETER;
641 return STATUS_NO_SUCH_DEVICE;
643 if (!(work_item = wv_malloc(sizeof *work_item)))
644 return STATUS_INSUFFICIENT_RESOURCES;
646 work_item->Cmd = WvBusWorkItemCmdAddPdo_;
647 work_item->Context.Node = Node;
648 if (!bus__add_work_item_(Bus, work_item)) {
650 return STATUS_UNSUCCESSFUL;
652 /* Fire and forget. */
653 KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
654 return STATUS_SUCCESS;
658 * Remove a PDO node from a bus.
660 * @v Node The PDO node to remove from its parent bus.
661 * @ret NTSTATUS The status of the operation.
663 * When WvBusProcessWorkItems() is called for the bus, it will
664 * then remove the node. This is usually from the bus' thread.
666 winvblock__lib_func NTSTATUS STDCALL WvBusRemoveNode(
670 WV_SP_BUS_WORK_ITEM_ work_item;
672 if (!Node || !(bus = Node->BusPrivate_.Bus))
673 return STATUS_INVALID_PARAMETER;
676 return STATUS_NO_SUCH_DEVICE;
678 if (!(work_item = wv_malloc(sizeof *work_item)))
679 return STATUS_INSUFFICIENT_RESOURCES;
681 work_item->Cmd = WvBusWorkItemCmdRemovePdo_;
682 work_item->Context.Node = Node;
683 if (!bus__add_work_item_(bus, work_item)) {
685 return STATUS_UNSUCCESSFUL;
687 /* Fire and forget. */
688 KeSetEvent(&bus->ThreadSignal, 0, FALSE);
689 return STATUS_SUCCESS;