]> 4ch.mooo.com Git - 16.git/blob - src/lib/doslib/hw/8250/8250.c
added a bunch of things~ and midi stuff~
[16.git] / src / lib / doslib / hw / 8250 / 8250.c
1 /* 8250.c
2  *
3  * 8250/16450/16550/16750 serial port UART library.
4  * (C) 2009-2012 Jonathan Campbell.
5  * Hackipedia DOS library.
6  *
7  * This code is licensed under the LGPL.
8  * <insert LGPL legal text here>
9  *
10  * Compiles for intended target environments:
11  *   - MS-DOS [pure DOS mode, or Windows or OS/2 DOS Box]
12  *
13  * On PCs that have them, the UART is usually a 8250/16450/16550 or compatible chip,
14  * or it is emulated on modern hardware to act like one. At a basic programming level
15  * the UART is very simple to talk to.
16  *
17  * The best way to play with this code, is to obtain a null-modem cable, connect two
18  * PCs together, and run this program on either end. On most PC hardware, this code
19  * should be able to run at a full baud rate sending and receiving without issues.
20  *
21  * For newer (post 486) systems with PnP serial ports and PnP aware programs, this
22  * library offers a PnP aware additional library that can be linked to. For some
23  * late 1990's hardware, the PnP awareness is required to correctly identify the
24  * IRQ associated with the device, such as on older Toshiba laptops that emulate
25  * a serial port using the IR infared device on the back. */
26  
27 #include <stdio.h>
28 #include <conio.h> /* this is where Open Watcom hides the outp() etc. functions */
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <fcntl.h>
33 #include <dos.h>
34
35 #include <hw/cpu/cpu.h>
36 #include <hw/dos/dos.h>
37 #include <hw/8250/8250.h>
38 #include <hw/dos/doswin.h>
39
40 const char *type_8250_strs[TYPE_8250_MAX] = {
41         "8250",
42         "16450",
43         "16550",
44         "16550A",
45         "16750"
46 };
47
48 const uint16_t          standard_8250_ports[STANDARD_8250_PORT_COUNT] = {0x3F8,0x2F8,0x3E8,0x2E8};
49 uint16_t                base_8250_port[MAX_8250_PORTS];
50 struct info_8250        info_8250_port[MAX_8250_PORTS];
51 unsigned int            base_8250_ports;
52 unsigned char           bios_8250_ports;
53 char                    use_8250_int=0;
54 char                    inited_8250=0;
55
56 int already_got_8250_port(uint16_t port) {
57         unsigned int i;
58
59         for (i=0;i < (unsigned int)base_8250_ports;i++) {
60                 if (base_8250_port[i] == port)
61                         return 1;
62         }
63
64         return 0;
65 }
66
67 uint16_t get_8250_bios_port(unsigned int index) {
68         if (index >= (unsigned int)bios_8250_ports)
69                 return 0;
70
71 #if TARGET_MSDOS == 32
72         return *((uint16_t*)(0x400 + (index*2)));
73 #else
74         return *((uint16_t far*)MK_FP(0x40,index*2));
75 #endif
76 }
77
78 int init_8250() {
79         if (!inited_8250) {
80                 uint16_t eqw;
81
82                 memset(base_8250_port,0,sizeof(base_8250_port));
83                 base_8250_ports = 0;
84                 bios_8250_ports = 0;
85                 inited_8250 = 1;
86
87                 /* read the BIOS equipment word[11-9]. how many serial ports? */
88 #if TARGET_MSDOS == 32
89                 eqw = *((uint16_t*)(0x400 + 0x10));
90 #else
91                 eqw = *((uint16_t far*)MK_FP(0x40,0x10));
92 #endif
93                 bios_8250_ports = (eqw >> 9) & 7;
94                 if (bios_8250_ports > 4) bios_8250_ports = 4;
95         }
96
97         return 1;
98 }
99
100 /* ISA PnP version. The program calling us does the work of scanning BIOS device nodes and ISA PnP device isolation,
101  * then presents us with the IRQ and port number. We take the caller's word for it. If for any reason the caller
102  * did not find the IRQ, it should pass irq == -1 */
103 int add_pnp_8250(uint16_t port,int irq) {
104         unsigned char ier,dlab1,dlab2,c,fcr;
105         struct info_8250 *inf;
106
107         if (already_got_8250_port(port))
108                 return 0;
109         if (base_8250_full())
110                 return 0;
111
112         inf = &info_8250_port[base_8250_ports];
113         inf->type = TYPE_8250_IS_8250;
114         inf->port = port;
115         inf->irq = irq;
116         if (windows_mode == WINDOWS_NONE || windows_mode == WINDOWS_REAL) {
117                 /* in real-mode DOS we can play with the UART to our heart's content. so we play with the
118                  * DLAB select and interrupt enable registers to detect the UART in a manner non-destructive
119                  * to the hardware state. */
120
121                 /* switch registers 0+1 back to RX/TX and interrupt enable, and then test the Interrupt Enable register */
122                 _cli();
123                 outp(port+3,inp(port+3) & 0x7F);
124                 if (inp(port+3) == 0xFF) { _sti(); return 0; }
125                 ier = inp(port+1);
126                 outp(port+1,0);
127                 if (inp(port+1) == 0xFF) { _sti(); return 0; }
128                 outp(port+1,ier);
129                 if ((inp(port+1) & 0xF) != (ier & 0xF)) { _sti(); return 0; }
130                 /* then switch 0+1 to DLAB (divisor registers) and see if values differ from what we read the first time */
131                 outp(port+3,inp(port+3) | 0x80);
132                 dlab1 = inp(port+0);
133                 dlab2 = inp(port+1);
134                 outp(port+0,ier ^ 0xAA);
135                 outp(port+1,ier ^ 0x55);
136                 if (inp(port+1) == ier || inp(port+0) != (ier ^ 0xAA) || inp(port+1) != (ier ^ 0x55)) {
137                         outp(port+0,dlab1);
138                         outp(port+1,dlab2);
139                         outp(port+3,inp(port+3) & 0x7F);
140                         _sti();
141                         return 0;
142                 }
143                 outp(port+0,dlab1);
144                 outp(port+1,dlab2);
145                 outp(port+3,inp(port+3) & 0x7F);
146
147                 /* now figure out what type */
148                 fcr = inp(port+2);
149                 outp(port+2,0xE7);      /* write FCR */
150                 c = inp(port+2);        /* read IIR */
151                 if (c & 0x40) { /* if FIFO */
152                         if (c & 0x80) {
153                                 if (c & 0x20) inf->type = TYPE_8250_IS_16750;
154                                 else inf->type = TYPE_8250_IS_16550A;
155                         }
156                         else {
157                                 inf->type = TYPE_8250_IS_16550;
158                         }
159                 }
160                 else {
161                         unsigned char oscratch = inp(port+7);
162
163                         /* no FIFO. try the scratch register */
164                         outp(port+7,0x55);
165                         if (inp(port+7) == 0x55) {
166                                 outp(port+7,0xAA);
167                                 if (inp(port+7) == 0xAA) {
168                                         outp(port+7,0x00);
169                                         if (inp(port+7) == 0x00) {
170                                                 inf->type = TYPE_8250_IS_16450;
171                                         }
172                                 }
173                         }
174
175                         outp(port+7,oscratch);
176                 }
177
178                 outp(port+2,fcr);
179                 _sti();
180         }
181         else {
182                 unsigned int i;
183
184                 /* if we were to actually do our self-test in a VM, Windows would mistakingly assume we
185                  * were trying to use it and would allocate the port. we're just enumerating at this point.
186                  * play it safe and assume it works if the port is listed as one of the BIOS ports.
187                  * we also don't use interrupts. */
188                 for (i=0;i < bios_8250_ports && port != get_8250_bios_port(i);) i++;
189                 if (i >= bios_8250_ports) return 0;
190         }
191
192         base_8250_port[base_8250_ports++] = port;
193         return 1;
194 }
195
196 /* this is used to probe for ports in standard locations, when we really don't know if it's there */
197 int probe_8250(uint16_t port) {
198         unsigned char ier,dlab1,dlab2,c,fcr;
199         struct info_8250 *inf;
200
201         if (already_got_8250_port(port))
202                 return 0;
203         if (base_8250_full())
204                 return 0;
205
206         inf = &info_8250_port[base_8250_ports];
207         inf->type = TYPE_8250_IS_8250;
208         inf->port = port;
209         inf->irq = -1;
210         if (windows_mode == WINDOWS_NONE || windows_mode == WINDOWS_REAL) {
211                 /* in real-mode DOS we can play with the UART to our heart's content. so we play with the
212                  * DLAB select and interrupt enable registers to detect the UART in a manner non-destructive
213                  * to the hardware state. */
214
215                 /* there's no way to autodetect the COM port's IRQ, we have to guess */
216                 if (port == 0x3F8 || port == 0x3E8)
217                         inf->irq = 4;
218                 else if (port == 0x2F8 || port == 0x2E8)
219                         inf->irq = 3;
220
221                 /* switch registers 0+1 back to RX/TX and interrupt enable, and then test the Interrupt Enable register */
222                 _cli();
223                 outp(port+3,inp(port+3) & 0x7F);
224                 if (inp(port+3) == 0xFF) { _sti(); return 0; }
225                 ier = inp(port+1);
226                 outp(port+1,0);
227                 if (inp(port+1) == 0xFF) { _sti(); return 0; }
228                 outp(port+1,ier);
229                 if ((inp(port+1) & 0xF) != (ier & 0xF)) { _sti(); return 0; }
230                 /* then switch 0+1 to DLAB (divisor registers) and see if values differ from what we read the first time */
231                 outp(port+3,inp(port+3) | 0x80);
232                 dlab1 = inp(port+0);
233                 dlab2 = inp(port+1);
234                 outp(port+0,ier ^ 0xAA);
235                 outp(port+1,ier ^ 0x55);
236                 if (inp(port+1) == ier || inp(port+0) != (ier ^ 0xAA) || inp(port+1) != (ier ^ 0x55)) {
237                         outp(port+0,dlab1);
238                         outp(port+1,dlab2);
239                         outp(port+3,inp(port+3) & 0x7F);
240                         _sti();
241                         return 0;
242                 }
243                 outp(port+0,dlab1);
244                 outp(port+1,dlab2);
245                 outp(port+3,inp(port+3) & 0x7F);
246
247                 /* now figure out what type */
248                 fcr = inp(port+2);
249                 outp(port+2,0xE7);      /* write FCR */
250                 c = inp(port+2);        /* read IIR */
251                 if (c & 0x40) { /* if FIFO */
252                         if (c & 0x80) {
253                                 if (c & 0x20) inf->type = TYPE_8250_IS_16750;
254                                 else inf->type = TYPE_8250_IS_16550A;
255                         }
256                         else {
257                                 inf->type = TYPE_8250_IS_16550;
258                         }
259                 }
260                 else {
261                         unsigned char oscratch = inp(port+7);
262
263                         /* no FIFO. try the scratch register */
264                         outp(port+7,0x55);
265                         if (inp(port+7) == 0x55) {
266                                 outp(port+7,0xAA);
267                                 if (inp(port+7) == 0xAA) {
268                                         outp(port+7,0x00);
269                                         if (inp(port+7) == 0x00) {
270                                                 inf->type = TYPE_8250_IS_16450;
271                                         }
272                                 }
273                         }
274
275                         outp(port+7,oscratch);
276                 }
277
278                 outp(port+2,fcr);
279                 _sti();
280         }
281         else {
282                 unsigned int i;
283
284                 /* if we were to actually do our self-test in a VM, Windows would mistakingly assume we
285                  * were trying to use it and would allocate the port. we're just enumerating at this point.
286                  * play it safe and assume it works if the port is listed as one of the BIOS ports.
287                  * we also don't use interrupts. */
288                 for (i=0;i < bios_8250_ports && port != get_8250_bios_port(i);) i++;
289                 if (i >= bios_8250_ports) return 0;
290         }
291
292         base_8250_port[base_8250_ports++] = port;
293         return 1;
294 }
295
296 void uart_8250_enable_interrupt(struct info_8250 *uart,uint8_t mask) {
297         uint8_t c;
298
299         outp(uart->port+PORT_8250_LCR,inp(uart->port+PORT_8250_LCR) & 0x7F);
300
301         /* the mask is written as-is to the IER. we assume the DLAB latch == 0 */
302         outp(uart->port+PORT_8250_IER,mask);
303
304         /* on PC platforms, we also have to diddle with the AUX 2 line (FIXME: why?) */
305         c = inp(uart->port+PORT_8250_MCR);
306         if (mask != 0) c |= 8;  /* AUX 2 output line */
307         else           c &= ~8;
308         outp(uart->port+PORT_8250_MCR,c);
309 }
310
311 void uart_8250_disable_FIFO(struct info_8250 *uart) {
312         if (uart->type <= TYPE_8250_IS_16550) return;
313         outp(uart->port+PORT_8250_FCR,7); /* enable and flush */
314         outp(uart->port+PORT_8250_FCR,0); /* then disable */
315 }
316
317 void uart_8250_set_FIFO(struct info_8250 *uart,uint8_t flags) {
318         if (uart->type <= TYPE_8250_IS_16550) return;
319         outp(uart->port+PORT_8250_FCR,flags | 7);
320         outp(uart->port+PORT_8250_FCR,flags);
321 }
322
323 void uart_8250_set_baudrate(struct info_8250 *uart,uint16_t dlab) {
324         uint8_t c;
325
326         /* enable access to the divisor */
327         c = inp(uart->port+PORT_8250_LCR);
328         outp(uart->port+PORT_8250_LCR,c | 0x80);
329         /* set rate */
330         outp(uart->port+PORT_8250_DIV_LO,dlab);
331         outp(uart->port+PORT_8250_DIV_HI,dlab >> 8);
332         /* disable access to the divisor */
333         outp(uart->port+PORT_8250_LCR,c & 0x7F);
334 }
335
336 uint16_t uart_8250_baud_to_divisor(struct info_8250 *uart,unsigned long rate) {
337         if (rate == 0) return 0;
338         return (uint16_t)(115200UL / rate);
339 }
340
341 unsigned long uart_8250_divisor_to_baud(struct info_8250 *uart,uint16_t rate) {
342         if (rate == 0) return 1;
343         return (unsigned long)(115200UL / (unsigned long)rate);
344 }
345
346 void uart_8250_get_config(struct info_8250 *uart,unsigned long *baud,unsigned char *bits,unsigned char *stop_bits,unsigned char *parity) {
347         uint16_t dlab;
348         uint8_t c = inp(uart->port+PORT_8250_LCR);
349         *bits = (c & 3) + 5;
350         *stop_bits = (c & 4) ? 2 : 1;
351         *parity = (c >> 3) & 7;
352
353         /* then switch on DLAB to get divisor */
354         outp(uart->port+PORT_8250_LCR,c | 0x80);
355
356         /* read back divisor */
357         dlab = inp(uart->port+PORT_8250_DIV_LO);
358         dlab |= (uint16_t)inp(uart->port+PORT_8250_DIV_HI) << 8;
359
360         /* then switch off DLAB */
361         outp(uart->port+PORT_8250_LCR,c & 0x7F);
362
363         *baud = uart_8250_divisor_to_baud(uart,dlab);
364 }
365
366 const char *type_8250_parity(unsigned char parity) {
367         if (parity & 1) {
368                 switch (parity >> 1) {
369                         case 0: return "odd parity";
370                         case 1: return "even parity";
371                         case 2: return "odd sticky parity";
372                         case 3: return "even sticky parity";
373                 };
374         }
375
376         return "no parity";
377 }
378
379 void uart_toggle_xmit_ien(struct info_8250 *uart) {
380         /* apparently if the XMIT buffer is empty, you can trigger
381          * another TX empty interrupt event by toggling bit 2 in
382          * the IER register. */
383         unsigned char c = inp(uart->port+PORT_8250_IER);
384         outp(uart->port+PORT_8250_IER,c & ~(1 << 2));
385         outp(uart->port+PORT_8250_IER,c);
386 }
387