Sat, 17 Nov 2012 19:18:29 +0000
add HDD support + fixes
Patch-Author: Andrew Warkentin <andreww591!gmail>
Patch-Message-ID: <50A772FC.8020009@gmail.com>
I have added floppy write support, full hard disk emulation, and proper handling of DMA page faults to FreeBee. I also fixed the floppy step commands, changed the "force interrupt" floppy command to generate a type 1 status, and changed the DMA address counter to reset to 3fff when a transfer completes (which is what Unix seems to expect - without it, the kernel says that the floppy isn't ready). The floppy, hard disk, and DMA page fault tests all pass. Initializing hard disks and floppies also works (the geometry for both is still fixed by the size of the image, though, and format commands only appear to succeed, but they don't modify the image). Unix still doesn't run, though (it hangs after reading some sectors from the floppy).
philpem@48 | 1 | #include <stdint.h> |
philpem@48 | 2 | #include <stdbool.h> |
philpem@49 | 3 | #include <malloc.h> |
philpem@53 | 4 | #include "musashi/m68k.h" |
philpem@48 | 5 | #include "wd279x.h" |
philpem@48 | 6 | |
philpem@73 | 7 | #ifndef WD279X_DEBUG |
philpem@71 | 8 | #define NDEBUG |
philpem@73 | 9 | #endif |
philpem@71 | 10 | #include "utils.h" |
philpem@71 | 11 | |
philpem@48 | 12 | /// WD2797 command constants |
philpem@48 | 13 | enum { |
philpem@48 | 14 | CMD_MASK = 0xF0, ///< Bit mask to detect command bits |
philpem@48 | 15 | CMD_RESTORE = 0x00, ///< Restore (recalibrate, seek to track 0) |
philpem@48 | 16 | CMD_SEEK = 0x10, ///< Seek to given track |
philpem@48 | 17 | CMD_STEP = 0x20, ///< Step |
philpem@48 | 18 | CMD_STEP_TU = 0x30, ///< Step and update track register |
philpem@48 | 19 | CMD_STEPIN = 0x40, ///< Step In |
philpem@48 | 20 | CMD_STEPIN_TU = 0x50, ///< Step In and update track register |
philpem@48 | 21 | CMD_STEPOUT = 0x60, ///< Step Out |
philpem@48 | 22 | CMD_STEPOUT_TU = 0x70, ///< Step Out and update track register |
philpem@48 | 23 | CMD_READ_SECTOR = 0x80, ///< Read Sector |
philpem@48 | 24 | CMD_READ_SECTOR_MULTI = 0x90, ///< Read Multiple Sectors |
philpem@48 | 25 | CMD_WRITE_SECTOR = 0xA0, ///< Write Sector |
philpem@48 | 26 | CMD_WRITE_SECTOR_MULTI = 0xB0, ///< Write Multiple Sectors |
philpem@48 | 27 | CMD_READ_ADDRESS = 0xC0, ///< Read Address (IDAM contents) |
philpem@48 | 28 | CMD_FORCE_INTERRUPT = 0xD0, ///< Force Interrupt |
philpem@48 | 29 | CMD_READ_TRACK = 0xE0, ///< Read Track |
philpem@48 | 30 | CMD_FORMAT_TRACK = 0xF0 ///< Format Track |
philpem@48 | 31 | }; |
philpem@48 | 32 | |
philpem@48 | 33 | |
philpem@49 | 34 | void wd2797_init(WD2797_CTX *ctx) |
philpem@48 | 35 | { |
philpem@48 | 36 | // track, head and sector unknown |
philpem@49 | 37 | ctx->track = ctx->head = ctx->sector = 0; |
philpem@111 | 38 | ctx->track_reg = 0; |
philpem@48 | 39 | |
philpem@49 | 40 | // no IRQ pending |
philpem@76 | 41 | ctx->irq = false; |
philpem@48 | 42 | |
philpem@48 | 43 | // no data available |
philpem@49 | 44 | ctx->data_pos = ctx->data_len = 0; |
philpem@49 | 45 | ctx->data = NULL; |
philpem@49 | 46 | |
philpem@76 | 47 | // Status register clear, not busy; type1 command |
philpem@49 | 48 | ctx->status = 0; |
philpem@76 | 49 | ctx->cmd_has_drq = false; |
philpem@49 | 50 | |
philpem@111 | 51 | // No format command in progress |
philpem@111 | 52 | ctx->formatting = false; |
philpem@111 | 53 | |
philpem@49 | 54 | // Clear data register |
philpem@49 | 55 | ctx->data_reg = 0; |
philpem@49 | 56 | |
philpem@76 | 57 | // Last step direction = "towards zero" |
philpem@49 | 58 | ctx->last_step_dir = -1; |
philpem@48 | 59 | |
philpem@48 | 60 | // No disc image loaded |
philpem@48 | 61 | ctx->disc_image = NULL; |
philpem@49 | 62 | ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0; |
philpem@48 | 63 | } |
philpem@48 | 64 | |
philpem@48 | 65 | |
philpem@49 | 66 | void wd2797_reset(WD2797_CTX *ctx) |
philpem@49 | 67 | { |
philpem@49 | 68 | // track, head and sector unknown |
philpem@49 | 69 | ctx->track = ctx->head = ctx->sector = 0; |
philpem@111 | 70 | ctx->track_reg = 0; |
philpem@49 | 71 | |
philpem@49 | 72 | // no IRQ pending |
philpem@76 | 73 | ctx->irq = false; |
philpem@49 | 74 | |
philpem@49 | 75 | // no data available |
philpem@49 | 76 | ctx->data_pos = ctx->data_len = 0; |
philpem@49 | 77 | |
philpem@49 | 78 | // Status register clear, not busy |
philpem@49 | 79 | ctx->status = 0; |
philpem@49 | 80 | |
philpem@49 | 81 | // Clear data register |
philpem@49 | 82 | ctx->data_reg = 0; |
philpem@49 | 83 | |
philpem@49 | 84 | // Last step direction |
philpem@49 | 85 | ctx->last_step_dir = -1; |
philpem@49 | 86 | } |
philpem@49 | 87 | |
philpem@49 | 88 | |
philpem@49 | 89 | void wd2797_done(WD2797_CTX *ctx) |
philpem@49 | 90 | { |
philpem@49 | 91 | // Reset the WD2797 |
philpem@49 | 92 | wd2797_reset(ctx); |
philpem@49 | 93 | |
philpem@49 | 94 | // Free any allocated memory |
philpem@49 | 95 | if (ctx->data) { |
philpem@49 | 96 | free(ctx->data); |
philpem@49 | 97 | ctx->data = NULL; |
philpem@49 | 98 | } |
philpem@49 | 99 | } |
philpem@49 | 100 | |
philpem@49 | 101 | |
philpem@49 | 102 | bool wd2797_get_irq(WD2797_CTX *ctx) |
philpem@48 | 103 | { |
philpem@76 | 104 | return ctx->irq; |
philpem@48 | 105 | } |
philpem@48 | 106 | |
philpem@49 | 107 | bool wd2797_get_drq(WD2797_CTX *ctx) |
philpem@48 | 108 | { |
philpem@48 | 109 | return (ctx->data_pos < ctx->data_len); |
philpem@48 | 110 | } |
philpem@48 | 111 | |
philpem@48 | 112 | |
philpem@111 | 113 | WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int spt, int heads, int writeable) |
philpem@49 | 114 | { |
philpem@49 | 115 | size_t filesize; |
philpem@49 | 116 | |
philpem@49 | 117 | // Start by finding out how big the image file is |
philpem@49 | 118 | fseek(fp, 0, SEEK_END); |
philpem@49 | 119 | filesize = ftell(fp); |
philpem@49 | 120 | fseek(fp, 0, SEEK_SET); |
philpem@49 | 121 | |
philpem@49 | 122 | // Now figure out how many tracks it contains |
philpem@49 | 123 | int tracks = filesize / secsz / spt / heads; |
philpem@49 | 124 | // Confirm... |
philpem@49 | 125 | if (tracks < 1) { |
philpem@49 | 126 | return WD2797_ERR_BAD_GEOM; |
philpem@49 | 127 | } |
philpem@49 | 128 | |
philpem@49 | 129 | // Allocate enough memory to store one disc track |
philpem@49 | 130 | if (ctx->data) { |
philpem@49 | 131 | free(ctx->data); |
philpem@49 | 132 | } |
philpem@49 | 133 | ctx->data = malloc(secsz * spt); |
philpem@49 | 134 | if (!ctx->data) |
philpem@49 | 135 | return WD2797_ERR_NO_MEMORY; |
philpem@49 | 136 | |
philpem@49 | 137 | // Load the image and the geometry data |
philpem@49 | 138 | ctx->disc_image = fp; |
philpem@49 | 139 | ctx->geom_tracks = tracks; |
philpem@49 | 140 | ctx->geom_secsz = secsz; |
philpem@49 | 141 | ctx->geom_heads = heads; |
philpem@49 | 142 | ctx->geom_spt = spt; |
philpem@111 | 143 | ctx->writeable = writeable; |
philpem@49 | 144 | return WD2797_ERR_OK; |
philpem@49 | 145 | } |
philpem@49 | 146 | |
philpem@49 | 147 | |
philpem@49 | 148 | void wd2797_unload(WD2797_CTX *ctx) |
philpem@49 | 149 | { |
philpem@49 | 150 | // Free memory buffer |
philpem@49 | 151 | if (ctx->data) { |
philpem@49 | 152 | free(ctx->data); |
philpem@49 | 153 | ctx->data = NULL; |
philpem@49 | 154 | } |
philpem@49 | 155 | |
philpem@49 | 156 | // Clear file pointer |
philpem@49 | 157 | ctx->disc_image = NULL; |
philpem@49 | 158 | |
philpem@49 | 159 | // Clear the disc geometry |
philpem@49 | 160 | ctx->geom_tracks = ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = 0; |
philpem@49 | 161 | } |
philpem@49 | 162 | |
philpem@49 | 163 | |
philpem@49 | 164 | uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr) |
philpem@48 | 165 | { |
philpem@48 | 166 | uint8_t temp = 0; |
philpem@111 | 167 | m68k_end_timeslice(); |
philpem@48 | 168 | |
philpem@48 | 169 | switch (addr & 0x03) { |
philpem@49 | 170 | case WD2797_REG_STATUS: // Status register |
philpem@48 | 171 | // Read from status register clears IRQ |
philpem@76 | 172 | ctx->irq = false; |
philpem@48 | 173 | |
philpem@48 | 174 | // Get current status flags (set by last command) |
philpem@48 | 175 | // DRQ bit |
philpem@53 | 176 | if (ctx->cmd_has_drq) { |
philpem@53 | 177 | temp = ctx->status & ~0x03; |
philpem@48 | 178 | temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; |
philpem@71 | 179 | LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp); |
philpem@53 | 180 | } else { |
philpem@53 | 181 | temp = ctx->status & ~0x01; |
philpem@53 | 182 | } |
philpem@48 | 183 | // FDC is busy if there is still data in the buffer |
philpem@111 | 184 | temp |= (ctx->data_pos < ctx->data_len) ? 0x81 : 0x00; // if data in buffer, then DMA hasn't copied it yet, and we're still busy! |
philpem@49 | 185 | // TODO: also if seek delay / read delay hasn't passed (but that's for later) |
philpem@48 | 186 | return temp; |
philpem@48 | 187 | |
philpem@49 | 188 | case WD2797_REG_TRACK: // Track register |
philpem@111 | 189 | return ctx->track_reg; |
philpem@48 | 190 | |
philpem@49 | 191 | case WD2797_REG_SECTOR: // Sector register |
philpem@48 | 192 | return ctx->sector; |
philpem@48 | 193 | |
philpem@49 | 194 | case WD2797_REG_DATA: // Data register |
philpem@48 | 195 | // If there's data in the buffer, return it. Otherwise return 0xFF. |
philpem@48 | 196 | if (ctx->data_pos < ctx->data_len) { |
philpem@53 | 197 | // set IRQ if this is the last data byte |
philpem@53 | 198 | if (ctx->data_pos == (ctx->data_len-1)) { |
philpem@76 | 199 | // Set IRQ |
philpem@76 | 200 | ctx->irq = true; |
philpem@53 | 201 | } |
philpem@48 | 202 | // return data byte and increment pointer |
philpem@48 | 203 | return ctx->data[ctx->data_pos++]; |
philpem@48 | 204 | } else { |
philpem@53 | 205 | // command finished |
philpem@111 | 206 | return ctx->data_reg; |
philpem@48 | 207 | } |
philpem@48 | 208 | |
philpem@48 | 209 | default: |
philpem@48 | 210 | // shut up annoying compilers which don't recognise unreachable code when they see it |
philpem@48 | 211 | // (here's looking at you, gcc!) |
philpem@48 | 212 | return 0xff; |
philpem@48 | 213 | } |
philpem@48 | 214 | } |
philpem@48 | 215 | |
philpem@48 | 216 | |
philpem@49 | 217 | void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val) |
philpem@48 | 218 | { |
philpem@48 | 219 | uint8_t cmd = val & CMD_MASK; |
philpem@48 | 220 | size_t lba; |
philpem@48 | 221 | bool is_type1 = false; |
philpem@49 | 222 | int temp; |
philpem@53 | 223 | m68k_end_timeslice(); |
philpem@53 | 224 | |
philpem@48 | 225 | switch (addr) { |
philpem@49 | 226 | case WD2797_REG_COMMAND: // Command register |
philpem@48 | 227 | // write to command register clears interrupt request |
philpem@76 | 228 | ctx->irq = false; |
philpem@48 | 229 | |
philpem@48 | 230 | // Is the drive ready? |
philpem@48 | 231 | if (ctx->disc_image == NULL) { |
philpem@48 | 232 | // No disc image, thus the drive is busy. |
philpem@48 | 233 | ctx->status = 0x80; |
philpem@48 | 234 | return; |
philpem@48 | 235 | } |
philpem@48 | 236 | |
philpem@48 | 237 | // Handle Type 1 commands |
philpem@48 | 238 | switch (cmd) { |
philpem@48 | 239 | case CMD_RESTORE: |
philpem@48 | 240 | // Restore. Set track to 0 and throw an IRQ. |
philpem@48 | 241 | is_type1 = true; |
philpem@111 | 242 | ctx->track = ctx->track_reg = 0; |
philpem@48 | 243 | break; |
philpem@48 | 244 | |
philpem@48 | 245 | case CMD_SEEK: |
philpem@48 | 246 | // Seek. Seek to the track specced in the Data Register. |
philpem@48 | 247 | is_type1 = true; |
philpem@48 | 248 | if (ctx->data_reg < ctx->geom_tracks) { |
philpem@111 | 249 | ctx->track = ctx->track_reg = ctx->data_reg; |
philpem@48 | 250 | } else { |
philpem@48 | 251 | // Seek error. :( |
philpem@48 | 252 | ctx->status = 0x10; |
philpem@48 | 253 | } |
philpem@48 | 254 | |
philpem@48 | 255 | case CMD_STEP: |
philpem@48 | 256 | // TODO! deal with trk0! |
philpem@48 | 257 | // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag. |
philpem@48 | 258 | is_type1 = true; |
philpem@48 | 259 | break; |
philpem@48 | 260 | |
philpem@48 | 261 | case CMD_STEPIN: |
philpem@48 | 262 | case CMD_STEPOUT: |
philpem@48 | 263 | case CMD_STEP_TU: |
philpem@48 | 264 | case CMD_STEPIN_TU: |
philpem@48 | 265 | case CMD_STEPOUT_TU: |
philpem@48 | 266 | // if this is a Step In or Step Out cmd, set the step-direction |
philpem@111 | 267 | if ((cmd & ~0x10) == CMD_STEPIN) { |
philpem@48 | 268 | ctx->last_step_dir = 1; |
philpem@111 | 269 | } else if ((cmd & ~0x10) == CMD_STEPOUT) { |
philpem@48 | 270 | ctx->last_step_dir = -1; |
philpem@48 | 271 | } |
philpem@48 | 272 | |
philpem@111 | 273 | |
philpem@48 | 274 | // Seek one step in the last direction used. |
philpem@48 | 275 | ctx->track += ctx->last_step_dir; |
philpem@48 | 276 | if (ctx->track < 0) ctx->track = 0; |
philpem@48 | 277 | if (ctx->track >= ctx->geom_tracks) { |
philpem@48 | 278 | // Seek past end of disc... that'll be a Seek Error then. |
philpem@48 | 279 | ctx->status = 0x10; |
philpem@48 | 280 | ctx->track = ctx->geom_tracks - 1; |
philpem@48 | 281 | } |
philpem@111 | 282 | if (cmd & 0x10){ |
philpem@111 | 283 | ctx->track_reg = ctx->track; |
philpem@111 | 284 | } |
philpem@111 | 285 | |
philpem@48 | 286 | is_type1 = true; |
philpem@48 | 287 | break; |
philpem@48 | 288 | |
philpem@48 | 289 | default: |
philpem@48 | 290 | break; |
philpem@48 | 291 | } |
philpem@48 | 292 | |
philpem@48 | 293 | if (is_type1) { |
philpem@48 | 294 | // Terminate any sector reads or writes |
philpem@48 | 295 | ctx->data_len = ctx->data_pos = 0; |
philpem@48 | 296 | |
philpem@48 | 297 | // No DRQ bit for these commands. |
philpem@48 | 298 | ctx->cmd_has_drq = false; |
philpem@48 | 299 | |
philpem@48 | 300 | // Type1 status byte... |
philpem@48 | 301 | ctx->status = 0; |
philpem@48 | 302 | // S7 = Not Ready. Command executed, therefore the drive was ready... :) |
philpem@48 | 303 | // S6 = Write Protect. TODO: add this |
philpem@48 | 304 | // S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded... |
philpem@48 | 305 | ctx->status |= 0x20; |
philpem@48 | 306 | // S4 = Seek Error. Not bloody likely if we got down here...! |
philpem@48 | 307 | // S3 = CRC Error. Not gonna happen on a disc image! |
philpem@48 | 308 | // S2 = Track 0 |
philpem@48 | 309 | ctx->status |= (ctx->track == 0) ? 0x04 : 0x00; |
philpem@48 | 310 | // S1 = Index Pulse. TODO -- need periodics to emulate this |
philpem@48 | 311 | // S0 = Busy. We just exec'd the command, thus we're not busy. |
philpem@48 | 312 | // TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that. |
philpem@48 | 313 | |
philpem@76 | 314 | // Set IRQ |
philpem@76 | 315 | ctx->irq = true; |
philpem@48 | 316 | return; |
philpem@48 | 317 | } |
philpem@48 | 318 | |
philpem@48 | 319 | // That's the Type 1 (seek) commands sorted. Now for the others. |
philpem@48 | 320 | |
philpem@53 | 321 | // All these commands return the DRQ bit... |
philpem@53 | 322 | ctx->cmd_has_drq = true; |
philpem@53 | 323 | |
philpem@48 | 324 | // If drive isn't ready, then set status B7 and exit |
philpem@48 | 325 | if (ctx->disc_image == NULL) { |
philpem@48 | 326 | ctx->status = 0x80; |
philpem@48 | 327 | return; |
philpem@48 | 328 | } |
philpem@48 | 329 | |
philpem@48 | 330 | // If this is a Write command, check write protect status too |
philpem@111 | 331 | if (!ctx->writeable) { |
philpem@48 | 332 | // Write protected disc... |
philpem@48 | 333 | if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) { |
philpem@48 | 334 | // Set Write Protect bit and bail. |
philpem@48 | 335 | ctx->status = 0x40; |
philpem@48 | 336 | |
philpem@76 | 337 | // Set IRQ |
philpem@76 | 338 | ctx->irq = true; |
philpem@48 | 339 | |
philpem@48 | 340 | return; |
philpem@48 | 341 | } |
philpem@48 | 342 | } |
philpem@48 | 343 | |
philpem@48 | 344 | // Disc is ready to go. Parse the command word. |
philpem@48 | 345 | switch (cmd) { |
philpem@48 | 346 | case CMD_READ_ADDRESS: |
philpem@48 | 347 | // Read Address |
philpem@54 | 348 | ctx->head = (val & 0x02) ? 1 : 0; |
philpem@48 | 349 | |
philpem@48 | 350 | // reset data pointers |
philpem@48 | 351 | ctx->data_pos = ctx->data_len = 0; |
philpem@48 | 352 | |
philpem@48 | 353 | // load data buffer |
philpem@48 | 354 | ctx->data[ctx->data_len++] = ctx->track; |
philpem@48 | 355 | ctx->data[ctx->data_len++] = ctx->head; |
philpem@48 | 356 | ctx->data[ctx->data_len++] = ctx->sector; |
philpem@48 | 357 | switch (ctx->geom_secsz) { |
philpem@48 | 358 | case 128: ctx->data[ctx->data_len++] = 0; break; |
philpem@48 | 359 | case 256: ctx->data[ctx->data_len++] = 1; break; |
philpem@48 | 360 | case 512: ctx->data[ctx->data_len++] = 2; break; |
philpem@48 | 361 | case 1024: ctx->data[ctx->data_len++] = 3; break; |
philpem@48 | 362 | default: ctx->data[ctx->data_len++] = 0xFF; break; // TODO: deal with invalid values better |
philpem@48 | 363 | } |
philpem@48 | 364 | ctx->data[ctx->data_len++] = 0; // TODO: IDAM CRC! |
philpem@48 | 365 | ctx->data[ctx->data_len++] = 0; |
philpem@48 | 366 | |
philpem@53 | 367 | ctx->status = 0; |
philpem@48 | 368 | // B6, B5 = 0 |
philpem@48 | 369 | // B4 = Record Not Found. We're not going to see this... FIXME-not emulated |
philpem@48 | 370 | // B3 = CRC Error. Not possible. |
philpem@48 | 371 | // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated |
philpem@48 | 372 | // B1 = DRQ. Data request. |
philpem@48 | 373 | ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; |
philpem@48 | 374 | break; |
philpem@48 | 375 | |
philpem@48 | 376 | case CMD_READ_SECTOR: |
philpem@48 | 377 | case CMD_READ_SECTOR_MULTI: |
philpem@54 | 378 | ctx->head = (val & 0x02) ? 1 : 0; |
philpem@71 | 379 | LOG("WD279X: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector); |
philpem@48 | 380 | // Read Sector or Read Sector Multiple |
philpem@57 | 381 | |
philpem@57 | 382 | // Check to see if the cyl, hd and sec are valid |
philpem@57 | 383 | if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt) || (ctx->sector == 0)) { |
philpem@71 | 384 | LOG("*** WD2797 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d", |
philpem@57 | 385 | ctx->track, ctx->head, ctx->sector, |
philpem@57 | 386 | ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt); |
philpem@57 | 387 | // CHS parameters exceed limits |
philpem@57 | 388 | ctx->status = 0x10; // Record Not Found |
philpem@78 | 389 | // Set IRQ |
philpem@78 | 390 | ctx->irq = true; |
philpem@111 | 391 | break; |
philpem@57 | 392 | } |
philpem@57 | 393 | |
philpem@48 | 394 | // reset data pointers |
philpem@48 | 395 | ctx->data_pos = ctx->data_len = 0; |
philpem@48 | 396 | |
philpem@49 | 397 | // Calculate number of sectors to read from disc |
philpem@49 | 398 | if (cmd == CMD_READ_SECTOR_MULTI) |
philpem@49 | 399 | temp = ctx->geom_spt; |
philpem@49 | 400 | else |
philpem@49 | 401 | temp = 1; |
philpem@48 | 402 | |
philpem@49 | 403 | for (int i=0; i<temp; i++) { |
philpem@49 | 404 | // Calculate the LBA address of the required sector |
philpem@57 | 405 | // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1 |
philpem@57 | 406 | lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i) - 1; |
philpem@57 | 407 | // convert LBA to byte address |
philpem@57 | 408 | lba *= ctx->geom_secsz; |
philpem@71 | 409 | LOG("\tREAD lba = %lu", lba); |
philpem@49 | 410 | |
philpem@49 | 411 | // Read the sector from the file |
philpem@49 | 412 | fseek(ctx->disc_image, lba, SEEK_SET); |
philpem@71 | 413 | // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr) |
philpem@49 | 414 | ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image); |
philpem@71 | 415 | LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz); |
philpem@49 | 416 | } |
philpem@48 | 417 | |
philpem@53 | 418 | ctx->status = 0; |
philpem@48 | 419 | // B6 = 0 |
philpem@48 | 420 | // B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks. |
philpem@57 | 421 | // B4 = Record Not Found. Basically, the CHS parameters are bullcrap. |
philpem@48 | 422 | // B3 = CRC Error. Not possible. |
philpem@48 | 423 | // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated |
philpem@48 | 424 | // B1 = DRQ. Data request. |
philpem@48 | 425 | ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; |
philpem@48 | 426 | break; |
philpem@48 | 427 | |
philpem@48 | 428 | case CMD_READ_TRACK: |
philpem@48 | 429 | // Read Track |
philpem@49 | 430 | // TODO! implement this |
philpem@111 | 431 | // ctx->head = (val & 0x02) ? 1 : 0; |
philpem@111 | 432 | // ctx->status = 0; |
philpem@48 | 433 | // B6, B5, B4, B3 = 0 |
philpem@48 | 434 | // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated |
philpem@48 | 435 | // B1 = DRQ. Data request. |
philpem@111 | 436 | // ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; |
philpem@111 | 437 | ctx->irq = true; |
philpem@111 | 438 | ctx->status = 0x10; |
philpem@48 | 439 | break; |
philpem@48 | 440 | |
philpem@48 | 441 | case CMD_WRITE_SECTOR: |
philpem@48 | 442 | case CMD_WRITE_SECTOR_MULTI: |
philpem@48 | 443 | // Write Sector or Write Sector Multiple |
philpem@48 | 444 | |
philpem@54 | 445 | ctx->head = (val & 0x02) ? 1 : 0; |
philpem@48 | 446 | // reset data pointers |
philpem@111 | 447 | ctx->data_pos = 0; |
philpem@48 | 448 | |
philpem@111 | 449 | // Calculate number of sectors to write to disc |
philpem@111 | 450 | if (cmd == CMD_WRITE_SECTOR_MULTI) |
philpem@111 | 451 | /*XXX: is this the correct value?*/ |
philpem@111 | 452 | temp = ctx->geom_spt; |
philpem@111 | 453 | else |
philpem@111 | 454 | temp = 1; |
philpem@111 | 455 | ctx->data_len = temp * ctx->geom_secsz; |
philpem@111 | 456 | |
philpem@111 | 457 | lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector)) - 1; |
philpem@111 | 458 | ctx->write_pos = lba * ctx->geom_secsz; |
philpem@48 | 459 | |
philpem@53 | 460 | ctx->status = 0; |
philpem@111 | 461 | // B6 = Write Protect. This would have been set earlier. |
philpem@48 | 462 | // B5 = 0 |
philpem@48 | 463 | // B4 = Record Not Found. We're not going to see this... FIXME-not emulated |
philpem@48 | 464 | // B3 = CRC Error. Not possible. |
philpem@48 | 465 | // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated |
philpem@48 | 466 | // B1 = DRQ. Data request. |
philpem@48 | 467 | ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; |
philpem@48 | 468 | break; |
philpem@48 | 469 | |
philpem@48 | 470 | case CMD_FORMAT_TRACK: |
philpem@48 | 471 | // Write Track (aka Format Track) |
philpem@54 | 472 | ctx->head = (val & 0x02) ? 1 : 0; |
philpem@53 | 473 | ctx->status = 0; |
philpem@48 | 474 | // B6 = Write Protect. FIXME -- emulate this! |
philpem@48 | 475 | // B5, B4, B3 = 0 |
philpem@48 | 476 | // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated |
philpem@111 | 477 | ctx->data_pos = 0; |
philpem@111 | 478 | ctx->data_len = 7170; |
philpem@48 | 479 | // B1 = DRQ. Data request. |
philpem@48 | 480 | ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; |
philpem@111 | 481 | ctx->formatting = true; |
philpem@48 | 482 | break; |
philpem@48 | 483 | |
philpem@48 | 484 | case CMD_FORCE_INTERRUPT: |
philpem@48 | 485 | // Force Interrupt... |
philpem@49 | 486 | // Terminates current operation and sends an interrupt |
philpem@49 | 487 | // TODO! |
philpem@111 | 488 | ctx->status = 0x20; |
philpem@111 | 489 | if (!ctx->writeable){ |
philpem@111 | 490 | ctx->status |= 0x40; |
philpem@111 | 491 | } |
philpem@111 | 492 | if (ctx->track == 0){ |
philpem@111 | 493 | ctx->status = 0x04; |
philpem@111 | 494 | } |
philpem@49 | 495 | ctx->data_pos = ctx->data_len = 0; |
philpem@111 | 496 | if (cmd & 8){ |
philpem@111 | 497 | // Set IRQ |
philpem@111 | 498 | ctx->irq = true; |
philpem@111 | 499 | } |
philpem@48 | 500 | break; |
philpem@48 | 501 | } |
philpem@48 | 502 | break; |
philpem@48 | 503 | |
philpem@49 | 504 | case WD2797_REG_TRACK: // Track register |
philpem@111 | 505 | ctx->track = ctx->track_reg = val; |
philpem@48 | 506 | break; |
philpem@48 | 507 | |
philpem@49 | 508 | case WD2797_REG_SECTOR: // Sector register |
philpem@48 | 509 | ctx->sector = val; |
philpem@48 | 510 | break; |
philpem@48 | 511 | |
philpem@49 | 512 | case WD2797_REG_DATA: // Data register |
philpem@48 | 513 | // Save the value written into the data register |
philpem@48 | 514 | ctx->data_reg = val; |
philpem@48 | 515 | // If we're processing a write command, and there's space in the |
philpem@48 | 516 | // buffer, allow the write. |
philpem@111 | 517 | if (ctx->data_pos < ctx->data_len && (ctx->write_pos >= 0 || ctx->formatting)) { |
philpem@111 | 518 | if (!ctx->formatting) ctx->data[ctx->data_pos] = val; |
philpem@111 | 519 | // store data byte and increment pointer |
philpem@111 | 520 | ctx->data_pos++; |
philpem@111 | 521 | |
philpem@111 | 522 | // set IRQ and write data if this is the last data byte |
philpem@111 | 523 | if (ctx->data_pos == ctx->data_len) { |
philpem@111 | 524 | if (!ctx->formatting){ |
philpem@111 | 525 | fseek(ctx->disc_image, ctx->write_pos, SEEK_SET); |
philpem@111 | 526 | fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image); |
philpem@111 | 527 | fflush(ctx->disc_image); |
philpem@111 | 528 | } |
philpem@111 | 529 | // Set IRQ and reset write pointer |
philpem@76 | 530 | ctx->irq = true; |
philpem@111 | 531 | ctx->write_pos = -1; |
philpem@111 | 532 | ctx->formatting = false; |
philpem@53 | 533 | } |
philpem@53 | 534 | |
philpem@48 | 535 | } |
philpem@48 | 536 | break; |
philpem@48 | 537 | } |
philpem@48 | 538 | } |
philpem@48 | 539 | |
philpem@111 | 540 | void wd2797_dma_miss(WD2797_CTX *ctx) |
philpem@111 | 541 | { |
philpem@111 | 542 | ctx->data_pos = ctx->data_len; |
philpem@111 | 543 | ctx->write_pos = 0; |
philpem@111 | 544 | ctx->status = 4; /* lost data */ |
philpem@111 | 545 | ctx->irq = true; |
philpem@111 | 546 | } |