Sat, 17 Nov 2012 22:15:23 +0000
wd2010: use LOGS when logging unformatted strings
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 }
107 void wd2010_done(WD2010_CTX *ctx)
108 {
109 // Reset the WD2010
110 wd2010_reset(ctx);
112 // Free any allocated memory
113 if (ctx->data) {
114 free(ctx->data);
115 ctx->data = NULL;
116 }
117 }
120 bool wd2010_get_irq(WD2010_CTX *ctx)
121 {
122 return ctx->irq;
123 }
125 bool wd2010_get_drq(WD2010_CTX *ctx)
126 {
127 return (ctx->drq && ctx->data_pos < ctx->data_len);
128 }
130 void wd2010_dma_miss(WD2010_CTX *ctx)
131 {
132 ctx->data_pos = ctx->data_len;
133 ctx->write_pos = 0;
134 ctx->status = SR_READY | SR_SEEK_COMPLETE;
135 ctx->irq = true;
136 }
138 uint8_t wd2010_read_data(WD2010_CTX *ctx)
139 {
140 // If there's data in the buffer, return it. Otherwise return 0xFF.
141 if (ctx->data_pos < ctx->data_len) {
142 if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
143 ctx->sector_count--;
144 ctx->sector_number++;
145 }
146 // set IRQ if this is the last data byte
147 if (ctx->data_pos == (ctx->data_len-1)) {
148 ctx->status = SR_READY | SR_SEEK_COMPLETE;
149 // Set IRQ
150 ctx->irq = true;
151 ctx->drq = false;
152 }
153 // return data byte and increment pointer
154 return ctx->data[ctx->data_pos++];
155 } else {
156 // empty buffer (this shouldn't happen)
157 LOGS("WD2010: attempt to read from empty data buffer");
158 return 0xff;
159 }
160 }
162 void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
163 {
164 // If we're processing a write command, and there's space in the
165 // buffer, allow the write.
166 if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
167 // store data byte and increment pointer
168 if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
169 ctx->sector_count--;
170 ctx->sector_number++;
171 }
172 ctx->data[ctx->data_pos++] = val;
173 // set IRQ and write data if this is the last data byte
174 if (ctx->data_pos == ctx->data_len) {
175 if (!ctx->formatting){
176 fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
177 fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
178 fflush(ctx->disc_image);
179 }
180 ctx->formatting = false;
181 ctx->status = SR_READY | SR_SEEK_COMPLETE;
182 // Set IRQ and reset write pointer
183 ctx->irq = true;
184 ctx->write_pos = -1;
185 ctx->drq = false;
186 }
187 }else{
188 LOGS("WD2010: attempt to write to data buffer without a write command in progress");
189 }
190 }
192 uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
193 {
194 /*m68k_end_timeslice();*/
195 ctx->status = SR_READY | SR_SEEK_COMPLETE;
196 ctx->irq = true;
197 return (0);
198 }
200 uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
201 {
202 /*m68k_end_timeslice();*/
203 ctx->drq = true;
204 return (0);
205 }
207 uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
208 {
209 uint8_t temp = 0;
211 /*cpu_log_enabled = 1;*/
213 switch (addr & 0x07) {
214 case WD2010_REG_ERROR:
215 return ctx->error_reg;
216 case WD2010_REG_SECTOR_COUNT:
217 return ctx->sector_count;
218 case WD2010_REG_SECTOR_NUMBER:
219 return ctx->sector_number;
220 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
221 return ctx->cylinder_high_reg;
222 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
223 return ctx->cylinder_low_reg;
224 case WD2010_REG_SDH:
225 return ctx->sdh;
226 case WD2010_REG_STATUS: // Status register
227 // Read from status register clears IRQ
228 ctx->irq = false;
229 // Get current status flags (set by last command)
230 // DRQ bit
231 if (ctx->cmd_has_drq) {
232 temp = ctx->status & ~(SR_BUSY & SR_DRQ);
233 temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
234 LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
235 } else {
236 temp = ctx->status & ~0x80;
237 }
238 /*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
239 // HDC is busy if there is still data in the buffer
240 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!
241 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
242 /*XXX: should anything else be set here?*/
243 return temp;
244 default:
245 // shut up annoying compilers which don't recognise unreachable code when they see it
246 // (here's looking at you, gcc!)
247 return 0xff;
248 }
249 }
252 void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
253 {
254 uint8_t cmd = val & CMD_MASK;
255 size_t lba;
256 int new_track;
257 int sector_count;
259 m68k_end_timeslice();
261 /*cpu_log_enabled = 1;*/
263 switch (addr & 0x07) {
264 case WD2010_REG_WRITE_PRECOMP_CYLINDER:
265 break;
266 case WD2010_REG_SECTOR_COUNT:
267 ctx->sector_count = val;
268 break;
269 case WD2010_REG_SECTOR_NUMBER:
270 ctx->sector_number = val;
271 break;
272 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
273 ctx->cylinder_high_reg = val;
274 break;
275 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
276 ctx->cylinder_low_reg = val;
277 break;
278 case WD2010_REG_SDH:
279 /*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
280 ctx->data_pos = ctx->data_len = 0;
281 ctx->sdh = val;
282 break;
283 case WD2010_REG_COMMAND: // Command register
284 // write to command register clears interrupt request
285 ctx->irq = false;
286 ctx->error_reg = 0;
288 /*cpu_log_enabled = 1;*/
289 switch (cmd) {
290 case CMD_RESTORE:
291 // Restore. Set track to 0 and throw an IRQ.
292 ctx->track = 0;
293 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
294 break;
295 case CMD_SCAN_ID:
296 ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
297 ctx->cylinder_low_reg = ctx->track & 0xff;
298 ctx->sector_number = ctx->sector;
299 ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
300 case CMD_WRITE_FORMAT:
301 case CMD_SEEK:
302 case CMD_READ_SECTOR:
303 case CMD_WRITE_SECTOR:
304 // Seek. Seek to the track specced in the cylinder
305 // registers.
306 new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
307 if (new_track < ctx->geom_tracks) {
308 ctx->track = new_track;
309 } else {
310 // Seek error. :(
311 ctx->status = SR_ERROR;
312 ctx->error_reg = ER_ID_NOT_FOUND;
313 ctx->irq = true;
314 break;
315 }
316 ctx->head = ctx->sdh & 0x07;
317 ctx->sector = ctx->sector_number;
319 ctx->formatting = cmd == CMD_WRITE_FORMAT;
320 switch (cmd){
321 case CMD_SEEK:
322 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
323 break;
324 case CMD_READ_SECTOR:
325 /*XXX: does a separate function to set the head have to be added?*/
326 LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
328 // Read Sector
330 // Check to see if the cyl, hd and sec are valid
331 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) {
332 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
333 ctx->track, ctx->head, ctx->sector,
334 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1);
335 // CHS parameters exceed limits
336 ctx->status = SR_ERROR;
337 ctx->error_reg = ER_ID_NOT_FOUND;
338 // Set IRQ
339 ctx->irq = true;
340 break;
341 }
343 // reset data pointers
344 ctx->data_pos = ctx->data_len = 0;
346 if (val & CMD_MULTI_SECTOR){
347 ctx->multi_sector = 1;
348 sector_count = ctx->sector_count;
349 }else{
350 ctx->multi_sector = 0;
351 sector_count = 1;
352 }
353 for (int i=0; i<sector_count; i++) {
354 // Calculate the LBA address of the required sector
355 // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
356 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
357 // convert LBA to byte address
358 lba *= ctx->geom_secsz;
359 LOG("\tREAD lba = %lu", lba);
361 // Read the sector from the file
362 fseek(ctx->disc_image, lba, SEEK_SET);
363 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
364 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
365 LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
366 }
368 ctx->status = 0;
369 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
370 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
372 break;
373 case CMD_WRITE_FORMAT:
374 ctx->sector = 0;
375 case CMD_WRITE_SECTOR:
376 LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
377 // Read Sector
379 // Check to see if the cyl, hd and sec are valid
380 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) {
381 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
382 ctx->track, ctx->head, ctx->sector,
383 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
384 // CHS parameters exceed limits
385 ctx->status = SR_ERROR;
386 ctx->error_reg = ER_ID_NOT_FOUND;
387 // Set IRQ
388 ctx->irq = true;
389 break;
390 }
392 // reset data pointers
393 ctx->data_pos = ctx->data_len = 0;
395 if (val & CMD_MULTI_SECTOR){
396 ctx->multi_sector = 1;
397 sector_count = ctx->sector_count;
398 }else{
399 ctx->multi_sector = 0;
400 sector_count = 1;
401 }
402 ctx->data_len = ctx->geom_secsz * sector_count;
403 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
404 // convert LBA to byte address
405 ctx->write_pos = lba * ctx->geom_secsz;
406 LOG("\tWRITE lba = %lu", lba);
408 ctx->status = 0;
409 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
410 SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
412 break;
413 default:
414 LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
415 break;
416 }
417 break;
418 case CMD_2010_EXT: /* not implemented */
419 default:
420 LOG("WD2010: unknown command %x\n", cmd);
421 ctx->status = SR_ERROR;
422 ctx->error_reg = ER_ABORTED_COMMAND;
423 ctx->irq = true;
424 break;
425 }
426 break;
428 }
429 }