[driver] Rename driver__dev_ext to WV_S_DEV_EXT
[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 winvblock__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 winvblock__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  * fat_extra and fat_super taken from syslinux/memdisk/setup.c by
174  * H. Peter Anvin.  Licensed under the terms of the GNU General Public
175  * License version 2 or later.
176  */
177 #ifdef _MSC_VER
178 #  pragma pack(1)
179 #endif
180 winvblock__def_struct(fat_extra) {
181     winvblock__uint8 bs_drvnum;
182     winvblock__uint8 bs_resv1;
183     winvblock__uint8 bs_bootsig;
184     winvblock__uint32 bs_volid;
185     char bs_vollab[11];
186     char bs_filsystype[8];
187   } __attribute__((packed));
188
189 winvblock__def_struct(fat_super) {
190     winvblock__uint8 bs_jmpboot[3];
191     char bs_oemname[8];
192     winvblock__uint16 bpb_bytspersec;
193     winvblock__uint8 bpb_secperclus;
194     winvblock__uint16 bpb_rsvdseccnt;
195     winvblock__uint8 bpb_numfats;
196     winvblock__uint16 bpb_rootentcnt;
197     winvblock__uint16 bpb_totsec16;
198     winvblock__uint8 bpb_media;
199     winvblock__uint16 bpb_fatsz16;
200     winvblock__uint16 bpb_secpertrk;
201     winvblock__uint16 bpb_numheads;
202     winvblock__uint32 bpb_hiddsec;
203     winvblock__uint32 bpb_totsec32;
204     union {
205         struct { fat_extra extra; } fat16;
206         struct {
207             winvblock__uint32 bpb_fatsz32;
208             winvblock__uint16 bpb_extflags;
209             winvblock__uint16 bpb_fsver;
210             winvblock__uint32 bpb_rootclus;
211             winvblock__uint16 bpb_fsinfo;
212             winvblock__uint16 bpb_bkbootsec;
213             char bpb_reserved[12];
214             /* Clever, eh?  Same fields, different offset... */
215             fat_extra extra;
216           } fat32 __attribute__((packed));
217       } x;
218   } __attribute__((__packed__));
219 #ifdef _MSC_VER
220 #  pragma pack()
221 #endif
222
223 /**
224  * Attempt to guess a disk's geometry.
225  *
226  * @v boot_sect_ptr     The MBR or VBR with possible geometry clues.
227  * @v disk_ptr          The disk to set the geometry for.
228  */
229 WVL_M_LIB void disk__guess_geometry(
230     IN WV_AP_DISK_BOOT_SECT boot_sect_ptr,
231     IN OUT WV_SP_DISK_T disk_ptr
232   ) {
233     winvblock__uint16 heads = 0, sects_per_track = 0, cylinders;
234     WVL_SP_DISK_MBR as_mbr;
235
236     if ((boot_sect_ptr == NULL) || (disk_ptr == NULL))
237       return;
238
239     /*
240      * FAT superblock geometry checking taken from syslinux/memdisk/setup.c by
241      * H. Peter Anvin.  Licensed under the terms of the GNU General Public
242      * License version 2 or later.
243      */
244     {
245         /*
246          * Look for a FAT superblock and if we find something that looks
247          * enough like one, use geometry from that.  This takes care of
248          * megafloppy images and unpartitioned hard disks. 
249          */
250         fat_extra_ptr extra = NULL;
251         fat_super_ptr fs = (fat_super_ptr) boot_sect_ptr;
252   
253         if (
254             (fs->bpb_media == 0xf0 || fs->bpb_media >= 0xf8) &&
255             (fs->bs_jmpboot[0] == 0xe9 || fs->bs_jmpboot[0] == 0xeb) &&
256             fs->bpb_bytspersec == 512 &&
257             fs->bpb_numheads >= 1 &&
258             fs->bpb_numheads <= 256 &&
259             fs->bpb_secpertrk >= 1 &&
260             fs->bpb_secpertrk <= 63
261           ) {
262             extra = fs->bpb_fatsz16 ? &fs->x.fat16.extra : &fs->x.fat32.extra;
263             if (!(
264                 extra->bs_bootsig == 0x29 &&
265                 extra->bs_filsystype[0] == 'F' &&
266                 extra->bs_filsystype[1] == 'A' &&
267                 extra->bs_filsystype[2] == 'T'
268               ))
269               extra = NULL;
270           }
271         if (extra) {
272             heads = fs->bpb_numheads;
273             sects_per_track = fs->bpb_secpertrk;
274           }
275       } /* sub-scope */
276     /*
277      * If we couldn't parse a FAT superblock, try checking MBR params.
278      * Logic derived from syslinux/memdisk/setup.c by H. Peter Anvin.
279      */
280     as_mbr = (WVL_SP_DISK_MBR) boot_sect_ptr;
281     if (
282         (heads == 0 ) &&
283         (sects_per_track == 0) &&
284         (as_mbr->mbr_sig == 0xAA55)
285       ) {
286         int i;
287
288         for (i = 0; i < 4; i++) {
289             if (
290                 !(as_mbr->partition[i].status & 0x7f) &&
291                 as_mbr->partition[i].type
292               ) {
293                 winvblock__uint8 h, s;
294
295                 h = chs_head(as_mbr->partition[i].chs_start) + 1;
296                 s = chs_sector(as_mbr->partition[i].chs_start);
297
298                 if (heads < h)
299                   heads = h;
300                 if (sects_per_track < s)
301                   sects_per_track = s;
302
303                 h = chs_head(as_mbr->partition[i].chs_end) + 1;
304                 s = chs_sector(as_mbr->partition[i].chs_end);
305
306                 if (heads < h)
307                   heads = h;
308                 if (sects_per_track < s)
309                   sects_per_track = s;
310               } /* if */
311           } /* for */
312       } /* if */
313     /* If we were unable to guess, use some hopeful defaults. */
314     if (!heads)
315       heads = 255;
316     if (!sects_per_track)
317       sects_per_track = 63;
318     /* Set params that are not already filled. */
319     if (!disk_ptr->Heads)
320       disk_ptr->Heads = heads;
321     if (!disk_ptr->Sectors)
322       disk_ptr->Sectors = sects_per_track;
323     if (!disk_ptr->Cylinders)
324       disk_ptr->Cylinders = disk_ptr->LBADiskSize / (heads * sects_per_track);
325   }
326
327 /**
328  * Create a new disk.
329  *
330  * @ret disk_ptr        The address of a new disk, or NULL for failure.
331  *
332  * See the header file for additional details.
333  */
334 WVL_M_LIB WV_SP_DISK_T disk__create(void) {
335     WV_SP_DEV_T dev_ptr;
336     WV_SP_DISK_T disk_ptr;
337
338     /*
339      * Disk devices might be used for booting and should
340      * not be allocated from a paged memory pool.
341      */
342     disk_ptr = wv_mallocz(sizeof *disk_ptr);
343     if (disk_ptr == NULL)
344       goto err_nodisk;
345
346     dev_ptr = disk_ptr->Dev;
347     WvDevInit(dev_ptr);
348
349     /* Track the new disk in our global list. */
350     ExInterlockedInsertTailList(
351         &disk_list,
352         &disk_ptr->tracking,
353         &disk_list_lock
354       );
355     /* Populate non-zero device defaults. */
356     disk_ptr->disk_ops.MaxXferLen = default_max_xfer_len;
357     disk_ptr->disk_ops.Init = WvDiskDefaultInit_;
358     disk_ptr->disk_ops.Close = WvDiskDefaultClose_;
359     dev_ptr->Ops.Close = disk__close_;
360     dev_ptr->Ops.CreatePdo = create_pdo;
361     dev_ptr->Ops.Free = free_disk;
362     dev_ptr->Ops.Init = disk__init_;
363     dev_ptr->ext = disk_ptr;
364     dev_ptr->IrpMj = &disk__irp_mj_;
365     KeInitializeSpinLock(&disk_ptr->SpinLock);
366
367     return disk_ptr;
368
369     err_nodisk:
370
371     return NULL;
372   }
373
374 /**
375  * Initialize the global, disk-common environment.
376  *
377  * @ret ntstatus        STATUS_SUCCESS or the NTSTATUS for a failure.
378  */
379 NTSTATUS disk__module_init(void) {
380     /* Initialize the global list of disks. */
381     InitializeListHead(&disk_list);
382     KeInitializeSpinLock(&disk_list_lock);
383
384     return STATUS_SUCCESS;
385   }
386
387 /**
388  * Default disk deletion operation.
389  *
390  * @v dev_ptr           Points to the disk device to delete.
391  */
392 static void STDCALL free_disk(IN WV_SP_DEV_T dev_ptr) {
393     WV_SP_DISK_T disk_ptr = disk__get_ptr(dev_ptr);
394
395     /*
396      * Track the disk deletion in our global list.  Unfortunately,
397      * for now we have faith that a disk won't be deleted twice and
398      * result in a race condition.  Something to keep in mind...
399      */
400     ExInterlockedRemoveHeadList(disk_ptr->tracking.Blink, &disk_list_lock);
401
402     wv_free(disk_ptr);
403   }
404
405 /* See header for details. */
406 NTSTATUS STDCALL disk__io(
407     IN WV_SP_DEV_T dev_ptr,
408     IN WV_E_DISK_IO_MODE mode,
409     IN LONGLONG start_sector,
410     IN winvblock__uint32 sector_count,
411     IN winvblock__uint8_ptr buffer,
412     IN PIRP irp
413   ) {
414     WV_SP_DISK_T disk_ptr;
415
416     /* Establish a pointer to the disk. */
417     disk_ptr = disk__get_ptr(dev_ptr);
418
419     return disk_ptr->disk_ops.Io(
420         dev_ptr,
421         mode,
422         start_sector,
423         sector_count,
424         buffer,
425         irp
426       );
427   }
428
429 /* See header for details. */
430 winvblock__uint32 disk__max_xfer_len(IN WV_SP_DISK_T disk_ptr) {
431     return disk_ptr->disk_ops.MaxXferLen(disk_ptr);
432   }