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).
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 }