[bus] Rename bus__process_work_items to WvBusProcessWorkItems
[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 bus_dev_ctl__dispatch;
40 /* IRP_MJ_PNP dispatcher from bus/pnp.c */
41 extern device__pnp_func bus_pnp__dispatch;
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 bus__free_;
60 static device__create_pdo_func bus__create_pdo_;
61 static device__dispatch_func bus__power_;
62 static device__dispatch_func bus__sys_ctl_;
63 static device__pnp_func bus__pnp_dispatch_;
64 static WV_F_BUS_THREAD bus__default_thread_;
65 static winvblock__bool bus__add_work_item_(
66     WV_SP_BUS_T,
67     WV_SP_BUS_WORK_ITEM_
68   );
69 static WV_SP_BUS_WORK_ITEM_ bus__get_work_item_(WV_SP_BUS_T);
70
71 /* Globals. */
72 struct device__irp_mj bus__irp_mj_ = {
73     bus__power_,
74     bus__sys_ctl_,
75     bus_dev_ctl__dispatch,
76     (device__scsi_func *) 0,
77     bus_pnp__dispatch,
78   };
79
80 /**
81  * Add a child node to the bus.
82  *
83  * @v bus_ptr           Points to the bus receiving the child.
84  * @v dev_ptr           Points to the child device to add.
85  * @ret                 TRUE for success, FALSE for failure.
86  */
87 winvblock__lib_func winvblock__bool STDCALL WvBusAddChild(
88     IN OUT WV_SP_BUS_T bus_ptr,
89     IN OUT struct device__type * dev_ptr
90   ) {
91     /* The new node's device object. */
92     PDEVICE_OBJECT dev_obj_ptr;
93     /* Walks the child nodes. */
94     struct device__type * walker;
95     winvblock__uint32 dev_num;
96
97     DBG("Entry\n");
98     if ((bus_ptr == NULL) || (dev_ptr == NULL)) {
99         DBG("No bus or no device!\n");
100         return FALSE;
101       }
102     /* Create the child device. */
103     dev_obj_ptr = device__create_pdo(dev_ptr);
104     if (dev_obj_ptr == NULL) {
105         DBG("PDO creation failed!\n");
106         device__free(dev_ptr);
107         return FALSE;
108       }
109
110     dev_ptr->Parent = bus_ptr->device->Self;
111     /*
112      * Initialize the device.  For disks, this routine is responsible for
113      * determining the disk's geometry appropriately for AoE/RAM/file disks.
114      */
115     dev_ptr->ops.init(dev_ptr);
116     dev_obj_ptr->Flags &= ~DO_DEVICE_INITIALIZING;
117     /* Add the new device's extension to the bus' list of children. */
118     dev_num = 0;
119     if (bus_ptr->first_child == NULL) {
120         bus_ptr->first_child = dev_ptr;
121       } else {
122         walker = bus_ptr->first_child;
123         /* If the first child device number isn't 0... */
124         if (walker->dev_num) {
125             /* We insert before. */
126             dev_ptr->next_sibling_ptr = walker;
127             bus_ptr->first_child = dev_ptr;
128           } else {
129             while (walker->next_sibling_ptr != NULL) {
130                 /* If there's a gap in the device numbers for the bus... */
131                 if (walker->dev_num < walker->next_sibling_ptr->dev_num - 1) {
132                     /* Insert here, instead of at the end. */
133                     dev_num = walker->dev_num + 1;
134                     dev_ptr->next_sibling_ptr = walker->next_sibling_ptr;
135                     walker->next_sibling_ptr = dev_ptr;
136                     break;
137                   }
138                 walker = walker->next_sibling_ptr;
139                 dev_num = walker->dev_num + 1;
140               }
141             /* If we haven't already inserted the device... */
142             if (!dev_ptr->next_sibling_ptr) {
143                 walker->next_sibling_ptr = dev_ptr;
144                 dev_num = walker->dev_num + 1;
145               }
146           }
147       }
148     dev_ptr->dev_num = dev_num;
149     bus_ptr->Children++;
150     if (bus_ptr->PhysicalDeviceObject != NULL) {
151         IoInvalidateDeviceRelations(
152             bus_ptr->PhysicalDeviceObject,
153             BusRelations
154           );
155       }
156     DBG("Exit\n");
157     return TRUE;
158   }
159
160 static NTSTATUS STDCALL bus__sys_ctl_(
161     IN struct device__type * dev,
162     IN PIRP irp
163   ) {
164     WV_SP_BUS_T bus = WvBusFromDev(dev);
165     PDEVICE_OBJECT lower = bus->LowerDeviceObject;
166
167     if (lower) {
168         DBG("Passing IRP_MJ_SYSTEM_CONTROL down\n");
169         IoSkipCurrentIrpStackLocation(irp);
170         return IoCallDriver(lower, irp);
171       }
172     return driver__complete_irp(irp, 0, STATUS_SUCCESS);
173   }
174
175 static NTSTATUS STDCALL bus__power_(
176     IN struct device__type * dev,
177     IN PIRP irp
178   ) {
179     WV_SP_BUS_T bus = WvBusFromDev(dev);
180     PDEVICE_OBJECT lower = bus->LowerDeviceObject;
181
182     PoStartNextPowerIrp(irp);
183     if (lower) {
184         IoSkipCurrentIrpStackLocation(irp);
185         return PoCallDriver(lower, irp);
186       }
187     return driver__complete_irp(irp, 0, STATUS_SUCCESS);
188   }
189
190 NTSTATUS STDCALL WvBusGetDevCapabilities(
191     IN PDEVICE_OBJECT DeviceObject,
192     IN PDEVICE_CAPABILITIES DeviceCapabilities
193   ) {
194     IO_STATUS_BLOCK ioStatus;
195     KEVENT pnpEvent;
196     NTSTATUS status;
197     PDEVICE_OBJECT targetObject;
198     PIO_STACK_LOCATION irpStack;
199     PIRP pnpIrp;
200
201     RtlZeroMemory(DeviceCapabilities, sizeof (DEVICE_CAPABILITIES));
202     DeviceCapabilities->Size = sizeof (DEVICE_CAPABILITIES);
203     DeviceCapabilities->Version = 1;
204     DeviceCapabilities->Address = -1;
205     DeviceCapabilities->UINumber = -1;
206
207     KeInitializeEvent(&pnpEvent, NotificationEvent, FALSE);
208     targetObject = IoGetAttachedDeviceReference(DeviceObject);
209     pnpIrp = IoBuildSynchronousFsdRequest(
210         IRP_MJ_PNP,
211         targetObject,
212         NULL,
213         0,
214         NULL,
215         &pnpEvent,
216         &ioStatus
217       );
218     if (pnpIrp == NULL) {
219         status = STATUS_INSUFFICIENT_RESOURCES;
220       } else {
221         pnpIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
222         irpStack = IoGetNextIrpStackLocation(pnpIrp);
223         RtlZeroMemory(irpStack, sizeof (IO_STACK_LOCATION));
224         irpStack->MajorFunction = IRP_MJ_PNP;
225         irpStack->MinorFunction = IRP_MN_QUERY_CAPABILITIES;
226         irpStack->Parameters.DeviceCapabilities.Capabilities =
227           DeviceCapabilities;
228         status = IoCallDriver(targetObject, pnpIrp);
229         if (status == STATUS_PENDING) {
230             KeWaitForSingleObject(
231                 &pnpEvent,
232                 Executive,
233                 KernelMode,
234                 FALSE,
235                 NULL
236               );
237             status = ioStatus.Status;
238           }
239       }
240     ObDereferenceObject(targetObject);
241     return status;
242   }
243
244 /* Initialize a bus. */
245 static winvblock__bool STDCALL bus__init_(IN struct device__type * dev) {
246     return TRUE;
247   }
248
249 /**
250  * Initialize bus defaults.
251  *
252  * @v bus               Points to the bus to initialize with defaults.
253  */
254 winvblock__lib_func void WvBusInit(WV_SP_BUS_T bus) {
255     struct device__type * dev = bus->device;
256
257     RtlZeroMemory(bus, sizeof *bus);
258     /* Populate non-zero bus device defaults. */
259     bus->device = dev;
260     bus->prev_free = dev->ops.free;
261     bus->thread = bus__default_thread_;
262     KeInitializeSpinLock(&bus->SpinLock);
263     KeInitializeSpinLock(&bus->work_items_lock);
264     InitializeListHead(&bus->work_items);
265     KeInitializeEvent(&bus->work_signal, SynchronizationEvent, FALSE);
266     dev->ops.create_pdo = bus__create_pdo_;
267     dev->ops.init = bus__init_;
268     dev->ops.free = bus__free_;
269     dev->ext = bus;
270     dev->irp_mj = &bus__irp_mj_;
271     dev->IsBus = TRUE;
272   }
273
274 /**
275  * Create a new bus.
276  *
277  * @ret bus_ptr         The address of a new bus, or NULL for failure.
278  *
279  * This function should not be confused with a PDO creation routine, which is
280  * actually implemented for each device type.  This routine will allocate a
281  * WV_S_BUS_T, track it in a global list, as well as populate the bus
282  * with default values.
283  */
284 winvblock__lib_func WV_SP_BUS_T WvBusCreate(void) {
285     struct device__type * dev;
286     WV_SP_BUS_T bus;
287
288     /* Try to create a device. */
289     dev = device__create();
290     if (dev == NULL)
291       goto err_no_dev;
292     /*
293      * Bus devices might be used for booting and should
294      * not be allocated from a paged memory pool.
295      */
296     bus = wv_malloc(sizeof *bus);
297     if (bus == NULL)
298       goto err_no_bus;
299
300     bus->device = dev;
301     WvBusInit(bus);
302     return bus;
303
304     err_no_bus:
305
306     device__free(dev);
307     err_no_dev:
308
309     return NULL;
310   }
311
312 /**
313  * Create a bus PDO.
314  *
315  * @v dev               Populate PDO dev. ext. space from these details.
316  * @ret pdo             Points to the new PDO, or is NULL upon failure.
317  *
318  * Returns a Physical Device Object pointer on success, NULL for failure.
319  */
320 static PDEVICE_OBJECT STDCALL bus__create_pdo_(IN struct device__type * dev) {
321     PDEVICE_OBJECT pdo = NULL;
322     WV_SP_BUS_T bus;
323     NTSTATUS status;
324
325     /* Note the bus device needing a PDO. */
326     if (dev == NULL) {
327         DBG("No device passed\n");
328         return NULL;
329       }
330     bus = WvBusFromDev(dev);
331     /* Create the PDO. */
332     status = IoCreateDevice(
333         dev->DriverObject,
334         sizeof (driver__dev_ext),
335         &bus->dev_name,
336         FILE_DEVICE_CONTROLLER,
337         FILE_DEVICE_SECURE_OPEN,
338         FALSE,
339         &pdo
340       );
341     if (pdo == NULL) {
342         DBG("IoCreateDevice() failed!\n");
343         goto err_pdo;
344       }
345     /* DosDevice symlink. */
346     if (bus->named) {
347         status = IoCreateSymbolicLink(
348             &bus->dos_dev_name,
349             &bus->dev_name
350           );
351       }
352     if (!NT_SUCCESS(status)) {
353         DBG("IoCreateSymbolicLink");
354         goto err_name;
355       }
356
357     /* Set associations for the bus, device, PDO. */
358     device__set(pdo, dev);
359     dev->Self = bus->PhysicalDeviceObject = pdo;
360
361     /* Set some DEVICE_OBJECT status. */
362     pdo->Flags |= DO_DIRECT_IO;         /* FIXME? */
363     pdo->Flags |= DO_POWER_INRUSH;      /* FIXME? */
364     pdo->Flags &= ~DO_DEVICE_INITIALIZING;
365     #ifdef RIS
366     dev->State = Started;
367     #endif
368
369     return pdo;
370
371     err_name:
372
373     IoDeleteDevice(pdo);
374     err_pdo:
375
376     return NULL;
377   }
378
379 /**
380  * Default bus deletion operation.
381  *
382  * @v dev_ptr           Points to the bus device to delete.
383  */
384 static void STDCALL bus__free_(IN struct device__type * dev_ptr) {
385     WV_SP_BUS_T bus_ptr = WvBusFromDev(dev_ptr);
386     /* Free the "inherited class". */
387     bus_ptr->prev_free(dev_ptr);
388
389     wv_free(bus_ptr);
390   }
391
392 /**
393  * Get a bus from a device.
394  *
395  * @v dev       A pointer to a device.
396  * @ret         A pointer to the device's associated bus.
397  */
398 extern winvblock__lib_func WV_SP_BUS_T WvBusFromDev(
399     struct device__type * dev
400   ) {
401     return dev->ext;
402   }
403
404 /**
405  * Add a work item for a bus to process.
406  *
407  * @v bus                       The bus to process the work item.
408  * @v work_item                 The work item to add.
409  * @ret winvblock__bool         TRUE if added, else FALSE
410  *
411  * Note that this function will initialize the work item's completion signal.
412  */
413 static winvblock__bool bus__add_work_item_(
414     WV_SP_BUS_T bus,
415     WV_SP_BUS_WORK_ITEM_ work_item
416   ) {
417     ExInterlockedInsertTailList(
418         &bus->work_items,
419         &work_item->Link,
420         &bus->work_items_lock
421       );
422
423     return TRUE;
424   }
425
426 /**
427  * Get (and dequeue) a work item from a bus' queue.
428  *
429  * @v bus                       The bus processing the work item.
430  * @ret bus__work_item_         The work item, or NULL for an empty queue.
431  */
432 static WV_SP_BUS_WORK_ITEM_ bus__get_work_item_(
433     WV_SP_BUS_T bus
434   ) {
435     PLIST_ENTRY list_entry;
436
437     list_entry = ExInterlockedRemoveHeadList(
438         &bus->work_items,
439         &bus->work_items_lock
440       );
441     if (!list_entry)
442       return NULL;
443
444     return CONTAINING_RECORD(list_entry, WV_S_BUS_WORK_ITEM_, Link);
445   }
446
447 /**
448  * Process work items for a bus.
449  *
450  * @v bus               The bus to process its work items.
451  */
452 winvblock__lib_func void WvBusProcessWorkItems(WV_SP_BUS_T bus) {
453     WV_SP_BUS_WORK_ITEM_ work_item;
454     WV_SP_BUS_NODE node;
455
456     while (work_item = bus__get_work_item_(bus)) {
457         switch (work_item->Cmd) {
458             case WvBusWorkItemCmdAddPdo_:
459               DBG("Adding PDO to bus...\n");
460
461               node = work_item->Context.Node;
462               /* It's too bad about having both linked list and bus ref. */
463               node->BusPrivate_.Bus = bus;
464               ObReferenceObject(node->BusPrivate_.Pdo);
465               InsertTailList(&bus->BusPrivate_.Nodes, &node->BusPrivate_.Link);
466               bus->BusPrivate_.NodeCount++;
467               break;
468
469             case WvBusWorkItemCmdRemovePdo_:
470               DBG("Removing PDO from bus...\n");
471
472               node = work_item->Context.Node;
473               RemoveEntryList(&node->BusPrivate_.Link);
474               ObDereferenceObject(node->BusPrivate_.Pdo);
475               bus->BusPrivate_.NodeCount--;
476               break;
477
478             default:
479               DBG("Unknown work item type!\n");
480           }
481         wv_free(work_item);
482       }
483     return;
484   }
485
486 /* The device__type::ops.free implementation for a threaded bus. */
487 static void STDCALL bus__thread_free_(IN struct device__type * dev) {
488     WV_SP_BUS_T bus = WvBusFromDev(dev);
489
490     bus->thread = (WV_FP_BUS_THREAD) 0;
491     KeSetEvent(&bus->work_signal, 0, FALSE);
492     return;
493   }
494
495 /**
496  * The bus thread wrapper.
497  *
498  * @v context           The thread context.  In our case, it points to
499  *                      the bus that the thread should use in processing.
500  */
501 static void STDCALL bus__thread_(IN void * context) {
502     WV_SP_BUS_T bus = context;
503
504     if (!bus || !bus->thread) {
505         DBG("No bus or no thread!\n");
506         return;
507       }
508
509     bus->thread(bus);
510     return;
511   }
512
513 /**
514  * The default bus thread routine.
515  *
516  * @v bus       Points to the bus device for the thread to work with.
517  *
518  * Note that if you implement your own bus type using this library,
519  * you can override the thread routine with your own.  If you do so,
520  * your thread routine should call WvBusProcessWorkItems() within
521  * its loop.  To start a bus thread, use bus__start_thread()
522  * If you implement your own thread routine, you are also responsible
523  * for freeing the bus.
524  */
525 static void STDCALL bus__default_thread_(IN WV_SP_BUS_T bus) {
526     LARGE_INTEGER timeout;
527
528     /* Wake up at least every 30 seconds. */
529     timeout.QuadPart = -300000000LL;
530
531     /* Hook device__type::ops.free() */
532     bus->device->ops.free = bus__thread_free_;
533
534     /* When bus::thread is cleared, we shut down. */
535     while (bus->thread) {
536         DBG("Alive.\n");
537
538         /* Wait for the work signal or the timeout. */
539         KeWaitForSingleObject(
540             &bus->work_signal,
541             Executive,
542             KernelMode,
543             FALSE,
544             &timeout
545           );
546         /* Reset the work signal. */
547         KeResetEvent(&bus->work_signal);
548
549         WvBusProcessWorkItems(bus);
550       } /* while bus->alive */
551
552     bus__free_(bus->device);
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 bus__start_thread(
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->device->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 (!bus__add_work_item_(Bus, work_item)) {
647         wv_free(work_item);
648         return STATUS_UNSUCCESSFUL;
649       }
650     /* Fire and forget. */
651     KeSetEvent(&Bus->work_signal, 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 (!bus__add_work_item_(bus, work_item)) {
682         wv_free(work_item);
683         return STATUS_UNSUCCESSFUL;
684       }
685     /* Fire and forget. */
686     KeSetEvent(&bus->work_signal, 0, FALSE);
687     return STATUS_SUCCESS;
688   }