Sun, 05 Dec 2010 03:55:46 +0000
add preliminary WD2797 FDC emulator
Makefile | file | annotate | diff | revisions | |
src/main.c | file | annotate | diff | revisions | |
src/wd279x.c | file | annotate | diff | revisions | |
src/wd279x.h | file | annotate | diff | revisions |
1.1 --- a/Makefile Fri Dec 03 14:21:19 2010 +0000 1.2 +++ b/Makefile Sun Dec 05 03:55:46 2010 +0000 1.3 @@ -118,7 +118,7 @@ 1.4 TARGET = freebee 1.5 1.6 # source files that produce object files 1.7 -SRC = main.c state.c memory.c 1.8 +SRC = main.c state.c memory.c wd279x.c 1.9 SRC += musashi/m68kcpu.c musashi/m68kdasm.c musashi/m68kops.c musashi/m68kopac.c musashi/m68kopdm.c musashi/m68kopnz.c 1.10 1.11 # source type - either "c" or "cpp" (C or C++)
2.1 --- a/src/main.c Fri Dec 03 14:21:19 2010 +0000 2.2 +++ b/src/main.c Sun Dec 05 03:55:46 2010 +0000 2.3 @@ -129,9 +129,9 @@ 2.4 return true; 2.5 case SDL_KEYDOWN: 2.6 switch (event.key.keysym.sym) { 2.7 - case SDLK_ESCAPE: 2.8 + case SDLK_F12: 2.9 if (event.key.keysym.mod & (KMOD_LALT | KMOD_RALT)) 2.10 - // ALT-ESC pressed; exit emulator 2.11 + // ALT-F12 pressed; exit emulator 2.12 return true; 2.13 break; 2.14 default:
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/src/wd279x.c Sun Dec 05 03:55:46 2010 +0000 3.3 @@ -0,0 +1,375 @@ 3.4 +#include <stdint.h> 3.5 +#include <stdbool.h> 3.6 +#include "wd279x.h" 3.7 + 3.8 + 3.9 +/// WD2797 command constants 3.10 +enum { 3.11 + CMD_MASK = 0xF0, ///< Bit mask to detect command bits 3.12 + CMD_RESTORE = 0x00, ///< Restore (recalibrate, seek to track 0) 3.13 + CMD_SEEK = 0x10, ///< Seek to given track 3.14 + CMD_STEP = 0x20, ///< Step 3.15 + CMD_STEP_TU = 0x30, ///< Step and update track register 3.16 + CMD_STEPIN = 0x40, ///< Step In 3.17 + CMD_STEPIN_TU = 0x50, ///< Step In and update track register 3.18 + CMD_STEPOUT = 0x60, ///< Step Out 3.19 + CMD_STEPOUT_TU = 0x70, ///< Step Out and update track register 3.20 + CMD_READ_SECTOR = 0x80, ///< Read Sector 3.21 + CMD_READ_SECTOR_MULTI = 0x90, ///< Read Multiple Sectors 3.22 + CMD_WRITE_SECTOR = 0xA0, ///< Write Sector 3.23 + CMD_WRITE_SECTOR_MULTI = 0xB0, ///< Write Multiple Sectors 3.24 + CMD_READ_ADDRESS = 0xC0, ///< Read Address (IDAM contents) 3.25 + CMD_FORCE_INTERRUPT = 0xD0, ///< Force Interrupt 3.26 + CMD_READ_TRACK = 0xE0, ///< Read Track 3.27 + CMD_FORMAT_TRACK = 0xF0 ///< Format Track 3.28 +}; 3.29 + 3.30 + 3.31 +/** 3.32 + * @brief Initialise a WD2797 context. 3.33 + */ 3.34 +void wd2797_init(WD279X_CTX *ctx) 3.35 +{ 3.36 + // track, head and sector unknown 3.37 + ctx->track = 0; 3.38 + ctx->head = 0; 3.39 + ctx->sector = 0; 3.40 + 3.41 + // no IRQ or DRQ, no IRQ callback 3.42 + ctx->irql = false; 3.43 + ctx->irqe = false; 3.44 + 3.45 + // no data available 3.46 + ctx->data_pos = 0; 3.47 + ctx->data_len = 0; 3.48 + 3.49 + // No disc image loaded 3.50 + ctx->disc_image = NULL; 3.51 +} 3.52 + 3.53 + 3.54 +/** 3.55 + * @brief Read IRQ Rising Edge status. Clears Rising Edge status if it is set. 3.56 + * @note No more IRQs will be sent until the Status Register is read, or a new command is written to the CR. 3.57 + */ 3.58 +bool wd279x_get_irq(WD279X_CTX *ctx) 3.59 +{ 3.60 + // If an IRQ is pending, clear it and return true, otherwise return false 3.61 + if (ctx->irqe) { 3.62 + ctx->irqe = false; 3.63 + return true; 3.64 + } else { 3.65 + return false; 3.66 + } 3.67 +} 3.68 + 3.69 + 3.70 +/** 3.71 + * @brief Read DRQ status. 3.72 + */ 3.73 +bool wd279x_get_drq(WD279X_CTX *ctx) 3.74 +{ 3.75 + return (ctx->data_pos < ctx->data_len); 3.76 +} 3.77 + 3.78 + 3.79 +/** 3.80 + * @brief Read WD279x register. 3.81 + */ 3.82 +uint8_t wd279x_read_reg(WD279X_CTX *ctx, uint8_t addr) 3.83 +{ 3.84 + uint8_t temp = 0; 3.85 + 3.86 + switch (addr & 0x03) { 3.87 + case WD279X_REG_STATUS: // Status register 3.88 + // Read from status register clears IRQ 3.89 + ctx->irql = false; 3.90 + 3.91 + // Get current status flags (set by last command) 3.92 + temp = ctx->status; 3.93 + // DRQ bit 3.94 + if (ctx->cmd_has_drq) 3.95 + temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 3.96 + // FDC is busy if there is still data in the buffer 3.97 + temp |= (ctx->data_pos < ctx->data_len) ? 0x01 : 0x00; // if data in buffer, then DMA hasn't copied it yet, and we're still busy! 3.98 + // TODO: also if seek delay / read delay hasn't passed (but that's for later) 3.99 + return temp; 3.100 + 3.101 + case WD279X_REG_TRACK: // Track register 3.102 + return ctx->track; 3.103 + 3.104 + case WD279X_REG_SECTOR: // Sector register 3.105 + return ctx->sector; 3.106 + 3.107 + case WD279X_REG_DATA: // Data register 3.108 + // If there's data in the buffer, return it. Otherwise return 0xFF. 3.109 + if (ctx->data_pos < ctx->data_len) { 3.110 + // return data byte and increment pointer 3.111 + return ctx->data[ctx->data_pos++]; 3.112 + } else { 3.113 + return 0xff; 3.114 + } 3.115 + 3.116 + default: 3.117 + // shut up annoying compilers which don't recognise unreachable code when they see it 3.118 + // (here's looking at you, gcc!) 3.119 + return 0xff; 3.120 + } 3.121 +} 3.122 + 3.123 + 3.124 +/** 3.125 + * Write WD279X register 3.126 + */ 3.127 +void wd279x_write_reg(WD279X_CTX *ctx, uint8_t addr, uint8_t val) 3.128 +{ 3.129 + uint8_t cmd = val & CMD_MASK; 3.130 + size_t lba; 3.131 + bool is_type1 = false; 3.132 + 3.133 + switch (addr) { 3.134 + case WD279X_REG_COMMAND: // Command register 3.135 + // write to command register clears interrupt request 3.136 + ctx->irql = false; 3.137 + 3.138 + // Is the drive ready? 3.139 + if (ctx->disc_image == NULL) { 3.140 + // No disc image, thus the drive is busy. 3.141 + ctx->status = 0x80; 3.142 + return; 3.143 + } 3.144 + 3.145 + // Handle Type 1 commands 3.146 + switch (cmd) { 3.147 + case CMD_RESTORE: 3.148 + // Restore. Set track to 0 and throw an IRQ. 3.149 + is_type1 = true; 3.150 + ctx->track = 0; 3.151 + break; 3.152 + 3.153 + case CMD_SEEK: 3.154 + // Seek. Seek to the track specced in the Data Register. 3.155 + is_type1 = true; 3.156 + if (ctx->data_reg < ctx->geom_tracks) { 3.157 + ctx->track = ctx->data_reg; 3.158 + } else { 3.159 + // Seek error. :( 3.160 + ctx->status = 0x10; 3.161 + } 3.162 + 3.163 + case CMD_STEP: 3.164 + // TODO! deal with trk0! 3.165 + // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag. 3.166 + is_type1 = true; 3.167 + break; 3.168 + 3.169 + case CMD_STEPIN: 3.170 + case CMD_STEPOUT: 3.171 + // TODO! deal with trk0! 3.172 + // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag. 3.173 + if (cmd == CMD_STEPIN) { 3.174 + ctx->last_step_dir = 1; 3.175 + } else { 3.176 + ctx->last_step_dir = -1; 3.177 + } 3.178 + is_type1 = true; 3.179 + break; 3.180 + 3.181 + case CMD_STEP_TU: 3.182 + case CMD_STEPIN_TU: 3.183 + case CMD_STEPOUT_TU: 3.184 + // if this is a Step In or Step Out cmd, set the step-direction 3.185 + if (cmd == CMD_STEPIN_TU) { 3.186 + ctx->last_step_dir = 1; 3.187 + } else if (cmd == CMD_STEPOUT_TU) { 3.188 + ctx->last_step_dir = -1; 3.189 + } 3.190 + 3.191 + // Seek one step in the last direction used. 3.192 + ctx->track += ctx->last_step_dir; 3.193 + if (ctx->track < 0) ctx->track = 0; 3.194 + if (ctx->track >= ctx->geom_tracks) { 3.195 + // Seek past end of disc... that'll be a Seek Error then. 3.196 + ctx->status = 0x10; 3.197 + ctx->track = ctx->geom_tracks - 1; 3.198 + } 3.199 + is_type1 = true; 3.200 + break; 3.201 + 3.202 + default: 3.203 + break; 3.204 + } 3.205 + 3.206 + if (is_type1) { 3.207 + // Terminate any sector reads or writes 3.208 + ctx->data_len = ctx->data_pos = 0; 3.209 + 3.210 + // No DRQ bit for these commands. 3.211 + ctx->cmd_has_drq = false; 3.212 + 3.213 + // Type1 status byte... 3.214 + ctx->status = 0; 3.215 + // S7 = Not Ready. Command executed, therefore the drive was ready... :) 3.216 + // S6 = Write Protect. TODO: add this 3.217 + // S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded... 3.218 + ctx->status |= 0x20; 3.219 + // S4 = Seek Error. Not bloody likely if we got down here...! 3.220 + // S3 = CRC Error. Not gonna happen on a disc image! 3.221 + // S2 = Track 0 3.222 + ctx->status |= (ctx->track == 0) ? 0x04 : 0x00; 3.223 + // S1 = Index Pulse. TODO -- need periodics to emulate this 3.224 + // S0 = Busy. We just exec'd the command, thus we're not busy. 3.225 + // TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that. 3.226 + 3.227 + // Set IRQ only if IRQL has been cleared (no pending IRQs) 3.228 + ctx->irqe = !ctx->irql; 3.229 + ctx->irql = true; 3.230 + return; 3.231 + } 3.232 + 3.233 + // That's the Type 1 (seek) commands sorted. Now for the others. 3.234 + 3.235 + // If drive isn't ready, then set status B7 and exit 3.236 + if (ctx->disc_image == NULL) { 3.237 + ctx->status = 0x80; 3.238 + return; 3.239 + } 3.240 + 3.241 + // If this is a Write command, check write protect status too 3.242 + // TODO! 3.243 + if (false) { 3.244 + // Write protected disc... 3.245 + if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) { 3.246 + // Set Write Protect bit and bail. 3.247 + ctx->status = 0x40; 3.248 + 3.249 + // Set IRQ only if IRQL has been cleared (no pending IRQs) 3.250 + ctx->irqe = !ctx->irql; 3.251 + ctx->irql = true; 3.252 + 3.253 + return; 3.254 + } 3.255 + } 3.256 + 3.257 + // Disc is ready to go. Parse the command word. 3.258 + switch (cmd) { 3.259 + case CMD_READ_ADDRESS: 3.260 + // Read Address 3.261 + 3.262 + // reset data pointers 3.263 + ctx->data_pos = ctx->data_len = 0; 3.264 + 3.265 + // load data buffer 3.266 + ctx->data[ctx->data_len++] = ctx->track; 3.267 + ctx->data[ctx->data_len++] = ctx->head; 3.268 + ctx->data[ctx->data_len++] = ctx->sector; 3.269 + switch (ctx->geom_secsz) { 3.270 + case 128: ctx->data[ctx->data_len++] = 0; break; 3.271 + case 256: ctx->data[ctx->data_len++] = 1; break; 3.272 + case 512: ctx->data[ctx->data_len++] = 2; break; 3.273 + case 1024: ctx->data[ctx->data_len++] = 3; break; 3.274 + default: ctx->data[ctx->data_len++] = 0xFF; break; // TODO: deal with invalid values better 3.275 + } 3.276 + ctx->data[ctx->data_len++] = 0; // TODO: IDAM CRC! 3.277 + ctx->data[ctx->data_len++] = 0; 3.278 + 3.279 + // B6, B5 = 0 3.280 + // B4 = Record Not Found. We're not going to see this... FIXME-not emulated 3.281 + // B3 = CRC Error. Not possible. 3.282 + // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 3.283 + // B1 = DRQ. Data request. 3.284 + ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 3.285 + break; 3.286 + 3.287 + case CMD_READ_SECTOR: 3.288 + case CMD_READ_SECTOR_MULTI: 3.289 + // Read Sector or Read Sector Multiple 3.290 + // TODO: multiple is not implemented! 3.291 + if (cmd == CMD_READ_SECTOR_MULTI) printf("WD279X: NOTIMP: read-multi\n"); 3.292 + 3.293 + // reset data pointers 3.294 + ctx->data_pos = ctx->data_len = 0; 3.295 + 3.296 + // Calculate the LBA address of the required sector 3.297 + lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ctx->sector - 1) * ctx->geom_secsz; 3.298 + 3.299 + // Read the sector from the file 3.300 + fseek(ctx->disc_image, lba, SEEK_SET); 3.301 + ctx->data_len = fread(ctx->data, 1, ctx->geom_secsz, ctx->disc_image); 3.302 + // TODO: if datalen < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr) 3.303 + // TODO: if we're asked to do a Read Multi, malloc for an entire track, then just read a full track... 3.304 + 3.305 + // B6 = 0 3.306 + // B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks. 3.307 + // B4 = Record Not Found. We're not going to see this... FIXME-not emulated 3.308 + // B3 = CRC Error. Not possible. 3.309 + // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 3.310 + // B1 = DRQ. Data request. 3.311 + ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 3.312 + break; 3.313 + 3.314 + case CMD_READ_TRACK: 3.315 + // Read Track 3.316 + // B6, B5, B4, B3 = 0 3.317 + // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 3.318 + // B1 = DRQ. Data request. 3.319 + ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 3.320 + break; 3.321 + 3.322 + case CMD_WRITE_SECTOR: 3.323 + case CMD_WRITE_SECTOR_MULTI: 3.324 + // Write Sector or Write Sector Multiple 3.325 + 3.326 + // reset data pointers 3.327 + ctx->data_pos = ctx->data_len = 0; 3.328 + 3.329 + // TODO: set "write pending" flag, and write LBA, and go from there. 3.330 + 3.331 + // B6 = Write Protect. FIXME -- emulate this! 3.332 + // B5 = 0 3.333 + // B4 = Record Not Found. We're not going to see this... FIXME-not emulated 3.334 + // B3 = CRC Error. Not possible. 3.335 + // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 3.336 + // B1 = DRQ. Data request. 3.337 + ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 3.338 + break; 3.339 + 3.340 + case CMD_FORMAT_TRACK: 3.341 + // Write Track (aka Format Track) 3.342 + // B6 = Write Protect. FIXME -- emulate this! 3.343 + // B5, B4, B3 = 0 3.344 + // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 3.345 + // B1 = DRQ. Data request. 3.346 + ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 3.347 + break; 3.348 + 3.349 + case CMD_FORCE_INTERRUPT: 3.350 + // Force Interrupt... 3.351 + // TODO: terminate current R/W op 3.352 + // TODO: variants for this command 3.353 + break; 3.354 + } 3.355 + break; 3.356 + 3.357 + case WD279X_REG_TRACK: // Track register 3.358 + ctx->track = val; 3.359 + break; 3.360 + 3.361 + case WD279X_REG_SECTOR: // Sector register 3.362 + ctx->sector = val; 3.363 + break; 3.364 + 3.365 + case WD279X_REG_DATA: // Data register 3.366 + // Save the value written into the data register 3.367 + ctx->data_reg = val; 3.368 + 3.369 + // If we're processing a write command, and there's space in the 3.370 + // buffer, allow the write. 3.371 + if (ctx->data_pos < ctx->data_len) { 3.372 + // store data byte and increment pointer 3.373 + ctx->data[ctx->data_pos++] = val; 3.374 + } 3.375 + break; 3.376 + } 3.377 +} 3.378 +
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/src/wd279x.h Sun Dec 05 03:55:46 2010 +0000 4.3 @@ -0,0 +1,42 @@ 4.4 +#ifndef _WD279X_H 4.5 +#define _WD279X_H 4.6 + 4.7 +#include <stdbool.h> 4.8 +#include <stddef.h> 4.9 +#include <stdint.h> 4.10 +#include <stdio.h> 4.11 + 4.12 +enum { 4.13 + WD279X_REG_STATUS = 0, 4.14 + WD279X_REG_COMMAND = 0, 4.15 + WD279X_REG_TRACK = 1, 4.16 + WD279X_REG_SECTOR = 2, 4.17 + WD279X_REG_DATA = 3 4.18 +} WD279X_REG; 4.19 + 4.20 +typedef struct { 4.21 + // Current track, head and sector 4.22 + int track, head, sector; 4.23 + // Geometry of current disc 4.24 + int geom_secsz, geom_spt, geom_heads, geom_tracks; 4.25 + // IRQ status, level and edge sensitive. 4.26 + // Edge sensitive is cleared when host polls the IRQ status. 4.27 + // Level sensitive is cleared when emulated CPU polls the status reg or writes a new cmnd. 4.28 + // No EDGE sensitive interrupts will be issued unless the LEVEL SENSITIVE IRQ is clear. 4.29 + bool irql, irqe; 4.30 + // Status of last command 4.31 + uint8_t status; 4.32 + // Last command uses DRQ bit? 4.33 + bool cmd_has_drq; 4.34 + // The last value written to the data register 4.35 + uint8_t data_reg; 4.36 + // Last step direction. -1 for "towards zero", 1 for "away from zero" 4.37 + int last_step_dir; 4.38 + // Data buffer, current DRQ pointer and length 4.39 + uint8_t data[1024]; 4.40 + size_t data_pos, data_len; 4.41 + // Current disc image file 4.42 + FILE *disc_image; 4.43 +} WD279X_CTX; 4.44 + 4.45 +#endif