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