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