6e41a1ec3b85f2b8a89ad42b664cacca8fc6ec85
[people/sha0/winvblock.git] / src / winvblock / driver.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  * Driver specifics.
26  */
27
28 #include <stdio.h>
29 #include <ntddk.h>
30 #include <scsi.h>
31
32 #include "winvblock.h"
33 #include "wv_stdlib.h"
34 #include "wv_string.h"
35 #include "portable.h"
36 #include "driver.h"
37 #include "bus.h"
38 #include "device.h"
39 #include "disk.h"
40 #include "registry.h"
41 #include "mount.h"
42 #include "filedisk.h"
43 #include "ramdisk.h"
44 #include "debug.h"
45
46 /* Names for the main bus. */
47 #define WV_M_BUS_NAME_ (L"\\Device\\" winvblock__literal_w)
48 #define WV_M_BUS_DOSNAME_ (L"\\DosDevices\\" winvblock__literal_w)
49
50 /* Exported. */
51 PDRIVER_OBJECT WvDriverObj = NULL;
52
53 /* Globals. */
54 static void * WvDriverStateHandle_;
55 static winvblock__bool WvDriverStarted_ = FALSE;
56 static PDEVICE_OBJECT WvDriverBusFdo_ = NULL;
57 static KSPIN_LOCK WvDriverBusFdoLock_;
58 static UNICODE_STRING WvDriverBusName_ = {
59     sizeof WV_M_BUS_NAME_,
60     sizeof WV_M_BUS_NAME_,
61     WV_M_BUS_NAME_
62   };
63 static UNICODE_STRING WvDriverBusDosname_ = {
64     sizeof WV_M_BUS_DOSNAME_,
65     sizeof WV_M_BUS_DOSNAME_,
66     WV_M_BUS_DOSNAME_
67   };
68 /* The main bus. */
69 static WV_S_BUS_T WvDriverBus_ = {0};
70 static WV_S_DEV_T WvDriverBusDev_ = {0};
71 /* Contains TXTSETUP.SIF/BOOT.INI-style OsLoadOptions parameters. */
72 static LPWSTR WvDriverOsLoadOpts_ = NULL;
73
74 /* Forward declarations. */
75 static driver__dispatch_func driver__dispatch_not_supported_;
76 static driver__dispatch_func driver__dispatch_power_;
77 static driver__dispatch_func driver__dispatch_create_close_;
78 static driver__dispatch_func driver__dispatch_sys_ctl_;
79 static driver__dispatch_func driver__dispatch_dev_ctl_;
80 static driver__dispatch_func driver__dispatch_scsi_;
81 static driver__dispatch_func driver__dispatch_pnp_;
82 static void STDCALL driver__unload_(IN PDRIVER_OBJECT);
83 static WV_F_DEV_DISPATCH WvDriverBusSysCtl_;
84 static WV_F_DEV_CTL WvDriverBusDevCtl_;
85 static WV_F_DEV_DISPATCH WvDriverBusPower_;
86 static WV_F_DEV_PNP WvDriverBusPnp_;
87 static WV_F_BUS_PNP WvDriverBusPnpQueryDevText_;
88
89 static LPWSTR STDCALL get_opt(IN LPWSTR opt_name) {
90     LPWSTR our_opts, the_opt;
91     WCHAR our_sig[] = L"WINVBLOCK=";
92     /* To produce constant integer expressions. */
93     enum {
94         our_sig_len_bytes = sizeof ( our_sig ) - sizeof ( WCHAR ),
95         our_sig_len = our_sig_len_bytes / sizeof ( WCHAR )
96       };
97     size_t opt_name_len, opt_name_len_bytes;
98
99     if (!WvDriverOsLoadOpts_ || !opt_name)
100       return NULL;
101
102     /* Find /WINVBLOCK= options. */
103     our_opts = WvDriverOsLoadOpts_;
104     while (*our_opts != L'\0') {
105         if (!wv_memcmpeq(our_opts, our_sig, our_sig_len_bytes)) {
106             our_opts++;
107             continue;
108           }
109         our_opts += our_sig_len;
110         break;
111       }
112
113     /* Search for the specific option. */
114     the_opt = our_opts;
115     opt_name_len = wcslen(opt_name);
116     opt_name_len_bytes = opt_name_len * sizeof (WCHAR);
117     while (*the_opt != L'\0' && *the_opt != L' ') {
118         if (!wv_memcmpeq(the_opt, opt_name, opt_name_len_bytes)) {
119             while (*the_opt != L'\0' && *the_opt != L' ' && *the_opt != L',')
120               the_opt++;
121             continue;
122           }
123         the_opt += opt_name_len;
124         break;
125       }
126
127     if (*the_opt == L'\0' || *the_opt == L' ')
128       return NULL;
129
130     /* Next should come "=". */
131     if (*the_opt != L'=')
132       return NULL;
133
134     /*
135      * And finally our option's value.  The caller needs
136      * to worry about looking past the end of the option.
137      */
138     the_opt++;
139     if (*the_opt == L'\0' || *the_opt == L' ')
140       return NULL;
141     return the_opt;
142   }
143
144 static NTSTATUS STDCALL driver__attach_fdo_(
145     IN PDRIVER_OBJECT DriverObject,
146     IN PDEVICE_OBJECT PhysicalDeviceObject
147   ) {
148     KIRQL irql;
149     NTSTATUS status;
150     PLIST_ENTRY walker;
151     PDEVICE_OBJECT fdo = NULL;
152     static WV_S_DEV_IRP_MJ irp_mj = {
153         WvDriverBusPower_,
154         WvDriverBusSysCtl_,
155         WvDriverBusDevCtl_,
156         (WV_FP_DEV_SCSI) 0,
157         WvDriverBusPnp_,
158       };
159
160     DBG("Entry\n");
161     /* Do we alreay have our main bus? */
162     if (WvDriverBusFdo_) {
163         DBG("Already have the main bus.  Refusing.\n");
164         status = STATUS_NOT_SUPPORTED;
165         goto err_already_established;
166       }
167     /* Initialize the bus. */
168     WvBusInit(&WvDriverBus_);
169     WvDevInit(&WvDriverBusDev_);
170     /* Create the bus FDO. */
171     status = IoCreateDevice(
172         DriverObject,
173         sizeof (driver__dev_ext),
174         &WvDriverBusName_,
175         FILE_DEVICE_CONTROLLER,
176         FILE_DEVICE_SECURE_OPEN,
177         FALSE,
178         &fdo
179       );
180     if (!NT_SUCCESS(status)) {
181         DBG("IoCreateDevice() failed!\n");
182         goto err_fdo;
183       }
184     /* DosDevice symlink. */
185     status = IoCreateSymbolicLink(
186         &WvDriverBusDosname_,
187         &WvDriverBusName_
188       );
189     if (!NT_SUCCESS(status)) {
190         DBG("IoCreateSymbolicLink() failed!\n");
191         goto err_dos_symlink;
192       }
193     /* Set associations for the bus, device, FDO, PDO. */
194     WvDevForDevObj(fdo, &WvDriverBusDev_);
195     WvDriverBusDev_.Self = WvDriverBus_.Fdo = fdo;
196     WvDriverBusDev_.IsBus = TRUE;
197     WvDriverBusDev_.IrpMj = &irp_mj;
198     WvDriverBus_.QueryDevText = WvDriverBusPnpQueryDevText_;
199     WvDriverBus_.PhysicalDeviceObject = PhysicalDeviceObject;
200     fdo->Flags |= DO_DIRECT_IO;         /* FIXME? */
201     fdo->Flags |= DO_POWER_INRUSH;      /* FIXME? */
202     /* Attach the FDO to the PDO. */
203     WvDriverBus_.LowerDeviceObject = IoAttachDeviceToDeviceStack(
204         fdo,
205         PhysicalDeviceObject
206       );
207     if (WvDriverBus_.LowerDeviceObject == NULL) {
208         status = STATUS_NO_SUCH_DEVICE;
209         DBG("IoAttachDeviceToDeviceStack() failed!\n");
210         goto err_attach;
211       }
212     status = WvBusStartThread(&WvDriverBus_);
213     if (!NT_SUCCESS(status)) {
214         DBG("Couldn't start bus thread!\n");
215         goto err_thread;
216       }
217     /* Ok! */
218     KeAcquireSpinLock(&WvDriverBusFdoLock_, &irql);
219     if (WvDriverBusFdo_) {
220         KeReleaseSpinLock(&WvDriverBusFdoLock_, irql);
221         DBG("Beaten to it!\n");
222         status = STATUS_NOT_SUPPORTED;
223         goto err_race_failed;
224       }
225     fdo->Flags &= ~DO_DEVICE_INITIALIZING;
226     #ifdef RIS
227     WvDriverBus_.Dev.State = Started;
228     #endif
229     WvDriverBusFdo_ = fdo;
230     KeReleaseSpinLock(&WvDriverBusFdoLock_, irql);
231     DBG("Exit\n");
232     return STATUS_SUCCESS;
233
234     err_race_failed:
235
236     err_thread:
237
238     err_attach:
239
240     IoDeleteSymbolicLink(&WvDriverBusDosname_);
241     err_dos_symlink:
242
243     IoDeleteDevice(fdo);
244     err_fdo:
245
246     err_already_established:
247
248     DBG("Exit with failure\n");
249     return status;
250   }
251
252 /* Create the root-enumerated, main bus device. */
253 NTSTATUS STDCALL driver__create_bus_(void) {
254     WV_SP_BUS_T bus;
255     NTSTATUS status;
256     PDEVICE_OBJECT bus_pdo = NULL;
257
258     /* Create the PDO. */
259     IoReportDetectedDevice(
260         WvDriverObj,
261         InterfaceTypeUndefined,
262         -1,
263         -1,
264         NULL,
265         NULL,
266         FALSE,
267         &bus_pdo
268       );
269     if (bus_pdo == NULL) {
270         DBG("IoReportDetectedDevice() went wrong!  Exiting.\n");
271         status = STATUS_UNSUCCESSFUL;
272         goto err_driver_bus;
273       }
274     /* Attach FDO to PDO. */
275     status = driver__attach_fdo_(WvDriverObj, bus_pdo);
276     if (!NT_SUCCESS(status)) {
277         DBG("driver__attach_fdo_() went wrong!\n");
278         goto err_add_dev;
279       }
280     /* PDO created, FDO attached.  All done. */
281     return STATUS_SUCCESS;
282
283     err_add_dev:
284
285     IoDeleteDevice(bus_pdo);
286     err_driver_bus:
287
288     return status;
289   }
290
291 /*
292  * Note the exception to the function naming convention.
293  * TODO: See if a Makefile change is good enough.
294  */
295 NTSTATUS STDCALL DriverEntry(
296     IN PDRIVER_OBJECT DriverObject,
297     IN PUNICODE_STRING RegistryPath
298   ) {
299     NTSTATUS status;
300     int i;
301
302     DBG("Entry\n");
303     if (WvDriverObj) {
304         DBG("Re-entry not allowed!\n");
305         return STATUS_NOT_SUPPORTED;
306       }
307     WvDriverObj = DriverObject;
308     if (WvDriverStarted_)
309       return STATUS_SUCCESS;
310     Debug_Initialize();
311     status = registry__note_os_load_opts(&WvDriverOsLoadOpts_);
312     if (!NT_SUCCESS(status))
313       return Error("registry__note_driver__os_load_opts", status);
314
315     WvDriverStateHandle_ = NULL;
316     KeInitializeSpinLock(&WvDriverBusFdoLock_);
317
318     if ((WvDriverStateHandle_ = PoRegisterSystemState(
319         NULL,
320         ES_CONTINUOUS
321       )) == NULL) {
322         DBG("Could not set system state to ES_CONTINUOUS!!\n");
323       }
324     /*
325      * Set up IRP MajorFunction function table for devices
326      * this driver handles.
327      */
328     for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
329       DriverObject->MajorFunction[i] = driver__dispatch_not_supported_;
330     DriverObject->MajorFunction[IRP_MJ_PNP] = driver__dispatch_pnp_;
331     DriverObject->MajorFunction[IRP_MJ_POWER] = driver__dispatch_power_;
332     DriverObject->MajorFunction[IRP_MJ_CREATE] = driver__dispatch_create_close_;
333     DriverObject->MajorFunction[IRP_MJ_CLOSE] = driver__dispatch_create_close_;
334     DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] =
335       driver__dispatch_sys_ctl_;
336     DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
337       driver__dispatch_dev_ctl_;
338     DriverObject->MajorFunction[IRP_MJ_SCSI] = driver__dispatch_scsi_;
339     /* Set the driver Unload callback. */
340     DriverObject->DriverUnload = driver__unload_;
341     /* Set the driver AddDevice callback. */
342     DriverObject->DriverExtension->AddDevice = driver__attach_fdo_;
343     /* Initialize various modules. */
344     disk__module_init();        /* TODO: Check for error. */
345     filedisk__module_init();    /* TODO: Check for error. */
346     ramdisk__module_init();     /* TODO: Check for error. */
347
348     /*
349      * Always create the root-enumerated, main bus device.
350      * This is required in order to boot from a WinVBlock disk.
351      */
352     status = driver__create_bus_();
353     if(!NT_SUCCESS(status))
354       goto err_bus;
355
356     WvDriverStarted_ = TRUE;
357     DBG("Exit\n");
358     return STATUS_SUCCESS;
359
360     err_bus:
361
362     driver__unload_(DriverObject);
363     DBG("Exit due to failure\n");
364     return status;
365   }
366
367 static NTSTATUS STDCALL driver__dispatch_not_supported_(
368     IN PDEVICE_OBJECT dev_obj,
369     IN PIRP irp
370   ) {
371     irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
372     IoCompleteRequest(irp, IO_NO_INCREMENT);
373     return irp->IoStatus.Status;
374   }
375
376 /**
377  * Common IRP completion routine.
378  *
379  * @v irp               Points to the IRP to complete.
380  * @v info              Number of bytes returned for the IRP, or 0.
381  * @v status            Status for the IRP to complete.
382  * @ret NTSTATUS        Returns the status value, as passed.
383  */
384 winvblock__lib_func NTSTATUS STDCALL driver__complete_irp(
385     IN PIRP irp,
386     IN ULONG_PTR info,
387     IN NTSTATUS status
388   ) {
389     irp->IoStatus.Information = info;
390     irp->IoStatus.Status = status;
391     IoCompleteRequest(irp, IO_NO_INCREMENT);
392     #ifdef DEBUGIRPS
393     Debug_IrpEnd(irp, status);
394     #endif
395     return status;
396   }
397
398 /* Handle a power IRP. */
399 static NTSTATUS driver__dispatch_power_(
400     IN PDEVICE_OBJECT dev_obj,
401     IN PIRP irp
402   ) {
403     /* WvDevFromDevObj() checks for a NULL dev_obj */
404     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
405
406     #ifdef DEBUGIRPS
407     Debug_IrpStart(dev_obj, irp);
408     #endif
409     /* Check that the device exists. */
410     if (!dev || dev->State == WvDevStateDeleted) {
411         /* Even if it doesn't, a power IRP is important! */
412         PoStartNextPowerIrp(irp);
413         return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
414       }
415     /* Call the particular device's power handler. */
416     if (dev->IrpMj && dev->IrpMj->Power)
417       return dev->IrpMj->Power(dev, irp);
418     /* Otherwise, we don't support the IRP. */
419     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
420   }
421
422 /* Handle an IRP_MJ_CREATE or IRP_MJ_CLOSE IRP. */
423 static NTSTATUS driver__dispatch_create_close_(
424     IN PDEVICE_OBJECT dev_obj,
425     IN PIRP irp
426   ) {
427     /* WvDevFromDevObj() checks for a NULL dev_obj */
428     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
429
430     #ifdef DEBUGIRPS
431     Debug_IrpStart(dev_obj, irp);
432     #endif
433     /* Check that the device exists. */
434     if (!dev || dev->State == WvDevStateDeleted)
435       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
436     /* Always succeed with nothing to do. */
437     return driver__complete_irp(irp, 0, STATUS_SUCCESS);
438   }
439
440 /* Handle an IRP_MJ_SYSTEM_CONTROL IRP. */
441 static NTSTATUS driver__dispatch_sys_ctl_(
442     IN PDEVICE_OBJECT dev_obj,
443     IN PIRP irp
444   ) {
445     /* WvDevFromDevObj() checks for a NULL dev_obj */
446     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
447
448     #ifdef DEBUGIRPS
449     Debug_IrpStart(dev_obj, irp);
450     #endif
451     /* Check that the device exists. */
452     if (!dev || dev->State == WvDevStateDeleted)
453       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
454     /* Call the particular device's power handler. */
455     if (dev->IrpMj && dev->IrpMj->SysCtl)
456       return dev->IrpMj->SysCtl(dev, irp);
457     /* Otherwise, we don't support the IRP. */
458     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
459   }
460
461 /* Handle an IRP_MJ_DEVICE_CONTROL IRP. */
462 static NTSTATUS driver__dispatch_dev_ctl_(
463     IN PDEVICE_OBJECT dev_obj,
464     IN PIRP irp
465   ) {
466     /* WvDevFromDevObj() checks for a NULL dev_obj */
467     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
468     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
469
470     #ifdef DEBUGIRPS
471     Debug_IrpStart(dev_obj, irp);
472     #endif
473     /* Check that the device exists. */
474     if (!dev || dev->State == WvDevStateDeleted)
475       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
476     /* Call the particular device's power handler. */
477     if (dev->IrpMj && dev->IrpMj->DevCtl) {
478         return dev->IrpMj->DevCtl(
479             dev,
480             irp,
481             io_stack_loc->Parameters.DeviceIoControl.IoControlCode
482           );
483       }
484     /* Otherwise, we don't support the IRP. */
485     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
486   }
487
488 /* Handle an IRP_MJ_SCSI IRP. */
489 static NTSTATUS driver__dispatch_scsi_(
490     IN PDEVICE_OBJECT dev_obj,
491     IN PIRP irp
492   ) {
493     /* WvDevFromDevObj() checks for a NULL dev_obj */
494     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
495     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
496
497     #ifdef DEBUGIRPS
498     Debug_IrpStart(dev_obj, irp);
499     #endif
500     /* Check that the device exists. */
501     if (!dev || dev->State == WvDevStateDeleted)
502       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
503     /* Call the particular device's power handler. */
504     if (dev->IrpMj && dev->IrpMj->Scsi) {
505         return dev->IrpMj->Scsi(
506             dev,
507             irp,
508             io_stack_loc->Parameters.Scsi.Srb->Function
509           );
510       }
511     /* Otherwise, we don't support the IRP. */
512     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
513   }
514
515 /* Handle an IRP_MJ_PNP IRP. */
516 static NTSTATUS driver__dispatch_pnp_(
517     IN PDEVICE_OBJECT dev_obj,
518     IN PIRP irp
519   ) {
520     /* WvDevFromDevObj() checks for a NULL dev_obj */
521     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
522     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
523     NTSTATUS status;
524
525     #ifdef DEBUGIRPS
526     Debug_IrpStart(dev_obj, irp);
527     #endif
528     /* Check that the device exists. */
529     if (!dev || dev->State == WvDevStateDeleted)
530       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
531     /* Call the particular device's power handler. */
532     if (dev->IrpMj && dev->IrpMj->Pnp) {
533         status = dev->IrpMj->Pnp(
534             dev,
535             irp,
536             io_stack_loc->MinorFunction
537           );
538         if (dev->IsBus) {
539             dev->OldState = WvDriverBus_.OldState;
540             dev->State = WvDriverBus_.State;
541           }
542         return status;
543       }
544     /* Otherwise, we don't support the IRP. */
545     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
546   }
547
548 static void STDCALL driver__unload_(IN PDRIVER_OBJECT DriverObject) {
549     DBG("Unloading...\n");
550     if (WvDriverStateHandle_ != NULL)
551       PoUnregisterSystemState(WvDriverStateHandle_);
552     IoDeleteSymbolicLink(&WvDriverBusDosname_);
553     WvDriverBusFdo_ = NULL;
554     wv_free(WvDriverOsLoadOpts_);
555     WvDriverStarted_ = FALSE;
556     DBG("Done\n");
557   }
558
559 winvblock__lib_func void STDCALL WvDriverCompletePendingIrp(IN PIRP Irp) {
560     #ifdef DEBUGIRPS
561     Debug_IrpEnd(Irp, Irp->IoStatus.Status);
562     #endif
563     IoCompleteRequest(Irp, IO_NO_INCREMENT);
564   }
565
566 /* Note the exception to the function naming convention. */
567 winvblock__lib_func NTSTATUS STDCALL Error(
568     IN PCHAR Message,
569     IN NTSTATUS Status
570   ) {
571     DBG("%s: 0x%08x\n", Message, Status);
572     return Status;
573   }
574
575 /* Pass an IRP_MJ_SYSTEM_CONTROL IRP to the bus. */
576 static NTSTATUS STDCALL WvDriverBusSysCtl_(IN WV_SP_DEV_T dev, IN PIRP irp) {
577     return WvBusSysCtl(&WvDriverBus_, irp);
578   }
579
580 /* Pass a power IRP to the bus. */
581 static NTSTATUS STDCALL WvDriverBusPower_(IN WV_SP_DEV_T dev, IN PIRP irp) {
582     return WvBusPower(&WvDriverBus_, irp);
583   }
584
585 /* Pass an IRP_MJ_PNP to the bus. */
586 static NTSTATUS STDCALL WvDriverBusPnp_(
587     IN WV_SP_DEV_T dev,
588     IN PIRP irp,
589     IN UCHAR code
590   ) {
591     return WvBusPnp(&WvDriverBus_, irp, code);
592   }
593     
594 /**
595  * Add a child node to the bus.
596  *
597  * @v Dev               Points to the child device to add.
598  * @ret                 TRUE for success, FALSE for failure.
599  */
600 winvblock__lib_func winvblock__bool STDCALL WvDriverBusAddDev(
601     IN OUT WV_SP_DEV_T Dev
602   ) {
603     /* The new node's device object. */
604     PDEVICE_OBJECT dev_obj;
605
606     DBG("Entry\n");
607     if (!WvDriverBusFdo_ || !Dev) {
608         DBG("No bus or no device!\n");
609         return FALSE;
610       }
611     /* Create the child device. */
612     dev_obj = WvDevCreatePdo(Dev);
613     if (!dev_obj) {
614         DBG("PDO creation failed!\n");
615         return FALSE;
616       }
617     WvBusInitNode(&Dev->BusNode, dev_obj);
618     /* Associate the parent bus. */
619     Dev->Parent = WvDriverBus_.Fdo;
620     /*
621      * Initialize the device.  For disks, this routine is responsible for
622      * determining the disk's geometry appropriately for AoE/RAM/file disks.
623      */
624     Dev->Ops.Init(Dev);
625     dev_obj->Flags &= ~DO_DEVICE_INITIALIZING;
626     /* Add the new PDO device to the bus' list of children. */
627     WvBusAddNode(&WvDriverBus_, &Dev->BusNode);
628     Dev->DevNum = WvBusGetNodeNum(&Dev->BusNode);
629
630     DBG("Exit\n");
631     return TRUE;
632   }
633
634 static NTSTATUS STDCALL WvDriverBusDevCtlDetach_(
635     IN PIRP irp
636   ) {
637     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
638     winvblock__uint32 unit_num;
639     WV_SP_BUS_NODE walker;
640
641     if (!(io_stack_loc->Control & SL_PENDING_RETURNED)) {
642         NTSTATUS status;
643
644         /* Enqueue the IRP. */
645         status = WvBusEnqueueIrp(&WvDriverBus_, irp);
646         if (status != STATUS_PENDING)
647           /* Problem. */
648           return driver__complete_irp(irp, 0, status);
649         /* Ok. */
650         return status;
651       }
652     /* If we get here, we should be called by WvBusProcessWorkItems() */
653     unit_num = *((winvblock__uint32_ptr) irp->AssociatedIrp.SystemBuffer);
654     DBG("Request to detach unit: %d\n", unit_num);
655
656     walker = NULL;
657     /* For each node on the bus... */
658     while (walker = WvBusGetNextNode(&WvDriverBus_, walker)) {
659         WV_SP_DEV_T dev = WvDevFromDevObj(WvBusGetNodePdo(walker));
660
661         /* If the unit number matches... */
662         if (WvBusGetNodeNum(walker) == unit_num) {
663             /* If it's not a boot-time device... */
664             if (dev->Boot) {
665                 DBG("Cannot detach a boot-time device.\n");
666                 /* Signal error. */
667                 walker = NULL;
668                 break;
669               }
670             /* Detach the node and free it. */
671             DBG("Removing unit %d\n", unit_num);
672             WvBusRemoveNode(walker);
673             WvDevClose(dev);
674             IoDeleteDevice(dev->Self);
675             WvDevFree(dev);
676             break;
677           }
678       }
679     if (!walker)
680       return driver__complete_irp(irp, 0, STATUS_INVALID_PARAMETER);
681     return driver__complete_irp(irp, 0, STATUS_SUCCESS);
682   }
683
684 NTSTATUS STDCALL WvDriverBusDevCtl_(
685     IN WV_SP_DEV_T dev,
686     IN PIRP irp,
687     IN ULONG POINTER_ALIGNMENT code
688   ) {
689     NTSTATUS status;
690
691     switch (code) {
692         case IOCTL_FILE_ATTACH:
693           status = filedisk__attach(dev, irp);
694           break;
695
696         case IOCTL_FILE_DETACH:
697           return WvDriverBusDevCtlDetach_(irp);
698
699         default:
700           irp->IoStatus.Information = 0;
701           status = STATUS_INVALID_DEVICE_REQUEST;
702       }
703
704     irp->IoStatus.Status = status;
705     IoCompleteRequest(irp, IO_NO_INCREMENT);
706     return status;
707   }
708
709 NTSTATUS STDCALL WvDriverGetDevCapabilities(
710     IN PDEVICE_OBJECT DevObj,
711     IN PDEVICE_CAPABILITIES DevCapabilities
712   ) {
713     IO_STATUS_BLOCK io_status;
714     KEVENT pnp_event;
715     NTSTATUS status;
716     PDEVICE_OBJECT target_obj;
717     PIO_STACK_LOCATION io_stack_loc;
718     PIRP pnp_irp;
719
720     RtlZeroMemory(DevCapabilities, sizeof *DevCapabilities);
721     DevCapabilities->Size = sizeof *DevCapabilities;
722     DevCapabilities->Version = 1;
723     DevCapabilities->Address = -1;
724     DevCapabilities->UINumber = -1;
725
726     KeInitializeEvent(&pnp_event, NotificationEvent, FALSE);
727     target_obj = IoGetAttachedDeviceReference(DevObj);
728     pnp_irp = IoBuildSynchronousFsdRequest(
729         IRP_MJ_PNP,
730         target_obj,
731         NULL,
732         0,
733         NULL,
734         &pnp_event,
735         &io_status
736       );
737     if (pnp_irp == NULL) {
738         status = STATUS_INSUFFICIENT_RESOURCES;
739       } else {
740         pnp_irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
741         io_stack_loc = IoGetNextIrpStackLocation(pnp_irp);
742         RtlZeroMemory(io_stack_loc, sizeof *io_stack_loc);
743         io_stack_loc->MajorFunction = IRP_MJ_PNP;
744         io_stack_loc->MinorFunction = IRP_MN_QUERY_CAPABILITIES;
745         io_stack_loc->Parameters.DeviceCapabilities.Capabilities =
746           DevCapabilities;
747         status = IoCallDriver(target_obj, pnp_irp);
748         if (status == STATUS_PENDING) {
749             KeWaitForSingleObject(
750                 &pnp_event,
751                 Executive,
752                 KernelMode,
753                 FALSE,
754                 NULL
755               );
756             status = io_status.Status;
757           }
758       }
759     ObDereferenceObject(target_obj);
760     return status;
761   }
762
763 static NTSTATUS STDCALL WvDriverBusPnpQueryDevText_(
764     IN WV_SP_BUS_T bus,
765     IN PIRP irp
766   ) {
767     WCHAR (*str)[512];
768     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
769     NTSTATUS status;
770     winvblock__uint32 str_len;
771
772     /* Allocate a string buffer. */
773     str = wv_mallocz(sizeof *str);
774     if (str == NULL) {
775         DBG("wv_malloc IRP_MN_QUERY_DEVICE_TEXT\n");
776         status = STATUS_INSUFFICIENT_RESOURCES;
777         goto alloc_str;
778       }
779     /* Determine the query type. */
780     switch (io_stack_loc->Parameters.QueryDeviceText.DeviceTextType) {
781         case DeviceTextDescription:
782           str_len = swprintf(*str, winvblock__literal_w L" Bus") + 1;
783           irp->IoStatus.Information =
784             (ULONG_PTR) wv_palloc(str_len * sizeof *str);
785           if (irp->IoStatus.Information == 0) {
786               DBG("wv_palloc DeviceTextDescription\n");
787               status = STATUS_INSUFFICIENT_RESOURCES;
788               goto alloc_info;
789             }
790           RtlCopyMemory(
791               (PWCHAR) irp->IoStatus.Information,
792               str,
793               str_len * sizeof (WCHAR)
794             );
795           status = STATUS_SUCCESS;
796           goto alloc_info;
797
798         case DeviceTextLocationInformation:
799           str_len = WvDevPnpId(
800               &WvDriverBusDev_,
801               BusQueryInstanceID,
802               str
803             );
804           irp->IoStatus.Information =
805             (ULONG_PTR) wv_palloc(str_len * sizeof *str);
806           if (irp->IoStatus.Information == 0) {
807               DBG("wv_palloc DeviceTextLocationInformation\n");
808               status = STATUS_INSUFFICIENT_RESOURCES;
809               goto alloc_info;
810             }
811           RtlCopyMemory(
812               (PWCHAR) irp->IoStatus.Information,
813               str,
814               str_len * sizeof (WCHAR)
815             );
816           status = STATUS_SUCCESS;
817           goto alloc_info;
818
819         default:
820           irp->IoStatus.Information = 0;
821           status = STATUS_NOT_SUPPORTED;
822       }
823     /* irp->IoStatus.Information not freed. */
824     alloc_info:
825
826     wv_free(str);
827     alloc_str:
828
829     return driver__complete_irp(irp, irp->IoStatus.Information, status);
830   }
831
832 /**
833  * Dummy device feature.
834  */
835
836 /* Prototype safety. */
837 WV_F_DEV_PNP WvDriverDummyPnp_;
838
839 /* Dummy PnP IRP handler. */
840 static NTSTATUS STDCALL WvDriverDummyPnp_(
841     IN WV_SP_DEV_T dev,
842     IN PIRP irp,
843     IN UCHAR code
844   ) {
845     if (code != IRP_MN_QUERY_ID)
846       return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
847
848     return WvDevPnpQueryId(dev, irp);
849   }
850
851 typedef struct WV_DRIVER_ADD_DUMMY_ {
852     WV_FP_DEV_PNP_ID PnpIdFunc;
853     DEVICE_TYPE DevType;
854     ULONG DevCharacteristics;
855     PKEVENT Event;
856     NTSTATUS Status;
857   } WV_S_DRIVER_ADD_DUMMY_, * WV_SP_DRIVER_ADD_DUMMY_;
858
859 /* Prototype safety. */
860 static WV_F_BUS_WORK_ITEM WvDriverAddDummy_;
861 /**
862  * Add a dummy PDO child node in the context of the bus' thread.
863  *
864  * @v context           Points to the WV_S_DRIVER_ADD_DUMMY_ to process.
865  */
866 static void STDCALL WvDriverAddDummy_(void * context) {
867     WV_SP_DRIVER_ADD_DUMMY_ dummy_context = context;
868     NTSTATUS status;
869     PDEVICE_OBJECT pdo = NULL;
870     WV_SP_DEV_T dev;
871     static WV_S_DEV_IRP_MJ irp_mj = {
872         (WV_FP_DEV_DISPATCH) 0,
873         (WV_FP_DEV_DISPATCH) 0,
874         (WV_FP_DEV_CTL) 0,
875         (WV_FP_DEV_SCSI) 0,
876         WvDriverDummyPnp_,
877       };
878
879     status = IoCreateDevice(
880         WvDriverObj,
881         sizeof (driver__dev_ext),
882         NULL,
883         dummy_context->DevType,
884         dummy_context->DevCharacteristics,
885         FALSE,
886         &pdo
887       );
888     if (!NT_SUCCESS(status) || !pdo) {
889         DBG("Couldn't create dummy device.\n");
890         dummy_context->Status = STATUS_INSUFFICIENT_RESOURCES;
891         goto out;
892       }
893
894     dev = wv_malloc(sizeof *dev);
895     if (!dev) {
896         DBG("Couldn't allocate dummy device.\n");
897         dummy_context->Status = STATUS_INSUFFICIENT_RESOURCES;
898         IoDeleteDevice(pdo);
899         goto out;
900       }
901
902     WvDevInit(dev);
903     dev->IrpMj = &irp_mj;
904     dev->Ops.PnpId = dummy_context->PnpIdFunc;
905     dev->Self = pdo;
906     WvDevForDevObj(pdo, dev);
907     WvBusInitNode(&dev->BusNode, pdo);
908     /* Associate the parent bus. */
909     dev->Parent = WvDriverBus_.Fdo;
910     /* Add the new PDO device to the bus' list of children. */
911     WvBusAddNode(&WvDriverBus_, &dev->BusNode);
912     dev->DevNum = WvBusGetNodeNum(&dev->BusNode);
913     pdo->Flags &= ~DO_DEVICE_INITIALIZING;
914     dummy_context->Status = STATUS_SUCCESS;
915
916     out:
917     KeSetEvent(dummy_context->Event, 0, FALSE);
918     return;
919   }
920
921 /**
922  * Produce a dummy PDO node on the main bus.
923  *
924  * @v PnpIdFunc                 The PnP ID query handler for the dummy.
925  * @v DevType                   The type for the dummy device.
926  * @v DevCharacteristics        The dummy device characteristics.
927  * @ret NTSTATUS                The status of the operation.
928  */
929 winvblock__lib_func NTSTATUS STDCALL WvDriverAddDummy(
930     IN WV_FP_DEV_PNP_ID PnpIdFunc,
931     IN DEVICE_TYPE DevType,
932     IN ULONG DevCharacteristics
933   ) {
934     KEVENT event;
935     WV_S_DRIVER_ADD_DUMMY_ context = {
936         PnpIdFunc,
937         DevType,
938         DevCharacteristics,
939         &event,
940         STATUS_UNSUCCESSFUL
941       };
942     WV_S_BUS_CUSTOM_WORK_ITEM work_item = {
943         WvDriverAddDummy_,
944         &context
945       };
946     NTSTATUS status;
947
948     if (!PnpIdFunc)
949       return STATUS_INVALID_PARAMETER;
950
951     KeInitializeEvent(&event, SynchronizationEvent, FALSE);
952
953     status = WvBusEnqueueCustomWorkItem(&WvDriverBus_, &work_item);
954     if (!NT_SUCCESS(status))
955       return status;
956
957     /* Wait for WvDriverAddDummy_() to complete. */
958     KeWaitForSingleObject(
959         &event,
960         Executive,
961         KernelMode,
962         FALSE,
963         NULL
964       );
965
966     return context.Status;
967   }
968
969 /**
970  * Handle a PnP ID query with a WV_S_DRIVER_DUMMY_IDS object.
971  *
972  * @v Irp               The PnP ID query IRP to handle.
973  * @v DummyIds          The object containing the IDs to respond with.
974  * @ret NTSTATUS        The status of the operation.
975  */
976 winvblock__lib_func NTSTATUS STDCALL WvDriverDummyIds(
977     IN PIRP Irp,
978     IN WV_SP_DRIVER_DUMMY_IDS DummyIds
979   ) {
980     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(Irp);
981     BUS_QUERY_ID_TYPE query_type = io_stack_loc->Parameters.QueryId.IdType;
982     const WCHAR * ids;
983     winvblock__uint32 len;
984     NTSTATUS status;
985
986     switch (query_type) {
987         case BusQueryDeviceID:
988           ids = DummyIds->Ids + DummyIds->DevOffset;
989           len = DummyIds->DevLen;
990           break;
991
992         case BusQueryInstanceID:
993           ids = DummyIds->Ids + DummyIds->InstanceOffset;
994           len = DummyIds->InstanceLen;
995           break;
996
997         case BusQueryHardwareIDs:
998           ids = DummyIds->Ids + DummyIds->HardwareOffset;
999           len = DummyIds->HardwareLen;
1000           break;
1001
1002         case BusQueryCompatibleIDs:
1003           ids = DummyIds->Ids + DummyIds->CompatOffset;
1004           len = DummyIds->CompatLen;
1005           break;
1006
1007         default:
1008           return driver__complete_irp(Irp, 0, STATUS_NOT_SUPPORTED);
1009       }
1010
1011     /* Allocate the return buffer. */
1012     Irp->IoStatus.Information = (ULONG_PTR) wv_palloc(len * sizeof *ids);
1013     if (Irp->IoStatus.Information == 0) {
1014         DBG("wv_palloc failed.\n");
1015         status = STATUS_INSUFFICIENT_RESOURCES;
1016         goto alloc_info;
1017       }
1018     /* Copy the working buffer to the return buffer. */
1019     RtlCopyMemory(
1020         (void *) Irp->IoStatus.Information,
1021         ids,
1022         len * sizeof *ids
1023       );
1024     status = STATUS_SUCCESS;
1025
1026     /* irp->IoStatus.Information not freed. */
1027     alloc_info:
1028
1029     return driver__complete_irp(Irp, Irp->IoStatus.Information, status);
1030   }