src/wd279x.c

Sun, 05 Dec 2010 03:55:46 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Sun, 05 Dec 2010 03:55:46 +0000
changeset 48
d52688dad7fd
child 49
545798274dad
permissions
-rw-r--r--

add preliminary WD2797 FDC emulator

philpem@48 1 #include <stdint.h>
philpem@48 2 #include <stdbool.h>
philpem@48 3 #include "wd279x.h"
philpem@48 4
philpem@48 5
philpem@48 6 /// WD2797 command constants
philpem@48 7 enum {
philpem@48 8 CMD_MASK = 0xF0, ///< Bit mask to detect command bits
philpem@48 9 CMD_RESTORE = 0x00, ///< Restore (recalibrate, seek to track 0)
philpem@48 10 CMD_SEEK = 0x10, ///< Seek to given track
philpem@48 11 CMD_STEP = 0x20, ///< Step
philpem@48 12 CMD_STEP_TU = 0x30, ///< Step and update track register
philpem@48 13 CMD_STEPIN = 0x40, ///< Step In
philpem@48 14 CMD_STEPIN_TU = 0x50, ///< Step In and update track register
philpem@48 15 CMD_STEPOUT = 0x60, ///< Step Out
philpem@48 16 CMD_STEPOUT_TU = 0x70, ///< Step Out and update track register
philpem@48 17 CMD_READ_SECTOR = 0x80, ///< Read Sector
philpem@48 18 CMD_READ_SECTOR_MULTI = 0x90, ///< Read Multiple Sectors
philpem@48 19 CMD_WRITE_SECTOR = 0xA0, ///< Write Sector
philpem@48 20 CMD_WRITE_SECTOR_MULTI = 0xB0, ///< Write Multiple Sectors
philpem@48 21 CMD_READ_ADDRESS = 0xC0, ///< Read Address (IDAM contents)
philpem@48 22 CMD_FORCE_INTERRUPT = 0xD0, ///< Force Interrupt
philpem@48 23 CMD_READ_TRACK = 0xE0, ///< Read Track
philpem@48 24 CMD_FORMAT_TRACK = 0xF0 ///< Format Track
philpem@48 25 };
philpem@48 26
philpem@48 27
philpem@48 28 /**
philpem@48 29 * @brief Initialise a WD2797 context.
philpem@48 30 */
philpem@48 31 void wd2797_init(WD279X_CTX *ctx)
philpem@48 32 {
philpem@48 33 // track, head and sector unknown
philpem@48 34 ctx->track = 0;
philpem@48 35 ctx->head = 0;
philpem@48 36 ctx->sector = 0;
philpem@48 37
philpem@48 38 // no IRQ or DRQ, no IRQ callback
philpem@48 39 ctx->irql = false;
philpem@48 40 ctx->irqe = false;
philpem@48 41
philpem@48 42 // no data available
philpem@48 43 ctx->data_pos = 0;
philpem@48 44 ctx->data_len = 0;
philpem@48 45
philpem@48 46 // No disc image loaded
philpem@48 47 ctx->disc_image = NULL;
philpem@48 48 }
philpem@48 49
philpem@48 50
philpem@48 51 /**
philpem@48 52 * @brief Read IRQ Rising Edge status. Clears Rising Edge status if it is set.
philpem@48 53 * @note No more IRQs will be sent until the Status Register is read, or a new command is written to the CR.
philpem@48 54 */
philpem@48 55 bool wd279x_get_irq(WD279X_CTX *ctx)
philpem@48 56 {
philpem@48 57 // If an IRQ is pending, clear it and return true, otherwise return false
philpem@48 58 if (ctx->irqe) {
philpem@48 59 ctx->irqe = false;
philpem@48 60 return true;
philpem@48 61 } else {
philpem@48 62 return false;
philpem@48 63 }
philpem@48 64 }
philpem@48 65
philpem@48 66
philpem@48 67 /**
philpem@48 68 * @brief Read DRQ status.
philpem@48 69 */
philpem@48 70 bool wd279x_get_drq(WD279X_CTX *ctx)
philpem@48 71 {
philpem@48 72 return (ctx->data_pos < ctx->data_len);
philpem@48 73 }
philpem@48 74
philpem@48 75
philpem@48 76 /**
philpem@48 77 * @brief Read WD279x register.
philpem@48 78 */
philpem@48 79 uint8_t wd279x_read_reg(WD279X_CTX *ctx, uint8_t addr)
philpem@48 80 {
philpem@48 81 uint8_t temp = 0;
philpem@48 82
philpem@48 83 switch (addr & 0x03) {
philpem@48 84 case WD279X_REG_STATUS: // Status register
philpem@48 85 // Read from status register clears IRQ
philpem@48 86 ctx->irql = false;
philpem@48 87
philpem@48 88 // Get current status flags (set by last command)
philpem@48 89 temp = ctx->status;
philpem@48 90 // DRQ bit
philpem@48 91 if (ctx->cmd_has_drq)
philpem@48 92 temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
philpem@48 93 // FDC is busy if there is still data in the buffer
philpem@48 94 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!
philpem@48 95 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
philpem@48 96 return temp;
philpem@48 97
philpem@48 98 case WD279X_REG_TRACK: // Track register
philpem@48 99 return ctx->track;
philpem@48 100
philpem@48 101 case WD279X_REG_SECTOR: // Sector register
philpem@48 102 return ctx->sector;
philpem@48 103
philpem@48 104 case WD279X_REG_DATA: // Data register
philpem@48 105 // If there's data in the buffer, return it. Otherwise return 0xFF.
philpem@48 106 if (ctx->data_pos < ctx->data_len) {
philpem@48 107 // return data byte and increment pointer
philpem@48 108 return ctx->data[ctx->data_pos++];
philpem@48 109 } else {
philpem@48 110 return 0xff;
philpem@48 111 }
philpem@48 112
philpem@48 113 default:
philpem@48 114 // shut up annoying compilers which don't recognise unreachable code when they see it
philpem@48 115 // (here's looking at you, gcc!)
philpem@48 116 return 0xff;
philpem@48 117 }
philpem@48 118 }
philpem@48 119
philpem@48 120
philpem@48 121 /**
philpem@48 122 * Write WD279X register
philpem@48 123 */
philpem@48 124 void wd279x_write_reg(WD279X_CTX *ctx, uint8_t addr, uint8_t val)
philpem@48 125 {
philpem@48 126 uint8_t cmd = val & CMD_MASK;
philpem@48 127 size_t lba;
philpem@48 128 bool is_type1 = false;
philpem@48 129
philpem@48 130 switch (addr) {
philpem@48 131 case WD279X_REG_COMMAND: // Command register
philpem@48 132 // write to command register clears interrupt request
philpem@48 133 ctx->irql = false;
philpem@48 134
philpem@48 135 // Is the drive ready?
philpem@48 136 if (ctx->disc_image == NULL) {
philpem@48 137 // No disc image, thus the drive is busy.
philpem@48 138 ctx->status = 0x80;
philpem@48 139 return;
philpem@48 140 }
philpem@48 141
philpem@48 142 // Handle Type 1 commands
philpem@48 143 switch (cmd) {
philpem@48 144 case CMD_RESTORE:
philpem@48 145 // Restore. Set track to 0 and throw an IRQ.
philpem@48 146 is_type1 = true;
philpem@48 147 ctx->track = 0;
philpem@48 148 break;
philpem@48 149
philpem@48 150 case CMD_SEEK:
philpem@48 151 // Seek. Seek to the track specced in the Data Register.
philpem@48 152 is_type1 = true;
philpem@48 153 if (ctx->data_reg < ctx->geom_tracks) {
philpem@48 154 ctx->track = ctx->data_reg;
philpem@48 155 } else {
philpem@48 156 // Seek error. :(
philpem@48 157 ctx->status = 0x10;
philpem@48 158 }
philpem@48 159
philpem@48 160 case CMD_STEP:
philpem@48 161 // TODO! deal with trk0!
philpem@48 162 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
philpem@48 163 is_type1 = true;
philpem@48 164 break;
philpem@48 165
philpem@48 166 case CMD_STEPIN:
philpem@48 167 case CMD_STEPOUT:
philpem@48 168 // TODO! deal with trk0!
philpem@48 169 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
philpem@48 170 if (cmd == CMD_STEPIN) {
philpem@48 171 ctx->last_step_dir = 1;
philpem@48 172 } else {
philpem@48 173 ctx->last_step_dir = -1;
philpem@48 174 }
philpem@48 175 is_type1 = true;
philpem@48 176 break;
philpem@48 177
philpem@48 178 case CMD_STEP_TU:
philpem@48 179 case CMD_STEPIN_TU:
philpem@48 180 case CMD_STEPOUT_TU:
philpem@48 181 // if this is a Step In or Step Out cmd, set the step-direction
philpem@48 182 if (cmd == CMD_STEPIN_TU) {
philpem@48 183 ctx->last_step_dir = 1;
philpem@48 184 } else if (cmd == CMD_STEPOUT_TU) {
philpem@48 185 ctx->last_step_dir = -1;
philpem@48 186 }
philpem@48 187
philpem@48 188 // Seek one step in the last direction used.
philpem@48 189 ctx->track += ctx->last_step_dir;
philpem@48 190 if (ctx->track < 0) ctx->track = 0;
philpem@48 191 if (ctx->track >= ctx->geom_tracks) {
philpem@48 192 // Seek past end of disc... that'll be a Seek Error then.
philpem@48 193 ctx->status = 0x10;
philpem@48 194 ctx->track = ctx->geom_tracks - 1;
philpem@48 195 }
philpem@48 196 is_type1 = true;
philpem@48 197 break;
philpem@48 198
philpem@48 199 default:
philpem@48 200 break;
philpem@48 201 }
philpem@48 202
philpem@48 203 if (is_type1) {
philpem@48 204 // Terminate any sector reads or writes
philpem@48 205 ctx->data_len = ctx->data_pos = 0;
philpem@48 206
philpem@48 207 // No DRQ bit for these commands.
philpem@48 208 ctx->cmd_has_drq = false;
philpem@48 209
philpem@48 210 // Type1 status byte...
philpem@48 211 ctx->status = 0;
philpem@48 212 // S7 = Not Ready. Command executed, therefore the drive was ready... :)
philpem@48 213 // S6 = Write Protect. TODO: add this
philpem@48 214 // S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
philpem@48 215 ctx->status |= 0x20;
philpem@48 216 // S4 = Seek Error. Not bloody likely if we got down here...!
philpem@48 217 // S3 = CRC Error. Not gonna happen on a disc image!
philpem@48 218 // S2 = Track 0
philpem@48 219 ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
philpem@48 220 // S1 = Index Pulse. TODO -- need periodics to emulate this
philpem@48 221 // S0 = Busy. We just exec'd the command, thus we're not busy.
philpem@48 222 // TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
philpem@48 223
philpem@48 224 // Set IRQ only if IRQL has been cleared (no pending IRQs)
philpem@48 225 ctx->irqe = !ctx->irql;
philpem@48 226 ctx->irql = true;
philpem@48 227 return;
philpem@48 228 }
philpem@48 229
philpem@48 230 // That's the Type 1 (seek) commands sorted. Now for the others.
philpem@48 231
philpem@48 232 // If drive isn't ready, then set status B7 and exit
philpem@48 233 if (ctx->disc_image == NULL) {
philpem@48 234 ctx->status = 0x80;
philpem@48 235 return;
philpem@48 236 }
philpem@48 237
philpem@48 238 // If this is a Write command, check write protect status too
philpem@48 239 // TODO!
philpem@48 240 if (false) {
philpem@48 241 // Write protected disc...
philpem@48 242 if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
philpem@48 243 // Set Write Protect bit and bail.
philpem@48 244 ctx->status = 0x40;
philpem@48 245
philpem@48 246 // Set IRQ only if IRQL has been cleared (no pending IRQs)
philpem@48 247 ctx->irqe = !ctx->irql;
philpem@48 248 ctx->irql = true;
philpem@48 249
philpem@48 250 return;
philpem@48 251 }
philpem@48 252 }
philpem@48 253
philpem@48 254 // Disc is ready to go. Parse the command word.
philpem@48 255 switch (cmd) {
philpem@48 256 case CMD_READ_ADDRESS:
philpem@48 257 // Read Address
philpem@48 258
philpem@48 259 // reset data pointers
philpem@48 260 ctx->data_pos = ctx->data_len = 0;
philpem@48 261
philpem@48 262 // load data buffer
philpem@48 263 ctx->data[ctx->data_len++] = ctx->track;
philpem@48 264 ctx->data[ctx->data_len++] = ctx->head;
philpem@48 265 ctx->data[ctx->data_len++] = ctx->sector;
philpem@48 266 switch (ctx->geom_secsz) {
philpem@48 267 case 128: ctx->data[ctx->data_len++] = 0; break;
philpem@48 268 case 256: ctx->data[ctx->data_len++] = 1; break;
philpem@48 269 case 512: ctx->data[ctx->data_len++] = 2; break;
philpem@48 270 case 1024: ctx->data[ctx->data_len++] = 3; break;
philpem@48 271 default: ctx->data[ctx->data_len++] = 0xFF; break; // TODO: deal with invalid values better
philpem@48 272 }
philpem@48 273 ctx->data[ctx->data_len++] = 0; // TODO: IDAM CRC!
philpem@48 274 ctx->data[ctx->data_len++] = 0;
philpem@48 275
philpem@48 276 // B6, B5 = 0
philpem@48 277 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
philpem@48 278 // B3 = CRC Error. Not possible.
philpem@48 279 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
philpem@48 280 // B1 = DRQ. Data request.
philpem@48 281 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
philpem@48 282 break;
philpem@48 283
philpem@48 284 case CMD_READ_SECTOR:
philpem@48 285 case CMD_READ_SECTOR_MULTI:
philpem@48 286 // Read Sector or Read Sector Multiple
philpem@48 287 // TODO: multiple is not implemented!
philpem@48 288 if (cmd == CMD_READ_SECTOR_MULTI) printf("WD279X: NOTIMP: read-multi\n");
philpem@48 289
philpem@48 290 // reset data pointers
philpem@48 291 ctx->data_pos = ctx->data_len = 0;
philpem@48 292
philpem@48 293 // Calculate the LBA address of the required sector
philpem@48 294 lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ctx->sector - 1) * ctx->geom_secsz;
philpem@48 295
philpem@48 296 // Read the sector from the file
philpem@48 297 fseek(ctx->disc_image, lba, SEEK_SET);
philpem@48 298 ctx->data_len = fread(ctx->data, 1, ctx->geom_secsz, ctx->disc_image);
philpem@48 299 // TODO: if datalen < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
philpem@48 300 // TODO: if we're asked to do a Read Multi, malloc for an entire track, then just read a full track...
philpem@48 301
philpem@48 302 // B6 = 0
philpem@48 303 // B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
philpem@48 304 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
philpem@48 305 // B3 = CRC Error. Not possible.
philpem@48 306 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
philpem@48 307 // B1 = DRQ. Data request.
philpem@48 308 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
philpem@48 309 break;
philpem@48 310
philpem@48 311 case CMD_READ_TRACK:
philpem@48 312 // Read Track
philpem@48 313 // B6, B5, B4, B3 = 0
philpem@48 314 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
philpem@48 315 // B1 = DRQ. Data request.
philpem@48 316 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
philpem@48 317 break;
philpem@48 318
philpem@48 319 case CMD_WRITE_SECTOR:
philpem@48 320 case CMD_WRITE_SECTOR_MULTI:
philpem@48 321 // Write Sector or Write Sector Multiple
philpem@48 322
philpem@48 323 // reset data pointers
philpem@48 324 ctx->data_pos = ctx->data_len = 0;
philpem@48 325
philpem@48 326 // TODO: set "write pending" flag, and write LBA, and go from there.
philpem@48 327
philpem@48 328 // B6 = Write Protect. FIXME -- emulate this!
philpem@48 329 // B5 = 0
philpem@48 330 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
philpem@48 331 // B3 = CRC Error. Not possible.
philpem@48 332 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
philpem@48 333 // B1 = DRQ. Data request.
philpem@48 334 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
philpem@48 335 break;
philpem@48 336
philpem@48 337 case CMD_FORMAT_TRACK:
philpem@48 338 // Write Track (aka Format Track)
philpem@48 339 // B6 = Write Protect. FIXME -- emulate this!
philpem@48 340 // B5, B4, B3 = 0
philpem@48 341 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
philpem@48 342 // B1 = DRQ. Data request.
philpem@48 343 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
philpem@48 344 break;
philpem@48 345
philpem@48 346 case CMD_FORCE_INTERRUPT:
philpem@48 347 // Force Interrupt...
philpem@48 348 // TODO: terminate current R/W op
philpem@48 349 // TODO: variants for this command
philpem@48 350 break;
philpem@48 351 }
philpem@48 352 break;
philpem@48 353
philpem@48 354 case WD279X_REG_TRACK: // Track register
philpem@48 355 ctx->track = val;
philpem@48 356 break;
philpem@48 357
philpem@48 358 case WD279X_REG_SECTOR: // Sector register
philpem@48 359 ctx->sector = val;
philpem@48 360 break;
philpem@48 361
philpem@48 362 case WD279X_REG_DATA: // Data register
philpem@48 363 // Save the value written into the data register
philpem@48 364 ctx->data_reg = val;
philpem@48 365
philpem@48 366 // If we're processing a write command, and there's space in the
philpem@48 367 // buffer, allow the write.
philpem@48 368 if (ctx->data_pos < ctx->data_len) {
philpem@48 369 // store data byte and increment pointer
philpem@48 370 ctx->data[ctx->data_pos++] = val;
philpem@48 371 }
philpem@48 372 break;
philpem@48 373 }
philpem@48 374 }
philpem@48 375