d67928cdbdf8cff7d1800add7f7540d7a8b3e008
[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 /* Establish the bus PDO. */
253 static NTSTATUS STDCALL WvDriverBusEstablish_(IN PUNICODE_STRING RegistryPath) {
254     WV_SP_BUS_T bus;
255     NTSTATUS status;
256     PDEVICE_OBJECT bus_pdo = NULL;
257
258     /* TODO: Check the Registry to see if we've already got a PDO. */
259     
260     /* Create the PDO. */
261     IoReportDetectedDevice(
262         WvDriverObj,
263         InterfaceTypeUndefined,
264         -1,
265         -1,
266         NULL,
267         NULL,
268         FALSE,
269         &bus_pdo
270       );
271     if (bus_pdo == NULL) {
272         DBG("IoReportDetectedDevice() went wrong!  Exiting.\n");
273         status = STATUS_UNSUCCESSFUL;
274         goto err_driver_bus;
275       }
276     /* Attach FDO to PDO. */
277     status = driver__attach_fdo_(WvDriverObj, bus_pdo);
278     if (!NT_SUCCESS(status)) {
279         DBG("driver__attach_fdo_() went wrong!\n");
280         goto err_add_dev;
281       }
282     /* PDO created, FDO attached.  All done. */
283     return STATUS_SUCCESS;
284
285     err_add_dev:
286
287     IoDeleteDevice(bus_pdo);
288     err_driver_bus:
289
290     return status;
291   }
292
293 /*
294  * Note the exception to the function naming convention.
295  * TODO: See if a Makefile change is good enough.
296  */
297 NTSTATUS STDCALL DriverEntry(
298     IN PDRIVER_OBJECT DriverObject,
299     IN PUNICODE_STRING RegistryPath
300   ) {
301     NTSTATUS status;
302     int i;
303
304     DBG("Entry\n");
305     if (WvDriverObj) {
306         DBG("Re-entry not allowed!\n");
307         return STATUS_NOT_SUPPORTED;
308       }
309     WvDriverObj = DriverObject;
310     if (WvDriverStarted_)
311       return STATUS_SUCCESS;
312     Debug_Initialize();
313     status = WvlRegNoteOsLoadOpts(&WvDriverOsLoadOpts_);
314     if (!NT_SUCCESS(status))
315       return Error("WvlRegNoteOsLoadOpts", status);
316
317     WvDriverStateHandle_ = NULL;
318     KeInitializeSpinLock(&WvDriverBusFdoLock_);
319
320     if ((WvDriverStateHandle_ = PoRegisterSystemState(
321         NULL,
322         ES_CONTINUOUS
323       )) == NULL) {
324         DBG("Could not set system state to ES_CONTINUOUS!!\n");
325       }
326     /*
327      * Set up IRP MajorFunction function table for devices
328      * this driver handles.
329      */
330     for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
331       DriverObject->MajorFunction[i] = driver__dispatch_not_supported_;
332     DriverObject->MajorFunction[IRP_MJ_PNP] = driver__dispatch_pnp_;
333     DriverObject->MajorFunction[IRP_MJ_POWER] = driver__dispatch_power_;
334     DriverObject->MajorFunction[IRP_MJ_CREATE] = driver__dispatch_create_close_;
335     DriverObject->MajorFunction[IRP_MJ_CLOSE] = driver__dispatch_create_close_;
336     DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] =
337       driver__dispatch_sys_ctl_;
338     DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
339       driver__dispatch_dev_ctl_;
340     DriverObject->MajorFunction[IRP_MJ_SCSI] = driver__dispatch_scsi_;
341     /* Set the driver Unload callback. */
342     DriverObject->DriverUnload = driver__unload_;
343     /* Set the driver AddDevice callback. */
344     DriverObject->DriverExtension->AddDevice = driver__attach_fdo_;
345     /* Initialize various modules. */
346     disk__module_init();        /* TODO: Check for error. */
347     filedisk__module_init();    /* TODO: Check for error. */
348     ramdisk__module_init();     /* TODO: Check for error. */
349
350     /* Establish the bus PDO. */
351     status = WvDriverBusEstablish_(RegistryPath);
352     if(!NT_SUCCESS(status))
353       goto err_bus;
354
355     WvDriverStarted_ = TRUE;
356     DBG("Exit\n");
357     return STATUS_SUCCESS;
358
359     err_bus:
360
361     driver__unload_(DriverObject);
362     DBG("Exit due to failure\n");
363     return status;
364   }
365
366 static NTSTATUS STDCALL driver__dispatch_not_supported_(
367     IN PDEVICE_OBJECT dev_obj,
368     IN PIRP irp
369   ) {
370     irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
371     IoCompleteRequest(irp, IO_NO_INCREMENT);
372     return irp->IoStatus.Status;
373   }
374
375 /**
376  * Common IRP completion routine.
377  *
378  * @v irp               Points to the IRP to complete.
379  * @v info              Number of bytes returned for the IRP, or 0.
380  * @v status            Status for the IRP to complete.
381  * @ret NTSTATUS        Returns the status value, as passed.
382  */
383 winvblock__lib_func NTSTATUS STDCALL driver__complete_irp(
384     IN PIRP irp,
385     IN ULONG_PTR info,
386     IN NTSTATUS status
387   ) {
388     irp->IoStatus.Information = info;
389     irp->IoStatus.Status = status;
390     IoCompleteRequest(irp, IO_NO_INCREMENT);
391     #ifdef DEBUGIRPS
392     Debug_IrpEnd(irp, status);
393     #endif
394     return status;
395   }
396
397 /* Handle a power IRP. */
398 static NTSTATUS driver__dispatch_power_(
399     IN PDEVICE_OBJECT dev_obj,
400     IN PIRP irp
401   ) {
402     /* WvDevFromDevObj() checks for a NULL dev_obj */
403     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
404
405     #ifdef DEBUGIRPS
406     Debug_IrpStart(dev_obj, irp);
407     #endif
408     /* Check that the device exists. */
409     if (!dev || dev->State == WvDevStateDeleted) {
410         /* Even if it doesn't, a power IRP is important! */
411         PoStartNextPowerIrp(irp);
412         return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
413       }
414     /* Call the particular device's power handler. */
415     if (dev->IrpMj && dev->IrpMj->Power)
416       return dev->IrpMj->Power(dev, irp);
417     /* Otherwise, we don't support the IRP. */
418     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
419   }
420
421 /* Handle an IRP_MJ_CREATE or IRP_MJ_CLOSE IRP. */
422 static NTSTATUS driver__dispatch_create_close_(
423     IN PDEVICE_OBJECT dev_obj,
424     IN PIRP irp
425   ) {
426     /* WvDevFromDevObj() checks for a NULL dev_obj */
427     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
428
429     #ifdef DEBUGIRPS
430     Debug_IrpStart(dev_obj, irp);
431     #endif
432     /* Check that the device exists. */
433     if (!dev || dev->State == WvDevStateDeleted)
434       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
435     /* Always succeed with nothing to do. */
436     return driver__complete_irp(irp, 0, STATUS_SUCCESS);
437   }
438
439 /* Handle an IRP_MJ_SYSTEM_CONTROL IRP. */
440 static NTSTATUS driver__dispatch_sys_ctl_(
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     /* Call the particular device's power handler. */
454     if (dev->IrpMj && dev->IrpMj->SysCtl)
455       return dev->IrpMj->SysCtl(dev, irp);
456     /* Otherwise, we don't support the IRP. */
457     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
458   }
459
460 /* Handle an IRP_MJ_DEVICE_CONTROL IRP. */
461 static NTSTATUS driver__dispatch_dev_ctl_(
462     IN PDEVICE_OBJECT dev_obj,
463     IN PIRP irp
464   ) {
465     /* WvDevFromDevObj() checks for a NULL dev_obj */
466     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
467     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
468
469     #ifdef DEBUGIRPS
470     Debug_IrpStart(dev_obj, irp);
471     #endif
472     /* Check that the device exists. */
473     if (!dev || dev->State == WvDevStateDeleted)
474       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
475     /* Call the particular device's power handler. */
476     if (dev->IrpMj && dev->IrpMj->DevCtl) {
477         return dev->IrpMj->DevCtl(
478             dev,
479             irp,
480             io_stack_loc->Parameters.DeviceIoControl.IoControlCode
481           );
482       }
483     /* Otherwise, we don't support the IRP. */
484     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
485   }
486
487 /* Handle an IRP_MJ_SCSI IRP. */
488 static NTSTATUS driver__dispatch_scsi_(
489     IN PDEVICE_OBJECT dev_obj,
490     IN PIRP irp
491   ) {
492     /* WvDevFromDevObj() checks for a NULL dev_obj */
493     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
494     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
495
496     #ifdef DEBUGIRPS
497     Debug_IrpStart(dev_obj, irp);
498     #endif
499     /* Check that the device exists. */
500     if (!dev || dev->State == WvDevStateDeleted)
501       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
502     /* Call the particular device's power handler. */
503     if (dev->IrpMj && dev->IrpMj->Scsi) {
504         return dev->IrpMj->Scsi(
505             dev,
506             irp,
507             io_stack_loc->Parameters.Scsi.Srb->Function
508           );
509       }
510     /* Otherwise, we don't support the IRP. */
511     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
512   }
513
514 /* Handle an IRP_MJ_PNP IRP. */
515 static NTSTATUS driver__dispatch_pnp_(
516     IN PDEVICE_OBJECT dev_obj,
517     IN PIRP irp
518   ) {
519     /* WvDevFromDevObj() checks for a NULL dev_obj */
520     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
521     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
522     NTSTATUS status;
523
524     #ifdef DEBUGIRPS
525     Debug_IrpStart(dev_obj, irp);
526     #endif
527     /* Check that the device exists. */
528     if (!dev || dev->State == WvDevStateDeleted)
529       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
530     /* Call the particular device's power handler. */
531     if (dev->IrpMj && dev->IrpMj->Pnp) {
532         status = dev->IrpMj->Pnp(
533             dev,
534             irp,
535             io_stack_loc->MinorFunction
536           );
537         if (dev->IsBus) {
538             dev->OldState = WvDriverBus_.OldState;
539             dev->State = WvDriverBus_.State;
540           }
541         return status;
542       }
543     /* Otherwise, we don't support the IRP. */
544     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
545   }
546
547 static void STDCALL driver__unload_(IN PDRIVER_OBJECT DriverObject) {
548     DBG("Unloading...\n");
549     if (WvDriverStateHandle_ != NULL)
550       PoUnregisterSystemState(WvDriverStateHandle_);
551     IoDeleteSymbolicLink(&WvDriverBusDosname_);
552     WvDriverBusFdo_ = NULL;
553     wv_free(WvDriverOsLoadOpts_);
554     WvDriverStarted_ = FALSE;
555     DBG("Done\n");
556   }
557
558 winvblock__lib_func void STDCALL WvDriverCompletePendingIrp(IN PIRP Irp) {
559     #ifdef DEBUGIRPS
560     Debug_IrpEnd(Irp, Irp->IoStatus.Status);
561     #endif
562     IoCompleteRequest(Irp, IO_NO_INCREMENT);
563   }
564
565 /* Note the exception to the function naming convention. */
566 winvblock__lib_func NTSTATUS STDCALL Error(
567     IN PCHAR Message,
568     IN NTSTATUS Status
569   ) {
570     DBG("%s: 0x%08x\n", Message, Status);
571     return Status;
572   }
573
574 /* Pass an IRP_MJ_SYSTEM_CONTROL IRP to the bus. */
575 static NTSTATUS STDCALL WvDriverBusSysCtl_(IN WV_SP_DEV_T dev, IN PIRP irp) {
576     return WvBusSysCtl(&WvDriverBus_, irp);
577   }
578
579 /* Pass a power IRP to the bus. */
580 static NTSTATUS STDCALL WvDriverBusPower_(IN WV_SP_DEV_T dev, IN PIRP irp) {
581     return WvBusPower(&WvDriverBus_, irp);
582   }
583
584 /* Pass an IRP_MJ_PNP to the bus. */
585 static NTSTATUS STDCALL WvDriverBusPnp_(
586     IN WV_SP_DEV_T dev,
587     IN PIRP irp,
588     IN UCHAR code
589   ) {
590     return WvBusPnp(&WvDriverBus_, irp, code);
591   }
592     
593 /**
594  * Add a child node to the bus.
595  *
596  * @v Dev               Points to the child device to add.
597  * @ret                 TRUE for success, FALSE for failure.
598  */
599 winvblock__lib_func winvblock__bool STDCALL WvDriverBusAddDev(
600     IN OUT WV_SP_DEV_T Dev
601   ) {
602     /* The new node's device object. */
603     PDEVICE_OBJECT dev_obj;
604
605     DBG("Entry\n");
606     if (!WvDriverBusFdo_ || !Dev) {
607         DBG("No bus or no device!\n");
608         return FALSE;
609       }
610     /* Create the child device. */
611     dev_obj = WvDevCreatePdo(Dev);
612     if (!dev_obj) {
613         DBG("PDO creation failed!\n");
614         return FALSE;
615       }
616     WvBusInitNode(&Dev->BusNode, dev_obj);
617     /* Associate the parent bus. */
618     Dev->Parent = WvDriverBus_.Fdo;
619     /*
620      * Initialize the device.  For disks, this routine is responsible for
621      * determining the disk's geometry appropriately for AoE/RAM/file disks.
622      */
623     Dev->Ops.Init(Dev);
624     dev_obj->Flags &= ~DO_DEVICE_INITIALIZING;
625     /* Add the new PDO device to the bus' list of children. */
626     WvBusAddNode(&WvDriverBus_, &Dev->BusNode);
627     Dev->DevNum = WvBusGetNodeNum(&Dev->BusNode);
628
629     DBG("Exit\n");
630     return TRUE;
631   }
632
633 static NTSTATUS STDCALL WvDriverBusDevCtlDetach_(
634     IN PIRP irp
635   ) {
636     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
637     winvblock__uint32 unit_num;
638     WV_SP_BUS_NODE walker;
639
640     if (!(io_stack_loc->Control & SL_PENDING_RETURNED)) {
641         NTSTATUS status;
642
643         /* Enqueue the IRP. */
644         status = WvBusEnqueueIrp(&WvDriverBus_, irp);
645         if (status != STATUS_PENDING)
646           /* Problem. */
647           return driver__complete_irp(irp, 0, status);
648         /* Ok. */
649         return status;
650       }
651     /* If we get here, we should be called by WvBusProcessWorkItems() */
652     unit_num = *((winvblock__uint32_ptr) irp->AssociatedIrp.SystemBuffer);
653     DBG("Request to detach unit: %d\n", unit_num);
654
655     walker = NULL;
656     /* For each node on the bus... */
657     while (walker = WvBusGetNextNode(&WvDriverBus_, walker)) {
658         WV_SP_DEV_T dev = WvDevFromDevObj(WvBusGetNodePdo(walker));
659
660         /* If the unit number matches... */
661         if (WvBusGetNodeNum(walker) == unit_num) {
662             /* If it's not a boot-time device... */
663             if (dev->Boot) {
664                 DBG("Cannot detach a boot-time device.\n");
665                 /* Signal error. */
666                 walker = NULL;
667                 break;
668               }
669             /* Detach the node and free it. */
670             DBG("Removing unit %d\n", unit_num);
671             WvBusRemoveNode(walker);
672             WvDevClose(dev);
673             IoDeleteDevice(dev->Self);
674             WvDevFree(dev);
675             break;
676           }
677       }
678     if (!walker)
679       return driver__complete_irp(irp, 0, STATUS_INVALID_PARAMETER);
680     return driver__complete_irp(irp, 0, STATUS_SUCCESS);
681   }
682
683 NTSTATUS STDCALL WvDriverBusDevCtl_(
684     IN WV_SP_DEV_T dev,
685     IN PIRP irp,
686     IN ULONG POINTER_ALIGNMENT code
687   ) {
688     NTSTATUS status;
689
690     switch (code) {
691         case IOCTL_FILE_ATTACH:
692           status = filedisk__attach(dev, irp);
693           break;
694
695         case IOCTL_FILE_DETACH:
696           return WvDriverBusDevCtlDetach_(irp);
697
698         default:
699           irp->IoStatus.Information = 0;
700           status = STATUS_INVALID_DEVICE_REQUEST;
701       }
702
703     irp->IoStatus.Status = status;
704     IoCompleteRequest(irp, IO_NO_INCREMENT);
705     return status;
706   }
707
708 NTSTATUS STDCALL WvDriverGetDevCapabilities(
709     IN PDEVICE_OBJECT DevObj,
710     IN PDEVICE_CAPABILITIES DevCapabilities
711   ) {
712     IO_STATUS_BLOCK io_status;
713     KEVENT pnp_event;
714     NTSTATUS status;
715     PDEVICE_OBJECT target_obj;
716     PIO_STACK_LOCATION io_stack_loc;
717     PIRP pnp_irp;
718
719     RtlZeroMemory(DevCapabilities, sizeof *DevCapabilities);
720     DevCapabilities->Size = sizeof *DevCapabilities;
721     DevCapabilities->Version = 1;
722     DevCapabilities->Address = -1;
723     DevCapabilities->UINumber = -1;
724
725     KeInitializeEvent(&pnp_event, NotificationEvent, FALSE);
726     target_obj = IoGetAttachedDeviceReference(DevObj);
727     pnp_irp = IoBuildSynchronousFsdRequest(
728         IRP_MJ_PNP,
729         target_obj,
730         NULL,
731         0,
732         NULL,
733         &pnp_event,
734         &io_status
735       );
736     if (pnp_irp == NULL) {
737         status = STATUS_INSUFFICIENT_RESOURCES;
738       } else {
739         pnp_irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
740         io_stack_loc = IoGetNextIrpStackLocation(pnp_irp);
741         RtlZeroMemory(io_stack_loc, sizeof *io_stack_loc);
742         io_stack_loc->MajorFunction = IRP_MJ_PNP;
743         io_stack_loc->MinorFunction = IRP_MN_QUERY_CAPABILITIES;
744         io_stack_loc->Parameters.DeviceCapabilities.Capabilities =
745           DevCapabilities;
746         status = IoCallDriver(target_obj, pnp_irp);
747         if (status == STATUS_PENDING) {
748             KeWaitForSingleObject(
749                 &pnp_event,
750                 Executive,
751                 KernelMode,
752                 FALSE,
753                 NULL
754               );
755             status = io_status.Status;
756           }
757       }
758     ObDereferenceObject(target_obj);
759     return status;
760   }
761
762 static NTSTATUS STDCALL WvDriverBusPnpQueryDevText_(
763     IN WV_SP_BUS_T bus,
764     IN PIRP irp
765   ) {
766     WCHAR (*str)[512];
767     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
768     NTSTATUS status;
769     winvblock__uint32 str_len;
770
771     /* Allocate a string buffer. */
772     str = wv_mallocz(sizeof *str);
773     if (str == NULL) {
774         DBG("wv_malloc IRP_MN_QUERY_DEVICE_TEXT\n");
775         status = STATUS_INSUFFICIENT_RESOURCES;
776         goto alloc_str;
777       }
778     /* Determine the query type. */
779     switch (io_stack_loc->Parameters.QueryDeviceText.DeviceTextType) {
780         case DeviceTextDescription:
781           str_len = swprintf(*str, winvblock__literal_w L" Bus") + 1;
782           irp->IoStatus.Information =
783             (ULONG_PTR) wv_palloc(str_len * sizeof *str);
784           if (irp->IoStatus.Information == 0) {
785               DBG("wv_palloc DeviceTextDescription\n");
786               status = STATUS_INSUFFICIENT_RESOURCES;
787               goto alloc_info;
788             }
789           RtlCopyMemory(
790               (PWCHAR) irp->IoStatus.Information,
791               str,
792               str_len * sizeof (WCHAR)
793             );
794           status = STATUS_SUCCESS;
795           goto alloc_info;
796
797         case DeviceTextLocationInformation:
798           str_len = WvDevPnpId(
799               &WvDriverBusDev_,
800               BusQueryInstanceID,
801               str
802             );
803           irp->IoStatus.Information =
804             (ULONG_PTR) wv_palloc(str_len * sizeof *str);
805           if (irp->IoStatus.Information == 0) {
806               DBG("wv_palloc DeviceTextLocationInformation\n");
807               status = STATUS_INSUFFICIENT_RESOURCES;
808               goto alloc_info;
809             }
810           RtlCopyMemory(
811               (PWCHAR) irp->IoStatus.Information,
812               str,
813               str_len * sizeof (WCHAR)
814             );
815           status = STATUS_SUCCESS;
816           goto alloc_info;
817
818         default:
819           irp->IoStatus.Information = 0;
820           status = STATUS_NOT_SUPPORTED;
821       }
822     /* irp->IoStatus.Information not freed. */
823     alloc_info:
824
825     wv_free(str);
826     alloc_str:
827
828     return driver__complete_irp(irp, irp->IoStatus.Information, status);
829   }
830
831 /**
832  * Dummy device feature.
833  */
834
835 /* Prototype safety. */
836 WV_F_DEV_PNP WvDriverDummyPnp_;
837
838 /* Dummy PnP IRP handler. */
839 static NTSTATUS STDCALL WvDriverDummyPnp_(
840     IN WV_SP_DEV_T dev,
841     IN PIRP irp,
842     IN UCHAR code
843   ) {
844     if (code != IRP_MN_QUERY_ID)
845       return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
846
847     /* The WV_S_DEV_T extension points to the dummy IDs. */
848     return WvDriverDummyIds(irp, dev->ext);
849   }
850
851 typedef struct WV_DRIVER_ADD_DUMMY_ {
852     const WV_S_DRIVER_DUMMY_IDS * DummyIds;
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     wv_size_t dummy_ids_size;
872     WV_SP_DRIVER_DUMMY_IDS dummy_ids;
873     static WV_S_DEV_IRP_MJ irp_mj = {
874         (WV_FP_DEV_DISPATCH) 0,
875         (WV_FP_DEV_DISPATCH) 0,
876         (WV_FP_DEV_CTL) 0,
877         (WV_FP_DEV_SCSI) 0,
878         WvDriverDummyPnp_,
879       };
880
881     status = IoCreateDevice(
882         WvDriverObj,
883         sizeof (driver__dev_ext),
884         NULL,
885         dummy_context->DevType,
886         dummy_context->DevCharacteristics,
887         FALSE,
888         &pdo
889       );
890     if (!NT_SUCCESS(status) || !pdo) {
891         DBG("Couldn't create dummy device.\n");
892         dummy_context->Status = STATUS_INSUFFICIENT_RESOURCES;
893         goto err_create_pdo;
894       }
895
896     dev = wv_malloc(sizeof *dev);
897     if (!dev) {
898         DBG("Couldn't allocate dummy device.\n");
899         dummy_context->Status = STATUS_INSUFFICIENT_RESOURCES;
900         goto err_dev;
901       }
902
903     dummy_ids_size =
904       sizeof *dummy_ids +
905       dummy_context->DummyIds->Len * sizeof dummy_ids->Text[0] -
906       sizeof dummy_ids->Text[0];        /* The struct hack uses a WCHAR[1]. */
907     dummy_ids = wv_malloc(dummy_ids_size);
908     if (!dummy_ids) {
909         DBG("Couldn't allocate dummy IDs.\n");
910         dummy_context->Status = STATUS_INSUFFICIENT_RESOURCES;
911         goto err_dummy_ids;
912       }
913     /* Copy the IDs offsets and lengths. */
914     RtlCopyMemory(dummy_ids, dummy_context->DummyIds, sizeof *dummy_ids);
915     /* Copy the text of the IDs. */
916     RtlCopyMemory(
917         &dummy_ids->Text,
918         dummy_context->DummyIds->Ids,
919         dummy_context->DummyIds->Len * sizeof dummy_ids->Text[0]
920       );
921     /* Point to the copy of the text. */
922     dummy_ids->Ids = dummy_ids->Text;
923
924     /* Ok! */
925     WvDevInit(dev);
926     dev->IrpMj = &irp_mj;
927     dev->ext = dummy_ids;       /* TODO: Implement a dummy free.  Leaking. */
928     dev->Self = pdo;
929     WvDevForDevObj(pdo, dev);
930     WvBusInitNode(&dev->BusNode, pdo);
931     /* Associate the parent bus. */
932     dev->Parent = WvDriverBus_.Fdo;
933     /* Add the new PDO device to the bus' list of children. */
934     WvBusAddNode(&WvDriverBus_, &dev->BusNode);
935     dev->DevNum = WvBusGetNodeNum(&dev->BusNode);
936     pdo->Flags &= ~DO_DEVICE_INITIALIZING;
937
938     dummy_context->Status = STATUS_SUCCESS;
939     KeSetEvent(dummy_context->Event, 0, FALSE);
940     return;
941
942     wv_free(dummy_ids);
943     err_dummy_ids:
944
945     wv_free(dev);
946     err_dev:
947
948     IoDeleteDevice(pdo);
949     err_create_pdo:
950
951     KeSetEvent(dummy_context->Event, 0, FALSE);
952     return;
953   }
954
955 /**
956  * Produce a dummy PDO node on the main bus.
957  *
958  * @v DummyIds                  The PnP IDs for the dummy.
959  * @v DevType                   The type for the dummy device.
960  * @v DevCharacteristics        The dummy device characteristics.
961  * @ret NTSTATUS                The status of the operation.
962  */
963 winvblock__lib_func NTSTATUS STDCALL WvDriverAddDummy(
964     IN const WV_S_DRIVER_DUMMY_IDS * DummyIds,
965     IN DEVICE_TYPE DevType,
966     IN ULONG DevCharacteristics
967   ) {
968     KEVENT event;
969     WV_S_DRIVER_ADD_DUMMY_ context = {
970         DummyIds,
971         DevType,
972         DevCharacteristics,
973         &event,
974         STATUS_UNSUCCESSFUL
975       };
976     WV_S_BUS_CUSTOM_WORK_ITEM work_item = {
977         WvDriverAddDummy_,
978         &context
979       };
980     NTSTATUS status;
981
982     if (!DummyIds)
983       return STATUS_INVALID_PARAMETER;
984
985     KeInitializeEvent(&event, SynchronizationEvent, FALSE);
986
987     status = WvBusEnqueueCustomWorkItem(&WvDriverBus_, &work_item);
988     if (!NT_SUCCESS(status))
989       return status;
990
991     /* Wait for WvDriverAddDummy_() to complete. */
992     KeWaitForSingleObject(
993         &event,
994         Executive,
995         KernelMode,
996         FALSE,
997         NULL
998       );
999
1000     return context.Status;
1001   }
1002
1003 /**
1004  * Handle a PnP ID query with a WV_S_DRIVER_DUMMY_IDS object.
1005  *
1006  * @v Irp               The PnP ID query IRP to handle.
1007  * @v DummyIds          The object containing the IDs to respond with.
1008  * @ret NTSTATUS        The status of the operation.
1009  */
1010 winvblock__lib_func NTSTATUS STDCALL WvDriverDummyIds(
1011     IN PIRP Irp,
1012     IN WV_SP_DRIVER_DUMMY_IDS DummyIds
1013   ) {
1014     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(Irp);
1015     BUS_QUERY_ID_TYPE query_type = io_stack_loc->Parameters.QueryId.IdType;
1016     const WCHAR * ids;
1017     winvblock__uint32 len;
1018     NTSTATUS status;
1019
1020     switch (query_type) {
1021         case BusQueryDeviceID:
1022           ids = DummyIds->Ids + DummyIds->DevOffset;
1023           len = DummyIds->DevLen;
1024           break;
1025
1026         case BusQueryInstanceID:
1027           ids = DummyIds->Ids + DummyIds->InstanceOffset;
1028           len = DummyIds->InstanceLen;
1029           break;
1030
1031         case BusQueryHardwareIDs:
1032           ids = DummyIds->Ids + DummyIds->HardwareOffset;
1033           len = DummyIds->HardwareLen;
1034           break;
1035
1036         case BusQueryCompatibleIDs:
1037           ids = DummyIds->Ids + DummyIds->CompatOffset;
1038           len = DummyIds->CompatLen;
1039           break;
1040
1041         default:
1042           return driver__complete_irp(Irp, 0, STATUS_NOT_SUPPORTED);
1043       }
1044
1045     /* Allocate the return buffer. */
1046     Irp->IoStatus.Information = (ULONG_PTR) wv_palloc(len * sizeof *ids);
1047     if (Irp->IoStatus.Information == 0) {
1048         DBG("wv_palloc failed.\n");
1049         status = STATUS_INSUFFICIENT_RESOURCES;
1050         goto alloc_info;
1051       }
1052     /* Copy the working buffer to the return buffer. */
1053     RtlCopyMemory(
1054         (void *) Irp->IoStatus.Information,
1055         ids,
1056         len * sizeof *ids
1057       );
1058     status = STATUS_SUCCESS;
1059
1060     /* irp->IoStatus.Information not freed. */
1061     alloc_info:
1062
1063     return driver__complete_irp(Irp, Irp->IoStatus.Information, status);
1064   }