Fri, 18 Jan 2013 17:03:48 +0000
experimental memory mapper, not quite working
1 #include <stdint.h>
2 #include <stdbool.h>
3 #include <malloc.h>
4 #include "SDL.h"
5 #include "musashi/m68k.h"
6 #include "wd2010.h"
8 #define WD2010_DEBUG
10 #ifndef WD2010_DEBUG
11 #define NDEBUG
12 #endif
13 #include "utils.h"
15 #ifndef WD2010_SEEK_DELAY
16 #define WD2010_SEEK_DELAY 30
17 #endif
19 #define CMD_ENABLE_RETRY 0x01
20 #define CMD_LONG_MODE 0x02
21 #define CMD_MULTI_SECTOR 0x04
22 #define CMD_INTRQ_WHEN_COMPLETE 0x08
24 #define ER_BAD_BLOCK 0x80
25 #define ER_CRC 0x40
26 #define ER_ID_NOT_FOUND 0x10
27 #define ER_ABORTED_COMMAND 0x04
28 #define ER_NO_TK0 0x02
29 #define ER_NO_ADDRESS_MARK 0x01
31 #define SR_BUSY 0x80
32 #define SR_READY 0x40
33 #define SR_WRITE_FAULT 0x20
34 #define SR_SEEK_COMPLETE 0x10
35 #define SR_DRQ 0x08
36 #define SR_CORRECTED 0x04
37 #define SR_COMMAND_IN_PROGRESS 0x02
38 #define SR_ERROR 0x01
40 extern int cpu_log_enabled;
42 /// WD2010 command constants
43 enum {
44 CMD_MASK = 0xF0, ///< Bit mask to detect command bits
45 CMD_2010_EXT = 0x00, ///< WD2010 extended commands (compute correction, set parameter)
46 CMD_RESTORE = 0x10, ///< Restore (recalibrate, seek to track 0)
47 CMD_READ_SECTOR = 0x20, ///< Read sector
48 CMD_WRITE_SECTOR = 0x30, ///< Write sector
49 CMD_SCAN_ID = 0x40, ///< Scan ID
50 CMD_WRITE_FORMAT = 0x50, ///< Write format
51 CMD_SEEK = 0x70, ///< Seek to given track
52 };
54 int wd2010_init(WD2010_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
55 {
56 size_t filesize;
58 wd2010_reset(ctx);
60 // Start by finding out how big the image file is
61 fseek(fp, 0, SEEK_END);
62 filesize = ftell(fp);
63 fseek(fp, 0, SEEK_SET);
65 // Now figure out how many tracks it contains
66 unsigned int tracks = filesize / secsz / spt / heads;
67 // Confirm...
68 if (tracks < 1) {
69 return WD2010_ERR_BAD_GEOM;
70 }
72 LOG("WD2010 initialised, %d cylinders, %d heads, %d sectors per track", tracks, heads, spt);
74 // Allocate enough memory to store one disc track
75 if (ctx->data) {
76 free(ctx->data);
77 }
78 ctx->data = malloc(secsz * spt);
79 if (!ctx->data)
80 return WD2010_ERR_NO_MEMORY;
82 // Load the image and the geometry data
83 ctx->disc_image = fp;
84 ctx->geom_tracks = tracks;
85 ctx->geom_secsz = secsz;
86 ctx->geom_heads = heads;
87 ctx->geom_spt = spt;
89 return WD2010_ERR_OK;
90 }
92 void wd2010_reset(WD2010_CTX *ctx)
93 {
94 // track, head and sector unknown
95 ctx->track = ctx->head = ctx->sector = 0;
97 // no IRQ pending
98 ctx->irq = false;
100 // no data available
101 ctx->data_pos = ctx->data_len = 0;
103 // Status register clear, not busy
104 ctx->status = 0;
106 ctx->sector_count = 0;
107 ctx->sector_number = 0;
108 ctx->cylinder_low_reg = 0;
109 ctx->cylinder_high_reg = 0;
110 ctx->sdh = 0;
111 ctx->mcr2_hdsel3 = 0;
112 ctx->mcr2_ddrive1 = 0;
113 }
115 void wd2010_done(WD2010_CTX *ctx)
116 {
117 // Reset the WD2010
118 wd2010_reset(ctx);
120 // Free any allocated memory
121 if (ctx->data) {
122 free(ctx->data);
123 ctx->data = NULL;
124 }
125 }
128 bool wd2010_get_irq(WD2010_CTX *ctx)
129 {
130 return ctx->irq;
131 }
133 bool wd2010_get_drq(WD2010_CTX *ctx)
134 {
135 return (ctx->drq && ctx->data_pos < ctx->data_len);
136 }
138 void wd2010_dma_miss(WD2010_CTX *ctx)
139 {
140 ctx->data_pos = ctx->data_len;
141 ctx->write_pos = 0;
142 ctx->status = SR_READY | SR_SEEK_COMPLETE;
143 ctx->irq = true;
144 }
146 uint8_t wd2010_read_data(WD2010_CTX *ctx)
147 {
148 // If there's data in the buffer, return it. Otherwise return 0xFF.
149 if (ctx->data_pos < ctx->data_len) {
150 if (ctx->multi_sector && (ctx->data_pos > 0) && ((ctx->data_pos % ctx->geom_secsz) == 0)){
151 ctx->sector_count--;
152 ctx->sector_number++;
153 }
154 // set IRQ if this is the last data byte
155 if (ctx->data_pos == (ctx->data_len-1)) {
156 ctx->status = SR_READY | SR_SEEK_COMPLETE;
157 // Set IRQ
158 ctx->irq = true;
159 ctx->drq = false;
160 }
161 // return data byte and increment pointer
162 return ctx->data[ctx->data_pos++];
163 } else {
164 // empty buffer (this shouldn't happen)
165 LOGS("WD2010: attempt to read from empty data buffer");
166 return 0xff;
167 }
168 }
170 void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
171 {
172 // If we're processing a write command, and there's space in the
173 // buffer, allow the write.
174 if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
175 // store data byte and increment pointer
176 if (ctx->multi_sector && (ctx->data_pos > 0) && ((ctx->data_pos % ctx->geom_secsz) == 0)){
177 ctx->sector_count--;
178 ctx->sector_number++;
179 }
180 ctx->data[ctx->data_pos++] = val;
181 // set IRQ and write data if this is the last data byte
182 if (ctx->data_pos == ctx->data_len) {
183 if (!ctx->formatting){
184 fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
185 fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
186 fflush(ctx->disc_image);
187 }
188 ctx->formatting = false;
189 ctx->status = SR_READY | SR_SEEK_COMPLETE;
190 // Set IRQ and reset write pointer
191 ctx->irq = true;
192 ctx->write_pos = -1;
193 ctx->drq = false;
194 }
195 }else{
196 LOGS("WD2010: attempt to write to data buffer without a write command in progress");
197 }
198 }
200 uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
201 {
202 /*m68k_end_timeslice();*/
203 ctx->status = SR_READY | SR_SEEK_COMPLETE;
204 ctx->irq = true;
205 return (0);
206 }
208 uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
209 {
210 /*m68k_end_timeslice();*/
211 ctx->drq = true;
212 return (0);
213 }
215 uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
216 {
217 uint8_t temp = 0;
219 /*cpu_log_enabled = 1;*/
221 switch (addr & 0x07) {
222 case WD2010_REG_ERROR:
223 return ctx->error_reg;
224 case WD2010_REG_SECTOR_COUNT:
225 return ctx->sector_count;
226 case WD2010_REG_SECTOR_NUMBER:
227 return ctx->sector_number;
228 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
229 return ctx->cylinder_high_reg;
230 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
231 return ctx->cylinder_low_reg;
232 case WD2010_REG_SDH:
233 return ctx->sdh;
234 case WD2010_REG_STATUS: // Status register
235 // Read from status register clears IRQ
236 ctx->irq = false;
237 // Get current status flags (set by last command)
238 // DRQ bit
239 if (ctx->cmd_has_drq) {
240 temp = ctx->status & ~(SR_BUSY & SR_DRQ);
241 temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
242 LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
243 } else {
244 temp = ctx->status & ~0x80;
245 }
246 /*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
247 // HDC is busy if there is still data in the buffer
248 temp |= (ctx->data_pos < ctx->data_len) ? SR_BUSY : 0; // if data in buffer, then DMA hasn't copied it yet, and we're still busy!
249 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
250 /*XXX: should anything else be set here?*/
251 return temp;
252 default:
253 // shut up annoying compilers which don't recognise unreachable code when they see it
254 // (here's looking at you, gcc!)
255 return 0xff;
256 }
257 }
260 void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
261 {
262 uint8_t cmd = val & CMD_MASK;
263 size_t lba;
264 int new_track;
265 int sector_count;
266 unsigned int ssz;
268 m68k_end_timeslice();
270 /*cpu_log_enabled = 1;*/
272 if (addr == UNIXPC_REG_MCR2) {
273 // The UNIX PC has an "MCR2" register with the following format:
274 // [ 7..2 ][1][0]
275 // Bits 7..2: Not used
276 // Bit 1: DDRIVE1 (hard disk drive 1 select - not used?)
277 // Bit 0: HDSEL3 (head-select bit 3)
278 ctx->mcr2_hdsel3 = ((val & 1) == 1);
279 ctx->mcr2_ddrive1 = ((val & 2) == 2);
280 return;
281 }
283 switch (addr & 0x07) {
284 case WD2010_REG_WRITE_PRECOMP_CYLINDER:
285 break;
286 case WD2010_REG_SECTOR_COUNT:
287 ctx->sector_count = val;
288 break;
289 case WD2010_REG_SECTOR_NUMBER:
290 ctx->sector_number = val;
291 break;
292 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
293 ctx->cylinder_high_reg = val;
294 break;
295 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
296 ctx->cylinder_low_reg = val;
297 break;
298 case WD2010_REG_SDH:
299 /*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
300 //ctx->data_pos = ctx->data_len = 0;
301 ctx->sdh = val;
302 break;
303 case WD2010_REG_COMMAND: // Command register
304 // write to command register clears interrupt request
305 ctx->irq = false;
306 ctx->error_reg = 0;
308 /*cpu_log_enabled = 1;*/
309 switch (cmd) {
310 case CMD_RESTORE:
311 // Restore. Set track to 0 and throw an IRQ.
312 ctx->track = 0;
313 SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
314 break;
315 case CMD_SCAN_ID:
316 ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
317 ctx->cylinder_low_reg = ctx->track & 0xff;
318 ctx->sector_number = ctx->sector;
319 ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
320 case CMD_WRITE_FORMAT:
321 case CMD_SEEK:
322 case CMD_READ_SECTOR:
323 case CMD_WRITE_SECTOR:
324 // Seek. Seek to the track specced in the cylinder
325 // registers.
326 new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
327 if (new_track < ctx->geom_tracks) {
328 ctx->track = new_track;
329 } else {
330 // Seek error. :(
331 ctx->status = SR_ERROR;
332 ctx->error_reg = ER_ID_NOT_FOUND;
333 ctx->irq = true;
334 break;
335 }
336 // The SDH register provides 3 head select bits; the 4th comes from MCR2.
337 ctx->head = (ctx->sdh & 0x07) + (ctx->mcr2_hdsel3 ? 8 : 0);
338 ctx->sector = ctx->sector_number;
340 ctx->formatting = cmd == CMD_WRITE_FORMAT;
341 switch (cmd){
342 case CMD_SEEK:
343 SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
344 break;
345 case CMD_READ_SECTOR:
346 /*XXX: does a separate function to set the head have to be added?*/
347 LOG("WD2010: READ SECTOR cmd=%02X sdh=0x%02X drive=%d ddrive1=%d chs=%d:%d:%d nsectors=%d", cmd, ctx->sdh, (ctx->sdh >> 3) & 3, ctx->mcr2_ddrive1, ctx->track, ctx->head, ctx->sector, val&CMD_MULTI_SECTOR ? ctx->sector_count : 1);
349 switch ((ctx->sdh >> 5) & 0x03) {
350 case 0: ssz = 256; break;
351 case 1: ssz = 512; break;
352 case 2: ssz = 1024; break;
353 case 3: ssz = 128; break;
354 }
355 if (ssz != ctx->geom_secsz)
356 LOG("WARNING: Geometry mismatch. WD2010 Write Sector with secsz %d != phys_secsz %d.", ssz, ctx->geom_secsz);
358 // Read Sector
360 // Check to see if the cyl, hd and sec are valid
361 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || ((ctx->sector + ctx->sector_count - 1) > ctx->geom_spt-1) || (ssz != ctx->geom_secsz)) {
362 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! dDrive1=%d CHS=%d:%d:%d, nSecs=%d, endSec=%d maxCHS=%d:%d:%d",
363 ctx->mcr2_ddrive1,
364 ctx->track, ctx->head, ctx->sector,
365 ctx->sector_count,
366 ctx->sector + ctx->sector_count - 1,
367 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
368 // CHS parameters exceed limits
369 ctx->status = SR_ERROR;
370 ctx->error_reg = ER_ID_NOT_FOUND;
371 // Set IRQ
372 ctx->irq = true;
373 break;
374 }
376 // reset data pointers
377 ctx->data_pos = ctx->data_len = 0;
379 if (val & CMD_MULTI_SECTOR){
380 ctx->multi_sector = 1;
381 sector_count = ctx->sector_count;
382 }else{
383 ctx->multi_sector = 0;
384 sector_count = 1;
385 }
386 for (int i=0; i<sector_count; i++) {
387 // Calculate the LBA address of the required sector
388 // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
389 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
390 // convert LBA to byte address
391 lba *= ctx->geom_secsz;
392 LOG("\tREAD lba = %lu", lba);
394 // Read the sector from the file
395 fseek(ctx->disc_image, lba, SEEK_SET);
396 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
397 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
398 LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
399 }
401 ctx->status = 0;
402 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
403 SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
405 break;
406 case CMD_WRITE_FORMAT:
407 ctx->sector = 0;
408 case CMD_WRITE_SECTOR:
409 LOG("WD2010: WRITE SECTOR cmd=%02X sdh=0x%02X drive=%d ddrive1=%d chs=%d:%d:%d nsectors=%d", cmd, ctx->sdh, (ctx->sdh >> 3) & 3, ctx->mcr2_ddrive1, ctx->track, ctx->head, ctx->sector, val&CMD_MULTI_SECTOR ? ctx->sector_count : 1);
410 // Write Sector
412 switch ((ctx->sdh >> 5) & 0x03) {
413 case 0: ssz = 256; break;
414 case 1: ssz = 512; break;
415 case 2: ssz = 1024; break;
416 case 3: ssz = 128; break;
417 }
418 if (ssz != ctx->geom_secsz)
419 LOG("WARNING: Geometry mismatch. WD2010 Write Sector with secsz %d != phys_secsz %d.", ssz, ctx->geom_secsz);
421 // Check to see if the cyl, hd and sec are valid
422 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || ((ctx->sector + ctx->sector_count - 1) > ctx->geom_spt-1) || (ssz != ctx->geom_secsz)) {
423 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! dDrive1=%d CHS=%d:%d:%d, nSecs=%d, endSec=%d maxCHS=%d:%d:%d",
424 ctx->mcr2_ddrive1,
425 ctx->track, ctx->head, ctx->sector,
426 ctx->sector_count,
427 ctx->sector + ctx->sector_count - 1,
428 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
429 // CHS parameters exceed limits
430 ctx->status = SR_ERROR;
431 ctx->error_reg = ER_ID_NOT_FOUND;
432 // Set IRQ
433 ctx->irq = true;
434 break;
435 }
437 // reset data pointers
438 ctx->data_pos = ctx->data_len = 0;
440 if (val & CMD_MULTI_SECTOR){
441 ctx->multi_sector = 1;
442 sector_count = ctx->sector_count;
443 }else{
444 ctx->multi_sector = 0;
445 sector_count = 1;
446 }
447 ctx->data_len = ctx->geom_secsz * sector_count;
448 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
449 // convert LBA to byte address
450 ctx->write_pos = (lba *= ctx->geom_secsz);
451 LOG("\tWRITE lba = %zu", lba);
453 ctx->status = 0;
454 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
455 SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
457 break;
458 default:
459 LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
460 break;
461 }
462 break;
463 case CMD_2010_EXT: /* not implemented */
464 default:
465 LOG("WD2010: unknown command %x\n", cmd);
466 ctx->status = SR_ERROR;
467 ctx->error_reg = ER_ABORTED_COMMAND;
468 ctx->irq = true;
469 break;
470 }
471 break;
473 }
474 }