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_DEV_DISPATCH WvBusSysCtl_;
65 static WV_F_BUS_THREAD WvBusDefaultThread_;
66 static winvblock__bool WvBusAddWorkItem_(
70 static WV_SP_BUS_WORK_ITEM_ WvBusGetWorkItem_(WV_SP_BUS_T);
73 WV_S_DEV_IRP_MJ WvBusIrpMj_ = {
82 * Add a child node to the bus.
84 * @v Bus Points to the bus receiving the child.
85 * @v Dev Points to the child device to add.
86 * @ret TRUE for success, FALSE for failure.
88 winvblock__lib_func winvblock__bool STDCALL WvBusAddChild(
89 IN OUT WV_SP_BUS_T Bus,
90 IN OUT WV_SP_DEV_T Dev
92 /* The new node's device object. */
93 PDEVICE_OBJECT dev_obj;
94 /* Walks the child nodes. */
96 winvblock__uint32 dev_num;
99 if ((Bus == NULL) || (Dev == NULL)) {
100 DBG("No bus or no device!\n");
103 /* Create the child device. */
104 dev_obj = WvDevCreatePdo(Dev);
105 if (dev_obj == NULL) {
106 DBG("PDO creation failed!\n");
111 Dev->Parent = Bus->Dev.Self;
113 * Initialize the device. For disks, this routine is responsible for
114 * determining the disk's geometry appropriately for AoE/RAM/file disks.
117 dev_obj->Flags &= ~DO_DEVICE_INITIALIZING;
118 /* Add the new device's extension to the bus' list of children. */
120 if (Bus->first_child == NULL) {
121 Bus->first_child = Dev;
123 walker = Bus->first_child;
124 /* If the first child device number isn't 0... */
125 if (walker->DevNum) {
126 /* We insert before. */
127 Dev->next_sibling_ptr = walker;
128 Bus->first_child = Dev;
130 while (walker->next_sibling_ptr != NULL) {
131 /* If there's a gap in the device numbers for the bus... */
132 if (walker->DevNum < walker->next_sibling_ptr->DevNum - 1) {
133 /* Insert here, instead of at the end. */
134 dev_num = walker->DevNum + 1;
135 Dev->next_sibling_ptr = walker->next_sibling_ptr;
136 walker->next_sibling_ptr = Dev;
139 walker = walker->next_sibling_ptr;
140 dev_num = walker->DevNum + 1;
142 /* If we haven't already inserted the device... */
143 if (!Dev->next_sibling_ptr) {
144 walker->next_sibling_ptr = Dev;
145 dev_num = walker->DevNum + 1;
149 Dev->DevNum = dev_num;
151 if (Bus->PhysicalDeviceObject != NULL) {
152 IoInvalidateDeviceRelations(
153 Bus->PhysicalDeviceObject,
161 /* Handle an IRP_MJ_SYSTEM_CONTROL IRP. */
162 static NTSTATUS STDCALL WvBusSysCtl_(IN WV_SP_DEV_T dev, IN PIRP irp) {
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 /* Handle a power IRP. */
175 static NTSTATUS STDCALL WvBusPower_(
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 DevObj,
192 IN PDEVICE_CAPABILITIES DevCapabilities
194 IO_STATUS_BLOCK io_status;
197 PDEVICE_OBJECT target_obj;
198 PIO_STACK_LOCATION io_stack_loc;
201 RtlZeroMemory(DevCapabilities, sizeof *DevCapabilities);
202 DevCapabilities->Size = sizeof *DevCapabilities;
203 DevCapabilities->Version = 1;
204 DevCapabilities->Address = -1;
205 DevCapabilities->UINumber = -1;
207 KeInitializeEvent(&pnp_event, NotificationEvent, FALSE);
208 target_obj = IoGetAttachedDeviceReference(DevObj);
209 pnp_irp = IoBuildSynchronousFsdRequest(
218 if (pnp_irp == NULL) {
219 status = STATUS_INSUFFICIENT_RESOURCES;
221 pnp_irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
222 io_stack_loc = IoGetNextIrpStackLocation(pnp_irp);
223 RtlZeroMemory(io_stack_loc, sizeof *io_stack_loc);
224 io_stack_loc->MajorFunction = IRP_MJ_PNP;
225 io_stack_loc->MinorFunction = IRP_MN_QUERY_CAPABILITIES;
226 io_stack_loc->Parameters.DeviceCapabilities.Capabilities =
228 status = IoCallDriver(target_obj, pnp_irp);
229 if (status == STATUS_PENDING) {
230 KeWaitForSingleObject(
237 status = io_status.Status;
240 ObDereferenceObject(target_obj);
244 /* Initialize a bus. */
245 static winvblock__bool STDCALL WvBusDevInit_(IN WV_SP_DEV_T 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 RtlZeroMemory(Bus, sizeof *Bus);
256 /* Populate non-zero bus device defaults. */
257 WvDevInit(&Bus->Dev);
258 Bus->Thread = WvBusDefaultThread_;
259 KeInitializeSpinLock(&Bus->BusPrivate_.WorkItemsLock);
260 InitializeListHead(&Bus->BusPrivate_.WorkItems);
261 KeInitializeEvent(&Bus->ThreadSignal, SynchronizationEvent, FALSE);
262 KeInitializeEvent(&Bus->ThreadStopped, SynchronizationEvent, FALSE);
263 Bus->Dev.Ops.CreatePdo = WvBusCreatePdo_;
264 Bus->Dev.Ops.Init = WvBusDevInit_;
265 Bus->Dev.Ops.Free = WvBusFree_;
267 Bus->Dev.IrpMj = &WvBusIrpMj_;
268 Bus->Dev.IsBus = TRUE;
274 * @ret WV_SP_BUS_T The address of a new bus, or NULL for failure.
276 * This function should not be confused with a PDO creation routine, which is
277 * actually implemented for each device type. This routine will allocate a
278 * WV_S_BUS_T as well as populate the bus with default values.
280 winvblock__lib_func WV_SP_BUS_T WvBusCreate(void) {
284 * Bus devices might be used for booting and should
285 * not be allocated from a paged memory pool.
287 bus = wv_malloc(sizeof *bus);
303 * @v dev Populate PDO dev. ext. space from these details.
304 * @ret PDEVICE_OBJECT Points to the new PDO, or is NULL upon failure.
306 * Returns a Physical Device Object pointer on success, NULL for failure.
308 static PDEVICE_OBJECT STDCALL WvBusCreatePdo_(IN WV_SP_DEV_T dev) {
309 PDEVICE_OBJECT pdo = NULL;
313 /* Note the bus device needing a PDO. */
315 DBG("No device passed\n");
318 bus = WvBusFromDev(dev);
319 /* Create the PDO. */
320 status = IoCreateDevice(
322 sizeof (driver__dev_ext),
324 FILE_DEVICE_CONTROLLER,
325 FILE_DEVICE_SECURE_OPEN,
330 DBG("IoCreateDevice() failed!\n");
334 /* Set associations for the bus, device, PDO. */
335 WvDevForDevObj(pdo, dev);
336 dev->Self = bus->PhysicalDeviceObject = pdo;
338 /* Set some DEVICE_OBJECT status. */
339 pdo->Flags |= DO_DIRECT_IO; /* FIXME? */
340 pdo->Flags |= DO_POWER_INRUSH; /* FIXME? */
341 pdo->Flags &= ~DO_DEVICE_INITIALIZING;
343 dev->State = Started;
355 * Default bus deletion operation.
357 * @v dev Points to the bus device to delete.
359 static void STDCALL WvBusFree_(IN WV_SP_DEV_T dev) {
360 WV_SP_BUS_T bus = WvBusFromDev(dev);
366 * Get a bus from a device.
368 * @v dev A pointer to a device.
369 * @ret A pointer to the device's associated bus.
371 extern winvblock__lib_func WV_SP_BUS_T WvBusFromDev(WV_SP_DEV_T Dev) {
376 * Add a work item for a bus to process.
378 * @v bus The bus to process the work item.
379 * @v work_item The work item to add.
380 * @ret winvblock__bool TRUE if added, else FALSE
382 * Note that this function will initialize the work item's completion signal.
384 static winvblock__bool WvBusAddWorkItem_(
386 WV_SP_BUS_WORK_ITEM_ work_item
388 ExInterlockedInsertTailList(
389 &bus->BusPrivate_.WorkItems,
391 &bus->BusPrivate_.WorkItemsLock
398 * Get (and dequeue) a work item from a bus' queue.
400 * @v bus The bus processing the work item.
401 * @ret WV_SP_BUS_WORK_ITEM_ The work item, or NULL for an empty queue.
403 static WV_SP_BUS_WORK_ITEM_ WvBusGetWorkItem_(
406 PLIST_ENTRY list_entry;
408 list_entry = ExInterlockedRemoveHeadList(
409 &bus->BusPrivate_.WorkItems,
410 &bus->BusPrivate_.WorkItemsLock
415 return CONTAINING_RECORD(list_entry, WV_S_BUS_WORK_ITEM_, Link);
419 * Process work items for a bus.
421 * @v Bus The bus to process its work items.
423 winvblock__lib_func void WvBusProcessWorkItems(WV_SP_BUS_T Bus) {
424 WV_SP_BUS_WORK_ITEM_ work_item;
427 PIO_STACK_LOCATION io_stack_loc;
428 PDEVICE_OBJECT dev_obj;
429 PDRIVER_OBJECT driver_obj;
431 while (work_item = WvBusGetWorkItem_(Bus)) {
432 switch (work_item->Cmd) {
433 case WvBusWorkItemCmdAddPdo_:
434 DBG("Adding PDO to bus...\n");
436 node = work_item->Context.Node;
437 /* It's too bad about having both linked list and bus ref. */
438 node->BusPrivate_.Bus = Bus;
439 ObReferenceObject(node->BusPrivate_.Pdo);
440 InsertTailList(&Bus->BusPrivate_.Nodes, &node->BusPrivate_.Link);
441 Bus->BusPrivate_.NodeCount++;
444 case WvBusWorkItemCmdRemovePdo_:
445 DBG("Removing PDO from bus...\n");
447 node = work_item->Context.Node;
448 RemoveEntryList(&node->BusPrivate_.Link);
449 ObDereferenceObject(node->BusPrivate_.Pdo);
450 Bus->BusPrivate_.NodeCount--;
453 case WvBusWorkItemCmdProcessIrp_:
454 irp = work_item->Context.Irp;
455 io_stack_loc = IoGetCurrentIrpStackLocation(irp);
456 dev_obj = Bus->Dev.Self;
457 driver_obj = dev_obj->DriverObject;
458 driver_obj->MajorFunction[io_stack_loc->MajorFunction](
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 = WvBusGetWorkItem_(Bus))
486 /* The WV_S_DEV_T::Ops.Free implementation for a threaded bus. */
487 static void STDCALL WvBusThreadFree_(IN WV_SP_DEV_T 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 * Note that we do not attempt to free the bus data; this is a bus
502 * implementor's responsibility. We do, however, set the ThreadStopped
503 * signal which should mean that resources can be freed, from a completed
504 * thread's perspective.
506 static void STDCALL WvBusThread_(IN void * context) {
507 WV_SP_BUS_T bus = context;
509 if (!bus || !bus->Thread) {
510 DBG("No bus or no thread!\n");
515 KeSetEvent(&bus->ThreadStopped, 0, FALSE);
520 * The default bus thread routine.
522 * @v bus Points to the bus device for the thread to work with.
524 * Note that if you implement your own bus type using this library,
525 * you can override the thread routine with your own. If you do so,
526 * your thread routine should call WvBusProcessWorkItems() within
527 * its loop. To start a bus thread, use WvBusStartThread()
528 * If you implement your own thread routine, you are also responsible
529 * for calling WvBusCancelWorkItems() and freeing the bus.
531 static void STDCALL WvBusDefaultThread_(IN WV_SP_BUS_T bus) {
532 LARGE_INTEGER timeout;
534 /* Wake up at least every 30 seconds. */
535 timeout.QuadPart = -300000000LL;
537 /* Hook WV_S_DEV_T::Ops.Free() */
538 bus->Dev.Ops.Free = WvBusThreadFree_;
540 /* When WV_S_BUS_T::Stop is set, we shut down. */
544 /* Wait for the work signal or the timeout. */
545 KeWaitForSingleObject(
552 /* Reset the work signal. */
553 KeResetEvent(&bus->ThreadSignal);
555 WvBusProcessWorkItems(bus);
556 } /* while !bus->Stop */
558 WvBusCancelWorkItems(bus);
563 * Start a bus thread.
565 * @v Bus The bus to start a thread for.
566 * @ret NTSTATUS The status of the thread creation operation.
568 * Also see WV_F_BUS_THREAD in the header for details about the prototype
569 * for implementing your own bus thread routine. You set WV_S_BUS_T::Thread
570 * to specify your own thread routine, then call this function to start it.
572 winvblock__lib_func NTSTATUS WvBusStartThread(
575 OBJECT_ATTRIBUTES obj_attrs;
576 HANDLE thread_handle;
579 DBG("No bus specified!\n");
580 return STATUS_INVALID_PARAMETER;
583 InitializeObjectAttributes(
590 return PsCreateSystemThread(
602 * Initialize a bus node with an associated PDO.
604 * @v Node The node to initialize.
605 * @v Pdo The PDO to associate the node with.
606 * @ret winvblock__bool FALSE for a NULL argument, otherwise TRUE
608 winvblock__lib_func winvblock__bool STDCALL WvBusInitNode(
609 OUT WV_SP_BUS_NODE Node,
610 IN PDEVICE_OBJECT Pdo
615 RtlZeroMemory(Node, sizeof *Node);
616 Node->BusPrivate_.Pdo = Pdo;
621 * Add a PDO node to a bus' list of children.
623 * @v Bus The bus to add the node to.
624 * @v Node The PDO node to add to the bus.
625 * @ret NTSTATUS The status of the operation.
627 * Do not attempt to add the same node to more than one bus.
628 * When WvBusProcessWorkItems() is called for the bus, the
629 * node will be added. This is usually from the bus' thread.
631 winvblock__lib_func NTSTATUS STDCALL WvBusAddNode(
635 WV_SP_BUS_WORK_ITEM_ work_item;
640 Bus->Dev.Self->DriverObject != Node->BusPrivate_.Pdo->DriverObject
642 return STATUS_INVALID_PARAMETER;
645 return STATUS_NO_SUCH_DEVICE;
647 if (!(work_item = wv_malloc(sizeof *work_item)))
648 return STATUS_INSUFFICIENT_RESOURCES;
650 work_item->Cmd = WvBusWorkItemCmdAddPdo_;
651 work_item->Context.Node = Node;
652 if (!WvBusAddWorkItem_(Bus, work_item)) {
654 return STATUS_UNSUCCESSFUL;
656 /* Fire and forget. */
657 KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
658 return STATUS_SUCCESS;
662 * Remove a PDO node from a bus.
664 * @v Node The PDO node to remove from its parent bus.
665 * @ret NTSTATUS The status of the operation.
667 * When WvBusProcessWorkItems() is called for the bus, it will
668 * then remove the node. This is usually from the bus' thread.
670 winvblock__lib_func NTSTATUS STDCALL WvBusRemoveNode(
674 WV_SP_BUS_WORK_ITEM_ work_item;
676 if (!Node || !(bus = Node->BusPrivate_.Bus))
677 return STATUS_INVALID_PARAMETER;
680 return STATUS_NO_SUCH_DEVICE;
682 if (!(work_item = wv_malloc(sizeof *work_item)))
683 return STATUS_INSUFFICIENT_RESOURCES;
685 work_item->Cmd = WvBusWorkItemCmdRemovePdo_;
686 work_item->Context.Node = Node;
687 if (!WvBusAddWorkItem_(bus, work_item)) {
689 return STATUS_UNSUCCESSFUL;
691 /* Fire and forget. */
692 KeSetEvent(&bus->ThreadSignal, 0, FALSE);
693 return STATUS_SUCCESS;
697 * Enqueue an IRP for a bus' thread to process.
699 * @v Bus The bus for the IRP.
700 * @v Irp The IRP for the bus.
701 * @ret NTSTATUS The status of the operation. Returns STATUS_PENDING
702 * if the IRP is successfully added to the queue.
704 winvblock__lib_func NTSTATUS STDCALL WvBusEnqueueIrp(
708 WV_SP_BUS_WORK_ITEM_ work_item;
711 return STATUS_INVALID_PARAMETER;
714 return STATUS_NO_SUCH_DEVICE;
716 if (!(work_item = wv_malloc(sizeof *work_item)))
717 return STATUS_INSUFFICIENT_RESOURCES;
719 work_item->Cmd = WvBusWorkItemCmdProcessIrp_;
720 work_item->Context.Irp = Irp;
721 IoMarkIrpPending(Irp);
722 if (!WvBusAddWorkItem_(Bus, work_item)) {
724 return STATUS_UNSUCCESSFUL;
726 /* Fire and forget. */
727 KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
728 return STATUS_PENDING;