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