3 * 8250/16450/16550/16750 serial port UART library.
4 * (C) 2009-2012 Jonathan Campbell.
5 * Hackipedia DOS library.
7 * This code is licensed under the LGPL.
8 * <insert LGPL legal text here>
10 * Compiles for intended target environments:
11 * - MS-DOS [pure DOS mode, or Windows or OS/2 DOS Box]
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.
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.
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. */
28 #include <conio.h> /* this is where Open Watcom hides the outp() etc. functions */
35 #include <hw/cpu/cpu.h>
36 #include <hw/dos/dos.h>
37 #include <hw/8250/8250.h>
38 #include <hw/dos/doswin.h>
40 const char *type_8250_strs[TYPE_8250_MAX] = {
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;
56 int already_got_8250_port(uint16_t port) {
59 for (i=0;i < (unsigned int)base_8250_ports;i++) {
60 if (base_8250_port[i] == port)
67 uint16_t get_8250_bios_port(unsigned int index) {
68 if (index >= (unsigned int)bios_8250_ports)
71 #if TARGET_MSDOS == 32
72 return *((uint16_t*)(0x400 + (index*2)));
74 return *((uint16_t far*)MK_FP(0x40,index*2));
82 memset(base_8250_port,0,sizeof(base_8250_port));
87 /* read the BIOS equipment word[11-9]. how many serial ports? */
88 #if TARGET_MSDOS == 32
89 eqw = *((uint16_t*)(0x400 + 0x10));
91 eqw = *((uint16_t far*)MK_FP(0x40,0x10));
93 bios_8250_ports = (eqw >> 9) & 7;
94 if (bios_8250_ports > 4) bios_8250_ports = 4;
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;
107 if (already_got_8250_port(port))
109 if (base_8250_full())
112 inf = &info_8250_port[base_8250_ports];
113 inf->type = TYPE_8250_IS_8250;
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. */
121 /* switch registers 0+1 back to RX/TX and interrupt enable, and then test the Interrupt Enable register */
123 outp(port+3,inp(port+3) & 0x7F);
124 if (inp(port+3) == 0xFF) { _sti(); return 0; }
127 if (inp(port+1) == 0xFF) { _sti(); return 0; }
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);
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)) {
139 outp(port+3,inp(port+3) & 0x7F);
145 outp(port+3,inp(port+3) & 0x7F);
147 /* now figure out what type */
149 outp(port+2,0xE7); /* write FCR */
150 c = inp(port+2); /* read IIR */
151 if (c & 0x40) { /* if FIFO */
153 if (c & 0x20) inf->type = TYPE_8250_IS_16750;
154 else inf->type = TYPE_8250_IS_16550A;
157 inf->type = TYPE_8250_IS_16550;
161 unsigned char oscratch = inp(port+7);
163 /* no FIFO. try the scratch register */
165 if (inp(port+7) == 0x55) {
167 if (inp(port+7) == 0xAA) {
169 if (inp(port+7) == 0x00) {
170 inf->type = TYPE_8250_IS_16450;
175 outp(port+7,oscratch);
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;
192 base_8250_port[base_8250_ports++] = port;
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;
201 if (already_got_8250_port(port))
203 if (base_8250_full())
206 inf = &info_8250_port[base_8250_ports];
207 inf->type = TYPE_8250_IS_8250;
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. */
215 /* there's no way to autodetect the COM port's IRQ, we have to guess */
216 if (port == 0x3F8 || port == 0x3E8)
218 else if (port == 0x2F8 || port == 0x2E8)
221 /* switch registers 0+1 back to RX/TX and interrupt enable, and then test the Interrupt Enable register */
223 outp(port+3,inp(port+3) & 0x7F);
224 if (inp(port+3) == 0xFF) { _sti(); return 0; }
227 if (inp(port+1) == 0xFF) { _sti(); return 0; }
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);
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)) {
239 outp(port+3,inp(port+3) & 0x7F);
245 outp(port+3,inp(port+3) & 0x7F);
247 /* now figure out what type */
249 outp(port+2,0xE7); /* write FCR */
250 c = inp(port+2); /* read IIR */
251 if (c & 0x40) { /* if FIFO */
253 if (c & 0x20) inf->type = TYPE_8250_IS_16750;
254 else inf->type = TYPE_8250_IS_16550A;
257 inf->type = TYPE_8250_IS_16550;
261 unsigned char oscratch = inp(port+7);
263 /* no FIFO. try the scratch register */
265 if (inp(port+7) == 0x55) {
267 if (inp(port+7) == 0xAA) {
269 if (inp(port+7) == 0x00) {
270 inf->type = TYPE_8250_IS_16450;
275 outp(port+7,oscratch);
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;
292 base_8250_port[base_8250_ports++] = port;
296 void uart_8250_enable_interrupt(struct info_8250 *uart,uint8_t mask) {
299 outp(uart->port+PORT_8250_LCR,inp(uart->port+PORT_8250_LCR) & 0x7F);
301 /* the mask is written as-is to the IER. we assume the DLAB latch == 0 */
302 outp(uart->port+PORT_8250_IER,mask);
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 */
308 outp(uart->port+PORT_8250_MCR,c);
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 */
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);
323 void uart_8250_set_baudrate(struct info_8250 *uart,uint16_t dlab) {
326 /* enable access to the divisor */
327 c = inp(uart->port+PORT_8250_LCR);
328 outp(uart->port+PORT_8250_LCR,c | 0x80);
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);
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);
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);
346 void uart_8250_get_config(struct info_8250 *uart,unsigned long *baud,unsigned char *bits,unsigned char *stop_bits,unsigned char *parity) {
348 uint8_t c = inp(uart->port+PORT_8250_LCR);
350 *stop_bits = (c & 4) ? 2 : 1;
351 *parity = (c >> 3) & 7;
353 /* then switch on DLAB to get divisor */
354 outp(uart->port+PORT_8250_LCR,c | 0x80);
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;
360 /* then switch off DLAB */
361 outp(uart->port+PORT_8250_LCR,c & 0x7F);
363 *baud = uart_8250_divisor_to_baud(uart,dlab);
366 const char *type_8250_parity(unsigned char parity) {
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";
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);