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 WV_F_BUS_THREAD WvBusDefaultThread_;
64 static winvblock__bool WvBusAddWorkItem_(
68 static WV_SP_BUS_WORK_ITEM_ WvBusGetWorkItem_(WV_SP_BUS_T);
71 struct device__irp_mj bus__irp_mj_ = {
75 (device__scsi_func *) 0,
80 * Add a child node to the bus.
82 * @v bus_ptr Points to the bus receiving the child.
83 * @v dev_ptr Points to the child device to add.
84 * @ret TRUE for success, FALSE for failure.
86 winvblock__lib_func winvblock__bool STDCALL WvBusAddChild(
87 IN OUT WV_SP_BUS_T bus_ptr,
88 IN OUT struct device__type * dev_ptr
90 /* The new node's device object. */
91 PDEVICE_OBJECT dev_obj_ptr;
92 /* Walks the child nodes. */
93 struct device__type * walker;
94 winvblock__uint32 dev_num;
97 if ((bus_ptr == NULL) || (dev_ptr == NULL)) {
98 DBG("No bus or no device!\n");
101 /* Create the child device. */
102 dev_obj_ptr = device__create_pdo(dev_ptr);
103 if (dev_obj_ptr == NULL) {
104 DBG("PDO creation failed!\n");
105 device__free(dev_ptr);
109 dev_ptr->Parent = bus_ptr->Dev->Self;
111 * Initialize the device. For disks, this routine is responsible for
112 * determining the disk's geometry appropriately for AoE/RAM/file disks.
114 dev_ptr->ops.init(dev_ptr);
115 dev_obj_ptr->Flags &= ~DO_DEVICE_INITIALIZING;
116 /* Add the new device's extension to the bus' list of children. */
118 if (bus_ptr->first_child == NULL) {
119 bus_ptr->first_child = dev_ptr;
121 walker = bus_ptr->first_child;
122 /* If the first child device number isn't 0... */
123 if (walker->dev_num) {
124 /* We insert before. */
125 dev_ptr->next_sibling_ptr = walker;
126 bus_ptr->first_child = dev_ptr;
128 while (walker->next_sibling_ptr != NULL) {
129 /* If there's a gap in the device numbers for the bus... */
130 if (walker->dev_num < walker->next_sibling_ptr->dev_num - 1) {
131 /* Insert here, instead of at the end. */
132 dev_num = walker->dev_num + 1;
133 dev_ptr->next_sibling_ptr = walker->next_sibling_ptr;
134 walker->next_sibling_ptr = dev_ptr;
137 walker = walker->next_sibling_ptr;
138 dev_num = walker->dev_num + 1;
140 /* If we haven't already inserted the device... */
141 if (!dev_ptr->next_sibling_ptr) {
142 walker->next_sibling_ptr = dev_ptr;
143 dev_num = walker->dev_num + 1;
147 dev_ptr->dev_num = dev_num;
149 if (bus_ptr->PhysicalDeviceObject != NULL) {
150 IoInvalidateDeviceRelations(
151 bus_ptr->PhysicalDeviceObject,
159 static NTSTATUS STDCALL WvBusSysCtl_(
160 IN struct device__type * dev,
163 WV_SP_BUS_T bus = WvBusFromDev(dev);
164 PDEVICE_OBJECT lower = bus->LowerDeviceObject;
167 DBG("Passing IRP_MJ_SYSTEM_CONTROL down\n");
168 IoSkipCurrentIrpStackLocation(irp);
169 return IoCallDriver(lower, irp);
171 return driver__complete_irp(irp, 0, STATUS_SUCCESS);
174 static NTSTATUS STDCALL WvBusPower_(
175 IN struct device__type * dev,
178 WV_SP_BUS_T bus = WvBusFromDev(dev);
179 PDEVICE_OBJECT lower = bus->LowerDeviceObject;
181 PoStartNextPowerIrp(irp);
183 IoSkipCurrentIrpStackLocation(irp);
184 return PoCallDriver(lower, irp);
186 return driver__complete_irp(irp, 0, STATUS_SUCCESS);
189 NTSTATUS STDCALL WvBusGetDevCapabilities(
190 IN PDEVICE_OBJECT DeviceObject,
191 IN PDEVICE_CAPABILITIES DeviceCapabilities
193 IO_STATUS_BLOCK ioStatus;
196 PDEVICE_OBJECT targetObject;
197 PIO_STACK_LOCATION irpStack;
200 RtlZeroMemory(DeviceCapabilities, sizeof (DEVICE_CAPABILITIES));
201 DeviceCapabilities->Size = sizeof (DEVICE_CAPABILITIES);
202 DeviceCapabilities->Version = 1;
203 DeviceCapabilities->Address = -1;
204 DeviceCapabilities->UINumber = -1;
206 KeInitializeEvent(&pnpEvent, NotificationEvent, FALSE);
207 targetObject = IoGetAttachedDeviceReference(DeviceObject);
208 pnpIrp = IoBuildSynchronousFsdRequest(
217 if (pnpIrp == NULL) {
218 status = STATUS_INSUFFICIENT_RESOURCES;
220 pnpIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
221 irpStack = IoGetNextIrpStackLocation(pnpIrp);
222 RtlZeroMemory(irpStack, sizeof (IO_STACK_LOCATION));
223 irpStack->MajorFunction = IRP_MJ_PNP;
224 irpStack->MinorFunction = IRP_MN_QUERY_CAPABILITIES;
225 irpStack->Parameters.DeviceCapabilities.Capabilities =
227 status = IoCallDriver(targetObject, pnpIrp);
228 if (status == STATUS_PENDING) {
229 KeWaitForSingleObject(
236 status = ioStatus.Status;
239 ObDereferenceObject(targetObject);
243 /* Initialize a bus. */
244 static winvblock__bool STDCALL bus__init_(IN struct device__type * dev) {
249 * Initialize bus defaults.
251 * @v bus Points to the bus to initialize with defaults.
253 winvblock__lib_func void WvBusInit(WV_SP_BUS_T bus) {
254 struct device__type * dev = bus->Dev;
256 RtlZeroMemory(bus, sizeof *bus);
257 /* Populate non-zero bus device defaults. */
259 bus->BusPrivate_.PrevFree = dev->ops.free;
260 bus->Thread = WvBusDefaultThread_;
261 KeInitializeSpinLock(&bus->BusPrivate_.WorkItemsLock);
262 InitializeListHead(&bus->BusPrivate_.WorkItems);
263 KeInitializeEvent(&bus->ThreadSignal, SynchronizationEvent, FALSE);
264 dev->ops.create_pdo = WvBusCreatePdo_;
265 dev->ops.init = bus__init_;
266 dev->ops.free = WvBusFree_;
268 dev->irp_mj = &bus__irp_mj_;
275 * @ret bus_ptr The address of a new bus, or NULL for failure.
277 * This function should not be confused with a PDO creation routine, which is
278 * actually implemented for each device type. This routine will allocate a
279 * WV_S_BUS_T, track it in a global list, as well as populate the bus
280 * with default values.
282 winvblock__lib_func WV_SP_BUS_T WvBusCreate(void) {
283 struct device__type * dev;
286 /* Try to create a device. */
287 dev = device__create();
291 * Bus devices might be used for booting and should
292 * not be allocated from a paged memory pool.
294 bus = wv_malloc(sizeof *bus);
313 * @v dev Populate PDO dev. ext. space from these details.
314 * @ret pdo Points to the new PDO, or is NULL upon failure.
316 * Returns a Physical Device Object pointer on success, NULL for failure.
318 static PDEVICE_OBJECT STDCALL WvBusCreatePdo_(IN struct device__type * dev) {
319 PDEVICE_OBJECT pdo = NULL;
323 /* Note the bus device needing a PDO. */
325 DBG("No device passed\n");
328 bus = WvBusFromDev(dev);
329 /* Create the PDO. */
330 status = IoCreateDevice(
332 sizeof (driver__dev_ext),
334 FILE_DEVICE_CONTROLLER,
335 FILE_DEVICE_SECURE_OPEN,
340 DBG("IoCreateDevice() failed!\n");
344 /* Set associations for the bus, device, PDO. */
345 device__set(pdo, dev);
346 dev->Self = bus->PhysicalDeviceObject = pdo;
348 /* Set some DEVICE_OBJECT status. */
349 pdo->Flags |= DO_DIRECT_IO; /* FIXME? */
350 pdo->Flags |= DO_POWER_INRUSH; /* FIXME? */
351 pdo->Flags &= ~DO_DEVICE_INITIALIZING;
353 dev->State = Started;
365 * Default bus deletion operation.
367 * @v dev_ptr Points to the bus device to delete.
369 static void STDCALL WvBusFree_(IN struct device__type * dev_ptr) {
370 WV_SP_BUS_T bus_ptr = WvBusFromDev(dev_ptr);
371 /* Free the "inherited class". */
372 bus_ptr->BusPrivate_.PrevFree(dev_ptr);
378 * Get a bus from a device.
380 * @v dev A pointer to a device.
381 * @ret A pointer to the device's associated bus.
383 extern winvblock__lib_func WV_SP_BUS_T WvBusFromDev(
384 struct device__type * dev
390 * Add a work item for a bus to process.
392 * @v bus The bus to process the work item.
393 * @v work_item The work item to add.
394 * @ret winvblock__bool TRUE if added, else FALSE
396 * Note that this function will initialize the work item's completion signal.
398 static winvblock__bool WvBusAddWorkItem_(
400 WV_SP_BUS_WORK_ITEM_ work_item
402 ExInterlockedInsertTailList(
403 &bus->BusPrivate_.WorkItems,
405 &bus->BusPrivate_.WorkItemsLock
412 * Get (and dequeue) a work item from a bus' queue.
414 * @v bus The bus processing the work item.
415 * @ret bus__work_item_ The work item, or NULL for an empty queue.
417 static WV_SP_BUS_WORK_ITEM_ WvBusGetWorkItem_(
420 PLIST_ENTRY list_entry;
422 list_entry = ExInterlockedRemoveHeadList(
423 &bus->BusPrivate_.WorkItems,
424 &bus->BusPrivate_.WorkItemsLock
429 return CONTAINING_RECORD(list_entry, WV_S_BUS_WORK_ITEM_, Link);
433 * Process work items for a bus.
435 * @v bus The bus to process its work items.
437 winvblock__lib_func void WvBusProcessWorkItems(WV_SP_BUS_T bus) {
438 WV_SP_BUS_WORK_ITEM_ work_item;
441 while (work_item = WvBusGetWorkItem_(bus)) {
442 switch (work_item->Cmd) {
443 case WvBusWorkItemCmdAddPdo_:
444 DBG("Adding PDO to bus...\n");
446 node = work_item->Context.Node;
447 /* It's too bad about having both linked list and bus ref. */
448 node->BusPrivate_.Bus = bus;
449 ObReferenceObject(node->BusPrivate_.Pdo);
450 InsertTailList(&bus->BusPrivate_.Nodes, &node->BusPrivate_.Link);
451 bus->BusPrivate_.NodeCount++;
454 case WvBusWorkItemCmdRemovePdo_:
455 DBG("Removing PDO from bus...\n");
457 node = work_item->Context.Node;
458 RemoveEntryList(&node->BusPrivate_.Link);
459 ObDereferenceObject(node->BusPrivate_.Pdo);
460 bus->BusPrivate_.NodeCount--;
464 DBG("Unknown work item type!\n");
472 * Cancel pending work items for a bus.
474 * @v bus The bus to cancel pending work items for.
476 winvblock__lib_func void WvBusCancelWorkItems(WV_SP_BUS_T bus) {
477 WV_SP_BUS_WORK_ITEM_ work_item;
479 DBG("Canceling work items.\n");
480 while (work_item = WvBusGetWorkItem_(bus))
485 /* The device__type::ops.free implementation for a threaded bus. */
486 static void STDCALL bus__thread_free_(IN struct device__type * dev) {
487 WV_SP_BUS_T bus = WvBusFromDev(dev);
490 KeSetEvent(&bus->ThreadSignal, 0, FALSE);
495 * The bus thread wrapper.
497 * @v context The thread context. In our case, it points to
498 * the bus that the thread should use in processing.
500 static void STDCALL bus__thread_(IN void * context) {
501 WV_SP_BUS_T bus = context;
503 if (!bus || !bus->Thread) {
504 DBG("No bus or no thread!\n");
513 * The default bus thread routine.
515 * @v bus Points to the bus device for the thread to work with.
517 * Note that if you implement your own bus type using this library,
518 * you can override the thread routine with your own. If you do so,
519 * your thread routine should call WvBusProcessWorkItems() within
520 * its loop. To start a bus thread, use WvBusStartThread()
521 * If you implement your own thread routine, you are also responsible
522 * for calling WvBusCancelWorkItems() and freeing the bus.
524 static void STDCALL WvBusDefaultThread_(IN WV_SP_BUS_T bus) {
525 LARGE_INTEGER timeout;
527 /* Wake up at least every 30 seconds. */
528 timeout.QuadPart = -300000000LL;
530 /* Hook device__type::ops.free() */
531 bus->Dev->ops.free = bus__thread_free_;
533 /* When bus::Stop is set, we shut down. */
537 /* Wait for the work signal or the timeout. */
538 KeWaitForSingleObject(
545 /* Reset the work signal. */
546 KeResetEvent(&bus->ThreadSignal);
548 WvBusProcessWorkItems(bus);
549 } /* while bus->alive */
551 WvBusCancelWorkItems(bus);
552 WvBusFree_(bus->Dev);
557 * Start a bus thread.
559 * @v bus The bus to start a thread for.
560 * @ret NTSTATUS The status of the thread creation operation.
562 * Also see WV_F_BUS_THREAD in the header for details about the prototype
563 * for implementing your own bus thread routine. You set bus::Thread to
564 * specify your own thread routine, then call this function to start it.
566 winvblock__lib_func NTSTATUS WvBusStartThread(
569 OBJECT_ATTRIBUTES obj_attrs;
570 HANDLE thread_handle;
573 DBG("No bus specified!\n");
574 return STATUS_INVALID_PARAMETER;
577 InitializeObjectAttributes(
584 return PsCreateSystemThread(
596 * Initialize a bus node with an associated PDO.
598 * @v Node The node to initialize.
599 * @v Pdo The PDO to associate the node with.
600 * @ret winvblock__bool FALSE for a NULL argument, otherwise TRUE
602 winvblock__lib_func winvblock__bool STDCALL WvBusInitNode(
603 OUT WV_SP_BUS_NODE Node,
604 IN PDEVICE_OBJECT Pdo
609 RtlZeroMemory(Node, sizeof *Node);
610 Node->BusPrivate_.Pdo = Pdo;
615 * Add a PDO node to a bus' list of children.
617 * @v Bus The bus to add the node to.
618 * @v Node The PDO node to add to the bus.
619 * @ret NTSTATUS The status of the operation.
621 * Do not attempt to add the same node to more than one bus.
622 * When WvBusProcessWorkItems() is called for the bus, the
623 * node will be added. This is usually from the bus' thread.
625 winvblock__lib_func NTSTATUS STDCALL WvBusAddNode(
629 WV_SP_BUS_WORK_ITEM_ work_item;
634 Bus->Dev->Self->DriverObject != Node->BusPrivate_.Pdo->DriverObject
636 return STATUS_INVALID_PARAMETER;
639 return STATUS_NO_SUCH_DEVICE;
641 if (!(work_item = wv_malloc(sizeof *work_item)))
642 return STATUS_INSUFFICIENT_RESOURCES;
644 work_item->Cmd = WvBusWorkItemCmdAddPdo_;
645 work_item->Context.Node = Node;
646 if (!WvBusAddWorkItem_(Bus, work_item)) {
648 return STATUS_UNSUCCESSFUL;
650 /* Fire and forget. */
651 KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
652 return STATUS_SUCCESS;
656 * Remove a PDO node from a bus.
658 * @v Node The PDO node to remove from its parent bus.
659 * @ret NTSTATUS The status of the operation.
661 * When WvBusProcessWorkItems() is called for the bus, it will
662 * then remove the node. This is usually from the bus' thread.
664 winvblock__lib_func NTSTATUS STDCALL WvBusRemoveNode(
668 WV_SP_BUS_WORK_ITEM_ work_item;
670 if (!Node || !(bus = Node->BusPrivate_.Bus))
671 return STATUS_INVALID_PARAMETER;
674 return STATUS_NO_SUCH_DEVICE;
676 if (!(work_item = wv_malloc(sizeof *work_item)))
677 return STATUS_INSUFFICIENT_RESOURCES;
679 work_item->Cmd = WvBusWorkItemCmdRemovePdo_;
680 work_item->Context.Node = Node;
681 if (!WvBusAddWorkItem_(bus, work_item)) {
683 return STATUS_UNSUCCESSFUL;
685 /* Fire and forget. */
686 KeSetEvent(&bus->ThreadSignal, 0, FALSE);
687 return STATUS_SUCCESS;