[project] Rename winvblock__uint16 back to UINT16
[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 "winvblock.h"
31 #include "wv_stdlib.h"
32 #include "portable.h"
33 #include "driver.h"
34 #include "bus.h"
35 #include "device.h"
36 #include "disk.h"
37 #include "debug.h"
38
39 #ifndef _MSC_VER
40 static long long
41 __divdi3 (
42   long long u,
43   long long v
44  )
45 {
46   return u / v;
47 }
48 #endif
49
50 /* IRP_MJ_DEVICE_CONTROL dispatcher from disk/dev_ctl.c */
51 extern WV_F_DEV_CTL disk_dev_ctl__dispatch;
52 /* IRP_MJ_SCSI dispatcher from disk/scsi.c */
53 extern WV_F_DEV_SCSI disk_scsi__dispatch;
54 /* IRP_MJ_PNP dispatcher from disk/pnp.c */
55 extern WV_F_DEV_PNP disk_pnp__dispatch;
56
57 /* Forward declarations. */
58 static WV_F_DEV_FREE free_disk;
59 static WV_F_DEV_DISPATCH disk__power_;
60 static WV_F_DEV_DISPATCH disk__sys_ctl_;
61 static WV_F_DISK_INIT WvDiskDefaultInit_;
62 static WV_F_DISK_CLOSE WvDiskDefaultClose_;
63
64 /* Globals. */
65 static LIST_ENTRY disk_list;
66 static KSPIN_LOCK disk_list_lock;
67 winvblock__bool WvDiskIsRemovable[WvDiskMediaTypes] = { TRUE, FALSE, TRUE };
68 PWCHAR WvDiskCompatIds[WvDiskMediaTypes] = {
69     L"GenSFloppy",
70     L"GenDisk",
71     L"GenCdRom"
72   };
73 WV_S_DEV_IRP_MJ disk__irp_mj_ = {
74     disk__power_,
75     disk__sys_ctl_,
76     disk_dev_ctl__dispatch,
77     disk_scsi__dispatch,
78     disk_pnp__dispatch,
79   };
80
81 static UINT32 default_max_xfer_len(IN WV_SP_DISK_T disk_ptr) {
82     return 1024 * 1024;
83   }
84
85 /* Initialize a disk. */
86 static winvblock__bool STDCALL disk__init_(IN WV_SP_DEV_T dev) {
87     WV_SP_DISK_T disk_ptr = disk__get_ptr(dev);
88     return disk_ptr->disk_ops.Init(disk_ptr);
89   }
90
91 static winvblock__bool STDCALL WvDiskDefaultInit_(IN WV_SP_DISK_T disk_ptr) {
92     return TRUE;
93   }
94
95 static void STDCALL disk__close_(IN WV_SP_DEV_T dev_ptr) {
96     WV_SP_DISK_T disk_ptr = disk__get_ptr(dev_ptr);
97     disk_ptr->disk_ops.Close(disk_ptr);
98     return;
99   }
100
101 static void STDCALL WvDiskDefaultClose_(IN WV_SP_DISK_T disk_ptr) {
102     return;
103   }
104
105 static NTSTATUS STDCALL disk__power_(IN WV_SP_DEV_T dev, IN PIRP irp) {
106     PoStartNextPowerIrp(irp);
107     return driver__complete_irp(irp, 0, STATUS_NOT_SUPPORTED);
108   }
109
110 static NTSTATUS STDCALL disk__sys_ctl_(IN WV_SP_DEV_T dev, IN PIRP irp) {
111     return driver__complete_irp(irp, 0, irp->IoStatus.Status);
112   }
113
114 /**
115  * Create a disk PDO filled with the given disk parameters.
116  *
117  * @v dev_ptr           Populate PDO dev. ext. space from these details.
118  * @ret dev_obj_ptr     Points to the new PDO, or is NULL upon failure.
119  *
120  * Returns a Physical Device Object pointer on success, NULL for failure.
121  */
122 static PDEVICE_OBJECT STDCALL create_pdo(IN WV_SP_DEV_T dev_ptr) {
123     /**
124      * @v disk_ptr          Used for pointing to disk details
125      * @v status            Status of last operation
126      * @v dev_obj_ptr       The new node's physical device object (PDO)
127      * @v new_ext_size      The extension space size
128      * @v disk_types[]      Floppy, hard disk, optical disc specifics
129      * @v characteristics[] Floppy, hard disk, optical disc specifics
130      */
131     WV_SP_DISK_T disk_ptr;
132     NTSTATUS status;
133     PDEVICE_OBJECT dev_obj_ptr;
134     static DEVICE_TYPE disk_types[WvDiskMediaTypes] =
135       { FILE_DEVICE_DISK, FILE_DEVICE_DISK, FILE_DEVICE_CD_ROM };
136     static UINT32 characteristics[WvDiskMediaTypes] =
137       { FILE_REMOVABLE_MEDIA | FILE_FLOPPY_DISKETTE, 0,
138       FILE_REMOVABLE_MEDIA | FILE_READ_ONLY_DEVICE
139     };
140   
141     DBG("Entry\n");
142     /* Point to the disk details provided. */
143     disk_ptr = disk__get_ptr(dev_ptr);
144     /* Create the disk PDO. */
145     status = IoCreateDevice(
146         WvDriverObj,
147         sizeof (WV_S_DEV_EXT),
148         NULL,
149         disk_types[disk_ptr->Media],
150         FILE_AUTOGENERATED_DEVICE_NAME |
151           FILE_DEVICE_SECURE_OPEN |
152           characteristics[disk_ptr->Media],
153         FALSE,
154         &dev_obj_ptr
155       );
156     if (!NT_SUCCESS(status)) {
157         Error("IoCreateDevice", status);
158         return NULL;
159       }
160     /* Set associations for the PDO, device, disk. */
161     WvDevForDevObj(dev_obj_ptr, dev_ptr);
162     dev_ptr->Self = dev_obj_ptr;
163     KeInitializeEvent(&disk_ptr->SearchEvent, SynchronizationEvent, FALSE);
164     KeInitializeSpinLock(&disk_ptr->SpinLock);
165     /* Some device parameters. */
166     dev_obj_ptr->Flags |= DO_DIRECT_IO;         /* FIXME? */
167     dev_obj_ptr->Flags |= DO_POWER_INRUSH;      /* FIXME? */
168     DBG("Exit\n");
169     return dev_obj_ptr;
170   }
171
172 /*
173  * WV_S_DISK_FAT_EXTRA and WV_S_DISK_FAT_SUPER taken from
174  * syslinux/memdisk/setup.c by H. Peter Anvin.  Licensed under the terms
175  * of the GNU General Public License version 2 or later.
176  */
177 #ifdef _MSC_VER
178 #  pragma pack(1)
179 #endif
180 struct WV_DISK_FAT_EXTRA {
181     UCHAR bs_drvnum;
182     UCHAR bs_resv1;
183     UCHAR bs_bootsig;
184     UINT32 bs_volid;
185     char bs_vollab[11];
186     char bs_filsystype[8];
187   } __attribute__((packed));
188 typedef struct WV_DISK_FAT_EXTRA WV_S_DISK_FAT_EXTRA, * WV_SP_DISK_FAT_EXTRA;
189
190 struct WV_DISK_FAT_SUPER {
191     UCHAR bs_jmpboot[3];
192     char bs_oemname[8];
193     UINT16 bpb_bytspersec;
194     UCHAR bpb_secperclus;
195     UINT16 bpb_rsvdseccnt;
196     UCHAR bpb_numfats;
197     UINT16 bpb_rootentcnt;
198     UINT16 bpb_totsec16;
199     UCHAR bpb_media;
200     UINT16 bpb_fatsz16;
201     UINT16 bpb_secpertrk;
202     UINT16 bpb_numheads;
203     UINT32 bpb_hiddsec;
204     UINT32 bpb_totsec32;
205     union {
206         struct { WV_S_DISK_FAT_EXTRA extra; } fat16;
207         struct {
208             UINT32 bpb_fatsz32;
209             UINT16 bpb_extflags;
210             UINT16 bpb_fsver;
211             UINT32 bpb_rootclus;
212             UINT16 bpb_fsinfo;
213             UINT16 bpb_bkbootsec;
214             char bpb_reserved[12];
215             /* Clever, eh?  Same fields, different offset... */
216             WV_S_DISK_FAT_EXTRA extra;
217           } fat32 __attribute__((packed));
218       } x;
219   } __attribute__((__packed__));
220 typedef struct WV_DISK_FAT_SUPER WV_S_DISK_FAT_SUPER, * WV_SP_DISK_FAT_SUPER;
221 #ifdef _MSC_VER
222 #  pragma pack()
223 #endif
224
225 /**
226  * Attempt to guess a disk's geometry.
227  *
228  * @v boot_sect_ptr     The MBR or VBR with possible geometry clues.
229  * @v disk_ptr          The disk to set the geometry for.
230  */
231 WVL_M_LIB void disk__guess_geometry(
232     IN WV_AP_DISK_BOOT_SECT boot_sect_ptr,
233     IN OUT WV_SP_DISK_T disk_ptr
234   ) {
235     UINT16 heads = 0, sects_per_track = 0, cylinders;
236     WVL_SP_DISK_MBR as_mbr;
237
238     if ((boot_sect_ptr == NULL) || (disk_ptr == NULL))
239       return;
240
241     /*
242      * FAT superblock geometry checking taken from syslinux/memdisk/setup.c by
243      * H. Peter Anvin.  Licensed under the terms of the GNU General Public
244      * License version 2 or later.
245      */
246     {
247         /*
248          * Look for a FAT superblock and if we find something that looks
249          * enough like one, use geometry from that.  This takes care of
250          * megafloppy images and unpartitioned hard disks. 
251          */
252         WV_SP_DISK_FAT_EXTRA extra = NULL;
253         WV_SP_DISK_FAT_SUPER fs = (WV_SP_DISK_FAT_SUPER) boot_sect_ptr;
254   
255         if (
256             (fs->bpb_media == 0xf0 || fs->bpb_media >= 0xf8) &&
257             (fs->bs_jmpboot[0] == 0xe9 || fs->bs_jmpboot[0] == 0xeb) &&
258             fs->bpb_bytspersec == 512 &&
259             fs->bpb_numheads >= 1 &&
260             fs->bpb_numheads <= 256 &&
261             fs->bpb_secpertrk >= 1 &&
262             fs->bpb_secpertrk <= 63
263           ) {
264             extra = fs->bpb_fatsz16 ? &fs->x.fat16.extra : &fs->x.fat32.extra;
265             if (!(
266                 extra->bs_bootsig == 0x29 &&
267                 extra->bs_filsystype[0] == 'F' &&
268                 extra->bs_filsystype[1] == 'A' &&
269                 extra->bs_filsystype[2] == 'T'
270               ))
271               extra = NULL;
272           }
273         if (extra) {
274             heads = fs->bpb_numheads;
275             sects_per_track = fs->bpb_secpertrk;
276           }
277       } /* sub-scope */
278     /*
279      * If we couldn't parse a FAT superblock, try checking MBR params.
280      * Logic derived from syslinux/memdisk/setup.c by H. Peter Anvin.
281      */
282     as_mbr = (WVL_SP_DISK_MBR) boot_sect_ptr;
283     if (
284         (heads == 0 ) &&
285         (sects_per_track == 0) &&
286         (as_mbr->mbr_sig == 0xAA55)
287       ) {
288         int i;
289
290         for (i = 0; i < 4; i++) {
291             if (
292                 !(as_mbr->partition[i].status & 0x7f) &&
293                 as_mbr->partition[i].type
294               ) {
295                 UCHAR h, s;
296
297                 h = chs_head(as_mbr->partition[i].chs_start) + 1;
298                 s = chs_sector(as_mbr->partition[i].chs_start);
299
300                 if (heads < h)
301                   heads = h;
302                 if (sects_per_track < s)
303                   sects_per_track = s;
304
305                 h = chs_head(as_mbr->partition[i].chs_end) + 1;
306                 s = chs_sector(as_mbr->partition[i].chs_end);
307
308                 if (heads < h)
309                   heads = h;
310                 if (sects_per_track < s)
311                   sects_per_track = s;
312               } /* if */
313           } /* for */
314       } /* if */
315     /* If we were unable to guess, use some hopeful defaults. */
316     if (!heads)
317       heads = 255;
318     if (!sects_per_track)
319       sects_per_track = 63;
320     /* Set params that are not already filled. */
321     if (!disk_ptr->Heads)
322       disk_ptr->Heads = heads;
323     if (!disk_ptr->Sectors)
324       disk_ptr->Sectors = sects_per_track;
325     if (!disk_ptr->Cylinders)
326       disk_ptr->Cylinders = disk_ptr->LBADiskSize / (heads * sects_per_track);
327   }
328
329 /**
330  * Create a new disk.
331  *
332  * @ret disk_ptr        The address of a new disk, or NULL for failure.
333  *
334  * See the header file for additional details.
335  */
336 WVL_M_LIB WV_SP_DISK_T disk__create(void) {
337     WV_SP_DEV_T dev_ptr;
338     WV_SP_DISK_T disk_ptr;
339
340     /*
341      * Disk devices might be used for booting and should
342      * not be allocated from a paged memory pool.
343      */
344     disk_ptr = wv_mallocz(sizeof *disk_ptr);
345     if (disk_ptr == NULL)
346       goto err_nodisk;
347
348     dev_ptr = disk_ptr->Dev;
349     WvDevInit(dev_ptr);
350
351     /* Track the new disk in our global list. */
352     ExInterlockedInsertTailList(
353         &disk_list,
354         &disk_ptr->tracking,
355         &disk_list_lock
356       );
357     /* Populate non-zero device defaults. */
358     disk_ptr->disk_ops.MaxXferLen = default_max_xfer_len;
359     disk_ptr->disk_ops.Init = WvDiskDefaultInit_;
360     disk_ptr->disk_ops.Close = WvDiskDefaultClose_;
361     dev_ptr->Ops.Close = disk__close_;
362     dev_ptr->Ops.CreatePdo = create_pdo;
363     dev_ptr->Ops.Free = free_disk;
364     dev_ptr->Ops.Init = disk__init_;
365     dev_ptr->ext = disk_ptr;
366     dev_ptr->IrpMj = &disk__irp_mj_;
367     KeInitializeSpinLock(&disk_ptr->SpinLock);
368
369     return disk_ptr;
370
371     err_nodisk:
372
373     return NULL;
374   }
375
376 /**
377  * Initialize the global, disk-common environment.
378  *
379  * @ret ntstatus        STATUS_SUCCESS or the NTSTATUS for a failure.
380  */
381 NTSTATUS disk__module_init(void) {
382     /* Initialize the global list of disks. */
383     InitializeListHead(&disk_list);
384     KeInitializeSpinLock(&disk_list_lock);
385
386     return STATUS_SUCCESS;
387   }
388
389 /**
390  * Default disk deletion operation.
391  *
392  * @v dev_ptr           Points to the disk device to delete.
393  */
394 static void STDCALL free_disk(IN WV_SP_DEV_T dev_ptr) {
395     WV_SP_DISK_T disk_ptr = disk__get_ptr(dev_ptr);
396
397     /*
398      * Track the disk deletion in our global list.  Unfortunately,
399      * for now we have faith that a disk won't be deleted twice and
400      * result in a race condition.  Something to keep in mind...
401      */
402     ExInterlockedRemoveHeadList(disk_ptr->tracking.Blink, &disk_list_lock);
403
404     wv_free(disk_ptr);
405   }
406
407 /* See header for details. */
408 NTSTATUS STDCALL disk__io(
409     IN WV_SP_DEV_T dev_ptr,
410     IN WV_E_DISK_IO_MODE mode,
411     IN LONGLONG start_sector,
412     IN UINT32 sector_count,
413     IN PUCHAR buffer,
414     IN PIRP irp
415   ) {
416     WV_SP_DISK_T disk_ptr;
417
418     /* Establish a pointer to the disk. */
419     disk_ptr = disk__get_ptr(dev_ptr);
420
421     return disk_ptr->disk_ops.Io(
422         dev_ptr,
423         mode,
424         start_sector,
425         sector_count,
426         buffer,
427         irp
428       );
429   }
430
431 /* See header for details. */
432 UINT32 disk__max_xfer_len(IN WV_SP_DISK_T disk_ptr) {
433     return disk_ptr->disk_ops.MaxXferLen(disk_ptr);
434   }