2 * Copyright (C) 2009-2011, 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/>.
31 #include "winvblock.h"
32 #include "wv_stdlib.h"
38 typedef enum WVL_BUS_WORK_ITEM_CMD {
39 WvlBusWorkItemCmdAddPdo_,
40 WvlBusWorkItemCmdRemovePdo_,
41 WvlBusWorkItemCmdProcessIrp_,
42 WvlBusWorkItemCmdCustom_,
44 } WVL_E_BUS_WORK_ITEM_CMD, * WVL_EP_BUS_WORK_ITEM_CMD;
46 typedef struct WVL_BUS_WORK_ITEM {
48 WVL_E_BUS_WORK_ITEM_CMD Cmd;
52 WVL_SP_BUS_CUSTOM_WORK_ITEM Custom;
54 } WVL_S_BUS_WORK_ITEM, * WVL_SP_BUS_WORK_ITEM;
56 /* Forward declarations. */
57 static WVL_F_BUS_THREAD WvlBusDefaultThread;
58 static BOOLEAN WvlBusAddWorkItem(
62 static WVL_SP_BUS_WORK_ITEM WvlBusGetWorkItem(WVL_SP_BUS_T);
64 /* Handle an IRP_MJ_SYSTEM_CONTROL IRP. */
65 WVL_M_LIB NTSTATUS STDCALL WvlBusSysCtl(
69 PDEVICE_OBJECT lower = Bus->LowerDeviceObject;
72 DBG("Passing IRP_MJ_SYSTEM_CONTROL down\n");
73 IoSkipCurrentIrpStackLocation(Irp);
74 return IoCallDriver(lower, Irp);
76 return WvlIrpComplete(Irp, 0, STATUS_SUCCESS);
79 /* Handle a power IRP. */
80 WVL_M_LIB NTSTATUS STDCALL WvlBusPower(
84 PDEVICE_OBJECT lower = Bus->LowerDeviceObject;
86 PoStartNextPowerIrp(Irp);
88 IoSkipCurrentIrpStackLocation(Irp);
89 return PoCallDriver(lower, Irp);
91 return WvlIrpComplete(Irp, 0, STATUS_SUCCESS);
95 * Initialize bus defaults.
97 * @v Bus Points to the bus to initialize with defaults.
99 WVL_M_LIB VOID WvlBusInit(WVL_SP_BUS_T Bus) {
100 RtlZeroMemory(Bus, sizeof *Bus);
101 /* Populate non-zero bus device defaults. */
102 Bus->Thread = WvlBusDefaultThread;
103 InitializeListHead(&Bus->BusPrivate_.Nodes);
104 KeInitializeSpinLock(&Bus->BusPrivate_.WorkItemsLock);
105 InitializeListHead(&Bus->BusPrivate_.WorkItems);
106 KeInitializeEvent(&Bus->ThreadSignal, SynchronizationEvent, FALSE);
110 * Add a work item for a bus to process.
112 * @v bus The bus to process the work item.
113 * @v work_item The work item to add.
114 * @ret BOOLEAN TRUE if added, else FALSE
116 * Note that this function will initialize the work item's completion signal.
118 static BOOLEAN WvlBusAddWorkItem(
120 WVL_SP_BUS_WORK_ITEM work_item
122 ExInterlockedInsertTailList(
123 &bus->BusPrivate_.WorkItems,
125 &bus->BusPrivate_.WorkItemsLock
132 * Get (and dequeue) a work item from a bus' queue.
134 * @v bus The bus processing the work item.
135 * @ret WVL_SP_BUS_WORK_ITEM The work item, or NULL for an empty queue.
137 static WVL_SP_BUS_WORK_ITEM WvlBusGetWorkItem(
140 PLIST_ENTRY list_entry;
142 list_entry = ExInterlockedRemoveHeadList(
143 &bus->BusPrivate_.WorkItems,
144 &bus->BusPrivate_.WorkItemsLock
149 return CONTAINING_RECORD(list_entry, WVL_S_BUS_WORK_ITEM, Link);
153 * Add a PDO node to a bus' list of children. Internal.
155 * @v bus The bus to add the node to.
156 * @v new_node The PDO node to add to the bus.
158 * Don't call this function yourself. It expects to have exclusive
159 * access to the bus' list of children.
161 static VOID STDCALL WvlBusAddNode_(WVL_SP_BUS_T bus, WVL_SP_BUS_NODE new_node) {
164 DBG("Adding PDO to bus...\n");
165 ObReferenceObject(new_node->BusPrivate_.Pdo);
166 bus->BusPrivate_.NodeCount++;
167 /* It's too bad about having both linked list and bus ref. */
168 new_node->BusPrivate_.Bus = bus;
170 /* Find a slot for the new child. */
171 walker = &bus->BusPrivate_.Nodes;
172 new_node->BusPrivate_.Num = 0;
173 while ((walker = walker->Flink) != &bus->BusPrivate_.Nodes) {
174 WVL_SP_BUS_NODE node = CONTAINING_RECORD(
181 node->BusPrivate_.Num &&
182 (node->BusPrivate_.Link.Blink == &bus->BusPrivate_.Nodes)
184 /* The first node's unit number is != 0. Insert here. */
187 if (node->BusPrivate_.Num > new_node->BusPrivate_.Num) {
188 /* There is a gap so insert here. */
191 /* Continue trying to find a slot. */
192 new_node->BusPrivate_.Num++;
194 /* Insert before walker. */
195 InsertTailList(walker, &new_node->BusPrivate_.Link);
200 * Remove a PDO node from a bus. Internal.
202 * @v bus The bus to remove the node from.
203 * @v node The PDO node to remove from its parent bus.
205 * Don't call this function yourself. It expects to have exclusive
206 * access to the bus' list of children.
208 static VOID STDCALL WvlBusRemoveNode_(
212 DBG("Removing PDO from bus...\n");
213 RemoveEntryList(&node->BusPrivate_.Link);
214 ObDereferenceObject(node->BusPrivate_.Pdo);
215 bus->BusPrivate_.NodeCount--;
220 * Process work items for a bus.
222 * @v Bus The bus to process its work items.
224 WVL_M_LIB VOID WvlBusProcessWorkItems(WVL_SP_BUS_T Bus) {
225 WVL_SP_BUS_WORK_ITEM work_item;
226 WVL_SP_BUS_NODE node;
228 PIO_STACK_LOCATION io_stack_loc;
229 PDEVICE_OBJECT dev_obj;
230 PDRIVER_OBJECT driver_obj;
231 BOOLEAN nodes_changed;
233 while (work_item = WvlBusGetWorkItem(Bus)) {
234 switch (work_item->Cmd) {
235 case WvlBusWorkItemCmdAddPdo_:
236 node = work_item->Context.Node;
237 WvlBusAddNode_(Bus, node);
238 nodes_changed = TRUE;
241 case WvlBusWorkItemCmdRemovePdo_:
242 node = work_item->Context.Node;
243 WvlBusRemoveNode_(Bus, node);
244 nodes_changed = TRUE;
247 case WvlBusWorkItemCmdProcessIrp_:
248 irp = work_item->Context.Irp;
249 io_stack_loc = IoGetCurrentIrpStackLocation(irp);
251 driver_obj = dev_obj->DriverObject;
252 driver_obj->MajorFunction[io_stack_loc->MajorFunction](
258 case WvlBusWorkItemCmdCustom_:
259 DBG("Custom work item.\n");
260 work_item->Context.Custom->Func(
261 work_item->Context.Custom->Context
266 DBG("Unknown work item type!\n");
270 if (nodes_changed && Bus->Pdo) {
271 nodes_changed = FALSE;
272 IoInvalidateDeviceRelations(
281 * Cancel pending work items for a bus.
283 * @v Bus The bus to cancel pending work items for.
285 WVL_M_LIB VOID WvlBusCancelWorkItems(WVL_SP_BUS_T Bus) {
286 WVL_SP_BUS_WORK_ITEM work_item;
288 DBG("Canceling work items.\n");
289 while (work_item = WvlBusGetWorkItem(Bus))
295 * The bus thread wrapper.
297 * @v context The thread context. In our case, it points to
298 * the bus that the thread should use in processing.
300 * Note that we do not attempt to free the bus data; this is a bus
301 * implementor's responsibility. We do, however, set the ThreadStopped
302 * signal which should mean that resources can be freed, from a completed
303 * thread's perspective.
305 static VOID STDCALL WvlBusThread(IN PVOID context) {
306 WVL_SP_BUS_T bus = context;
308 if (!bus || !bus->Thread) {
309 DBG("No bus or no thread!\n");
315 PsTerminateSystemThread(STATUS_SUCCESS);
320 * The default bus thread routine.
322 * @v bus Points to the bus device for the thread to work with.
324 * Note that if you implement your own bus type using this library,
325 * you can override the thread routine with your own. If you do so,
326 * your thread routine should call WvlBusProcessWorkItems() within
327 * its loop. To start a bus thread, use WvlBusStartThread()
328 * If you implement your own thread routine, you are also responsible
329 * for calling WvlBusCancelWorkItems() and freeing the bus.
331 static VOID STDCALL WvlBusDefaultThread(IN WVL_SP_BUS_T bus) {
332 LARGE_INTEGER timeout;
334 /* Wake up at least every 30 seconds. */
335 timeout.QuadPart = -300000000LL;
337 /* When WVL_S_BUS_T::Stop is set, we shut down. */
341 /* Wait for the work signal or the timeout. */
342 KeWaitForSingleObject(
349 /* Reset the work signal. */
350 KeResetEvent(&bus->ThreadSignal);
352 WvlBusProcessWorkItems(bus);
353 } /* while !bus->Stop */
355 WvlBusCancelWorkItems(bus);
360 * Start a bus thread.
362 * @v Bus The bus to start a thread for.
363 * @v Thread A PETHREAD to be filled to reference the thread.
364 * @ret NTSTATUS The status of the thread creation operation.
366 * Also see WVL_F_BUS_THREAD in the header for details about the prototype
367 * for implementing your own bus thread routine. You set WVL_S_BUS_T::Thread
368 * to specify your own thread routine, then call this function to start it.
369 * When stopping the thread, you can wait on the thread handle.
371 WVL_M_LIB NTSTATUS WvlBusStartThread(
373 OUT PETHREAD * Thread
375 OBJECT_ATTRIBUTES obj_attrs;
376 HANDLE thread_handle;
380 DBG("No bus specified!\n");
381 return STATUS_INVALID_PARAMETER;
384 InitializeObjectAttributes(
391 status = PsCreateSystemThread(
400 if (!NT_SUCCESS(status))
402 return ObReferenceObjectByHandle(
413 * Initialize a bus node with an associated PDO.
415 * @v Node The node to initialize.
416 * @v Pdo The PDO to associate the node with.
417 * @ret BOOLEAN FALSE for a NULL argument, otherwise TRUE
419 WVL_M_LIB BOOLEAN STDCALL WvlBusInitNode(
420 OUT WVL_SP_BUS_NODE Node,
421 IN PDEVICE_OBJECT Pdo
426 RtlZeroMemory(Node, sizeof *Node);
427 Node->BusPrivate_.Pdo = Pdo;
432 * Add a PDO node to a bus' list of children.
434 * @v Bus The bus to add the node to.
435 * @v Node The PDO node to add to the bus.
436 * @ret NTSTATUS The status of the operation.
438 * Do not attempt to add the same node to more than one bus.
439 * When WvlBusProcessWorkItems() is called for the bus, the
440 * node will be added. This is usually from the bus' thread.
442 WVL_M_LIB NTSTATUS STDCALL WvlBusAddNode(
446 WVL_SP_BUS_WORK_ITEM work_item;
451 Bus->Fdo->DriverObject != Node->BusPrivate_.Pdo->DriverObject
453 return STATUS_INVALID_PARAMETER;
456 return STATUS_NO_SUCH_DEVICE;
458 if (!(work_item = wv_malloc(sizeof *work_item)))
459 return STATUS_INSUFFICIENT_RESOURCES;
461 work_item->Cmd = WvlBusWorkItemCmdAddPdo_;
462 work_item->Context.Node = Node;
463 if (!WvlBusAddWorkItem(Bus, work_item)) {
465 return STATUS_UNSUCCESSFUL;
467 /* Fire and forget. */
468 KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
469 return STATUS_SUCCESS;
473 * Remove a PDO node from a bus.
475 * @v Node The PDO node to remove from its parent bus.
476 * @ret NTSTATUS The status of the operation.
478 * When WvlBusProcessWorkItems() is called for the bus, it will
479 * then remove the node. This is usually from the bus' thread.
481 WVL_M_LIB NTSTATUS STDCALL WvlBusRemoveNode(
485 WVL_SP_BUS_WORK_ITEM work_item;
487 if (!Node || !(bus = Node->BusPrivate_.Bus))
488 return STATUS_INVALID_PARAMETER;
491 return STATUS_NO_SUCH_DEVICE;
493 if (!(work_item = wv_malloc(sizeof *work_item)))
494 return STATUS_INSUFFICIENT_RESOURCES;
496 work_item->Cmd = WvlBusWorkItemCmdRemovePdo_;
497 work_item->Context.Node = Node;
498 if (!WvlBusAddWorkItem(bus, work_item)) {
500 return STATUS_UNSUCCESSFUL;
502 /* Fire and forget. */
503 KeSetEvent(&bus->ThreadSignal, 0, FALSE);
504 return STATUS_SUCCESS;
508 * Enqueue an IRP for a bus' thread to process.
510 * @v Bus The bus for the IRP.
511 * @v Irp The IRP for the bus.
512 * @ret NTSTATUS The status of the operation. Returns STATUS_PENDING
513 * if the IRP is successfully added to the queue.
515 WVL_M_LIB NTSTATUS STDCALL WvlBusEnqueueIrp(
519 WVL_SP_BUS_WORK_ITEM work_item;
522 return STATUS_INVALID_PARAMETER;
525 return STATUS_NO_SUCH_DEVICE;
527 if (!(work_item = wv_malloc(sizeof *work_item)))
528 return STATUS_INSUFFICIENT_RESOURCES;
530 work_item->Cmd = WvlBusWorkItemCmdProcessIrp_;
531 work_item->Context.Irp = Irp;
532 IoMarkIrpPending(Irp);
533 if (!WvlBusAddWorkItem(Bus, work_item)) {
535 return STATUS_UNSUCCESSFUL;
537 /* Fire and forget. */
538 KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
539 return STATUS_PENDING;
543 * Enqueue a custom work item for a bus' thread to process.
545 * @v Bus The bus for the IRP.
546 * @v CustomWorkItem The custom work item for the bus' thread to process.
547 * @ret NTSTATUS The status of the operation.
549 WVL_M_LIB NTSTATUS STDCALL WvlBusEnqueueCustomWorkItem(
551 WVL_SP_BUS_CUSTOM_WORK_ITEM CustomWorkItem
553 WVL_SP_BUS_WORK_ITEM work_item;
555 if (!Bus || !CustomWorkItem)
556 return STATUS_INVALID_PARAMETER;
559 return STATUS_NO_SUCH_DEVICE;
561 if (!(work_item = wv_malloc(sizeof *work_item)))
562 return STATUS_INSUFFICIENT_RESOURCES;
564 work_item->Cmd = WvlBusWorkItemCmdCustom_;
565 work_item->Context.Custom = CustomWorkItem;
566 if (!WvlBusAddWorkItem(Bus, work_item)) {
568 return STATUS_UNSUCCESSFUL;
570 /* Fire and forget. */
571 KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
572 return STATUS_SUCCESS;
576 * Get the unit number for a child node on a bus.
578 * @v Node The node whose unit number we request.
579 * @ret UINT32 The unit number for the node.
581 WVL_M_LIB UINT32 STDCALL WvlBusGetNodeNum(
582 IN WVL_SP_BUS_NODE Node
584 return Node->BusPrivate_.Num;
588 * Get the next child node on a bus.
590 * @v Bus The bus whose nodes are fetched.
591 * @v PrevNode The previous node. Pass NULL to begin.
592 * @ret WVL_SP_BUS_NODE Returns NULL when there are no more nodes.
594 * This function should only be called within the thread context of
595 * whichever thread calls WvlBusProcessWorkItems() because it expects
596 * the list of child nodes to remain static between calls.
598 WVL_M_LIB WVL_SP_BUS_NODE STDCALL WvlBusGetNextNode(
600 IN WVL_SP_BUS_NODE PrevNode
605 link = &Bus->BusPrivate_.Nodes;
607 link = &PrevNode->BusPrivate_.Link;
609 if (link == &Bus->BusPrivate_.Nodes)
611 return CONTAINING_RECORD(link, WVL_S_BUS_NODE, BusPrivate_.Link);
615 * Get a child node's PDO.
617 * @v Node The node whose PDO will be returned.
618 * @ret PDEVICE_OBJECT The PDO for the node.
620 WVL_M_LIB PDEVICE_OBJECT STDCALL WvlBusGetNodePdo(
621 IN WVL_SP_BUS_NODE Node
623 return Node->BusPrivate_.Pdo;
627 * Get the count of child nodes on a bus.
629 * @v Bus The bus whose node-count will be returned.
630 * @v UINT32 The count of nodes on the bus.
632 * In order for this function to yield a race-free, useful result, it
633 * should be used by whatever thread calls WvlBusProcessWorkItems()
635 WVL_M_LIB UINT32 STDCALL WvlBusGetNodeCount(
638 return Bus->BusPrivate_.NodeCount;