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