src/wd2010.c

Mon, 14 Jan 2013 09:50:37 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Mon, 14 Jan 2013 09:50:37 +0000
changeset 120
df40e6668a46
parent 116
21521e62007f
child 122
b214cf455ff2
permissions
-rw-r--r--

Max out system memory by default

Set the system memory to 2MiB base, 2MiB ext. This is a fully loaded 3B1
motherboard with a RAM expansion board. 512KiB base/no ext is the minimum
which can be specified (e.g. kernel memory map area only) but does not leave
any room for userspace. The kernel doesn't like that and doesn't handle it
gracefully...!

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@116 105 ctx->mcr2_hdsel3 = 0;
philpem@116 106 ctx->mcr2_ddrive1 = 0;
philpem@112 107 }
philpem@112 108
philpem@112 109 void wd2010_done(WD2010_CTX *ctx)
philpem@112 110 {
philpem@112 111 // Reset the WD2010
philpem@112 112 wd2010_reset(ctx);
philpem@112 113
philpem@112 114 // Free any allocated memory
philpem@112 115 if (ctx->data) {
philpem@112 116 free(ctx->data);
philpem@112 117 ctx->data = NULL;
philpem@112 118 }
philpem@112 119 }
philpem@112 120
philpem@112 121
philpem@112 122 bool wd2010_get_irq(WD2010_CTX *ctx)
philpem@112 123 {
philpem@112 124 return ctx->irq;
philpem@112 125 }
philpem@112 126
philpem@112 127 bool wd2010_get_drq(WD2010_CTX *ctx)
philpem@112 128 {
philpem@112 129 return (ctx->drq && ctx->data_pos < ctx->data_len);
philpem@112 130 }
philpem@112 131
philpem@112 132 void wd2010_dma_miss(WD2010_CTX *ctx)
philpem@112 133 {
philpem@112 134 ctx->data_pos = ctx->data_len;
philpem@112 135 ctx->write_pos = 0;
philpem@112 136 ctx->status = SR_READY | SR_SEEK_COMPLETE;
philpem@112 137 ctx->irq = true;
philpem@112 138 }
philpem@112 139
philpem@112 140 uint8_t wd2010_read_data(WD2010_CTX *ctx)
philpem@112 141 {
philpem@112 142 // If there's data in the buffer, return it. Otherwise return 0xFF.
philpem@112 143 if (ctx->data_pos < ctx->data_len) {
philpem@112 144 if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
philpem@112 145 ctx->sector_count--;
philpem@112 146 ctx->sector_number++;
philpem@112 147 }
philpem@112 148 // set IRQ if this is the last data byte
philpem@112 149 if (ctx->data_pos == (ctx->data_len-1)) {
philpem@112 150 ctx->status = SR_READY | SR_SEEK_COMPLETE;
philpem@112 151 // Set IRQ
philpem@112 152 ctx->irq = true;
philpem@112 153 ctx->drq = false;
philpem@112 154 }
philpem@112 155 // return data byte and increment pointer
philpem@112 156 return ctx->data[ctx->data_pos++];
philpem@112 157 } else {
philpem@112 158 // empty buffer (this shouldn't happen)
philpem@115 159 LOGS("WD2010: attempt to read from empty data buffer");
philpem@112 160 return 0xff;
philpem@112 161 }
philpem@112 162 }
philpem@112 163
philpem@112 164 void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
philpem@112 165 {
philpem@112 166 // If we're processing a write command, and there's space in the
philpem@112 167 // buffer, allow the write.
philpem@112 168 if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
philpem@112 169 // store data byte and increment pointer
philpem@112 170 if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
philpem@112 171 ctx->sector_count--;
philpem@112 172 ctx->sector_number++;
philpem@112 173 }
philpem@112 174 ctx->data[ctx->data_pos++] = val;
philpem@112 175 // set IRQ and write data if this is the last data byte
philpem@112 176 if (ctx->data_pos == ctx->data_len) {
philpem@112 177 if (!ctx->formatting){
philpem@112 178 fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
philpem@112 179 fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
philpem@112 180 fflush(ctx->disc_image);
philpem@112 181 }
philpem@112 182 ctx->formatting = false;
philpem@112 183 ctx->status = SR_READY | SR_SEEK_COMPLETE;
philpem@112 184 // Set IRQ and reset write pointer
philpem@112 185 ctx->irq = true;
philpem@112 186 ctx->write_pos = -1;
philpem@112 187 ctx->drq = false;
philpem@112 188 }
philpem@112 189 }else{
philpem@115 190 LOGS("WD2010: attempt to write to data buffer without a write command in progress");
philpem@112 191 }
philpem@112 192 }
philpem@112 193
philpem@112 194 uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
philpem@112 195 {
philpem@112 196 /*m68k_end_timeslice();*/
philpem@112 197 ctx->status = SR_READY | SR_SEEK_COMPLETE;
philpem@112 198 ctx->irq = true;
philpem@112 199 return (0);
philpem@112 200 }
philpem@112 201
philpem@112 202 uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
philpem@112 203 {
philpem@112 204 /*m68k_end_timeslice();*/
philpem@112 205 ctx->drq = true;
philpem@112 206 return (0);
philpem@112 207 }
philpem@112 208
philpem@112 209 uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
philpem@112 210 {
philpem@112 211 uint8_t temp = 0;
philpem@112 212
philpem@112 213 /*cpu_log_enabled = 1;*/
philpem@112 214
philpem@112 215 switch (addr & 0x07) {
philpem@112 216 case WD2010_REG_ERROR:
philpem@112 217 return ctx->error_reg;
philpem@112 218 case WD2010_REG_SECTOR_COUNT:
philpem@112 219 return ctx->sector_count;
philpem@112 220 case WD2010_REG_SECTOR_NUMBER:
philpem@112 221 return ctx->sector_number;
philpem@112 222 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
philpem@112 223 return ctx->cylinder_high_reg;
philpem@112 224 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
philpem@112 225 return ctx->cylinder_low_reg;
philpem@112 226 case WD2010_REG_SDH:
philpem@112 227 return ctx->sdh;
philpem@112 228 case WD2010_REG_STATUS: // Status register
philpem@112 229 // Read from status register clears IRQ
philpem@112 230 ctx->irq = false;
philpem@112 231 // Get current status flags (set by last command)
philpem@112 232 // DRQ bit
philpem@112 233 if (ctx->cmd_has_drq) {
philpem@112 234 temp = ctx->status & ~(SR_BUSY & SR_DRQ);
philpem@112 235 temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
philpem@112 236 LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
philpem@112 237 } else {
philpem@112 238 temp = ctx->status & ~0x80;
philpem@112 239 }
philpem@112 240 /*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
philpem@112 241 // HDC is busy if there is still data in the buffer
philpem@112 242 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 243 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
philpem@112 244 /*XXX: should anything else be set here?*/
philpem@112 245 return temp;
philpem@112 246 default:
philpem@112 247 // shut up annoying compilers which don't recognise unreachable code when they see it
philpem@112 248 // (here's looking at you, gcc!)
philpem@112 249 return 0xff;
philpem@112 250 }
philpem@112 251 }
philpem@112 252
philpem@112 253
philpem@112 254 void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
philpem@112 255 {
philpem@112 256 uint8_t cmd = val & CMD_MASK;
philpem@112 257 size_t lba;
philpem@112 258 int new_track;
philpem@112 259 int sector_count;
philpem@112 260
philpem@112 261 m68k_end_timeslice();
philpem@112 262
philpem@112 263 /*cpu_log_enabled = 1;*/
philpem@112 264
philpem@116 265 if (addr == UNIXPC_REG_MCR2) {
philpem@116 266 // The UNIX PC has an "MCR2" register with the following format:
philpem@116 267 // [ 7..2 ][1][0]
philpem@116 268 // Bits 7..2: Not used
philpem@116 269 // Bit 1: DDRIVE1 (hard disk drive 1 select - not used?)
philpem@116 270 // Bit 0: HDSEL3 (head-select bit 3)
philpem@116 271 ctx->mcr2_hdsel3 = ((val & 1) == 1);
philpem@116 272 ctx->mcr2_ddrive1 = ((val & 2) == 2);
philpem@116 273 return;
philpem@116 274 }
philpem@116 275
philpem@112 276 switch (addr & 0x07) {
philpem@112 277 case WD2010_REG_WRITE_PRECOMP_CYLINDER:
philpem@112 278 break;
philpem@112 279 case WD2010_REG_SECTOR_COUNT:
philpem@112 280 ctx->sector_count = val;
philpem@112 281 break;
philpem@112 282 case WD2010_REG_SECTOR_NUMBER:
philpem@112 283 ctx->sector_number = val;
philpem@112 284 break;
philpem@112 285 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
philpem@112 286 ctx->cylinder_high_reg = val;
philpem@112 287 break;
philpem@112 288 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
philpem@112 289 ctx->cylinder_low_reg = val;
philpem@112 290 break;
philpem@112 291 case WD2010_REG_SDH:
philpem@112 292 /*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
philpem@112 293 ctx->data_pos = ctx->data_len = 0;
philpem@112 294 ctx->sdh = val;
philpem@112 295 break;
philpem@112 296 case WD2010_REG_COMMAND: // Command register
philpem@112 297 // write to command register clears interrupt request
philpem@112 298 ctx->irq = false;
philpem@112 299 ctx->error_reg = 0;
philpem@112 300
philpem@112 301 /*cpu_log_enabled = 1;*/
philpem@112 302 switch (cmd) {
philpem@112 303 case CMD_RESTORE:
philpem@112 304 // Restore. Set track to 0 and throw an IRQ.
philpem@112 305 ctx->track = 0;
philpem@112 306 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
philpem@112 307 break;
philpem@112 308 case CMD_SCAN_ID:
philpem@112 309 ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
philpem@112 310 ctx->cylinder_low_reg = ctx->track & 0xff;
philpem@112 311 ctx->sector_number = ctx->sector;
philpem@112 312 ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
philpem@112 313 case CMD_WRITE_FORMAT:
philpem@112 314 case CMD_SEEK:
philpem@112 315 case CMD_READ_SECTOR:
philpem@112 316 case CMD_WRITE_SECTOR:
philpem@112 317 // Seek. Seek to the track specced in the cylinder
philpem@112 318 // registers.
philpem@112 319 new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
philpem@112 320 if (new_track < ctx->geom_tracks) {
philpem@112 321 ctx->track = new_track;
philpem@112 322 } else {
philpem@112 323 // Seek error. :(
philpem@112 324 ctx->status = SR_ERROR;
philpem@112 325 ctx->error_reg = ER_ID_NOT_FOUND;
philpem@112 326 ctx->irq = true;
philpem@112 327 break;
philpem@112 328 }
philpem@116 329 // The SDH register provides 3 head select bits; the 4th comes from MCR2.
philpem@116 330 ctx->head = (ctx->sdh & 0x07) + (ctx->mcr2_hdsel3 ? 8 : 0);
philpem@112 331 ctx->sector = ctx->sector_number;
philpem@112 332
philpem@112 333 ctx->formatting = cmd == CMD_WRITE_FORMAT;
philpem@112 334 switch (cmd){
philpem@112 335 case CMD_SEEK:
philpem@112 336 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
philpem@112 337 break;
philpem@112 338 case CMD_READ_SECTOR:
philpem@112 339 /*XXX: does a separate function to set the head have to be added?*/
philpem@112 340 LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
philpem@112 341
philpem@112 342 // Read Sector
philpem@112 343
philpem@112 344 // Check to see if the cyl, hd and sec are valid
philpem@112 345 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) {
philpem@112 346 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
philpem@112 347 ctx->track, ctx->head, ctx->sector,
philpem@112 348 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1);
philpem@112 349 // CHS parameters exceed limits
philpem@112 350 ctx->status = SR_ERROR;
philpem@112 351 ctx->error_reg = ER_ID_NOT_FOUND;
philpem@112 352 // Set IRQ
philpem@112 353 ctx->irq = true;
philpem@112 354 break;
philpem@112 355 }
philpem@112 356
philpem@112 357 // reset data pointers
philpem@112 358 ctx->data_pos = ctx->data_len = 0;
philpem@112 359
philpem@112 360 if (val & CMD_MULTI_SECTOR){
philpem@112 361 ctx->multi_sector = 1;
philpem@112 362 sector_count = ctx->sector_count;
philpem@112 363 }else{
philpem@112 364 ctx->multi_sector = 0;
philpem@112 365 sector_count = 1;
philpem@112 366 }
philpem@112 367 for (int i=0; i<sector_count; i++) {
philpem@112 368 // Calculate the LBA address of the required sector
philpem@112 369 // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
philpem@112 370 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
philpem@112 371 // convert LBA to byte address
philpem@112 372 lba *= ctx->geom_secsz;
philpem@112 373 LOG("\tREAD lba = %lu", lba);
philpem@112 374
philpem@112 375 // Read the sector from the file
philpem@112 376 fseek(ctx->disc_image, lba, SEEK_SET);
philpem@112 377 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
philpem@112 378 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
philpem@112 379 LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
philpem@112 380 }
philpem@112 381
philpem@112 382 ctx->status = 0;
philpem@112 383 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
philpem@112 384 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
philpem@112 385
philpem@112 386 break;
philpem@112 387 case CMD_WRITE_FORMAT:
philpem@112 388 ctx->sector = 0;
philpem@112 389 case CMD_WRITE_SECTOR:
philpem@112 390 LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
philpem@112 391 // Read Sector
philpem@112 392
philpem@112 393 // Check to see if the cyl, hd and sec are valid
philpem@112 394 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) {
philpem@112 395 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
philpem@112 396 ctx->track, ctx->head, ctx->sector,
philpem@112 397 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
philpem@112 398 // CHS parameters exceed limits
philpem@112 399 ctx->status = SR_ERROR;
philpem@112 400 ctx->error_reg = ER_ID_NOT_FOUND;
philpem@112 401 // Set IRQ
philpem@112 402 ctx->irq = true;
philpem@112 403 break;
philpem@112 404 }
philpem@112 405
philpem@112 406 // reset data pointers
philpem@112 407 ctx->data_pos = ctx->data_len = 0;
philpem@112 408
philpem@112 409 if (val & CMD_MULTI_SECTOR){
philpem@112 410 ctx->multi_sector = 1;
philpem@112 411 sector_count = ctx->sector_count;
philpem@112 412 }else{
philpem@112 413 ctx->multi_sector = 0;
philpem@112 414 sector_count = 1;
philpem@112 415 }
philpem@112 416 ctx->data_len = ctx->geom_secsz * sector_count;
philpem@112 417 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
philpem@112 418 // convert LBA to byte address
philpem@112 419 ctx->write_pos = lba * ctx->geom_secsz;
philpem@112 420 LOG("\tWRITE lba = %lu", lba);
philpem@112 421
philpem@112 422 ctx->status = 0;
philpem@112 423 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
philpem@112 424 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
philpem@112 425
philpem@112 426 break;
philpem@112 427 default:
philpem@112 428 LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
philpem@112 429 break;
philpem@112 430 }
philpem@112 431 break;
philpem@112 432 case CMD_2010_EXT: /* not implemented */
philpem@112 433 default:
philpem@112 434 LOG("WD2010: unknown command %x\n", cmd);
philpem@112 435 ctx->status = SR_ERROR;
philpem@112 436 ctx->error_reg = ER_ABORTED_COMMAND;
philpem@112 437 ctx->irq = true;
philpem@112 438 break;
philpem@112 439 }
philpem@112 440 break;
philpem@112 441
philpem@112 442 }
philpem@112 443 }
philpem@112 444