Improve serial port detection.
[seabios.git] / src / serial.c
1 // 16bit code to handle serial and printer services.
2 //
3 // Copyright (C) 2008,2009  Kevin O'Connor <kevin@koconnor.net>
4 // Copyright (C) 2002  MandrakeSoft S.A.
5 //
6 // This file may be distributed under the terms of the GNU LGPLv3 license.
7
8 #include "biosvar.h" // SET_BDA
9 #include "util.h" // debug_enter
10 #include "bregs.h" // struct bregs
11
12
13 /****************************************************************
14  * COM ports
15  ****************************************************************/
16
17 static u16
18 detect_serial(u16 port, u8 timeout, u8 count)
19 {
20     outb(0x02, port+SEROFF_IER);
21     u8 ier = inb(port+SEROFF_IER);
22     if (ier != 0x02)
23         return 0;
24     u8 iir = inb(port+SEROFF_IIR);
25     if ((iir & 0x3f) != 0x02)
26         return 0;
27
28     outb(0x00, port+SEROFF_IER);
29     SET_BDA(port_com[count], port);
30     SET_BDA(com_timeout[count], timeout);
31     return 1;
32 }
33
34 void
35 serial_setup()
36 {
37     if (! CONFIG_SERIAL)
38         return;
39     dprintf(3, "init serial\n");
40
41     u16 count = 0;
42     count += detect_serial(PORT_SERIAL1, 0x0a, count);
43     count += detect_serial(PORT_SERIAL2, 0x0a, count);
44     count += detect_serial(PORT_SERIAL3, 0x0a, count);
45     count += detect_serial(PORT_SERIAL4, 0x0a, count);
46     dprintf(1, "Found %d serial ports\n", count);
47
48     // Equipment word bits 9..11 determing # serial ports
49     u16 eqb = GET_BDA(equipment_list_flags);
50     SET_BDA(equipment_list_flags, (eqb & 0xf1ff) | (count << 9));
51 }
52
53 static u16
54 getComAddr(struct bregs *regs)
55 {
56     if (regs->dx >= 4) {
57         set_fail(regs);
58         return 0;
59     }
60     u16 addr = GET_BDA(port_com[regs->dx]);
61     if (! addr)
62         set_fail(regs);
63     return addr;
64 }
65
66 // SERIAL - INITIALIZE PORT
67 static void
68 handle_1400(struct bregs *regs)
69 {
70     u16 addr = getComAddr(regs);
71     if (!addr)
72         return;
73     outb(inb(addr+SEROFF_LCR) | 0x80, addr+SEROFF_LCR);
74     if ((regs->al & 0xE0) == 0) {
75         outb(0x17, addr+SEROFF_DLL);
76         outb(0x04, addr+SEROFF_DLH);
77     } else {
78         u16 val16 = 0x600 >> ((regs->al & 0xE0) >> 5);
79         outb(val16 & 0xFF, addr+SEROFF_DLL);
80         outb(val16 >> 8, addr+SEROFF_DLH);
81     }
82     outb(regs->al & 0x1F, addr+SEROFF_LCR);
83     regs->ah = inb(addr+SEROFF_LSR);
84     regs->al = inb(addr+SEROFF_MSR);
85     set_success(regs);
86 }
87
88 // SERIAL - WRITE CHARACTER TO PORT
89 static void
90 handle_1401(struct bregs *regs)
91 {
92     u16 addr = getComAddr(regs);
93     if (!addr)
94         return;
95     u16 timer = GET_BDA(timer_counter);
96     u16 timeout = GET_BDA(com_timeout[regs->dx]);
97     while (((inb(addr+SEROFF_LSR) & 0x60) != 0x60) && (timeout)) {
98         u16 val16 = GET_BDA(timer_counter);
99         if (val16 != timer) {
100             timer = val16;
101             timeout--;
102         }
103     }
104     if (timeout)
105         outb(regs->al, addr+SEROFF_DATA);
106     regs->ah = inb(addr+SEROFF_LSR);
107     if (!timeout)
108         regs->ah |= 0x80;
109     set_success(regs);
110 }
111
112 // SERIAL - READ CHARACTER FROM PORT
113 static void
114 handle_1402(struct bregs *regs)
115 {
116     u16 addr = getComAddr(regs);
117     if (!addr)
118         return;
119     u16 timer = GET_BDA(timer_counter);
120     u16 timeout = GET_BDA(com_timeout[regs->dx]);
121     while (((inb(addr+SEROFF_LSR) & 0x01) == 0) && (timeout)) {
122         u16 val16 = GET_BDA(timer_counter);
123         if (val16 != timer) {
124             timer = val16;
125             timeout--;
126         }
127     }
128     if (timeout) {
129         regs->ah = 0;
130         regs->al = inb(addr+SEROFF_DATA);
131     } else {
132         regs->ah = inb(addr+SEROFF_LSR);
133     }
134     set_success(regs);
135 }
136
137 // SERIAL - GET PORT STATUS
138 static void
139 handle_1403(struct bregs *regs)
140 {
141     u16 addr = getComAddr(regs);
142     if (!addr)
143         return;
144     regs->ah = inb(addr+SEROFF_LSR);
145     regs->al = inb(addr+SEROFF_MSR);
146     set_success(regs);
147 }
148
149 static void
150 handle_14XX(struct bregs *regs)
151 {
152     // Unsupported
153     set_fail(regs);
154 }
155
156 // INT 14h Serial Communications Service Entry Point
157 void VISIBLE16
158 handle_14(struct bregs *regs)
159 {
160     debug_enter(regs, DEBUG_HDL_14);
161     if (! CONFIG_SERIAL) {
162         handle_14XX(regs);
163         return;
164     }
165
166     irq_enable();
167
168     switch (regs->ah) {
169     case 0x00: handle_1400(regs); break;
170     case 0x01: handle_1401(regs); break;
171     case 0x02: handle_1402(regs); break;
172     case 0x03: handle_1403(regs); break;
173     default:   handle_14XX(regs); break;
174     }
175 }
176
177 // XXX - Baud Rate Generator Table
178 u8 BaudTable[16] VAR16FIXED(0xe729);
179
180
181 /****************************************************************
182  * LPT ports
183  ****************************************************************/
184
185 static u16
186 detect_parport(u16 port, u8 timeout, u8 count)
187 {
188     // clear input mode
189     outb(inb(port+2) & 0xdf, port+2);
190
191     outb(0xaa, port);
192     if (inb(port) != 0xaa)
193         // Not present
194         return 0;
195     SET_BDA(port_lpt[count], port);
196     SET_BDA(lpt_timeout[count], timeout);
197     return 1;
198 }
199
200 void
201 lpt_setup()
202 {
203     if (! CONFIG_LPT)
204         return;
205     dprintf(3, "init lpt\n");
206
207     u16 count = 0;
208     count += detect_parport(0x378, 0x14, count);
209     count += detect_parport(0x278, 0x14, count);
210     dprintf(1, "Found %d lpt ports\n", count);
211
212     // Equipment word bits 14..15 determing # parallel ports
213     u16 eqb = GET_BDA(equipment_list_flags);
214     SET_BDA(equipment_list_flags, (eqb & 0x3fff) | (count << 14));
215 }
216
217 static u16
218 getLptAddr(struct bregs *regs)
219 {
220     if (regs->dx >= 3) {
221         set_fail(regs);
222         return 0;
223     }
224     u16 addr = GET_BDA(port_lpt[regs->dx]);
225     if (! addr)
226         set_fail(regs);
227     return addr;
228 }
229
230 static void
231 lpt_ret(struct bregs *regs, u16 addr, u16 timeout)
232 {
233     u8 val8 = inb(addr+1);
234     regs->ah = (val8 ^ 0x48);
235     if (!timeout)
236         regs->ah |= 0x01;
237     set_success(regs);
238 }
239
240 // INT 17 - PRINTER - WRITE CHARACTER
241 static void
242 handle_1700(struct bregs *regs)
243 {
244     u16 addr = getLptAddr(regs);
245     if (!addr)
246         return;
247     u16 timeout = GET_BDA(lpt_timeout[regs->dx]) << 8;
248
249     outb(regs->al, addr);
250     u8 val8 = inb(addr+2);
251     outb(val8 | 0x01, addr+2); // send strobe
252     nop();
253     outb(val8 & ~0x01, addr+2);
254     // XXX - implement better timeout code.
255     while (((inb(addr+1) & 0x40) == 0x40) && (timeout))
256         timeout--;
257
258     lpt_ret(regs, addr, timeout);
259 }
260
261 // INT 17 - PRINTER - INITIALIZE PORT
262 static void
263 handle_1701(struct bregs *regs)
264 {
265     u16 addr = getLptAddr(regs);
266     if (!addr)
267         return;
268     u16 timeout = GET_BDA(lpt_timeout[regs->dx]) << 8;
269
270     u8 val8 = inb(addr+2);
271     outb(val8 & ~0x04, addr+2); // send init
272     nop();
273     outb(val8 | 0x04, addr+2);
274
275     lpt_ret(regs, addr, timeout);
276 }
277
278 // INT 17 - PRINTER - GET STATUS
279 static void
280 handle_1702(struct bregs *regs)
281 {
282     u16 addr = getLptAddr(regs);
283     if (!addr)
284         return;
285     u16 timeout = GET_BDA(lpt_timeout[regs->dx]) << 8;
286
287     lpt_ret(regs, addr, timeout);
288 }
289
290 static void
291 handle_17XX(struct bregs *regs)
292 {
293     // Unsupported
294     set_fail(regs);
295 }
296
297 // INT17h : Printer Service Entry Point
298 void VISIBLE16
299 handle_17(struct bregs *regs)
300 {
301     debug_enter(regs, DEBUG_HDL_17);
302     if (! CONFIG_LPT) {
303         handle_17XX(regs);
304         return;
305     }
306
307     irq_enable();
308
309     switch (regs->ah) {
310     case 0x00: handle_1700(regs); break;
311     case 0x01: handle_1701(regs); break;
312     case 0x02: handle_1702(regs); break;
313     default:   handle_17XX(regs); break;
314     }
315 }