- Improve error messages.
[wine] / msdos / ioports.c
1 /*
2  * Emulation of processor ioports.
3  *
4  * Copyright 1995 Morten Welinder
5  * Copyright 1998 Andreas Mohr, Ove Kaaven
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 /* Known problems:
23    - only a few ports are emulated.
24    - real-time clock in "cmos" is bogus.  A nifty alarm() setup could
25      fix that, I guess.
26 */
27
28 #include "config.h"
29
30 #include <ctype.h>
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <time.h>
35 #include <unistd.h>
36 #include "windef.h"
37 #include "callback.h"
38 #include "options.h"
39 #include "miscemu.h"
40 #include "wine/debug.h"
41
42 WINE_DEFAULT_DEBUG_CHANNEL(int);
43
44 static struct {
45     WORD        countmax;
46     BOOL16      byte_toggle; /* if TRUE, then hi byte has already been written */
47     WORD        latch;
48     BOOL16      latched;
49     BYTE        ctrlbyte_ch;
50     WORD        oldval;
51 } tmr_8253[3] = {
52     {0xFFFF,    FALSE,  0,      FALSE,  0x36,   0},
53     {0x0012,    FALSE,  0,      FALSE,  0x74,   0},
54     {0x0001,    FALSE,  0,      FALSE,  0xB6,   0},
55 };
56
57 static int dummy_ctr = 0;
58
59 static BYTE parport_8255[4] = {0x4f, 0x20, 0xff, 0xff};
60
61 static BYTE cmosaddress;
62
63 static BYTE cmosimage[64] =
64 {
65   0x27, 0x34, 0x31, 0x47, 0x16, 0x15, 0x00, 0x01,
66   0x04, 0x94, 0x26, 0x02, 0x50, 0x80, 0x00, 0x00,
67   0x40, 0xb1, 0x00, 0x9c, 0x01, 0x80, 0x02, 0x00,
68   0x1c, 0x00, 0x00, 0xad, 0x02, 0x10, 0x00, 0x00,
69   0x08, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00,
70   0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x03, 0x19,
71   0x00, 0x1c, 0x19, 0x81, 0x00, 0x0e, 0x00, 0x80,
72   0x1b, 0x7b, 0x21, 0x00, 0x00, 0x00, 0x05, 0x5f
73 };
74
75 #if defined(linux) && defined(__i386__)
76 # define DIRECT_IO_ACCESS
77 #else
78 # undef DIRECT_IO_ACCESS
79 # undef PP_IO_ACCESS
80 #endif  /* linux && __i386__ */
81
82 #ifdef DIRECT_IO_ACCESS
83
84 extern int iopl(int level);
85 static char do_direct_port_access = -1;
86 #ifdef HAVE_PPDEV
87 static char do_pp_port_access = -1; /* -1: uninitialized, 1: not available
88                                        0: available);*/
89 #endif
90 static char port_permissions[0x10000];
91
92 #define IO_READ  1
93 #define IO_WRITE 2
94
95 static void IO_FixCMOSCheckSum(void)
96 {
97         WORD sum = 0;
98         int i;
99
100         for (i=0x10; i < 0x2d; i++)
101                 sum += cmosimage[i];
102         cmosimage[0x2e] = sum >> 8; /* yes, this IS hi byte !! */
103         cmosimage[0x2f] = sum & 0xff;
104         TRACE("calculated hi %02x, lo %02x\n", cmosimage[0x2e], cmosimage[0x2f]);
105 }
106
107 #endif  /* DIRECT_IO_ACCESS */
108
109 static void set_timer_maxval(unsigned timer, unsigned maxval)
110 {
111     switch (timer) {
112         case 0: /* System timer counter divisor */
113             if (Dosvm.SetTimer) Dosvm.SetTimer(maxval);
114             break;
115         case 1: /* RAM refresh */
116             FIXME("RAM refresh counter handling not implemented !\n");
117             break;
118         case 2: /* cassette & speaker */
119             /* speaker on ? */
120             if (((BYTE)parport_8255[1] & 3) == 3)
121             {
122                 TRACE("Beep (freq: %d) !\n", 1193180 / maxval );
123                 Beep(1193180 / maxval, 20);
124             }
125             break;
126     }
127 }
128
129 /**********************************************************************
130  *          IO_port_init
131  */
132
133 /* set_IO_permissions(int val1, int val)
134  * Helper function for IO_port_init
135  */
136 #ifdef DIRECT_IO_ACCESS
137 static void set_IO_permissions(int val1, int val, char rw)
138 {
139         int j;
140         if (val1 != -1) {
141                 if (val == -1) val = 0x3ff;             
142                 for (j = val1; j <= val; j++)
143                         port_permissions[j] |= rw;              
144
145                 do_direct_port_access = 1;
146
147                 val1 = -1;
148         } else if (val != -1) {         
149                 do_direct_port_access = 1;
150
151                 port_permissions[val] |= rw;
152         }
153
154 }
155
156 /* do_IO_port_init_read_or_write(char* temp, char rw)
157  * Helper function for IO_port_init
158  */
159
160 static void do_IO_port_init_read_or_write(char* temp, char rw)
161 {
162         int val, val1, i, len;
163         if (!strcasecmp(temp, "all")) {
164                 MESSAGE("Warning!!! Granting FULL IO port access to"
165                         " windoze programs!\nWarning!!! "
166                         "*** THIS IS NOT AT ALL "
167                         "RECOMMENDED!!! ***\n");
168                 for (i=0; i < sizeof(port_permissions); i++)
169                         port_permissions[i] |= rw;
170
171         } else if (!(!strcmp(temp, "*") || *temp == '\0')) {
172                 len = strlen(temp);
173                 val = -1;
174                 val1 = -1;              
175                 for (i = 0; i < len; i++) {
176                         switch (temp[i]) {
177                         case '0':
178                                 if (temp[i+1] == 'x' || temp[i+1] == 'X') {
179                                         sscanf(temp+i, "%x", &val);
180                                         i += 2;
181                                 } else {
182                                         sscanf(temp+i, "%d", &val);
183                                 }
184                                 while (isxdigit(temp[i]))
185                                         i++;
186                                 i--;
187                                 break;
188                         case ',':
189                         case ' ':
190                         case '\t':
191                                 set_IO_permissions(val1, val, rw);
192                                 val1 = -1; val = -1;
193                                 break;
194                         case '-':
195                                 val1 = val;
196                                 if (val1 == -1) val1 = 0;
197                                 break;
198                         default:
199                                 if (temp[i] >= '0' && temp[i] <= '9') {
200                                         sscanf(temp+i, "%d", &val);
201                                         while (isdigit(temp[i]))
202                                                 i++;
203                                 }
204                         }
205                 }
206                 set_IO_permissions(val1, val, rw);              
207         }
208 }
209
210 static inline BYTE inb( WORD port )
211 {
212     BYTE b;
213     __asm__ __volatile__( "inb %w1,%0" : "=a" (b) : "d" (port) );
214     return b;
215 }
216
217 static inline WORD inw( WORD port )
218 {
219     WORD w;
220     __asm__ __volatile__( "inw %w1,%0" : "=a" (w) : "d" (port) );
221     return w;
222 }
223
224 static inline DWORD inl( WORD port )
225 {
226     DWORD dw;
227     __asm__ __volatile__( "inl %w1,%0" : "=a" (dw) : "d" (port) );
228     return dw;
229 }
230
231 static inline void outb( BYTE value, WORD port )
232 {
233     __asm__ __volatile__( "outb %b0,%w1" : : "a" (value), "d" (port) );
234 }
235
236 static inline void outw( WORD value, WORD port )
237 {
238     __asm__ __volatile__( "outw %w0,%w1" : : "a" (value), "d" (port) );
239 }
240
241 static inline void outl( DWORD value, WORD port )
242 {
243     __asm__ __volatile__( "outl %0,%w1" : : "a" (value), "d" (port) );
244 }
245
246 static void IO_port_init(void)
247 {
248         char temp[1024];
249
250         do_direct_port_access = 0;
251         /* Can we do that? */
252         if (!iopl(3)) {
253                 iopl(0);
254
255                 PROFILE_GetWineIniString( "ports", "read", "*",
256                                          temp, sizeof(temp) );
257                 do_IO_port_init_read_or_write(temp, IO_READ);
258                 PROFILE_GetWineIniString( "ports", "write", "*",
259                                          temp, sizeof(temp) );
260                 do_IO_port_init_read_or_write(temp, IO_WRITE);
261         }
262     IO_FixCMOSCheckSum();
263 }
264
265 #endif  /* DIRECT_IO_ACCESS */
266
267 /**********************************************************************
268  *          IO_inport
269  *
270  * Note: The size argument has to be handled correctly _externally_ 
271  * (as we always return a DWORD)
272  */
273 DWORD IO_inport( int port, int size )
274 {
275     DWORD res = 0;
276
277     TRACE("%d-byte value from port 0x%02x\n", size, port );
278
279 #ifdef HAVE_PPDEV
280     if (do_pp_port_access == -1) 
281       do_pp_port_access =IO_pp_init();
282     if ((do_pp_port_access == 0 ) && (size == 1))
283       if (!IO_pp_inp(port,&res))
284          return res;
285 #endif
286 #ifdef DIRECT_IO_ACCESS
287     if (do_direct_port_access == -1) IO_port_init();
288     if ((do_direct_port_access)
289         /* Make sure we have access to the port */
290         && (port_permissions[port] & IO_READ))
291     {
292         iopl(3);
293         switch(size)
294         {
295         case 1: res = inb( port ); break;
296         case 2: res = inw( port ); break;
297         case 4: res = inl( port ); break;
298         default:
299             ERR("invalid data size %d\n", size);
300         }
301         iopl(0);
302         return res;
303     }
304 #endif
305
306     /* first give the DOS VM a chance to handle it */
307     if (Dosvm.inport && Dosvm.inport( port, size, &res )) return res;
308
309     switch (port)
310     {
311     case 0x40:
312     case 0x41:
313     case 0x42:
314     {
315         BYTE chan = port & 3;
316         WORD tempval = 0;
317         if (tmr_8253[chan].latched)
318             tempval = tmr_8253[chan].latch;
319         else
320         {
321             dummy_ctr -= 1 + (int)(10.0 * rand() / (RAND_MAX + 1.0));
322             if (chan == 0) /* System timer counter divisor */
323             {
324                 /* FIXME: Dosvm.GetTimer() returns quite rigid values */
325                 if (Dosvm.GetTimer)
326                   tempval = dummy_ctr + (WORD)Dosvm.GetTimer();
327                 else
328                   tempval = dummy_ctr;
329             }
330             else
331             {
332                 /* FIXME: intelligent hardware timer emulation needed */
333                 tempval = dummy_ctr;
334             }
335         }
336
337         switch ((tmr_8253[chan].ctrlbyte_ch & 0x30) >> 4)
338         {
339         case 0:
340             res = 0; /* shouldn't happen? */
341             break;
342         case 1: /* read lo byte */
343             res = (BYTE)tempval;
344             tmr_8253[chan].latched = FALSE;
345             break;
346         case 3: /* read lo byte, then hi byte */
347             tmr_8253[chan].byte_toggle ^= TRUE; /* toggle */
348             if (tmr_8253[chan].byte_toggle)
349             {
350                 res = (BYTE)tempval;
351                 break;
352             }
353             /* else [fall through if read hi byte !] */
354         case 2: /* read hi byte */
355             res = (BYTE)(tempval >> 8);
356             tmr_8253[chan].latched = FALSE;
357             break;
358         }
359     }
360     break;
361     case 0x60:
362 #if 0 /* what's this port got to do with parport ? */
363         res = (DWORD)parport_8255[0];
364 #endif
365         break;
366     case 0x61:
367         res = (DWORD)parport_8255[1];
368         break;
369     case 0x62:
370         res = (DWORD)parport_8255[2];
371         break;
372     case 0x70:
373         res = (DWORD)cmosaddress;
374         break;
375     case 0x71:
376         res = (DWORD)cmosimage[cmosaddress & 0x3f];
377         break;
378     case 0x200:
379     case 0x201:
380         res = 0xffffffff; /* no joystick */
381         break;
382     default:
383         WARN("Direct I/O read attempted from port %x\n", port);
384         res = 0xffffffff;
385         break;
386     }
387     TRACE("  returning ( 0x%lx )\n", res );
388     return res;
389 }
390
391
392 /**********************************************************************
393  *          IO_outport
394  */
395 void IO_outport( int port, int size, DWORD value )
396 {
397     TRACE("IO: 0x%lx (%d-byte value) to port 0x%02x\n",
398                  value, size, port );
399
400 #ifdef HAVE_PPDEV
401     if (do_pp_port_access == -1) 
402       do_pp_port_access = IO_pp_init();
403     if ((do_pp_port_access == 0) && (size == 1))
404       if (!IO_pp_outp(port,&value))
405          return;
406 #endif
407 #ifdef DIRECT_IO_ACCESS
408
409     if (do_direct_port_access == -1) IO_port_init();
410     if ((do_direct_port_access)
411         /* Make sure we have access to the port */
412         && (port_permissions[port] & IO_WRITE))
413     {
414         iopl(3);
415         switch(size)
416         {
417         case 1: outb( LOBYTE(value), port ); break;
418         case 2: outw( LOWORD(value), port ); break;
419         case 4: outl( value, port ); break;
420         default:
421             WARN("Invalid data size %d\n", size);
422         }
423         iopl(0);
424         return;
425     }
426 #endif
427
428     /* first give the DOS VM a chance to handle it */
429     if (Dosvm.outport && Dosvm.outport( port, size, value )) return;
430
431     switch (port)
432     {
433     case 0x40:
434     case 0x41:
435     case 0x42:
436     {
437         BYTE chan = port & 3;
438
439         /* we need to get the oldval before any lo/hi byte change has been made */
440         if (((tmr_8253[chan].ctrlbyte_ch & 0x30) != 0x30) ||
441             !tmr_8253[chan].byte_toggle)
442             tmr_8253[chan].oldval = tmr_8253[chan].countmax;
443         switch ((tmr_8253[chan].ctrlbyte_ch & 0x30) >> 4)
444         {
445         case 0:
446             break; /* shouldn't happen? */
447         case 1: /* write lo byte */
448             tmr_8253[chan].countmax =
449                 (tmr_8253[chan].countmax & 0xff00) | (BYTE)value;
450             break;
451         case 3: /* write lo byte, then hi byte */
452             tmr_8253[chan].byte_toggle ^= TRUE; /* toggle */
453             if (tmr_8253[chan].byte_toggle)
454             {
455                 tmr_8253[chan].countmax =
456                     (tmr_8253[chan].countmax & 0xff00) | (BYTE)value;
457                 break;
458             }
459             /* else [fall through if write hi byte !] */
460         case 2: /* write hi byte */
461             tmr_8253[chan].countmax =
462                 (tmr_8253[chan].countmax & 0x00ff) | ((BYTE)value << 8);
463             break;
464         }
465         /* if programming is finished and value has changed
466            then update to new value */
467         if ((((tmr_8253[chan].ctrlbyte_ch & 0x30) != 0x30) ||
468              !tmr_8253[chan].byte_toggle) &&
469             (tmr_8253[chan].countmax != tmr_8253[chan].oldval))
470             set_timer_maxval(chan, tmr_8253[chan].countmax);
471     }
472     break;          
473     case 0x43:
474     {
475         BYTE chan = ((BYTE)value & 0xc0) >> 6;
476         /* ctrl byte for specific timer channel */
477         if (chan == 3)
478         {
479             FIXME("8254 timer readback not implemented yet\n");
480             break;
481         }
482         switch (((BYTE)value & 0x30) >> 4)
483         {
484         case 0: /* latch timer */
485             tmr_8253[chan].latched = TRUE;
486             dummy_ctr -= 1 + (int)(10.0 * rand() / (RAND_MAX + 1.0));
487             if (chan == 0) /* System timer divisor */
488                 if (Dosvm.GetTimer)
489                   tmr_8253[chan].latch = dummy_ctr + (WORD)Dosvm.GetTimer();
490                 else
491                   tmr_8253[chan].latch = dummy_ctr;
492             else
493             {
494                 /* FIXME: intelligent hardware timer emulation needed */
495                 tmr_8253[chan].latch = dummy_ctr;
496             }
497             break;
498         case 3: /* write lo byte, then hi byte */
499             tmr_8253[chan].byte_toggle = FALSE; /* init */
500             /* fall through */
501         case 1: /* write lo byte only */
502         case 2: /* write hi byte only */
503             tmr_8253[chan].ctrlbyte_ch = (BYTE)value;
504             break;
505         }
506     }
507     break;
508     case 0x61:
509         parport_8255[1] = (BYTE)value;
510         if ((((BYTE)parport_8255[1] & 3) == 3) && (tmr_8253[2].countmax != 1))
511         {
512             TRACE("Beep (freq: %d) !\n", 1193180 / tmr_8253[2].countmax);
513             Beep(1193180 / tmr_8253[2].countmax, 20);
514         }
515         break;
516     case 0x70:
517         cmosaddress = (BYTE)value & 0x7f;
518         break;
519     case 0x71:
520         cmosimage[cmosaddress & 0x3f] = (BYTE)value;
521         break;
522     default:
523         WARN("Direct I/O write attempted to port %x\n", port );
524         break;
525     }
526 }