[install] Add reg_set_sz() function
[sanbootconf.git] / driver / sanbootconf.c
1 /*
2  * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <ntddk.h>
20 #include <initguid.h>
21 #include <wdmsec.h>
22 #include <ntdddisk.h>
23 #include "sanbootconf.h"
24 #include "acpi.h"
25 #include "ibft.h"
26 #include "sbft.h"
27
28 /** Maximum time to wait for system disk, in seconds */
29 #define SANBOOTCONF_MAX_WAIT 120
30
31 /** Device private data */
32 typedef struct _SANBOOTCONF_PRIV {
33         /* Copy of iBFT, if any */
34         PIBFT_TABLE ibft;
35         /* Copy of sBFT, if any */
36         PSBFT_TABLE sbft;
37 } SANBOOTCONF_PRIV, *PSANBOOTCONF_PRIV;
38
39 /** Unique GUID for IoCreateDeviceSecure() */
40 DEFINE_GUID ( GUID_SANBOOTCONF_CLASS, 0x8a2f8602, 0x8f0b, 0x4138,
41               0x8e, 0x16, 0x51, 0x9a, 0x59, 0xf3, 0x07, 0xca );
42
43 /** IoControl code to retrieve iBFT */
44 #define IOCTL_SANBOOTCONF_IBFT \
45         CTL_CODE ( FILE_DEVICE_UNKNOWN, 0x0001, METHOD_BUFFERED, \
46                    FILE_READ_ACCESS )
47
48 /** IoControl code to retrieve sBFT */
49 #define IOCTL_SANBOOTCONF_SBFT \
50         CTL_CODE ( FILE_DEVICE_UNKNOWN, 0x0873, METHOD_BUFFERED, \
51                    FILE_READ_ACCESS )
52
53 /** Device name */
54 static const WCHAR sanbootconf_device_name[] = L"\\Device\\sanbootconf";
55
56 /** Device symlinks */
57 static const PWCHAR sanbootconf_device_symlink[] = {
58         L"\\Device\\iSCSIBoot",
59         L"\\DosDevices\\iSCSIBoot",
60 };
61
62 /**
63  * Dummy IRP handler
64  *
65  * @v device            Device object
66  * @v irp               IRP
67  * @ret ntstatus        NT status
68  */
69 static NTSTATUS sanbootconf_dummy_irp ( PDEVICE_OBJECT device, PIRP irp ) {
70
71         irp->IoStatus.Status = STATUS_SUCCESS;
72         irp->IoStatus.Information = 0;
73         IoCompleteRequest ( irp, IO_NO_INCREMENT );
74
75         ( VOID ) device;
76         return STATUS_SUCCESS;
77 }
78
79 /**
80  * IoControl IRP handler
81  *
82  * @v device            Device object
83  * @v irp               IRP
84  * @ret ntstatus        NT status
85  */
86 static NTSTATUS sanbootconf_iocontrol_irp ( PDEVICE_OBJECT device, PIRP irp ) {
87         PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation ( irp );
88         PSANBOOTCONF_PRIV priv = device->DeviceExtension;
89         ULONG len = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
90         NTSTATUS status;
91
92         switch ( irpsp->Parameters.DeviceIoControl.IoControlCode ) {
93         case IOCTL_SANBOOTCONF_IBFT:
94                 DbgPrint ( "iBFT requested\n" );
95                 if ( priv->ibft ) {
96                         if ( len > priv->ibft->acpi.length )
97                                 len = priv->ibft->acpi.length;
98                         RtlCopyMemory ( irp->AssociatedIrp.SystemBuffer,
99                                         priv->ibft, len );
100                         status = STATUS_SUCCESS;
101                 } else {
102                         DbgPrint ( "No iBFT available!\n" );
103                         status = STATUS_NO_SUCH_FILE;
104                 }
105                 break;
106         case IOCTL_SANBOOTCONF_SBFT:
107                 DbgPrint ( "sBFT requested\n" );
108                 if ( priv->sbft ) {
109                         if ( len > priv->sbft->acpi.length )
110                                 len = priv->sbft->acpi.length;
111                         RtlCopyMemory ( irp->AssociatedIrp.SystemBuffer,
112                                         priv->sbft, len );
113                         status = STATUS_SUCCESS;
114                 } else {
115                         DbgPrint ( "No sbft available!\n" );
116                         status = STATUS_NO_SUCH_FILE;
117                 }
118                 break;
119         default:
120                 DbgPrint ( "Unrecognised IoControl %x\n",
121                            irpsp->Parameters.DeviceIoControl.IoControlCode );
122                 status = STATUS_INVALID_DEVICE_REQUEST;
123                 break;
124         }
125
126         irp->IoStatus.Status = status;
127         irp->IoStatus.Information = 0;
128         IoCompleteRequest ( irp, IO_NO_INCREMENT );
129         return status;
130 }
131
132 /**
133  * Create device object and symlinks
134  *
135  * @v driver            Driver object
136  * @ret ntstatus        NT status
137  */
138 static NTSTATUS create_sanbootconf_device ( PDRIVER_OBJECT driver,
139                                             PDEVICE_OBJECT *device ) {
140         UNICODE_STRING u_device_name;
141         UNICODE_STRING u_device_symlink;
142         PSANBOOTCONF_PRIV priv;
143         ULONG i;
144         NTSTATUS status;
145
146         /* Create device */
147         RtlInitUnicodeString ( &u_device_name, sanbootconf_device_name );
148         status = IoCreateDeviceSecure ( driver, sizeof ( *priv ),
149                                         &u_device_name, FILE_DEVICE_UNKNOWN,
150                                         FILE_DEVICE_SECURE_OPEN, FALSE,
151                                         &SDDL_DEVOBJ_SYS_ALL_ADM_ALL,
152                                         &GUID_SANBOOTCONF_CLASS, device );
153         if ( ! NT_SUCCESS ( status ) ) {
154                 DbgPrint ( "Could not create device \"%S\": %x\n",
155                            sanbootconf_device_name, status );
156                 return status;
157         }
158         priv = (*device)->DeviceExtension;
159         RtlZeroMemory ( priv, sizeof ( *priv ) );
160         (*device)->Flags &= ~DO_DEVICE_INITIALIZING;
161
162         /* Create device symlinks */
163         for ( i = 0 ; i < ( sizeof ( sanbootconf_device_symlink ) /
164                             sizeof ( sanbootconf_device_symlink[0] ) ) ; i++ ){
165                 RtlInitUnicodeString ( &u_device_symlink,
166                                        sanbootconf_device_symlink[i] );
167                 status = IoCreateSymbolicLink ( &u_device_symlink,
168                                                 &u_device_name );
169                 if ( ! NT_SUCCESS ( status ) ) {
170                         DbgPrint ( "Could not create device symlink \"%S\": "
171                                    "%x\n", sanbootconf_device_symlink[i],
172                                    status );
173                         return status;
174                 }
175         }
176
177         return STATUS_SUCCESS;
178 }
179
180 /**
181  * Fetch disk signature
182  *
183  * @v name              Disk device name
184  * @v device            Disk device object
185  * @v file              Disk file object
186  * @v info              Partition information buffer
187  * @ret status          NT status
188  */
189 static NTSTATUS fetch_partition_info ( PUNICODE_STRING name,
190                                        PDEVICE_OBJECT device,
191                                        PFILE_OBJECT file,
192                                        PDISK_PARTITION_INFO info ) {
193         KEVENT event;
194         struct {
195                 DISK_GEOMETRY_EX geometry;
196                 DISK_PARTITION_INFO __dummy_partition_info;
197                 DISK_DETECTION_INFO __dummy_detection_info;
198         } buf;
199         IO_STATUS_BLOCK io_status;
200         PIRP irp;
201         PIO_STACK_LOCATION io_stack;
202         PDISK_PARTITION_INFO partition_info;
203         NTSTATUS status;
204
205         /* Construct IRP to fetch drive geometry */
206         KeInitializeEvent ( &event, NotificationEvent, FALSE );
207         irp = IoBuildDeviceIoControlRequest ( IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
208                                               device, NULL, 0, &buf,
209                                               sizeof ( buf ), FALSE, &event,
210                                               &io_status );
211         if ( ! irp ) {
212                 DbgPrint ( "Could not build IRP to retrieve geometry for "
213                            "\"%wZ\"\n", name );
214                 return STATUS_UNSUCCESSFUL;
215         }
216         io_stack = IoGetNextIrpStackLocation ( irp );
217         io_stack->FileObject = file;
218
219         /* Issue IRP */
220         status = IoCallDriver ( device, irp );
221         if ( status == STATUS_PENDING ) {
222                 status = KeWaitForSingleObject ( &event, Executive, KernelMode,
223                                                  FALSE, NULL );
224         }
225         if ( NT_SUCCESS ( status ) )
226                 status = io_status.Status;
227         if ( ! NT_SUCCESS ( status ) ) {
228                 DbgPrint ( "IRP failed to retrieve geometry for \"%wZ\": %x\n",
229                            name, status );
230                 return status;
231         }
232
233         /* Extract partition information */
234         partition_info = DiskGeometryGetPartition ( &buf.geometry );
235         memcpy ( info, partition_info, sizeof ( *info ) );
236
237         return STATUS_SUCCESS;
238 }
239
240 /**
241  * Check for system disk
242  *
243  * @v name              Disk device name
244  * @v boot_info         Boot disk information
245  * @ret status          NT status
246  */
247 static NTSTATUS check_system_disk ( PUNICODE_STRING name,
248                                     PBOOTDISK_INFORMATION_EX boot_info ) {
249         BOOLEAN must_disable;
250         PFILE_OBJECT file;
251         PDEVICE_OBJECT device;
252         DISK_PARTITION_INFO info;
253         NTSTATUS status;
254
255         /* Enable interface if not already done */
256         status = IoSetDeviceInterfaceState ( name, TRUE );
257         must_disable = ( NT_SUCCESS ( status ) ? TRUE : FALSE );
258
259         /* Get device and file object pointers */
260         status = IoGetDeviceObjectPointer ( name, FILE_ALL_ACCESS, &file,
261                                             &device );
262         if ( ! NT_SUCCESS ( status ) ) {
263                 /* Most probably not yet attached */
264                 DbgPrint ( "  Disk unavailable (%lx): \"%wZ\"\n", status, name );
265                 goto err_iogetdeviceobjectpointer;
266         }
267
268         /* Get disk signature */
269         status = fetch_partition_info ( name, device, file, &info );
270         if ( ! NT_SUCCESS ( status ) )
271                 goto err_fetch_partition_info;
272         switch ( info.PartitionStyle ) {
273         case PARTITION_STYLE_MBR:
274                 DbgPrint ( "  MBR %08lx: \"%wZ\"\n",
275                            info.Mbr.Signature, name );
276                 if ( boot_info->SystemDeviceIsGpt )
277                         goto err_not_system_disk;
278                 if ( info.Mbr.Signature != boot_info->SystemDeviceSignature )
279                         goto err_not_system_disk;
280                 break;
281         case PARTITION_STYLE_GPT:
282                 DbgPrint ( "  GPT " GUID_FMT ": \"%wZ\"\n",
283                            GUID_ARGS ( info.Gpt.DiskId ), name );
284                 if ( ! boot_info->SystemDeviceIsGpt )
285                         goto err_not_system_disk;
286                 if ( ! IsEqualGUID ( &info.Gpt.DiskId,
287                                      &boot_info->SystemDeviceGuid ) )
288                         goto err_not_system_disk;
289                 break;
290         default:
291                 DbgPrint ( "  Unhandled disk style %d: \"%wZ\"\n",
292                            info.PartitionStyle, name );
293                 status = STATUS_NOT_SUPPORTED;
294                 goto err_unknown_type;
295         }
296
297         /* Success */
298         DbgPrint ( "Found system disk at \"%wZ\"\n", name );
299         status = STATUS_SUCCESS;
300
301  err_not_system_disk:
302  err_unknown_type:
303  err_fetch_partition_info:
304         /* Drop object reference */
305         ObDereferenceObject ( file );
306  err_iogetdeviceobjectpointer:
307         /* Disable interface if we had to enable it */
308         if ( must_disable )
309                 IoSetDeviceInterfaceState ( name, FALSE );
310         return status;
311 }
312
313 /**
314  * Find system disk
315  *
316  * @ret status          NT status
317  */
318 static NTSTATUS find_system_disk ( VOID ) {
319         union {
320                 BOOTDISK_INFORMATION basic;
321                 BOOTDISK_INFORMATION_EX extended;
322         } boot_info;
323         PWSTR symlinks;
324         PWSTR symlink;
325         UNICODE_STRING u_symlink;
326         NTSTATUS status;
327
328         /* Get boot disk information */
329         RtlZeroMemory ( &boot_info, sizeof ( boot_info ) );
330         status = IoGetBootDiskInformation ( &boot_info.basic,
331                                             sizeof ( boot_info ) );
332         if ( ! NT_SUCCESS ( status ) ) {
333                 DbgPrint ( "Could not get boot disk information: %x\n",
334                            status );
335                 goto err_getbootdiskinformation;
336         }
337         if ( boot_info.extended.SystemDeviceIsGpt ) {
338                 DbgPrint ( "  System disk is GPT " GUID_FMT ",",
339                            GUID_ARGS ( boot_info.extended.SystemDeviceGuid ) );
340         } else if ( boot_info.extended.SystemDeviceSignature ) {
341                 DbgPrint ( "  System disk is MBR %08lx,",
342                            boot_info.extended.SystemDeviceSignature );
343         } else {
344                 DbgPrint ( "  System disk is <unknown>," );
345         }
346         if ( boot_info.extended.BootDeviceIsGpt ) {
347                 DbgPrint ( " boot disk is GPT " GUID_FMT "\n",
348                            GUID_ARGS ( boot_info.extended.BootDeviceGuid ) );
349         } else if ( boot_info.extended.BootDeviceSignature ) {
350                 DbgPrint ( " boot disk is MBR %08lx\n",
351                            boot_info.extended.BootDeviceSignature );
352         } else {
353                 DbgPrint ( " boot disk is <unknown>\n" );
354         }
355
356         /* Enumerate all disks */
357         status = IoGetDeviceInterfaces ( &GUID_DEVINTERFACE_DISK, NULL,
358                                          DEVICE_INTERFACE_INCLUDE_NONACTIVE,
359                                          &symlinks );
360         if ( ! NT_SUCCESS ( status ) ) {
361                 DbgPrint ( "Could not fetch disk list: %x\n", status );
362                 goto err_getdeviceinterfaces;
363         }
364
365         /* Look for the system disk */
366         for ( symlink = symlinks ;
367               RtlInitUnicodeString ( &u_symlink, symlink ) , *symlink ;
368               symlink += ( ( u_symlink.Length / sizeof ( *symlink ) ) + 1 ) ) {
369                 status = check_system_disk ( &u_symlink, &boot_info.extended );
370                 if ( NT_SUCCESS ( status ) )
371                         break;
372         }
373
374         /* Free object list */
375         ExFreePool ( symlinks );
376  err_getdeviceinterfaces:
377  err_getbootdiskinformation:
378         return status;
379 }
380
381 /**
382  * Wait for SAN system disk to appear
383  *
384  * @v driver            Driver object
385  * @v context           Context
386  * @v count             Number of times this routine has been called
387  */
388 static VOID sanbootconf_wait ( PDRIVER_OBJECT driver, PVOID context,
389                                ULONG count ) {
390         LARGE_INTEGER delay;
391         NTSTATUS status;
392
393         DbgPrint ( "Waiting for SAN system disk (attempt %ld)\n", count );
394
395         /* Check for existence of system disk */
396         status = find_system_disk();
397         if ( NT_SUCCESS ( status ) ) {
398                 DbgPrint ( "Found SAN system disk; proceeding with boot\n" );
399                 return;
400         }
401
402         /* Give up after too many attempts */
403         if ( count >= SANBOOTCONF_MAX_WAIT ) {
404                 DbgPrint ( "Giving up waiting for SAN system disk\n" );
405                 return;
406         }
407
408         /* Sleep for a second, reschedule self */
409         delay.QuadPart = -10000000L /* 1 second, relative to current time */;
410         KeDelayExecutionThread ( KernelMode, FALSE, &delay );
411         IoRegisterBootDriverReinitialization ( driver, sanbootconf_wait,
412                                                context );
413 }
414
415 /**
416  * Driver entry point
417  *
418  * @v DriverObject      Driver object
419  * @v RegistryPath      Driver-specific registry path
420  * @ret ntstatus        NT status
421  */
422 NTSTATUS DriverEntry ( IN PDRIVER_OBJECT DriverObject,
423                        IN PUNICODE_STRING RegistryPath ) {
424         PDEVICE_OBJECT device;
425         PSANBOOTCONF_PRIV priv;
426         PACPI_DESCRIPTION_HEADER table;
427         NTSTATUS status;
428         BOOLEAN found_san = FALSE;
429
430         DbgPrint ( "SAN Boot Configuration Driver initialising\n" );
431
432         /* Hook in driver methods */
433         DriverObject->MajorFunction[IRP_MJ_CREATE] = sanbootconf_dummy_irp;
434         DriverObject->MajorFunction[IRP_MJ_CLOSE] = sanbootconf_dummy_irp;
435         DriverObject->MajorFunction[IRP_MJ_CLEANUP] = sanbootconf_dummy_irp;
436         DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
437                 sanbootconf_iocontrol_irp;
438
439         /* Create device object */
440         status = create_sanbootconf_device ( DriverObject, &device );
441         if ( ! NT_SUCCESS ( status ) )
442                 goto err_create_sanbootconf_device;
443         priv = device->DeviceExtension;
444
445         /* Look for an iBFT */
446         status = find_acpi_table ( IBFT_SIG, &table );
447         if ( NT_SUCCESS ( status ) ) {
448                 priv->ibft = ( ( PIBFT_TABLE ) table );
449                 parse_ibft ( priv->ibft );
450                 found_san = TRUE;
451         } else {
452                 /* Lack of an iBFT is not necessarily an error */
453                 DbgPrint ( "No iBFT found\n" );
454                 status = STATUS_SUCCESS;
455         }
456
457         /* Look for an sBFT */
458         status = find_acpi_table ( SBFT_SIG, &table );
459         if ( NT_SUCCESS ( status ) ) {
460                 priv->sbft = ( ( PSBFT_TABLE ) table );
461                 parse_sbft ( priv->sbft );
462                 found_san = TRUE;
463         } else {
464                 /* Lack of an sBFT is not necessarily an error */
465                 DbgPrint ( "No sBFT found\n" );
466                 status = STATUS_SUCCESS;
467         }
468
469         /* Wait for system disk, if booting from SAN */
470         if ( found_san ) {
471                 DbgPrint ( "Attempting SAN boot; will wait for system disk\n");
472                 IoRegisterBootDriverReinitialization ( DriverObject,
473                                                        sanbootconf_wait,
474                                                        NULL );
475         } else {
476                 DbgPrint ( "No SAN boot method detected\n" );
477         }
478
479  err_create_sanbootconf_device:
480         ( VOID ) RegistryPath;
481         return status;
482 }