Tue, 28 Dec 2010 19:55:13 +0000
add LOG macro and convert WD279x printfs to LOG() calls
1 #include <stdint.h>
2 #include <stdbool.h>
3 #include <malloc.h>
4 #include "musashi/m68k.h"
5 #include "wd279x.h"
7 #define NDEBUG
8 #include "utils.h"
10 /// WD2797 command constants
11 enum {
12 CMD_MASK = 0xF0, ///< Bit mask to detect command bits
13 CMD_RESTORE = 0x00, ///< Restore (recalibrate, seek to track 0)
14 CMD_SEEK = 0x10, ///< Seek to given track
15 CMD_STEP = 0x20, ///< Step
16 CMD_STEP_TU = 0x30, ///< Step and update track register
17 CMD_STEPIN = 0x40, ///< Step In
18 CMD_STEPIN_TU = 0x50, ///< Step In and update track register
19 CMD_STEPOUT = 0x60, ///< Step Out
20 CMD_STEPOUT_TU = 0x70, ///< Step Out and update track register
21 CMD_READ_SECTOR = 0x80, ///< Read Sector
22 CMD_READ_SECTOR_MULTI = 0x90, ///< Read Multiple Sectors
23 CMD_WRITE_SECTOR = 0xA0, ///< Write Sector
24 CMD_WRITE_SECTOR_MULTI = 0xB0, ///< Write Multiple Sectors
25 CMD_READ_ADDRESS = 0xC0, ///< Read Address (IDAM contents)
26 CMD_FORCE_INTERRUPT = 0xD0, ///< Force Interrupt
27 CMD_READ_TRACK = 0xE0, ///< Read Track
28 CMD_FORMAT_TRACK = 0xF0 ///< Format Track
29 };
32 void wd2797_init(WD2797_CTX *ctx)
33 {
34 // track, head and sector unknown
35 ctx->track = ctx->head = ctx->sector = 0;
37 // no IRQ pending
38 ctx->irql = ctx->irqe = false;
40 // no data available
41 ctx->data_pos = ctx->data_len = 0;
42 ctx->data = NULL;
44 // Status register clear, not busy
45 ctx->status = 0;
47 // Clear data register
48 ctx->data_reg = 0;
50 // Last step direction
51 ctx->last_step_dir = -1;
53 // No disc image loaded
54 ctx->disc_image = NULL;
55 ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0;
56 }
59 void wd2797_reset(WD2797_CTX *ctx)
60 {
61 // track, head and sector unknown
62 ctx->track = ctx->head = ctx->sector = 0;
64 // no IRQ pending
65 ctx->irql = ctx->irqe = false;
67 // no data available
68 ctx->data_pos = ctx->data_len = 0;
70 // Status register clear, not busy
71 ctx->status = 0;
73 // Clear data register
74 ctx->data_reg = 0;
76 // Last step direction
77 ctx->last_step_dir = -1;
78 }
81 void wd2797_done(WD2797_CTX *ctx)
82 {
83 // Reset the WD2797
84 wd2797_reset(ctx);
86 // Free any allocated memory
87 if (ctx->data) {
88 free(ctx->data);
89 ctx->data = NULL;
90 }
91 }
94 bool wd2797_get_irq(WD2797_CTX *ctx)
95 {
96 // If an IRQ is pending, clear it and return true, otherwise return false
97 if (ctx->irqe) {
98 ctx->irqe = false;
99 return true;
100 } else {
101 return false;
102 }
103 }
106 bool wd2797_get_drq(WD2797_CTX *ctx)
107 {
108 return (ctx->data_pos < ctx->data_len);
109 }
112 WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
113 {
114 size_t filesize;
116 // Start by finding out how big the image file is
117 fseek(fp, 0, SEEK_END);
118 filesize = ftell(fp);
119 fseek(fp, 0, SEEK_SET);
121 // Now figure out how many tracks it contains
122 int tracks = filesize / secsz / spt / heads;
123 // Confirm...
124 if (tracks < 1) {
125 return WD2797_ERR_BAD_GEOM;
126 }
128 // Allocate enough memory to store one disc track
129 if (ctx->data) {
130 free(ctx->data);
131 }
132 ctx->data = malloc(secsz * spt);
133 if (!ctx->data)
134 return WD2797_ERR_NO_MEMORY;
136 // Load the image and the geometry data
137 ctx->disc_image = fp;
138 ctx->geom_tracks = tracks;
139 ctx->geom_secsz = secsz;
140 ctx->geom_heads = heads;
141 ctx->geom_spt = spt;
143 return WD2797_ERR_OK;
144 }
147 void wd2797_unload(WD2797_CTX *ctx)
148 {
149 // Free memory buffer
150 if (ctx->data) {
151 free(ctx->data);
152 ctx->data = NULL;
153 }
155 // Clear file pointer
156 ctx->disc_image = NULL;
158 // Clear the disc geometry
159 ctx->geom_tracks = ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = 0;
160 }
163 uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr)
164 {
165 uint8_t temp = 0;
167 switch (addr & 0x03) {
168 case WD2797_REG_STATUS: // Status register
169 // Read from status register clears IRQ
170 ctx->irql = false;
171 ctx->irqe = false;
173 // Get current status flags (set by last command)
174 // DRQ bit
175 if (ctx->cmd_has_drq) {
176 temp = ctx->status & ~0x03;
177 temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
178 LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
179 } else {
180 temp = ctx->status & ~0x01;
181 }
182 // FDC is busy if there is still data in the buffer
183 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!
184 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
185 return temp;
187 case WD2797_REG_TRACK: // Track register
188 return ctx->track;
190 case WD2797_REG_SECTOR: // Sector register
191 return ctx->sector;
193 case WD2797_REG_DATA: // Data register
194 // If there's data in the buffer, return it. Otherwise return 0xFF.
195 if (ctx->data_pos < ctx->data_len) {
196 // set IRQ if this is the last data byte
197 if (ctx->data_pos == (ctx->data_len-1)) {
198 // Set IRQ only if IRQL has been cleared (no pending IRQs)
199 ctx->irqe = ctx->irql ? ctx->irqe : true;
200 ctx->irql = true;
201 }
202 // return data byte and increment pointer
203 return ctx->data[ctx->data_pos++];
204 } else {
205 // command finished
206 return 0xff;
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;
224 m68k_end_timeslice();
226 switch (addr) {
227 case WD2797_REG_COMMAND: // Command register
228 // write to command register clears interrupt request
229 ctx->irql = false;
231 // Is the drive ready?
232 if (ctx->disc_image == NULL) {
233 // No disc image, thus the drive is busy.
234 ctx->status = 0x80;
235 return;
236 }
238 // Handle Type 1 commands
239 switch (cmd) {
240 case CMD_RESTORE:
241 // Restore. Set track to 0 and throw an IRQ.
242 is_type1 = true;
243 ctx->track = 0;
244 break;
246 case CMD_SEEK:
247 // Seek. Seek to the track specced in the Data Register.
248 is_type1 = true;
249 if (ctx->data_reg < ctx->geom_tracks) {
250 ctx->track = ctx->data_reg;
251 } else {
252 // Seek error. :(
253 ctx->status = 0x10;
254 }
256 case CMD_STEP:
257 // TODO! deal with trk0!
258 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
259 is_type1 = true;
260 break;
262 case CMD_STEPIN:
263 case CMD_STEPOUT:
264 // TODO! deal with trk0!
265 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
266 if (cmd == CMD_STEPIN) {
267 ctx->last_step_dir = 1;
268 } else {
269 ctx->last_step_dir = -1;
270 }
271 is_type1 = true;
272 break;
274 case CMD_STEP_TU:
275 case CMD_STEPIN_TU:
276 case CMD_STEPOUT_TU:
277 // if this is a Step In or Step Out cmd, set the step-direction
278 if (cmd == CMD_STEPIN_TU) {
279 ctx->last_step_dir = 1;
280 } else if (cmd == CMD_STEPOUT_TU) {
281 ctx->last_step_dir = -1;
282 }
284 // Seek one step in the last direction used.
285 ctx->track += ctx->last_step_dir;
286 if (ctx->track < 0) ctx->track = 0;
287 if (ctx->track >= ctx->geom_tracks) {
288 // Seek past end of disc... that'll be a Seek Error then.
289 ctx->status = 0x10;
290 ctx->track = ctx->geom_tracks - 1;
291 }
292 is_type1 = true;
293 break;
295 default:
296 break;
297 }
299 if (is_type1) {
300 // Terminate any sector reads or writes
301 ctx->data_len = ctx->data_pos = 0;
303 // No DRQ bit for these commands.
304 ctx->cmd_has_drq = false;
306 // Type1 status byte...
307 ctx->status = 0;
308 // S7 = Not Ready. Command executed, therefore the drive was ready... :)
309 // S6 = Write Protect. TODO: add this
310 // S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
311 ctx->status |= 0x20;
312 // S4 = Seek Error. Not bloody likely if we got down here...!
313 // S3 = CRC Error. Not gonna happen on a disc image!
314 // S2 = Track 0
315 ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
316 // S1 = Index Pulse. TODO -- need periodics to emulate this
317 // S0 = Busy. We just exec'd the command, thus we're not busy.
318 // TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
320 // Set IRQ only if IRQL has been cleared (no pending IRQs)
321 ctx->irqe = ctx->irql ? ctx->irqe : true;
322 ctx->irql = true;
323 return;
324 }
326 // That's the Type 1 (seek) commands sorted. Now for the others.
328 // All these commands return the DRQ bit...
329 ctx->cmd_has_drq = true;
331 // If drive isn't ready, then set status B7 and exit
332 if (ctx->disc_image == NULL) {
333 ctx->status = 0x80;
334 return;
335 }
337 // If this is a Write command, check write protect status too
338 // TODO!
339 if (false) {
340 // Write protected disc...
341 if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
342 // Set Write Protect bit and bail.
343 ctx->status = 0x40;
345 // Set IRQ only if IRQL has been cleared (no pending IRQs)
346 ctx->irqe = ctx->irql ? ctx->irqe : true;
347 ctx->irql = true;
349 return;
350 }
351 }
353 // Disc is ready to go. Parse the command word.
354 switch (cmd) {
355 case CMD_READ_ADDRESS:
356 // Read Address
357 ctx->head = (val & 0x02) ? 1 : 0;
359 // reset data pointers
360 ctx->data_pos = ctx->data_len = 0;
362 // load data buffer
363 ctx->data[ctx->data_len++] = ctx->track;
364 ctx->data[ctx->data_len++] = ctx->head;
365 ctx->data[ctx->data_len++] = ctx->sector;
366 switch (ctx->geom_secsz) {
367 case 128: ctx->data[ctx->data_len++] = 0; break;
368 case 256: ctx->data[ctx->data_len++] = 1; break;
369 case 512: ctx->data[ctx->data_len++] = 2; break;
370 case 1024: ctx->data[ctx->data_len++] = 3; break;
371 default: ctx->data[ctx->data_len++] = 0xFF; break; // TODO: deal with invalid values better
372 }
373 ctx->data[ctx->data_len++] = 0; // TODO: IDAM CRC!
374 ctx->data[ctx->data_len++] = 0;
376 ctx->status = 0;
377 // B6, B5 = 0
378 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
379 // B3 = CRC Error. Not possible.
380 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
381 // B1 = DRQ. Data request.
382 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
383 break;
385 case CMD_READ_SECTOR:
386 case CMD_READ_SECTOR_MULTI:
387 ctx->head = (val & 0x02) ? 1 : 0;
388 LOG("WD279X: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
389 // Read Sector or Read Sector Multiple
391 // Check to see if the cyl, hd and sec are valid
392 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt) || (ctx->sector == 0)) {
393 LOG("*** WD2797 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
394 ctx->track, ctx->head, ctx->sector,
395 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
396 // CHS parameters exceed limits
397 ctx->status = 0x10; // Record Not Found
398 break;
399 // Set IRQ only if IRQL has been cleared (no pending IRQs)
400 ctx->irqe = ctx->irql ? ctx->irqe : true;
401 ctx->irql = true;
402 }
404 // reset data pointers
405 ctx->data_pos = ctx->data_len = 0;
407 // Calculate number of sectors to read from disc
408 if (cmd == CMD_READ_SECTOR_MULTI)
409 temp = ctx->geom_spt;
410 else
411 temp = 1;
413 for (int i=0; i<temp; i++) {
414 // Calculate the LBA address of the required sector
415 // lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ((ctx->sector + i - 1) % ctx->geom_spt)) * ctx->geom_secsz;
416 // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
417 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i) - 1;
418 // convert LBA to byte address
419 lba *= ctx->geom_secsz;
420 LOG("\tREAD lba = %lu", lba);
422 // Read the sector from the file
423 fseek(ctx->disc_image, lba, SEEK_SET);
424 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
425 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
426 LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
427 }
429 ctx->status = 0;
430 // B6 = 0
431 // B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
432 // B4 = Record Not Found. Basically, the CHS parameters are bullcrap.
433 // B3 = CRC Error. Not possible.
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 break;
439 case CMD_READ_TRACK:
440 // Read Track
441 // TODO! implement this
442 ctx->head = (val & 0x02) ? 1 : 0;
443 ctx->status = 0;
444 // B6, B5, B4, B3 = 0
445 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
446 // B1 = DRQ. Data request.
447 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
448 break;
450 case CMD_WRITE_SECTOR:
451 case CMD_WRITE_SECTOR_MULTI:
452 // Write Sector or Write Sector Multiple
454 ctx->head = (val & 0x02) ? 1 : 0;
455 // reset data pointers
456 ctx->data_pos = ctx->data_len = 0;
458 // TODO: set "write pending" flag, and write LBA, and go from there.
460 ctx->status = 0;
461 // B6 = Write Protect. FIXME -- emulate this!
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 // B1 = DRQ. Data request.
478 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
479 break;
481 case CMD_FORCE_INTERRUPT:
482 // Force Interrupt...
483 // Terminates current operation and sends an interrupt
484 // TODO!
485 ctx->status = 0;
486 ctx->data_pos = ctx->data_len = 0;
487 // Set IRQ only if IRQL has been cleared (no pending IRQs)
488 ctx->irqe = ctx->irql ? ctx->irqe : true;
489 ctx->irql = true;
490 break;
491 }
492 break;
494 case WD2797_REG_TRACK: // Track register
495 ctx->track = val;
496 break;
498 case WD2797_REG_SECTOR: // Sector register
499 ctx->sector = val;
500 break;
502 case WD2797_REG_DATA: // Data register
503 // Save the value written into the data register
504 ctx->data_reg = val;
506 // If we're processing a write command, and there's space in the
507 // buffer, allow the write.
508 if (ctx->data_pos < ctx->data_len) {
509 // set IRQ if this is the last data byte
510 if (ctx->data_pos == (ctx->data_len-1)) {
511 // Set IRQ only if IRQL has been cleared (no pending IRQs)
512 ctx->irqe = ctx->irql ? ctx->irqe : true;
513 ctx->irql = true;
514 }
516 // store data byte and increment pointer
517 ctx->data[ctx->data_pos++] = val;
518 }
519 break;
520 }
521 }