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 WV_F_DEV_CTL WvBusDevCtlDispatch;
40 /* IRP_MJ_PNP dispatcher from bus/pnp.c */
41 extern WV_F_DEV_PNP WvBusPnpDispatch;
44 typedef enum WV_BUS_WORK_ITEM_CMD_ {
45 WvBusWorkItemCmdAddPdo_,
46 WvBusWorkItemCmdRemovePdo_,
47 WvBusWorkItemCmdProcessIrp_,
49 } WV_E_BUS_WORK_ITEM_CMD_, * WV_EP_BUS_WORK_ITEM_CMD_;
51 typedef struct WV_BUS_WORK_ITEM_ {
53 WV_E_BUS_WORK_ITEM_CMD_ Cmd;
58 } WV_S_BUS_WORK_ITEM_, * WV_SP_BUS_WORK_ITEM_;
60 /* Forward declarations. */
61 static WV_F_DEV_FREE WvBusFree_;
62 static WV_F_DEV_CREATE_PDO WvBusCreatePdo_;
63 static WV_F_DEV_DISPATCH WvBusPower_;
64 static WV_F_BUS_THREAD WvBusDefaultThread_;
65 static winvblock__bool WvBusAddWorkItem_(
69 static WV_SP_BUS_WORK_ITEM_ WvBusGetWorkItem_(WV_SP_BUS_T);
72 WV_S_DEV_IRP_MJ WvBusIrpMj_ = {
74 (WV_FP_DEV_DISPATCH) 0,
81 * Add a child node to the bus.
83 * @v Bus Points to the bus receiving the child.
84 * @v Dev 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,
89 IN OUT WV_SP_DEV_T Dev
91 /* The new node's device object. */
92 PDEVICE_OBJECT dev_obj;
93 /* Walks the child nodes. */
95 winvblock__uint32 dev_num;
98 if ((Bus == NULL) || (Dev == NULL)) {
99 DBG("No bus or no device!\n");
102 /* Create the child device. */
103 dev_obj = WvDevCreatePdo(Dev);
104 if (dev_obj == NULL) {
105 DBG("PDO creation failed!\n");
110 Dev->Parent = Bus->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.
116 dev_obj->Flags &= ~DO_DEVICE_INITIALIZING;
117 /* Add the new device's extension to the bus' list of children. */
119 if (Bus->first_child == NULL) {
120 Bus->first_child = Dev;
122 walker = Bus->first_child;
123 /* If the first child device number isn't 0... */
124 if (walker->DevNum) {
125 /* We insert before. */
126 Dev->next_sibling_ptr = walker;
127 Bus->first_child = Dev;
129 while (walker->next_sibling_ptr != NULL) {
130 /* If there's a gap in the device numbers for the bus... */
131 if (walker->DevNum < walker->next_sibling_ptr->DevNum - 1) {
132 /* Insert here, instead of at the end. */
133 dev_num = walker->DevNum + 1;
134 Dev->next_sibling_ptr = walker->next_sibling_ptr;
135 walker->next_sibling_ptr = Dev;
138 walker = walker->next_sibling_ptr;
139 dev_num = walker->DevNum + 1;
141 /* If we haven't already inserted the device... */
142 if (!Dev->next_sibling_ptr) {
143 walker->next_sibling_ptr = Dev;
144 dev_num = walker->DevNum + 1;
148 Dev->DevNum = dev_num;
150 if (Bus->PhysicalDeviceObject != NULL) {
151 IoInvalidateDeviceRelations(
152 Bus->PhysicalDeviceObject,
160 /* Handle an IRP_MJ_SYSTEM_CONTROL IRP. */
161 winvblock__lib_func NTSTATUS STDCALL WvBusSysCtl(
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 /* Handle a power IRP. */
176 static NTSTATUS STDCALL WvBusPower_(
180 WV_SP_BUS_T bus = WvBusFromDev(dev);
181 PDEVICE_OBJECT lower = bus->LowerDeviceObject;
183 PoStartNextPowerIrp(irp);
185 IoSkipCurrentIrpStackLocation(irp);
186 return PoCallDriver(lower, irp);
188 return driver__complete_irp(irp, 0, STATUS_SUCCESS);
191 NTSTATUS STDCALL WvBusGetDevCapabilities(
192 IN PDEVICE_OBJECT DevObj,
193 IN PDEVICE_CAPABILITIES DevCapabilities
195 IO_STATUS_BLOCK io_status;
198 PDEVICE_OBJECT target_obj;
199 PIO_STACK_LOCATION io_stack_loc;
202 RtlZeroMemory(DevCapabilities, sizeof *DevCapabilities);
203 DevCapabilities->Size = sizeof *DevCapabilities;
204 DevCapabilities->Version = 1;
205 DevCapabilities->Address = -1;
206 DevCapabilities->UINumber = -1;
208 KeInitializeEvent(&pnp_event, NotificationEvent, FALSE);
209 target_obj = IoGetAttachedDeviceReference(DevObj);
210 pnp_irp = IoBuildSynchronousFsdRequest(
219 if (pnp_irp == NULL) {
220 status = STATUS_INSUFFICIENT_RESOURCES;
222 pnp_irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
223 io_stack_loc = IoGetNextIrpStackLocation(pnp_irp);
224 RtlZeroMemory(io_stack_loc, sizeof *io_stack_loc);
225 io_stack_loc->MajorFunction = IRP_MJ_PNP;
226 io_stack_loc->MinorFunction = IRP_MN_QUERY_CAPABILITIES;
227 io_stack_loc->Parameters.DeviceCapabilities.Capabilities =
229 status = IoCallDriver(target_obj, pnp_irp);
230 if (status == STATUS_PENDING) {
231 KeWaitForSingleObject(
238 status = io_status.Status;
241 ObDereferenceObject(target_obj);
245 /* Initialize a bus. */
246 static winvblock__bool STDCALL WvBusDevInit_(IN WV_SP_DEV_T dev) {
251 * Initialize bus defaults.
253 * @v Bus Points to the bus to initialize with defaults.
255 winvblock__lib_func void WvBusInit(WV_SP_BUS_T Bus) {
256 RtlZeroMemory(Bus, sizeof *Bus);
257 /* Populate non-zero bus device defaults. */
258 WvDevInit(&Bus->Dev);
259 Bus->Thread = WvBusDefaultThread_;
260 KeInitializeSpinLock(&Bus->BusPrivate_.WorkItemsLock);
261 InitializeListHead(&Bus->BusPrivate_.WorkItems);
262 KeInitializeEvent(&Bus->ThreadSignal, SynchronizationEvent, FALSE);
263 KeInitializeEvent(&Bus->ThreadStopped, SynchronizationEvent, FALSE);
264 Bus->Dev.Ops.CreatePdo = WvBusCreatePdo_;
265 Bus->Dev.Ops.Init = WvBusDevInit_;
266 Bus->Dev.Ops.Free = WvBusFree_;
268 Bus->Dev.IrpMj = &WvBusIrpMj_;
269 Bus->Dev.IsBus = TRUE;
275 * @ret WV_SP_BUS_T 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 as well as populate the bus with default values.
281 winvblock__lib_func WV_SP_BUS_T WvBusCreate(void) {
285 * Bus devices might be used for booting and should
286 * not be allocated from a paged memory pool.
288 bus = wv_malloc(sizeof *bus);
304 * @v dev Populate PDO dev. ext. space from these details.
305 * @ret PDEVICE_OBJECT Points to the new PDO, or is NULL upon failure.
307 * Returns a Physical Device Object pointer on success, NULL for failure.
309 static PDEVICE_OBJECT STDCALL WvBusCreatePdo_(IN WV_SP_DEV_T dev) {
310 PDEVICE_OBJECT pdo = NULL;
314 /* Note the bus device needing a PDO. */
316 DBG("No device passed\n");
319 bus = WvBusFromDev(dev);
320 /* Create the PDO. */
321 status = IoCreateDevice(
323 sizeof (driver__dev_ext),
325 FILE_DEVICE_CONTROLLER,
326 FILE_DEVICE_SECURE_OPEN,
331 DBG("IoCreateDevice() failed!\n");
335 /* Set associations for the bus, device, PDO. */
336 WvDevForDevObj(pdo, dev);
337 dev->Self = bus->PhysicalDeviceObject = pdo;
339 /* Set some DEVICE_OBJECT status. */
340 pdo->Flags |= DO_DIRECT_IO; /* FIXME? */
341 pdo->Flags |= DO_POWER_INRUSH; /* FIXME? */
342 pdo->Flags &= ~DO_DEVICE_INITIALIZING;
344 dev->State = Started;
356 * Default bus deletion operation.
358 * @v dev Points to the bus device to delete.
360 static void STDCALL WvBusFree_(IN WV_SP_DEV_T dev) {
361 WV_SP_BUS_T bus = WvBusFromDev(dev);
367 * Get a bus from a device.
369 * @v dev A pointer to a device.
370 * @ret A pointer to the device's associated bus.
372 extern winvblock__lib_func WV_SP_BUS_T WvBusFromDev(WV_SP_DEV_T Dev) {
377 * Add a work item for a bus to process.
379 * @v bus The bus to process the work item.
380 * @v work_item The work item to add.
381 * @ret winvblock__bool TRUE if added, else FALSE
383 * Note that this function will initialize the work item's completion signal.
385 static winvblock__bool WvBusAddWorkItem_(
387 WV_SP_BUS_WORK_ITEM_ work_item
389 ExInterlockedInsertTailList(
390 &bus->BusPrivate_.WorkItems,
392 &bus->BusPrivate_.WorkItemsLock
399 * Get (and dequeue) a work item from a bus' queue.
401 * @v bus The bus processing the work item.
402 * @ret WV_SP_BUS_WORK_ITEM_ The work item, or NULL for an empty queue.
404 static WV_SP_BUS_WORK_ITEM_ WvBusGetWorkItem_(
407 PLIST_ENTRY list_entry;
409 list_entry = ExInterlockedRemoveHeadList(
410 &bus->BusPrivate_.WorkItems,
411 &bus->BusPrivate_.WorkItemsLock
416 return CONTAINING_RECORD(list_entry, WV_S_BUS_WORK_ITEM_, Link);
420 * Add a PDO node to a bus' list of children. Internal.
422 * @v bus The bus to add the node to.
423 * @v new_node The PDO node to add to the bus.
425 * Don't call this function yourself. It expects to have exclusive
426 * access to the bus' list of children.
428 static void STDCALL WvBusAddNode_(WV_SP_BUS_T bus, WV_SP_BUS_NODE new_node) {
431 DBG("Adding PDO to bus...\n");
432 ObReferenceObject(new_node->BusPrivate_.Pdo);
433 bus->BusPrivate_.NodeCount++;
434 /* It's too bad about having both linked list and bus ref. */
435 new_node->BusPrivate_.Bus = bus;
437 /* Find a slot for the new child. */
438 walker = &bus->BusPrivate_.Nodes;
439 new_node->BusPrivate_.Num = 0;
440 while ((walker = walker->Flink) != &bus->BusPrivate_.Nodes) {
441 WV_SP_BUS_NODE node = CONTAINING_RECORD(
448 node->BusPrivate_.Num &&
449 (node->BusPrivate_.Link.Blink == &bus->BusPrivate_.Nodes)
451 /* The first node's unit number is != 0. Insert here. */
454 if (node->BusPrivate_.Num > new_node->BusPrivate_.Num) {
455 /* There is a gap so insert here. */
458 /* Continue trying to find a slot. */
459 new_node->BusPrivate_.Num++;
461 /* Insert before walker. */
462 InsertTailList(walker, &new_node->BusPrivate_.Link);
467 * Process work items for a bus.
469 * @v Bus The bus to process its work items.
471 winvblock__lib_func void WvBusProcessWorkItems(WV_SP_BUS_T Bus) {
472 WV_SP_BUS_WORK_ITEM_ work_item;
475 PIO_STACK_LOCATION io_stack_loc;
476 PDEVICE_OBJECT dev_obj;
477 PDRIVER_OBJECT driver_obj;
479 while (work_item = WvBusGetWorkItem_(Bus)) {
480 switch (work_item->Cmd) {
481 case WvBusWorkItemCmdAddPdo_:
482 node = work_item->Context.Node;
483 WvBusAddNode_(Bus, node);
486 case WvBusWorkItemCmdRemovePdo_:
487 DBG("Removing PDO from bus...\n");
489 node = work_item->Context.Node;
490 RemoveEntryList(&node->BusPrivate_.Link);
491 ObDereferenceObject(node->BusPrivate_.Pdo);
492 Bus->BusPrivate_.NodeCount--;
495 case WvBusWorkItemCmdProcessIrp_:
496 irp = work_item->Context.Irp;
497 io_stack_loc = IoGetCurrentIrpStackLocation(irp);
498 dev_obj = Bus->Dev.Self;
499 driver_obj = dev_obj->DriverObject;
500 driver_obj->MajorFunction[io_stack_loc->MajorFunction](
507 DBG("Unknown work item type!\n");
515 * Cancel pending work items for a bus.
517 * @v Bus The bus to cancel pending work items for.
519 winvblock__lib_func void WvBusCancelWorkItems(WV_SP_BUS_T Bus) {
520 WV_SP_BUS_WORK_ITEM_ work_item;
522 DBG("Canceling work items.\n");
523 while (work_item = WvBusGetWorkItem_(Bus))
528 /* The WV_S_DEV_T::Ops.Free implementation for a threaded bus. */
529 static void STDCALL WvBusThreadFree_(IN WV_SP_DEV_T dev) {
530 WV_SP_BUS_T bus = WvBusFromDev(dev);
533 KeSetEvent(&bus->ThreadSignal, 0, FALSE);
538 * The bus thread wrapper.
540 * @v context The thread context. In our case, it points to
541 * the bus that the thread should use in processing.
543 * Note that we do not attempt to free the bus data; this is a bus
544 * implementor's responsibility. We do, however, set the ThreadStopped
545 * signal which should mean that resources can be freed, from a completed
546 * thread's perspective.
548 static void STDCALL WvBusThread_(IN void * context) {
549 WV_SP_BUS_T bus = context;
551 if (!bus || !bus->Thread) {
552 DBG("No bus or no thread!\n");
557 KeSetEvent(&bus->ThreadStopped, 0, FALSE);
562 * The default bus thread routine.
564 * @v bus Points to the bus device for the thread to work with.
566 * Note that if you implement your own bus type using this library,
567 * you can override the thread routine with your own. If you do so,
568 * your thread routine should call WvBusProcessWorkItems() within
569 * its loop. To start a bus thread, use WvBusStartThread()
570 * If you implement your own thread routine, you are also responsible
571 * for calling WvBusCancelWorkItems() and freeing the bus.
573 static void STDCALL WvBusDefaultThread_(IN WV_SP_BUS_T bus) {
574 LARGE_INTEGER timeout;
576 /* Wake up at least every 30 seconds. */
577 timeout.QuadPart = -300000000LL;
579 /* Hook WV_S_DEV_T::Ops.Free() */
580 bus->Dev.Ops.Free = WvBusThreadFree_;
582 /* When WV_S_BUS_T::Stop is set, we shut down. */
586 /* Wait for the work signal or the timeout. */
587 KeWaitForSingleObject(
594 /* Reset the work signal. */
595 KeResetEvent(&bus->ThreadSignal);
597 WvBusProcessWorkItems(bus);
598 } /* while !bus->Stop */
600 WvBusCancelWorkItems(bus);
605 * Start a bus thread.
607 * @v Bus The bus to start a thread for.
608 * @ret NTSTATUS The status of the thread creation operation.
610 * Also see WV_F_BUS_THREAD in the header for details about the prototype
611 * for implementing your own bus thread routine. You set WV_S_BUS_T::Thread
612 * to specify your own thread routine, then call this function to start it.
614 winvblock__lib_func NTSTATUS WvBusStartThread(
617 OBJECT_ATTRIBUTES obj_attrs;
618 HANDLE thread_handle;
621 DBG("No bus specified!\n");
622 return STATUS_INVALID_PARAMETER;
625 InitializeObjectAttributes(
632 return PsCreateSystemThread(
644 * Initialize a bus node with an associated PDO.
646 * @v Node The node to initialize.
647 * @v Pdo The PDO to associate the node with.
648 * @ret winvblock__bool FALSE for a NULL argument, otherwise TRUE
650 winvblock__lib_func winvblock__bool STDCALL WvBusInitNode(
651 OUT WV_SP_BUS_NODE Node,
652 IN PDEVICE_OBJECT Pdo
657 RtlZeroMemory(Node, sizeof *Node);
658 Node->BusPrivate_.Pdo = Pdo;
663 * Add a PDO node to a bus' list of children.
665 * @v Bus The bus to add the node to.
666 * @v Node The PDO node to add to the bus.
667 * @ret NTSTATUS The status of the operation.
669 * Do not attempt to add the same node to more than one bus.
670 * When WvBusProcessWorkItems() is called for the bus, the
671 * node will be added. This is usually from the bus' thread.
673 winvblock__lib_func NTSTATUS STDCALL WvBusAddNode(
677 WV_SP_BUS_WORK_ITEM_ work_item;
682 Bus->Dev.Self->DriverObject != Node->BusPrivate_.Pdo->DriverObject
684 return STATUS_INVALID_PARAMETER;
687 return STATUS_NO_SUCH_DEVICE;
689 if (!(work_item = wv_malloc(sizeof *work_item)))
690 return STATUS_INSUFFICIENT_RESOURCES;
692 work_item->Cmd = WvBusWorkItemCmdAddPdo_;
693 work_item->Context.Node = Node;
694 if (!WvBusAddWorkItem_(Bus, work_item)) {
696 return STATUS_UNSUCCESSFUL;
698 /* Fire and forget. */
699 KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
700 return STATUS_SUCCESS;
704 * Remove a PDO node from a bus.
706 * @v Node The PDO node to remove from its parent bus.
707 * @ret NTSTATUS The status of the operation.
709 * When WvBusProcessWorkItems() is called for the bus, it will
710 * then remove the node. This is usually from the bus' thread.
712 winvblock__lib_func NTSTATUS STDCALL WvBusRemoveNode(
716 WV_SP_BUS_WORK_ITEM_ work_item;
718 if (!Node || !(bus = Node->BusPrivate_.Bus))
719 return STATUS_INVALID_PARAMETER;
722 return STATUS_NO_SUCH_DEVICE;
724 if (!(work_item = wv_malloc(sizeof *work_item)))
725 return STATUS_INSUFFICIENT_RESOURCES;
727 work_item->Cmd = WvBusWorkItemCmdRemovePdo_;
728 work_item->Context.Node = Node;
729 if (!WvBusAddWorkItem_(bus, work_item)) {
731 return STATUS_UNSUCCESSFUL;
733 /* Fire and forget. */
734 KeSetEvent(&bus->ThreadSignal, 0, FALSE);
735 return STATUS_SUCCESS;
739 * Enqueue an IRP for a bus' thread to process.
741 * @v Bus The bus for the IRP.
742 * @v Irp The IRP for the bus.
743 * @ret NTSTATUS The status of the operation. Returns STATUS_PENDING
744 * if the IRP is successfully added to the queue.
746 winvblock__lib_func NTSTATUS STDCALL WvBusEnqueueIrp(
750 WV_SP_BUS_WORK_ITEM_ work_item;
753 return STATUS_INVALID_PARAMETER;
756 return STATUS_NO_SUCH_DEVICE;
758 if (!(work_item = wv_malloc(sizeof *work_item)))
759 return STATUS_INSUFFICIENT_RESOURCES;
761 work_item->Cmd = WvBusWorkItemCmdProcessIrp_;
762 work_item->Context.Irp = Irp;
763 IoMarkIrpPending(Irp);
764 if (!WvBusAddWorkItem_(Bus, work_item)) {
766 return STATUS_UNSUCCESSFUL;
768 /* Fire and forget. */
769 KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
770 return STATUS_PENDING;