Sat, 17 Nov 2012 19:18:29 +0000
add HDD support + fixes
Patch-Author: Andrew Warkentin <andreww591!gmail>
Patch-Message-ID: <50A772FC.8020009@gmail.com>
I have added floppy write support, full hard disk emulation, and proper handling of DMA page faults to FreeBee. I also fixed the floppy step commands, changed the "force interrupt" floppy command to generate a type 1 status, and changed the DMA address counter to reset to 3fff when a transfer completes (which is what Unix seems to expect - without it, the kernel says that the floppy isn't ready). The floppy, hard disk, and DMA page fault tests all pass. Initializing hard disks and floppies also works (the geometry for both is still fixed by the size of the image, though, and format commands only appear to succeed, but they don't modify the image). Unix still doesn't run, though (it hangs after reading some sectors from the floppy).
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 LOG("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 LOG("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 }