Tue, 28 Dec 2010 17:47:01 +0000
finish expansion memory emulation
1 #include <stdint.h>
2 #include <stdbool.h>
3 #include <malloc.h>
4 #include "musashi/m68k.h"
5 #include "wd279x.h"
7 /// WD2797 command constants
8 enum {
9 CMD_MASK = 0xF0, ///< Bit mask to detect command bits
10 CMD_RESTORE = 0x00, ///< Restore (recalibrate, seek to track 0)
11 CMD_SEEK = 0x10, ///< Seek to given track
12 CMD_STEP = 0x20, ///< Step
13 CMD_STEP_TU = 0x30, ///< Step and update track register
14 CMD_STEPIN = 0x40, ///< Step In
15 CMD_STEPIN_TU = 0x50, ///< Step In and update track register
16 CMD_STEPOUT = 0x60, ///< Step Out
17 CMD_STEPOUT_TU = 0x70, ///< Step Out and update track register
18 CMD_READ_SECTOR = 0x80, ///< Read Sector
19 CMD_READ_SECTOR_MULTI = 0x90, ///< Read Multiple Sectors
20 CMD_WRITE_SECTOR = 0xA0, ///< Write Sector
21 CMD_WRITE_SECTOR_MULTI = 0xB0, ///< Write Multiple Sectors
22 CMD_READ_ADDRESS = 0xC0, ///< Read Address (IDAM contents)
23 CMD_FORCE_INTERRUPT = 0xD0, ///< Force Interrupt
24 CMD_READ_TRACK = 0xE0, ///< Read Track
25 CMD_FORMAT_TRACK = 0xF0 ///< Format Track
26 };
29 void wd2797_init(WD2797_CTX *ctx)
30 {
31 // track, head and sector unknown
32 ctx->track = ctx->head = ctx->sector = 0;
34 // no IRQ pending
35 ctx->irql = ctx->irqe = false;
37 // no data available
38 ctx->data_pos = ctx->data_len = 0;
39 ctx->data = NULL;
41 // Status register clear, not busy
42 ctx->status = 0;
44 // Clear data register
45 ctx->data_reg = 0;
47 // Last step direction
48 ctx->last_step_dir = -1;
50 // No disc image loaded
51 ctx->disc_image = NULL;
52 ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0;
53 }
56 void wd2797_reset(WD2797_CTX *ctx)
57 {
58 // track, head and sector unknown
59 ctx->track = ctx->head = ctx->sector = 0;
61 // no IRQ pending
62 ctx->irql = ctx->irqe = false;
64 // no data available
65 ctx->data_pos = ctx->data_len = 0;
67 // Status register clear, not busy
68 ctx->status = 0;
70 // Clear data register
71 ctx->data_reg = 0;
73 // Last step direction
74 ctx->last_step_dir = -1;
75 }
78 void wd2797_done(WD2797_CTX *ctx)
79 {
80 // Reset the WD2797
81 wd2797_reset(ctx);
83 // Free any allocated memory
84 if (ctx->data) {
85 free(ctx->data);
86 ctx->data = NULL;
87 }
88 }
91 bool wd2797_get_irq(WD2797_CTX *ctx)
92 {
93 // If an IRQ is pending, clear it and return true, otherwise return false
94 if (ctx->irqe) {
95 ctx->irqe = false;
96 return true;
97 } else {
98 return false;
99 }
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->irql = false;
168 ctx->irqe = false;
170 // Get current status flags (set by last command)
171 // DRQ bit
172 if (ctx->cmd_has_drq) {
173 temp = ctx->status & ~0x03;
174 temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
175 printf("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X\n", ctx->data_pos, ctx->data_len, temp);
176 } else {
177 temp = ctx->status & ~0x01;
178 }
179 // FDC is busy if there is still data in the buffer
180 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!
181 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
182 return temp;
184 case WD2797_REG_TRACK: // Track register
185 return ctx->track;
187 case WD2797_REG_SECTOR: // Sector register
188 return ctx->sector;
190 case WD2797_REG_DATA: // Data register
191 // If there's data in the buffer, return it. Otherwise return 0xFF.
192 if (ctx->data_pos < ctx->data_len) {
193 // set IRQ if this is the last data byte
194 if (ctx->data_pos == (ctx->data_len-1)) {
195 // Set IRQ only if IRQL has been cleared (no pending IRQs)
196 ctx->irqe = ctx->irql ? ctx->irqe : true;
197 ctx->irql = true;
198 }
199 // return data byte and increment pointer
200 return ctx->data[ctx->data_pos++];
201 } else {
202 // command finished
203 return 0xff;
204 }
206 default:
207 // shut up annoying compilers which don't recognise unreachable code when they see it
208 // (here's looking at you, gcc!)
209 return 0xff;
210 }
211 }
214 void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val)
215 {
216 uint8_t cmd = val & CMD_MASK;
217 size_t lba;
218 bool is_type1 = false;
219 int temp;
221 m68k_end_timeslice();
223 switch (addr) {
224 case WD2797_REG_COMMAND: // Command register
225 // write to command register clears interrupt request
226 ctx->irql = false;
228 // Is the drive ready?
229 if (ctx->disc_image == NULL) {
230 // No disc image, thus the drive is busy.
231 ctx->status = 0x80;
232 return;
233 }
235 // Handle Type 1 commands
236 switch (cmd) {
237 case CMD_RESTORE:
238 // Restore. Set track to 0 and throw an IRQ.
239 is_type1 = true;
240 ctx->track = 0;
241 break;
243 case CMD_SEEK:
244 // Seek. Seek to the track specced in the Data Register.
245 is_type1 = true;
246 if (ctx->data_reg < ctx->geom_tracks) {
247 ctx->track = ctx->data_reg;
248 } else {
249 // Seek error. :(
250 ctx->status = 0x10;
251 }
253 case CMD_STEP:
254 // TODO! deal with trk0!
255 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
256 is_type1 = true;
257 break;
259 case CMD_STEPIN:
260 case CMD_STEPOUT:
261 // TODO! deal with trk0!
262 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
263 if (cmd == CMD_STEPIN) {
264 ctx->last_step_dir = 1;
265 } else {
266 ctx->last_step_dir = -1;
267 }
268 is_type1 = true;
269 break;
271 case CMD_STEP_TU:
272 case CMD_STEPIN_TU:
273 case CMD_STEPOUT_TU:
274 // if this is a Step In or Step Out cmd, set the step-direction
275 if (cmd == CMD_STEPIN_TU) {
276 ctx->last_step_dir = 1;
277 } else if (cmd == CMD_STEPOUT_TU) {
278 ctx->last_step_dir = -1;
279 }
281 // Seek one step in the last direction used.
282 ctx->track += ctx->last_step_dir;
283 if (ctx->track < 0) ctx->track = 0;
284 if (ctx->track >= ctx->geom_tracks) {
285 // Seek past end of disc... that'll be a Seek Error then.
286 ctx->status = 0x10;
287 ctx->track = ctx->geom_tracks - 1;
288 }
289 is_type1 = true;
290 break;
292 default:
293 break;
294 }
296 if (is_type1) {
297 // Terminate any sector reads or writes
298 ctx->data_len = ctx->data_pos = 0;
300 // No DRQ bit for these commands.
301 ctx->cmd_has_drq = false;
303 // Type1 status byte...
304 ctx->status = 0;
305 // S7 = Not Ready. Command executed, therefore the drive was ready... :)
306 // S6 = Write Protect. TODO: add this
307 // S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
308 ctx->status |= 0x20;
309 // S4 = Seek Error. Not bloody likely if we got down here...!
310 // S3 = CRC Error. Not gonna happen on a disc image!
311 // S2 = Track 0
312 ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
313 // S1 = Index Pulse. TODO -- need periodics to emulate this
314 // S0 = Busy. We just exec'd the command, thus we're not busy.
315 // TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
317 // Set IRQ only if IRQL has been cleared (no pending IRQs)
318 ctx->irqe = ctx->irql ? ctx->irqe : true;
319 ctx->irql = true;
320 return;
321 }
323 // That's the Type 1 (seek) commands sorted. Now for the others.
325 // All these commands return the DRQ bit...
326 ctx->cmd_has_drq = true;
328 // If drive isn't ready, then set status B7 and exit
329 if (ctx->disc_image == NULL) {
330 ctx->status = 0x80;
331 return;
332 }
334 // If this is a Write command, check write protect status too
335 // TODO!
336 if (false) {
337 // Write protected disc...
338 if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
339 // Set Write Protect bit and bail.
340 ctx->status = 0x40;
342 // Set IRQ only if IRQL has been cleared (no pending IRQs)
343 ctx->irqe = ctx->irql ? ctx->irqe : true;
344 ctx->irql = true;
346 return;
347 }
348 }
350 // Disc is ready to go. Parse the command word.
351 switch (cmd) {
352 case CMD_READ_ADDRESS:
353 // Read Address
354 ctx->head = (val & 0x02) ? 1 : 0;
356 // reset data pointers
357 ctx->data_pos = ctx->data_len = 0;
359 // load data buffer
360 ctx->data[ctx->data_len++] = ctx->track;
361 ctx->data[ctx->data_len++] = ctx->head;
362 ctx->data[ctx->data_len++] = ctx->sector;
363 switch (ctx->geom_secsz) {
364 case 128: ctx->data[ctx->data_len++] = 0; break;
365 case 256: ctx->data[ctx->data_len++] = 1; break;
366 case 512: ctx->data[ctx->data_len++] = 2; break;
367 case 1024: ctx->data[ctx->data_len++] = 3; break;
368 default: ctx->data[ctx->data_len++] = 0xFF; break; // TODO: deal with invalid values better
369 }
370 ctx->data[ctx->data_len++] = 0; // TODO: IDAM CRC!
371 ctx->data[ctx->data_len++] = 0;
373 ctx->status = 0;
374 // B6, B5 = 0
375 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
376 // B3 = CRC Error. Not possible.
377 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
378 // B1 = DRQ. Data request.
379 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
380 break;
382 case CMD_READ_SECTOR:
383 case CMD_READ_SECTOR_MULTI:
384 ctx->head = (val & 0x02) ? 1 : 0;
385 printf("WD279X: READ SECTOR cmd=%02X chs=%d:%d:%d\n", cmd, ctx->track, ctx->head, ctx->sector);
386 // Read Sector or Read Sector Multiple
388 // Check to see if the cyl, hd and sec are valid
389 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt) || (ctx->sector == 0)) {
390 fprintf(stderr, "*** WD2797 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d\n",
391 ctx->track, ctx->head, ctx->sector,
392 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
393 // CHS parameters exceed limits
394 ctx->status = 0x10; // Record Not Found
395 break;
396 // Set IRQ only if IRQL has been cleared (no pending IRQs)
397 ctx->irqe = ctx->irql ? ctx->irqe : true;
398 ctx->irql = true;
399 }
401 // reset data pointers
402 ctx->data_pos = ctx->data_len = 0;
404 // Calculate number of sectors to read from disc
405 if (cmd == CMD_READ_SECTOR_MULTI)
406 temp = ctx->geom_spt;
407 else
408 temp = 1;
410 for (int i=0; i<temp; i++) {
411 // Calculate the LBA address of the required sector
412 // lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ((ctx->sector + i - 1) % ctx->geom_spt)) * ctx->geom_secsz;
413 // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
414 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i) - 1;
415 // convert LBA to byte address
416 lba *= ctx->geom_secsz;
417 printf("\tREAD lba = %lu\n", lba);
419 // Read the sector from the file
420 fseek(ctx->disc_image, lba, SEEK_SET);
421 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
422 printf("\tREAD len=%lu, pos=%lu, ssz=%d\n", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
423 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
424 }
426 ctx->status = 0;
427 // B6 = 0
428 // B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
429 // B4 = Record Not Found. Basically, the CHS parameters are bullcrap.
430 // B3 = CRC Error. Not possible.
431 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
432 // B1 = DRQ. Data request.
433 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
434 break;
436 case CMD_READ_TRACK:
437 // Read Track
438 // TODO! implement this
439 ctx->head = (val & 0x02) ? 1 : 0;
440 ctx->status = 0;
441 // B6, B5, B4, B3 = 0
442 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
443 // B1 = DRQ. Data request.
444 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
445 break;
447 case CMD_WRITE_SECTOR:
448 case CMD_WRITE_SECTOR_MULTI:
449 // Write Sector or Write Sector Multiple
451 ctx->head = (val & 0x02) ? 1 : 0;
452 // reset data pointers
453 ctx->data_pos = ctx->data_len = 0;
455 // TODO: set "write pending" flag, and write LBA, and go from there.
457 ctx->status = 0;
458 // B6 = Write Protect. FIXME -- emulate this!
459 // B5 = 0
460 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
461 // B3 = CRC Error. Not possible.
462 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
463 // B1 = DRQ. Data request.
464 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
465 break;
467 case CMD_FORMAT_TRACK:
468 // Write Track (aka Format Track)
469 ctx->head = (val & 0x02) ? 1 : 0;
470 ctx->status = 0;
471 // B6 = Write Protect. FIXME -- emulate this!
472 // B5, B4, B3 = 0
473 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
474 // B1 = DRQ. Data request.
475 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
476 break;
478 case CMD_FORCE_INTERRUPT:
479 // Force Interrupt...
480 // Terminates current operation and sends an interrupt
481 // TODO!
482 ctx->status = 0;
483 ctx->data_pos = ctx->data_len = 0;
484 // Set IRQ only if IRQL has been cleared (no pending IRQs)
485 ctx->irqe = ctx->irql ? ctx->irqe : true;
486 ctx->irql = true;
487 break;
488 }
489 break;
491 case WD2797_REG_TRACK: // Track register
492 ctx->track = val;
493 break;
495 case WD2797_REG_SECTOR: // Sector register
496 ctx->sector = val;
497 break;
499 case WD2797_REG_DATA: // Data register
500 // Save the value written into the data register
501 ctx->data_reg = val;
503 // If we're processing a write command, and there's space in the
504 // buffer, allow the write.
505 if (ctx->data_pos < ctx->data_len) {
506 // set IRQ if this is the last data byte
507 if (ctx->data_pos == (ctx->data_len-1)) {
508 // Set IRQ only if IRQL has been cleared (no pending IRQs)
509 ctx->irqe = ctx->irql ? ctx->irqe : true;
510 ctx->irql = true;
511 }
513 // store data byte and increment pointer
514 ctx->data[ctx->data_pos++] = val;
515 }
516 break;
517 }
518 }