Wed, 16 Jan 2013 00:35:10 +0000
[wd2010] fix confusing expressions used for multisector mode
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 size_t filesize;
56 wd2010_reset(ctx);
58 // Start by finding out how big the image file is
59 fseek(fp, 0, SEEK_END);
60 filesize = ftell(fp);
61 fseek(fp, 0, SEEK_SET);
63 // Now figure out how many tracks it contains
64 unsigned int tracks = filesize / secsz / spt / heads;
65 // Confirm...
66 if (tracks < 1) {
67 return WD2010_ERR_BAD_GEOM;
68 }
70 LOG("WD2010 initialised, %d cylinders, %d heads, %d sectors per track", tracks, heads, spt);
72 // Allocate enough memory to store one disc track
73 if (ctx->data) {
74 free(ctx->data);
75 }
76 ctx->data = malloc(secsz * spt);
77 if (!ctx->data)
78 return WD2010_ERR_NO_MEMORY;
80 // Load the image and the geometry data
81 ctx->disc_image = fp;
82 ctx->geom_tracks = tracks;
83 ctx->geom_secsz = secsz;
84 ctx->geom_heads = heads;
85 ctx->geom_spt = spt;
87 return WD2010_ERR_OK;
88 }
90 void wd2010_reset(WD2010_CTX *ctx)
91 {
92 // track, head and sector unknown
93 ctx->track = ctx->head = ctx->sector = 0;
95 // no IRQ pending
96 ctx->irq = false;
98 // no data available
99 ctx->data_pos = ctx->data_len = 0;
101 // Status register clear, not busy
102 ctx->status = 0;
104 ctx->sector_count = 0;
105 ctx->sector_number = 0;
106 ctx->cylinder_low_reg = 0;
107 ctx->cylinder_high_reg = 0;
108 ctx->sdh = 0;
109 ctx->mcr2_hdsel3 = 0;
110 ctx->mcr2_ddrive1 = 0;
111 }
113 void wd2010_done(WD2010_CTX *ctx)
114 {
115 // Reset the WD2010
116 wd2010_reset(ctx);
118 // Free any allocated memory
119 if (ctx->data) {
120 free(ctx->data);
121 ctx->data = NULL;
122 }
123 }
126 bool wd2010_get_irq(WD2010_CTX *ctx)
127 {
128 return ctx->irq;
129 }
131 bool wd2010_get_drq(WD2010_CTX *ctx)
132 {
133 return (ctx->drq && ctx->data_pos < ctx->data_len);
134 }
136 void wd2010_dma_miss(WD2010_CTX *ctx)
137 {
138 ctx->data_pos = ctx->data_len;
139 ctx->write_pos = 0;
140 ctx->status = SR_READY | SR_SEEK_COMPLETE;
141 ctx->irq = true;
142 }
144 uint8_t wd2010_read_data(WD2010_CTX *ctx)
145 {
146 // If there's data in the buffer, return it. Otherwise return 0xFF.
147 if (ctx->data_pos < ctx->data_len) {
148 if (ctx->multi_sector && (ctx->data_pos > 0) && ((ctx->data_pos % ctx->geom_secsz) == 0)){
149 ctx->sector_count--;
150 ctx->sector_number++;
151 }
152 // set IRQ if this is the last data byte
153 if (ctx->data_pos == (ctx->data_len-1)) {
154 ctx->status = SR_READY | SR_SEEK_COMPLETE;
155 // Set IRQ
156 ctx->irq = true;
157 ctx->drq = false;
158 }
159 // return data byte and increment pointer
160 return ctx->data[ctx->data_pos++];
161 } else {
162 // empty buffer (this shouldn't happen)
163 LOGS("WD2010: attempt to read from empty data buffer");
164 return 0xff;
165 }
166 }
168 void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
169 {
170 // If we're processing a write command, and there's space in the
171 // buffer, allow the write.
172 if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
173 // store data byte and increment pointer
174 if (ctx->multi_sector && (ctx->data_pos > 0) && ((ctx->data_pos % ctx->geom_secsz) == 0)){
175 ctx->sector_count--;
176 ctx->sector_number++;
177 }
178 ctx->data[ctx->data_pos++] = val;
179 // set IRQ and write data if this is the last data byte
180 if (ctx->data_pos == ctx->data_len) {
181 if (!ctx->formatting){
182 fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
183 fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
184 fflush(ctx->disc_image);
185 }
186 ctx->formatting = false;
187 ctx->status = SR_READY | SR_SEEK_COMPLETE;
188 // Set IRQ and reset write pointer
189 ctx->irq = true;
190 ctx->write_pos = -1;
191 ctx->drq = false;
192 }
193 }else{
194 LOGS("WD2010: attempt to write to data buffer without a write command in progress");
195 }
196 }
198 uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
199 {
200 /*m68k_end_timeslice();*/
201 ctx->status = SR_READY | SR_SEEK_COMPLETE;
202 ctx->irq = true;
203 return (0);
204 }
206 uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
207 {
208 /*m68k_end_timeslice();*/
209 ctx->drq = true;
210 return (0);
211 }
213 uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
214 {
215 uint8_t temp = 0;
217 /*cpu_log_enabled = 1;*/
219 switch (addr & 0x07) {
220 case WD2010_REG_ERROR:
221 return ctx->error_reg;
222 case WD2010_REG_SECTOR_COUNT:
223 return ctx->sector_count;
224 case WD2010_REG_SECTOR_NUMBER:
225 return ctx->sector_number;
226 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
227 return ctx->cylinder_high_reg;
228 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
229 return ctx->cylinder_low_reg;
230 case WD2010_REG_SDH:
231 return ctx->sdh;
232 case WD2010_REG_STATUS: // Status register
233 // Read from status register clears IRQ
234 ctx->irq = false;
235 // Get current status flags (set by last command)
236 // DRQ bit
237 if (ctx->cmd_has_drq) {
238 temp = ctx->status & ~(SR_BUSY & SR_DRQ);
239 temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
240 LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
241 } else {
242 temp = ctx->status & ~0x80;
243 }
244 /*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
245 // HDC is busy if there is still data in the buffer
246 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!
247 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
248 /*XXX: should anything else be set here?*/
249 return temp;
250 default:
251 // shut up annoying compilers which don't recognise unreachable code when they see it
252 // (here's looking at you, gcc!)
253 return 0xff;
254 }
255 }
258 void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
259 {
260 uint8_t cmd = val & CMD_MASK;
261 size_t lba;
262 int new_track;
263 int sector_count;
265 m68k_end_timeslice();
267 /*cpu_log_enabled = 1;*/
269 if (addr == UNIXPC_REG_MCR2) {
270 // The UNIX PC has an "MCR2" register with the following format:
271 // [ 7..2 ][1][0]
272 // Bits 7..2: Not used
273 // Bit 1: DDRIVE1 (hard disk drive 1 select - not used?)
274 // Bit 0: HDSEL3 (head-select bit 3)
275 ctx->mcr2_hdsel3 = ((val & 1) == 1);
276 ctx->mcr2_ddrive1 = ((val & 2) == 2);
277 return;
278 }
280 switch (addr & 0x07) {
281 case WD2010_REG_WRITE_PRECOMP_CYLINDER:
282 break;
283 case WD2010_REG_SECTOR_COUNT:
284 ctx->sector_count = val;
285 break;
286 case WD2010_REG_SECTOR_NUMBER:
287 ctx->sector_number = val;
288 break;
289 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
290 ctx->cylinder_high_reg = val;
291 break;
292 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
293 ctx->cylinder_low_reg = val;
294 break;
295 case WD2010_REG_SDH:
296 /*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
297 ctx->data_pos = ctx->data_len = 0;
298 ctx->sdh = val;
299 break;
300 case WD2010_REG_COMMAND: // Command register
301 // write to command register clears interrupt request
302 ctx->irq = false;
303 ctx->error_reg = 0;
305 /*cpu_log_enabled = 1;*/
306 switch (cmd) {
307 case CMD_RESTORE:
308 // Restore. Set track to 0 and throw an IRQ.
309 ctx->track = 0;
310 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
311 break;
312 case CMD_SCAN_ID:
313 ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
314 ctx->cylinder_low_reg = ctx->track & 0xff;
315 ctx->sector_number = ctx->sector;
316 ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
317 case CMD_WRITE_FORMAT:
318 case CMD_SEEK:
319 case CMD_READ_SECTOR:
320 case CMD_WRITE_SECTOR:
321 // Seek. Seek to the track specced in the cylinder
322 // registers.
323 new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
324 if (new_track < ctx->geom_tracks) {
325 ctx->track = new_track;
326 } else {
327 // Seek error. :(
328 ctx->status = SR_ERROR;
329 ctx->error_reg = ER_ID_NOT_FOUND;
330 ctx->irq = true;
331 break;
332 }
333 // The SDH register provides 3 head select bits; the 4th comes from MCR2.
334 ctx->head = (ctx->sdh & 0x07) + (ctx->mcr2_hdsel3 ? 8 : 0);
335 ctx->sector = ctx->sector_number;
337 ctx->formatting = cmd == CMD_WRITE_FORMAT;
338 switch (cmd){
339 case CMD_SEEK:
340 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
341 break;
342 case CMD_READ_SECTOR:
343 /*XXX: does a separate function to set the head have to be added?*/
344 LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
346 // Read Sector
348 // Check to see if the cyl, hd and sec are valid
349 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) {
350 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
351 ctx->track, ctx->head, ctx->sector,
352 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1);
353 // CHS parameters exceed limits
354 ctx->status = SR_ERROR;
355 ctx->error_reg = ER_ID_NOT_FOUND;
356 // Set IRQ
357 ctx->irq = true;
358 break;
359 }
361 // reset data pointers
362 ctx->data_pos = ctx->data_len = 0;
364 if (val & CMD_MULTI_SECTOR){
365 ctx->multi_sector = 1;
366 sector_count = ctx->sector_count;
367 }else{
368 ctx->multi_sector = 0;
369 sector_count = 1;
370 }
371 for (int i=0; i<sector_count; i++) {
372 // Calculate the LBA address of the required sector
373 // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
374 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
375 // convert LBA to byte address
376 lba *= ctx->geom_secsz;
377 LOG("\tREAD lba = %lu", lba);
379 // Read the sector from the file
380 fseek(ctx->disc_image, lba, SEEK_SET);
381 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
382 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
383 LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
384 }
386 ctx->status = 0;
387 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
388 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
390 break;
391 case CMD_WRITE_FORMAT:
392 ctx->sector = 0;
393 case CMD_WRITE_SECTOR:
394 LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
395 // Read Sector
397 // Check to see if the cyl, hd and sec are valid
398 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) {
399 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
400 ctx->track, ctx->head, ctx->sector,
401 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
402 // CHS parameters exceed limits
403 ctx->status = SR_ERROR;
404 ctx->error_reg = ER_ID_NOT_FOUND;
405 // Set IRQ
406 ctx->irq = true;
407 break;
408 }
410 // reset data pointers
411 ctx->data_pos = ctx->data_len = 0;
413 if (val & CMD_MULTI_SECTOR){
414 ctx->multi_sector = 1;
415 sector_count = ctx->sector_count;
416 }else{
417 ctx->multi_sector = 0;
418 sector_count = 1;
419 }
420 ctx->data_len = ctx->geom_secsz * sector_count;
421 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
422 // convert LBA to byte address
423 ctx->write_pos = lba * ctx->geom_secsz;
424 LOG("\tWRITE lba = %lu", lba);
426 ctx->status = 0;
427 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
428 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
430 break;
431 default:
432 LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
433 break;
434 }
435 break;
436 case CMD_2010_EXT: /* not implemented */
437 default:
438 LOG("WD2010: unknown command %x\n", cmd);
439 ctx->status = SR_ERROR;
440 ctx->error_reg = ER_ABORTED_COMMAND;
441 ctx->irq = true;
442 break;
443 }
444 break;
446 }
447 }