Merge pull request #2434 from elyosh/hddtiming

Add realistic HDD timing simulation
This commit is contained in:
Miran Grča
2022-07-10 00:29:21 +02:00
committed by GitHub
4 changed files with 521 additions and 55 deletions

View File

@@ -220,7 +220,7 @@ ide_get_drive(int ch)
double
ide_get_period(ide_t *ide, int size)
ide_get_xfer_time(ide_t *ide, int size)
{
double period = (10.0 / 3.0);
@@ -313,7 +313,7 @@ ide_atapi_get_period(uint8_t channel)
return -1.0;
}
return ide_get_period(ide, 1);
return ide_get_xfer_time(ide, 1);
}
@@ -535,7 +535,7 @@ static void ide_hd_identify(ide_t *ide)
ide_padstr((char *) (ide->buffer + 27), device_identify, 40); /* Model */
ide->buffer[0] = (1 << 6); /*Fixed drive*/
ide->buffer[20] = 3; /*Buffer type*/
ide->buffer[21] = 512; /*Buffer size*/
ide->buffer[21] = hdd[ide->hdd_num].cache.num_segments * hdd[ide->hdd_num].cache.segment_size; /*Buffer size*/
ide->buffer[50] = 0x4000; /* Capabilities */
ide->buffer[59] = ide->blocksize ? (ide->blocksize | 0x100) : 0;
@@ -577,12 +577,11 @@ static void ide_hd_identify(ide_t *ide)
ide_log("Current CHS translation: %i, %i, %i\n", ide->buffer[54], ide->buffer[55], ide->buffer[56]);
}
ide->buffer[47] = hdd[ide->hdd_num].max_multiple_block | 0x8000; /*Max sectors on multiple transfer command*/
if (!ide_boards[ide->board]->force_ata3 && ide_bm[ide->board]) {
ide->buffer[47] = 32 | 0x8000; /*Max sectors on multiple transfer command*/
ide->buffer[80] = 0x7e; /*ATA-1 to ATA-6 supported*/
ide->buffer[81] = 0x19; /*ATA-6 revision 3a supported*/
} else {
ide->buffer[47] = 16 | 0x8000; /*Max sectors on multiple transfer command*/
ide->buffer[80] = 0x0e; /*ATA-1 to ATA-3 supported*/
}
}
@@ -692,13 +691,15 @@ ide_get_sector(ide_t *ide)
uint32_t heads, sectors;
if (ide->lba)
return (off64_t)ide->lba_addr + ide->skip512;
return (off64_t)ide->lba_addr;
else {
heads = ide->cfg_hpc;
sectors = ide->cfg_spt;
uint8_t sector = ide->sector ? ide->sector : 1;
return ((((off64_t) ide->cylinder * heads) + ide->head) *
sectors) + (ide->sector - 1) + ide->skip512;
sectors) + (sector - 1);
}
}
@@ -733,6 +734,8 @@ loadhd(ide_t *ide, int d, const char *fn)
return;
}
hdd_preset_auto(&hdd[d]);
ide->spt = ide->cfg_spt = hdd[d].spt;
ide->hpc = ide->cfg_hpc = hdd[d].hpc;
ide->tracks = hdd[d].tracks;
@@ -1226,31 +1229,41 @@ ide_write_data(ide_t *ide, uint32_t val, int length)
if (ide->type == IDE_ATAPI)
ide_atapi_packet_write(ide, val, length);
} else {
switch(length) {
case 1:
idebufferb[ide->pos] = val & 0xff;
ide->pos++;
break;
case 2:
idebufferw[ide->pos >> 1] = val & 0xffff;
ide->pos += 2;
break;
case 4:
idebufferl[ide->pos >> 2] = val;
ide->pos += 4;
break;
default:
return;
}
switch(length) {
case 1:
idebufferb[ide->pos] = val & 0xff;
ide->pos++;
break;
case 2:
idebufferw[ide->pos >> 1] = val & 0xffff;
ide->pos += 2;
break;
case 4:
idebufferl[ide->pos >> 2] = val;
ide->pos += 4;
break;
default:
return;
}
if (ide->pos >= 512) {
ide->pos=0;
ide->atastat = BSY_STAT;
if (ide->command == WIN_WRITE_MULTIPLE)
ide_callback(ide);
else
ide_set_callback(ide, ide_get_period(ide, 512));
}
if (ide->pos >= 512) {
ide->pos=0;
ide->atastat = BSY_STAT;
double seek_time = hdd_timing_write(&hdd[ide->hdd_num], ide_get_sector(ide), 1);
double xfer_time = ide_get_xfer_time(ide, 512);
double wait_time = seek_time + xfer_time;
if (ide->command == WIN_WRITE_MULTIPLE) {
if ((ide->blockcount+1) >= ide->blocksize || ide->secount == 1) {
ide_set_callback(ide, seek_time + xfer_time + ide->pending_delay);
ide->pending_delay = 0;
} else {
ide->pending_delay += wait_time;
ide_callback(ide);
}
} else {
ide_set_callback(ide, wait_time);
}
}
}
}
@@ -1601,9 +1614,13 @@ ide_writeb(uint16_t addr, uint8_t val, void *priv)
else
ide->atastat = READY_STAT | BSY_STAT;
if (ide->type == IDE_ATAPI)
if (ide->type == IDE_ATAPI) {
ide->sc->callback = 100.0 * IDE_TIME;
ide_set_callback(ide, 100.0 * IDE_TIME);
ide_set_callback(ide, 100.0 * IDE_TIME);
} else {
double seek_time = hdd_seek_get_time(&hdd[ide->hdd_num], ide_get_sector(ide), HDD_OP_SEEK, 0, 0.0);
ide_set_callback(ide, seek_time);
}
return;
}
@@ -1641,15 +1658,26 @@ ide_writeb(uint16_t addr, uint8_t val, void *priv)
ide->atastat = BSY_STAT;
if (ide->type == IDE_HDD) {
uint32_t sec_count;
double wait_time;
if ((val == WIN_READ_DMA) || (val == WIN_READ_DMA_ALT)) {
if (ide->secount)
ide_set_callback(ide, ide_get_period(ide, (int) ide->secount << 9));
else
ide_set_callback(ide, ide_get_period(ide, 131072));
} else if (val == WIN_READ_MULTIPLE)
ide_set_callback(ide, 200.0 * IDE_TIME);
else
ide_set_callback(ide, ide_get_period(ide, 512));
// TODO make DMA timing more accurate
sec_count = ide->secount ? ide->secount : 256;
double seek_time = hdd_timing_read(&hdd[ide->hdd_num], ide_get_sector(ide), sec_count);
double xfer_time = ide_get_xfer_time(ide, 512 * sec_count);
wait_time = seek_time > xfer_time ? seek_time : xfer_time;
} else if (val == WIN_READ_MULTIPLE) {
sec_count = (ide->secount < ide->blocksize) ? ide->secount : ide->blocksize;
double seek_time = hdd_timing_read(&hdd[ide->hdd_num], ide_get_sector(ide), sec_count);
double xfer_time = ide_get_xfer_time(ide, 512 * sec_count);
wait_time = seek_time + xfer_time;
} else {
sec_count = 1;
double seek_time = hdd_timing_read(&hdd[ide->hdd_num], ide_get_sector(ide), sec_count);
double xfer_time = ide_get_xfer_time(ide, 512 * sec_count);
wait_time = seek_time + xfer_time;
}
ide_set_callback(ide, wait_time);
} else
ide_set_callback(ide, 200.0 * IDE_TIME);
ide->do_initial_read = 1;
@@ -1690,14 +1718,16 @@ ide_writeb(uint16_t addr, uint8_t val, void *priv)
if ((ide->type == IDE_HDD) &&
((val == WIN_WRITE_DMA) || (val == WIN_WRITE_DMA_ALT))) {
if (ide->secount)
ide_set_callback(ide, ide_get_period(ide, (int) ide->secount << 9));
else
ide_set_callback(ide, ide_get_period(ide, 131072));
uint32_t sec_count = ide->secount ? ide->secount : 256;
double seek_time = hdd_timing_read(&hdd[ide->hdd_num], ide_get_sector(ide), sec_count);
double xfer_time = ide_get_xfer_time(ide, 512 * sec_count);
double wait_time = seek_time > xfer_time ? seek_time : xfer_time;
ide_set_callback(ide, wait_time);
} else if ((ide->type == IDE_HDD) &&
((val == WIN_VERIFY) || (val == WIN_VERIFY_ONCE)))
ide_set_callback(ide, ide_get_period(ide, 512));
else if (val == WIN_IDENTIFY)
((val == WIN_VERIFY) || (val == WIN_VERIFY_ONCE))) {
double seek_time = hdd_timing_read(&hdd[ide->hdd_num], ide_get_sector(ide), ide->secount);
ide_set_callback(ide, seek_time + ide_get_xfer_time(ide, 2));
} else if (val == WIN_IDENTIFY)
ide_callback(ide);
else
ide_set_callback(ide, 200.0 * IDE_TIME);
@@ -1864,10 +1894,20 @@ ide_read_data(ide_t *ide, int length)
if (ide->secount) {
ide_next_sector(ide);
ide->atastat = BSY_STAT | READY_STAT | DSC_STAT;
if (ide->command == WIN_READ_MULTIPLE)
ide_callback(ide);
else
ide_set_callback(ide, ide_get_period(ide, 512));
if (ide->command == WIN_READ_MULTIPLE) {
if (!ide->blockcount) {
uint32_t sec_count = (ide->secount < ide->blocksize) ? ide->secount : ide->blocksize;
double seek_time = hdd_timing_read(&hdd[ide->hdd_num], ide_get_sector(ide), sec_count);
double xfer_time = ide_get_xfer_time(ide, 512 * sec_count);
ide_set_callback(ide, seek_time + xfer_time);
} else {
ide_callback(ide);
}
} else {
double seek_time = hdd_timing_read(&hdd[ide->hdd_num], ide_get_sector(ide), 1);
double xfer_time = ide_get_xfer_time(ide, 512);
ide_set_callback(ide, seek_time + xfer_time);
}
} else if (ide->command != WIN_READ_MULTIPLE)
ui_sb_update_icon(SB_HDD | hdd[ide->hdd_num].bus, 0);
}

