1.1 diff -r c472ae8f44b2 -r d52688dad7fd src/wd279x.c 1.2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.3 +++ b/src/wd279x.c Sun Dec 05 03:55:46 2010 +0000 1.4 @@ -0,0 +1,375 @@ 1.5 +#include <stdint.h> 1.6 +#include <stdbool.h> 1.7 +#include "wd279x.h" 1.8 + 1.9 + 1.10 +/// WD2797 command constants 1.11 +enum { 1.12 + CMD_MASK = 0xF0, ///< Bit mask to detect command bits 1.13 + CMD_RESTORE = 0x00, ///< Restore (recalibrate, seek to track 0) 1.14 + CMD_SEEK = 0x10, ///< Seek to given track 1.15 + CMD_STEP = 0x20, ///< Step 1.16 + CMD_STEP_TU = 0x30, ///< Step and update track register 1.17 + CMD_STEPIN = 0x40, ///< Step In 1.18 + CMD_STEPIN_TU = 0x50, ///< Step In and update track register 1.19 + CMD_STEPOUT = 0x60, ///< Step Out 1.20 + CMD_STEPOUT_TU = 0x70, ///< Step Out and update track register 1.21 + CMD_READ_SECTOR = 0x80, ///< Read Sector 1.22 + CMD_READ_SECTOR_MULTI = 0x90, ///< Read Multiple Sectors 1.23 + CMD_WRITE_SECTOR = 0xA0, ///< Write Sector 1.24 + CMD_WRITE_SECTOR_MULTI = 0xB0, ///< Write Multiple Sectors 1.25 + CMD_READ_ADDRESS = 0xC0, ///< Read Address (IDAM contents) 1.26 + CMD_FORCE_INTERRUPT = 0xD0, ///< Force Interrupt 1.27 + CMD_READ_TRACK = 0xE0, ///< Read Track 1.28 + CMD_FORMAT_TRACK = 0xF0 ///< Format Track 1.29 +}; 1.30 + 1.31 + 1.32 +/** 1.33 + * @brief Initialise a WD2797 context. 1.34 + */ 1.35 +void wd2797_init(WD279X_CTX *ctx) 1.36 +{ 1.37 + // track, head and sector unknown 1.38 + ctx->track = 0; 1.39 + ctx->head = 0; 1.40 + ctx->sector = 0; 1.41 + 1.42 + // no IRQ or DRQ, no IRQ callback 1.43 + ctx->irql = false; 1.44 + ctx->irqe = false; 1.45 + 1.46 + // no data available 1.47 + ctx->data_pos = 0; 1.48 + ctx->data_len = 0; 1.49 + 1.50 + // No disc image loaded 1.51 + ctx->disc_image = NULL; 1.52 +} 1.53 + 1.54 + 1.55 +/** 1.56 + * @brief Read IRQ Rising Edge status. Clears Rising Edge status if it is set. 1.57 + * @note No more IRQs will be sent until the Status Register is read, or a new command is written to the CR. 1.58 + */ 1.59 +bool wd279x_get_irq(WD279X_CTX *ctx) 1.60 +{ 1.61 + // If an IRQ is pending, clear it and return true, otherwise return false 1.62 + if (ctx->irqe) { 1.63 + ctx->irqe = false; 1.64 + return true; 1.65 + } else { 1.66 + return false; 1.67 + } 1.68 +} 1.69 + 1.70 + 1.71 +/** 1.72 + * @brief Read DRQ status. 1.73 + */ 1.74 +bool wd279x_get_drq(WD279X_CTX *ctx) 1.75 +{ 1.76 + return (ctx->data_pos < ctx->data_len); 1.77 +} 1.78 + 1.79 + 1.80 +/** 1.81 + * @brief Read WD279x register. 1.82 + */ 1.83 +uint8_t wd279x_read_reg(WD279X_CTX *ctx, uint8_t addr) 1.84 +{ 1.85 + uint8_t temp = 0; 1.86 + 1.87 + switch (addr & 0x03) { 1.88 + case WD279X_REG_STATUS: // Status register 1.89 + // Read from status register clears IRQ 1.90 + ctx->irql = false; 1.91 + 1.92 + // Get current status flags (set by last command) 1.93 + temp = ctx->status; 1.94 + // DRQ bit 1.95 + if (ctx->cmd_has_drq) 1.96 + temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 1.97 + // FDC is busy if there is still data in the buffer 1.98 + 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! 1.99 + // TODO: also if seek delay / read delay hasn't passed (but that's for later) 1.100 + return temp; 1.101 + 1.102 + case WD279X_REG_TRACK: // Track register 1.103 + return ctx->track; 1.104 + 1.105 + case WD279X_REG_SECTOR: // Sector register 1.106 + return ctx->sector; 1.107 + 1.108 + case WD279X_REG_DATA: // Data register 1.109 + // If there's data in the buffer, return it. Otherwise return 0xFF. 1.110 + if (ctx->data_pos < ctx->data_len) { 1.111 + // return data byte and increment pointer 1.112 + return ctx->data[ctx->data_pos++]; 1.113 + } else { 1.114 + return 0xff; 1.115 + } 1.116 + 1.117 + default: 1.118 + // shut up annoying compilers which don't recognise unreachable code when they see it 1.119 + // (here's looking at you, gcc!) 1.120 + return 0xff; 1.121 + } 1.122 +} 1.123 + 1.124 + 1.125 +/** 1.126 + * Write WD279X register 1.127 + */ 1.128 +void wd279x_write_reg(WD279X_CTX *ctx, uint8_t addr, uint8_t val) 1.129 +{ 1.130 + uint8_t cmd = val & CMD_MASK; 1.131 + size_t lba; 1.132 + bool is_type1 = false; 1.133 + 1.134 + switch (addr) { 1.135 + case WD279X_REG_COMMAND: // Command register 1.136 + // write to command register clears interrupt request 1.137 + ctx->irql = false; 1.138 + 1.139 + // Is the drive ready? 1.140 + if (ctx->disc_image == NULL) { 1.141 + // No disc image, thus the drive is busy. 1.142 + ctx->status = 0x80; 1.143 + return; 1.144 + } 1.145 + 1.146 + // Handle Type 1 commands 1.147 + switch (cmd) { 1.148 + case CMD_RESTORE: 1.149 + // Restore. Set track to 0 and throw an IRQ. 1.150 + is_type1 = true; 1.151 + ctx->track = 0; 1.152 + break; 1.153 + 1.154 + case CMD_SEEK: 1.155 + // Seek. Seek to the track specced in the Data Register. 1.156 + is_type1 = true; 1.157 + if (ctx->data_reg < ctx->geom_tracks) { 1.158 + ctx->track = ctx->data_reg; 1.159 + } else { 1.160 + // Seek error. :( 1.161 + ctx->status = 0x10; 1.162 + } 1.163 + 1.164 + case CMD_STEP: 1.165 + // TODO! deal with trk0! 1.166 + // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag. 1.167 + is_type1 = true; 1.168 + break; 1.169 + 1.170 + case CMD_STEPIN: 1.171 + case CMD_STEPOUT: 1.172 + // TODO! deal with trk0! 1.173 + // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag. 1.174 + if (cmd == CMD_STEPIN) { 1.175 + ctx->last_step_dir = 1; 1.176 + } else { 1.177 + ctx->last_step_dir = -1; 1.178 + } 1.179 + is_type1 = true; 1.180 + break; 1.181 + 1.182 + case CMD_STEP_TU: 1.183 + case CMD_STEPIN_TU: 1.184 + case CMD_STEPOUT_TU: 1.185 + // if this is a Step In or Step Out cmd, set the step-direction 1.186 + if (cmd == CMD_STEPIN_TU) { 1.187 + ctx->last_step_dir = 1; 1.188 + } else if (cmd == CMD_STEPOUT_TU) { 1.189 + ctx->last_step_dir = -1; 1.190 + } 1.191 + 1.192 + // Seek one step in the last direction used. 1.193 + ctx->track += ctx->last_step_dir; 1.194 + if (ctx->track < 0) ctx->track = 0; 1.195 + if (ctx->track >= ctx->geom_tracks) { 1.196 + // Seek past end of disc... that'll be a Seek Error then. 1.197 + ctx->status = 0x10; 1.198 + ctx->track = ctx->geom_tracks - 1; 1.199 + } 1.200 + is_type1 = true; 1.201 + break; 1.202 + 1.203 + default: 1.204 + break; 1.205 + } 1.206 + 1.207 + if (is_type1) { 1.208 + // Terminate any sector reads or writes 1.209 + ctx->data_len = ctx->data_pos = 0; 1.210 + 1.211 + // No DRQ bit for these commands. 1.212 + ctx->cmd_has_drq = false; 1.213 + 1.214 + // Type1 status byte... 1.215 + ctx->status = 0; 1.216 + // S7 = Not Ready. Command executed, therefore the drive was ready... :) 1.217 + // S6 = Write Protect. TODO: add this 1.218 + // S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded... 1.219 + ctx->status |= 0x20; 1.220 + // S4 = Seek Error. Not bloody likely if we got down here...! 1.221 + // S3 = CRC Error. Not gonna happen on a disc image! 1.222 + // S2 = Track 0 1.223 + ctx->status |= (ctx->track == 0) ? 0x04 : 0x00; 1.224 + // S1 = Index Pulse. TODO -- need periodics to emulate this 1.225 + // S0 = Busy. We just exec'd the command, thus we're not busy. 1.226 + // TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that. 1.227 + 1.228 + // Set IRQ only if IRQL has been cleared (no pending IRQs) 1.229 + ctx->irqe = !ctx->irql; 1.230 + ctx->irql = true; 1.231 + return; 1.232 + } 1.233 + 1.234 + // That's the Type 1 (seek) commands sorted. Now for the others. 1.235 + 1.236 + // If drive isn't ready, then set status B7 and exit 1.237 + if (ctx->disc_image == NULL) { 1.238 + ctx->status = 0x80; 1.239 + return; 1.240 + } 1.241 + 1.242 + // If this is a Write command, check write protect status too 1.243 + // TODO! 1.244 + if (false) { 1.245 + // Write protected disc... 1.246 + if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) { 1.247 + // Set Write Protect bit and bail. 1.248 + ctx->status = 0x40; 1.249 + 1.250 + // Set IRQ only if IRQL has been cleared (no pending IRQs) 1.251 + ctx->irqe = !ctx->irql; 1.252 + ctx->irql = true; 1.253 + 1.254 + return; 1.255 + } 1.256 + } 1.257 + 1.258 + // Disc is ready to go. Parse the command word. 1.259 + switch (cmd) { 1.260 + case CMD_READ_ADDRESS: 1.261 + // Read Address 1.262 + 1.263 + // reset data pointers 1.264 + ctx->data_pos = ctx->data_len = 0; 1.265 + 1.266 + // load data buffer 1.267 + ctx->data[ctx->data_len++] = ctx->track; 1.268 + ctx->data[ctx->data_len++] = ctx->head; 1.269 + ctx->data[ctx->data_len++] = ctx->sector; 1.270 + switch (ctx->geom_secsz) { 1.271 + case 128: ctx->data[ctx->data_len++] = 0; break; 1.272 + case 256: ctx->data[ctx->data_len++] = 1; break; 1.273 + case 512: ctx->data[ctx->data_len++] = 2; break; 1.274 + case 1024: ctx->data[ctx->data_len++] = 3; break; 1.275 + default: ctx->data[ctx->data_len++] = 0xFF; break; // TODO: deal with invalid values better 1.276 + } 1.277 + ctx->data[ctx->data_len++] = 0; // TODO: IDAM CRC! 1.278 + ctx->data[ctx->data_len++] = 0; 1.279 + 1.280 + // B6, B5 = 0 1.281 + // B4 = Record Not Found. We're not going to see this... FIXME-not emulated 1.282 + // B3 = CRC Error. Not possible. 1.283 + // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 1.284 + // B1 = DRQ. Data request. 1.285 + ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 1.286 + break; 1.287 + 1.288 + case CMD_READ_SECTOR: 1.289 + case CMD_READ_SECTOR_MULTI: 1.290 + // Read Sector or Read Sector Multiple 1.291 + // TODO: multiple is not implemented! 1.292 + if (cmd == CMD_READ_SECTOR_MULTI) printf("WD279X: NOTIMP: read-multi\n"); 1.293 + 1.294 + // reset data pointers 1.295 + ctx->data_pos = ctx->data_len = 0; 1.296 + 1.297 + // Calculate the LBA address of the required sector 1.298 + lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ctx->sector - 1) * ctx->geom_secsz; 1.299 + 1.300 + // Read the sector from the file 1.301 + fseek(ctx->disc_image, lba, SEEK_SET); 1.302 + ctx->data_len = fread(ctx->data, 1, ctx->geom_secsz, ctx->disc_image); 1.303 + // TODO: if datalen < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr) 1.304 + // TODO: if we're asked to do a Read Multi, malloc for an entire track, then just read a full track... 1.305 + 1.306 + // B6 = 0 1.307 + // B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks. 1.308 + // B4 = Record Not Found. We're not going to see this... FIXME-not emulated 1.309 + // B3 = CRC Error. Not possible. 1.310 + // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 1.311 + // B1 = DRQ. Data request. 1.312 + ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 1.313 + break; 1.314 + 1.315 + case CMD_READ_TRACK: 1.316 + // Read Track 1.317 + // B6, B5, B4, B3 = 0 1.318 + // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 1.319 + // B1 = DRQ. Data request. 1.320 + ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 1.321 + break; 1.322 + 1.323 + case CMD_WRITE_SECTOR: 1.324 + case CMD_WRITE_SECTOR_MULTI: 1.325 + // Write Sector or Write Sector Multiple 1.326 + 1.327 + // reset data pointers 1.328 + ctx->data_pos = ctx->data_len = 0; 1.329 + 1.330 + // TODO: set "write pending" flag, and write LBA, and go from there. 1.331 + 1.332 + // B6 = Write Protect. FIXME -- emulate this! 1.333 + // B5 = 0 1.334 + // B4 = Record Not Found. We're not going to see this... FIXME-not emulated 1.335 + // B3 = CRC Error. Not possible. 1.336 + // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 1.337 + // B1 = DRQ. Data request. 1.338 + ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 1.339 + break; 1.340 + 1.341 + case CMD_FORMAT_TRACK: 1.342 + // Write Track (aka Format Track) 1.343 + // B6 = Write Protect. FIXME -- emulate this! 1.344 + // B5, B4, B3 = 0 1.345 + // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 1.346 + // B1 = DRQ. Data request. 1.347 + ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 1.348 + break; 1.349 + 1.350 + case CMD_FORCE_INTERRUPT: 1.351 + // Force Interrupt... 1.352 + // TODO: terminate current R/W op 1.353 + // TODO: variants for this command 1.354 + break; 1.355 + } 1.356 + break; 1.357 + 1.358 + case WD279X_REG_TRACK: // Track register 1.359 + ctx->track = val; 1.360 + break; 1.361 + 1.362 + case WD279X_REG_SECTOR: // Sector register 1.363 + ctx->sector = val; 1.364 + break; 1.365 + 1.366 + case WD279X_REG_DATA: // Data register 1.367 + // Save the value written into the data register 1.368 + ctx->data_reg = val; 1.369 + 1.370 + // If we're processing a write command, and there's space in the 1.371 + // buffer, allow the write. 1.372 + if (ctx->data_pos < ctx->data_len) { 1.373 + // store data byte and increment pointer 1.374 + ctx->data[ctx->data_pos++] = val; 1.375 + } 1.376 + break; 1.377 + } 1.378 +} 1.379 +