jcs's openbsd hax
openbsd
1/* $OpenBSD: qcuart.c,v 1.1 2026/01/29 11:23:35 kettenis Exp $ */
2/*
3 * Copyright (c) 2026 Mark Kettenis <kettenis@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/param.h>
19#include <sys/conf.h>
20#include <sys/fcntl.h>
21#include <sys/proc.h>
22#include <sys/systm.h>
23#include <sys/tty.h>
24
25#include <machine/bus.h>
26
27#include <dev/cons.h>
28
29#include <dev/ic/qcuartvar.h>
30
31#define GENI_STATUS 0x040
32#define GENI_STATUS_M_CMD_ACTIVE (1U << 0)
33#define GENI_STATUS_S_CMD_ACTIVE (1U << 12)
34#define GENI_UART_TX_TRANS_LEN 0x270
35#define GENI_M_CMD0 0x600
36#define GENI_M_CMD0_OPCODE_UART_START_TX (1U << 27)
37#define GENI_M_IRQ_STATUS 0x610
38#define GENI_M_IRQ_EN 0x614
39#define GENI_M_IRQ_CLEAR 0x618
40#define GENI_M_IRQ_CMD_DONE (1U << 0)
41#define GENI_M_IRQ_TX_FIFO_WATERMARK (1U << 30)
42#define GENI_M_IRQ_SEC_IRQ (1U << 31)
43#define GENI_S_CMD0 0x630
44#define GENI_S_CMD0_OPCODE_UART_START_RX (1U << 27)
45#define GENI_S_IRQ_STATUS 0x640
46#define GENI_S_IRQ_EN 0x644
47#define GENI_S_IRQ_CLEAR 0x648
48#define GENI_S_IRQ_RX_FIFO_WATERMARK (1U << 26)
49#define GENI_S_IRQ_RX_FIFO_LAST (1U << 27)
50#define GENI_TX_FIFO 0x700
51#define GENI_RX_FIFO 0x780
52#define GENI_TX_FIFO_STATUS 0x800
53#define GENI_TX_FIFO_STATUS_WC_MASK 0xfffffff
54#define GENI_RX_FIFO_STATUS 0x804
55#define GENI_RX_FIFO_STATUS_WC_MASK 0x1ffffff
56#define GENI_TX_FIFO_WATERMARK 0x80c
57
58#define GENI_SPACE 0x1000
59
60#define QCUART_TX_WATERMARK 2
61
62#define HREAD4(sc, reg) \
63 (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
64#define HWRITE4(sc, reg, val) \
65 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
66#define HSET4(sc, reg, bits) \
67 HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
68#define HCLR4(sc, reg, bits) \
69 HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
70
71cdev_decl(com);
72cdev_decl(qcuart);
73
74#define DEVUNIT(x) (minor(x) & 0x7f)
75#define DEVCUA(x) (minor(x) & 0x80)
76
77struct cdevsw qcuartdev = cdev_tty_init(3, qcuart);
78
79struct cfdriver qcuart_cd = {
80 NULL, "qcuart", DV_TTY
81};
82
83bus_space_tag_t qcuartconsiot;
84bus_space_handle_t qcuartconsioh;
85
86struct qcuart_softc *qcuart_sc(dev_t);
87
88void qcuart_softintr(void *);
89void qcuart_start(struct tty *);
90
91int qcuartcnattach(bus_space_tag_t, bus_addr_t);
92int qcuartcngetc(dev_t);
93void qcuartcnputc(dev_t, int);
94void qcuartcnpollc(dev_t, int);
95
96void
97qcuart_attach_common(struct qcuart_softc *sc, int console)
98{
99 int maj;
100
101 if (console) {
102 /* Locate the major number. */
103 for (maj = 0; maj < nchrdev; maj++)
104 if (cdevsw[maj].d_open == qcuartopen)
105 break;
106 cn_tab->cn_dev = makedev(maj, sc->sc_dev.dv_unit);
107 printf(": console");
108 }
109
110 /* Disable all interrupts. */
111 HWRITE4(sc, GENI_M_IRQ_EN, 0);
112 HWRITE4(sc, GENI_S_IRQ_EN, 0);
113
114 sc->sc_si = softintr_establish(IPL_TTY, qcuart_softintr, sc);
115 if (sc->sc_si == NULL) {
116 printf(": can't establish soft interrupt\n");
117 return;
118 }
119
120 printf("\n");
121}
122
123void
124qcuart_tx_intr(struct qcuart_softc *sc)
125{
126 struct tty *tp = sc->sc_tty;
127
128 if (ISSET(tp->t_state, TS_BUSY)) {
129 CLR(tp->t_state, TS_BUSY | TS_FLUSH);
130 if (sc->sc_halt > 0)
131 wakeup(&tp->t_outq);
132 (*linesw[tp->t_line].l_start)(tp);
133 }
134}
135
136void
137qcuart_rx_intr(struct qcuart_softc *sc)
138{
139 uint32_t stat;
140 u_char c;
141 int *p;
142
143 p = sc->sc_ibufp;
144 for (;;) {
145 stat = HREAD4(sc, GENI_RX_FIFO_STATUS);
146 if ((stat & GENI_RX_FIFO_STATUS_WC_MASK) == 0)
147 break;
148
149 c = HREAD4(sc, GENI_RX_FIFO);
150 if (p >= sc->sc_ibufend)
151 sc->sc_floods++;
152 else
153 *p++ = c;
154 }
155 if (sc->sc_ibufp != p) {
156 sc->sc_ibufp = p;
157 softintr_schedule(sc->sc_si);
158 }
159}
160
161int
162qcuart_intr(void *arg)
163{
164 struct qcuart_softc *sc = arg;
165 struct tty *tp = sc->sc_tty;
166 uint32_t m_stat, s_stat;
167
168 m_stat = HREAD4(sc, GENI_M_IRQ_STATUS);
169 s_stat = HREAD4(sc, GENI_S_IRQ_STATUS);
170 HWRITE4(sc, GENI_M_IRQ_CLEAR, m_stat);
171 HWRITE4(sc, GENI_S_IRQ_CLEAR, s_stat);
172 m_stat &= HREAD4(sc, GENI_M_IRQ_EN);
173
174 if (tp == NULL)
175 return 0;
176
177 if (m_stat & GENI_M_IRQ_CMD_DONE)
178 qcuart_tx_intr(sc);
179
180 if (m_stat & GENI_M_IRQ_SEC_IRQ)
181 qcuart_rx_intr(sc);
182
183 return m_stat ? 1 : 0;
184}
185
186void
187qcuart_softintr(void *arg)
188{
189 struct qcuart_softc *sc = arg;
190 struct tty *tp = sc->sc_tty;
191 int *ibufp, *ibufend;
192 int s;
193
194 if (sc->sc_ibufp == sc->sc_ibuf)
195 return;
196
197 s = spltty();
198
199 ibufp = sc->sc_ibuf;
200 ibufend = sc->sc_ibufp;
201
202 if (ibufp == ibufend) {
203 splx(s);
204 return;
205 }
206
207 sc->sc_ibufp = sc->sc_ibuf = (ibufp == sc->sc_ibufs[0]) ?
208 sc->sc_ibufs[1] : sc->sc_ibufs[0];
209 sc->sc_ibufhigh = sc->sc_ibuf + QCUART_IHIGHWATER;
210 sc->sc_ibufend = sc->sc_ibuf + QCUART_IBUFSIZE;
211
212 if (tp == NULL || !ISSET(tp->t_state, TS_ISOPEN)) {
213 splx(s);
214 return;
215 }
216
217 splx(s);
218
219 while (ibufp < ibufend) {
220 int i = *ibufp++;
221#ifdef DDB
222 if (tp->t_dev == cn_tab->cn_dev) {
223 int j = db_rint(i);
224
225 if (j == 1) /* Escape received, skip */
226 continue;
227 if (j == 2) /* Second char wasn't 'D' */
228 (*linesw[tp->t_line].l_rint)(27, tp);
229 }
230#endif
231 (*linesw[tp->t_line].l_rint)(i, tp);
232 }
233}
234
235int
236qcuart_param(struct tty *tp, struct termios *t)
237{
238 struct qcuart_softc *sc = qcuart_sc(tp->t_dev);
239 int ospeed = t->c_ospeed;
240
241 /* Check requested parameters. */
242 if (ospeed < 0 || (t->c_ispeed && t->c_ispeed != t->c_ospeed))
243 return EINVAL;
244
245 switch (ISSET(t->c_cflag, CSIZE)) {
246 case CS5:
247 case CS6:
248 case CS7:
249 return EINVAL;
250 case CS8:
251 break;
252 }
253
254 if (ospeed != 0) {
255 while (ISSET(tp->t_state, TS_BUSY)) {
256 int error;
257
258 sc->sc_halt++;
259 error = ttysleep(tp, &tp->t_outq,
260 TTOPRI | PCATCH, "qcuprm");
261 sc->sc_halt--;
262 if (error) {
263 qcuart_start(tp);
264 return error;
265 }
266 }
267 }
268
269 tp->t_ispeed = t->c_ispeed;
270 tp->t_ospeed = t->c_ospeed;
271 tp->t_cflag = t->c_cflag;
272
273 /* Just to be sure... */
274 qcuart_start(tp);
275 return 0;
276}
277
278void
279qcuart_start(struct tty *tp)
280{
281 struct qcuart_softc *sc = qcuart_sc(tp->t_dev);
282 int s;
283
284 s = spltty();
285 if (ISSET(tp->t_state, TS_BUSY))
286 goto out;
287 if (ISSET(tp->t_state, TS_TIMEOUT | TS_TTSTOP) || sc->sc_halt > 0)
288 goto out;
289 ttwakeupwr(tp);
290 if (tp->t_outq.c_cc == 0)
291 goto out;
292 SET(tp->t_state, TS_BUSY);
293
294 /* Enable Tx completion interrupts. */
295 HSET4(sc, GENI_M_IRQ_EN, GENI_M_IRQ_CMD_DONE);
296
297 /* Send a single character. */
298 HWRITE4(sc, GENI_UART_TX_TRANS_LEN, 1);
299 HWRITE4(sc, GENI_M_CMD0, GENI_M_CMD0_OPCODE_UART_START_TX);
300 HWRITE4(sc, GENI_TX_FIFO, getc(&tp->t_outq));
301
302out:
303 splx(s);
304}
305
306int
307qcuartopen(dev_t dev, int flag, int mode, struct proc *p)
308{
309 struct qcuart_softc *sc = qcuart_sc(dev);
310 struct tty *tp;
311 int error;
312 int s;
313
314 if (sc == NULL)
315 return ENXIO;
316
317 s = spltty();
318 if (sc->sc_tty == NULL)
319 tp = sc->sc_tty = ttymalloc(0);
320 else
321 tp = sc->sc_tty;
322 splx(s);
323
324 tp->t_oproc = qcuart_start;
325 tp->t_param = qcuart_param;
326 tp->t_dev = dev;
327
328 if (!ISSET(tp->t_state, TS_ISOPEN)) {
329 SET(tp->t_state, TS_WOPEN);
330 ttychars(tp);
331 tp->t_iflag = TTYDEF_IFLAG;
332 tp->t_oflag = TTYDEF_OFLAG;
333 tp->t_cflag = TTYDEF_CFLAG;
334 tp->t_lflag = TTYDEF_LFLAG;
335 tp->t_ispeed = tp->t_ospeed =
336 sc->sc_conspeed ? sc->sc_conspeed : B115200;
337
338 s = spltty();
339
340 qcuart_param(tp, &tp->t_termios);
341 ttsetwater(tp);
342
343 sc->sc_ibufp = sc->sc_ibuf = sc->sc_ibufs[0];
344 sc->sc_ibufhigh = sc->sc_ibuf + QCUART_IHIGHWATER;
345 sc->sc_ibufend = sc->sc_ibuf + QCUART_IBUFSIZE;
346
347 /* Enable Rx interrupts. */
348 HSET4(sc, GENI_S_IRQ_EN,
349 GENI_S_IRQ_RX_FIFO_WATERMARK | GENI_S_IRQ_RX_FIFO_LAST);
350 HSET4(sc, GENI_M_IRQ_EN, GENI_M_IRQ_SEC_IRQ);
351
352 /* Start Rx engine. */
353 HWRITE4(sc, GENI_S_CMD0, GENI_S_CMD0_OPCODE_UART_START_RX);
354
355 /* No carrier detect support. */
356 SET(tp->t_state, TS_CARR_ON);
357 } else if (ISSET(tp->t_state, TS_XCLUDE) && suser(p) != 0)
358 return EBUSY;
359 else
360 s = spltty();
361
362 if (DEVCUA(dev)) {
363 if (ISSET(tp->t_state, TS_ISOPEN)) {
364 /* Ah, but someone already is dialed in... */
365 splx(s);
366 return EBUSY;
367 }
368 sc->sc_cua = 1; /* We go into CUA mode. */
369 } else {
370 if (ISSET(flag, O_NONBLOCK) && sc->sc_cua) {
371 /* Opening TTY non-blocking... but the CUA is busy. */
372 splx(s);
373 return EBUSY;
374 } else {
375 while (sc->sc_cua) {
376 SET(tp->t_state, TS_WOPEN);
377 error = ttysleep(tp, &tp->t_rawq,
378 TTIPRI | PCATCH, ttopen);
379 /*
380 * If TS_WOPEN has been reset, that means the
381 * cua device has been closed.
382 * We don't want to fail in that case,
383 * so just go around again.
384 */
385 if (error && ISSET(tp->t_state, TS_WOPEN)) {
386 CLR(tp->t_state, TS_WOPEN);
387 splx(s);
388 return error;
389 }
390 }
391 }
392 }
393 splx(s);
394
395 return (*linesw[tp->t_line].l_open)(dev, tp, p);
396}
397
398int
399qcuartclose(dev_t dev, int flag, int mode, struct proc *p)
400{
401 struct qcuart_softc *sc = qcuart_sc(dev);
402 struct tty *tp = sc->sc_tty;
403 int s;
404
405 if (!ISSET(tp->t_state, TS_ISOPEN))
406 return 0;
407
408 (*linesw[tp->t_line].l_close)(tp, flag, p);
409 s = spltty();
410 if (!ISSET(tp->t_state, TS_WOPEN)) {
411 /* Disable interrupts. */
412 HWRITE4(sc, GENI_M_IRQ_EN, 0);
413 HWRITE4(sc, GENI_S_IRQ_EN, 0);
414 }
415 CLR(tp->t_state, TS_BUSY | TS_FLUSH);
416 sc->sc_cua = 0;
417 splx(s);
418 ttyclose(tp);
419
420 return 0;
421}
422
423int
424qcuartread(dev_t dev, struct uio *uio, int flag)
425{
426 struct tty *tp = qcuarttty(dev);
427
428 if (tp == NULL)
429 return ENODEV;
430
431 return (*linesw[tp->t_line].l_read)(tp, uio, flag);
432}
433
434int
435qcuartwrite(dev_t dev, struct uio *uio, int flag)
436{
437 struct tty *tp = qcuarttty(dev);
438
439 if (tp == NULL)
440 return ENODEV;
441
442 return (*linesw[tp->t_line].l_write)(tp, uio, flag);
443}
444
445int
446qcuartioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
447{
448 struct qcuart_softc *sc = qcuart_sc(dev);
449 struct tty *tp;
450 int error;
451
452 if (sc == NULL)
453 return ENODEV;
454
455 tp = sc->sc_tty;
456 if (tp == NULL)
457 return ENXIO;
458
459 error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
460 if (error >= 0)
461 return error;
462
463 error = ttioctl(tp, cmd, data, flag, p);
464 if (error >= 0)
465 return error;
466
467 switch(cmd) {
468 case TIOCSBRK:
469 case TIOCCBRK:
470 case TIOCSDTR:
471 case TIOCCDTR:
472 case TIOCMSET:
473 case TIOCMBIS:
474 case TIOCMBIC:
475 case TIOCMGET:
476 case TIOCGFLAGS:
477 break;
478 case TIOCSFLAGS:
479 error = suser(p);
480 if (error != 0)
481 return EPERM;
482 break;
483 default:
484 return ENOTTY;
485 }
486
487 return 0;
488}
489
490int
491qcuartstop(struct tty *tp, int flag)
492{
493 return 0;
494}
495
496struct tty *
497qcuarttty(dev_t dev)
498{
499 struct qcuart_softc *sc = qcuart_sc(dev);
500
501 if (sc == NULL)
502 return NULL;
503 return sc->sc_tty;
504}
505
506struct qcuart_softc *
507qcuart_sc(dev_t dev)
508{
509 int unit = DEVUNIT(dev);
510
511 if (unit >= qcuart_cd.cd_ndevs)
512 return NULL;
513 return (struct qcuart_softc *)qcuart_cd.cd_devs[unit];
514}
515
516int
517qcuartcnattach(bus_space_tag_t iot, bus_addr_t iobase)
518{
519 static struct consdev qcuartcons = {
520 NULL, NULL, qcuartcngetc, qcuartcnputc, qcuartcnpollc, NULL,
521 NODEV, CN_MIDPRI
522 };
523 int maj;
524
525 qcuartconsiot = iot;
526 if (bus_space_map(iot, iobase, GENI_SPACE, 0, &qcuartconsioh))
527 return ENOMEM;
528
529 /* Look for major of com(4) to replace. */
530 for (maj = 0; maj < nchrdev; maj++)
531 if (cdevsw[maj].d_open == comopen)
532 break;
533 if (maj == nchrdev)
534 return ENXIO;
535
536 cn_tab = &qcuartcons;
537 cn_tab->cn_dev = makedev(maj, 0);
538 cdevsw[maj] = qcuartdev; /* KLUDGE */
539
540 return 0;
541}
542
543int
544qcuartcngetc(dev_t dev)
545{
546 bus_space_tag_t iot = qcuartconsiot;
547 bus_space_handle_t ioh = qcuartconsioh;
548 uint32_t stat;
549 uint8_t c;
550
551 bus_space_write_4(iot, ioh, GENI_S_CMD0,
552 GENI_S_CMD0_OPCODE_UART_START_RX);
553
554 stat = bus_space_read_4(iot, ioh, GENI_M_IRQ_STATUS);
555 bus_space_write_4(iot, ioh, GENI_M_IRQ_CLEAR, stat);
556 stat = bus_space_read_4(iot, ioh, GENI_S_IRQ_STATUS);
557 bus_space_write_4(iot, ioh, GENI_S_IRQ_CLEAR, stat);
558
559 for (;;) {
560 stat = bus_space_read_4(iot, ioh, GENI_RX_FIFO_STATUS);
561 if (stat & GENI_RX_FIFO_STATUS_WC_MASK)
562 break;
563 CPU_BUSY_CYCLE();
564 }
565
566 c = bus_space_read_4(iot, ioh, GENI_RX_FIFO);
567 return c;
568}
569
570void
571qcuartcnputc(dev_t dev, int c)
572{
573 bus_space_tag_t iot = qcuartconsiot;
574 bus_space_handle_t ioh = qcuartconsioh;
575 uint32_t stat;
576
577 bus_space_write_4(iot, ioh, GENI_TX_FIFO_WATERMARK,
578 QCUART_TX_WATERMARK);
579
580 bus_space_write_4(iot, ioh, GENI_UART_TX_TRANS_LEN, 1);
581 bus_space_write_4(iot, ioh, GENI_M_CMD0,
582 GENI_M_CMD0_OPCODE_UART_START_TX);
583
584 for (;;) {
585 stat = bus_space_read_4(iot, ioh, GENI_M_IRQ_STATUS);
586 if (stat & GENI_M_IRQ_TX_FIFO_WATERMARK)
587 break;
588 CPU_BUSY_CYCLE();
589 }
590 bus_space_write_4(iot, ioh, GENI_TX_FIFO, c);
591
592 bus_space_write_4(iot, ioh, GENI_M_IRQ_CLEAR,
593 GENI_M_IRQ_TX_FIFO_WATERMARK);
594
595 for (;;) {
596 stat = bus_space_read_4(iot, ioh, GENI_M_IRQ_STATUS);
597 if (stat & GENI_M_IRQ_CMD_DONE)
598 break;
599 CPU_BUSY_CYCLE();
600 }
601 bus_space_write_4(iot, ioh, GENI_M_IRQ_CLEAR, GENI_M_IRQ_CMD_DONE);
602}
603
604void
605qcuartcnpollc(dev_t dev, int on)
606{
607}