Fri, 12 Apr 2013 16:26:25 +0100
Don't set PS1 if there is a level-7 interrupt or bus error
PS1 should only be set if the page was originally present (PS1 or PS0 set). If
PS0 and PS1 are clear (page not present) then do NOT set PS1.
Once again the TRM is blatantly and spectacularly wrong...
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 ((cmd != CMD_WRITE_FORMAT) && ((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 }