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 WvBusDevCtlDispatch;
40 /* IRP_MJ_PNP dispatcher from bus/pnp.c */
41 extern device__pnp_func WvBusPnpDispatch;
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 WvBusFree_;
60 static device__create_pdo_func WvBusCreatePdo_;
61 static device__dispatch_func WvBusPower_;
62 static device__dispatch_func WvBusSysCtl_;
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_ = {
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 WvBusSysCtl_(
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 WvBusPower_(
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->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_;
269 dev->irp_mj = &bus__irp_mj_;
276 * @ret bus_ptr The address of a new bus, or NULL for failure.
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.
283 winvblock__lib_func WV_SP_BUS_T WvBusCreate(void) {
284 struct device__type * dev;
287 /* Try to create a device. */
288 dev = device__create();
292 * Bus devices might be used for booting and should
293 * not be allocated from a paged memory pool.
295 bus = wv_malloc(sizeof *bus);
314 * @v dev Populate PDO dev. ext. space from these details.
315 * @ret pdo Points to the new PDO, or is NULL upon failure.
317 * Returns a Physical Device Object pointer on success, NULL for failure.
319 static PDEVICE_OBJECT STDCALL WvBusCreatePdo_(IN struct device__type * dev) {
320 PDEVICE_OBJECT pdo = NULL;
324 /* Note the bus device needing a PDO. */
326 DBG("No device passed\n");
329 bus = WvBusFromDev(dev);
330 /* Create the PDO. */
331 status = IoCreateDevice(
333 sizeof (driver__dev_ext),
335 FILE_DEVICE_CONTROLLER,
336 FILE_DEVICE_SECURE_OPEN,
341 DBG("IoCreateDevice() failed!\n");
345 /* Set associations for the bus, device, PDO. */
346 device__set(pdo, dev);
347 dev->Self = bus->PhysicalDeviceObject = pdo;
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;
354 dev->State = Started;
366 * Default bus deletion operation.
368 * @v dev_ptr Points to the bus device to delete.
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);
379 * Get a bus from a device.
381 * @v dev A pointer to a device.
382 * @ret A pointer to the device's associated bus.
384 extern winvblock__lib_func WV_SP_BUS_T WvBusFromDev(
385 struct device__type * dev
391 * Add a work item for a bus to process.
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
397 * Note that this function will initialize the work item's completion signal.
399 static winvblock__bool bus__add_work_item_(
401 WV_SP_BUS_WORK_ITEM_ work_item
403 ExInterlockedInsertTailList(
404 &bus->BusPrivate_.WorkItems,
406 &bus->BusPrivate_.WorkItemsLock
413 * Get (and dequeue) a work item from a bus' queue.
415 * @v bus The bus processing the work item.
416 * @ret bus__work_item_ The work item, or NULL for an empty queue.
418 static WV_SP_BUS_WORK_ITEM_ bus__get_work_item_(
421 PLIST_ENTRY list_entry;
423 list_entry = ExInterlockedRemoveHeadList(
424 &bus->BusPrivate_.WorkItems,
425 &bus->BusPrivate_.WorkItemsLock
430 return CONTAINING_RECORD(list_entry, WV_S_BUS_WORK_ITEM_, Link);
434 * Process work items for a bus.
436 * @v bus The bus to process its work items.
438 winvblock__lib_func void WvBusProcessWorkItems(WV_SP_BUS_T bus) {
439 WV_SP_BUS_WORK_ITEM_ work_item;
442 while (work_item = bus__get_work_item_(bus)) {
443 switch (work_item->Cmd) {
444 case WvBusWorkItemCmdAddPdo_:
445 DBG("Adding PDO to bus...\n");
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++;
455 case WvBusWorkItemCmdRemovePdo_:
456 DBG("Removing PDO from bus...\n");
458 node = work_item->Context.Node;
459 RemoveEntryList(&node->BusPrivate_.Link);
460 ObDereferenceObject(node->BusPrivate_.Pdo);
461 bus->BusPrivate_.NodeCount--;
465 DBG("Unknown work item type!\n");
473 * Cancel pending work items for a bus.
475 * @v bus The bus to cancel pending work items for.
477 winvblock__lib_func void WvBusCancelWorkItems(WV_SP_BUS_T bus) {
478 WV_SP_BUS_WORK_ITEM_ work_item;
480 DBG("Canceling work items.\n");
481 while (work_item = bus__get_work_item_(bus))
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);
491 KeSetEvent(&bus->ThreadSignal, 0, FALSE);
496 * The bus thread wrapper.
498 * @v context The thread context. In our case, it points to
499 * the bus that the thread should use in processing.
501 static void STDCALL bus__thread_(IN void * context) {
502 WV_SP_BUS_T bus = context;
504 if (!bus || !bus->Thread) {
505 DBG("No bus or no thread!\n");
514 * The default bus thread routine.
516 * @v bus Points to the bus device for the thread to work with.
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.
525 static void STDCALL bus__default_thread_(IN WV_SP_BUS_T bus) {
526 LARGE_INTEGER timeout;
528 /* Wake up at least every 30 seconds. */
529 timeout.QuadPart = -300000000LL;
531 /* Hook device__type::ops.free() */
532 bus->Dev->ops.free = bus__thread_free_;
534 /* When bus::Stop is set, we shut down. */
538 /* Wait for the work signal or the timeout. */
539 KeWaitForSingleObject(
546 /* Reset the work signal. */
547 KeResetEvent(&bus->ThreadSignal);
549 WvBusProcessWorkItems(bus);
550 } /* while bus->alive */
552 WvBusCancelWorkItems(bus);
553 WvBusFree_(bus->Dev);
558 * Start a bus thread.
560 * @v bus The bus to start a thread for.
561 * @ret NTSTATUS The status of the thread creation operation.
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.
567 winvblock__lib_func NTSTATUS WvBusStartThread(
570 OBJECT_ATTRIBUTES obj_attrs;
571 HANDLE thread_handle;
574 DBG("No bus specified!\n");
575 return STATUS_INVALID_PARAMETER;
578 InitializeObjectAttributes(
585 return PsCreateSystemThread(
597 * Initialize a bus node with an associated PDO.
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
603 winvblock__lib_func winvblock__bool STDCALL WvBusInitNode(
604 OUT WV_SP_BUS_NODE Node,
605 IN PDEVICE_OBJECT Pdo
610 RtlZeroMemory(Node, sizeof *Node);
611 Node->BusPrivate_.Pdo = Pdo;
616 * Add a PDO node to a bus' list of children.
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.
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.
626 winvblock__lib_func NTSTATUS STDCALL WvBusAddNode(
630 WV_SP_BUS_WORK_ITEM_ work_item;
635 Bus->Dev->Self->DriverObject != Node->BusPrivate_.Pdo->DriverObject
637 return STATUS_INVALID_PARAMETER;
640 return STATUS_NO_SUCH_DEVICE;
642 if (!(work_item = wv_malloc(sizeof *work_item)))
643 return STATUS_INSUFFICIENT_RESOURCES;
645 work_item->Cmd = WvBusWorkItemCmdAddPdo_;
646 work_item->Context.Node = Node;
647 if (!bus__add_work_item_(Bus, work_item)) {
649 return STATUS_UNSUCCESSFUL;
651 /* Fire and forget. */
652 KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
653 return STATUS_SUCCESS;
657 * Remove a PDO node from a bus.
659 * @v Node The PDO node to remove from its parent bus.
660 * @ret NTSTATUS The status of the operation.
662 * When WvBusProcessWorkItems() is called for the bus, it will
663 * then remove the node. This is usually from the bus' thread.
665 winvblock__lib_func NTSTATUS STDCALL WvBusRemoveNode(
669 WV_SP_BUS_WORK_ITEM_ work_item;
671 if (!Node || !(bus = Node->BusPrivate_.Bus))
672 return STATUS_INVALID_PARAMETER;
675 return STATUS_NO_SUCH_DEVICE;
677 if (!(work_item = wv_malloc(sizeof *work_item)))
678 return STATUS_INSUFFICIENT_RESOURCES;
680 work_item->Cmd = WvBusWorkItemCmdRemovePdo_;
681 work_item->Context.Node = Node;
682 if (!bus__add_work_item_(bus, work_item)) {
684 return STATUS_UNSUCCESSFUL;
686 /* Fire and forget. */
687 KeSetEvent(&bus->ThreadSignal, 0, FALSE);
688 return STATUS_SUCCESS;