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