backported fixes from 256boss
[bootcensus] / src / serial.c
1 /*
2 pcboot - bootable PC demo/game kernel
3 Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY, without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 #include <stdio.h>
19 #include <string.h>
20 #include "config.h"
21 #include "serial.h"
22 #include "asmops.h"
23 #include "intr.h"
24 #include "panic.h"
25
26 #define UART1_BASE      0x3f8
27 #define UART2_BASE      0x2f8
28 #define UART1_IRQ       4
29 #define UART2_IRQ       3
30
31 #define UART_DATA       0
32 #define UART_INTR       1
33 #define UART_DIVLO      0
34 #define UART_DIVHI      1
35 #define UART_FIFO       2
36 #define UART_IID        2
37 #define UART_LCTL       3
38 #define UART_MCTL       4
39 #define UART_LSTAT      5
40 #define UART_MSTAT      6
41
42 /* interrupt enable register bits */
43 #define INTR_RECV       1
44 #define INTR_SEND       2
45 #define INTR_LSTAT      4
46 #define INTR_DELTA      8
47
48 /* fifo control register bits */
49 #define FIFO_ENABLE             0x01
50 #define FIFO_RECV_CLEAR 0x02
51 #define FIFO_SEND_CLEAR 0x04
52 #define FIFO_DMA                0x08
53 #define FIFO_TRIG_4             0x40
54 #define FIFO_TRIG_8             0x80
55 #define FIFO_TRIG_14    0xc0
56
57 /* interrupt id register bits */
58 #define IID_PENDING             0x01
59 #define IID_ID0                 0x02
60 #define IID_ID1                 0x04
61 #define IID_ID2                 0x08
62 #define IID_FIFO_EN             0xc0
63
64 #define IID_SOURCE              0xe
65
66 #define IID_DELTA               0
67 #define IID_SEND                0x2
68 #define IID_RECV                0x4
69 #define IID_FIFO                0xc
70 #define IID_STATUS              0x6
71
72 /* line control register bits */
73 #define LCTL_BITS_8     0x03
74 #define LCTL_STOP_2     0x04
75 #define LCTL_DLAB       0x80
76 #define LCTL_8N1        LCTL_BITS_8
77 #define LCTL_8N2        (LCTL_BITS_8 | LCTL_STOP_2)
78
79 /* modem control register bits */
80 #define MCTL_DTR        0x01
81 #define MCTL_RTS        0x02
82 #define MCTL_OUT1       0x04
83 #define MCTL_OUT2       0x08
84 #define MCTL_LOOP       0x10
85
86 /* line status register bits */
87 #define LST_DRDY                0x01
88 #define LST_ERR_OVER    0x02
89 #define LST_ERR_PARITY  0x04
90 #define LST_ERR_FRAME   0x08
91 #define LST_ERR_BRK             0x10
92 #define LST_TREG_EMPTY  0x20
93 #define LST_TIDLE               0x40
94 #define LST_ERROR               0x80
95
96 /* modem status register bits */
97 #define MST_DELTA_CTS   0x01
98 #define MST_DELTA_DSR   0x02
99 #define MST_TERI                0x04
100 #define MST_DELTA_DCD   0x08
101 #define MST_CTS                 0x10
102 #define MST_DSR                 0x20
103 #define MST_RING                0x40
104 #define MST_DCD                 0x80
105
106 /* interrupt controller stuff */
107 #define PIC1_CMD_PORT   0x20
108 #define PIC1_DATA_PORT  0x21
109 #define PIC2_CMD_PORT   0xa0
110 #define PIC2_DATA_PORT  0xa1
111 #define OCW2_EOI                0x20
112
113 #define COM_FMT_8N1             LCTL_8N1
114 #define COM_FMT_8N2             LCTL_8N2
115
116 struct serial_port {
117         int base, intr;
118         int blocking;
119
120         char inbuf[256];
121         int inbuf_ridx, inbuf_widx;
122 };
123
124 #define BNEXT(x)        (((x) + 1) & 0xff)
125 #define BEMPTY(b)       (b##_ridx == b##_widx)
126
127 static int have_recv(int base);
128 static void recv_intr();
129
130 static struct serial_port ports[2];
131 static int num_open;
132
133 static int uart_base[] = {UART1_BASE, UART2_BASE};
134 static int uart_irq[] = {UART1_IRQ, UART2_IRQ};
135
136 int ser_open(int pidx, int baud, unsigned int mode)
137 {
138         unsigned short div = 115200 / baud;
139         int base, intr;
140         unsigned int fmt;
141
142         if(pidx < 0 || pidx > 1) {
143                 printf("ser_open: invalid serial port: %d\n", pidx);
144                 return -1;
145         }
146
147         if(ports[pidx].base) {
148                 printf("ser_open: port %d already open!\n", pidx);
149                 return -1;
150         }
151         memset(ports + pidx, 0, sizeof ports[pidx]);
152
153         base = uart_base[pidx];
154         intr = uart_irq[pidx];
155
156         if(mode & SER_8N2) {
157                 fmt = COM_FMT_8N2;
158         } else {
159                 fmt = COM_FMT_8N1;
160         }
161
162         interrupt(IRQ_TO_INTR(uart_irq[pidx]), recv_intr);
163
164         outb(LCTL_DLAB, base + UART_LCTL);
165         outb(div & 0xff, base + UART_DIVLO);
166         outb((div >> 8) & 0xff, base + UART_DIVHI);
167         outb(fmt, base + UART_LCTL);    /* fmt should be LCTL_8N1, LCTL_8N2 etc */
168         outb(FIFO_ENABLE | FIFO_SEND_CLEAR | FIFO_RECV_CLEAR, base + UART_FIFO);
169         outb(MCTL_DTR | MCTL_RTS | MCTL_OUT2, base + UART_MCTL);
170         outb(INTR_RECV, base + UART_INTR);
171
172         ports[pidx].base = base;
173         ports[pidx].intr = intr;
174         ports[pidx].blocking = 1;
175         ++num_open;
176         return pidx;
177 }
178
179 void ser_close(int fd)
180 {
181         if(--num_open == 0) {
182                 outb(0, ports[fd].base + UART_INTR);
183                 outb(0, ports[fd].base + UART_MCTL);
184         }
185
186         ports[fd].base = 0;
187 }
188
189 int ser_block(int fd)
190 {
191         ports[fd].blocking = 1;
192         return 0;
193 }
194
195 int ser_nonblock(int fd)
196 {
197         ports[fd].blocking = 0;
198         return 0;
199 }
200
201 int ser_pending(int fd)
202 {
203         return !BEMPTY(ports[fd].inbuf);
204 }
205
206 /* if msec < 0: wait for ever */
207 int ser_wait(int fd, long msec)
208 {
209         int res;
210         while(!(res = ser_pending(fd))) {
211                 /* TODO timeout */
212         }
213         return res;
214 }
215
216 static int can_send(int fd)
217 {
218         int base = ports[fd].base;
219         return inb(base + UART_LSTAT) & LST_TREG_EMPTY;
220 }
221
222 void ser_putc(int fd, char c)
223 {
224         int base = ports[fd].base;
225
226         if(c == '\n') {
227                 ser_putc(fd, '\r');
228         }
229
230         while(!can_send(fd));
231         /*while((inb(base + UART_MSTAT) & MST_CTS) == 0);*/
232         outb(c, base + UART_DATA);
233 }
234
235 int ser_getc(int fd)
236 {
237         struct serial_port *p = ports + fd;
238         int have, c = -1;
239
240         if(p->blocking) {
241                 while(!(have = ser_pending(fd)));
242         } else {
243                 have = ser_pending(fd);
244         }
245
246         if(have) {
247                 c = p->inbuf[p->inbuf_ridx];
248                 p->inbuf_ridx = BNEXT(p->inbuf_ridx);
249         }
250         return c;
251 }
252
253 int ser_write(int fd, const char *buf, int count)
254 {
255         int n = count;
256         while(n--) {
257                 ser_putc(fd, *buf++);
258         }
259         return count;
260 }
261
262 int ser_read(int fd, char *buf, int count)
263 {
264         int c, n = 0;
265         while(n < count && (c = ser_getc(fd)) != -1) {
266                 *buf++ = c;
267                 ++n;
268         }
269         return n;
270 }
271
272 char *ser_getline(int fd, char *buf, int bsz)
273 {
274         static char linebuf[512];
275         static int widx;
276         int i, rd, size, offs;
277
278         size = sizeof linebuf - widx;
279         while(size && (rd = ser_read(fd, linebuf + widx, size)) > 0) {
280                 widx += rd;
281                 size -= rd;
282         }
283
284         linebuf[widx] = 0;
285
286         for(i=0; i<widx; i++) {
287                 if(linebuf[i] == '\r' || linebuf[i] == '\n') {
288                         size = i >= bsz ? bsz - 1 : i;
289                         memcpy(buf, linebuf, size);
290                         buf[size] = 0;
291
292                         offs = i + 1;
293                         memmove(linebuf, linebuf + offs, widx - offs);
294                         widx -= offs;
295                         return buf;
296                 }
297         }
298         return 0;
299 }
300
301 static int have_recv(int base)
302 {
303         unsigned short stat = inb(base + UART_LSTAT);
304         if(stat & LST_ERROR) {
305                 panic("serial receive error\n");
306         }
307         return stat & LST_DRDY;
308 }
309
310 static void recv_intr()
311 {
312         int i, idreg, c;
313
314         for(i=0; i<2; i++) {
315                 int base = uart_base[i];
316                 struct serial_port *p = ports + i;
317
318                 while(((idreg = inb(base + UART_IID)) & IID_PENDING) == 0) {
319                         while(have_recv(base)) {
320                                 c = inb(base + UART_DATA);
321
322 #ifdef ENABLE_GDB_STUB
323                                 if(c == 3 && i == GDB_SERIAL_PORT) {
324                                         asm("int $3");
325                                         continue;
326                                 }
327 #endif
328
329                                 p->inbuf[p->inbuf_widx] = c;
330                                 p->inbuf_widx = BNEXT(p->inbuf_widx);
331
332                                 if(p->inbuf_widx == p->inbuf_ridx) {
333                                         /* we overflowed, drop the oldest */
334                                         p->inbuf_ridx = BNEXT(p->inbuf_ridx);
335                                 }
336                         }
337                 }
338         }
339 }
340
341 #ifdef ENABLE_GDB_STUB
342 void putDebugChar(int c)
343 {
344         ser_putc(GDB_SERIAL_PORT, c);
345 }
346
347 int getDebugChar(void)
348 {
349         return ser_getc(GDB_SERIAL_PORT);
350 }
351 #endif