cee342f5d04b0ea9a9e53046118f3f91584d7f9e
[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 "irp.h"
34 #include "driver.h"
35 #include "device.h"
36 #include "bus.h"
37 #include "bus_pnp.h"
38 #include "bus_dev_ctl.h"
39 #include "debug.h"
40
41 /* Globals. */
42 static LIST_ENTRY bus__list_;
43 static KSPIN_LOCK bus__list_lock_;
44 winvblock__bool bus__module_up_ = FALSE;
45
46 /* Forward declarations. */
47 static device__free_func bus__free_;
48 static device__create_pdo_func bus__create_pdo_;
49
50 /**
51  * Tear down the global, bus-common environment.
52  */
53 void bus__module_shutdown(void) {
54
55     DBG("Entry\n");
56     bus__module_up_ = FALSE;
57     DBG("Exit\n");
58     return;
59   }
60
61 /**
62  * Add a child node to the bus.
63  *
64  * @v bus_ptr           Points to the bus receiving the child.
65  * @v dev_ptr           Points to the child device to add.
66  * @ret                 TRUE for success, FALSE for failure.
67  */
68 winvblock__lib_func winvblock__bool STDCALL bus__add_child(
69     IN OUT struct bus__type * bus_ptr,
70     IN OUT device__type_ptr dev_ptr
71   ) {
72     /* The new node's device object. */
73     PDEVICE_OBJECT dev_obj_ptr;
74     /* Walks the child nodes. */
75     device__type_ptr walker;
76
77     DBG("Entry\n");
78     if (!bus__module_up_) {
79         DBG("Bus module not initialized.\n");
80         return FALSE;
81       }
82     if ((bus_ptr == NULL) || (dev_ptr == NULL)) {
83         DBG("No bus or no device!\n");
84         return FALSE;
85       }
86     /* Create the child device. */
87     dev_obj_ptr = device__create_pdo(dev_ptr);
88     if (dev_obj_ptr == NULL) {
89         DBG("PDO creation failed!\n");
90         device__free(dev_ptr);
91         return FALSE;
92       }
93
94     dev_ptr->Parent = bus_ptr->device->Self;
95     /*
96      * Initialize the device.  For disks, this routine is responsible for
97      * determining the disk's geometry appropriately for AoE/RAM/file disks.
98      */
99     dev_ptr->ops.init(dev_ptr);
100     dev_obj_ptr->Flags &= ~DO_DEVICE_INITIALIZING;
101     /* Add the new device's extension to the bus' list of children. */
102     if (bus_ptr->first_child_ptr == NULL) {
103         bus_ptr->first_child_ptr = dev_ptr;
104       } else {
105         walker = bus_ptr->first_child_ptr;
106         while (walker->next_sibling_ptr != NULL)
107           walker = walker->next_sibling_ptr;
108         walker->next_sibling_ptr = dev_ptr;
109       }
110     bus_ptr->Children++;
111     if (bus_ptr->PhysicalDeviceObject != NULL) {
112         IoInvalidateDeviceRelations(
113             bus_ptr->PhysicalDeviceObject,
114             BusRelations
115           );
116       }
117     DBG("Exit\n");
118     return TRUE;
119   }
120
121 static NTSTATUS STDCALL sys_ctl(
122     IN PDEVICE_OBJECT DeviceObject,
123     IN PIRP Irp,
124     IN PIO_STACK_LOCATION Stack,
125     IN struct _device__type * dev_ptr,
126     OUT winvblock__bool_ptr completion_ptr
127   ) {
128     struct bus__type * bus_ptr = bus__get(dev_ptr);
129     PDEVICE_OBJECT lower = bus_ptr->LowerDeviceObject;
130
131     DBG("...\n");
132     IoSkipCurrentIrpStackLocation(Irp);
133     *completion_ptr = TRUE;
134     return lower ? IoCallDriver(lower, Irp) : STATUS_SUCCESS;
135   }
136
137 static NTSTATUS STDCALL power(
138     IN PDEVICE_OBJECT DeviceObject,
139     IN PIRP Irp,
140     IN PIO_STACK_LOCATION Stack,
141     IN struct _device__type * dev_ptr,
142     OUT winvblock__bool_ptr completion_ptr
143   ) {
144     struct bus__type * bus_ptr = bus__get(dev_ptr);
145     PDEVICE_OBJECT lower = bus_ptr->LowerDeviceObject;
146
147     PoStartNextPowerIrp(Irp);
148     IoSkipCurrentIrpStackLocation(Irp);
149     *completion_ptr = TRUE;
150     return lower ? PoCallDriver(lower, Irp) : STATUS_SUCCESS;
151   }
152
153 NTSTATUS STDCALL bus__get_dev_capabilities(
154     IN PDEVICE_OBJECT DeviceObject,
155     IN PDEVICE_CAPABILITIES DeviceCapabilities
156   ) {
157     IO_STATUS_BLOCK ioStatus;
158     KEVENT pnpEvent;
159     NTSTATUS status;
160     PDEVICE_OBJECT targetObject;
161     PIO_STACK_LOCATION irpStack;
162     PIRP pnpIrp;
163
164     RtlZeroMemory(DeviceCapabilities, sizeof (DEVICE_CAPABILITIES));
165     DeviceCapabilities->Size = sizeof (DEVICE_CAPABILITIES);
166     DeviceCapabilities->Version = 1;
167     DeviceCapabilities->Address = -1;
168     DeviceCapabilities->UINumber = -1;
169
170     KeInitializeEvent(&pnpEvent, NotificationEvent, FALSE);
171     targetObject = IoGetAttachedDeviceReference(DeviceObject);
172     pnpIrp = IoBuildSynchronousFsdRequest(
173         IRP_MJ_PNP,
174         targetObject,
175         NULL,
176         0,
177         NULL,
178         &pnpEvent,
179         &ioStatus
180       );
181     if (pnpIrp == NULL) {
182         status = STATUS_INSUFFICIENT_RESOURCES;
183       } else {
184         pnpIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
185         irpStack = IoGetNextIrpStackLocation(pnpIrp);
186         RtlZeroMemory(irpStack, sizeof (IO_STACK_LOCATION));
187         irpStack->MajorFunction = IRP_MJ_PNP;
188         irpStack->MinorFunction = IRP_MN_QUERY_CAPABILITIES;
189         irpStack->Parameters.DeviceCapabilities.Capabilities =
190           DeviceCapabilities;
191         status = IoCallDriver(targetObject, pnpIrp);
192         if (status == STATUS_PENDING) {
193             KeWaitForSingleObject(
194                 &pnpEvent,
195                 Executive,
196                 KernelMode,
197                 FALSE,
198                 NULL
199               );
200             status = ioStatus.Status;
201           }
202       }
203     ObDereferenceObject(targetObject);
204     return status;
205   }
206
207 static NTSTATUS STDCALL attach_fdo(
208     IN PDRIVER_OBJECT DriverObject,
209     IN PDEVICE_OBJECT PhysicalDeviceObject
210   ) {
211     PLIST_ENTRY walker;
212     struct bus__type * bus_ptr;
213     NTSTATUS status;
214     PUNICODE_STRING dev_name = NULL;
215     PDEVICE_OBJECT fdo = NULL;
216     device__type_ptr dev_ptr;
217
218     DBG("Entry\n");
219     /* Search for the associated bus. */
220     walker = bus__list_.Flink;
221     while (walker != &bus__list_) {
222         bus_ptr = CONTAINING_RECORD(walker, struct bus__type, tracking);
223         if (bus_ptr->PhysicalDeviceObject == PhysicalDeviceObject)
224           break;
225         walker = walker->Flink;
226       }
227     /* If we get back to the list head, we need to create a bus. */
228     if (walker == &bus__list_) {
229         DBG("No bus->PDO association.  Creating a new bus\n");
230         bus_ptr = bus__create();
231         if (bus_ptr == NULL) {
232             return Error(
233                 "Could not create a bus!\n",
234                 STATUS_INSUFFICIENT_RESOURCES
235               );
236           }
237       }
238     /* This bus might have an associated name. */
239     if (bus_ptr->named)
240       dev_name = &bus_ptr->dev_name;
241     /* Create the bus FDO. */
242     status = IoCreateDevice(
243         DriverObject,
244         sizeof (driver__dev_ext),
245         dev_name,
246         FILE_DEVICE_CONTROLLER,
247         FILE_DEVICE_SECURE_OPEN,
248         FALSE,
249         &fdo
250       );
251     if (!NT_SUCCESS(status)) {
252         device__free(bus_ptr->device);
253         return Error("IoCreateDevice", status);
254       }
255     /* DosDevice symlink. */
256     if (bus_ptr->named) {
257         status = IoCreateSymbolicLink(
258             &bus_ptr->dos_dev_name,
259             &bus_ptr->dev_name
260           );
261       }
262     if (!NT_SUCCESS(status)) {
263         IoDeleteDevice(fdo);
264         device__free(bus_ptr->device);
265         return Error("IoCreateSymbolicLink", status);
266       }
267
268     /* Set associations for the bus, device, FDO, PDO. */
269     dev_ptr = bus_ptr->device;
270     device__set(fdo, dev_ptr);
271     dev_ptr->Self = fdo;
272
273     bus_ptr->PhysicalDeviceObject = PhysicalDeviceObject;
274     fdo->Flags |= DO_DIRECT_IO;         /* FIXME? */
275     fdo->Flags |= DO_POWER_INRUSH;      /* FIXME? */
276     /* Add the bus to the device tree. */
277     if (PhysicalDeviceObject != NULL) {
278         bus_ptr->LowerDeviceObject = IoAttachDeviceToDeviceStack(
279             fdo,
280             PhysicalDeviceObject
281           );
282         if (bus_ptr->LowerDeviceObject == NULL) {
283             IoDeleteDevice(fdo);
284             device__free(bus_ptr->device);
285             return Error("IoAttachDeviceToDeviceStack", STATUS_NO_SUCH_DEVICE);
286           }
287       }
288     fdo->Flags &= ~DO_DEVICE_INITIALIZING;
289     #ifdef RIS
290     dev_ptr->State = Started;
291     #endif
292     DBG("Exit\n");
293     return STATUS_SUCCESS;
294   }
295
296 /* Bus dispatch routine. */
297 static NTSTATUS STDCALL bus_dispatch(
298     IN PDEVICE_OBJECT dev,
299     IN PIRP irp
300   ) {
301     NTSTATUS status;
302     winvblock__bool completion = FALSE;
303     static const irp__handling handling_table[] = {
304         /*
305          * Major, minor, any major?, any minor?, handler
306          * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
307          * Note that the fall-through case must come FIRST!
308          * Why? It sets completion to true, so others won't be called.
309          */
310         {                     0, 0,  TRUE, TRUE, driver__not_supported },
311         {          IRP_MJ_CLOSE, 0, FALSE, TRUE,  driver__create_close },
312         {         IRP_MJ_CREATE, 0, FALSE, TRUE,  driver__create_close },
313         { IRP_MJ_SYSTEM_CONTROL, 0, FALSE, TRUE,               sys_ctl },
314         {          IRP_MJ_POWER, 0, FALSE, TRUE,                 power },
315         { IRP_MJ_DEVICE_CONTROL, 0, FALSE, TRUE, bus_dev_ctl__dispatch },
316         {            IRP_MJ_PNP, 0, FALSE, TRUE,       bus_pnp__simple },
317         {            IRP_MJ_PNP,
318                IRP_MN_START_DEVICE, FALSE, FALSE,   bus_pnp__start_dev },
319         {            IRP_MJ_PNP,
320               IRP_MN_REMOVE_DEVICE, FALSE, FALSE,  bus_pnp__remove_dev },
321         {            IRP_MJ_PNP,
322      IRP_MN_QUERY_DEVICE_RELATIONS, FALSE, FALSE,
323                                           bus_pnp__query_dev_relations },
324       };
325
326     /* Try registered mini IRP handling tables first.  Deprecated. */
327     status = irp__process(
328         dev,
329         irp,
330         IoGetCurrentIrpStackLocation(irp),
331         device__get(dev),
332         &completion
333       );
334     /* Fall through to the bus defaults, if needed. */
335     if (status == STATUS_NOT_SUPPORTED && !completion)
336       status = irp__process_with_table(
337           dev,
338           irp,
339           handling_table,
340           sizeof handling_table,
341           &completion
342         );
343     #ifdef DEBUGIRPS
344     if (status != STATUS_PENDING)
345       Debug_IrpEnd(irp, status);
346     #endif
347
348     return status;
349   }
350
351 /* Initialize a bus. */
352 static winvblock__bool STDCALL bus__init_(IN device__type_ptr dev) {
353     return TRUE;
354   }
355
356 /**
357  * Create a new bus.
358  *
359  * @ret bus_ptr         The address of a new bus, or NULL for failure.
360  *
361  * This function should not be confused with a PDO creation routine, which is
362  * actually implemented for each device type.  This routine will allocate a
363  * bus__type, track it in a global list, as well as populate the bus
364  * with default values.
365  */
366 winvblock__lib_func struct bus__type * bus__create(void) {
367     device__type_ptr dev_ptr;
368     struct bus__type * bus_ptr;
369
370     if (!bus__module_up_) {
371         DBG("Bus module not initialized.\n");
372         return NULL;
373       }
374     /* Try to create a device. */
375     dev_ptr = device__create();
376     if (dev_ptr == NULL)
377       goto err_nodev;
378     /*
379      * Bus devices might be used for booting and should
380      * not be allocated from a paged memory pool.
381      */
382     bus_ptr = wv_mallocz(sizeof *bus_ptr);
383     if (bus_ptr == NULL)
384       goto err_nobus;
385     /* Track the new bus in our global list. */
386     ExInterlockedInsertTailList(
387         &bus__list_,
388         &bus_ptr->tracking,
389         &bus__list_lock_
390       );
391     /* Populate non-zero device defaults. */
392     bus_ptr->device = dev_ptr;
393     bus_ptr->prev_free = dev_ptr->ops.free;
394     dev_ptr->dispatch = bus_dispatch;
395     dev_ptr->ops.create_pdo = bus__create_pdo_;
396     dev_ptr->ops.init = bus__init_;
397     dev_ptr->ops.free = bus__free_;
398     dev_ptr->ext = bus_ptr;
399     dev_ptr->IsBus = TRUE;
400     KeInitializeSpinLock(&bus_ptr->SpinLock);
401
402     return bus_ptr;
403
404     err_nobus:
405
406     device__free(dev_ptr);
407     err_nodev:
408
409     return NULL;
410   }
411
412 /**
413  * Create a bus PDO.
414  *
415  * @v dev               Populate PDO dev. ext. space from these details.
416  * @ret pdo             Points to the new PDO, or is NULL upon failure.
417  *
418  * Returns a Physical Device Object pointer on success, NULL for failure.
419  */
420 static PDEVICE_OBJECT STDCALL bus__create_pdo_(IN device__type_ptr dev) {
421     PDEVICE_OBJECT pdo = NULL;
422     struct bus__type * bus;
423     NTSTATUS status;
424
425     /* Note the bus device needing a PDO. */
426     if (dev == NULL) {
427         DBG("No device passed\n");
428         return NULL;
429       }
430     bus = bus__get(dev);
431     /* Create the PDO. */
432     status = IoCreateDevice(
433         dev->DriverObject,
434         sizeof (driver__dev_ext),
435         &bus->dev_name,
436         FILE_DEVICE_CONTROLLER,
437         FILE_DEVICE_SECURE_OPEN,
438         FALSE,
439         &pdo
440       );
441     if (pdo == NULL) {
442         DBG("IoCreateDevice() failed!\n");
443         goto err_pdo;
444       }
445     /* DosDevice symlink. */
446     if (bus->named) {
447         status = IoCreateSymbolicLink(
448             &bus->dos_dev_name,
449             &bus->dev_name
450           );
451       }
452     if (!NT_SUCCESS(status)) {
453         DBG("IoCreateSymbolicLink");
454         goto err_name;
455       }
456
457     /* Set associations for the bus, device, PDO. */
458     device__set(pdo, dev);
459     dev->Self = bus->PhysicalDeviceObject = pdo;
460
461     /* Set some DEVICE_OBJECT status. */
462     pdo->Flags |= DO_DIRECT_IO;         /* FIXME? */
463     pdo->Flags |= DO_POWER_INRUSH;      /* FIXME? */
464     pdo->Flags &= ~DO_DEVICE_INITIALIZING;
465     #ifdef RIS
466     dev->State = Started;
467     #endif
468
469     return pdo;
470
471     err_name:
472
473     IoDeleteDevice(pdo);
474     err_pdo:
475
476     return NULL;
477   }
478
479 /**
480  * Initialize the global, bus-common environment.
481  *
482  * @ret ntstatus        STATUS_SUCCESS or the NTSTATUS for a failure.
483  */
484 NTSTATUS bus__module_init(void) {
485     struct bus__type * boot_bus_ptr;
486
487     /* Initialize the global list of devices. */
488     InitializeListHead(&bus__list_);
489     KeInitializeSpinLock(&bus__list_lock_);
490     /* We handle AddDevice call-backs for the driver. */
491     driver__obj_ptr->DriverExtension->AddDevice = attach_fdo;
492     bus__module_up_ = TRUE;
493
494     return STATUS_SUCCESS;
495   }
496
497 /**
498  * Default bus deletion operation.
499  *
500  * @v dev_ptr           Points to the bus device to delete.
501  */
502 static void STDCALL bus__free_(IN device__type_ptr dev_ptr) {
503     struct bus__type * bus_ptr = bus__get(dev_ptr);
504     /* Free the "inherited class". */
505     bus_ptr->prev_free(dev_ptr);
506     /*
507      * Track the bus deletion in our global list.  Unfortunately,
508      * for now we have faith that a bus won't be deleted twice and
509      * result in a race condition.  Something to keep in mind...
510      */
511     ExInterlockedRemoveHeadList(bus_ptr->tracking.Blink, &bus__list_lock_);
512
513     wv_free(bus_ptr);
514   }
515
516 /**
517  * Get a bus from a device.
518  *
519  * @v dev       A pointer to a device.
520  * @ret         A pointer to the device's associated bus.
521  */
522 extern winvblock__lib_func struct bus__type * bus__get(device__type_ptr dev) {
523     return dev->ext;
524   }