Mon, 14 Jan 2013 09:48:21 +0000
Handle memory more gracefully
Fixed in this cset:
* Return an 'empty space' value if the memory wraps around
* Allow the 'empty bus' value to be changed via an ifdef
* Properly handle situations where expansion memory is turned off
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;
38 ctx->track_reg = 0;
40 // no IRQ pending
41 ctx->irq = false;
43 // no data available
44 ctx->data_pos = ctx->data_len = 0;
45 ctx->data = NULL;
47 // Status register clear, not busy; type1 command
48 ctx->status = 0;
49 ctx->cmd_has_drq = false;
51 // No format command in progress
52 ctx->formatting = false;
54 // Clear data register
55 ctx->data_reg = 0;
57 // Last step direction = "towards zero"
58 ctx->last_step_dir = -1;
60 // No disc image loaded
61 ctx->disc_image = NULL;
62 ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0;
63 }
66 void wd2797_reset(WD2797_CTX *ctx)
67 {
68 // track, head and sector unknown
69 ctx->track = ctx->head = ctx->sector = 0;
70 ctx->track_reg = 0;
72 // no IRQ pending
73 ctx->irq = false;
75 // no data available
76 ctx->data_pos = ctx->data_len = 0;
78 // Status register clear, not busy
79 ctx->status = 0;
81 // Clear data register
82 ctx->data_reg = 0;
84 // Last step direction
85 ctx->last_step_dir = -1;
86 }
89 void wd2797_done(WD2797_CTX *ctx)
90 {
91 // Reset the WD2797
92 wd2797_reset(ctx);
94 // Free any allocated memory
95 if (ctx->data) {
96 free(ctx->data);
97 ctx->data = NULL;
98 }
99 }
102 bool wd2797_get_irq(WD2797_CTX *ctx)
103 {
104 return ctx->irq;
105 }
107 bool wd2797_get_drq(WD2797_CTX *ctx)
108 {
109 return (ctx->data_pos < ctx->data_len);
110 }
113 WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int spt, int heads, int writeable)
114 {
115 size_t filesize;
117 // Start by finding out how big the image file is
118 fseek(fp, 0, SEEK_END);
119 filesize = ftell(fp);
120 fseek(fp, 0, SEEK_SET);
122 // Now figure out how many tracks it contains
123 int tracks = filesize / secsz / spt / heads;
124 // Confirm...
125 if (tracks < 1) {
126 return WD2797_ERR_BAD_GEOM;
127 }
129 // Allocate enough memory to store one disc track
130 if (ctx->data) {
131 free(ctx->data);
132 }
133 ctx->data = malloc(secsz * spt);
134 if (!ctx->data)
135 return WD2797_ERR_NO_MEMORY;
137 // Load the image and the geometry data
138 ctx->disc_image = fp;
139 ctx->geom_tracks = tracks;
140 ctx->geom_secsz = secsz;
141 ctx->geom_heads = heads;
142 ctx->geom_spt = spt;
143 ctx->writeable = writeable;
144 return WD2797_ERR_OK;
145 }
148 void wd2797_unload(WD2797_CTX *ctx)
149 {
150 // Free memory buffer
151 if (ctx->data) {
152 free(ctx->data);
153 ctx->data = NULL;
154 }
156 // Clear file pointer
157 ctx->disc_image = NULL;
159 // Clear the disc geometry
160 ctx->geom_tracks = ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = 0;
161 }
164 uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr)
165 {
166 uint8_t temp = 0;
167 m68k_end_timeslice();
169 switch (addr & 0x03) {
170 case WD2797_REG_STATUS: // Status register
171 // Read from status register clears IRQ
172 ctx->irq = false;
174 // Get current status flags (set by last command)
175 // DRQ bit
176 if (ctx->cmd_has_drq) {
177 temp = ctx->status & ~0x03;
178 temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
179 LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
180 } else {
181 temp = ctx->status & ~0x01;
182 }
183 // FDC is busy if there is still data in the buffer
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!
185 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
186 return temp;
188 case WD2797_REG_TRACK: // Track register
189 return ctx->track_reg;
191 case WD2797_REG_SECTOR: // Sector register
192 return ctx->sector;
194 case WD2797_REG_DATA: // Data register
195 // If there's data in the buffer, return it. Otherwise return 0xFF.
196 if (ctx->data_pos < ctx->data_len) {
197 // set IRQ if this is the last data byte
198 if (ctx->data_pos == (ctx->data_len-1)) {
199 // Set IRQ
200 ctx->irq = true;
201 }
202 // return data byte and increment pointer
203 return ctx->data[ctx->data_pos++];
204 } else {
205 // command finished
206 return ctx->data_reg;
207 }
209 default:
210 // shut up annoying compilers which don't recognise unreachable code when they see it
211 // (here's looking at you, gcc!)
212 return 0xff;
213 }
214 }
217 void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val)
218 {
219 uint8_t cmd = val & CMD_MASK;
220 size_t lba;
221 bool is_type1 = false;
222 int temp;
223 m68k_end_timeslice();
225 switch (addr) {
226 case WD2797_REG_COMMAND: // Command register
227 // write to command register clears interrupt request
228 ctx->irq = false;
230 // Is the drive ready?
231 if (ctx->disc_image == NULL) {
232 // No disc image, thus the drive is busy.
233 ctx->status = 0x80;
234 return;
235 }
237 // Handle Type 1 commands
238 switch (cmd) {
239 case CMD_RESTORE:
240 // Restore. Set track to 0 and throw an IRQ.
241 is_type1 = true;
242 ctx->track = ctx->track_reg = 0;
243 break;
245 case CMD_SEEK:
246 // Seek. Seek to the track specced in the Data Register.
247 is_type1 = true;
248 if (ctx->data_reg < ctx->geom_tracks) {
249 ctx->track = ctx->track_reg = ctx->data_reg;
250 } else {
251 // Seek error. :(
252 ctx->status = 0x10;
253 }
255 case CMD_STEP:
256 // TODO! deal with trk0!
257 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
258 is_type1 = true;
259 break;
261 case CMD_STEPIN:
262 case CMD_STEPOUT:
263 case CMD_STEP_TU:
264 case CMD_STEPIN_TU:
265 case CMD_STEPOUT_TU:
266 // if this is a Step In or Step Out cmd, set the step-direction
267 if ((cmd & ~0x10) == CMD_STEPIN) {
268 ctx->last_step_dir = 1;
269 } else if ((cmd & ~0x10) == CMD_STEPOUT) {
270 ctx->last_step_dir = -1;
271 }
274 // Seek one step in the last direction used.
275 ctx->track += ctx->last_step_dir;
276 if (ctx->track < 0) ctx->track = 0;
277 if (ctx->track >= ctx->geom_tracks) {
278 // Seek past end of disc... that'll be a Seek Error then.
279 ctx->status = 0x10;
280 ctx->track = ctx->geom_tracks - 1;
281 }
282 if (cmd & 0x10){
283 ctx->track_reg = ctx->track;
284 }
286 is_type1 = true;
287 break;
289 default:
290 break;
291 }
293 if (is_type1) {
294 // Terminate any sector reads or writes
295 ctx->data_len = ctx->data_pos = 0;
297 // No DRQ bit for these commands.
298 ctx->cmd_has_drq = false;
300 // Type1 status byte...
301 ctx->status = 0;
302 // S7 = Not Ready. Command executed, therefore the drive was ready... :)
303 // S6 = Write Protect. TODO: add this
304 // S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
305 ctx->status |= 0x20;
306 // S4 = Seek Error. Not bloody likely if we got down here...!
307 // S3 = CRC Error. Not gonna happen on a disc image!
308 // S2 = Track 0
309 ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
310 // S1 = Index Pulse. TODO -- need periodics to emulate this
311 // S0 = Busy. We just exec'd the command, thus we're not busy.
312 // TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
314 // Set IRQ
315 ctx->irq = true;
316 return;
317 }
319 // That's the Type 1 (seek) commands sorted. Now for the others.
321 // All these commands return the DRQ bit...
322 ctx->cmd_has_drq = true;
324 // If drive isn't ready, then set status B7 and exit
325 if (ctx->disc_image == NULL) {
326 ctx->status = 0x80;
327 return;
328 }
330 // If this is a Write command, check write protect status too
331 if (!ctx->writeable) {
332 // Write protected disc...
333 if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
334 // Set Write Protect bit and bail.
335 ctx->status = 0x40;
337 // Set IRQ
338 ctx->irq = true;
340 return;
341 }
342 }
344 // Disc is ready to go. Parse the command word.
345 switch (cmd) {
346 case CMD_READ_ADDRESS:
347 // Read Address
348 ctx->head = (val & 0x02) ? 1 : 0;
350 // reset data pointers
351 ctx->data_pos = ctx->data_len = 0;
353 // load data buffer
354 ctx->data[ctx->data_len++] = ctx->track;
355 ctx->data[ctx->data_len++] = ctx->head;
356 ctx->data[ctx->data_len++] = ctx->sector;
357 switch (ctx->geom_secsz) {
358 case 128: ctx->data[ctx->data_len++] = 0; break;
359 case 256: ctx->data[ctx->data_len++] = 1; break;
360 case 512: ctx->data[ctx->data_len++] = 2; break;
361 case 1024: ctx->data[ctx->data_len++] = 3; break;
362 default: ctx->data[ctx->data_len++] = 0xFF; break; // TODO: deal with invalid values better
363 }
364 ctx->data[ctx->data_len++] = 0; // TODO: IDAM CRC!
365 ctx->data[ctx->data_len++] = 0;
367 ctx->status = 0;
368 // B6, B5 = 0
369 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
370 // B3 = CRC Error. Not possible.
371 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
372 // B1 = DRQ. Data request.
373 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
374 break;
376 case CMD_READ_SECTOR:
377 case CMD_READ_SECTOR_MULTI:
378 ctx->head = (val & 0x02) ? 1 : 0;
379 LOG("WD279X: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
380 // Read Sector or Read Sector Multiple
382 // Check to see if the cyl, hd and sec are valid
383 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt) || (ctx->sector == 0)) {
384 LOG("*** WD2797 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
385 ctx->track, ctx->head, ctx->sector,
386 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
387 // CHS parameters exceed limits
388 ctx->status = 0x10; // Record Not Found
389 // Set IRQ
390 ctx->irq = true;
391 break;
392 }
394 // reset data pointers
395 ctx->data_pos = ctx->data_len = 0;
397 // Calculate number of sectors to read from disc
398 if (cmd == CMD_READ_SECTOR_MULTI)
399 temp = ctx->geom_spt;
400 else
401 temp = 1;
403 for (int i=0; i<temp; i++) {
404 // Calculate the LBA address of the required sector
405 // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
406 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i) - 1;
407 // convert LBA to byte address
408 lba *= ctx->geom_secsz;
409 LOG("\tREAD lba = %lu", lba);
411 // Read the sector from the file
412 fseek(ctx->disc_image, lba, SEEK_SET);
413 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
414 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
415 LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
416 }
418 ctx->status = 0;
419 // B6 = 0
420 // B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
421 // B4 = Record Not Found. Basically, the CHS parameters are bullcrap.
422 // B3 = CRC Error. Not possible.
423 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
424 // B1 = DRQ. Data request.
425 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
426 break;
428 case CMD_READ_TRACK:
429 // Read Track
430 // TODO! implement this
431 // ctx->head = (val & 0x02) ? 1 : 0;
432 // ctx->status = 0;
433 // B6, B5, B4, B3 = 0
434 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
435 // B1 = DRQ. Data request.
436 // ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
437 ctx->irq = true;
438 ctx->status = 0x10;
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 = 0;
449 // Calculate number of sectors to write to disc
450 if (cmd == CMD_WRITE_SECTOR_MULTI)
451 /*XXX: is this the correct value?*/
452 temp = ctx->geom_spt;
453 else
454 temp = 1;
455 ctx->data_len = temp * ctx->geom_secsz;
457 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector)) - 1;
458 ctx->write_pos = lba * ctx->geom_secsz;
460 ctx->status = 0;
461 // B6 = Write Protect. This would have been set earlier.
462 // B5 = 0
463 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
464 // B3 = CRC Error. Not possible.
465 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
466 // B1 = DRQ. Data request.
467 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
468 break;
470 case CMD_FORMAT_TRACK:
471 // Write Track (aka Format Track)
472 ctx->head = (val & 0x02) ? 1 : 0;
473 ctx->status = 0;
474 // B6 = Write Protect. FIXME -- emulate this!
475 // B5, B4, B3 = 0
476 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
477 ctx->data_pos = 0;
478 ctx->data_len = 7170;
479 // B1 = DRQ. Data request.
480 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
481 ctx->formatting = true;
482 break;
484 case CMD_FORCE_INTERRUPT:
485 // Force Interrupt...
486 // Terminates current operation and sends an interrupt
487 // TODO!
488 ctx->status = 0x20;
489 if (!ctx->writeable){
490 ctx->status |= 0x40;
491 }
492 if (ctx->track == 0){
493 ctx->status = 0x04;
494 }
495 ctx->data_pos = ctx->data_len = 0;
496 if (cmd & 8){
497 // Set IRQ
498 ctx->irq = true;
499 }
500 break;
501 }
502 break;
504 case WD2797_REG_TRACK: // Track register
505 ctx->track = ctx->track_reg = val;
506 break;
508 case WD2797_REG_SECTOR: // Sector register
509 ctx->sector = val;
510 break;
512 case WD2797_REG_DATA: // Data register
513 // Save the value written into the data register
514 ctx->data_reg = val;
515 // If we're processing a write command, and there's space in the
516 // buffer, allow the write.
517 if (ctx->data_pos < ctx->data_len && (ctx->write_pos >= 0 || ctx->formatting)) {
518 if (!ctx->formatting) ctx->data[ctx->data_pos] = val;
519 // store data byte and increment pointer
520 ctx->data_pos++;
522 // set IRQ and write data if this is the last data byte
523 if (ctx->data_pos == ctx->data_len) {
524 if (!ctx->formatting){
525 fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
526 fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
527 fflush(ctx->disc_image);
528 }
529 // Set IRQ and reset write pointer
530 ctx->irq = true;
531 ctx->write_pos = -1;
532 ctx->formatting = false;
533 }
535 }
536 break;
537 }
538 }
540 void wd2797_dma_miss(WD2797_CTX *ctx)
541 {
542 ctx->data_pos = ctx->data_len;
543 ctx->write_pos = 0;
544 ctx->status = 4; /* lost data */
545 ctx->irq = true;
546 }