7ab34455ed3f2e3f457d2063697176a0df644ec9
[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->Dev->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->Dev;
256
257     RtlZeroMemory(bus, sizeof *bus);
258     /* Populate non-zero bus device defaults. */
259     bus->Dev = dev;
260     bus->BusPrivate_.PrevFree = 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->Dev = 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         NULL,
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
346     /* Set associations for the bus, device, PDO. */
347     device__set(pdo, dev);
348     dev->Self = bus->PhysicalDeviceObject = pdo;
349
350     /* Set some DEVICE_OBJECT status. */
351     pdo->Flags |= DO_DIRECT_IO;         /* FIXME? */
352     pdo->Flags |= DO_POWER_INRUSH;      /* FIXME? */
353     pdo->Flags &= ~DO_DEVICE_INITIALIZING;
354     #ifdef RIS
355     dev->State = Started;
356     #endif
357
358     return pdo;
359
360     IoDeleteDevice(pdo);
361     err_pdo:
362
363     return NULL;
364   }
365
366 /**
367  * Default bus deletion operation.
368  *
369  * @v dev_ptr           Points to the bus device to delete.
370  */
371 static void STDCALL bus__free_(IN struct device__type * dev_ptr) {
372     WV_SP_BUS_T bus_ptr = WvBusFromDev(dev_ptr);
373     /* Free the "inherited class". */
374     bus_ptr->BusPrivate_.PrevFree(dev_ptr);
375
376     wv_free(bus_ptr);
377   }
378
379 /**
380  * Get a bus from a device.
381  *
382  * @v dev       A pointer to a device.
383  * @ret         A pointer to the device's associated bus.
384  */
385 extern winvblock__lib_func WV_SP_BUS_T WvBusFromDev(
386     struct device__type * dev
387   ) {
388     return dev->ext;
389   }
390
391 /**
392  * Add a work item for a bus to process.
393  *
394  * @v bus                       The bus to process the work item.
395  * @v work_item                 The work item to add.
396  * @ret winvblock__bool         TRUE if added, else FALSE
397  *
398  * Note that this function will initialize the work item's completion signal.
399  */
400 static winvblock__bool bus__add_work_item_(
401     WV_SP_BUS_T bus,
402     WV_SP_BUS_WORK_ITEM_ work_item
403   ) {
404     ExInterlockedInsertTailList(
405         &bus->work_items,
406         &work_item->Link,
407         &bus->work_items_lock
408       );
409
410     return TRUE;
411   }
412
413 /**
414  * Get (and dequeue) a work item from a bus' queue.
415  *
416  * @v bus                       The bus processing the work item.
417  * @ret bus__work_item_         The work item, or NULL for an empty queue.
418  */
419 static WV_SP_BUS_WORK_ITEM_ bus__get_work_item_(
420     WV_SP_BUS_T bus
421   ) {
422     PLIST_ENTRY list_entry;
423
424     list_entry = ExInterlockedRemoveHeadList(
425         &bus->work_items,
426         &bus->work_items_lock
427       );
428     if (!list_entry)
429       return NULL;
430
431     return CONTAINING_RECORD(list_entry, WV_S_BUS_WORK_ITEM_, Link);
432   }
433
434 /**
435  * Process work items for a bus.
436  *
437  * @v bus               The bus to process its work items.
438  */
439 winvblock__lib_func void WvBusProcessWorkItems(WV_SP_BUS_T bus) {
440     WV_SP_BUS_WORK_ITEM_ work_item;
441     WV_SP_BUS_NODE node;
442
443     while (work_item = bus__get_work_item_(bus)) {
444         switch (work_item->Cmd) {
445             case WvBusWorkItemCmdAddPdo_:
446               DBG("Adding PDO to bus...\n");
447
448               node = work_item->Context.Node;
449               /* It's too bad about having both linked list and bus ref. */
450               node->BusPrivate_.Bus = bus;
451               ObReferenceObject(node->BusPrivate_.Pdo);
452               InsertTailList(&bus->BusPrivate_.Nodes, &node->BusPrivate_.Link);
453               bus->BusPrivate_.NodeCount++;
454               break;
455
456             case WvBusWorkItemCmdRemovePdo_:
457               DBG("Removing PDO from bus...\n");
458
459               node = work_item->Context.Node;
460               RemoveEntryList(&node->BusPrivate_.Link);
461               ObDereferenceObject(node->BusPrivate_.Pdo);
462               bus->BusPrivate_.NodeCount--;
463               break;
464
465             default:
466               DBG("Unknown work item type!\n");
467           }
468         wv_free(work_item);
469       }
470     return;
471   }
472
473 /* The device__type::ops.free implementation for a threaded bus. */
474 static void STDCALL bus__thread_free_(IN struct device__type * dev) {
475     WV_SP_BUS_T bus = WvBusFromDev(dev);
476
477     bus->thread = (WV_FP_BUS_THREAD) 0;
478     KeSetEvent(&bus->work_signal, 0, FALSE);
479     return;
480   }
481
482 /**
483  * The bus thread wrapper.
484  *
485  * @v context           The thread context.  In our case, it points to
486  *                      the bus that the thread should use in processing.
487  */
488 static void STDCALL bus__thread_(IN void * context) {
489     WV_SP_BUS_T bus = context;
490
491     if (!bus || !bus->thread) {
492         DBG("No bus or no thread!\n");
493         return;
494       }
495
496     bus->thread(bus);
497     return;
498   }
499
500 /**
501  * The default bus thread routine.
502  *
503  * @v bus       Points to the bus device for the thread to work with.
504  *
505  * Note that if you implement your own bus type using this library,
506  * you can override the thread routine with your own.  If you do so,
507  * your thread routine should call WvBusProcessWorkItems() within
508  * its loop.  To start a bus thread, use WvBusStartThread()
509  * If you implement your own thread routine, you are also responsible
510  * for freeing the bus.
511  */
512 static void STDCALL bus__default_thread_(IN WV_SP_BUS_T bus) {
513     LARGE_INTEGER timeout;
514
515     /* Wake up at least every 30 seconds. */
516     timeout.QuadPart = -300000000LL;
517
518     /* Hook device__type::ops.free() */
519     bus->Dev->ops.free = bus__thread_free_;
520
521     /* When bus::thread is cleared, we shut down. */
522     while (bus->thread) {
523         DBG("Alive.\n");
524
525         /* Wait for the work signal or the timeout. */
526         KeWaitForSingleObject(
527             &bus->work_signal,
528             Executive,
529             KernelMode,
530             FALSE,
531             &timeout
532           );
533         /* Reset the work signal. */
534         KeResetEvent(&bus->work_signal);
535
536         WvBusProcessWorkItems(bus);
537       } /* while bus->alive */
538
539     bus__free_(bus->Dev);
540     return;
541   }
542
543 /**
544  * Start a bus thread.
545  *
546  * @v bus               The bus to start a thread for.
547  * @ret NTSTATUS        The status of the thread creation operation.
548  *
549  * Also see WV_F_BUS_THREAD in the header for details about the prototype
550  * for implementing your own bus thread routine.  You set bus::thread to
551  * specify your own thread routine, then call this function to start it.
552  */
553 winvblock__lib_func NTSTATUS WvBusStartThread(
554     WV_SP_BUS_T bus
555   ) {
556     OBJECT_ATTRIBUTES obj_attrs;
557     HANDLE thread_handle;
558
559     if (!bus) {
560         DBG("No bus specified!\n");
561         return STATUS_INVALID_PARAMETER;
562       }
563
564     InitializeObjectAttributes(
565         &obj_attrs,
566         NULL,
567         OBJ_KERNEL_HANDLE,
568         NULL,
569         NULL
570       );
571     return PsCreateSystemThread(
572         &thread_handle,
573         THREAD_ALL_ACCESS,
574         &obj_attrs,
575         NULL,
576         NULL,
577         bus__thread_,
578         bus
579       );
580   }
581
582 /**
583  * Initialize a bus node with an associated PDO.
584  *
585  * @v Node              The node to initialize.
586  * @v Pdo               The PDO to associate the node with.
587  * @ret winvblock__bool FALSE for a NULL argument, otherwise TRUE
588  */
589 winvblock__lib_func winvblock__bool STDCALL WvBusInitNode(
590     OUT WV_SP_BUS_NODE Node,
591     IN PDEVICE_OBJECT Pdo
592   ) {
593     if (!Node || !Pdo)
594       return FALSE;
595
596     RtlZeroMemory(Node, sizeof *Node);
597     Node->BusPrivate_.Pdo = Pdo;
598     return TRUE;
599   }
600
601 /**
602  * Add a PDO node to a bus' list of children.
603  *
604  * @v Bus               The bus to add the node to.
605  * @v Node              The PDO node to add to the bus.
606  * @ret NTSTATUS        The status of the operation.
607  *
608  * Do not attempt to add the same node to more than one bus.
609  * When WvBusProcessWorkItems() is called for the bus, the
610  * node will be added.  This is usually from the bus' thread.
611  */
612 winvblock__lib_func NTSTATUS STDCALL WvBusAddNode(
613     WV_SP_BUS_T Bus,
614     WV_SP_BUS_NODE Node
615   ) {
616     WV_SP_BUS_WORK_ITEM_ work_item;
617
618     if (
619         !Bus ||
620         !Node ||
621         Bus->Dev->Self->DriverObject != Node->BusPrivate_.Pdo->DriverObject
622       )
623       return STATUS_INVALID_PARAMETER;
624
625     if (Bus->Stop)
626       return STATUS_NO_SUCH_DEVICE;
627
628     if (!(work_item = wv_malloc(sizeof *work_item)))
629       return STATUS_INSUFFICIENT_RESOURCES;
630
631     work_item->Cmd = WvBusWorkItemCmdAddPdo_;
632     work_item->Context.Node = Node;
633     if (!bus__add_work_item_(Bus, work_item)) {
634         wv_free(work_item);
635         return STATUS_UNSUCCESSFUL;
636       }
637     /* Fire and forget. */
638     KeSetEvent(&Bus->work_signal, 0, FALSE);
639     return STATUS_SUCCESS;
640   }
641
642 /**
643  * Remove a PDO node from a bus.
644  *
645  * @v Node              The PDO node to remove from its parent bus.
646  * @ret NTSTATUS        The status of the operation.
647  *
648  * When WvBusProcessWorkItems() is called for the bus, it will
649  * then remove the node.  This is usually from the bus' thread.
650  */
651 winvblock__lib_func NTSTATUS STDCALL WvBusRemoveNode(
652     WV_SP_BUS_NODE Node
653   ) {
654     WV_SP_BUS_T bus;
655     WV_SP_BUS_WORK_ITEM_ work_item;
656
657     if (!Node || !(bus = Node->BusPrivate_.Bus))
658       return STATUS_INVALID_PARAMETER;
659
660     if (bus->Stop)
661       return STATUS_NO_SUCH_DEVICE;
662
663     if (!(work_item = wv_malloc(sizeof *work_item)))
664       return STATUS_INSUFFICIENT_RESOURCES;
665
666     work_item->Cmd = WvBusWorkItemCmdRemovePdo_;
667     work_item->Context.Node = Node;
668     if (!bus__add_work_item_(bus, work_item)) {
669         wv_free(work_item);
670         return STATUS_UNSUCCESSFUL;
671       }
672     /* Fire and forget. */
673     KeSetEvent(&bus->work_signal, 0, FALSE);
674     return STATUS_SUCCESS;
675   }