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