Mon, 14 Jan 2013 09:48:21 +0000
Handle memory more gracefully
Fixed in this cset:
* Return an 'empty space' value if the memory wraps around
* Allow the 'empty bus' value to be changed via an ifdef
* Properly handle situations where expansion memory is turned off
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 }