c24b8c67ca2b4bed341fbc984e3cf9660ba6d248
[people/sha0/winvblock.git] / src / winvblock / disk / disk.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  * Disk device specifics.
26  */
27
28 #include <ntddk.h>
29
30 #include "winvblock.h"
31 #include "wv_stdlib.h"
32 #include "portable.h"
33 #include "irp.h"
34 #include "driver.h"
35 #include "device.h"
36 #include "disk.h"
37 #include "disk_pnp.h"
38 #include "disk_dev_ctl.h"
39 #include "disk_scsi.h"
40 #include "debug.h"
41
42 #ifndef _MSC_VER
43 static long long
44 __divdi3 (
45   long long u,
46   long long v
47  )
48 {
49   return u / v;
50 }
51 #endif
52
53 /* Globals. */
54 static winvblock__uint32 next_disk = 0;
55 static LIST_ENTRY disk_list;
56 static KSPIN_LOCK disk_list_lock;
57 winvblock__bool disk__removable[disk__media_count] = { TRUE, FALSE, TRUE };
58 PWCHAR disk__compat_ids[disk__media_count] =
59   { L"GenSFloppy", L"GenDisk", L"GenCdRom" };
60
61 /* Forward declarations. */
62 static device__free_func free_disk;
63
64 static
65 disk__max_xfer_len_decl (
66   default_max_xfer_len
67  )
68 {
69   return 1024 * 1024;
70 }
71
72 /* Initialize a disk. */
73 static winvblock__bool STDCALL disk__init_(IN struct device__type * dev) {
74     disk__type_ptr disk_ptr = disk__get_ptr(dev);
75     return disk_ptr->disk_ops.init(disk_ptr);
76   }
77
78 disk__init_decl ( default_init )
79 {
80   return TRUE;
81 }
82
83 static void STDCALL disk__close_(IN struct device__type * dev_ptr) {
84     disk__type_ptr disk_ptr = disk__get_ptr(dev_ptr);
85     disk_ptr->disk_ops.close(disk_ptr);
86     return;
87   }
88
89 disk__close_decl ( default_close )
90 {
91   return;
92 }
93
94 static NTSTATUS STDCALL power(
95     IN PDEVICE_OBJECT DeviceObject,
96     IN PIRP Irp,
97     IN PIO_STACK_LOCATION Stack,
98     IN struct device__type * dev_ptr,
99     OUT winvblock__bool_ptr completion_ptr
100   )
101   {
102     PoStartNextPowerIrp ( Irp );
103     Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
104     IoCompleteRequest ( Irp, IO_NO_INCREMENT );
105     *completion_ptr = TRUE;
106     return STATUS_NOT_SUPPORTED;
107   }
108
109 static NTSTATUS STDCALL sys_ctl(
110     IN PDEVICE_OBJECT DeviceObject,
111     IN PIRP Irp,
112     IN PIO_STACK_LOCATION Stack,
113     IN struct device__type * dev_ptr,
114     OUT winvblock__bool_ptr completion_ptr
115   )
116 {
117   NTSTATUS status = Irp->IoStatus.Status;
118   IoCompleteRequest ( Irp, IO_NO_INCREMENT );
119   *completion_ptr = TRUE;
120   return status;
121 }
122
123 /**
124  * Create a disk PDO filled with the given disk parameters.
125  *
126  * @v dev_ptr           Populate PDO dev. ext. space from these details.
127  * @ret dev_obj_ptr     Points to the new PDO, or is NULL upon failure.
128  *
129  * Returns a Physical Device Object pointer on success, NULL for failure.
130  */
131 static PDEVICE_OBJECT STDCALL create_pdo(IN struct device__type * dev_ptr) {
132     /**
133      * @v disk_ptr          Used for pointing to disk details
134      * @v status            Status of last operation
135      * @v dev_obj_ptr       The new node's physical device object (PDO)
136      * @v new_ext_size      The extension space size
137      * @v disk_types[]      Floppy, hard disk, optical disc specifics
138      * @v characteristics[] Floppy, hard disk, optical disc specifics
139      */
140     disk__type_ptr disk_ptr;
141     NTSTATUS status;
142     PDEVICE_OBJECT dev_obj_ptr;
143     static DEVICE_TYPE disk_types[disk__media_count] =
144       { FILE_DEVICE_DISK, FILE_DEVICE_DISK, FILE_DEVICE_CD_ROM };
145     static winvblock__uint32 characteristics[disk__media_count] =
146       { FILE_REMOVABLE_MEDIA | FILE_FLOPPY_DISKETTE, 0,
147       FILE_REMOVABLE_MEDIA | FILE_READ_ONLY_DEVICE
148     };
149   
150     DBG ( "Entry\n" );
151     /*
152      * Point to the disk details provided
153      */
154     disk_ptr = disk__get_ptr ( dev_ptr );
155     /*
156      * Create the disk PDO
157      */
158     status =
159       IoCreateDevice ( driver__obj_ptr, sizeof ( driver__dev_ext ), NULL,
160                      disk_types[disk_ptr->media],
161                      FILE_AUTOGENERATED_DEVICE_NAME | FILE_DEVICE_SECURE_OPEN |
162                      characteristics[disk_ptr->media], FALSE, &dev_obj_ptr );
163     if ( !NT_SUCCESS ( status ) )
164       {
165         Error ( "IoCreateDevice", status );
166         return NULL;
167       }
168     /*
169      * Set associations for the PDO, device, disk
170      */
171     device__set(dev_obj_ptr, dev_ptr);
172     dev_ptr->Self = dev_obj_ptr;
173     KeInitializeEvent ( &disk_ptr->SearchEvent, SynchronizationEvent, FALSE );
174     KeInitializeSpinLock ( &disk_ptr->SpinLock );
175     disk_ptr->DiskNumber = InterlockedIncrement ( &next_disk ) - 1;
176     /*
177      * Some device parameters
178      */
179     dev_obj_ptr->Flags |= DO_DIRECT_IO; /* FIXME? */
180     dev_obj_ptr->Flags |= DO_POWER_INRUSH;      /* FIXME? */
181     DBG ( "Exit\n" );
182     return dev_obj_ptr;
183   }
184
185 /*
186  * fat_extra and fat_super taken from syslinux/memdisk/setup.c by
187  * H. Peter Anvin.  Licensed under the terms of the GNU General Public
188  * License version 2 or later.
189  */
190 #ifdef _MSC_VER
191 #  pragma pack(1)
192 #endif
193 winvblock__def_struct ( fat_extra )
194 {
195   winvblock__uint8 bs_drvnum;
196   winvblock__uint8 bs_resv1;
197   winvblock__uint8 bs_bootsig;
198   winvblock__uint32 bs_volid;
199   char bs_vollab[11];
200   char bs_filsystype[8];
201 } __attribute__ ( ( packed ) );
202
203 winvblock__def_struct ( fat_super )
204 {
205   winvblock__uint8 bs_jmpboot[3];
206   char bs_oemname[8];
207   winvblock__uint16 bpb_bytspersec;
208   winvblock__uint8 bpb_secperclus;
209   winvblock__uint16 bpb_rsvdseccnt;
210   winvblock__uint8 bpb_numfats;
211   winvblock__uint16 bpb_rootentcnt;
212   winvblock__uint16 bpb_totsec16;
213   winvblock__uint8 bpb_media;
214   winvblock__uint16 bpb_fatsz16;
215   winvblock__uint16 bpb_secpertrk;
216   winvblock__uint16 bpb_numheads;
217   winvblock__uint32 bpb_hiddsec;
218   winvblock__uint32 bpb_totsec32;
219   union
220   {
221     struct
222     {
223       fat_extra extra;
224     } fat16;
225     struct
226     {
227       winvblock__uint32 bpb_fatsz32;
228       winvblock__uint16 bpb_extflags;
229       winvblock__uint16 bpb_fsver;
230       winvblock__uint32 bpb_rootclus;
231       winvblock__uint16 bpb_fsinfo;
232       winvblock__uint16 bpb_bkbootsec;
233       char bpb_reserved[12];
234       /*
235        * Clever, eh?  Same fields, different offset... 
236        */
237       fat_extra extra;
238     } fat32 __attribute__ ( ( packed ) );
239   } x;
240 } __attribute__ ( ( __packed__ ) );
241
242 #ifdef _MSC_VER
243 #  pragma pack()
244 #endif
245
246 /**
247  * Attempt to guess a disk's geometry
248  *
249  * @v boot_sect_ptr     The MBR or VBR with possible geometry clues
250  * @v disk_ptr          The disk to set the geometry for
251  */
252 winvblock__lib_func void
253 disk__guess_geometry (
254   IN disk__boot_sect_ptr boot_sect_ptr,
255   IN OUT disk__type_ptr disk_ptr
256  )
257 {
258   winvblock__uint16 heads = 0,
259     sects_per_track = 0,
260     cylinders;
261   mbr_ptr as_mbr;
262
263   if ( ( boot_sect_ptr == NULL ) || ( disk_ptr == NULL ) )
264     return;
265
266   /*
267    * FAT superblock geometry checking taken from syslinux/memdisk/setup.c by
268    * H. Peter Anvin.  Licensed under the terms of the GNU General Public
269    * License version 2 or later.
270    */
271   {
272     /*
273      * Look for a FAT superblock and if we find something that looks
274      * enough like one, use geometry from that.  This takes care of
275      * megafloppy images and unpartitioned hard disks. 
276      */
277     fat_extra_ptr extra = NULL;
278     fat_super_ptr fs = ( fat_super_ptr ) boot_sect_ptr;
279
280     if ( ( fs->bpb_media == 0xf0 || fs->bpb_media >= 0xf8 )
281          && ( fs->bs_jmpboot[0] == 0xe9 || fs->bs_jmpboot[0] == 0xeb )
282          && fs->bpb_bytspersec == 512 && fs->bpb_numheads >= 1
283          && fs->bpb_numheads <= 256 && fs->bpb_secpertrk >= 1
284          && fs->bpb_secpertrk <= 63 )
285       {
286         extra = fs->bpb_fatsz16 ? &fs->x.fat16.extra : &fs->x.fat32.extra;
287         if ( !
288              ( extra->bs_bootsig == 0x29 && extra->bs_filsystype[0] == 'F'
289                && extra->bs_filsystype[1] == 'A'
290                && extra->bs_filsystype[2] == 'T' ) )
291           extra = NULL;
292       }
293     if ( extra )
294       {
295         heads = fs->bpb_numheads;
296         sects_per_track = fs->bpb_secpertrk;
297       }
298   }
299   /*
300    * If we couldn't parse a FAT superblock, try checking MBR params.
301    * Logic derived from syslinux/memdisk/setup.c by H. Peter Anvin
302    */
303   as_mbr = ( mbr_ptr ) boot_sect_ptr;
304   if ( ( heads == 0 ) && ( sects_per_track == 0 )
305        && ( as_mbr->mbr_sig == 0xAA55 ) )
306     {
307       int i;
308       for ( i = 0; i < 4; i++ )
309         {
310           if ( !( as_mbr->partition[i].status & 0x7f )
311                && as_mbr->partition[i].type )
312             {
313               winvblock__uint8 h,
314                s;
315
316               h = chs_head ( as_mbr->partition[i].chs_start ) + 1;
317               s = chs_sector ( as_mbr->partition[i].chs_start );
318
319               if ( heads < h )
320                 heads = h;
321               if ( sects_per_track < s )
322                 sects_per_track = s;
323
324               h = chs_head ( as_mbr->partition[i].chs_end ) + 1;
325               s = chs_sector ( as_mbr->partition[i].chs_end );
326
327               if ( heads < h )
328                 heads = h;
329               if ( sects_per_track < s )
330                 sects_per_track = s;
331             }
332         }
333     }
334   /*
335    * If we were unable to guess, use some hopeful defaults
336    */
337   if ( !heads )
338     heads = 255;
339   if ( !sects_per_track )
340     sects_per_track = 63;
341   /*
342    * Set params that are not already filled
343    */
344   if ( !disk_ptr->Heads )
345     disk_ptr->Heads = heads;
346   if ( !disk_ptr->Sectors )
347     disk_ptr->Sectors = sects_per_track;
348   if ( !disk_ptr->Cylinders )
349     disk_ptr->Cylinders = disk_ptr->LBADiskSize / ( heads * sects_per_track );
350 }
351
352 /* Disk dispatch routine. */
353 static NTSTATUS STDCALL (disk_dispatch)(
354     IN PDEVICE_OBJECT (dev),
355     IN PIRP (irp)
356   ) {
357     NTSTATUS (status);
358     winvblock__bool (completion) = FALSE;
359     static const irp__handling (handling_table)[] = {
360         /*
361          * Major, minor, any major?, any minor?, handler
362          * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
363          * Note that the fall-through case must come FIRST!
364          * Why? It sets completion to true, so others won't be called.
365          */
366         {                     0, 0,  TRUE,  TRUE,  driver__not_supported },
367         {          IRP_MJ_CLOSE, 0, FALSE,  TRUE,   driver__create_close },
368         {         IRP_MJ_CREATE, 0, FALSE,  TRUE,   driver__create_close },
369         { IRP_MJ_DEVICE_CONTROL, 0, FALSE,  TRUE, disk_dev_ctl__dispatch },
370         { IRP_MJ_SYSTEM_CONTROL, 0, FALSE,  TRUE,                sys_ctl },
371         {          IRP_MJ_POWER, 0, FALSE,  TRUE,                  power },
372         {           IRP_MJ_SCSI, 0, FALSE,  TRUE,    disk_scsi__dispatch },
373         {            IRP_MJ_PNP, 0, FALSE,  TRUE,       disk_pnp__simple },
374         {            IRP_MJ_PNP,
375          IRP_MN_QUERY_CAPABILITIES, FALSE, FALSE,
376                                             disk_pnp__query_capabilities },
377         {            IRP_MJ_PNP,
378       IRP_MN_QUERY_BUS_INFORMATION, FALSE, FALSE,
379                                                 disk_pnp__query_bus_info },
380         {            IRP_MJ_PNP,
381      IRP_MN_QUERY_DEVICE_RELATIONS, FALSE, FALSE,
382                                            disk_pnp__query_dev_relations },
383         {            IRP_MJ_PNP,
384           IRP_MN_QUERY_DEVICE_TEXT, FALSE, FALSE,
385                                                 disk_pnp__query_dev_text },
386         {            IRP_MJ_PNP,
387                    IRP_MN_QUERY_ID, FALSE, FALSE,   device__pnp_query_id },
388       };
389
390     status = irp__process_with_table(
391         dev,
392         irp,
393         handling_table,
394         sizeof handling_table,
395         &completion
396       );
397     #ifdef DEBUGIRPS
398     if (status != STATUS_PENDING)
399       Debug_IrpEnd(irp, status);
400     #endif
401
402     return status;
403   }
404
405 /**
406  * Create a new disk
407  *
408  * @ret disk_ptr        The address of a new disk, or NULL for failure
409  *
410  * See the header file for additional details
411  */
412 winvblock__lib_func disk__type_ptr
413 disk__create (
414   void
415  )
416 {
417   struct device__type * dev_ptr;
418   disk__type_ptr disk_ptr;
419
420   /*
421    * Try to create a device
422    */
423   dev_ptr = device__create (  );
424   if ( dev_ptr == NULL )
425     goto err_nodev;
426   /*
427    * Disk devices might be used for booting and should
428    * not be allocated from a paged memory pool
429    */
430   disk_ptr = wv_mallocz(sizeof *disk_ptr);
431   if ( disk_ptr == NULL )
432     goto err_nodisk;
433   /*
434    * Track the new disk in our global list
435    */
436   ExInterlockedInsertTailList ( &disk_list, &disk_ptr->tracking,
437                                 &disk_list_lock );
438   /*
439    * Populate non-zero device defaults
440    */
441   disk_ptr->device = dev_ptr;
442   disk_ptr->prev_free = dev_ptr->ops.free;
443   disk_ptr->disk_ops.max_xfer_len = default_max_xfer_len;
444   disk_ptr->disk_ops.init = default_init;
445   disk_ptr->disk_ops.close = default_close;
446   dev_ptr->dispatch = disk_dispatch;
447   dev_ptr->ops.close = disk__close_;
448   dev_ptr->ops.create_pdo = create_pdo;
449   dev_ptr->ops.free = free_disk;
450   dev_ptr->ops.init = disk__init_;
451   dev_ptr->ext = disk_ptr;
452   KeInitializeSpinLock ( &disk_ptr->SpinLock );
453
454   return disk_ptr;
455
456 err_nodisk:
457
458   device__free ( dev_ptr );
459 err_nodev:
460
461   return NULL;
462 }
463
464 /**
465  * Initialize the global, disk-common environment
466  *
467  * @ret ntstatus        STATUS_SUCCESS or the NTSTATUS for a failure
468  */
469 NTSTATUS
470 disk__init (
471   void
472  )
473 {
474   /*
475    * Initialize the global list of disks
476    */
477   InitializeListHead ( &disk_list );
478   KeInitializeSpinLock ( &disk_list_lock );
479
480   return STATUS_SUCCESS;
481 }
482
483 /**
484  * Default disk deletion operation.
485  *
486  * @v dev_ptr           Points to the disk device to delete.
487  */
488 static void STDCALL free_disk(IN struct device__type * dev_ptr)
489   {
490     disk__type_ptr disk_ptr = disk__get_ptr(dev_ptr);
491     /* Free the "inherited class". */
492     disk_ptr->prev_free(dev_ptr);
493     /*
494      * Track the disk deletion in our global list.  Unfortunately,
495      * for now we have faith that a disk won't be deleted twice and
496      * result in a race condition.  Something to keep in mind...
497      */
498     ExInterlockedRemoveHeadList(disk_ptr->tracking.Blink, &disk_list_lock);
499   
500     wv_free(disk_ptr);
501   }
502
503 /* See header for details */
504 disk__io_decl ( disk__io )
505 {
506   disk__type_ptr disk_ptr;
507
508   /*
509    * Establish a pointer to the disk
510    */
511   disk_ptr = disk__get_ptr ( dev_ptr );
512
513   return disk_ptr->disk_ops.io ( dev_ptr, mode, start_sector, sector_count,
514                                  buffer, irp );
515 }
516
517 /* See header for details */
518 disk__max_xfer_len_decl ( disk__max_xfer_len )
519 {
520   return disk_ptr->disk_ops.max_xfer_len ( disk_ptr );
521 }