src/wd2010.c

Sat, 17 Nov 2012 19:18:29 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Sat, 17 Nov 2012 19:18:29 +0000
changeset 112
a392eb8f9806
child 115
da3d10af0711
permissions
-rw-r--r--

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@112 1 #include <stdint.h>
philpem@112 2 #include <stdbool.h>
philpem@112 3 #include <malloc.h>
philpem@112 4 #include "SDL.h"
philpem@112 5 #include "musashi/m68k.h"
philpem@112 6 #include "wd2010.h"
philpem@112 7
philpem@112 8 #define WD2010_DEBUG
philpem@112 9
philpem@112 10 #ifndef WD2010_DEBUG
philpem@112 11 #define NDEBUG
philpem@112 12 #endif
philpem@112 13 #include "utils.h"
philpem@112 14
philpem@112 15 #define SEEK_DELAY 30
philpem@112 16
philpem@112 17 #define CMD_ENABLE_RETRY 0x01
philpem@112 18 #define CMD_LONG_MODE 0x02
philpem@112 19 #define CMD_MULTI_SECTOR 0x04
philpem@112 20 #define CMD_INTRQ_WHEN_COMPLETE 0x08
philpem@112 21
philpem@112 22 #define ER_BAD_BLOCK 0x80
philpem@112 23 #define ER_CRC 0x40
philpem@112 24 #define ER_ID_NOT_FOUND 0x10
philpem@112 25 #define ER_ABORTED_COMMAND 0x04
philpem@112 26 #define ER_NO_TK0 0x02
philpem@112 27 #define ER_NO_ADDRESS_MARK 0x01
philpem@112 28
philpem@112 29 #define SR_BUSY 0x80
philpem@112 30 #define SR_READY 0x40
philpem@112 31 #define SR_WRITE_FAULT 0x20
philpem@112 32 #define SR_SEEK_COMPLETE 0x10
philpem@112 33 #define SR_DRQ 0x08
philpem@112 34 #define SR_CORRECTED 0x04
philpem@112 35 #define SR_COMMAND_IN_PROGRESS 0x02
philpem@112 36 #define SR_ERROR 0x01
philpem@112 37
philpem@112 38 extern int cpu_log_enabled;
philpem@112 39
philpem@112 40 /// WD2010 command constants
philpem@112 41 enum {
philpem@112 42 CMD_MASK = 0xF0, ///< Bit mask to detect command bits
philpem@112 43 CMD_2010_EXT = 0x00, ///< WD2010 extended commands (compute correction, set parameter)
philpem@112 44 CMD_RESTORE = 0x10, ///< Restore (recalibrate, seek to track 0)
philpem@112 45 CMD_READ_SECTOR = 0x20, ///< Read sector
philpem@112 46 CMD_WRITE_SECTOR = 0x30, ///< Write sector
philpem@112 47 CMD_SCAN_ID = 0x40, ///< Scan ID
philpem@112 48 CMD_WRITE_FORMAT = 0x50, ///< Write format
philpem@112 49 CMD_SEEK = 0x70, ///< Seek to given track
philpem@112 50 };
philpem@112 51
philpem@112 52 int wd2010_init(WD2010_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
philpem@112 53 {
philpem@112 54 long filesize;
philpem@112 55 wd2010_reset(ctx);
philpem@112 56 // Start by finding out how big the image file is
philpem@112 57 fseek(fp, 0, SEEK_END);
philpem@112 58 filesize = ftell(fp);
philpem@112 59 fseek(fp, 0, SEEK_SET);
philpem@112 60
philpem@112 61 // Now figure out how many tracks it contains
philpem@112 62 int tracks = filesize / secsz / spt / heads;
philpem@112 63 // Confirm...
philpem@112 64 if (tracks < 1) {
philpem@112 65 return WD2010_ERR_BAD_GEOM;
philpem@112 66 }
philpem@112 67
philpem@112 68 // Allocate enough memory to store one disc track
philpem@112 69 if (ctx->data) {
philpem@112 70 free(ctx->data);
philpem@112 71 }
philpem@112 72 ctx->data = malloc(secsz * spt);
philpem@112 73 if (!ctx->data)
philpem@112 74 return WD2010_ERR_NO_MEMORY;
philpem@112 75
philpem@112 76 // Load the image and the geometry data
philpem@112 77 ctx->disc_image = fp;
philpem@112 78 ctx->geom_tracks = tracks;
philpem@112 79 ctx->geom_secsz = secsz;
philpem@112 80 ctx->geom_heads = heads;
philpem@112 81 ctx->geom_spt = spt;
philpem@112 82 return WD2010_ERR_OK;
philpem@112 83
philpem@112 84 }
philpem@112 85
philpem@112 86 void wd2010_reset(WD2010_CTX *ctx)
philpem@112 87 {
philpem@112 88 // track, head and sector unknown
philpem@112 89 ctx->track = ctx->head = ctx->sector = 0;
philpem@112 90
philpem@112 91 // no IRQ pending
philpem@112 92 ctx->irq = false;
philpem@112 93
philpem@112 94 // no data available
philpem@112 95 ctx->data_pos = ctx->data_len = 0;
philpem@112 96
philpem@112 97 // Status register clear, not busy
philpem@112 98 ctx->status = 0;
philpem@112 99
philpem@112 100 ctx->sector_count = 0;
philpem@112 101 ctx->sector_number = 0;
philpem@112 102 ctx->cylinder_low_reg = 0;
philpem@112 103 ctx->cylinder_high_reg = 0;
philpem@112 104 ctx->sdh = 0;
philpem@112 105 }
philpem@112 106
philpem@112 107 void wd2010_done(WD2010_CTX *ctx)
philpem@112 108 {
philpem@112 109 // Reset the WD2010
philpem@112 110 wd2010_reset(ctx);
philpem@112 111
philpem@112 112 // Free any allocated memory
philpem@112 113 if (ctx->data) {
philpem@112 114 free(ctx->data);
philpem@112 115 ctx->data = NULL;
philpem@112 116 }
philpem@112 117 }
philpem@112 118
philpem@112 119
philpem@112 120 bool wd2010_get_irq(WD2010_CTX *ctx)
philpem@112 121 {
philpem@112 122 return ctx->irq;
philpem@112 123 }
philpem@112 124
philpem@112 125 bool wd2010_get_drq(WD2010_CTX *ctx)
philpem@112 126 {
philpem@112 127 return (ctx->drq && ctx->data_pos < ctx->data_len);
philpem@112 128 }
philpem@112 129
philpem@112 130 void wd2010_dma_miss(WD2010_CTX *ctx)
philpem@112 131 {
philpem@112 132 ctx->data_pos = ctx->data_len;
philpem@112 133 ctx->write_pos = 0;
philpem@112 134 ctx->status = SR_READY | SR_SEEK_COMPLETE;
philpem@112 135 ctx->irq = true;
philpem@112 136 }
philpem@112 137
philpem@112 138 uint8_t wd2010_read_data(WD2010_CTX *ctx)
philpem@112 139 {
philpem@112 140 // If there's data in the buffer, return it. Otherwise return 0xFF.
philpem@112 141 if (ctx->data_pos < ctx->data_len) {
philpem@112 142 if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
philpem@112 143 ctx->sector_count--;
philpem@112 144 ctx->sector_number++;
philpem@112 145 }
philpem@112 146 // set IRQ if this is the last data byte
philpem@112 147 if (ctx->data_pos == (ctx->data_len-1)) {
philpem@112 148 ctx->status = SR_READY | SR_SEEK_COMPLETE;
philpem@112 149 // Set IRQ
philpem@112 150 ctx->irq = true;
philpem@112 151 ctx->drq = false;
philpem@112 152 }
philpem@112 153 // return data byte and increment pointer
philpem@112 154 return ctx->data[ctx->data_pos++];
philpem@112 155 } else {
philpem@112 156 // empty buffer (this shouldn't happen)
philpem@112 157 LOG("WD2010: attempt to read from empty data buffer");
philpem@112 158 return 0xff;
philpem@112 159 }
philpem@112 160 }
philpem@112 161
philpem@112 162 void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
philpem@112 163 {
philpem@112 164 // If we're processing a write command, and there's space in the
philpem@112 165 // buffer, allow the write.
philpem@112 166 if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
philpem@112 167 // store data byte and increment pointer
philpem@112 168 if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
philpem@112 169 ctx->sector_count--;
philpem@112 170 ctx->sector_number++;
philpem@112 171 }
philpem@112 172 ctx->data[ctx->data_pos++] = val;
philpem@112 173 // set IRQ and write data if this is the last data byte
philpem@112 174 if (ctx->data_pos == ctx->data_len) {
philpem@112 175 if (!ctx->formatting){
philpem@112 176 fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
philpem@112 177 fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
philpem@112 178 fflush(ctx->disc_image);
philpem@112 179 }
philpem@112 180 ctx->formatting = false;
philpem@112 181 ctx->status = SR_READY | SR_SEEK_COMPLETE;
philpem@112 182 // Set IRQ and reset write pointer
philpem@112 183 ctx->irq = true;
philpem@112 184 ctx->write_pos = -1;
philpem@112 185 ctx->drq = false;
philpem@112 186 }
philpem@112 187 }else{
philpem@112 188 LOG("WD2010: attempt to write to data buffer without a write command in progress");
philpem@112 189 }
philpem@112 190 }
philpem@112 191
philpem@112 192 uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
philpem@112 193 {
philpem@112 194 /*m68k_end_timeslice();*/
philpem@112 195 ctx->status = SR_READY | SR_SEEK_COMPLETE;
philpem@112 196 ctx->irq = true;
philpem@112 197 return (0);
philpem@112 198 }
philpem@112 199
philpem@112 200 uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
philpem@112 201 {
philpem@112 202 /*m68k_end_timeslice();*/
philpem@112 203 ctx->drq = true;
philpem@112 204 return (0);
philpem@112 205 }
philpem@112 206
philpem@112 207 uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
philpem@112 208 {
philpem@112 209 uint8_t temp = 0;
philpem@112 210
philpem@112 211 /*cpu_log_enabled = 1;*/
philpem@112 212
philpem@112 213 switch (addr & 0x07) {
philpem@112 214 case WD2010_REG_ERROR:
philpem@112 215 return ctx->error_reg;
philpem@112 216 case WD2010_REG_SECTOR_COUNT:
philpem@112 217 return ctx->sector_count;
philpem@112 218 case WD2010_REG_SECTOR_NUMBER:
philpem@112 219 return ctx->sector_number;
philpem@112 220 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
philpem@112 221 return ctx->cylinder_high_reg;
philpem@112 222 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
philpem@112 223 return ctx->cylinder_low_reg;
philpem@112 224 case WD2010_REG_SDH:
philpem@112 225 return ctx->sdh;
philpem@112 226 case WD2010_REG_STATUS: // Status register
philpem@112 227 // Read from status register clears IRQ
philpem@112 228 ctx->irq = false;
philpem@112 229 // Get current status flags (set by last command)
philpem@112 230 // DRQ bit
philpem@112 231 if (ctx->cmd_has_drq) {
philpem@112 232 temp = ctx->status & ~(SR_BUSY & SR_DRQ);
philpem@112 233 temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
philpem@112 234 LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
philpem@112 235 } else {
philpem@112 236 temp = ctx->status & ~0x80;
philpem@112 237 }
philpem@112 238 /*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
philpem@112 239 // HDC is busy if there is still data in the buffer
philpem@112 240 temp |= (ctx->data_pos < ctx->data_len) ? SR_BUSY : 0; // if data in buffer, then DMA hasn't copied it yet, and we're still busy!
philpem@112 241 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
philpem@112 242 /*XXX: should anything else be set here?*/
philpem@112 243 return temp;
philpem@112 244 default:
philpem@112 245 // shut up annoying compilers which don't recognise unreachable code when they see it
philpem@112 246 // (here's looking at you, gcc!)
philpem@112 247 return 0xff;
philpem@112 248 }
philpem@112 249 }
philpem@112 250
philpem@112 251
philpem@112 252 void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
philpem@112 253 {
philpem@112 254 uint8_t cmd = val & CMD_MASK;
philpem@112 255 size_t lba;
philpem@112 256 int new_track;
philpem@112 257 int sector_count;
philpem@112 258
philpem@112 259 m68k_end_timeslice();
philpem@112 260
philpem@112 261 /*cpu_log_enabled = 1;*/
philpem@112 262
philpem@112 263 switch (addr & 0x07) {
philpem@112 264 case WD2010_REG_WRITE_PRECOMP_CYLINDER:
philpem@112 265 break;
philpem@112 266 case WD2010_REG_SECTOR_COUNT:
philpem@112 267 ctx->sector_count = val;
philpem@112 268 break;
philpem@112 269 case WD2010_REG_SECTOR_NUMBER:
philpem@112 270 ctx->sector_number = val;
philpem@112 271 break;
philpem@112 272 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
philpem@112 273 ctx->cylinder_high_reg = val;
philpem@112 274 break;
philpem@112 275 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
philpem@112 276 ctx->cylinder_low_reg = val;
philpem@112 277 break;
philpem@112 278 case WD2010_REG_SDH:
philpem@112 279 /*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
philpem@112 280 ctx->data_pos = ctx->data_len = 0;
philpem@112 281 ctx->sdh = val;
philpem@112 282 break;
philpem@112 283 case WD2010_REG_COMMAND: // Command register
philpem@112 284 // write to command register clears interrupt request
philpem@112 285 ctx->irq = false;
philpem@112 286 ctx->error_reg = 0;
philpem@112 287
philpem@112 288 /*cpu_log_enabled = 1;*/
philpem@112 289 switch (cmd) {
philpem@112 290 case CMD_RESTORE:
philpem@112 291 // Restore. Set track to 0 and throw an IRQ.
philpem@112 292 ctx->track = 0;
philpem@112 293 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
philpem@112 294 break;
philpem@112 295 case CMD_SCAN_ID:
philpem@112 296 ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
philpem@112 297 ctx->cylinder_low_reg = ctx->track & 0xff;
philpem@112 298 ctx->sector_number = ctx->sector;
philpem@112 299 ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
philpem@112 300 case CMD_WRITE_FORMAT:
philpem@112 301 case CMD_SEEK:
philpem@112 302 case CMD_READ_SECTOR:
philpem@112 303 case CMD_WRITE_SECTOR:
philpem@112 304 // Seek. Seek to the track specced in the cylinder
philpem@112 305 // registers.
philpem@112 306 new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
philpem@112 307 if (new_track < ctx->geom_tracks) {
philpem@112 308 ctx->track = new_track;
philpem@112 309 } else {
philpem@112 310 // Seek error. :(
philpem@112 311 ctx->status = SR_ERROR;
philpem@112 312 ctx->error_reg = ER_ID_NOT_FOUND;
philpem@112 313 ctx->irq = true;
philpem@112 314 break;
philpem@112 315 }
philpem@112 316 ctx->head = ctx->sdh & 0x07;
philpem@112 317 ctx->sector = ctx->sector_number;
philpem@112 318
philpem@112 319 ctx->formatting = cmd == CMD_WRITE_FORMAT;
philpem@112 320 switch (cmd){
philpem@112 321 case CMD_SEEK:
philpem@112 322 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
philpem@112 323 break;
philpem@112 324 case CMD_READ_SECTOR:
philpem@112 325 /*XXX: does a separate function to set the head have to be added?*/
philpem@112 326 LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
philpem@112 327
philpem@112 328 // Read Sector
philpem@112 329
philpem@112 330 // Check to see if the cyl, hd and sec are valid
philpem@112 331 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) {
philpem@112 332 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
philpem@112 333 ctx->track, ctx->head, ctx->sector,
philpem@112 334 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1);
philpem@112 335 // CHS parameters exceed limits
philpem@112 336 ctx->status = SR_ERROR;
philpem@112 337 ctx->error_reg = ER_ID_NOT_FOUND;
philpem@112 338 // Set IRQ
philpem@112 339 ctx->irq = true;
philpem@112 340 break;
philpem@112 341 }
philpem@112 342
philpem@112 343 // reset data pointers
philpem@112 344 ctx->data_pos = ctx->data_len = 0;
philpem@112 345
philpem@112 346 if (val & CMD_MULTI_SECTOR){
philpem@112 347 ctx->multi_sector = 1;
philpem@112 348 sector_count = ctx->sector_count;
philpem@112 349 }else{
philpem@112 350 ctx->multi_sector = 0;
philpem@112 351 sector_count = 1;
philpem@112 352 }
philpem@112 353 for (int i=0; i<sector_count; i++) {
philpem@112 354 // Calculate the LBA address of the required sector
philpem@112 355 // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
philpem@112 356 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
philpem@112 357 // convert LBA to byte address
philpem@112 358 lba *= ctx->geom_secsz;
philpem@112 359 LOG("\tREAD lba = %lu", lba);
philpem@112 360
philpem@112 361 // Read the sector from the file
philpem@112 362 fseek(ctx->disc_image, lba, SEEK_SET);
philpem@112 363 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
philpem@112 364 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
philpem@112 365 LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
philpem@112 366 }
philpem@112 367
philpem@112 368 ctx->status = 0;
philpem@112 369 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
philpem@112 370 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
philpem@112 371
philpem@112 372 break;
philpem@112 373 case CMD_WRITE_FORMAT:
philpem@112 374 ctx->sector = 0;
philpem@112 375 case CMD_WRITE_SECTOR:
philpem@112 376 LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
philpem@112 377 // Read Sector
philpem@112 378
philpem@112 379 // Check to see if the cyl, hd and sec are valid
philpem@112 380 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) {
philpem@112 381 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
philpem@112 382 ctx->track, ctx->head, ctx->sector,
philpem@112 383 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
philpem@112 384 // CHS parameters exceed limits
philpem@112 385 ctx->status = SR_ERROR;
philpem@112 386 ctx->error_reg = ER_ID_NOT_FOUND;
philpem@112 387 // Set IRQ
philpem@112 388 ctx->irq = true;
philpem@112 389 break;
philpem@112 390 }
philpem@112 391
philpem@112 392 // reset data pointers
philpem@112 393 ctx->data_pos = ctx->data_len = 0;
philpem@112 394
philpem@112 395 if (val & CMD_MULTI_SECTOR){
philpem@112 396 ctx->multi_sector = 1;
philpem@112 397 sector_count = ctx->sector_count;
philpem@112 398 }else{
philpem@112 399 ctx->multi_sector = 0;
philpem@112 400 sector_count = 1;
philpem@112 401 }
philpem@112 402 ctx->data_len = ctx->geom_secsz * sector_count;
philpem@112 403 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
philpem@112 404 // convert LBA to byte address
philpem@112 405 ctx->write_pos = lba * ctx->geom_secsz;
philpem@112 406 LOG("\tWRITE lba = %lu", lba);
philpem@112 407
philpem@112 408 ctx->status = 0;
philpem@112 409 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
philpem@112 410 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
philpem@112 411
philpem@112 412 break;
philpem@112 413 default:
philpem@112 414 LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
philpem@112 415 break;
philpem@112 416 }
philpem@112 417 break;
philpem@112 418 case CMD_2010_EXT: /* not implemented */
philpem@112 419 default:
philpem@112 420 LOG("WD2010: unknown command %x\n", cmd);
philpem@112 421 ctx->status = SR_ERROR;
philpem@112 422 ctx->error_reg = ER_ABORTED_COMMAND;
philpem@112 423 ctx->irq = true;
philpem@112 424 break;
philpem@112 425 }
philpem@112 426 break;
philpem@112 427
philpem@112 428 }
philpem@112 429 }
philpem@112 430