DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/ints/bios_memdisk.cpp
00001 /*
00002 *
00003 *  Copyright (c) 2018 Shane Krueger
00004 *
00005 *  This program is free software; you can redistribute it and/or modify
00006 *  it under the terms of the GNU General Public License as published by
00007 *  the Free Software Foundation; either version 2 of the License, or
00008 *  (at your option) any later version.
00009 *
00010 *  This program is distributed in the hope that it will be useful,
00011 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 *  GNU General Public License for more details.
00014 *
00015 *  You should have received a copy of the GNU General Public License
00016 *  along with this program; if not, write to the Free Software
00017 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00018 */
00019 
00020 #include "dosbox.h"
00021 #include "callback.h"
00022 #include "bios.h"
00023 #include "bios_disk.h"
00024 #include "regs.h"
00025 #include "mem.h"
00026 #include "dos_inc.h" /* for Drives[] */
00027 #include "../dos/drives.h"
00028 #include "mapper.h"
00029 
00030 /* imageDiskMemory simulates a hard drive image or floppy drive image in RAM
00031 *
00032 * It can be initialized as a floppy, using one of the predefined floppy
00033 *   geometries, or can be initialized as a hard drive, accepting either
00034 *   a size in kilobytes or a specific set of chs values to emulate
00035 * It will then split the image into 64k chunks that are allocated on-demand.
00036 * Initially the only RAM required is for the chunk map
00037 * The image is effectively intialized to all zeros, as each chunk is zeroed
00038 *   upon allocation
00039 * Writes of all zeros do not allocate memory if none has yet been assigned
00040 *
00041 */
00042 
00043 // Create a hard drive image of a specified size; automatically select c/h/s
00044 imageDiskMemory::imageDiskMemory(Bit32u imgSizeK) : imageDisk(ID_MEMORY), total_sectors(0), underlyingImage(NULL) {
00045         //notes:
00046         //  this code always returns HARD DRIVES with 512 byte sectors
00047         //  the code will round up in case it cannot make an exact match
00048         //  it enforces a minimum drive size of 32kb, since a hard drive cannot be formatted as FAT12 with a smaller parition
00049         //  the code works properly throughout the range of a 32-bit unsigned integer, however:
00050         //    a) for drives requesting more than 8,225,280kb, the number of cylinders will exceed 1024
00051         //    b) for drives requesting ULONG_MAX kb, the drive it creates will be slightly larger than ULONG_MAX kb, due to rounding
00052     
00053         //BIOS and MBR c/h/s limits:      1024 / 255 /  63     8,225,280kb
00054         //ATA c/h/s limits:              65536 /  16 / 255   267,386,880kb
00055         //combined limits:                1024 /  16 /  63       516,096kb
00056 
00057         //since this is a virtual drive, we are not restricted to ATA limits, so go with BIOS/MBR limits
00058 
00059         //it targets c/h/s values as follows:
00060         //  minimum:                         4 /   1 /  16            32kb
00061         //  through:                      1024 /   1 /  16         8,192kb
00062         //  through:                      1024 /  16 /  16       131,072kb
00063         //  through:                      1024 /  16 /  63       516,096kb
00064         //  max FAT16 drive size:         1024 / 130 /  63     4,193,280kb
00065         //  through:                      1024 / 255 /  63     8,225,280kb
00066         //  maximum:                    534699 / 255 /  63             4tb
00067         
00068         //use 32kb as minimum drive size, since it is not possible to format a smaller drive that has 16 sectors
00069         if (imgSizeK < 32) imgSizeK = 32;
00070         //set the sector size to 512 bytes
00071         Bit32u sector_size = 512;
00072         //calculate the total number of sectors on the drive (imgSizeK * 2)
00073         Bit64u total_sectors = ((Bit64u)imgSizeK * 1024 + sector_size - 1) / sector_size;
00074 
00075         //calculate cylinders, sectors, and heads according to the targets above
00076         Bit32u sectors, heads, cylinders;
00077         if (total_sectors <= 1024 * 16 * 16) {
00078                 sectors = 16;
00079                 heads = (Bit32u)((total_sectors + (1024 * sectors - 1)) / (1024 * sectors));
00080                 cylinders = (Bit32u)((total_sectors + (sectors * heads - 1)) / (sectors * heads));
00081         }
00082         else if (total_sectors <= 1024 * 16 * 63) {
00083                 heads = 16;
00084                 sectors = (Bit32u)((total_sectors + (1024 * heads - 1)) / (1024 * heads));
00085                 cylinders = (Bit32u)((total_sectors + (sectors * heads - 1)) / (sectors * heads));
00086         }
00087         else if (total_sectors <= 1024 * 255 * 63) {
00088                 sectors = 63;
00089                 heads = (Bit32u)((total_sectors + (1024 * sectors - 1)) / (1024 * sectors));
00090                 cylinders = (Bit32u)((total_sectors + (sectors * heads - 1)) / (sectors * heads));
00091         }
00092         else {
00093                 sectors = 63;
00094                 heads = 255;
00095                 cylinders = (Bit32u)((total_sectors + (sectors * heads - 1)) / (sectors * heads));
00096         }
00097 
00098         LOG_MSG("Creating ramdrive as C/H/S %u/%u/%u with %u bytes/sector\n",
00099                 (unsigned int)cylinders, (unsigned int)heads, (unsigned int)sectors, (unsigned int)sector_size);
00100 
00101         diskGeo diskParams;
00102         diskParams.secttrack = sectors;
00103         diskParams.cylcount = cylinders;
00104         diskParams.headscyl = heads;
00105         diskParams.bytespersect = sector_size;
00106         diskParams.biosval = 0;
00107         diskParams.ksize = imgSizeK;
00108         diskParams.mediaid = 0xF0;
00109         diskParams.rootentries = 512;
00110         diskParams.biosval = 0;
00111         diskParams.sectcluster = 1;
00112         init(diskParams, true, 0);
00113 }
00114 
00115 // Create a floppy image of a specified geometry
00116 imageDiskMemory::imageDiskMemory(diskGeo floppyGeometry) : imageDisk(ID_MEMORY), total_sectors(0), underlyingImage(NULL) {
00117         init(floppyGeometry, false, 0);
00118 }
00119 
00120 // Create a hard drive image of a specified geometry
00121 imageDiskMemory::imageDiskMemory(Bit16u cylinders, Bit16u heads, Bit16u sectors, Bit16u sector_size) : imageDisk(ID_MEMORY), total_sectors(0), underlyingImage(NULL) {
00122         diskGeo diskParams;
00123         diskParams.secttrack = sectors;
00124         diskParams.cylcount = cylinders;
00125         diskParams.headscyl = heads;
00126         diskParams.bytespersect = sector_size;
00127         diskParams.biosval = 0;
00128         diskParams.ksize = 0;
00129         diskParams.mediaid = 0xF0;
00130         diskParams.rootentries = 512;
00131         diskParams.biosval = 0;
00132         diskParams.sectcluster = 1;
00133         init(diskParams, true, 0);
00134 }
00135 
00136 // Create a copy-on-write memory image of an existing image
00137 imageDiskMemory::imageDiskMemory(imageDisk* underlyingImage) : imageDisk(ID_MEMORY), total_sectors(0), underlyingImage(NULL) {
00138         diskGeo diskParams;
00139         Bit32u heads, cylinders, sectors, bytesPerSector;
00140         underlyingImage->Get_Geometry(&heads, &cylinders, &sectors, &bytesPerSector);
00141         diskParams.headscyl = (Bit16u)heads;
00142         diskParams.cylcount = (Bit16u)cylinders;
00143         diskParams.secttrack = (Bit16u)sectors;
00144         diskParams.bytespersect = (Bit16u)bytesPerSector;
00145         diskParams.biosval = 0;
00146         diskParams.ksize = 0;
00147         diskParams.mediaid = 0xF0;
00148         diskParams.rootentries = 0;
00149         diskParams.biosval = 0;
00150         diskParams.sectcluster = 0;
00151         init(diskParams, true, underlyingImage);
00152 }
00153 
00154 // Internal initialization code to create a image of a specified geometry
00155 void imageDiskMemory::init(diskGeo diskParams, bool isHardDrive, imageDisk* underlyingImage) {
00156         //initialize internal variables in case we fail out
00157         this->total_sectors = 0;
00158         this->underlyingImage = underlyingImage;
00159         if (underlyingImage) underlyingImage->Addref();
00160 
00161         //calculate the total number of sectors on the drive, and check for overflows
00162         Bit64u absoluteSectors = (Bit64u)diskParams.cylcount * (Bit64u)diskParams.headscyl;
00163         if (absoluteSectors > 0x100000000u) {
00164                 LOG_MSG("Image size too large in imageDiskMemory constructor.\n");
00165                 return;
00166         }
00167         absoluteSectors *= (Bit64u)diskParams.secttrack;
00168         if (absoluteSectors > 0x100000000u) {
00169                 LOG_MSG("Image size too large in imageDiskMemory constructor.\n");
00170                 return;
00171         }
00172         //make sure that the number of total sectors on the drive is not zero
00173         if (absoluteSectors == 0) {
00174                 LOG_MSG("Image size too small in imageDiskMemory constructor.\n");
00175                 return;
00176         }
00177 
00178         //calculate total size of the drive in kilobytes, and check for overflow
00179         Bit64u diskSizeK = ((Bit64u)diskParams.headscyl * (Bit64u)diskParams.cylcount * (Bit64u)diskParams.secttrack * (Bit64u)diskParams.bytespersect + (Bit64u)1023) / (Bit64u)1024;
00180         if (diskSizeK >= 0x100000000u)
00181         {
00182                 LOG_MSG("Image size too large in imageDiskMemory constructor.\n");
00183                 return;
00184         }
00185 
00186         //split the sectors into chunks, which are allocated together on an as-needed basis
00187         //assuming a maximum of 63 heads and 255 sectors, this formula gives a nice chunk size that scales with the drive size
00188         //for any drives under 64mb, the chunks are 8kb each, and the map consumes 1/1000 of the total drive size
00189         //then the chunk size grows up to a 8gb drive, while the map consumes 30-60k of memory (on 64bit PCs)
00190         //with a 8gb drive, the chunks are about 1mb each, and the map consumes about 60k of memory 
00191         //and for larger drives (with over 1023 cylinders), the chunks remain at 1mb each while the map grows
00192         this->sectors_per_chunk = (diskParams.headscyl + 7u) / 8u * diskParams.secttrack;
00193         this->total_chunks = (Bit32u)((absoluteSectors + sectors_per_chunk - 1u) / sectors_per_chunk);
00194         this->chunk_size = sectors_per_chunk * diskParams.bytespersect;
00195         //allocate a map of chunks that have been allocated and their memory addresses
00196         ChunkMap = (Bit8u**)malloc(total_chunks * sizeof(Bit8u*));
00197         if (ChunkMap == NULL) {
00198                 LOG_MSG("Error allocating memory map in imageDiskMemory constructor for %lu clusters.\n", (unsigned long)total_chunks);
00199                 return;
00200         }
00201         //clear memory map
00202         memset((void*)ChunkMap, 0, total_chunks * sizeof(Bit8u*));
00203 
00204         //set internal variables
00205         this->diskname = "ram drive";
00206         this->heads = diskParams.headscyl;
00207         this->cylinders = diskParams.cylcount;
00208         this->sectors = diskParams.secttrack;
00209         this->sector_size = diskParams.bytespersect;
00210         this->diskSizeK = diskSizeK;
00211         this->total_sectors = (Bit32u)absoluteSectors;
00212         this->reserved_cylinders = 0;
00213         this->hardDrive = isHardDrive;
00214         this->floppyInfo = diskParams;
00215         this->active = true;
00216 }
00217 
00218 // imageDiskMemory destructor will release all allocated memory
00219 imageDiskMemory::~imageDiskMemory() {
00220         //quit if the map is already not allocated
00221         if (!active) return;
00222         //release the underlying image
00223         if (this->underlyingImage) this->underlyingImage->Release();
00224         //loop through each chunk and release it if it has been allocated
00225         Bit8u* chunk;
00226         for (Bit32u i = 0; i < total_chunks; i++) {
00227                 chunk = ChunkMap[i];
00228                 if (chunk) free(chunk);
00229         }
00230         //release the memory map
00231         free(ChunkMap);
00232         //reset internal variables
00233         ChunkMap = 0;
00234         total_sectors = 0;
00235         active = false;
00236 }
00237 
00238 // Return the BIOS type of the floppy image, or 0 for hard drives
00239 Bit8u imageDiskMemory::GetBiosType(void) {
00240         return this->hardDrive ? 0 : this->floppyInfo.biosval;
00241 }
00242 
00243 void imageDiskMemory::Set_Geometry(Bit32u setHeads, Bit32u setCyl, Bit32u setSect, Bit32u setSectSize) {
00244         if (setHeads != this->heads || setCyl != this->cylinders || setSect != this->sectors || setSectSize != this->sector_size) {
00245                 LOG_MSG("imageDiskMemory::Set_Geometry not implemented");
00246                 //validate geometry and resize ramdrive
00247         }
00248 }
00249 
00250 // Read a specific sector from the ramdrive
00251 Bit8u imageDiskMemory::Read_AbsoluteSector(Bit32u sectnum, void * data) {
00252         //sector number is a zero-based offset
00253 
00254         //verify the sector number is valid
00255         if (sectnum >= total_sectors) {
00256                 LOG_MSG("Invalid sector number in Read_AbsoluteSector for sector %lu.\n", (unsigned long)sectnum);
00257                 return 0x05;
00258         }
00259 
00260         //calculate which chunk the sector is located within, and which sector within the chunk
00261         Bit32u chunknum, chunksect;
00262         chunknum = sectnum / sectors_per_chunk;
00263         chunksect = sectnum % sectors_per_chunk;
00264 
00265         //retrieve the memory address of the chunk
00266         Bit8u* datalocation;
00267         datalocation = ChunkMap[chunknum];
00268 
00269         //if the chunk has not yet been allocated, return underlying image if any, or else zeros
00270         if (datalocation == 0) {
00271                 if (this->underlyingImage) {
00272                         return this->underlyingImage->Read_AbsoluteSector(sectnum, data);
00273                 }
00274                 memset(data, 0, sector_size);
00275                 return 0x00;
00276         }
00277 
00278         //update the address to the specific sector within the chunk
00279         datalocation = &datalocation[chunksect * sector_size];
00280 
00281         //copy the data to the output and return success
00282         memcpy(data, datalocation, sector_size);
00283         return 0x00;
00284 }
00285 
00286 // Write a specific sector from the ramdrive
00287 Bit8u imageDiskMemory::Write_AbsoluteSector(Bit32u sectnum, const void * data) {
00288         //sector number is a zero-based offset
00289 
00290         //verify the sector number is valid
00291         if (sectnum >= total_sectors) {
00292                 LOG_MSG("Invalid sector number in Write_AbsoluteSector for sector %lu.\n", (unsigned long)sectnum);
00293                 return 0x05;
00294         }
00295 
00296         //calculate which chunk the sector is located within, and which sector within the chunk
00297         Bit32u chunknum, chunksect;
00298         chunknum = sectnum / sectors_per_chunk;
00299         chunksect = sectnum % sectors_per_chunk;
00300 
00301         //retrieve the memory address of the chunk
00302         Bit8u* datalocation;
00303         datalocation = ChunkMap[chunknum];
00304 
00305         //if the chunk has not yet been allocated, allocate the chunk
00306         if (datalocation == NULL) {
00307                 //if no underlying image, first check if we are actually saving anything
00308                 if (this->underlyingImage == NULL) {
00309                         Bit8u anyData = 0;
00310                         for (Bit32u i = 0; i < sector_size; i++) {
00311                                 anyData |= ((Bit8u*)data)[i];
00312                         }
00313                         //if it's all zeros, return success
00314                         if (anyData == 0) return 0x00;
00315                 }
00316 
00317                 //allocate a new memory chunk
00318                 datalocation = (Bit8u*)malloc(chunk_size);
00319                 if (datalocation == NULL) {
00320                         LOG_MSG("Could not allocate memory in Write_AbsoluteSector for sector %lu.\n", (unsigned long)sectnum);
00321                         return 0x05;
00322                 }
00323                 //save the memory chunk address within the memory map
00324                 ChunkMap[chunknum] = datalocation;
00325                 //initialize the memory chunk to all zeros (since we are only writing to a single sector within this chunk)
00326                 memset((void*)datalocation, 0, chunk_size);
00327                 //if there is an underlying image, read from the underlying image to fill the chunk
00328                 if (this->underlyingImage) {
00329                         Bit32u chunkFirstSector = chunknum * this->sectors_per_chunk;
00330                         Bit32u sectorsToCopy = this->sectors_per_chunk;
00331                         //if this is the last chunk, don't read past the end of the original image
00332                         if ((chunknum + 1) == this->total_chunks) sectorsToCopy = this->total_sectors - chunkFirstSector;
00333                         //copy the sectors
00334                         Bit8u* target = datalocation;
00335                         for (Bit32u i = 0; i < sectorsToCopy; i++) {
00336                                 this->underlyingImage->Read_AbsoluteSector(i + chunkFirstSector, target);
00337                                 datalocation += this->sector_size;
00338                         }
00339                 }
00340         }
00341 
00342         //update the address to the specific sector within the chunk
00343         datalocation = &datalocation[chunksect * sector_size];
00344 
00345         //write the sector to the chunk and return success
00346         memcpy(datalocation, data, sector_size);
00347         return 0x00;
00348 }
00349 
00350 // Parition and format the ramdrive
00351 Bit8u imageDiskMemory::Format() {
00352         //verify that the geometry of the drive is valid
00353         if (this->sector_size != 512) {
00354                 LOG_MSG("imageDiskMemory::Format only designed for disks with 512-byte sectors.\n");
00355                 return 0x01;
00356         }
00357         if (this->sectors > 63) {
00358                 LOG_MSG("imageDiskMemory::Format only designed for disks with <= 63 sectors.\n");
00359                 return 0x02;
00360         }
00361         if (this->heads > 255) {
00362                 LOG_MSG("imageDiskMemory::Format only designed for disks with <= 255 heads.\n");
00363                 return 0x03;
00364         }
00365         if (this->cylinders <= this->Get_Reserved_Cylinders()) {
00366                 LOG_MSG("Invalid number of reserved cylinders in imageDiskMemory::Format\n");
00367                 return 0x06;
00368         }
00369         Bit32u reported_cylinders = this->cylinders - this->Get_Reserved_Cylinders();
00370         if (reported_cylinders > 1024) {
00371                 LOG_MSG("imageDiskMemory::Format only designed for disks with <= 1024 cylinders.\n");
00372                 return 0x04;
00373         }
00374         Bit32u reported_total_sectors = reported_cylinders * this->heads * this->sectors;
00375 
00376         //plan the drive
00377         //write a MBR?
00378         bool writeMBR = this->hardDrive;
00379         Bit32u partitionStart = writeMBR ? this->sectors : 0; //must be aligned with the start of a head (multiple of this->sectors)
00380         Bit32u partitionLength = reported_total_sectors - partitionStart; //must be aligned with the start of a head (multiple of this->sectors)
00381         //figure out the media id
00382         Bit8u mediaID = this->hardDrive ? 0xF8 : this->floppyInfo.mediaid;
00383         //figure out the number of root entries and minimum number of sectors per cluster
00384         Bit32u root_ent = this->hardDrive ? 512 : this->floppyInfo.rootentries;
00385         Bit32u sectors_per_cluster = this->hardDrive ? 4 : this->floppyInfo.sectcluster; //fat requires 2k clusters minimum on hard drives
00386 
00387         //calculate the number of:
00388         //  root sectors
00389         //  FAT sectors
00390         //  reserved sectors
00391         //  sectors per cluster
00392         //  if this is a FAT16 or FAT12 drive
00393         Bit32u root_sectors;
00394         bool isFat16;
00395         Bit32u fatSectors;
00396         Bit32u reservedSectors;
00397         if (!this->CalculateFAT(partitionStart, partitionLength, this->hardDrive, root_ent, &root_sectors, &sectors_per_cluster, &isFat16, &fatSectors, &reservedSectors)) {
00398                 LOG_MSG("imageDiskMemory::Format could not calculate FAT sectors.\n");
00399                 return 0x05;
00400         }
00401 
00402         LOG_MSG("Formatting FAT%u %s drive C/H/S %u/%u/%u with %u bytes/sector, %u root entries, %u-byte clusters, media id 0x%X\n",
00403                 (unsigned int)(isFat16 ? 16 : 12), this->hardDrive ? "hard" : "floppy",
00404                 (unsigned int)reported_cylinders, (unsigned int)this->heads, (unsigned int)this->sectors, (unsigned int)this->sector_size,
00405                 (unsigned int)root_ent, (unsigned int)(sectors_per_cluster * this->sector_size), (unsigned int)mediaID);
00406 
00407         //write MBR if applicable
00408         Bit8u sbuf[512];
00409         if (writeMBR) {
00410                 // initialize sbuf with the freedos MBR
00411                 memcpy(sbuf, freedos_mbr, 512);
00412                 // active partition
00413                 sbuf[0x1be] = 0x80;
00414                 //ASSUMING that partitionStart==this->sectors;
00415                 // start head - head 0 has the partition table, head 1 first partition
00416                 sbuf[0x1bf] = this->heads > 1 ? 1 : 0;
00417                 // start sector with bits 8-9 of start cylinder in bits 6-7
00418                 sbuf[0x1c0] = this->heads > 1 ? 1 : (this->sectors > 1 ? 2 : 1);
00419                 // start cylinder bits 0-7
00420                 sbuf[0x1c1] = this->heads > 1 || this->sectors > 1 ? 0 : 1;
00421                 // OS indicator: DOS what else ;)
00422                 sbuf[0x1c2] = 0x06;
00423                 //ASSUMING that partitionLength==(reported_total_sectors-partitionStart)
00424                 // end head (0-based)
00425                 sbuf[0x1c3] = this->heads - 1;
00426                 // end sector with bits 8-9 of end cylinder (0-based) in bits 6-7
00427                 sbuf[0x1c4] = this->sectors | (((this->cylinders - 1 - this->Get_Reserved_Cylinders()) & 0x300) >> 2);
00428                 // end cylinder (0-based) bits 0-7
00429                 sbuf[0x1c5] = (this->cylinders - 1 - this->Get_Reserved_Cylinders()) & 0xFF;
00430                 // sectors preceding partition1 (one head)
00431                 host_writed(&sbuf[0x1c6], this->sectors);
00432                 // length of partition1, align to chs value
00433                 // ASSUMING that partitionLength aligns to chs value
00434                 host_writed(&sbuf[0x1ca], partitionLength);
00435 
00436                 // write partition table
00437                 this->Write_AbsoluteSector(0, sbuf);
00438         }
00439 
00440         // initialize boot sector values
00441         memset(sbuf, 0, 512);
00442         // TODO boot code jump
00443         sbuf[0] = 0xEB; sbuf[1] = 0x3c; sbuf[2] = 0x90;
00444         // OEM
00445         sprintf((char*)&sbuf[0x03], "MSDOS5.0");
00446         // bytes per sector: always 512
00447         host_writew(&sbuf[0x0b], 512);
00448         // sectors per cluster: 1,2,4,8,16,...
00449         sbuf[0x0d] = sectors_per_cluster;
00450         // reserved sectors: 1 for floppies (the boot sector), or align to 4k boundary for hard drives (not required to align to 4k boundary)
00451         host_writew(&sbuf[0x0e], reservedSectors);
00452         // Number of FATs - always 2
00453         sbuf[0x10] = 2;
00454         // Root directory entries
00455         host_writew(&sbuf[0x11], root_ent);
00456         // total sectors (<= 65535)
00457         if (partitionLength < 0x10000u) host_writew(&sbuf[0x13], (Bit16u)partitionLength);
00458         // media descriptor
00459         sbuf[0x15] = mediaID;
00460         // sectors per FAT
00461         host_writew(&sbuf[0x16], fatSectors);
00462         // sectors per track
00463         host_writew(&sbuf[0x18], this->sectors);
00464         // heads
00465         host_writew(&sbuf[0x1a], this->heads);
00466         // hidden sectors
00467         host_writed(&sbuf[0x1c], partitionStart);
00468         // sectors (>= 65536)
00469         if (partitionLength >= 0x10000u) host_writed(&sbuf[0x20], partitionLength);
00470         // BIOS drive
00471         sbuf[0x24] = this->hardDrive ? 0x80 : 0x00;
00472         // ext. boot signature
00473         sbuf[0x26] = 0x29;
00474         // volume serial number
00475         // let's use the BIOS time (cheap, huh?)
00476         host_writed(&sbuf[0x27], mem_readd(BIOS_TIMER));
00477         // Volume label
00478         sprintf((char*)&sbuf[0x2b], "RAMDISK    ");
00479         // file system type
00480         sprintf((char*)&sbuf[0x36], isFat16 ? "FAT16   " : "FAT12   ");
00481         // boot sector signature (indicates disk is bootable)
00482         host_writew(&sbuf[0x1fe], 0xAA55);
00483 
00484         // write the boot sector
00485         this->Write_AbsoluteSector(partitionStart, sbuf);
00486 
00487         // initialize the FATs and root sectors
00488         memset(sbuf, 0, sector_size);
00489         // erase both FATs and the root directory sectors
00490         for (Bit32u i = partitionStart + 1; i < partitionStart + reservedSectors + fatSectors + fatSectors + root_sectors; i++) {
00491                 this->Write_AbsoluteSector(i, sbuf);
00492         }
00493         // set the special markers for cluster 0 and cluster 1
00494         if (isFat16) {
00495                 host_writed(&sbuf[0], 0xFFFFFF00u | mediaID);
00496         }
00497         else {
00498                 host_writed(&sbuf[0], 0xFFFF00u | mediaID);
00499         }
00500         this->Write_AbsoluteSector(partitionStart + reservedSectors, sbuf);
00501         this->Write_AbsoluteSector(partitionStart + reservedSectors + fatSectors, sbuf);
00502 
00503         //success
00504         return 0x00;
00505 }
00506 
00507 // Calculate the number of sectors per cluster, the number of sectors per fat, and if it is FAT12/FAT16
00508 // Note that sectorsPerCluster is required to be set to the minimum, which will be 2 for certain types of floppies
00509 bool imageDiskMemory::CalculateFAT(Bit32u partitionStartingSector, Bit32u partitionLength, bool isHardDrive, Bit32u rootEntries, Bit32u* rootSectors, Bit32u* sectorsPerCluster, bool* isFat16, Bit32u* fatSectors, Bit32u* reservedSectors) {
00510         //check for null references
00511         if (rootSectors == NULL || sectorsPerCluster == NULL || isFat16 == NULL || fatSectors == NULL || reservedSectors == NULL) return false;
00512         //make sure variables all make sense
00513         switch (*sectorsPerCluster) {
00514                 case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: break;
00515                 default:
00516                         LOG_MSG("Invalid number of sectors per cluster\n");
00517                         return false;
00518         }
00519         if (((Bit64u)partitionStartingSector + partitionLength) > 0xfffffffful) {
00520                 LOG_MSG("Invalid partition size\n");
00521                 return false;
00522         }
00523         if ((rootEntries > (isHardDrive ? 512u : 240u)) ||
00524                 (rootEntries / 16) * 16 != rootEntries ||
00525                 rootEntries == 0) {
00526                 LOG_MSG("Invalid number of root entries\n");
00527                 return false;
00528         }
00529         //set the number of root sectors
00530         *rootSectors = rootEntries * 32 / 512;
00531         //make sure there is a minimum number of sectors available
00532         //  minimum sectors = root sectors + 1 for boot sector + 1 for fat #1 + 1 for fat #2 + 1 cluster for data + add 7 for hard drives due to allow for 4k alignment
00533         if (partitionLength < (*rootSectors + 3 + *sectorsPerCluster + (isHardDrive ? 7 : 0))) {
00534                 LOG_MSG("Partition too small to format\n");
00535                 return false;
00536         }
00537 
00538 
00539         //set the minimum number of reserved sectors
00540         //the first sector of a partition is always reserved, of course
00541         *reservedSectors = 1;
00542         //if this is a hard drive, we will align to a 4kb boundary,
00543         //  so set (partitionStartingSector + reservedSectors) to an even number.
00544         //Additional alignment can be made by increasing the number of reserved sectors,
00545         //  or by increasing the reservedSectors value -- both done after the calculation
00546         if (isHardDrive && ((partitionStartingSector + *reservedSectors) % 2 == 1)) (*reservedSectors)++;
00547 
00548 
00549         //compute the number of fat sectors and data sectors
00550         Bit32u dataSectors;
00551         Bit32u dataClusters;
00552         //first try FAT12 - compute the minimum number of fat sectors necessary using the minimum number of sectors per cluster
00553         *fatSectors = (((Bit64u)partitionLength - *reservedSectors - *rootSectors + 2) * 3 + *sectorsPerCluster * 1024 + 5) / (*sectorsPerCluster * 1024 + 6);
00554         dataSectors = partitionLength - *reservedSectors - *rootSectors - *fatSectors - *fatSectors;
00555         dataClusters = dataSectors / *sectorsPerCluster;
00556         //check if this calculation makes the drive too big to fit within a FAT12 drive
00557         if (dataClusters >= 4085) {
00558                 //so instead let's calculate for FAT16, and increase the number of sectors per cluster if necessary
00559                 //note: changing the drive to FAT16 will reducing the number of data sectors, which might change the drive
00560                 //  from a FAT16 drive to a FAT12 drive.  however, the only difference is that there are unused fat sectors
00561                 //  allocated.  we cannot allocate them, or else the drive will change back to a FAT16 drive, so, just leave it as-is
00562                 do {
00563                         //compute the minimum number of fat sectors necessary starting with the minimum number of sectors per cluster
00564                         //  the +2 is for the two reserved data sectors (sector #0 and sector #1) which are not stored on the drive, but are in the map
00565                         //  rounds up and assumes 512 byte sector size
00566                         *fatSectors = ((Bit64u)partitionLength - *reservedSectors - *rootSectors + 2 + (*sectorsPerCluster * 256 + 1)) / (*sectorsPerCluster * 256 + 2);
00567 
00568                         //compute the number of data sectors and clusters with this arrangement
00569                         dataSectors = partitionLength - *reservedSectors - *rootSectors - *fatSectors - *fatSectors;
00570                         dataClusters = dataSectors / *sectorsPerCluster;
00571 
00572                         //check and see if this makes a valid FAT16 drive
00573                         if (dataClusters < 65525) break;
00574 
00575                         //otherwise, double the number of sectors per cluster and try again
00576                         *sectorsPerCluster <<= 1;
00577                 } while (true);
00578                 //determine if the drive is too large for FAT16
00579                 if (*sectorsPerCluster >= 256) {
00580                         LOG_MSG("Partition too large to format as FAT16\n");
00581                         return false;
00582                 }
00583         }
00584 
00585         //add padding to align hard drives to 4kb boundary
00586         if (isHardDrive) {
00587                 //update the reserved sector count
00588                 *reservedSectors = (partitionStartingSector + *reservedSectors + *rootSectors + *fatSectors + *fatSectors) % 8;
00589                 if (*reservedSectors == 0) *reservedSectors = 8;
00590                 //recompute the number of data sectors
00591                 dataSectors = partitionLength - *reservedSectors - *rootSectors - *fatSectors - *fatSectors;
00592                 dataClusters = dataSectors / *sectorsPerCluster;
00593 
00594                 //note: by reducing the number of data sectors, the drive might have changed from a FAT16 drive to a FAT12 drive.
00595                 //  however, the only difference is that there are unused fat sectors allocated.  we cannot allocate them, or else
00596                 //  the drive alignment will change again, and most likely the drive would change back into a FAT16 drive.
00597                 //  so, just leave it as-is
00598         }
00599 
00600         //determine whether the drive is FAT16 or not
00601         //note: this is AFTER adding padding for 4kb alignment
00602         *isFat16 = (dataClusters >= 4085);
00603 
00604         //success
00605         return true;
00606 }