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