1 /*
2  * parallel-trap.c
3  *
4  * Written by
5  *  Andreas Boose <viceteam@t-online.de>
6  *  Andre Fachat <a.fachat@physik.tu-chemnitz.de>
7  *
8  * This file is part of VICE, the Versatile Commodore Emulator.
9  * See README for copyright notice.
10  *
11  *  This program is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 2 of the License, or
14  *  (at your option) any later version.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
24  *  02111-1307  USA.
25  *
26  */
27 
28 #include "vice.h"
29 
30 #include <stdio.h>
31 
32 #include "attach.h"
33 #include "drive.h"
34 #include "drivetypes.h"
35 #include "log.h"
36 #include "parallel-trap.h"
37 #include "parallel.h"
38 #include "serial.h"
39 #include "types.h"
40 
41 
42 #define SERIAL_NAMELENGTH 255
43 
44 
45 /* On which channel did listen happen to?  */
46 static uint8_t TrapDevice;
47 static uint8_t TrapSecondary;
48 
49 /* Function to call when EOF happens in `serialreceivebyte()'.  */
50 static void (*eof_callback_func)(void);
51 
52 /* Function to call when the `serialattention()' trap is called.  */
53 static void (*attention_callback_func)(void);
54 
55 /* Logging goes here.  */
56 static log_t parallel_log = LOG_DEFAULT;
57 
58 static uint8_t SerialBuffer[SERIAL_NAMELENGTH + 1];
59 static int SerialPtr;
60 
61 /*
62    On a real system an opened channel is affected only after having
63    received and parsed the complete next open command.
64    This patch tries to emulate this behavior and allows to keep the
65    status channel continuously open while opening and closing other
66    files on the same device.
67 
68    FIXME: test for regressions and either revert to, or remove old code
69 */
70 #define DELAYEDCLOSE
71 
parallelcommand(void)72 static int parallelcommand(void)
73 {
74     serial_t *p;
75     uint8_t b;
76     int channel;
77     int i, st = 0;
78     void *vdrive;
79     unsigned int dnr;
80 
81     dnr = TrapDevice & 0x0f;
82     if (dnr >= DRIVE_UNIT_MIN &&
83 	dnr < DRIVE_UNIT_MIN+NUM_DISK_UNITS &&
84 	diskunit_context[dnr - DRIVE_UNIT_MIN]->enable) {
85             return PAR_STATUS_DEVICE_NOT_PRESENT +    /* device not present */
86                    PAR_STATUS_TIME_OUT_ON_WRITE +
87                    PAR_STATUS_TIME_OUT_ON_READ;
88     }
89 
90     /* which device ? */
91     p = serial_device_get(TrapDevice & 0x0f);
92     /* TODO: drive 1? */
93     vdrive = (void *)file_system_get_vdrive(TrapDevice & 0x0f, 0);
94     channel = TrapSecondary & 0x0f;
95 
96     /* if command on a channel, reset output buffer... */
97     if ((TrapSecondary & 0xf0) != 0x60) {
98         p->nextok[channel] = 0;
99         p->lastok[channel] = 0;
100     }
101     switch (TrapSecondary & 0xf0) {
102         case 0x60: /* Secondary address */
103             /* Open Channel */
104             if (p->isopen[channel] == ISOPEN_CLOSED) {
105                 p->isopen[channel] = ISOPEN_OPEN;
106                 st = (*(p->openf))(vdrive, NULL, 0, channel, NULL);
107                 for (i = 0; i < SerialPtr; i++) {
108                     (*(p->putf))(vdrive, SerialBuffer[i], channel);
109                 }
110                 SerialPtr = 0;
111             }
112             if (p->flushf) {
113                 (*(p->flushf))(vdrive, channel);
114             }
115 
116             if ((!st) && ((TrapDevice & 0xf0) == 0x40)) { /* isTalking */
117                 /* any error, except eof */
118                 st = parallel_trap_receivebyte(&b, 1) & 0xbf;
119             }
120             break;
121         case 0xE0:
122             /* Close File */
123             p->isopen[channel] = ISOPEN_CLOSED;
124             st = (*(p->closef))(vdrive, channel);
125             break;
126         case 0xF0:
127             /* Open File */
128             if (p->isopen[channel] != ISOPEN_CLOSED) {
129 #ifndef DELAYEDCLOSE
130                 if (p->isopen[channel] == ISOPEN_OPEN) {
131                     log_warning(parallel_log, "Bogus close?");
132                     (*(p->closef))(vdrive, channel);
133                 }
134                 p->isopen[channel] = ISOPEN_OPEN;
135                 SerialBuffer[SerialPtr] = 0;
136                 st = (*(p->openf))(vdrive, SerialBuffer, SerialPtr, channel, NULL);
137                 SerialPtr = 0;
138 
139                 if (st) {
140                     p->isopen[channel] = ISOPEN_CLOSED;
141                     (*(p->closef))(vdrive, channel);
142                     log_error(parallel_log, "Cannot open file. Status $%02x.", st);
143                 }
144 #else /* DELAYEDCLOSE */
145                 if (SerialPtr != 0 || channel == 0x0f) {
146                     (*(p->closef))(vdrive, channel);
147                     p->isopen[channel] = ISOPEN_OPEN;
148 
149                     SerialBuffer[SerialPtr] = 0;
150                     st = (*(p->openf))(vdrive, SerialBuffer, SerialPtr, channel, NULL);
151                     SerialPtr = 0;
152 
153                     if (st) {
154                         p->isopen[channel] = ISOPEN_CLOSED;
155                         (*(p->closef))(vdrive, channel);
156                         log_error(parallel_log,
157                                 "Cannot open file. Status $%02x.",
158                                 (unsigned int)st);
159                     }
160                 }
161 #endif /* DELAYEDCLOSE */
162             }
163             if (p->flushf) {
164                 (*(p->flushf))(vdrive, channel);
165             }
166             break;
167         default:
168             log_error(parallel_log, "Unknown command %02X.", TrapSecondary & 0xff);
169     }
170     return st;
171 }
172 
parallel_trap_attention(int b)173 int parallel_trap_attention(int b)
174 {
175     int st = 0;
176     serial_t *p;
177     void *vdrive;
178 
179 #ifdef DEBUG
180     if (debug.ieee) {
181         log_message(parallel_log, "ParallelAttention(%02x).", (unsigned int)b);
182     }
183 #endif
184 
185     if (b == 0x3f					/* unlisten */
186         && (((TrapSecondary & 0xf0) == 0xf0)		/* open filename */
187             || ((TrapSecondary & 0x0f) == 0x0f))) {	/* secaddr #15 */
188         st = parallelcommand();
189     } else {
190         switch (b & 0xf0) {
191             case 0x20:		/* Listen + device */
192             case 0x40:		/* Talk + device */
193 		/* If this device is already emulated with TDE, don't
194 		 * try to react to it here. */
195 		{
196 		    int dnr = b & 0x0f;
197 		    if (dnr >= DRIVE_UNIT_MIN &&
198 			    dnr < DRIVE_UNIT_MIN+NUM_DISK_UNITS &&
199 			    diskunit_context[dnr - DRIVE_UNIT_MIN]->enable) {
200 		    } else {
201 			TrapDevice = b;
202 		    }
203 		}
204                 break;
205 
206             case 0x60:          /* Secondary address */
207             case 0xe0:          /* Close a file */
208 		if (TrapDevice != 0) {
209 		    TrapSecondary = b;
210 		    st |= parallelcommand();
211 		}
212                 break;
213 
214             case 0xf0:          /* Open File; needs the filename first */
215 		if (TrapDevice != 0) {
216 		    TrapSecondary = b;
217 		    p = serial_device_get(TrapDevice & 0x0f);
218 #ifndef DELAYEDCLOSE
219 		    /* TODO drive 1? */
220 		    vdrive = (void *)file_system_get_vdrive(TrapDevice & 0x0f, 0);
221 		    if (p->isopen[b & 0x0f] == ISOPEN_OPEN) {
222 			(*(p->closef))(vdrive, b & 0x0f);
223 		    }
224 #endif
225 		    p->isopen[b & 0x0f] = ISOPEN_AWAITING_NAME;
226 		}
227                 break;
228         }
229     }
230 
231     if (TrapDevice != 0) {
232 	p = serial_device_get(TrapDevice & 0x0f);
233 	if (!(p->inuse)) {
234 	    st |= PAR_STATUS_DEVICE_NOT_PRESENT;
235 	}
236 
237 	/* If it was a listen or talk or secondary addr or unlisten */
238 	if (((b & 0xf0) == 0x20) || ((b & 0xf0) == 0x40) || ((b & 0xf0) == 0x60)
239 	    || (b == 0x3f)) {
240 	    if (p->listenf) {
241 		/* send talk/listen/unlisten to emulated devices for
242 		   flushing of REL file write buffer. */
243 		if ((TrapDevice & 0x0f) >= DRIVE_UNIT_MIN) {
244 		    /* TODO drive 1? */
245 		    vdrive = (void *)file_system_get_vdrive(TrapDevice & 0x0f, 0);
246 		    (*(p->listenf))(vdrive, TrapSecondary & 0x0f);
247 		}
248 	    }
249 	}
250     }
251 
252     if ((b == 0x3f) || (b == 0x5f)) {
253         TrapDevice = 0;
254         TrapSecondary = 0;
255     }
256 
257     st |= TrapDevice << 8;
258 
259     if (attention_callback_func) {
260         attention_callback_func();
261     }
262 
263     return st;
264 }
265 
parallel_trap_sendbyte(uint8_t data)266 int parallel_trap_sendbyte(uint8_t data)
267 {
268     int st = 0;
269     serial_t *p;
270     void *vdrive;
271     unsigned int dnr;
272 
273     dnr = TrapDevice & 0x0f;
274     if (dnr >= DRIVE_UNIT_MIN &&
275 	dnr < DRIVE_UNIT_MIN+NUM_DISK_UNITS &&
276 	diskunit_context[dnr - DRIVE_UNIT_MIN]->enable) {
277             return PAR_STATUS_DEVICE_NOT_PRESENT +    /* device not present */
278                    PAR_STATUS_TIME_OUT_ON_WRITE +
279                    PAR_STATUS_TIME_OUT_ON_READ;
280     }
281 
282     p = serial_device_get(TrapDevice & 0x0f);
283     /* TODO drive 1 */
284     vdrive = (void *)file_system_get_vdrive(TrapDevice & 0x0f, 0);
285 
286     if (p->inuse) {
287         if (p->isopen[TrapSecondary & 0x0f] == ISOPEN_AWAITING_NAME) {
288 #ifdef DEBUG
289             if (debug.ieee) {
290                 log_message(parallel_log,
291                             "SerialSendByte[%2d] = %02x.", SerialPtr, data);
292             }
293 #endif
294             /* Store name here */
295             if (SerialPtr < SERIAL_NAMELENGTH) {
296                 SerialBuffer[SerialPtr++] = data;
297             }
298         } else {
299             /* Send to device */
300             st = (*(p->putf))(vdrive, data, TrapSecondary & 0x0f);
301         }
302     } else {                    /* Not present */
303         st = PAR_STATUS_DEVICE_NOT_PRESENT +
304              PAR_STATUS_TIME_OUT_ON_WRITE +
305              PAR_STATUS_TIME_OUT_ON_READ;
306     }
307 
308     return st + (TrapDevice << 8);
309 }
310 
parallel_trap_receivebyte(uint8_t * data,int fake)311 int parallel_trap_receivebyte(uint8_t *data, int fake)
312 {
313     int st = 0;
314     int secadr = TrapSecondary & 0x0f;
315     serial_t *p;
316     void *vdrive;
317     unsigned int dnr;
318 
319     dnr = TrapDevice & 0x0f;
320     if (dnr >= DRIVE_UNIT_MIN &&
321 	dnr < DRIVE_UNIT_MIN+NUM_DISK_UNITS &&
322 	diskunit_context[dnr - DRIVE_UNIT_MIN]->enable) {
323             return 0x83;    /* device not present */
324     }
325 
326     p = serial_device_get(TrapDevice & 0x0f);
327     /* TODO: drive 1 */
328     vdrive = (void *)file_system_get_vdrive(TrapDevice & 0x0f, 0);
329 
330     /* first fill up buffers */
331 #if 0
332     if (!p->lastok[secadr]) {
333         p->lastok[secadr] = p->nextok[secadr];
334         p->lastbyte[secadr] = p->nextbyte[secadr];
335         p->lastst[secadr] = p->nextst[secadr];
336         p->nextok[secadr] = 0;
337 #endif
338     if (!p->lastok[secadr]) {
339         p->lastst[secadr] =
340             (*(p->getf))(vdrive, &(p->lastbyte[secadr]), secadr);
341         p->lastok[secadr] = 1;
342     }
343 #if 0
344 }
345 if ((!p->nextok[secadr]) && (!p->lastst[secadr])) {
346     p->nextst[secadr] =
347         (*(p->getf))(vdrive, &(p->nextbyte[secadr]), secadr);
348     p->nextok[secadr] = 1;
349 }
350 #endif
351     *data = p->lastbyte[secadr];
352     if (!fake) {
353         p->lastok[secadr] = 0;
354     }
355 #if 0
356     st = p->nextok[secadr] ? p->nextst[secadr] :
357          (p->lastok[secadr] ? p->lastst[secadr] : 2);
358 #endif
359     st = p->lastst[secadr]; /* added */
360     st += TrapDevice << 8;
361 
362 #ifdef DEBUG
363     if (debug.ieee) {
364         log_message(parallel_log,
365                     "receive: sa=%02x lastb = %02x (data=%02x), "
366                     "ok=%s, st=%04x, nextb = %02x, "
367                     "ok=%s, st=%04x.",
368                     (unsigned int)secadr,
369                     p->lastbyte[secadr],
370                     (int)*data,
371                     p->lastok[secadr] ? "ok" : "no",
372                     (unsigned int)(p->lastst[secadr]),
373                     p->nextbyte[secadr],
374                     p->nextok[secadr] ? "ok" : "no",
375                     (unsigned int)(p->nextst[secadr]));
376     }
377 #endif
378 #if 0
379     if ((!fake) && p->nextok[secadr] && p->nextst[secadr]) {
380         p->nextok[secadr] = 0;
381     }
382 #endif
383     if ((st & 0x40) && eof_callback_func != NULL) {
384         eof_callback_func();
385     }
386     return st;
387 }
388 
389 /* Specify a function to call when EOF happens in `serialreceivebyte()'.  */
parallel_trap_eof_callback_set(void (* func)(void))390 void parallel_trap_eof_callback_set(void (*func)(void))
391 {
392     eof_callback_func = func;
393 }
394 
395 /* Specify a function to call when the `serialattention()' trap is called.  */
parallel_trap_attention_callback_set(void (* func)(void))396 void parallel_trap_attention_callback_set(void (*func)(void))
397 {
398     attention_callback_func = func;
399 }
400