[bus,driver] Put bus IRP_MJ_SYSTEM_CONTROL handler in driver
[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 "device.h"
38 #include "disk.h"
39 #include "registry.h"
40 #include "mount.h"
41 #include "bus.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 /* Contains TXTSETUP.SIF/BOOT.INI-style OsLoadOptions parameters. */
71 static LPWSTR WvDriverOsLoadOpts_ = NULL;
72
73 /* Forward declarations. */
74 static driver__dispatch_func driver__dispatch_not_supported_;
75 static driver__dispatch_func driver__dispatch_power_;
76 static driver__dispatch_func driver__dispatch_create_close_;
77 static driver__dispatch_func driver__dispatch_sys_ctl_;
78 static driver__dispatch_func driver__dispatch_dev_ctl_;
79 static driver__dispatch_func driver__dispatch_scsi_;
80 static driver__dispatch_func driver__dispatch_pnp_;
81 static void STDCALL driver__unload_(IN PDRIVER_OBJECT);
82 static WV_F_DEV_DISPATCH WvDriverBusSysCtl_;
83
84 static LPWSTR STDCALL get_opt(IN LPWSTR opt_name) {
85     LPWSTR our_opts, the_opt;
86     WCHAR our_sig[] = L"WINVBLOCK=";
87     /* To produce constant integer expressions. */
88     enum {
89         our_sig_len_bytes = sizeof ( our_sig ) - sizeof ( WCHAR ),
90         our_sig_len = our_sig_len_bytes / sizeof ( WCHAR )
91       };
92     size_t opt_name_len, opt_name_len_bytes;
93
94     if (!WvDriverOsLoadOpts_ || !opt_name)
95       return NULL;
96
97     /* Find /WINVBLOCK= options. */
98     our_opts = WvDriverOsLoadOpts_;
99     while (*our_opts != L'\0') {
100         if (!wv_memcmpeq(our_opts, our_sig, our_sig_len_bytes)) {
101             our_opts++;
102             continue;
103           }
104         our_opts += our_sig_len;
105         break;
106       }
107
108     /* Search for the specific option. */
109     the_opt = our_opts;
110     opt_name_len = wcslen(opt_name);
111     opt_name_len_bytes = opt_name_len * sizeof (WCHAR);
112     while (*the_opt != L'\0' && *the_opt != L' ') {
113         if (!wv_memcmpeq(the_opt, opt_name, opt_name_len_bytes)) {
114             while (*the_opt != L'\0' && *the_opt != L' ' && *the_opt != L',')
115               the_opt++;
116             continue;
117           }
118         the_opt += opt_name_len;
119         break;
120       }
121
122     if (*the_opt == L'\0' || *the_opt == L' ')
123       return NULL;
124
125     /* Next should come "=". */
126     if (*the_opt != L'=')
127       return NULL;
128
129     /*
130      * And finally our option's value.  The caller needs
131      * to worry about looking past the end of the option.
132      */
133     the_opt++;
134     if (*the_opt == L'\0' || *the_opt == L' ')
135       return NULL;
136     return the_opt;
137   }
138
139 static NTSTATUS STDCALL driver__attach_fdo_(
140     IN PDRIVER_OBJECT DriverObject,
141     IN PDEVICE_OBJECT PhysicalDeviceObject
142   ) {
143     KIRQL irql;
144     NTSTATUS status;
145     PLIST_ENTRY walker;
146     PUNICODE_STRING dev_name = NULL;
147     PDEVICE_OBJECT fdo = NULL;
148
149     DBG("Entry\n");
150     /* Do we alreay have our main bus? */
151     if (WvDriverBusFdo_) {
152         DBG("Already have the main bus.  Refusing.\n");
153         status = STATUS_NOT_SUPPORTED;
154         goto err_already_established;
155       }
156     /* Initialize the bus. */
157     WvBusInit(&WvDriverBus_);
158     /* Create the bus FDO. */
159     status = IoCreateDevice(
160         DriverObject,
161         sizeof (driver__dev_ext),
162         &WvDriverBusName_,
163         FILE_DEVICE_CONTROLLER,
164         FILE_DEVICE_SECURE_OPEN,
165         FALSE,
166         &fdo
167       );
168     if (!NT_SUCCESS(status)) {
169         DBG("IoCreateDevice() failed!\n");
170         goto err_fdo;
171       }
172     /* DosDevice symlink. */
173     status = IoCreateSymbolicLink(
174         &WvDriverBusDosname_,
175         &WvDriverBusName_
176       );
177     if (!NT_SUCCESS(status)) {
178         DBG("IoCreateSymbolicLink() failed!\n");
179         goto err_dos_symlink;
180       }
181     /* Set associations for the bus, device, FDO, PDO. */
182     WvDevForDevObj(fdo, &WvDriverBus_.Dev);
183     WvDriverBus_.Dev.Self = WvDriverBus_.Fdo = fdo;
184     WvDriverBus_.Dev.IsBus = TRUE;
185     WvDriverBus_.Dev.IrpMj->SysCtl = WvDriverBusSysCtl_;
186     WvDriverBus_.PhysicalDeviceObject = PhysicalDeviceObject;
187     fdo->Flags |= DO_DIRECT_IO;         /* FIXME? */
188     fdo->Flags |= DO_POWER_INRUSH;      /* FIXME? */
189     /* Attach the FDO to the PDO. */
190     WvDriverBus_.LowerDeviceObject = IoAttachDeviceToDeviceStack(
191         fdo,
192         PhysicalDeviceObject
193       );
194     if (WvDriverBus_.LowerDeviceObject == NULL) {
195         status = STATUS_NO_SUCH_DEVICE;
196         DBG("IoAttachDeviceToDeviceStack() failed!\n");
197         goto err_attach;
198       }
199     status = WvBusStartThread(&WvDriverBus_);
200     if (!NT_SUCCESS(status)) {
201         DBG("Couldn't start bus thread!\n");
202         goto err_thread;
203       }
204     /* Ok! */
205     KeAcquireSpinLock(&WvDriverBusFdoLock_, &irql);
206     if (WvDriverBusFdo_) {
207         KeReleaseSpinLock(&WvDriverBusFdoLock_, irql);
208         DBG("Beaten to it!\n");
209         status = STATUS_NOT_SUPPORTED;
210         goto err_race_failed;
211       }
212     fdo->Flags &= ~DO_DEVICE_INITIALIZING;
213     #ifdef RIS
214     WvDriverBus_.Dev.State = Started;
215     #endif
216     WvDriverBusFdo_ = fdo;
217     KeReleaseSpinLock(&WvDriverBusFdoLock_, irql);
218     DBG("Exit\n");
219     return STATUS_SUCCESS;
220
221     err_race_failed:
222
223     err_thread:
224
225     err_attach:
226
227     IoDeleteSymbolicLink(&WvDriverBusDosname_);
228     err_dos_symlink:
229
230     IoDeleteDevice(fdo);
231     err_fdo:
232
233     err_already_established:
234
235     DBG("Exit with failure\n");
236     return status;
237   }
238
239 /* Create the root-enumerated, main bus device. */
240 NTSTATUS STDCALL driver__create_bus_(void) {
241     WV_SP_BUS_T bus;
242     NTSTATUS status;
243     PDEVICE_OBJECT bus_pdo = NULL;
244
245     /* Create the PDO. */
246     IoReportDetectedDevice(
247         WvDriverObj,
248         InterfaceTypeUndefined,
249         -1,
250         -1,
251         NULL,
252         NULL,
253         FALSE,
254         &bus_pdo
255       );
256     if (bus_pdo == NULL) {
257         DBG("IoReportDetectedDevice() went wrong!  Exiting.\n");
258         status = STATUS_UNSUCCESSFUL;
259         goto err_driver_bus;
260       }
261     /* Attach FDO to PDO. */
262     status = driver__attach_fdo_(WvDriverObj, bus_pdo);
263     if (!NT_SUCCESS(status)) {
264         DBG("driver__attach_fdo_() went wrong!\n");
265         goto err_add_dev;
266       }
267     /* PDO created, FDO attached.  All done. */
268     return STATUS_SUCCESS;
269
270     err_add_dev:
271
272     IoDeleteDevice(bus_pdo);
273     err_driver_bus:
274
275     return status;
276   }
277
278 /*
279  * Note the exception to the function naming convention.
280  * TODO: See if a Makefile change is good enough.
281  */
282 NTSTATUS STDCALL DriverEntry(
283     IN PDRIVER_OBJECT DriverObject,
284     IN PUNICODE_STRING RegistryPath
285   ) {
286     NTSTATUS status;
287     int i;
288
289     DBG("Entry\n");
290     if (WvDriverObj) {
291         DBG("Re-entry not allowed!\n");
292         return STATUS_NOT_SUPPORTED;
293       }
294     WvDriverObj = DriverObject;
295     if (WvDriverStarted_)
296       return STATUS_SUCCESS;
297     Debug_Initialize();
298     status = registry__note_os_load_opts(&WvDriverOsLoadOpts_);
299     if (!NT_SUCCESS(status))
300       return Error("registry__note_driver__os_load_opts", status);
301
302     WvDriverStateHandle_ = NULL;
303     KeInitializeSpinLock(&WvDriverBusFdoLock_);
304
305     if ((WvDriverStateHandle_ = PoRegisterSystemState(
306         NULL,
307         ES_CONTINUOUS
308       )) == NULL) {
309         DBG("Could not set system state to ES_CONTINUOUS!!\n");
310       }
311     /*
312      * Set up IRP MajorFunction function table for devices
313      * this driver handles.
314      */
315     for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
316       DriverObject->MajorFunction[i] = driver__dispatch_not_supported_;
317     DriverObject->MajorFunction[IRP_MJ_PNP] = driver__dispatch_pnp_;
318     DriverObject->MajorFunction[IRP_MJ_POWER] = driver__dispatch_power_;
319     DriverObject->MajorFunction[IRP_MJ_CREATE] = driver__dispatch_create_close_;
320     DriverObject->MajorFunction[IRP_MJ_CLOSE] = driver__dispatch_create_close_;
321     DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] =
322       driver__dispatch_sys_ctl_;
323     DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
324       driver__dispatch_dev_ctl_;
325     DriverObject->MajorFunction[IRP_MJ_SCSI] = driver__dispatch_scsi_;
326     /* Set the driver Unload callback. */
327     DriverObject->DriverUnload = driver__unload_;
328     /* Set the driver AddDevice callback. */
329     DriverObject->DriverExtension->AddDevice = driver__attach_fdo_;
330     /* Initialize various modules. */
331     disk__module_init();        /* TODO: Check for error. */
332     filedisk__module_init();    /* TODO: Check for error. */
333     ramdisk__module_init();     /* TODO: Check for error. */
334
335     /*
336      * Always create the root-enumerated, main bus device.
337      * This is required in order to boot from a WinVBlock disk.
338      */
339     status = driver__create_bus_();
340     if(!NT_SUCCESS(status))
341       goto err_bus;
342
343     WvDriverStarted_ = TRUE;
344     DBG("Exit\n");
345     return STATUS_SUCCESS;
346
347     err_bus:
348
349     driver__unload_(DriverObject);
350     DBG("Exit due to failure\n");
351     return status;
352   }
353
354 static NTSTATUS STDCALL driver__dispatch_not_supported_(
355     IN PDEVICE_OBJECT dev_obj,
356     IN PIRP irp
357   ) {
358     irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
359     IoCompleteRequest(irp, IO_NO_INCREMENT);
360     return irp->IoStatus.Status;
361   }
362
363 /**
364  * Common IRP completion routine.
365  *
366  * @v irp               Points to the IRP to complete.
367  * @v info              Number of bytes returned for the IRP, or 0.
368  * @v status            Status for the IRP to complete.
369  * @ret NTSTATUS        Returns the status value, as passed.
370  */
371 winvblock__lib_func NTSTATUS STDCALL driver__complete_irp(
372     IN PIRP irp,
373     IN ULONG_PTR info,
374     IN NTSTATUS status
375   ) {
376     irp->IoStatus.Information = info;
377     irp->IoStatus.Status = status;
378     IoCompleteRequest(irp, IO_NO_INCREMENT);
379     #ifdef DEBUGIRPS
380     Debug_IrpEnd(irp, status);
381     #endif
382     return status;
383   }
384
385 /* Handle a power IRP. */
386 static NTSTATUS driver__dispatch_power_(
387     IN PDEVICE_OBJECT dev_obj,
388     IN PIRP irp
389   ) {
390     /* WvDevFromDevObj() checks for a NULL dev_obj */
391     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
392
393     #ifdef DEBUGIRPS
394     Debug_IrpStart(dev_obj, irp);
395     #endif
396     /* Check that the device exists. */
397     if (!dev || dev->State == WvDevStateDeleted) {
398         /* Even if it doesn't, a power IRP is important! */
399         PoStartNextPowerIrp(irp);
400         return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
401       }
402     /* Call the particular device's power handler. */
403     if (dev->IrpMj && dev->IrpMj->Power)
404       return dev->IrpMj->Power(dev, irp);
405     /* Otherwise, we don't support the IRP. */
406     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
407   }
408
409 /* Handle an IRP_MJ_CREATE or IRP_MJ_CLOSE IRP. */
410 static NTSTATUS driver__dispatch_create_close_(
411     IN PDEVICE_OBJECT dev_obj,
412     IN PIRP irp
413   ) {
414     /* WvDevFromDevObj() checks for a NULL dev_obj */
415     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
416
417     #ifdef DEBUGIRPS
418     Debug_IrpStart(dev_obj, irp);
419     #endif
420     /* Check that the device exists. */
421     if (!dev || dev->State == WvDevStateDeleted)
422       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
423     /* Always succeed with nothing to do. */
424     return driver__complete_irp(irp, 0, STATUS_SUCCESS);
425   }
426
427 /* Handle an IRP_MJ_SYSTEM_CONTROL IRP. */
428 static NTSTATUS driver__dispatch_sys_ctl_(
429     IN PDEVICE_OBJECT dev_obj,
430     IN PIRP irp
431   ) {
432     /* WvDevFromDevObj() checks for a NULL dev_obj */
433     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
434
435     #ifdef DEBUGIRPS
436     Debug_IrpStart(dev_obj, irp);
437     #endif
438     /* Check that the device exists. */
439     if (!dev || dev->State == WvDevStateDeleted)
440       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
441     /* Call the particular device's power handler. */
442     if (dev->IrpMj && dev->IrpMj->SysCtl)
443       return dev->IrpMj->SysCtl(dev, irp);
444     /* Otherwise, we don't support the IRP. */
445     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
446   }
447
448 /* Handle an IRP_MJ_DEVICE_CONTROL IRP. */
449 static NTSTATUS driver__dispatch_dev_ctl_(
450     IN PDEVICE_OBJECT dev_obj,
451     IN PIRP irp
452   ) {
453     /* WvDevFromDevObj() checks for a NULL dev_obj */
454     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
455     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
456
457     #ifdef DEBUGIRPS
458     Debug_IrpStart(dev_obj, irp);
459     #endif
460     /* Check that the device exists. */
461     if (!dev || dev->State == WvDevStateDeleted)
462       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
463     /* Call the particular device's power handler. */
464     if (dev->IrpMj && dev->IrpMj->DevCtl) {
465         return dev->IrpMj->DevCtl(
466             dev,
467             irp,
468             io_stack_loc->Parameters.DeviceIoControl.IoControlCode
469           );
470       }
471     /* Otherwise, we don't support the IRP. */
472     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
473   }
474
475 /* Handle an IRP_MJ_SCSI IRP. */
476 static NTSTATUS driver__dispatch_scsi_(
477     IN PDEVICE_OBJECT dev_obj,
478     IN PIRP irp
479   ) {
480     /* WvDevFromDevObj() checks for a NULL dev_obj */
481     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
482     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
483
484     #ifdef DEBUGIRPS
485     Debug_IrpStart(dev_obj, irp);
486     #endif
487     /* Check that the device exists. */
488     if (!dev || dev->State == WvDevStateDeleted)
489       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
490     /* Call the particular device's power handler. */
491     if (dev->IrpMj && dev->IrpMj->Scsi) {
492         return dev->IrpMj->Scsi(
493             dev,
494             irp,
495             io_stack_loc->Parameters.Scsi.Srb->Function
496           );
497       }
498     /* Otherwise, we don't support the IRP. */
499     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
500   }
501
502 /* Handle an IRP_MJ_PNP IRP. */
503 static NTSTATUS driver__dispatch_pnp_(
504     IN PDEVICE_OBJECT dev_obj,
505     IN PIRP irp
506   ) {
507     /* WvDevFromDevObj() checks for a NULL dev_obj */
508     WV_SP_DEV_T dev = WvDevFromDevObj(dev_obj);
509     PIO_STACK_LOCATION io_stack_loc = IoGetCurrentIrpStackLocation(irp);
510
511     #ifdef DEBUGIRPS
512     Debug_IrpStart(dev_obj, irp);
513     #endif
514     /* Check that the device exists. */
515     if (!dev || dev->State == WvDevStateDeleted)
516       return driver__complete_irp(irp, 0, STATUS_NO_SUCH_DEVICE);
517     /* Call the particular device's power handler. */
518     if (dev->IrpMj && dev->IrpMj->Pnp) {
519         return dev->IrpMj->Pnp(
520             dev,
521             irp,
522             io_stack_loc->MinorFunction
523           );
524       }
525     /* Otherwise, we don't support the IRP. */
526     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
527   }
528
529 static void STDCALL driver__unload_(IN PDRIVER_OBJECT DriverObject) {
530     DBG("Unloading...\n");
531     if (WvDriverStateHandle_ != NULL)
532       PoUnregisterSystemState(WvDriverStateHandle_);
533     IoDeleteSymbolicLink(&WvDriverBusDosname_);
534     WvDriverBusFdo_ = NULL;
535     wv_free(WvDriverOsLoadOpts_);
536     WvDriverStarted_ = FALSE;
537     DBG("Done\n");
538   }
539
540 winvblock__lib_func void STDCALL WvDriverCompletePendingIrp(IN PIRP Irp) {
541     #ifdef DEBUGIRPS
542     Debug_IrpEnd(Irp, Irp->IoStatus.Status);
543     #endif
544     IoCompleteRequest(Irp, IO_NO_INCREMENT);
545   }
546
547 /* Note the exception to the function naming convention. */
548 winvblock__lib_func NTSTATUS STDCALL Error(
549     IN PCHAR Message,
550     IN NTSTATUS Status
551   ) {
552     DBG("%s: 0x%08x\n", Message, Status);
553     return Status;
554   }
555
556 /**
557  * Get a pointer to the driver bus device.
558  *
559  * @ret         A pointer to the driver bus, or NULL.
560  */
561 winvblock__lib_func WV_SP_BUS_T driver__bus(void) {
562     if (!WvDriverBusFdo_) {
563         DBG("No driver bus device!\n");
564         return NULL;
565       }
566     return WvBusFromDev(WvDevFromDevObj(WvDriverBusFdo_));
567   }
568
569 /* Pass an IRP_MJ_SYSTEM_CONTROL IRP to the bus. */
570 static NTSTATUS STDCALL WvDriverBusSysCtl_(IN WV_SP_DEV_T dev, IN PIRP irp) {
571     WV_SP_BUS_T bus = WvBusFromDev(dev);
572
573     return WvBusSysCtl(bus, irp);
574   }