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