Tue, 15 Nov 2011 10:12:37 +0000
[musashi] Fix handling of bus errors
Patch-Author: Andrew Warkentin <andreww591!gmail>
Patch-MessageID: <4EC200CE.2020304@gmail.com>
I have fixed the first page fault test failure in FreeBee (the page fault test now hangs rather than errors out, because it is trying to read from the hard drive to test DMA page faults).
There were actually two bugs (the first bug was masking the second one).
First, the ancient version of Musashi that you used is unable to properly resume from bus errors that happen in the middle of certain instructions (some instructions are fetched in stages, with the PC being advanced to each part of the instruction, so basically what happens is the CPU core attempts to read the memory location referenced by the first operand, the bus error occurs, causing the PC to jump to the exception vector, but the faulting instruction is still in the middle of being fetched, so the PC is then advanced past the beginning of the exception handler). I fixed this by delaying the jump to the bus error vector until after the faulting instruction finishes.
The second bug is simpler - you had the UDS and LDS bits in BSR0 inverted (they are supposed to be active low).
1 #include <stdint.h>
2 #include <stdbool.h>
3 #include <malloc.h>
4 #include "musashi/m68k.h"
5 #include "wd279x.h"
7 #ifndef WD279X_DEBUG
8 #define NDEBUG
9 #endif
10 #include "utils.h"
12 /// WD2797 command constants
13 enum {
14 CMD_MASK = 0xF0, ///< Bit mask to detect command bits
15 CMD_RESTORE = 0x00, ///< Restore (recalibrate, seek to track 0)
16 CMD_SEEK = 0x10, ///< Seek to given track
17 CMD_STEP = 0x20, ///< Step
18 CMD_STEP_TU = 0x30, ///< Step and update track register
19 CMD_STEPIN = 0x40, ///< Step In
20 CMD_STEPIN_TU = 0x50, ///< Step In and update track register
21 CMD_STEPOUT = 0x60, ///< Step Out
22 CMD_STEPOUT_TU = 0x70, ///< Step Out and update track register
23 CMD_READ_SECTOR = 0x80, ///< Read Sector
24 CMD_READ_SECTOR_MULTI = 0x90, ///< Read Multiple Sectors
25 CMD_WRITE_SECTOR = 0xA0, ///< Write Sector
26 CMD_WRITE_SECTOR_MULTI = 0xB0, ///< Write Multiple Sectors
27 CMD_READ_ADDRESS = 0xC0, ///< Read Address (IDAM contents)
28 CMD_FORCE_INTERRUPT = 0xD0, ///< Force Interrupt
29 CMD_READ_TRACK = 0xE0, ///< Read Track
30 CMD_FORMAT_TRACK = 0xF0 ///< Format Track
31 };
34 void wd2797_init(WD2797_CTX *ctx)
35 {
36 // track, head and sector unknown
37 ctx->track = ctx->head = ctx->sector = 0;
39 // no IRQ pending
40 ctx->irq = false;
42 // no data available
43 ctx->data_pos = ctx->data_len = 0;
44 ctx->data = NULL;
46 // Status register clear, not busy; type1 command
47 ctx->status = 0;
48 ctx->cmd_has_drq = false;
50 // Clear data register
51 ctx->data_reg = 0;
53 // Last step direction = "towards zero"
54 ctx->last_step_dir = -1;
56 // No disc image loaded
57 ctx->disc_image = NULL;
58 ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0;
59 }
62 void wd2797_reset(WD2797_CTX *ctx)
63 {
64 // track, head and sector unknown
65 ctx->track = ctx->head = ctx->sector = 0;
67 // no IRQ pending
68 ctx->irq = false;
70 // no data available
71 ctx->data_pos = ctx->data_len = 0;
73 // Status register clear, not busy
74 ctx->status = 0;
76 // Clear data register
77 ctx->data_reg = 0;
79 // Last step direction
80 ctx->last_step_dir = -1;
81 }
84 void wd2797_done(WD2797_CTX *ctx)
85 {
86 // Reset the WD2797
87 wd2797_reset(ctx);
89 // Free any allocated memory
90 if (ctx->data) {
91 free(ctx->data);
92 ctx->data = NULL;
93 }
94 }
97 bool wd2797_get_irq(WD2797_CTX *ctx)
98 {
99 return ctx->irq;
100 }
103 bool wd2797_get_drq(WD2797_CTX *ctx)
104 {
105 return (ctx->data_pos < ctx->data_len);
106 }
109 WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
110 {
111 size_t filesize;
113 // Start by finding out how big the image file is
114 fseek(fp, 0, SEEK_END);
115 filesize = ftell(fp);
116 fseek(fp, 0, SEEK_SET);
118 // Now figure out how many tracks it contains
119 int tracks = filesize / secsz / spt / heads;
120 // Confirm...
121 if (tracks < 1) {
122 return WD2797_ERR_BAD_GEOM;
123 }
125 // Allocate enough memory to store one disc track
126 if (ctx->data) {
127 free(ctx->data);
128 }
129 ctx->data = malloc(secsz * spt);
130 if (!ctx->data)
131 return WD2797_ERR_NO_MEMORY;
133 // Load the image and the geometry data
134 ctx->disc_image = fp;
135 ctx->geom_tracks = tracks;
136 ctx->geom_secsz = secsz;
137 ctx->geom_heads = heads;
138 ctx->geom_spt = spt;
140 return WD2797_ERR_OK;
141 }
144 void wd2797_unload(WD2797_CTX *ctx)
145 {
146 // Free memory buffer
147 if (ctx->data) {
148 free(ctx->data);
149 ctx->data = NULL;
150 }
152 // Clear file pointer
153 ctx->disc_image = NULL;
155 // Clear the disc geometry
156 ctx->geom_tracks = ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = 0;
157 }
160 uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr)
161 {
162 uint8_t temp = 0;
164 switch (addr & 0x03) {
165 case WD2797_REG_STATUS: // Status register
166 // Read from status register clears IRQ
167 ctx->irq = false;
169 // Get current status flags (set by last command)
170 // DRQ bit
171 if (ctx->cmd_has_drq) {
172 temp = ctx->status & ~0x03;
173 temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
174 LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
175 } else {
176 temp = ctx->status & ~0x01;
177 }
178 // FDC is busy if there is still data in the buffer
179 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!
180 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
181 return temp;
183 case WD2797_REG_TRACK: // Track register
184 return ctx->track;
186 case WD2797_REG_SECTOR: // Sector register
187 return ctx->sector;
189 case WD2797_REG_DATA: // Data register
190 // If there's data in the buffer, return it. Otherwise return 0xFF.
191 if (ctx->data_pos < ctx->data_len) {
192 // set IRQ if this is the last data byte
193 if (ctx->data_pos == (ctx->data_len-1)) {
194 // Set IRQ
195 ctx->irq = true;
196 }
197 // return data byte and increment pointer
198 return ctx->data[ctx->data_pos++];
199 } else {
200 // command finished
201 return 0xff;
202 }
204 default:
205 // shut up annoying compilers which don't recognise unreachable code when they see it
206 // (here's looking at you, gcc!)
207 return 0xff;
208 }
209 }
212 void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val)
213 {
214 uint8_t cmd = val & CMD_MASK;
215 size_t lba;
216 bool is_type1 = false;
217 int temp;
219 m68k_end_timeslice();
221 switch (addr) {
222 case WD2797_REG_COMMAND: // Command register
223 // write to command register clears interrupt request
224 ctx->irq = false;
226 // Is the drive ready?
227 if (ctx->disc_image == NULL) {
228 // No disc image, thus the drive is busy.
229 ctx->status = 0x80;
230 return;
231 }
233 // Handle Type 1 commands
234 switch (cmd) {
235 case CMD_RESTORE:
236 // Restore. Set track to 0 and throw an IRQ.
237 is_type1 = true;
238 ctx->track = 0;
239 break;
241 case CMD_SEEK:
242 // Seek. Seek to the track specced in the Data Register.
243 is_type1 = true;
244 if (ctx->data_reg < ctx->geom_tracks) {
245 ctx->track = ctx->data_reg;
246 } else {
247 // Seek error. :(
248 ctx->status = 0x10;
249 }
251 case CMD_STEP:
252 // TODO! deal with trk0!
253 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
254 is_type1 = true;
255 break;
257 case CMD_STEPIN:
258 case CMD_STEPOUT:
259 // TODO! deal with trk0!
260 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
261 if (cmd == CMD_STEPIN) {
262 ctx->last_step_dir = 1;
263 } else {
264 ctx->last_step_dir = -1;
265 }
266 is_type1 = true;
267 break;
269 case CMD_STEP_TU:
270 case CMD_STEPIN_TU:
271 case CMD_STEPOUT_TU:
272 // if this is a Step In or Step Out cmd, set the step-direction
273 if (cmd == CMD_STEPIN_TU) {
274 ctx->last_step_dir = 1;
275 } else if (cmd == CMD_STEPOUT_TU) {
276 ctx->last_step_dir = -1;
277 }
279 // Seek one step in the last direction used.
280 ctx->track += ctx->last_step_dir;
281 if (ctx->track < 0) ctx->track = 0;
282 if (ctx->track >= ctx->geom_tracks) {
283 // Seek past end of disc... that'll be a Seek Error then.
284 ctx->status = 0x10;
285 ctx->track = ctx->geom_tracks - 1;
286 }
287 is_type1 = true;
288 break;
290 default:
291 break;
292 }
294 if (is_type1) {
295 // Terminate any sector reads or writes
296 ctx->data_len = ctx->data_pos = 0;
298 // No DRQ bit for these commands.
299 ctx->cmd_has_drq = false;
301 // Type1 status byte...
302 ctx->status = 0;
303 // S7 = Not Ready. Command executed, therefore the drive was ready... :)
304 // S6 = Write Protect. TODO: add this
305 // S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
306 ctx->status |= 0x20;
307 // S4 = Seek Error. Not bloody likely if we got down here...!
308 // S3 = CRC Error. Not gonna happen on a disc image!
309 // S2 = Track 0
310 ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
311 // S1 = Index Pulse. TODO -- need periodics to emulate this
312 // S0 = Busy. We just exec'd the command, thus we're not busy.
313 // TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
315 // Set IRQ
316 ctx->irq = true;
317 return;
318 }
320 // That's the Type 1 (seek) commands sorted. Now for the others.
322 // All these commands return the DRQ bit...
323 ctx->cmd_has_drq = true;
325 // If drive isn't ready, then set status B7 and exit
326 if (ctx->disc_image == NULL) {
327 ctx->status = 0x80;
328 return;
329 }
331 // If this is a Write command, check write protect status too
332 // TODO!
333 if (false) {
334 // Write protected disc...
335 if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
336 // Set Write Protect bit and bail.
337 ctx->status = 0x40;
339 // Set IRQ
340 ctx->irq = true;
342 return;
343 }
344 }
346 // Disc is ready to go. Parse the command word.
347 switch (cmd) {
348 case CMD_READ_ADDRESS:
349 // Read Address
350 ctx->head = (val & 0x02) ? 1 : 0;
352 // reset data pointers
353 ctx->data_pos = ctx->data_len = 0;
355 // load data buffer
356 ctx->data[ctx->data_len++] = ctx->track;
357 ctx->data[ctx->data_len++] = ctx->head;
358 ctx->data[ctx->data_len++] = ctx->sector;
359 switch (ctx->geom_secsz) {
360 case 128: ctx->data[ctx->data_len++] = 0; break;
361 case 256: ctx->data[ctx->data_len++] = 1; break;
362 case 512: ctx->data[ctx->data_len++] = 2; break;
363 case 1024: ctx->data[ctx->data_len++] = 3; break;
364 default: ctx->data[ctx->data_len++] = 0xFF; break; // TODO: deal with invalid values better
365 }
366 ctx->data[ctx->data_len++] = 0; // TODO: IDAM CRC!
367 ctx->data[ctx->data_len++] = 0;
369 ctx->status = 0;
370 // B6, B5 = 0
371 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
372 // B3 = CRC Error. Not possible.
373 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
374 // B1 = DRQ. Data request.
375 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
376 break;
378 case CMD_READ_SECTOR:
379 case CMD_READ_SECTOR_MULTI:
380 ctx->head = (val & 0x02) ? 1 : 0;
381 LOG("WD279X: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
382 // Read Sector or Read Sector Multiple
384 // Check to see if the cyl, hd and sec are valid
385 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt) || (ctx->sector == 0)) {
386 LOG("*** WD2797 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
387 ctx->track, ctx->head, ctx->sector,
388 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
389 // CHS parameters exceed limits
390 ctx->status = 0x10; // Record Not Found
391 break;
392 // Set IRQ
393 ctx->irq = true;
394 }
396 // reset data pointers
397 ctx->data_pos = ctx->data_len = 0;
399 // Calculate number of sectors to read from disc
400 if (cmd == CMD_READ_SECTOR_MULTI)
401 temp = ctx->geom_spt;
402 else
403 temp = 1;
405 for (int i=0; i<temp; i++) {
406 // Calculate the LBA address of the required sector
407 // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
408 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i) - 1;
409 // convert LBA to byte address
410 lba *= ctx->geom_secsz;
411 LOG("\tREAD lba = %lu", lba);
413 // Read the sector from the file
414 fseek(ctx->disc_image, lba, SEEK_SET);
415 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
416 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
417 LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
418 }
420 ctx->status = 0;
421 // B6 = 0
422 // B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
423 // B4 = Record Not Found. Basically, the CHS parameters are bullcrap.
424 // B3 = CRC Error. Not possible.
425 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
426 // B1 = DRQ. Data request.
427 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
428 break;
430 case CMD_READ_TRACK:
431 // Read Track
432 // TODO! implement this
433 ctx->head = (val & 0x02) ? 1 : 0;
434 ctx->status = 0;
435 // B6, B5, B4, B3 = 0
436 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
437 // B1 = DRQ. Data request.
438 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
439 break;
441 case CMD_WRITE_SECTOR:
442 case CMD_WRITE_SECTOR_MULTI:
443 // Write Sector or Write Sector Multiple
445 ctx->head = (val & 0x02) ? 1 : 0;
446 // reset data pointers
447 ctx->data_pos = ctx->data_len = 0;
449 // TODO: set "write pending" flag, and write LBA, and go from there.
451 ctx->status = 0;
452 // B6 = Write Protect. FIXME -- emulate this!
453 // B5 = 0
454 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
455 // B3 = CRC Error. Not possible.
456 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
457 // B1 = DRQ. Data request.
458 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
459 break;
461 case CMD_FORMAT_TRACK:
462 // Write Track (aka Format Track)
463 ctx->head = (val & 0x02) ? 1 : 0;
464 ctx->status = 0;
465 // B6 = Write Protect. FIXME -- emulate this!
466 // B5, B4, B3 = 0
467 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
468 // B1 = DRQ. Data request.
469 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
470 break;
472 case CMD_FORCE_INTERRUPT:
473 // Force Interrupt...
474 // Terminates current operation and sends an interrupt
475 // TODO!
476 ctx->status = 0;
477 ctx->data_pos = ctx->data_len = 0;
478 // Set IRQ
479 ctx->irq = true;
480 break;
481 }
482 break;
484 case WD2797_REG_TRACK: // Track register
485 ctx->track = val;
486 break;
488 case WD2797_REG_SECTOR: // Sector register
489 ctx->sector = val;
490 break;
492 case WD2797_REG_DATA: // Data register
493 // Save the value written into the data register
494 ctx->data_reg = val;
496 // If we're processing a write command, and there's space in the
497 // buffer, allow the write.
498 if (ctx->data_pos < ctx->data_len) {
499 // set IRQ if this is the last data byte
500 if (ctx->data_pos == (ctx->data_len-1)) {
501 // Set IRQ
502 ctx->irq = true;
503 }
505 // store data byte and increment pointer
506 ctx->data[ctx->data_pos++] = val;
507 }
508 break;
509 }
510 }