Wed, 13 Mar 2013 00:43:25 +0000
[wd2010,main] WD2010 disc geometry fixes
I believe I have fixed the geometry problem with FreeBee. The geometry was set
to 17 sectors per track instead of 16, which obviously throws off addressing.
I changed it to use 16 sectors per track. However, s4diag tries to format
sector 17, so I changed the WD2010 emulation to accept any address when
formatting (since the format command doesn't actually do anything, it doesn't
matter). It is now possible to format the hard disk, initialize the file
system, and mount it. However, cpio still fails to copy the system to the hard
disk.
Author: Andrew Warkentin <andreww591 gmail com>
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 #ifndef WD2010_SEEK_DELAY
16 #define WD2010_SEEK_DELAY 30
17 #endif
19 #define CMD_ENABLE_RETRY 0x01
20 #define CMD_LONG_MODE 0x02
21 #define CMD_MULTI_SECTOR 0x04
22 #define CMD_INTRQ_WHEN_COMPLETE 0x08
24 #define ER_BAD_BLOCK 0x80
25 #define ER_CRC 0x40
26 #define ER_ID_NOT_FOUND 0x10
27 #define ER_ABORTED_COMMAND 0x04
28 #define ER_NO_TK0 0x02
29 #define ER_NO_ADDRESS_MARK 0x01
31 #define SR_BUSY 0x80
32 #define SR_READY 0x40
33 #define SR_WRITE_FAULT 0x20
34 #define SR_SEEK_COMPLETE 0x10
35 #define SR_DRQ 0x08
36 #define SR_CORRECTED 0x04
37 #define SR_COMMAND_IN_PROGRESS 0x02
38 #define SR_ERROR 0x01
40 extern int cpu_log_enabled;
42 /// WD2010 command constants
43 enum {
44 CMD_MASK = 0xF0, ///< Bit mask to detect command bits
45 CMD_2010_EXT = 0x00, ///< WD2010 extended commands (compute correction, set parameter)
46 CMD_RESTORE = 0x10, ///< Restore (recalibrate, seek to track 0)
47 CMD_READ_SECTOR = 0x20, ///< Read sector
48 CMD_WRITE_SECTOR = 0x30, ///< Write sector
49 CMD_SCAN_ID = 0x40, ///< Scan ID
50 CMD_WRITE_FORMAT = 0x50, ///< Write format
51 CMD_SEEK = 0x70, ///< Seek to given track
52 };
54 int wd2010_init(WD2010_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
55 {
56 size_t filesize;
58 wd2010_reset(ctx);
60 // Start by finding out how big the image file is
61 fseek(fp, 0, SEEK_END);
62 filesize = ftell(fp);
63 fseek(fp, 0, SEEK_SET);
65 // Now figure out how many tracks it contains
66 unsigned int tracks = filesize / secsz / spt / heads;
67 // Confirm...
68 if (tracks < 1) {
69 return WD2010_ERR_BAD_GEOM;
70 }
72 LOG("WD2010 initialised, %d cylinders, %d heads, %d sectors per track", tracks, heads, spt);
74 // Allocate enough memory to store one disc track
75 if (ctx->data) {
76 free(ctx->data);
77 }
78 ctx->data = malloc(secsz * spt);
79 if (!ctx->data)
80 return WD2010_ERR_NO_MEMORY;
82 // Load the image and the geometry data
83 ctx->disc_image = fp;
84 ctx->geom_tracks = tracks;
85 ctx->geom_secsz = secsz;
86 ctx->geom_heads = heads;
87 ctx->geom_spt = spt;
89 return WD2010_ERR_OK;
90 }
92 void wd2010_reset(WD2010_CTX *ctx)
93 {
94 // track, head and sector unknown
95 ctx->track = ctx->head = ctx->sector = 0;
97 // no IRQ pending
98 ctx->irq = false;
100 // no data available
101 ctx->data_pos = ctx->data_len = 0;
103 // Status register clear, not busy
104 ctx->status = 0;
106 ctx->sector_count = 0;
107 ctx->sector_number = 0;
108 ctx->cylinder_low_reg = 0;
109 ctx->cylinder_high_reg = 0;
110 ctx->sdh = 0;
111 ctx->mcr2_hdsel3 = 0;
112 ctx->mcr2_ddrive1 = 0;
113 }
115 void wd2010_done(WD2010_CTX *ctx)
116 {
117 // Reset the WD2010
118 wd2010_reset(ctx);
120 // Free any allocated memory
121 if (ctx->data) {
122 free(ctx->data);
123 ctx->data = NULL;
124 }
125 }
128 bool wd2010_get_irq(WD2010_CTX *ctx)
129 {
130 return ctx->irq;
131 }
133 bool wd2010_get_drq(WD2010_CTX *ctx)
134 {
135 return (ctx->drq && ctx->data_pos < ctx->data_len);
136 }
138 void wd2010_dma_miss(WD2010_CTX *ctx)
139 {
140 ctx->data_pos = ctx->data_len;
141 ctx->write_pos = 0;
142 ctx->status = SR_READY | SR_SEEK_COMPLETE;
143 ctx->irq = true;
144 }
146 uint8_t wd2010_read_data(WD2010_CTX *ctx)
147 {
148 // If there's data in the buffer, return it. Otherwise return 0xFF.
149 if (ctx->data_pos < ctx->data_len) {
150 if (ctx->multi_sector && (ctx->data_pos > 0) && ((ctx->data_pos % ctx->geom_secsz) == 0)){
151 ctx->sector_count--;
152 ctx->sector_number++;
153 }
154 // set IRQ if this is the last data byte
155 if (ctx->data_pos == (ctx->data_len-1)) {
156 ctx->status = SR_READY | SR_SEEK_COMPLETE;
157 // Set IRQ
158 ctx->irq = true;
159 ctx->drq = false;
160 }
161 // return data byte and increment pointer
162 return ctx->data[ctx->data_pos++];
163 } else {
164 // empty buffer (this shouldn't happen)
165 LOGS("WD2010: attempt to read from empty data buffer");
166 return 0xff;
167 }
168 }
170 void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
171 {
172 // If we're processing a write command, and there's space in the
173 // buffer, allow the write.
174 if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
175 // store data byte and increment pointer
176 if (ctx->multi_sector && (ctx->data_pos > 0) && ((ctx->data_pos % ctx->geom_secsz) == 0)){
177 ctx->sector_count--;
178 ctx->sector_number++;
179 }
180 ctx->data[ctx->data_pos++] = val;
181 // set IRQ and write data if this is the last data byte
182 if (ctx->data_pos == ctx->data_len) {
183 if (!ctx->formatting){
184 fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
185 fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
186 fflush(ctx->disc_image);
187 }
188 ctx->formatting = false;
189 ctx->status = SR_READY | SR_SEEK_COMPLETE;
190 // Set IRQ and reset write pointer
191 ctx->irq = true;
192 ctx->write_pos = -1;
193 ctx->drq = false;
194 }
195 }else{
196 LOGS("WD2010: attempt to write to data buffer without a write command in progress");
197 }
198 }
200 uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
201 {
202 /*m68k_end_timeslice();*/
203 ctx->status = SR_READY | SR_SEEK_COMPLETE;
204 ctx->irq = true;
205 return (0);
206 }
208 uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
209 {
210 /*m68k_end_timeslice();*/
211 ctx->drq = true;
212 return (0);
213 }
215 uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
216 {
217 uint8_t temp = 0;
219 /*cpu_log_enabled = 1;*/
221 switch (addr & 0x07) {
222 case WD2010_REG_ERROR:
223 return ctx->error_reg;
224 case WD2010_REG_SECTOR_COUNT:
225 return ctx->sector_count;
226 case WD2010_REG_SECTOR_NUMBER:
227 return ctx->sector_number;
228 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
229 return ctx->cylinder_high_reg;
230 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
231 return ctx->cylinder_low_reg;
232 case WD2010_REG_SDH:
233 return ctx->sdh;
234 case WD2010_REG_STATUS: // Status register
235 // Read from status register clears IRQ
236 ctx->irq = false;
237 // Get current status flags (set by last command)
238 // DRQ bit
239 if (ctx->cmd_has_drq) {
240 temp = ctx->status & ~(SR_BUSY & SR_DRQ);
241 temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
242 LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
243 } else {
244 temp = ctx->status & ~0x80;
245 }
246 /*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
247 // HDC is busy if there is still data in the buffer
248 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!
249 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
250 /*XXX: should anything else be set here?*/
251 return temp;
252 default:
253 // shut up annoying compilers which don't recognise unreachable code when they see it
254 // (here's looking at you, gcc!)
255 return 0xff;
256 }
257 }
260 void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
261 {
262 uint8_t cmd = val & CMD_MASK;
263 size_t lba;
264 int new_track;
265 int sector_count;
267 m68k_end_timeslice();
269 /*cpu_log_enabled = 1;*/
271 if (addr == UNIXPC_REG_MCR2) {
272 // The UNIX PC has an "MCR2" register with the following format:
273 // [ 7..2 ][1][0]
274 // Bits 7..2: Not used
275 // Bit 1: DDRIVE1 (hard disk drive 1 select - not used?)
276 // Bit 0: HDSEL3 (head-select bit 3)
277 ctx->mcr2_hdsel3 = ((val & 1) == 1);
278 ctx->mcr2_ddrive1 = ((val & 2) == 2);
279 return;
280 }
282 switch (addr & 0x07) {
283 case WD2010_REG_WRITE_PRECOMP_CYLINDER:
284 break;
285 case WD2010_REG_SECTOR_COUNT:
286 ctx->sector_count = val;
287 break;
288 case WD2010_REG_SECTOR_NUMBER:
289 ctx->sector_number = val;
290 break;
291 case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder
292 ctx->cylinder_high_reg = val;
293 break;
294 case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder
295 ctx->cylinder_low_reg = val;
296 break;
297 case WD2010_REG_SDH:
298 /*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
299 //ctx->data_pos = ctx->data_len = 0;
300 ctx->sdh = val;
301 break;
302 case WD2010_REG_COMMAND: // Command register
303 // write to command register clears interrupt request
304 ctx->irq = false;
305 ctx->error_reg = 0;
307 /*cpu_log_enabled = 1;*/
308 switch (cmd) {
309 case CMD_RESTORE:
310 // Restore. Set track to 0 and throw an IRQ.
311 ctx->track = 0;
312 SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
313 break;
314 case CMD_SCAN_ID:
315 ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
316 ctx->cylinder_low_reg = ctx->track & 0xff;
317 ctx->sector_number = ctx->sector;
318 ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
319 case CMD_WRITE_FORMAT:
320 case CMD_SEEK:
321 case CMD_READ_SECTOR:
322 case CMD_WRITE_SECTOR:
323 // Seek. Seek to the track specced in the cylinder
324 // registers.
325 new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
326 if (new_track < ctx->geom_tracks) {
327 ctx->track = new_track;
328 } else {
329 // Seek error. :(
330 LOG("WD2010 ALERT: track %d out of range", new_track);
331 ctx->status = SR_ERROR;
332 ctx->error_reg = ER_ID_NOT_FOUND;
333 ctx->irq = true;
334 break;
335 }
336 // The SDH register provides 3 head select bits; the 4th comes from MCR2.
337 ctx->head = (ctx->sdh & 0x07) + (ctx->mcr2_hdsel3 ? 8 : 0);
338 ctx->sector = ctx->sector_number;
340 ctx->formatting = cmd == CMD_WRITE_FORMAT;
341 switch (cmd){
342 case CMD_SEEK:
343 SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
344 break;
345 case CMD_READ_SECTOR:
346 /*XXX: does a separate function to set the head have to be added?*/
347 LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d nsectors=%d", cmd, ctx->track, ctx->head, ctx->sector, ctx->sector_count);
349 // Read Sector
351 // Check to see if the cyl, hd and sec are valid
352 if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || ((ctx->sector + ctx->sector_count - 1) > ctx->geom_spt-1)) {
353 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, nSecs=%d, endSec=%d maxCHS=%d:%d:%d",
354 ctx->track, ctx->head, ctx->sector,
355 ctx->sector_count,
356 ctx->sector + ctx->sector_count - 1,
357 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
358 // CHS parameters exceed limits
359 ctx->status = SR_ERROR;
360 ctx->error_reg = ER_ID_NOT_FOUND;
361 // Set IRQ
362 ctx->irq = true;
363 break;
364 }
366 // reset data pointers
367 ctx->data_pos = ctx->data_len = 0;
369 if (val & CMD_MULTI_SECTOR){
370 ctx->multi_sector = 1;
371 sector_count = ctx->sector_count;
372 }else{
373 ctx->multi_sector = 0;
374 sector_count = 1;
375 }
376 for (int i=0; i<sector_count; i++) {
377 // Calculate the LBA address of the required sector
378 // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
379 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
380 // convert LBA to byte address
381 lba *= ctx->geom_secsz;
382 LOG("\tREAD lba = %lu", lba);
384 // Read the sector from the file
385 fseek(ctx->disc_image, lba, SEEK_SET);
386 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
387 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
388 LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
389 }
391 ctx->status = 0;
392 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
393 SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
395 break;
396 case CMD_WRITE_FORMAT:
397 ctx->sector = 0;
398 case CMD_WRITE_SECTOR:
399 LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d nsectors=%d", cmd, ctx->track, ctx->head, ctx->sector, ctx->sector_count);
400 // Write Sector
402 // Check to see if the cyl, hd and sec are valid
403 if (cmd != CMD_WRITE_FORMAT && ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || ((ctx->sector + ctx->sector_count - 1) > ctx->geom_spt-1))) {
404 LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, nSecs=%d, endSec=%d maxCHS=%d:%d:%d",
405 ctx->track, ctx->head, ctx->sector,
406 ctx->sector_count,
407 ctx->sector + ctx->sector_count - 1,
408 ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
409 // CHS parameters exceed limits
410 ctx->status = SR_ERROR;
411 ctx->error_reg = ER_ID_NOT_FOUND;
412 // Set IRQ
413 ctx->irq = true;
414 break;
415 }
417 // reset data pointers
418 ctx->data_pos = ctx->data_len = 0;
420 if (val & CMD_MULTI_SECTOR){
421 ctx->multi_sector = 1;
422 sector_count = ctx->sector_count;
423 }else{
424 ctx->multi_sector = 0;
425 sector_count = 1;
426 }
427 ctx->data_len = ctx->geom_secsz * sector_count;
428 lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
429 // convert LBA to byte address
430 ctx->write_pos = (lba *= ctx->geom_secsz);
431 LOG("\tWRITE lba = %zu", lba);
433 ctx->status = 0;
434 ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
435 SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
437 break;
438 default:
439 LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
440 break;
441 }
442 break;
443 case CMD_2010_EXT: /* not implemented */
444 default:
445 LOG("WD2010: unknown command %x\n", cmd);
446 ctx->status = SR_ERROR;
447 ctx->error_reg = ER_ABORTED_COMMAND;
448 ctx->irq = true;
449 break;
450 }
451 break;
453 }
454 }