View File

@@ -17,15 +17,19 @@
* Copyright 2017-2019 Fred N. van Kempen.
*/
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <wchar.h>
#include <86box/86box.h>
#include <86box/plat.h>
#include <86box/ui.h>
#include <86box/hdd.h>
#include <86box/cdrom.h>
#include <86box/video.h>
#include "cpu.h"
hard_disk_t hdd[HDD_NUM];
@@ -150,3 +154,346 @@ hdd_is_valid(int c)
return(1);
}
double
hdd_seek_get_time(hard_disk_t *hdd, uint32_t dst_addr, uint8_t operation, uint8_t continuous, double max_seek_time)
{
hdd_zone_t *zone = NULL;
for (int i = 0; i < hdd->num_zones; i++) {
zone = &hdd->zones[i];
if (zone->end_sector >= dst_addr)
break;
}
uint32_t new_track = zone->start_track + ((dst_addr - zone->start_sector) / zone->sectors_per_track);
uint32_t new_cylinder = new_track / hdd->phy_heads;
uint32_t cylinder_diff = abs((int)hdd->cur_cylinder - (int)new_cylinder);
bool sequential = dst_addr == hdd->cur_addr + 1;
continuous = continuous && sequential;
double seek_time = 0.0;
if (continuous) {
if (new_track == hdd->cur_track) {
// Same track
seek_time = zone->sector_time_usec;
} else if (!cylinder_diff) {
// Same cylinder, sequential track
seek_time = hdd->head_switch_usec;
} else {
// Sequential cylinder
seek_time = hdd->cyl_switch_usec;
}
} else {
if (!cylinder_diff) {
if (operation != HDD_OP_SEEK) {
seek_time = hdd->avg_rotation_lat_usec;
} else {
//seek_time = hdd->cyl_switch_usec;
seek_time = 50.0;
}
} else {
seek_time = hdd->cyl_switch_usec + (hdd->full_stroke_usec * (double)cylinder_diff / (double)hdd->phy_cyl);
if (operation != HDD_OP_SEEK) {
seek_time += hdd->avg_rotation_lat_usec;
}
}
}
if (!max_seek_time || seek_time <= max_seek_time) {
hdd->cur_addr = dst_addr;
hdd->cur_track = new_track;
hdd->cur_cylinder = new_cylinder;
}
return seek_time;
}
static void
hdd_readahead_update(hard_disk_t *hdd)
{
hdd_cache_t *cache = &hdd->cache;
if (cache->ra_ongoing) {
hdd_cache_seg_t *segment = &cache->segments[cache->ra_segment];
uint64_t elapsed_cycles = tsc - cache->ra_start_time;
double elapsed_us = (double)elapsed_cycles / cpuclock * 1000000.0;
// Do not overwrite data not yet read by host
uint32_t max_read_ahead = (segment->host_addr + cache->segment_size) - segment->ra_addr;
double seek_time = 0.0;
for (uint32_t i = 0; i < max_read_ahead; i++) {
seek_time += hdd_seek_get_time(hdd, segment->ra_addr, HDD_OP_READ, 1, elapsed_us - seek_time);
if (seek_time > elapsed_us)
break;
segment->ra_addr++;
}
if (segment->ra_addr > segment->lba_addr + cache->segment_size) {
uint32_t space_needed = segment->ra_addr - (segment->lba_addr + cache->segment_size);
segment->lba_addr += space_needed;
}
}
}
static double
hdd_writecache_flush(hard_disk_t *hdd)
{
double seek_time = 0.0;
while (hdd->cache.write_pending) {
seek_time += hdd_seek_get_time(hdd, hdd->cache.write_addr, HDD_OP_WRITE, 1, 0);
hdd->cache.write_addr++;
hdd->cache.write_pending--;
}
return seek_time;
}
static void
hdd_writecache_update(hard_disk_t *hdd)
{
if (hdd->cache.write_pending) {
uint64_t elapsed_cycles = tsc - hdd->cache.write_start_time;
double elapsed_us = (double)elapsed_cycles / cpuclock * 1000000.0;
double seek_time = 0.0;
while (hdd->cache.write_pending) {
seek_time += hdd_seek_get_time(hdd, hdd->cache.write_addr, HDD_OP_WRITE, 1, elapsed_us - seek_time);
if (seek_time > elapsed_us)
break;
hdd->cache.write_addr++;
hdd->cache.write_pending--;
}
}
}
double
hdd_timing_write(hard_disk_t *hdd, uint32_t addr, uint32_t len)
{
hdd_readahead_update(hdd);
hdd_writecache_update(hdd);
hdd->cache.ra_ongoing = 0;
double seek_time = 0.0;
if (hdd->cache.write_pending && (addr != (hdd->cache.write_addr + hdd->cache.write_pending))) {
// New request is not sequential to existing cache, need to flush it
seek_time += hdd_writecache_flush(hdd);
}
if (!hdd->cache.write_pending) {
// Cache is empty
hdd->cache.write_addr = addr;
}
hdd->cache.write_pending += len;
if (hdd->cache.write_pending > hdd->cache.write_size) {
// If request is bigger than free cache, flush some data first
uint32_t flush_needed = hdd->cache.write_pending - hdd->cache.write_size;
for (uint32_t i = 0; i < flush_needed; i++) {
seek_time += hdd_seek_get_time(hdd, hdd->cache.write_addr, HDD_OP_WRITE, 1, 0);
hdd->cache.write_addr++;
}
}
hdd->cache.write_start_time = tsc + (uint32_t)(seek_time * cpuclock / 1000000.0);
return seek_time;
}
double
hdd_timing_read(hard_disk_t *hdd, uint32_t addr, uint32_t len)
{
hdd_readahead_update(hdd);
hdd_writecache_update(hdd);
double seek_time = 0.0;
seek_time += hdd_writecache_flush(hdd);
hdd_cache_t *cache = &hdd->cache;
hdd_cache_seg_t *active_seg = &cache->segments[0];
for (uint32_t i = 0; i < cache->num_segments; i++) {
hdd_cache_seg_t *segment = &cache->segments[i];
if (!segment->valid) {
active_seg = segment;
continue;
}
if (segment->lba_addr <= addr && (segment->lba_addr + cache->segment_size) >= addr) {
// Cache HIT
segment->host_addr = addr;
active_seg = segment;
if (addr + len > segment->ra_addr) {
uint32_t need_read = (addr + len) - segment->ra_addr;
for (uint32_t j = 0; j < need_read; j++) {
seek_time += hdd_seek_get_time(hdd, segment->ra_addr, HDD_OP_READ, 1, 0.0);
segment->ra_addr++;
}
}
if (addr + len > segment->lba_addr + cache->segment_size) {
// Need to erase some previously cached data
uint32_t space_needed = (addr + len) - (segment->lba_addr + cache->segment_size);
segment->lba_addr += space_needed;
}
goto update_lru;
} else {
if (segment->lru > active_seg->lru) {
active_seg = segment;
}
}
}
// Cache MISS
active_seg->lba_addr = addr;
active_seg->valid = 1;
active_seg->host_addr = addr;
active_seg->ra_addr = addr;
for (uint32_t i = 0; i < len; i++) {
seek_time += hdd_seek_get_time(hdd, active_seg->ra_addr, HDD_OP_READ, i != 0, 0.0);
active_seg->ra_addr++;
}
update_lru:
for (uint32_t i = 0; i < cache->num_segments; i++) {
cache->segments[i].lru++;
}
active_seg->lru = 0;
cache->ra_ongoing = 1;
cache->ra_segment = active_seg->id;
cache->ra_start_time = tsc + (uint32_t)(seek_time * cpuclock / 1000000.0);
return seek_time;
}
static void
hdd_cache_init(hard_disk_t *hdd)
{
hdd_cache_t *cache = &hdd->cache;
cache->ra_segment = 0;
cache->ra_ongoing = 0;
cache->ra_start_time = 0;
for (uint32_t i = 0; i < cache->num_segments; i++) {
cache->segments[i].valid = 0;
cache->segments[i].lru = 0;
cache->segments[i].id = i;
cache->segments[i].ra_addr = 0;
cache->segments[i].host_addr = 0;
}
}
static void
hdd_zones_init(hard_disk_t *hdd)
{
uint32_t lba = 0;
uint32_t track = 0;
double revolution_usec = 60.0 / (double)hdd->rpm * 1000000.0;
for (uint32_t i = 0; i < hdd->num_zones; i++) {
hdd_zone_t *zone = &hdd->zones[i];
zone->start_sector = lba;
zone->start_track = track;
zone->sector_time_usec = revolution_usec / (double)zone->sectors_per_track;
uint32_t tracks = zone->cylinders * hdd->phy_heads;
lba += tracks * zone->sectors_per_track;
zone->end_sector = lba - 1;
track += tracks - 1;
}
}
hdd_preset_t hdd_presets[] = {
{ .target_year = 1989, .match_max_mbyte = 99, .zones = 1, .avg_spt = 35, .heads = 2, .rpm = 3500, .full_stroke_ms = 40, .track_seek_ms = 8,
.rcache_num_seg = 1, .rcache_seg_size = 16, .max_multiple = 8 },
{ .target_year = 1992, .match_max_mbyte = 249, .zones = 1, .avg_spt = 45, .heads = 2, .rpm = 3500, .full_stroke_ms = 30, .track_seek_ms = 6,
.rcache_num_seg = 4, .rcache_seg_size = 16, .max_multiple = 8 },
{ .target_year = 1994, .match_max_mbyte = 999, .zones = 8, .avg_spt = 80, .heads = 4, .rpm = 4500, .full_stroke_ms = 26, .track_seek_ms = 5,
.rcache_num_seg = 4, .rcache_seg_size = 32, .max_multiple = 16 },
{ .target_year = 1996, .match_max_mbyte = 1999, .zones = 16, .avg_spt = 135, .heads = 4, .rpm = 5400, .full_stroke_ms = 24, .track_seek_ms = 3,
.rcache_num_seg = 4, .rcache_seg_size = 64, .max_multiple = 16 },
{ .target_year = 1997, .match_max_mbyte = 4999, .zones = 16, .avg_spt = 185, .heads = 6, .rpm = 5400, .full_stroke_ms = 20, .track_seek_ms = 2.5,
.rcache_num_seg = 8, .rcache_seg_size = 64, .max_multiple = 32 },
{ .target_year = 1998, .match_max_mbyte = 9999, .zones = 16, .avg_spt = 300, .heads = 8, .rpm = 5400, .full_stroke_ms = 20, .track_seek_ms = 2,
.rcache_num_seg = 8, .rcache_seg_size = 128, .max_multiple = 32 },
{ .target_year = 2000, .match_max_mbyte = 99999, .zones = 16, .avg_spt = 350, .heads = 6, .rpm = 7200, .full_stroke_ms = 15, .track_seek_ms = 2,
.rcache_num_seg = 16, .rcache_seg_size = 128, .max_multiple = 32 },
};
void
hdd_preset_apply(hard_disk_t *hdd, hdd_preset_t *preset)
{
hdd->phy_heads = preset->heads;
hdd->rpm = preset->rpm;
double revolution_usec = 60.0 / (double)hdd->rpm * 1000000.0;
hdd->avg_rotation_lat_usec = revolution_usec / 2;
hdd->full_stroke_usec = preset->full_stroke_ms * 1000;
hdd->head_switch_usec = preset->track_seek_ms * 1000;
hdd->cyl_switch_usec = preset->track_seek_ms * 1000;
hdd->cache.num_segments = preset->rcache_num_seg;
hdd->cache.segment_size = preset->rcache_seg_size;
hdd->max_multiple_block = preset->max_multiple;
hdd->cache.write_size = 64;
hdd->num_zones = preset->zones;
uint32_t disk_sectors = hdd->tracks * hdd->hpc * hdd->spt;
uint32_t sectors_per_surface = (uint32_t)ceil((double)disk_sectors / (double)hdd->phy_heads);
uint32_t cylinders = (uint32_t)ceil((double)sectors_per_surface / (double)preset->avg_spt);
hdd->phy_cyl = cylinders;
uint32_t cylinders_per_zone = cylinders / preset->zones;
uint32_t total_sectors = 0;
for (uint32_t i = 0; i < preset->zones; i++) {
uint32_t spt;
double zone_percent = i * 100 / (double)preset->zones;
if (i < preset->zones - 1) {
// Function for realistic zone sector density
double spt_percent = -0.00341684 * pow(zone_percent, 2) - 0.175811 * zone_percent + 118.48;
spt = (uint32_t)ceil((double)preset->avg_spt * spt_percent / 100);
} else {
spt = (uint32_t)ceil((double)(disk_sectors - total_sectors) / (double)(cylinders_per_zone*preset->heads));
}
uint32_t zone_sectors = spt * cylinders_per_zone * preset->heads;
total_sectors += zone_sectors;
hdd->zones[i].cylinders = cylinders_per_zone;
hdd->zones[i].sectors_per_track = spt;
}
hdd_zones_init(hdd);
hdd_cache_init(hdd);
}
void
hdd_preset_auto(hard_disk_t *hdd)
{
uint32_t disk_sectors = hdd->tracks * hdd->hpc * hdd->spt;
uint32_t disk_size_mb = disk_sectors * 512 / 1024 / 1024;
int i;
for (i = 0; i < (sizeof(hdd_presets) / sizeof(hdd_presets[0])); i++) {
if (hdd_presets[i].match_max_mbyte >= disk_size_mb)
break;
}
hdd_preset_t *preset = &hdd_presets[i];
hdd_preset_apply(hdd, preset);
}

