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