[bus] Rename bus__add_work_item_ to WvBusAddWorkItem_
[people/sha0/winvblock.git] / src / winvblock / bus / bus.c
1 /**
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/
5  *
6  * This file is part of WinVBlock, derived from WinAoE.
7  *
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.
12  *
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.
17  *
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/>.
20  */
21
22 /**
23  * @file
24  *
25  * Bus specifics.
26  */
27
28 #include <ntddk.h>
29
30 #include "winvblock.h"
31 #include "wv_stdlib.h"
32 #include "portable.h"
33 #include "driver.h"
34 #include "device.h"
35 #include "bus.h"
36 #include "debug.h"
37
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;
42
43 /* Types. */
44 typedef enum WV_BUS_WORK_ITEM_CMD_ {
45     WvBusWorkItemCmdAddPdo_,
46     WvBusWorkItemCmdRemovePdo_,
47     WvBusWorkItemCmds_
48   } WV_E_BUS_WORK_ITEM_CMD_, * WV_EP_BUS_WORK_ITEM_CMD_;
49
50 typedef struct WV_BUS_WORK_ITEM_ {
51     LIST_ENTRY Link;
52     WV_E_BUS_WORK_ITEM_CMD_ Cmd;
53     union {
54         WV_SP_BUS_NODE Node;
55       } Context;
56   } WV_S_BUS_WORK_ITEM_, * WV_SP_BUS_WORK_ITEM_;
57
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_(
65     WV_SP_BUS_T,
66     WV_SP_BUS_WORK_ITEM_
67   );
68 static WV_SP_BUS_WORK_ITEM_ bus__get_work_item_(WV_SP_BUS_T);
69
70 /* Globals. */
71 struct device__irp_mj bus__irp_mj_ = {
72     WvBusPower_,
73     WvBusSysCtl_,
74     WvBusDevCtlDispatch,
75     (device__scsi_func *) 0,
76     WvBusPnpDispatch,
77   };
78
79 /**
80  * Add a child node to the bus.
81  *
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.
85  */
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
89   ) {
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;
95
96     DBG("Entry\n");
97     if ((bus_ptr == NULL) || (dev_ptr == NULL)) {
98         DBG("No bus or no device!\n");
99         return FALSE;
100       }
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);
106         return FALSE;
107       }
108
109     dev_ptr->Parent = bus_ptr->Dev->Self;
110     /*
111      * Initialize the device.  For disks, this routine is responsible for
112      * determining the disk's geometry appropriately for AoE/RAM/file disks.
113      */
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. */
117     dev_num = 0;
118     if (bus_ptr->first_child == NULL) {
119         bus_ptr->first_child = dev_ptr;
120       } else {
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;
127           } else {
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;
135                     break;
136                   }
137                 walker = walker->next_sibling_ptr;
138                 dev_num = walker->dev_num + 1;
139               }
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;
144               }
145           }
146       }
147     dev_ptr->dev_num = dev_num;
148     bus_ptr->Children++;
149     if (bus_ptr->PhysicalDeviceObject != NULL) {
150         IoInvalidateDeviceRelations(
151             bus_ptr->PhysicalDeviceObject,
152             BusRelations
153           );
154       }
155     DBG("Exit\n");
156     return TRUE;
157   }
158
159 static NTSTATUS STDCALL WvBusSysCtl_(
160     IN struct device__type * dev,
161     IN PIRP irp
162   ) {
163     WV_SP_BUS_T bus = WvBusFromDev(dev);
164     PDEVICE_OBJECT lower = bus->LowerDeviceObject;
165
166     if (lower) {
167         DBG("Passing IRP_MJ_SYSTEM_CONTROL down\n");
168         IoSkipCurrentIrpStackLocation(irp);
169         return IoCallDriver(lower, irp);
170       }
171     return driver__complete_irp(irp, 0, STATUS_SUCCESS);
172   }
173
174 static NTSTATUS STDCALL WvBusPower_(
175     IN struct device__type * dev,
176     IN PIRP irp
177   ) {
178     WV_SP_BUS_T bus = WvBusFromDev(dev);
179     PDEVICE_OBJECT lower = bus->LowerDeviceObject;
180
181     PoStartNextPowerIrp(irp);
182     if (lower) {
183         IoSkipCurrentIrpStackLocation(irp);
184         return PoCallDriver(lower, irp);
185       }
186     return driver__complete_irp(irp, 0, STATUS_SUCCESS);
187   }
188
189 NTSTATUS STDCALL WvBusGetDevCapabilities(
190     IN PDEVICE_OBJECT DeviceObject,
191     IN PDEVICE_CAPABILITIES DeviceCapabilities
192   ) {
193     IO_STATUS_BLOCK ioStatus;
194     KEVENT pnpEvent;
195     NTSTATUS status;
196     PDEVICE_OBJECT targetObject;
197     PIO_STACK_LOCATION irpStack;
198     PIRP pnpIrp;
199
200     RtlZeroMemory(DeviceCapabilities, sizeof (DEVICE_CAPABILITIES));
201     DeviceCapabilities->Size = sizeof (DEVICE_CAPABILITIES);
202     DeviceCapabilities->Version = 1;
203     DeviceCapabilities->Address = -1;
204     DeviceCapabilities->UINumber = -1;
205
206     KeInitializeEvent(&pnpEvent, NotificationEvent, FALSE);
207     targetObject = IoGetAttachedDeviceReference(DeviceObject);
208     pnpIrp = IoBuildSynchronousFsdRequest(
209         IRP_MJ_PNP,
210         targetObject,
211         NULL,
212         0,
213         NULL,
214         &pnpEvent,
215         &ioStatus
216       );
217     if (pnpIrp == NULL) {
218         status = STATUS_INSUFFICIENT_RESOURCES;
219       } else {
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 =
226           DeviceCapabilities;
227         status = IoCallDriver(targetObject, pnpIrp);
228         if (status == STATUS_PENDING) {
229             KeWaitForSingleObject(
230                 &pnpEvent,
231                 Executive,
232                 KernelMode,
233                 FALSE,
234                 NULL
235               );
236             status = ioStatus.Status;
237           }
238       }
239     ObDereferenceObject(targetObject);
240     return status;
241   }
242
243 /* Initialize a bus. */
244 static winvblock__bool STDCALL bus__init_(IN struct device__type * dev) {
245     return TRUE;
246   }
247
248 /**
249  * Initialize bus defaults.
250  *
251  * @v bus               Points to the bus to initialize with defaults.
252  */
253 winvblock__lib_func void WvBusInit(WV_SP_BUS_T bus) {
254     struct device__type * dev = bus->Dev;
255
256     RtlZeroMemory(bus, sizeof *bus);
257     /* Populate non-zero bus device defaults. */
258     bus->Dev = dev;
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_;
267     dev->ext = bus;
268     dev->irp_mj = &bus__irp_mj_;
269     dev->IsBus = TRUE;
270   }
271
272 /**
273  * Create a new bus.
274  *
275  * @ret bus_ptr         The address of a new bus, or NULL for failure.
276  *
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.
281  */
282 winvblock__lib_func WV_SP_BUS_T WvBusCreate(void) {
283     struct device__type * dev;
284     WV_SP_BUS_T bus;
285
286     /* Try to create a device. */
287     dev = device__create();
288     if (dev == NULL)
289       goto err_no_dev;
290     /*
291      * Bus devices might be used for booting and should
292      * not be allocated from a paged memory pool.
293      */
294     bus = wv_malloc(sizeof *bus);
295     if (bus == NULL)
296       goto err_no_bus;
297
298     bus->Dev = dev;
299     WvBusInit(bus);
300     return bus;
301
302     err_no_bus:
303
304     device__free(dev);
305     err_no_dev:
306
307     return NULL;
308   }
309
310 /**
311  * Create a bus PDO.
312  *
313  * @v dev               Populate PDO dev. ext. space from these details.
314  * @ret pdo             Points to the new PDO, or is NULL upon failure.
315  *
316  * Returns a Physical Device Object pointer on success, NULL for failure.
317  */
318 static PDEVICE_OBJECT STDCALL WvBusCreatePdo_(IN struct device__type * dev) {
319     PDEVICE_OBJECT pdo = NULL;
320     WV_SP_BUS_T bus;
321     NTSTATUS status;
322
323     /* Note the bus device needing a PDO. */
324     if (dev == NULL) {
325         DBG("No device passed\n");
326         return NULL;
327       }
328     bus = WvBusFromDev(dev);
329     /* Create the PDO. */
330     status = IoCreateDevice(
331         dev->DriverObject,
332         sizeof (driver__dev_ext),
333         NULL,
334         FILE_DEVICE_CONTROLLER,
335         FILE_DEVICE_SECURE_OPEN,
336         FALSE,
337         &pdo
338       );
339     if (pdo == NULL) {
340         DBG("IoCreateDevice() failed!\n");
341         goto err_pdo;
342       }
343
344     /* Set associations for the bus, device, PDO. */
345     device__set(pdo, dev);
346     dev->Self = bus->PhysicalDeviceObject = pdo;
347
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;
352     #ifdef RIS
353     dev->State = Started;
354     #endif
355
356     return pdo;
357
358     IoDeleteDevice(pdo);
359     err_pdo:
360
361     return NULL;
362   }
363
364 /**
365  * Default bus deletion operation.
366  *
367  * @v dev_ptr           Points to the bus device to delete.
368  */
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);
373
374     wv_free(bus_ptr);
375   }
376
377 /**
378  * Get a bus from a device.
379  *
380  * @v dev       A pointer to a device.
381  * @ret         A pointer to the device's associated bus.
382  */
383 extern winvblock__lib_func WV_SP_BUS_T WvBusFromDev(
384     struct device__type * dev
385   ) {
386     return dev->ext;
387   }
388
389 /**
390  * Add a work item for a bus to process.
391  *
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
395  *
396  * Note that this function will initialize the work item's completion signal.
397  */
398 static winvblock__bool WvBusAddWorkItem_(
399     WV_SP_BUS_T bus,
400     WV_SP_BUS_WORK_ITEM_ work_item
401   ) {
402     ExInterlockedInsertTailList(
403         &bus->BusPrivate_.WorkItems,
404         &work_item->Link,
405         &bus->BusPrivate_.WorkItemsLock
406       );
407
408     return TRUE;
409   }
410
411 /**
412  * Get (and dequeue) a work item from a bus' queue.
413  *
414  * @v bus                       The bus processing the work item.
415  * @ret bus__work_item_         The work item, or NULL for an empty queue.
416  */
417 static WV_SP_BUS_WORK_ITEM_ bus__get_work_item_(
418     WV_SP_BUS_T bus
419   ) {
420     PLIST_ENTRY list_entry;
421
422     list_entry = ExInterlockedRemoveHeadList(
423         &bus->BusPrivate_.WorkItems,
424         &bus->BusPrivate_.WorkItemsLock
425       );
426     if (!list_entry)
427       return NULL;
428
429     return CONTAINING_RECORD(list_entry, WV_S_BUS_WORK_ITEM_, Link);
430   }
431
432 /**
433  * Process work items for a bus.
434  *
435  * @v bus               The bus to process its work items.
436  */
437 winvblock__lib_func void WvBusProcessWorkItems(WV_SP_BUS_T bus) {
438     WV_SP_BUS_WORK_ITEM_ work_item;
439     WV_SP_BUS_NODE node;
440
441     while (work_item = bus__get_work_item_(bus)) {
442         switch (work_item->Cmd) {
443             case WvBusWorkItemCmdAddPdo_:
444               DBG("Adding PDO to bus...\n");
445
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++;
452               break;
453
454             case WvBusWorkItemCmdRemovePdo_:
455               DBG("Removing PDO from bus...\n");
456
457               node = work_item->Context.Node;
458               RemoveEntryList(&node->BusPrivate_.Link);
459               ObDereferenceObject(node->BusPrivate_.Pdo);
460               bus->BusPrivate_.NodeCount--;
461               break;
462
463             default:
464               DBG("Unknown work item type!\n");
465           }
466         wv_free(work_item);
467       }
468     return;
469   }
470
471 /**
472  * Cancel pending work items for a bus.
473  *
474  * @v bus       The bus to cancel pending work items for.
475  */
476 winvblock__lib_func void WvBusCancelWorkItems(WV_SP_BUS_T bus) {
477     WV_SP_BUS_WORK_ITEM_ work_item;
478
479     DBG("Canceling work items.\n");
480     while (work_item = bus__get_work_item_(bus))
481       wv_free(work_item);
482     return;
483   }
484
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);
488
489     bus->Stop = TRUE;
490     KeSetEvent(&bus->ThreadSignal, 0, FALSE);
491     return;
492   }
493
494 /**
495  * The bus thread wrapper.
496  *
497  * @v context           The thread context.  In our case, it points to
498  *                      the bus that the thread should use in processing.
499  */
500 static void STDCALL bus__thread_(IN void * context) {
501     WV_SP_BUS_T bus = context;
502
503     if (!bus || !bus->Thread) {
504         DBG("No bus or no thread!\n");
505         return;
506       }
507
508     bus->Thread(bus);
509     return;
510   }
511
512 /**
513  * The default bus thread routine.
514  *
515  * @v bus       Points to the bus device for the thread to work with.
516  *
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.
523  */
524 static void STDCALL WvBusDefaultThread_(IN WV_SP_BUS_T bus) {
525     LARGE_INTEGER timeout;
526
527     /* Wake up at least every 30 seconds. */
528     timeout.QuadPart = -300000000LL;
529
530     /* Hook device__type::ops.free() */
531     bus->Dev->ops.free = bus__thread_free_;
532
533     /* When bus::Stop is set, we shut down. */
534     while (bus->Stop) {
535         DBG("Alive.\n");
536
537         /* Wait for the work signal or the timeout. */
538         KeWaitForSingleObject(
539             &bus->ThreadSignal,
540             Executive,
541             KernelMode,
542             FALSE,
543             &timeout
544           );
545         /* Reset the work signal. */
546         KeResetEvent(&bus->ThreadSignal);
547
548         WvBusProcessWorkItems(bus);
549       } /* while bus->alive */
550
551     WvBusCancelWorkItems(bus);
552     WvBusFree_(bus->Dev);
553     return;
554   }
555
556 /**
557  * Start a bus thread.
558  *
559  * @v bus               The bus to start a thread for.
560  * @ret NTSTATUS        The status of the thread creation operation.
561  *
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.
565  */
566 winvblock__lib_func NTSTATUS WvBusStartThread(
567     WV_SP_BUS_T bus
568   ) {
569     OBJECT_ATTRIBUTES obj_attrs;
570     HANDLE thread_handle;
571
572     if (!bus) {
573         DBG("No bus specified!\n");
574         return STATUS_INVALID_PARAMETER;
575       }
576
577     InitializeObjectAttributes(
578         &obj_attrs,
579         NULL,
580         OBJ_KERNEL_HANDLE,
581         NULL,
582         NULL
583       );
584     return PsCreateSystemThread(
585         &thread_handle,
586         THREAD_ALL_ACCESS,
587         &obj_attrs,
588         NULL,
589         NULL,
590         bus__thread_,
591         bus
592       );
593   }
594
595 /**
596  * Initialize a bus node with an associated PDO.
597  *
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
601  */
602 winvblock__lib_func winvblock__bool STDCALL WvBusInitNode(
603     OUT WV_SP_BUS_NODE Node,
604     IN PDEVICE_OBJECT Pdo
605   ) {
606     if (!Node || !Pdo)
607       return FALSE;
608
609     RtlZeroMemory(Node, sizeof *Node);
610     Node->BusPrivate_.Pdo = Pdo;
611     return TRUE;
612   }
613
614 /**
615  * Add a PDO node to a bus' list of children.
616  *
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.
620  *
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.
624  */
625 winvblock__lib_func NTSTATUS STDCALL WvBusAddNode(
626     WV_SP_BUS_T Bus,
627     WV_SP_BUS_NODE Node
628   ) {
629     WV_SP_BUS_WORK_ITEM_ work_item;
630
631     if (
632         !Bus ||
633         !Node ||
634         Bus->Dev->Self->DriverObject != Node->BusPrivate_.Pdo->DriverObject
635       )
636       return STATUS_INVALID_PARAMETER;
637
638     if (Bus->Stop)
639       return STATUS_NO_SUCH_DEVICE;
640
641     if (!(work_item = wv_malloc(sizeof *work_item)))
642       return STATUS_INSUFFICIENT_RESOURCES;
643
644     work_item->Cmd = WvBusWorkItemCmdAddPdo_;
645     work_item->Context.Node = Node;
646     if (!WvBusAddWorkItem_(Bus, work_item)) {
647         wv_free(work_item);
648         return STATUS_UNSUCCESSFUL;
649       }
650     /* Fire and forget. */
651     KeSetEvent(&Bus->ThreadSignal, 0, FALSE);
652     return STATUS_SUCCESS;
653   }
654
655 /**
656  * Remove a PDO node from a bus.
657  *
658  * @v Node              The PDO node to remove from its parent bus.
659  * @ret NTSTATUS        The status of the operation.
660  *
661  * When WvBusProcessWorkItems() is called for the bus, it will
662  * then remove the node.  This is usually from the bus' thread.
663  */
664 winvblock__lib_func NTSTATUS STDCALL WvBusRemoveNode(
665     WV_SP_BUS_NODE Node
666   ) {
667     WV_SP_BUS_T bus;
668     WV_SP_BUS_WORK_ITEM_ work_item;
669
670     if (!Node || !(bus = Node->BusPrivate_.Bus))
671       return STATUS_INVALID_PARAMETER;
672
673     if (bus->Stop)
674       return STATUS_NO_SUCH_DEVICE;
675
676     if (!(work_item = wv_malloc(sizeof *work_item)))
677       return STATUS_INSUFFICIENT_RESOURCES;
678
679     work_item->Cmd = WvBusWorkItemCmdRemovePdo_;
680     work_item->Context.Node = Node;
681     if (!WvBusAddWorkItem_(bus, work_item)) {
682         wv_free(work_item);
683         return STATUS_UNSUCCESSFUL;
684       }
685     /* Fire and forget. */
686     KeSetEvent(&bus->ThreadSignal, 0, FALSE);
687     return STATUS_SUCCESS;
688   }