View File

@@ -49,7 +49,7 @@ typedef struct ide_s {
blocksize, blockcount,
hdd_num, channel,
pos, sector_pos,
lba, skip512,
lba,
reset, mdma_mode,
do_initial_read;
uint32_t secount, sector,
@@ -67,6 +67,7 @@ typedef struct ide_s {
/* Stuff mostly used by ATAPI */
scsi_common_t *sc;
int interrupt_drq;
double pending_delay;
int (*get_max)(int ide_has_dma, int type);
int (*get_timings)(int ide_has_dma, int type);

View File

@@ -72,6 +72,62 @@ enum {
};
#endif
enum {
HDD_OP_SEEK = 0,
HDD_OP_READ,
HDD_OP_WRITE
};
#define HDD_MAX_ZONES 16
#define HDD_MAX_CACHE_SEG 16
typedef struct {
uint32_t match_max_mbyte;
uint32_t zones;
uint32_t avg_spt;
uint32_t heads;
uint32_t rpm;
uint32_t target_year;
uint32_t rcache_num_seg;
uint32_t rcache_seg_size;
uint32_t max_multiple;
double full_stroke_ms;
double track_seek_ms;
} hdd_preset_t;
typedef struct {
uint32_t id;
uint32_t lba_addr;
uint32_t ra_addr;
uint32_t host_addr;
uint8_t lru;
uint8_t valid;
} hdd_cache_seg_t;
typedef struct {
// Read cache
hdd_cache_seg_t segments[HDD_MAX_CACHE_SEG];
uint32_t num_segments;
uint32_t segment_size;
uint32_t ra_segment;
uint8_t ra_ongoing;
uint64_t ra_start_time;
// Write cache
uint32_t write_addr;
uint32_t write_pending;
uint32_t write_size;
uint64_t write_start_time;
} hdd_cache_t;
typedef struct {
uint32_t cylinders;
uint32_t sectors_per_track;
double sector_time_usec;
uint32_t start_sector;
uint32_t end_sector;
uint32_t start_track;
} hdd_zone_t;
/* Define the virtual Hard Disk. */
typedef struct {
@@ -100,6 +156,23 @@ typedef struct {
spt,
hpc, /* Physical geometry parameters */
tracks;
hdd_zone_t zones[HDD_MAX_ZONES];
uint32_t num_zones;
hdd_cache_t cache;
uint32_t phy_cyl;
uint32_t phy_heads;
uint32_t rpm;
uint8_t max_multiple_block;
uint32_t cur_cylinder;
uint32_t cur_track;
uint32_t cur_addr;
double avg_rotation_lat_usec;
double full_stroke_usec;
double head_switch_usec;
double cyl_switch_usec;
} hard_disk_t;
@@ -131,5 +204,10 @@ extern int image_is_hdi(const char *s);
extern int image_is_hdx(const char *s, int check_signature);
extern int image_is_vhd(const char *s, int check_signature);
extern double hdd_timing_write(hard_disk_t *hdd, uint32_t addr, uint32_t len);
extern double hdd_timing_read(hard_disk_t *hdd, uint32_t addr, uint32_t len);
extern double hdd_seek_get_time(hard_disk_t *hdd, uint32_t dst_addr, uint8_t operation, uint8_t continuous, double max_seek_time);
extern void hdd_preset_apply(hard_disk_t *hdd, hdd_preset_t *preset);
extern void hdd_preset_auto(hard_disk_t *hdd);
#endif /*EMU_HDD_H*/