Sun, 05 Dec 2010 03:55:46 +0000
add preliminary WD2797 FDC emulator
1 #include <stdint.h>
2 #include <stdbool.h>
3 #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 /**
29 * @brief Initialise a WD2797 context.
30 */
31 void wd2797_init(WD279X_CTX *ctx)
32 {
33 // track, head and sector unknown
34 ctx->track = 0;
35 ctx->head = 0;
36 ctx->sector = 0;
38 // no IRQ or DRQ, no IRQ callback
39 ctx->irql = false;
40 ctx->irqe = false;
42 // no data available
43 ctx->data_pos = 0;
44 ctx->data_len = 0;
46 // No disc image loaded
47 ctx->disc_image = NULL;
48 }
51 /**
52 * @brief Read IRQ Rising Edge status. Clears Rising Edge status if it is set.
53 * @note No more IRQs will be sent until the Status Register is read, or a new command is written to the CR.
54 */
55 bool wd279x_get_irq(WD279X_CTX *ctx)
56 {
57 // If an IRQ is pending, clear it and return true, otherwise return false
58 if (ctx->irqe) {
59 ctx->irqe = false;
60 return true;
61 } else {
62 return false;
63 }
64 }
67 /**
68 * @brief Read DRQ status.
69 */
70 bool wd279x_get_drq(WD279X_CTX *ctx)
71 {
72 return (ctx->data_pos < ctx->data_len);
73 }
76 /**
77 * @brief Read WD279x register.
78 */
79 uint8_t wd279x_read_reg(WD279X_CTX *ctx, uint8_t addr)
80 {
81 uint8_t temp = 0;
83 switch (addr & 0x03) {
84 case WD279X_REG_STATUS: // Status register
85 // Read from status register clears IRQ
86 ctx->irql = false;
88 // Get current status flags (set by last command)
89 temp = ctx->status;
90 // DRQ bit
91 if (ctx->cmd_has_drq)
92 temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
93 // FDC is busy if there is still data in the buffer
94 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!
95 // TODO: also if seek delay / read delay hasn't passed (but that's for later)
96 return temp;
98 case WD279X_REG_TRACK: // Track register
99 return ctx->track;
101 case WD279X_REG_SECTOR: // Sector register
102 return ctx->sector;
104 case WD279X_REG_DATA: // Data register
105 // If there's data in the buffer, return it. Otherwise return 0xFF.
106 if (ctx->data_pos < ctx->data_len) {
107 // return data byte and increment pointer
108 return ctx->data[ctx->data_pos++];
109 } else {
110 return 0xff;
111 }
113 default:
114 // shut up annoying compilers which don't recognise unreachable code when they see it
115 // (here's looking at you, gcc!)
116 return 0xff;
117 }
118 }
121 /**
122 * Write WD279X register
123 */
124 void wd279x_write_reg(WD279X_CTX *ctx, uint8_t addr, uint8_t val)
125 {
126 uint8_t cmd = val & CMD_MASK;
127 size_t lba;
128 bool is_type1 = false;
130 switch (addr) {
131 case WD279X_REG_COMMAND: // Command register
132 // write to command register clears interrupt request
133 ctx->irql = false;
135 // Is the drive ready?
136 if (ctx->disc_image == NULL) {
137 // No disc image, thus the drive is busy.
138 ctx->status = 0x80;
139 return;
140 }
142 // Handle Type 1 commands
143 switch (cmd) {
144 case CMD_RESTORE:
145 // Restore. Set track to 0 and throw an IRQ.
146 is_type1 = true;
147 ctx->track = 0;
148 break;
150 case CMD_SEEK:
151 // Seek. Seek to the track specced in the Data Register.
152 is_type1 = true;
153 if (ctx->data_reg < ctx->geom_tracks) {
154 ctx->track = ctx->data_reg;
155 } else {
156 // Seek error. :(
157 ctx->status = 0x10;
158 }
160 case CMD_STEP:
161 // TODO! deal with trk0!
162 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
163 is_type1 = true;
164 break;
166 case CMD_STEPIN:
167 case CMD_STEPOUT:
168 // TODO! deal with trk0!
169 // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
170 if (cmd == CMD_STEPIN) {
171 ctx->last_step_dir = 1;
172 } else {
173 ctx->last_step_dir = -1;
174 }
175 is_type1 = true;
176 break;
178 case CMD_STEP_TU:
179 case CMD_STEPIN_TU:
180 case CMD_STEPOUT_TU:
181 // if this is a Step In or Step Out cmd, set the step-direction
182 if (cmd == CMD_STEPIN_TU) {
183 ctx->last_step_dir = 1;
184 } else if (cmd == CMD_STEPOUT_TU) {
185 ctx->last_step_dir = -1;
186 }
188 // Seek one step in the last direction used.
189 ctx->track += ctx->last_step_dir;
190 if (ctx->track < 0) ctx->track = 0;
191 if (ctx->track >= ctx->geom_tracks) {
192 // Seek past end of disc... that'll be a Seek Error then.
193 ctx->status = 0x10;
194 ctx->track = ctx->geom_tracks - 1;
195 }
196 is_type1 = true;
197 break;
199 default:
200 break;
201 }
203 if (is_type1) {
204 // Terminate any sector reads or writes
205 ctx->data_len = ctx->data_pos = 0;
207 // No DRQ bit for these commands.
208 ctx->cmd_has_drq = false;
210 // Type1 status byte...
211 ctx->status = 0;
212 // S7 = Not Ready. Command executed, therefore the drive was ready... :)
213 // S6 = Write Protect. TODO: add this
214 // S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
215 ctx->status |= 0x20;
216 // S4 = Seek Error. Not bloody likely if we got down here...!
217 // S3 = CRC Error. Not gonna happen on a disc image!
218 // S2 = Track 0
219 ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
220 // S1 = Index Pulse. TODO -- need periodics to emulate this
221 // S0 = Busy. We just exec'd the command, thus we're not busy.
222 // TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
224 // Set IRQ only if IRQL has been cleared (no pending IRQs)
225 ctx->irqe = !ctx->irql;
226 ctx->irql = true;
227 return;
228 }
230 // That's the Type 1 (seek) commands sorted. Now for the others.
232 // If drive isn't ready, then set status B7 and exit
233 if (ctx->disc_image == NULL) {
234 ctx->status = 0x80;
235 return;
236 }
238 // If this is a Write command, check write protect status too
239 // TODO!
240 if (false) {
241 // Write protected disc...
242 if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
243 // Set Write Protect bit and bail.
244 ctx->status = 0x40;
246 // Set IRQ only if IRQL has been cleared (no pending IRQs)
247 ctx->irqe = !ctx->irql;
248 ctx->irql = true;
250 return;
251 }
252 }
254 // Disc is ready to go. Parse the command word.
255 switch (cmd) {
256 case CMD_READ_ADDRESS:
257 // Read Address
259 // reset data pointers
260 ctx->data_pos = ctx->data_len = 0;
262 // load data buffer
263 ctx->data[ctx->data_len++] = ctx->track;
264 ctx->data[ctx->data_len++] = ctx->head;
265 ctx->data[ctx->data_len++] = ctx->sector;
266 switch (ctx->geom_secsz) {
267 case 128: ctx->data[ctx->data_len++] = 0; break;
268 case 256: ctx->data[ctx->data_len++] = 1; break;
269 case 512: ctx->data[ctx->data_len++] = 2; break;
270 case 1024: ctx->data[ctx->data_len++] = 3; break;
271 default: ctx->data[ctx->data_len++] = 0xFF; break; // TODO: deal with invalid values better
272 }
273 ctx->data[ctx->data_len++] = 0; // TODO: IDAM CRC!
274 ctx->data[ctx->data_len++] = 0;
276 // B6, B5 = 0
277 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
278 // B3 = CRC Error. Not possible.
279 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
280 // B1 = DRQ. Data request.
281 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
282 break;
284 case CMD_READ_SECTOR:
285 case CMD_READ_SECTOR_MULTI:
286 // Read Sector or Read Sector Multiple
287 // TODO: multiple is not implemented!
288 if (cmd == CMD_READ_SECTOR_MULTI) printf("WD279X: NOTIMP: read-multi\n");
290 // reset data pointers
291 ctx->data_pos = ctx->data_len = 0;
293 // Calculate the LBA address of the required sector
294 lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ctx->sector - 1) * ctx->geom_secsz;
296 // Read the sector from the file
297 fseek(ctx->disc_image, lba, SEEK_SET);
298 ctx->data_len = fread(ctx->data, 1, ctx->geom_secsz, ctx->disc_image);
299 // TODO: if datalen < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
300 // TODO: if we're asked to do a Read Multi, malloc for an entire track, then just read a full track...
302 // B6 = 0
303 // B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
304 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
305 // B3 = CRC Error. Not possible.
306 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
307 // B1 = DRQ. Data request.
308 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
309 break;
311 case CMD_READ_TRACK:
312 // Read Track
313 // B6, B5, B4, B3 = 0
314 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
315 // B1 = DRQ. Data request.
316 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
317 break;
319 case CMD_WRITE_SECTOR:
320 case CMD_WRITE_SECTOR_MULTI:
321 // Write Sector or Write Sector Multiple
323 // reset data pointers
324 ctx->data_pos = ctx->data_len = 0;
326 // TODO: set "write pending" flag, and write LBA, and go from there.
328 // B6 = Write Protect. FIXME -- emulate this!
329 // B5 = 0
330 // B4 = Record Not Found. We're not going to see this... FIXME-not emulated
331 // B3 = CRC Error. Not possible.
332 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
333 // B1 = DRQ. Data request.
334 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
335 break;
337 case CMD_FORMAT_TRACK:
338 // Write Track (aka Format Track)
339 // B6 = Write Protect. FIXME -- emulate this!
340 // B5, B4, B3 = 0
341 // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
342 // B1 = DRQ. Data request.
343 ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
344 break;
346 case CMD_FORCE_INTERRUPT:
347 // Force Interrupt...
348 // TODO: terminate current R/W op
349 // TODO: variants for this command
350 break;
351 }
352 break;
354 case WD279X_REG_TRACK: // Track register
355 ctx->track = val;
356 break;
358 case WD279X_REG_SECTOR: // Sector register
359 ctx->sector = val;
360 break;
362 case WD279X_REG_DATA: // Data register
363 // Save the value written into the data register
364 ctx->data_reg = val;
366 // If we're processing a write command, and there's space in the
367 // buffer, allow the write.
368 if (ctx->data_pos < ctx->data_len) {
369 // store data byte and increment pointer
370 ctx->data[ctx->data_pos++] = val;
371 }
372 break;
373 }
374 }