Sun, 05 Dec 2010 10:17:38 +0000
more floppy controller stuff
1 #include <stdint.h>
2 #include <stdbool.h>
3 #include <malloc.h>
4 #include "wd279x.h"
6 /// WD2797 command constants
7 enum {
8 CMD_MASK = 0xF0, ///< Bit mask to detect command bits
9 CMD_RESTORE = 0x00, ///< Restore (recalibrate, seek to track 0)
10 CMD_SEEK = 0x10, ///< Seek to given track
11 CMD_STEP = 0x20, ///< Step
12 CMD_STEP_TU = 0x30, ///< Step and update track register
13 CMD_STEPIN = 0x40, ///< Step In
14 CMD_STEPIN_TU = 0x50, ///< Step In and update track register
15 CMD_STEPOUT = 0x60, ///< Step Out
16 CMD_STEPOUT_TU = 0x70, ///< Step Out and update track register
17 CMD_READ_SECTOR = 0x80, ///< Read Sector
18 CMD_READ_SECTOR_MULTI = 0x90, ///< Read Multiple Sectors
19 CMD_WRITE_SECTOR = 0xA0, ///< Write Sector
20 CMD_WRITE_SECTOR_MULTI = 0xB0, ///< Write Multiple Sectors
21 CMD_READ_ADDRESS = 0xC0, ///< Read Address (IDAM contents)
22 CMD_FORCE_INTERRUPT = 0xD0, ///< Force Interrupt
23 CMD_READ_TRACK = 0xE0, ///< Read Track
24 CMD_FORMAT_TRACK = 0xF0 ///< Format Track
25 };
28 void wd2797_init(WD2797_CTX *ctx)
29 {
30 // track, head and sector unknown
31 ctx->track = ctx->head = ctx->sector = 0;
33 // no IRQ pending
34 ctx->irql = ctx->irqe = false;
36 // no data available
37 ctx->data_pos = ctx->data_len = 0;
38 ctx->data = NULL;
40 // Status register clear, not busy
41 ctx->status = 0;
43 // Clear data register
44 ctx->data_reg = 0;
46 // Last step direction
47 ctx->last_step_dir = -1;
49 // No disc image loaded
50 ctx->disc_image = NULL;
51 ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0;
52 }
55 void wd2797_reset(WD2797_CTX *ctx)
56 {
57 // track, head and sector unknown
58 ctx->track = ctx->head = ctx->sector = 0;
60 // no IRQ pending
61 ctx->irql = ctx->irqe = false;
63 // no data available
64 ctx->data_pos = ctx->data_len = 0;
66 // Status register clear, not busy
67 ctx->status = 0;
69 // Clear data register
70 ctx->data_reg = 0;
72 // Last step direction
73 ctx->last_step_dir = -1;
74 }
77 void wd2797_done(WD2797_CTX *ctx)
78 {
79 // Reset the WD2797
80 wd2797_reset(ctx);
82 // Free any allocated memory
83 if (ctx->data) {
84 free(ctx->data);
85 ctx->data = NULL;
86 }
87 }
90 bool wd2797_get_irq(WD2797_CTX *ctx)
91 {
92 // If an IRQ is pending, clear it and return true, otherwise return false
93 if (ctx->irqe) {
94 ctx->irqe = false;
95 return true;
96 } else {
97 return false;
98 }
99 }
102 bool wd2797_get_drq(WD2797_CTX *ctx)
103 {
104 return (ctx->data_pos < ctx->data_len);
105 }
108 WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
109 {
110 size_t filesize;
112 // Start by finding out how big the image file is
113 fseek(fp, 0, SEEK_END);
114 filesize = ftell(fp);
115 fseek(fp, 0, SEEK_SET);
117 // Now figure out how many tracks it contains
118 int tracks = filesize / secsz / spt / heads;
119 // Confirm...
120 if (tracks < 1) {
121 return WD2797_ERR_BAD_GEOM;
122 }
124 // Allocate enough memory to store one disc track
125 if (ctx->data) {
126 free(ctx->data);
127 }
128 ctx->data = malloc(secsz * spt);
129 if (!ctx->data)
130 return WD2797_ERR_NO_MEMORY;
132 // Load the image and the geometry data
133 ctx->disc_image = fp;
134 ctx->geom_tracks = tracks;
135 ctx->geom_secsz = secsz;
136 ctx->geom_heads = heads;
137 ctx->geom_spt = spt;
139 return WD2797_ERR_OK;
140 }
143 void wd2797_unload(WD2797_CTX *ctx)
144 {
145 // Free memory buffer
146 if (ctx->data) {
147 free(ctx->data);
148 ctx->data = NULL;
149 }
151 // Clear file pointer
152 ctx->disc_image = NULL;
154 // Clear the disc geometry
155 ctx->geom_tracks = ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = 0;
156 }
159 uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr)
160 {
161 uint8_t temp = 0;
163 switch (addr & 0x03) {
164 case WD2797_REG_STATUS: // Status register
165 // Read from status register clears IRQ
166 ctx->irql = false;
168 // Get current status flags (set by last command)
169 temp = ctx->status;
170 // DRQ bit
171 if (ctx->cmd_has_drq)
172 temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
173 // FDC is busy if there is still data in the buffer
174 temp |= (ctx->data_pos < ctx->data_len) ? 0x01 : 0x00; // if data in buffer, then DMA hasn't copied it yet, and we're still busy!
175 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
176 return temp;
178 case WD2797_REG_TRACK: // Track register
179 return ctx->track;
181 case WD2797_REG_SECTOR: // Sector register
182 return ctx->sector;
184 case WD2797_REG_DATA: // Data register
185 // If there's data in the buffer, return it. Otherwise return 0xFF.
186 if (ctx->data_pos < ctx->data_len) {
187 // return data byte and increment pointer
188 return ctx->data[ctx->data_pos++];
189 } else {
190 return 0xff;
191 }
193 default:
194 // shut up annoying compilers which don't recognise unreachable code when they see it
195 // (here's looking at you, gcc!)
196 return 0xff;
197 }
198 }
201 void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val)
202 {
203 uint8_t cmd = val & CMD_MASK;
204 size_t lba;
205 bool is_type1 = false;
206 int temp;
208 switch (addr) {
209 case WD2797_REG_COMMAND: // Command register
210 // write to command register clears interrupt request
211 ctx->irql = false;
213 // Is the drive ready?
214 if (ctx->disc_image == NULL) {
215 // No disc image, thus the drive is busy.
216 ctx->status = 0x80;
217 return;
218 }
220 // Handle Type 1 commands
221 switch (cmd) {
222 case CMD_RESTORE:
223 // Restore. Set track to 0 and throw an IRQ.
224 is_type1 = true;
225 ctx->track = 0;
226 break;
228 case CMD_SEEK:
229 // Seek. Seek to the track specced in the Data Register.
230 is_type1 = true;
231 if (ctx->data_reg < ctx->geom_tracks) {
232 ctx->track = ctx->data_reg;
233 } else {
234 // Seek error. :(
235 ctx->status = 0x10;
236 }
238 case CMD_STEP:
239 // TODO! deal with trk0!
240 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
241 is_type1 = true;
242 break;
244 case CMD_STEPIN:
245 case CMD_STEPOUT:
246 // TODO! deal with trk0!
247 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
248 if (cmd == CMD_STEPIN) {
249 ctx->last_step_dir = 1;
250 } else {
251 ctx->last_step_dir = -1;
252 }
253 is_type1 = true;
254 break;
256 case CMD_STEP_TU:
257 case CMD_STEPIN_TU:
258 case CMD_STEPOUT_TU:
259 // if this is a Step In or Step Out cmd, set the step-direction
260 if (cmd == CMD_STEPIN_TU) {
261 ctx->last_step_dir = 1;
262 } else if (cmd == CMD_STEPOUT_TU) {
263 ctx->last_step_dir = -1;
264 }
266 // Seek one step in the last direction used.
267 ctx->track += ctx->last_step_dir;
268 if (ctx->track < 0) ctx->track = 0;
269 if (ctx->track >= ctx->geom_tracks) {
270 // Seek past end of disc... that'll be a Seek Error then.
271 ctx->status = 0x10;
272 ctx->track = ctx->geom_tracks - 1;
273 }
274 is_type1 = true;
275 break;
277 default:
278 break;
279 }
281 if (is_type1) {
282 // Terminate any sector reads or writes
283 ctx->data_len = ctx->data_pos = 0;
285 // No DRQ bit for these commands.
286 ctx->cmd_has_drq = false;
288 // Type1 status byte...
289 ctx->status = 0;
290 // S7 = Not Ready. Command executed, therefore the drive was ready... :)
291 // S6 = Write Protect. TODO: add this
292 // S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
293 ctx->status |= 0x20;
294 // S4 = Seek Error. Not bloody likely if we got down here...!
295 // S3 = CRC Error. Not gonna happen on a disc image!
296 // S2 = Track 0
297 ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
298 // S1 = Index Pulse. TODO -- need periodics to emulate this
299 // S0 = Busy. We just exec'd the command, thus we're not busy.
300 // TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
302 // Set IRQ only if IRQL has been cleared (no pending IRQs)
303 ctx->irqe = !ctx->irql;
304 ctx->irql = true;
305 return;
306 }
308 // That's the Type 1 (seek) commands sorted. Now for the others.
310 // If drive isn't ready, then set status B7 and exit
311 if (ctx->disc_image == NULL) {
312 ctx->status = 0x80;
313 return;
314 }
316 // If this is a Write command, check write protect status too
317 // TODO!
318 if (false) {
319 // Write protected disc...
320 if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
321 // Set Write Protect bit and bail.
322 ctx->status = 0x40;
324 // Set IRQ only if IRQL has been cleared (no pending IRQs)
325 ctx->irqe = !ctx->irql;
326 ctx->irql = true;
328 return;
329 }
330 }
332 // Disc is ready to go. Parse the command word.
333 switch (cmd) {
334 case CMD_READ_ADDRESS:
335 // Read Address
337 // reset data pointers
338 ctx->data_pos = ctx->data_len = 0;
340 // load data buffer
341 ctx->data[ctx->data_len++] = ctx->track;
342 ctx->data[ctx->data_len++] = ctx->head;
343 ctx->data[ctx->data_len++] = ctx->sector;
344 switch (ctx->geom_secsz) {
345 case 128: ctx->data[ctx->data_len++] = 0; break;
346 case 256: ctx->data[ctx->data_len++] = 1; break;
347 case 512: ctx->data[ctx->data_len++] = 2; break;
348 case 1024: ctx->data[ctx->data_len++] = 3; break;
349 default: ctx->data[ctx->data_len++] = 0xFF; break; // TODO: deal with invalid values better
350 }
351 ctx->data[ctx->data_len++] = 0; // TODO: IDAM CRC!
352 ctx->data[ctx->data_len++] = 0;
354 // B6, B5 = 0
355 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
356 // B3 = CRC Error. Not possible.
357 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
358 // B1 = DRQ. Data request.
359 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
360 break;
362 case CMD_READ_SECTOR:
363 case CMD_READ_SECTOR_MULTI:
364 // Read Sector or Read Sector Multiple
365 // reset data pointers
366 ctx->data_pos = ctx->data_len = 0;
368 // Calculate number of sectors to read from disc
369 if (cmd == CMD_READ_SECTOR_MULTI)
370 temp = ctx->geom_spt;
371 else
372 temp = 1;
374 for (int i=0; i<temp; i++) {
375 // Calculate the LBA address of the required sector
376 lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ctx->sector - 1) * ctx->geom_secsz;
378 // Read the sector from the file
379 fseek(ctx->disc_image, lba, SEEK_SET);
380 ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
381 // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
382 }
384 // B6 = 0
385 // B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
386 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
387 // B3 = CRC Error. Not possible.
388 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
389 // B1 = DRQ. Data request.
390 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
391 break;
393 case CMD_READ_TRACK:
394 // Read Track
395 // TODO! implement this
396 // B6, B5, B4, B3 = 0
397 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
398 // B1 = DRQ. Data request.
399 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
400 break;
402 case CMD_WRITE_SECTOR:
403 case CMD_WRITE_SECTOR_MULTI:
404 // Write Sector or Write Sector Multiple
406 // reset data pointers
407 ctx->data_pos = ctx->data_len = 0;
409 // TODO: set "write pending" flag, and write LBA, and go from there.
411 // B6 = Write Protect. FIXME -- emulate this!
412 // B5 = 0
413 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
414 // B3 = CRC Error. Not possible.
415 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
416 // B1 = DRQ. Data request.
417 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
418 break;
420 case CMD_FORMAT_TRACK:
421 // Write Track (aka Format Track)
422 // B6 = Write Protect. FIXME -- emulate this!
423 // B5, B4, B3 = 0
424 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
425 // B1 = DRQ. Data request.
426 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
427 break;
429 case CMD_FORCE_INTERRUPT:
430 // Force Interrupt...
431 // Terminates current operation and sends an interrupt
432 // TODO!
433 ctx->data_pos = ctx->data_len = 0;
434 // Set IRQ only if IRQL has been cleared (no pending IRQs)
435 ctx->irqe = !ctx->irql;
436 ctx->irql = true;
437 break;
438 }
439 break;
441 case WD2797_REG_TRACK: // Track register
442 ctx->track = val;
443 break;
445 case WD2797_REG_SECTOR: // Sector register
446 ctx->sector = val;
447 break;
449 case WD2797_REG_DATA: // Data register
450 // Save the value written into the data register
451 ctx->data_reg = val;
453 // If we're processing a write command, and there's space in the
454 // buffer, allow the write.
455 if (ctx->data_pos < ctx->data_len) {
456 // store data byte and increment pointer
457 ctx->data[ctx->data_pos++] = val;
458 }
459 break;
460 }
461 }