4c9854aa689a4a7b39b617de7cd383e6612339fc
[people/sha0/winvblock.git] / src / winvblock / disk / disk.c
1 /**
2  * Copyright (C) 2009-2011, 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 "portable.h"
31 #include "winvblock.h"
32 #include "wv_stdlib.h"
33 #include "irp.h"
34 #include "driver.h"
35 #include "bus.h"
36 #include "device.h"
37 #include "disk.h"
38 #include "debug.h"
39
40 #ifndef _MSC_VER
41 static long long __divdi3(long long u, long long v) {
42     return u / v;
43   }
44 #endif
45
46 /* IRP_MJ_DEVICE_CONTROL dispatcher from disk/dev_ctl.c */
47 extern WV_F_DEV_CTL disk_dev_ctl__dispatch;
48 /* IRP_MJ_SCSI dispatcher from disk/scsi.c */
49 extern WV_F_DEV_SCSI disk_scsi__dispatch;
50 /* IRP_MJ_PNP dispatcher from disk/pnp.c */
51 extern WV_F_DEV_PNP disk_pnp__dispatch;
52
53 /* Forward declarations. */
54 static WV_F_DEV_FREE WvDiskDevFree_;
55 static WV_F_DEV_DISPATCH WvDiskIrpPower_;
56 static WV_F_DEV_DISPATCH WvDiskIrpSysCtl_;
57 static WV_F_DISK_INIT WvDiskDefaultInit_;
58 static WV_F_DISK_CLOSE WvDiskDefaultClose_;
59
60 /* Globals. */
61 BOOLEAN WvDiskIsRemovable[WvlDiskMediaTypes] = { TRUE, FALSE, TRUE };
62 PWCHAR WvDiskCompatIds[WvlDiskMediaTypes] = {
63     L"GenSFloppy",
64     L"GenDisk",
65     L"GenCdRom"
66   };
67 /* Device IRP major function dispatch table. */
68 WV_S_DEV_IRP_MJ WvDiskIrpMj_ = {
69     WvDiskIrpPower_,
70     WvDiskIrpSysCtl_,
71     disk_dev_ctl__dispatch,
72     disk_scsi__dispatch,
73     disk_pnp__dispatch,
74   };
75
76 static UINT32 WvDiskDefaultMaxXferLen_(IN WV_SP_DISK_T disk) {
77     return 1024 * 1024;
78   }
79
80 /* Initialize a disk. */
81 static BOOLEAN STDCALL WvDiskDevInit_(IN WV_SP_DEV_T dev) {
82     WV_SP_DISK_T disk = disk__get_ptr(dev);
83
84     /* Use the disk operation, if there is one. */
85     if (disk->disk_ops.Init)
86       return disk->disk_ops.Init(disk);
87     return TRUE;
88   }
89
90 static BOOLEAN STDCALL WvDiskDefaultInit_(IN WV_SP_DISK_T disk) {
91     return TRUE;
92   }
93
94 static VOID STDCALL WvDiskDevClose_(IN WV_SP_DEV_T dev) {
95     WV_SP_DISK_T disk = disk__get_ptr(dev);
96
97     /* Use the disk operation, if there is one. */
98     if (disk->disk_ops.Close)
99       disk->disk_ops.Close(disk);
100     return;
101   }
102
103 static VOID STDCALL WvDiskDefaultClose_(IN WV_SP_DISK_T disk) {
104     return;
105   }
106
107 static NTSTATUS STDCALL WvDiskIrpPower_(IN WV_SP_DEV_T dev, IN PIRP irp) {
108     PoStartNextPowerIrp(irp);
109     return WvlIrpComplete(irp, 0, STATUS_NOT_SUPPORTED);
110   }
111
112 static NTSTATUS STDCALL WvDiskIrpSysCtl_(IN WV_SP_DEV_T dev, IN PIRP irp) {
113     return WvlIrpComplete(irp, 0, irp->IoStatus.Status);
114   }
115
116
117 /**
118  * Create a disk PDO.
119  *
120  * @v DriverObj         The driver to associate with the PDO.
121  * @v ExtensionSize     The device object extension size.
122  * @v MediaType         The media type for the device.
123  * @v Pdo               Points to the PDO pointer to fill, upon success.
124  */
125 WVL_M_LIB NTSTATUS STDCALL WvlDiskCreatePdo(
126     IN PDRIVER_OBJECT DriverObj,
127     IN SIZE_T ExtensionSize,
128     IN WVL_E_DISK_MEDIA_TYPE MediaType,
129     OUT PDEVICE_OBJECT * Pdo
130   ) {
131     /* Floppy, hard disk, optical disc specifics. */
132     static const DEVICE_TYPE disk_types[WvlDiskMediaTypes] =
133       { FILE_DEVICE_DISK, FILE_DEVICE_DISK, FILE_DEVICE_CD_ROM };
134     static const UINT32 characteristics[WvlDiskMediaTypes] = {
135         FILE_REMOVABLE_MEDIA | FILE_FLOPPY_DISKETTE,
136         0,
137         FILE_REMOVABLE_MEDIA | FILE_READ_ONLY_DEVICE
138       };
139     NTSTATUS status;
140     PDEVICE_OBJECT pdo;
141
142     /* Validate parameters. */
143     status = STATUS_INVALID_PARAMETER;
144     if (!DriverObj) {
145         DBG("No driver passed!\n");
146         goto err_driver_obj;
147       }
148     if (ExtensionSize < sizeof (WV_S_DEV_EXT)) {
149         DBG("Extension size too small!\n");
150         goto err_ext_size;
151       }
152     if (MediaType >= WvlDiskMediaTypes) {
153         DBG("Unknown media type!\n");
154         goto err_media_type;
155       }
156     if (!Pdo) {
157         DBG("No PDO to fill!\n");
158         goto err_pdo_fill;
159       }
160
161     /* Create the PDO. */
162     status = IoCreateDevice(
163         DriverObj,
164         ExtensionSize,
165         NULL,
166         disk_types[MediaType],
167         FILE_AUTOGENERATED_DEVICE_NAME |
168           FILE_DEVICE_SECURE_OPEN |
169           characteristics[MediaType],
170         FALSE,
171         &pdo
172       );
173     if (!NT_SUCCESS(status)) {
174         DBG("Could not create disk PDO.\n");
175         goto err_pdo;
176       }
177
178     *Pdo = pdo;
179     return STATUS_SUCCESS;
180
181     IoDeleteDevice(pdo);
182     err_pdo:
183
184     err_pdo_fill:
185
186     err_media_type:
187
188     err_ext_size:
189
190     err_driver_obj:
191
192     return status;
193   }
194
195 /**
196  * Create a disk PDO filled with the given disk parameters.
197  *
198  * @v dev_ptr           Populate PDO dev. ext. space from these details.
199  * @ret dev_obj_ptr     Points to the new PDO, or is NULL upon failure.
200  *
201  * Returns a Physical Device Object pointer on success, NULL for failure.
202  */
203 static PDEVICE_OBJECT STDCALL WvDiskCreatePdo_(IN WV_SP_DEV_T dev_ptr) {
204     /* Used for pointing to disk details. */
205     WV_SP_DISK_T disk_ptr;
206     /* Status of the last operation. */
207     NTSTATUS status;
208     /* The new node's physical device object (PDO). */
209     PDEVICE_OBJECT dev_obj_ptr;
210   
211     DBG("Creating PDO for dev %p...\n", dev_ptr);
212
213     /* Point to the disk details provided. */
214     disk_ptr = disk__get_ptr(dev_ptr);
215
216     /* Create the disk PDO. */
217     status = WvlDiskCreatePdo(
218         disk_ptr->DriverObj,
219         sizeof (WV_S_DEV_EXT),
220         disk_ptr->Media,
221         &dev_obj_ptr
222       );
223     if (!NT_SUCCESS(status)) {
224         WvlError("WvlDiskCreatePdo", status);
225         return NULL;
226       }
227
228     /* Set associations for the PDO, device, disk. */
229     WvDevForDevObj(dev_obj_ptr, dev_ptr);
230     dev_ptr->Self = dev_obj_ptr;
231     KeInitializeEvent(&disk_ptr->SearchEvent, SynchronizationEvent, FALSE);
232     KeInitializeSpinLock(&disk_ptr->SpinLock);
233
234     /* Some device parameters. */
235     dev_obj_ptr->Flags |= DO_DIRECT_IO;         /* FIXME? */
236     dev_obj_ptr->Flags |= DO_POWER_INRUSH;      /* FIXME? */
237
238     DBG("Done.\n");
239     return dev_obj_ptr;
240   }
241
242 /*
243  * WV_S_DISK_FAT_EXTRA and WV_S_DISK_FAT_SUPER taken from
244  * syslinux/memdisk/setup.c by H. Peter Anvin.  Licensed under the terms
245  * of the GNU General Public License version 2 or later.
246  */
247 #ifdef _MSC_VER
248 #  pragma pack(1)
249 #endif
250 struct WV_DISK_FAT_EXTRA {
251     UCHAR bs_drvnum;
252     UCHAR bs_resv1;
253     UCHAR bs_bootsig;
254     UINT32 bs_volid;
255     char bs_vollab[11];
256     char bs_filsystype[8];
257   } __attribute__((packed));
258 typedef struct WV_DISK_FAT_EXTRA WV_S_DISK_FAT_EXTRA, * WV_SP_DISK_FAT_EXTRA;
259
260 struct WV_DISK_FAT_SUPER {
261     UCHAR bs_jmpboot[3];
262     char bs_oemname[8];
263     UINT16 bpb_bytspersec;
264     UCHAR bpb_secperclus;
265     UINT16 bpb_rsvdseccnt;
266     UCHAR bpb_numfats;
267     UINT16 bpb_rootentcnt;
268     UINT16 bpb_totsec16;
269     UCHAR bpb_media;
270     UINT16 bpb_fatsz16;
271     UINT16 bpb_secpertrk;
272     UINT16 bpb_numheads;
273     UINT32 bpb_hiddsec;
274     UINT32 bpb_totsec32;
275     union {
276         struct { WV_S_DISK_FAT_EXTRA extra; } fat16;
277         struct {
278             UINT32 bpb_fatsz32;
279             UINT16 bpb_extflags;
280             UINT16 bpb_fsver;
281             UINT32 bpb_rootclus;
282             UINT16 bpb_fsinfo;
283             UINT16 bpb_bkbootsec;
284             char bpb_reserved[12];
285             /* Clever, eh?  Same fields, different offset... */
286             WV_S_DISK_FAT_EXTRA extra;
287           } fat32 __attribute__((packed));
288       } x;
289   } __attribute__((__packed__));
290 typedef struct WV_DISK_FAT_SUPER WV_S_DISK_FAT_SUPER, * WV_SP_DISK_FAT_SUPER;
291 #ifdef _MSC_VER
292 #  pragma pack()
293 #endif
294
295 /**
296  * Attempt to guess a disk's geometry.
297  *
298  * @v boot_sect_ptr     The MBR or VBR with possible geometry clues.
299  * @v disk_ptr          The disk to set the geometry for.
300  */
301 WVL_M_LIB VOID disk__guess_geometry(
302     IN WVL_AP_DISK_BOOT_SECT boot_sect_ptr,
303     IN OUT WV_SP_DISK_T disk_ptr
304   ) {
305     UINT16 heads = 0, sects_per_track = 0, cylinders;
306     WVL_SP_DISK_MBR as_mbr;
307
308     if ((boot_sect_ptr == NULL) || (disk_ptr == NULL))
309       return;
310
311     /*
312      * FAT superblock geometry checking taken from syslinux/memdisk/setup.c by
313      * H. Peter Anvin.  Licensed under the terms of the GNU General Public
314      * License version 2 or later.
315      */
316     {
317         /*
318          * Look for a FAT superblock and if we find something that looks
319          * enough like one, use geometry from that.  This takes care of
320          * megafloppy images and unpartitioned hard disks. 
321          */
322         WV_SP_DISK_FAT_EXTRA extra = NULL;
323         WV_SP_DISK_FAT_SUPER fs = (WV_SP_DISK_FAT_SUPER) boot_sect_ptr;
324   
325         if (
326             (fs->bpb_media == 0xf0 || fs->bpb_media >= 0xf8) &&
327             (fs->bs_jmpboot[0] == 0xe9 || fs->bs_jmpboot[0] == 0xeb) &&
328             fs->bpb_bytspersec == 512 &&
329             fs->bpb_numheads >= 1 &&
330             fs->bpb_numheads <= 256 &&
331             fs->bpb_secpertrk >= 1 &&
332             fs->bpb_secpertrk <= 63
333           ) {
334             extra = fs->bpb_fatsz16 ? &fs->x.fat16.extra : &fs->x.fat32.extra;
335             if (!(
336                 extra->bs_bootsig == 0x29 &&
337                 extra->bs_filsystype[0] == 'F' &&
338                 extra->bs_filsystype[1] == 'A' &&
339                 extra->bs_filsystype[2] == 'T'
340               ))
341               extra = NULL;
342           }
343         if (extra) {
344             heads = fs->bpb_numheads;
345             sects_per_track = fs->bpb_secpertrk;
346           }
347       } /* sub-scope */
348     /*
349      * If we couldn't parse a FAT superblock, try checking MBR params.
350      * Logic derived from syslinux/memdisk/setup.c by H. Peter Anvin.
351      */
352     as_mbr = (WVL_SP_DISK_MBR) boot_sect_ptr;
353     if (
354         (heads == 0 ) &&
355         (sects_per_track == 0) &&
356         (as_mbr->mbr_sig == 0xAA55)
357       ) {
358         int i;
359
360         for (i = 0; i < 4; i++) {
361             if (
362                 !(as_mbr->partition[i].status & 0x7f) &&
363                 as_mbr->partition[i].type
364               ) {
365                 UCHAR h, s;
366
367                 h = chs_head(as_mbr->partition[i].chs_start) + 1;
368                 s = chs_sector(as_mbr->partition[i].chs_start);
369
370                 if (heads < h)
371                   heads = h;
372                 if (sects_per_track < s)
373                   sects_per_track = s;
374
375                 h = chs_head(as_mbr->partition[i].chs_end) + 1;
376                 s = chs_sector(as_mbr->partition[i].chs_end);
377
378                 if (heads < h)
379                   heads = h;
380                 if (sects_per_track < s)
381                   sects_per_track = s;
382               } /* if */
383           } /* for */
384       } /* if */
385     /* If we were unable to guess, use some hopeful defaults. */
386     if (!heads)
387       heads = 255;
388     if (!sects_per_track)
389       sects_per_track = 63;
390     /* Set params that are not already filled. */
391     if (!disk_ptr->Heads)
392       disk_ptr->Heads = heads;
393     if (!disk_ptr->Sectors)
394       disk_ptr->Sectors = sects_per_track;
395     if (!disk_ptr->Cylinders)
396       disk_ptr->Cylinders = disk_ptr->LBADiskSize / (heads * sects_per_track);
397   }
398
399 /**
400  * Initialize disk defaults.
401  *
402  * @v disk              The disk to initialize.
403  */
404 WVL_M_LIB VOID STDCALL WvDiskInit(IN WV_SP_DISK_T disk) {
405     /* Populate non-zero device defaults. */
406     disk->disk_ops.MaxXferLen = WvDiskDefaultMaxXferLen_;
407     disk->disk_ops.Init = WvDiskDefaultInit_;
408     disk->disk_ops.Close = WvDiskDefaultClose_;
409     KeInitializeSpinLock(&disk->SpinLock);
410
411     return;
412   }
413
414 /**
415  * Create a new disk.
416  *
417  * @ret disk            The address of a new disk, or NULL for failure.
418  *
419  * This function should not be confused with a PDO creation routine, which is
420  * actually implemented for each device type.  This routine will allocate a
421  * WV_S_DISK_T, track it in a global list, as well as populate the disk
422  * with default values.
423  */
424 WVL_M_LIB WV_SP_DISK_T disk__create(void) {
425     WV_SP_DISK_T disk;
426     WV_SP_DEV_T dev;
427
428     /*
429      * Disk devices might be used for booting and should
430      * not be allocated from a paged memory pool.
431      */
432     disk = wv_mallocz(sizeof *disk);
433     if (disk == NULL)
434       goto err_nodisk;
435
436     /* Initialize the device with defaults. */
437     dev = disk->Dev;
438     WvDevInit(dev);
439
440     /* Initialize with defaults. */
441     WvDiskInit(disk);
442     dev->Ops.Close = WvDiskDevClose_;
443     dev->Ops.CreatePdo = WvDiskCreatePdo_;
444     dev->Ops.Free = WvDiskDevFree_;
445     dev->Ops.Init = WvDiskDevInit_;
446     dev->ext = disk;
447     dev->IrpMj = &WvDiskIrpMj_;
448     return disk;
449
450     wv_free(disk);
451     err_nodisk:
452
453     return NULL;
454   }
455
456 /**
457  * Default disk deletion operation.
458  *
459  * @v dev               Points to the disk device to delete.
460  */
461 static VOID STDCALL WvDiskDevFree_(IN WV_SP_DEV_T dev) {
462     WV_SP_DISK_T disk = disk__get_ptr(dev);
463
464     wv_free(disk);
465   }
466
467 /* See header for details. */
468 NTSTATUS STDCALL disk__io(
469     IN WV_SP_DEV_T dev,
470     IN WVL_E_DISK_IO_MODE mode,
471     IN LONGLONG start_sector,
472     IN UINT32 sector_count,
473     IN PUCHAR buffer,
474     IN PIRP irp
475   ) {
476     WV_SP_DISK_T disk = disk__get_ptr(dev);
477
478     if (disk->disk_ops.Io) {
479         return disk->disk_ops.Io(
480             dev,
481             mode,
482             start_sector,
483             sector_count,
484             buffer,
485             irp
486           );
487       }
488     return WvlIrpComplete(irp, 0, STATUS_DRIVER_INTERNAL_ERROR);
489   }
490
491 /* See header for details. */
492 UINT32 disk__max_xfer_len(IN WV_SP_DISK_T disk) {
493     /* Use the disk operation, if there is one. */
494     if (disk->disk_ops.MaxXferLen)
495       return disk->disk_ops.MaxXferLen(disk);
496     return WvDiskDefaultMaxXferLen_(disk);
497   }