Tue, 15 Jan 2013 17:02:56 +0000
Implement m68k_read_disassembler_* properly
The previous implementations of m68k_read_disassembler are unsuitable due to
interactions with the memory mapper. A read from memory by the DASM should not
mutate system state.
So we modify the m68k_read_disassembler_{8,16,32} functions to do the memory
mapping themselves without causing page faults (bus error exception) or
updating the page flag bits (which could really upset the kernel).
Now all we need is a debugger/disassembler...
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 #define SEEK_DELAY 30
17 #define CMD_ENABLE_RETRY 0x01
18 #define CMD_LONG_MODE 0x02
19 #define CMD_MULTI_SECTOR 0x04
20 #define CMD_INTRQ_WHEN_COMPLETE 0x08
22 #define ER_BAD_BLOCK 0x80
23 #define ER_CRC 0x40
24 #define ER_ID_NOT_FOUND 0x10
25 #define ER_ABORTED_COMMAND 0x04
26 #define ER_NO_TK0 0x02
27 #define ER_NO_ADDRESS_MARK 0x01
29 #define SR_BUSY 0x80
30 #define SR_READY 0x40
31 #define SR_WRITE_FAULT 0x20
32 #define SR_SEEK_COMPLETE 0x10
33 #define SR_DRQ 0x08
34 #define SR_CORRECTED 0x04
35 #define SR_COMMAND_IN_PROGRESS 0x02
36 #define SR_ERROR 0x01
38 extern int cpu_log_enabled;
40 /// WD2010 command constants
41 enum {
42 CMD_MASK = 0xF0, ///< Bit mask to detect command bits
43 CMD_2010_EXT = 0x00, ///< WD2010 extended commands (compute correction, set parameter)
44 CMD_RESTORE = 0x10, ///< Restore (recalibrate, seek to track 0)
45 CMD_READ_SECTOR = 0x20, ///< Read sector
46 CMD_WRITE_SECTOR = 0x30, ///< Write sector
47 CMD_SCAN_ID = 0x40, ///< Scan ID
48 CMD_WRITE_FORMAT = 0x50, ///< Write format
49 CMD_SEEK = 0x70, ///< Seek to given track
50 };
52 int wd2010_init(WD2010_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
53 {
54 long filesize;
55 wd2010_reset(ctx);
56 // Start by finding out how big the image file is
57 fseek(fp, 0, SEEK_END);
58 filesize = ftell(fp);
59 fseek(fp, 0, SEEK_SET);
61 // Now figure out how many tracks it contains
62 int tracks = filesize / secsz / spt / heads;
63 // Confirm...
64 if (tracks < 1) {
65 return WD2010_ERR_BAD_GEOM;
66 }
68 // Allocate enough memory to store one disc track
69 if (ctx->data) {
70 free(ctx->data);
71 }
72 ctx->data = malloc(secsz * spt);
73 if (!ctx->data)
74 return WD2010_ERR_NO_MEMORY;
76 // Load the image and the geometry data
77 ctx->disc_image = fp;
78 ctx->geom_tracks = tracks;
79 ctx->geom_secsz = secsz;
80 ctx->geom_heads = heads;
81 ctx->geom_spt = spt;
82 return WD2010_ERR_OK;
84 }
86 void wd2010_reset(WD2010_CTX *ctx)
87 {
88 // track, head and sector unknown
89 ctx->track = ctx->head = ctx->sector = 0;
91 // no IRQ pending
92 ctx->irq = false;
94 // no data available
95 ctx->data_pos = ctx->data_len = 0;
97 // Status register clear, not busy
98 ctx->status = 0;
100 ctx->sector_count = 0;
101 ctx->sector_number = 0;
102 ctx->cylinder_low_reg = 0;
103 ctx->cylinder_high_reg = 0;
104 ctx->sdh = 0;
105 ctx->mcr2_hdsel3 = 0;
106 ctx->mcr2_ddrive1 = 0;
107 }
109 void wd2010_done(WD2010_CTX *ctx)
110 {
111 // Reset the WD2010
112 wd2010_reset(ctx);
114 // Free any allocated memory
115 if (ctx->data) {
116 free(ctx->data);
117 ctx->data = NULL;
118 }
119 }
122 bool wd2010_get_irq(WD2010_CTX *ctx)
123 {
124 return ctx->irq;
125 }
127 bool wd2010_get_drq(WD2010_CTX *ctx)
128 {
129 return (ctx->drq && ctx->data_pos < ctx->data_len);
130 }
132 void wd2010_dma_miss(WD2010_CTX *ctx)
133 {
134 ctx->data_pos = ctx->data_len;
135 ctx->write_pos = 0;
136 ctx->status = SR_READY | SR_SEEK_COMPLETE;
137 ctx->irq = true;
138 }
140 uint8_t wd2010_read_data(WD2010_CTX *ctx)
141 {
142 // If there's data in the buffer, return it. Otherwise return 0xFF.
143 if (ctx->data_pos < ctx->data_len) {
144 if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
145 ctx->sector_count--;
146 ctx->sector_number++;
147 }
148 // set IRQ if this is the last data byte
149 if (ctx->data_pos == (ctx->data_len-1)) {
150 ctx->status = SR_READY | SR_SEEK_COMPLETE;
151 // Set IRQ
152 ctx->irq = true;
153 ctx->drq = false;
154 }
155 // return data byte and increment pointer
156 return ctx->data[ctx->data_pos++];
157 } else {
158 // empty buffer (this shouldn't happen)
159 LOGS("WD2010: attempt to read from empty data buffer");
160 return 0xff;
161 }
162 }
164 void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
165 {
166 // If we're processing a write command, and there's space in the
167 // buffer, allow the write.
168 if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
169 // store data byte and increment pointer
170 if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
171 ctx->sector_count--;
172 ctx->sector_number++;
173 }
174 ctx->data[ctx->data_pos++] = val;
175 // set IRQ and write data if this is the last data byte
176 if (ctx->data_pos == ctx->data_len) {
177 if (!ctx->formatting){
178 fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
179 fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
180 fflush(ctx->disc_image);
181 }
182 ctx->formatting = false;
183 ctx->status = SR_READY | SR_SEEK_COMPLETE;
184 // Set IRQ and reset write pointer
185 ctx->irq = true;
186 ctx->write_pos = -1;
187 ctx->drq = false;
188 }
189 }else{
190 LOGS("WD2010: attempt to write to data buffer without a write command in progress");
191 }
192 }
194 uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
195 {
196 /*m68k_end_timeslice();*/
197 ctx->status = SR_READY | SR_SEEK_COMPLETE;
198 ctx->irq = true;
199 return (0);
200 }
202 uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
203 {
204 /*m68k_end_timeslice();*/
205 ctx->drq = true;
206 return (0);
207 }
209 uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
210 {
211 uint8_t temp = 0;
213 /*cpu_log_enabled = 1;*/
215 switch (addr & 0x07) {
216 case WD2010_REG_ERROR:
217 return ctx->error_reg;
218 case WD2010_REG_SECTOR_COUNT:
219 return ctx->sector_count;
220 case WD2010_REG_SECTOR_NUMBER:
221 return ctx->sector_number;
222 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
223 return ctx->cylinder_high_reg;
224 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
225 return ctx->cylinder_low_reg;
226 case WD2010_REG_SDH:
227 return ctx->sdh;
228 case WD2010_REG_STATUS: // Status register
229 // Read from status register clears IRQ
230 ctx->irq = false;
231 // Get current status flags (set by last command)
232 // DRQ bit
233 if (ctx->cmd_has_drq) {
234 temp = ctx->status & ~(SR_BUSY & SR_DRQ);
235 temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
236 LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
237 } else {
238 temp = ctx->status & ~0x80;
239 }
240 /*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
241 // HDC is busy if there is still data in the buffer
242 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!
243 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
244 /*XXX: should anything else be set here?*/
245 return temp;
246 default:
247 // shut up annoying compilers which don't recognise unreachable code when they see it
248 // (here's looking at you, gcc!)
249 return 0xff;
250 }
251 }
254 void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
255 {
256 uint8_t cmd = val & CMD_MASK;
257 size_t lba;
258 int new_track;
259 int sector_count;
261 m68k_end_timeslice();
263 /*cpu_log_enabled = 1;*/
265 if (addr == UNIXPC_REG_MCR2) {
266 // The UNIX PC has an "MCR2" register with the following format:
267 // [ 7..2 ][1][0]
268 // Bits 7..2: Not used
269 // Bit 1: DDRIVE1 (hard disk drive 1 select - not used?)
270 // Bit 0: HDSEL3 (head-select bit 3)
271 ctx->mcr2_hdsel3 = ((val & 1) == 1);
272 ctx->mcr2_ddrive1 = ((val & 2) == 2);
273 return;
274 }
276 switch (addr & 0x07) {
277 case WD2010_REG_WRITE_PRECOMP_CYLINDER:
278 break;
279 case WD2010_REG_SECTOR_COUNT:
280 ctx->sector_count = val;
281 break;
282 case WD2010_REG_SECTOR_NUMBER:
283 ctx->sector_number = val;
284 break;
285 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
286 ctx->cylinder_high_reg = val;
287 break;
288 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
289 ctx->cylinder_low_reg = val;
290 break;
291 case WD2010_REG_SDH:
292 /*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
293 ctx->data_pos = ctx->data_len = 0;
294 ctx->sdh = val;
295 break;
296 case WD2010_REG_COMMAND: // Command register
297 // write to command register clears interrupt request
298 ctx->irq = false;
299 ctx->error_reg = 0;
301 /*cpu_log_enabled = 1;*/
302 switch (cmd) {
303 case CMD_RESTORE:
304 // Restore. Set track to 0 and throw an IRQ.
305 ctx->track = 0;
306 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
307 break;
308 case CMD_SCAN_ID:
309 ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
310 ctx->cylinder_low_reg = ctx->track & 0xff;
311 ctx->sector_number = ctx->sector;
312 ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
313 case CMD_WRITE_FORMAT:
314 case CMD_SEEK:
315 case CMD_READ_SECTOR:
316 case CMD_WRITE_SECTOR:
317 // Seek. Seek to the track specced in the cylinder
318 // registers.
319 new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
320 if (new_track < ctx->geom_tracks) {
321 ctx->track = new_track;
322 } else {
323 // Seek error. :(
324 ctx->status = SR_ERROR;
325 ctx->error_reg = ER_ID_NOT_FOUND;
326 ctx->irq = true;
327 break;
328 }
329 // The SDH register provides 3 head select bits; the 4th comes from MCR2.
330 ctx->head = (ctx->sdh & 0x07) + (ctx->mcr2_hdsel3 ? 8 : 0);
331 ctx->sector = ctx->sector_number;
333 ctx->formatting = cmd == CMD_WRITE_FORMAT;
334 switch (cmd){
335 case CMD_SEEK:
336 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
337 break;
338 case CMD_READ_SECTOR:
339 /*XXX: does a separate function to set the head have to be added?*/
340 LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
342 // Read Sector
344 // Check to see if the cyl, hd and sec are valid
345 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) {
346 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
347 ctx->track, ctx->head, ctx->sector,
348 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1);
349 // CHS parameters exceed limits
350 ctx->status = SR_ERROR;
351 ctx->error_reg = ER_ID_NOT_FOUND;
352 // Set IRQ
353 ctx->irq = true;
354 break;
355 }
357 // reset data pointers
358 ctx->data_pos = ctx->data_len = 0;
360 if (val & CMD_MULTI_SECTOR){
361 ctx->multi_sector = 1;
362 sector_count = ctx->sector_count;
363 }else{
364 ctx->multi_sector = 0;
365 sector_count = 1;
366 }
367 for (int i=0; i<sector_count; i++) {
368 // Calculate the LBA address of the required sector
369 // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
370 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
371 // convert LBA to byte address
372 lba *= ctx->geom_secsz;
373 LOG("\tREAD lba = %lu", lba);
375 // Read the sector from the file
376 fseek(ctx->disc_image, lba, SEEK_SET);
377 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
378 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
379 LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
380 }
382 ctx->status = 0;
383 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
384 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
386 break;
387 case CMD_WRITE_FORMAT:
388 ctx->sector = 0;
389 case CMD_WRITE_SECTOR:
390 LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
391 // Read Sector
393 // Check to see if the cyl, hd and sec are valid
394 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) {
395 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
396 ctx->track, ctx->head, ctx->sector,
397 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
398 // CHS parameters exceed limits
399 ctx->status = SR_ERROR;
400 ctx->error_reg = ER_ID_NOT_FOUND;
401 // Set IRQ
402 ctx->irq = true;
403 break;
404 }
406 // reset data pointers
407 ctx->data_pos = ctx->data_len = 0;
409 if (val & CMD_MULTI_SECTOR){
410 ctx->multi_sector = 1;
411 sector_count = ctx->sector_count;
412 }else{
413 ctx->multi_sector = 0;
414 sector_count = 1;
415 }
416 ctx->data_len = ctx->geom_secsz * sector_count;
417 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
418 // convert LBA to byte address
419 ctx->write_pos = lba * ctx->geom_secsz;
420 LOG("\tWRITE lba = %lu", lba);
422 ctx->status = 0;
423 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
424 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
426 break;
427 default:
428 LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
429 break;
430 }
431 break;
432 case CMD_2010_EXT: /* not implemented */
433 default:
434 LOG("WD2010: unknown command %x\n", cmd);
435 ctx->status = SR_ERROR;
436 ctx->error_reg = ER_ABORTED_COMMAND;
437 ctx->irq = true;
438 break;
439 }
440 break;
442 }
443 }