1.1 diff -r 4c85846b09cd -r a392eb8f9806 src/wd2010.c 1.2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.3 +++ b/src/wd2010.c Sat Nov 17 19:18:29 2012 +0000 1.4 @@ -0,0 +1,430 @@ 1.5 +#include <stdint.h> 1.6 +#include <stdbool.h> 1.7 +#include <malloc.h> 1.8 +#include "SDL.h" 1.9 +#include "musashi/m68k.h" 1.10 +#include "wd2010.h" 1.11 + 1.12 +#define WD2010_DEBUG 1.13 + 1.14 +#ifndef WD2010_DEBUG 1.15 +#define NDEBUG 1.16 +#endif 1.17 +#include "utils.h" 1.18 + 1.19 +#define SEEK_DELAY 30 1.20 + 1.21 +#define CMD_ENABLE_RETRY 0x01 1.22 +#define CMD_LONG_MODE 0x02 1.23 +#define CMD_MULTI_SECTOR 0x04 1.24 +#define CMD_INTRQ_WHEN_COMPLETE 0x08 1.25 + 1.26 +#define ER_BAD_BLOCK 0x80 1.27 +#define ER_CRC 0x40 1.28 +#define ER_ID_NOT_FOUND 0x10 1.29 +#define ER_ABORTED_COMMAND 0x04 1.30 +#define ER_NO_TK0 0x02 1.31 +#define ER_NO_ADDRESS_MARK 0x01 1.32 + 1.33 +#define SR_BUSY 0x80 1.34 +#define SR_READY 0x40 1.35 +#define SR_WRITE_FAULT 0x20 1.36 +#define SR_SEEK_COMPLETE 0x10 1.37 +#define SR_DRQ 0x08 1.38 +#define SR_CORRECTED 0x04 1.39 +#define SR_COMMAND_IN_PROGRESS 0x02 1.40 +#define SR_ERROR 0x01 1.41 + 1.42 +extern int cpu_log_enabled; 1.43 + 1.44 +/// WD2010 command constants 1.45 +enum { 1.46 + CMD_MASK = 0xF0, ///< Bit mask to detect command bits 1.47 + CMD_2010_EXT = 0x00, ///< WD2010 extended commands (compute correction, set parameter) 1.48 + CMD_RESTORE = 0x10, ///< Restore (recalibrate, seek to track 0) 1.49 + CMD_READ_SECTOR = 0x20, ///< Read sector 1.50 + CMD_WRITE_SECTOR = 0x30, ///< Write sector 1.51 + CMD_SCAN_ID = 0x40, ///< Scan ID 1.52 + CMD_WRITE_FORMAT = 0x50, ///< Write format 1.53 + CMD_SEEK = 0x70, ///< Seek to given track 1.54 +}; 1.55 + 1.56 +int wd2010_init(WD2010_CTX *ctx, FILE *fp, int secsz, int spt, int heads) 1.57 +{ 1.58 + long filesize; 1.59 + wd2010_reset(ctx); 1.60 + // Start by finding out how big the image file is 1.61 + fseek(fp, 0, SEEK_END); 1.62 + filesize = ftell(fp); 1.63 + fseek(fp, 0, SEEK_SET); 1.64 + 1.65 + // Now figure out how many tracks it contains 1.66 + int tracks = filesize / secsz / spt / heads; 1.67 + // Confirm... 1.68 + if (tracks < 1) { 1.69 + return WD2010_ERR_BAD_GEOM; 1.70 + } 1.71 + 1.72 + // Allocate enough memory to store one disc track 1.73 + if (ctx->data) { 1.74 + free(ctx->data); 1.75 + } 1.76 + ctx->data = malloc(secsz * spt); 1.77 + if (!ctx->data) 1.78 + return WD2010_ERR_NO_MEMORY; 1.79 + 1.80 + // Load the image and the geometry data 1.81 + ctx->disc_image = fp; 1.82 + ctx->geom_tracks = tracks; 1.83 + ctx->geom_secsz = secsz; 1.84 + ctx->geom_heads = heads; 1.85 + ctx->geom_spt = spt; 1.86 + return WD2010_ERR_OK; 1.87 + 1.88 +} 1.89 + 1.90 +void wd2010_reset(WD2010_CTX *ctx) 1.91 +{ 1.92 + // track, head and sector unknown 1.93 + ctx->track = ctx->head = ctx->sector = 0; 1.94 + 1.95 + // no IRQ pending 1.96 + ctx->irq = false; 1.97 + 1.98 + // no data available 1.99 + ctx->data_pos = ctx->data_len = 0; 1.100 + 1.101 + // Status register clear, not busy 1.102 + ctx->status = 0; 1.103 + 1.104 + ctx->sector_count = 0; 1.105 + ctx->sector_number = 0; 1.106 + ctx->cylinder_low_reg = 0; 1.107 + ctx->cylinder_high_reg = 0; 1.108 + ctx->sdh = 0; 1.109 +} 1.110 + 1.111 +void wd2010_done(WD2010_CTX *ctx) 1.112 +{ 1.113 + // Reset the WD2010 1.114 + wd2010_reset(ctx); 1.115 + 1.116 + // Free any allocated memory 1.117 + if (ctx->data) { 1.118 + free(ctx->data); 1.119 + ctx->data = NULL; 1.120 + } 1.121 +} 1.122 + 1.123 + 1.124 +bool wd2010_get_irq(WD2010_CTX *ctx) 1.125 +{ 1.126 + return ctx->irq; 1.127 +} 1.128 + 1.129 +bool wd2010_get_drq(WD2010_CTX *ctx) 1.130 +{ 1.131 + return (ctx->drq && ctx->data_pos < ctx->data_len); 1.132 +} 1.133 + 1.134 +void wd2010_dma_miss(WD2010_CTX *ctx) 1.135 +{ 1.136 + ctx->data_pos = ctx->data_len; 1.137 + ctx->write_pos = 0; 1.138 + ctx->status = SR_READY | SR_SEEK_COMPLETE; 1.139 + ctx->irq = true; 1.140 +} 1.141 + 1.142 +uint8_t wd2010_read_data(WD2010_CTX *ctx) 1.143 +{ 1.144 + // If there's data in the buffer, return it. Otherwise return 0xFF. 1.145 + if (ctx->data_pos < ctx->data_len) { 1.146 + if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){ 1.147 + ctx->sector_count--; 1.148 + ctx->sector_number++; 1.149 + } 1.150 + // set IRQ if this is the last data byte 1.151 + if (ctx->data_pos == (ctx->data_len-1)) { 1.152 + ctx->status = SR_READY | SR_SEEK_COMPLETE; 1.153 + // Set IRQ 1.154 + ctx->irq = true; 1.155 + ctx->drq = false; 1.156 + } 1.157 + // return data byte and increment pointer 1.158 + return ctx->data[ctx->data_pos++]; 1.159 + } else { 1.160 + // empty buffer (this shouldn't happen) 1.161 + LOG("WD2010: attempt to read from empty data buffer"); 1.162 + return 0xff; 1.163 + } 1.164 +} 1.165 + 1.166 +void wd2010_write_data(WD2010_CTX *ctx, uint8_t val) 1.167 +{ 1.168 + // If we're processing a write command, and there's space in the 1.169 + // buffer, allow the write. 1.170 + if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) { 1.171 + // store data byte and increment pointer 1.172 + if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){ 1.173 + ctx->sector_count--; 1.174 + ctx->sector_number++; 1.175 + } 1.176 + ctx->data[ctx->data_pos++] = val; 1.177 + // set IRQ and write data if this is the last data byte 1.178 + if (ctx->data_pos == ctx->data_len) { 1.179 + if (!ctx->formatting){ 1.180 + fseek(ctx->disc_image, ctx->write_pos, SEEK_SET); 1.181 + fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image); 1.182 + fflush(ctx->disc_image); 1.183 + } 1.184 + ctx->formatting = false; 1.185 + ctx->status = SR_READY | SR_SEEK_COMPLETE; 1.186 + // Set IRQ and reset write pointer 1.187 + ctx->irq = true; 1.188 + ctx->write_pos = -1; 1.189 + ctx->drq = false; 1.190 + } 1.191 + }else{ 1.192 + LOG("WD2010: attempt to write to data buffer without a write command in progress"); 1.193 + } 1.194 +} 1.195 + 1.196 +uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx) 1.197 +{ 1.198 + /*m68k_end_timeslice();*/ 1.199 + ctx->status = SR_READY | SR_SEEK_COMPLETE; 1.200 + ctx->irq = true; 1.201 + return (0); 1.202 +} 1.203 + 1.204 +uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx) 1.205 +{ 1.206 + /*m68k_end_timeslice();*/ 1.207 + ctx->drq = true; 1.208 + return (0); 1.209 +} 1.210 + 1.211 +uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr) 1.212 +{ 1.213 + uint8_t temp = 0; 1.214 + 1.215 + /*cpu_log_enabled = 1;*/ 1.216 + 1.217 + switch (addr & 0x07) { 1.218 + case WD2010_REG_ERROR: 1.219 + return ctx->error_reg; 1.220 + case WD2010_REG_SECTOR_COUNT: 1.221 + return ctx->sector_count; 1.222 + case WD2010_REG_SECTOR_NUMBER: 1.223 + return ctx->sector_number; 1.224 + case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder 1.225 + return ctx->cylinder_high_reg; 1.226 + case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder 1.227 + return ctx->cylinder_low_reg; 1.228 + case WD2010_REG_SDH: 1.229 + return ctx->sdh; 1.230 + case WD2010_REG_STATUS: // Status register 1.231 + // Read from status register clears IRQ 1.232 + ctx->irq = false; 1.233 + // Get current status flags (set by last command) 1.234 + // DRQ bit 1.235 + if (ctx->cmd_has_drq) { 1.236 + temp = ctx->status & ~(SR_BUSY & SR_DRQ); 1.237 + temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0; 1.238 + LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp); 1.239 + } else { 1.240 + temp = ctx->status & ~0x80; 1.241 + } 1.242 + /*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/ 1.243 + // HDC is busy if there is still data in the buffer 1.244 + temp |= (ctx->data_pos < ctx->data_len) ? SR_BUSY : 0; // if data in buffer, then DMA hasn't copied it yet, and we're still busy! 1.245 + // TODO: also if seek delay / read delay hasn't passed (but that's for later) 1.246 + /*XXX: should anything else be set here?*/ 1.247 + return temp; 1.248 + default: 1.249 + // shut up annoying compilers which don't recognise unreachable code when they see it 1.250 + // (here's looking at you, gcc!) 1.251 + return 0xff; 1.252 + } 1.253 +} 1.254 + 1.255 + 1.256 +void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val) 1.257 +{ 1.258 + uint8_t cmd = val & CMD_MASK; 1.259 + size_t lba; 1.260 + int new_track; 1.261 + int sector_count; 1.262 + 1.263 + m68k_end_timeslice(); 1.264 + 1.265 + /*cpu_log_enabled = 1;*/ 1.266 + 1.267 + switch (addr & 0x07) { 1.268 + case WD2010_REG_WRITE_PRECOMP_CYLINDER: 1.269 + break; 1.270 + case WD2010_REG_SECTOR_COUNT: 1.271 + ctx->sector_count = val; 1.272 + break; 1.273 + case WD2010_REG_SECTOR_NUMBER: 1.274 + ctx->sector_number = val; 1.275 + break; 1.276 + case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder 1.277 + ctx->cylinder_high_reg = val; 1.278 + break; 1.279 + case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder 1.280 + ctx->cylinder_low_reg = val; 1.281 + break; 1.282 + case WD2010_REG_SDH: 1.283 + /*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/ 1.284 + ctx->data_pos = ctx->data_len = 0; 1.285 + ctx->sdh = val; 1.286 + break; 1.287 + case WD2010_REG_COMMAND: // Command register 1.288 + // write to command register clears interrupt request 1.289 + ctx->irq = false; 1.290 + ctx->error_reg = 0; 1.291 + 1.292 + /*cpu_log_enabled = 1;*/ 1.293 + switch (cmd) { 1.294 + case CMD_RESTORE: 1.295 + // Restore. Set track to 0 and throw an IRQ. 1.296 + ctx->track = 0; 1.297 + SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx); 1.298 + break; 1.299 + case CMD_SCAN_ID: 1.300 + ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff; 1.301 + ctx->cylinder_low_reg = ctx->track & 0xff; 1.302 + ctx->sector_number = ctx->sector; 1.303 + ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7); 1.304 + case CMD_WRITE_FORMAT: 1.305 + case CMD_SEEK: 1.306 + case CMD_READ_SECTOR: 1.307 + case CMD_WRITE_SECTOR: 1.308 + // Seek. Seek to the track specced in the cylinder 1.309 + // registers. 1.310 + new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg; 1.311 + if (new_track < ctx->geom_tracks) { 1.312 + ctx->track = new_track; 1.313 + } else { 1.314 + // Seek error. :( 1.315 + ctx->status = SR_ERROR; 1.316 + ctx->error_reg = ER_ID_NOT_FOUND; 1.317 + ctx->irq = true; 1.318 + break; 1.319 + } 1.320 + ctx->head = ctx->sdh & 0x07; 1.321 + ctx->sector = ctx->sector_number; 1.322 + 1.323 + ctx->formatting = cmd == CMD_WRITE_FORMAT; 1.324 + switch (cmd){ 1.325 + case CMD_SEEK: 1.326 + SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx); 1.327 + break; 1.328 + case CMD_READ_SECTOR: 1.329 + /*XXX: does a separate function to set the head have to be added?*/ 1.330 + LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector); 1.331 + 1.332 + // Read Sector 1.333 + 1.334 + // Check to see if the cyl, hd and sec are valid 1.335 + if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) { 1.336 + LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d", 1.337 + ctx->track, ctx->head, ctx->sector, 1.338 + ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1); 1.339 + // CHS parameters exceed limits 1.340 + ctx->status = SR_ERROR; 1.341 + ctx->error_reg = ER_ID_NOT_FOUND; 1.342 + // Set IRQ 1.343 + ctx->irq = true; 1.344 + break; 1.345 + } 1.346 + 1.347 + // reset data pointers 1.348 + ctx->data_pos = ctx->data_len = 0; 1.349 + 1.350 + if (val & CMD_MULTI_SECTOR){ 1.351 + ctx->multi_sector = 1; 1.352 + sector_count = ctx->sector_count; 1.353 + }else{ 1.354 + ctx->multi_sector = 0; 1.355 + sector_count = 1; 1.356 + } 1.357 + for (int i=0; i<sector_count; i++) { 1.358 + // Calculate the LBA address of the required sector 1.359 + // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1 1.360 + lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i); 1.361 + // convert LBA to byte address 1.362 + lba *= ctx->geom_secsz; 1.363 + LOG("\tREAD lba = %lu", lba); 1.364 + 1.365 + // Read the sector from the file 1.366 + fseek(ctx->disc_image, lba, SEEK_SET); 1.367 + // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr) 1.368 + ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image); 1.369 + LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz); 1.370 + } 1.371 + 1.372 + ctx->status = 0; 1.373 + ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00; 1.374 + SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx); 1.375 + 1.376 + break; 1.377 + case CMD_WRITE_FORMAT: 1.378 + ctx->sector = 0; 1.379 + case CMD_WRITE_SECTOR: 1.380 + LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector); 1.381 + // Read Sector 1.382 + 1.383 + // Check to see if the cyl, hd and sec are valid 1.384 + if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) { 1.385 + LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d", 1.386 + ctx->track, ctx->head, ctx->sector, 1.387 + ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt); 1.388 + // CHS parameters exceed limits 1.389 + ctx->status = SR_ERROR; 1.390 + ctx->error_reg = ER_ID_NOT_FOUND; 1.391 + // Set IRQ 1.392 + ctx->irq = true; 1.393 + break; 1.394 + } 1.395 + 1.396 + // reset data pointers 1.397 + ctx->data_pos = ctx->data_len = 0; 1.398 + 1.399 + if (val & CMD_MULTI_SECTOR){ 1.400 + ctx->multi_sector = 1; 1.401 + sector_count = ctx->sector_count; 1.402 + }else{ 1.403 + ctx->multi_sector = 0; 1.404 + sector_count = 1; 1.405 + } 1.406 + ctx->data_len = ctx->geom_secsz * sector_count; 1.407 + lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector)); 1.408 + // convert LBA to byte address 1.409 + ctx->write_pos = lba * ctx->geom_secsz; 1.410 + LOG("\tWRITE lba = %lu", lba); 1.411 + 1.412 + ctx->status = 0; 1.413 + ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00; 1.414 + SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx); 1.415 + 1.416 + break; 1.417 + default: 1.418 + LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd); 1.419 + break; 1.420 + } 1.421 + break; 1.422 + case CMD_2010_EXT: /* not implemented */ 1.423 + default: 1.424 + LOG("WD2010: unknown command %x\n", cmd); 1.425 + ctx->status = SR_ERROR; 1.426 + ctx->error_reg = ER_ABORTED_COMMAND; 1.427 + ctx->irq = true; 1.428 + break; 1.429 + } 1.430 + break; 1.431 + 1.432 + } 1.433 +} 1.434 +