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