jcs's openbsd hax
openbsd
1/* $OpenBSD: viocon.c,v 1.19 2025/11/03 09:36:39 jan Exp $ */
2
3/*
4 * Copyright (c) 2013-2015 Stefan Fritsch <sf@sfritsch.de>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/param.h>
20#include <sys/systm.h>
21#include <sys/malloc.h>
22#include <machine/bus.h>
23#include <sys/device.h>
24#include <sys/conf.h>
25#include <sys/tty.h>
26#include <dev/pv/virtioreg.h>
27#include <dev/pv/virtiovar.h>
28
29
30/* features */
31#define VIRTIO_CONSOLE_F_SIZE (1ULL<<0)
32#define VIRTIO_CONSOLE_F_MULTIPORT (1ULL<<1)
33#define VIRTIO_CONSOLE_F_EMERG_WRITE (1ULL<<2)
34
35/* config space */
36#define VIRTIO_CONSOLE_COLS 0 /* 16 bits */
37#define VIRTIO_CONSOLE_ROWS 2 /* 16 bits */
38#define VIRTIO_CONSOLE_MAX_NR_PORTS 4 /* 32 bits */
39#define VIRTIO_CONSOLE_EMERG_WR 8 /* 32 bits */
40
41#define VIOCON_DEBUG 0
42
43#if VIOCON_DEBUG
44#define DPRINTF(x...) printf(x)
45#else
46#define DPRINTF(x...)
47#endif
48
49static const struct virtio_feature_name viocon_feature_names[] = {
50#if VIRTIO_DEBUG
51 { VIRTIO_CONSOLE_F_SIZE, "Size" },
52 { VIRTIO_CONSOLE_F_MULTIPORT, "MultiPort" },
53 { VIRTIO_CONSOLE_F_EMERG_WRITE, "EmergWrite" },
54#endif
55 { 0, NULL },
56};
57
58struct virtio_console_control {
59 uint32_t id; /* Port number */
60
61#define VIRTIO_CONSOLE_DEVICE_READY 0
62#define VIRTIO_CONSOLE_PORT_ADD 1
63#define VIRTIO_CONSOLE_PORT_REMOVE 2
64#define VIRTIO_CONSOLE_PORT_READY 3
65#define VIRTIO_CONSOLE_CONSOLE_PORT 4
66#define VIRTIO_CONSOLE_RESIZE 5
67#define VIRTIO_CONSOLE_PORT_OPEN 6
68#define VIRTIO_CONSOLE_PORT_NAME 7
69 uint16_t event;
70
71 uint16_t value;
72};
73
74struct virtio_console_control_resize {
75 /* yes, the order is different than in config space */
76 uint16_t rows;
77 uint16_t cols;
78};
79
80#define BUFSIZE 128
81CTASSERT(BUFSIZE < TTHIWATMINSPACE);
82
83#define VIOCONUNIT(x) (minor(x) >> 4)
84#define VIOCONPORT(x) (minor(x) & 0x0f)
85
86struct viocon_port {
87 struct viocon_softc *vp_sc;
88 struct virtqueue *vp_rx;
89 struct virtqueue *vp_tx;
90 void *vp_si;
91 struct tty *vp_tty;
92 const char *vp_name;
93 bus_dma_segment_t vp_dmaseg;
94 bus_dmamap_t vp_dmamap;
95#ifdef NOTYET
96 unsigned int vp_host_open:1; /* XXX needs F_MULTIPORT */
97 unsigned int vp_guest_open:1; /* XXX needs F_MULTIPORT */
98 unsigned int vp_is_console:1; /* XXX needs F_MULTIPORT */
99#endif
100 unsigned int vp_iflow:1; /* rx flow control */
101 uint16_t vp_rows;
102 uint16_t vp_cols;
103 u_char *vp_rx_buf;
104 u_char *vp_tx_buf;
105};
106
107struct viocon_softc {
108 struct device sc_dev;
109 struct virtio_softc *sc_virtio;
110
111 struct virtqueue *sc_c_vq_rx;
112 struct virtqueue *sc_c_vq_tx;
113
114 unsigned int sc_max_ports;
115 struct viocon_port **sc_ports;
116
117 bus_dmamap_t sc_dmamap;
118};
119
120int viocon_match(struct device *, void *, void *);
121void viocon_attach(struct device *, struct device *, void *);
122int viocon_tx_intr(struct virtqueue *);
123int viocon_tx_drain(struct viocon_port *, struct virtqueue *vq);
124int viocon_rx_intr(struct virtqueue *);
125void viocon_rx_soft(void *);
126void viocon_rx_fill(struct viocon_port *);
127int viocon_port_create(struct viocon_softc *, int);
128void vioconstart(struct tty *);
129int vioconhwiflow(struct tty *, int);
130int vioconparam(struct tty *, struct termios *);
131int vioconopen(dev_t, int, int, struct proc *);
132int vioconclose(dev_t, int, int, struct proc *);
133int vioconread(dev_t, struct uio *, int);
134int vioconwrite(dev_t, struct uio *, int);
135int vioconstop(struct tty *, int);
136int vioconioctl(dev_t, u_long, caddr_t, int, struct proc *);
137struct tty *viocontty(dev_t dev);
138
139const struct cfattach viocon_ca = {
140 sizeof(struct viocon_softc),
141 viocon_match,
142 viocon_attach,
143 NULL
144};
145
146struct cfdriver viocon_cd = {
147 NULL, "viocon", DV_TTY, CD_COCOVM
148};
149
150static inline struct viocon_softc *
151dev2sc(dev_t dev)
152{
153 return viocon_cd.cd_devs[VIOCONUNIT(dev)];
154}
155
156static inline struct viocon_port *
157dev2port(dev_t dev)
158{
159 return dev2sc(dev)->sc_ports[VIOCONPORT(dev)];
160}
161
162int
163viocon_match(struct device *parent, void *match, void *aux)
164{
165 struct virtio_attach_args *va = aux;
166 if (va->va_devid == PCI_PRODUCT_VIRTIO_CONSOLE)
167 return 1;
168 return 0;
169}
170
171void
172viocon_attach(struct device *parent, struct device *self, void *aux)
173{
174 struct viocon_softc *sc = (struct viocon_softc *)self;
175 struct virtio_softc *vsc = (struct virtio_softc *)parent;
176 struct virtio_attach_args *va = aux;
177 int maxports = 1;
178
179 if (vsc->sc_child)
180 panic("already attached to something else");
181 vsc->sc_child = self;
182 vsc->sc_ipl = IPL_TTY;
183 sc->sc_virtio = vsc;
184 sc->sc_max_ports = maxports;
185
186 vsc->sc_vqs = malloc(2 * (maxports + 1) * sizeof(struct virtqueue), M_DEVBUF,
187 M_WAITOK|M_CANFAIL|M_ZERO);
188 sc->sc_ports = malloc(maxports * sizeof(sc->sc_ports[0]), M_DEVBUF,
189 M_WAITOK|M_CANFAIL|M_ZERO);
190 if (vsc->sc_vqs == NULL || sc->sc_ports == NULL) {
191 printf("\n%s: Cannot allocate memory\n", __func__);
192 goto err;
193 }
194
195 vsc->sc_driver_features = VIRTIO_CONSOLE_F_SIZE;
196 if (virtio_negotiate_features(vsc, viocon_feature_names) != 0)
197 goto err;
198
199 printf("\n");
200 DPRINTF("%s: softc: %p\n", __func__, sc);
201 if (viocon_port_create(sc, 0) != 0) {
202 printf("\n%s: viocon_port_create failed\n", __func__);
203 goto err;
204 }
205 if (virtio_attach_finish(vsc, va) != 0)
206 goto err;
207 viocon_rx_fill(sc->sc_ports[0]);
208 return;
209
210err:
211 vsc->sc_child = VIRTIO_CHILD_ERROR;
212 free(vsc->sc_vqs, M_DEVBUF, 2 * (maxports + 1) * sizeof(struct virtqueue));
213 free(sc->sc_ports, M_DEVBUF, maxports * sizeof(sc->sc_ports[0]));
214}
215
216int
217viocon_port_create(struct viocon_softc *sc, int portidx)
218{
219 struct virtio_softc *vsc = sc->sc_virtio;
220 int rxidx, txidx, allocsize, nsegs;
221 char name[6];
222 struct viocon_port *vp;
223 caddr_t kva;
224 struct tty *tp;
225
226 vp = malloc(sizeof(*vp), M_DEVBUF, M_WAITOK|M_CANFAIL|M_ZERO);
227 if (vp == NULL)
228 return ENOMEM;
229 sc->sc_ports[portidx] = vp;
230 vp->vp_sc = sc;
231 DPRINTF("%s: vp: %p\n", __func__, vp);
232
233 if (portidx == 0)
234 rxidx = 0;
235 else
236 rxidx = 2 * (portidx + 1);
237 txidx = rxidx + 1;
238
239 snprintf(name, sizeof(name), "p%drx", portidx);
240 if (virtio_alloc_vq(vsc, &vsc->sc_vqs[rxidx], rxidx, 1, name) != 0) {
241 printf("\nCan't alloc %s virtqueue\n", name);
242 goto err;
243 }
244 vp->vp_rx = &vsc->sc_vqs[rxidx];
245 vp->vp_rx->vq_done = viocon_rx_intr;
246 vp->vp_si = softintr_establish(IPL_TTY, viocon_rx_soft, vp);
247 DPRINTF("%s: rx: %p\n", __func__, vp->vp_rx);
248
249 snprintf(name, sizeof(name), "p%dtx", portidx);
250 if (virtio_alloc_vq(vsc, &vsc->sc_vqs[txidx], txidx, 1, name) != 0) {
251 printf("\nCan't alloc %s virtqueue\n", name);
252 goto err;
253 }
254 vp->vp_tx = &vsc->sc_vqs[txidx];
255 vp->vp_tx->vq_done = viocon_tx_intr;
256 DPRINTF("%s: tx: %p\n", __func__, vp->vp_tx);
257
258 vsc->sc_nvqs += 2;
259
260 allocsize = (vp->vp_rx->vq_num + vp->vp_tx->vq_num) * BUFSIZE;
261
262 if (bus_dmamap_create(vsc->sc_dmat, allocsize, 1, allocsize, 0,
263 BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &vp->vp_dmamap) != 0)
264 goto err;
265 if (bus_dmamem_alloc(vsc->sc_dmat, allocsize, 8, 0, &vp->vp_dmaseg,
266 1, &nsegs, BUS_DMA_NOWAIT | BUS_DMA_ZERO) != 0)
267 goto err;
268 if (bus_dmamem_map(vsc->sc_dmat, &vp->vp_dmaseg, nsegs,
269 allocsize, &kva, BUS_DMA_NOWAIT) != 0)
270 goto err;
271 if (bus_dmamap_load(vsc->sc_dmat, vp->vp_dmamap, kva,
272 allocsize, NULL, BUS_DMA_NOWAIT) != 0)
273 goto err;
274 vp->vp_rx_buf = (unsigned char *)kva;
275 /*
276 * XXX use only a small circular tx buffer instead of many BUFSIZE buffers?
277 */
278 vp->vp_tx_buf = vp->vp_rx_buf + vp->vp_rx->vq_num * BUFSIZE;
279
280 if (virtio_has_feature(vsc, VIRTIO_CONSOLE_F_SIZE)) {
281 vp->vp_cols = virtio_read_device_config_2(vsc,
282 VIRTIO_CONSOLE_COLS);
283 vp->vp_rows = virtio_read_device_config_2(vsc,
284 VIRTIO_CONSOLE_ROWS);
285 }
286
287 tp = ttymalloc(1000000);
288 tp->t_oproc = vioconstart;
289 tp->t_param = vioconparam;
290 tp->t_hwiflow = vioconhwiflow;
291 tp->t_dev = (sc->sc_dev.dv_unit << 4) | portidx;
292 vp->vp_tty = tp;
293 DPRINTF("%s: tty: %p\n", __func__, tp);
294
295 virtio_start_vq_intr(vsc, vp->vp_rx);
296 virtio_start_vq_intr(vsc, vp->vp_tx);
297
298 return 0;
299err:
300 panic("%s failed", __func__);
301 return -1;
302}
303
304int
305viocon_tx_drain(struct viocon_port *vp, struct virtqueue *vq)
306{
307 struct virtio_softc *vsc = vq->vq_owner;
308 int ndone = 0, len, slot;
309
310 splassert(IPL_TTY);
311 while (virtio_dequeue(vsc, vq, &slot, &len) == 0) {
312 bus_dmamap_sync(vsc->sc_dmat, vp->vp_dmamap,
313 vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, BUFSIZE,
314 BUS_DMASYNC_POSTREAD);
315 virtio_dequeue_commit(vq, slot);
316 ndone++;
317 }
318 return ndone;
319}
320
321int
322viocon_tx_intr(struct virtqueue *vq)
323{
324 struct virtio_softc *vsc = vq->vq_owner;
325 struct viocon_softc *sc = (struct viocon_softc *)vsc->sc_child;
326 int ndone = 0;
327 int portidx = (vq->vq_index - 1) / 2;
328 struct viocon_port *vp = sc->sc_ports[portidx];
329 struct tty *tp = vp->vp_tty;
330
331 splassert(IPL_TTY);
332 ndone = viocon_tx_drain(vp, vq);
333 if (ndone && ISSET(tp->t_state, TS_BUSY)) {
334 CLR(tp->t_state, TS_BUSY);
335 linesw[tp->t_line].l_start(tp);
336 }
337
338 return 1;
339}
340
341void
342viocon_rx_fill(struct viocon_port *vp)
343{
344 struct virtqueue *vq = vp->vp_rx;
345 struct virtio_softc *vsc = vp->vp_sc->sc_virtio;
346 int r, slot, ndone = 0;
347
348 while ((r = virtio_enqueue_prep(vq, &slot)) == 0) {
349 if (virtio_enqueue_reserve(vq, slot, 1) != 0)
350 break;
351 bus_dmamap_sync(vsc->sc_dmat, vp->vp_dmamap, slot * BUFSIZE,
352 BUFSIZE, BUS_DMASYNC_PREREAD);
353 virtio_enqueue_p(vq, slot, vp->vp_dmamap, slot * BUFSIZE,
354 BUFSIZE, 0);
355 virtio_enqueue_commit(vsc, vq, slot, 0);
356 ndone++;
357 }
358 KASSERT(r == 0 || r == EAGAIN);
359 if (ndone > 0)
360 virtio_notify(vsc, vq);
361}
362
363int
364viocon_rx_intr(struct virtqueue *vq)
365{
366 struct virtio_softc *vsc = vq->vq_owner;
367 struct viocon_softc *sc = (struct viocon_softc *)vsc->sc_child;
368 int portidx = (vq->vq_index - 1) / 2;
369 struct viocon_port *vp = sc->sc_ports[portidx];
370
371 softintr_schedule(vp->vp_si);
372 return 1;
373}
374
375void
376viocon_rx_soft(void *arg)
377{
378 struct viocon_port *vp = arg;
379 struct virtqueue *vq = vp->vp_rx;
380 struct virtio_softc *vsc = vq->vq_owner;
381 struct tty *tp = vp->vp_tty;
382 int slot, len, i;
383 u_char *p;
384
385 while (!vp->vp_iflow && virtio_dequeue(vsc, vq, &slot, &len) == 0) {
386 bus_dmamap_sync(vsc->sc_dmat, vp->vp_dmamap,
387 slot * BUFSIZE, BUFSIZE, BUS_DMASYNC_POSTREAD);
388 p = vp->vp_rx_buf + slot * BUFSIZE;
389 for (i = 0; i < len; i++)
390 (*linesw[tp->t_line].l_rint)(*p++, tp);
391 virtio_dequeue_commit(vq, slot);
392 }
393
394 viocon_rx_fill(vp);
395
396 return;
397}
398
399void
400vioconstart(struct tty *tp)
401{
402 struct viocon_softc *sc = dev2sc(tp->t_dev);
403 struct virtio_softc *vsc;
404 struct viocon_port *vp = dev2port(tp->t_dev);
405 struct virtqueue *vq;
406 u_char *buf;
407 int s, cnt, slot, ret, ndone;
408
409 vsc = sc->sc_virtio;
410 vq = vp->vp_tx;
411
412 s = spltty();
413
414 ndone = viocon_tx_drain(vp, vq);
415 if (ISSET(tp->t_state, TS_BUSY)) {
416 if (ndone > 0)
417 CLR(tp->t_state, TS_BUSY);
418 else
419 goto out;
420 }
421 if (ISSET(tp->t_state, TS_TIMEOUT | TS_TTSTOP))
422 goto out;
423
424 if (tp->t_outq.c_cc == 0)
425 goto out;
426 ndone = 0;
427
428 while (tp->t_outq.c_cc > 0) {
429 ret = virtio_enqueue_prep(vq, &slot);
430 if (ret == EAGAIN)
431 break;
432 KASSERT(ret == 0);
433 ret = virtio_enqueue_reserve(vq, slot, 1);
434 KASSERT(ret == 0);
435 buf = vp->vp_tx_buf + slot * BUFSIZE;
436 cnt = q_to_b(&tp->t_outq, buf, BUFSIZE);
437 bus_dmamap_sync(vsc->sc_dmat, vp->vp_dmamap,
438 vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, cnt,
439 BUS_DMASYNC_PREWRITE);
440 virtio_enqueue_p(vq, slot, vp->vp_dmamap,
441 vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, cnt, 1);
442 virtio_enqueue_commit(vsc, vq, slot, 0);
443 ndone++;
444 }
445 if (ret == EAGAIN)
446 SET(tp->t_state, TS_BUSY);
447 if (ndone > 0)
448 virtio_notify(vsc, vq);
449 ttwakeupwr(tp);
450out:
451 splx(s);
452}
453
454int
455vioconhwiflow(struct tty *tp, int stop)
456{
457 struct viocon_port *vp = dev2port(tp->t_dev);
458 int s;
459
460 s = spltty();
461 vp->vp_iflow = stop;
462 if (stop) {
463 virtio_stop_vq_intr(vp->vp_sc->sc_virtio, vp->vp_rx);
464 } else {
465 virtio_start_vq_intr(vp->vp_sc->sc_virtio, vp->vp_rx);
466 virtio_check_vq(vp->vp_sc->sc_virtio, vp->vp_rx);
467 }
468 splx(s);
469 return 1;
470}
471
472int
473vioconparam(struct tty *tp, struct termios *t)
474{
475 tp->t_ispeed = t->c_ispeed;
476 tp->t_ospeed = t->c_ospeed;
477 tp->t_cflag = t->c_cflag;
478
479 vioconstart(tp);
480 return 0;
481}
482
483int
484vioconopen(dev_t dev, int flag, int mode, struct proc *p)
485{
486 int unit = VIOCONUNIT(dev);
487 int port = VIOCONPORT(dev);
488 struct viocon_softc *sc;
489 struct viocon_port *vp;
490 struct tty *tp;
491 int s, error;
492
493 if (unit >= viocon_cd.cd_ndevs)
494 return (ENXIO);
495 sc = viocon_cd.cd_devs[unit];
496 if (sc == NULL)
497 return (ENXIO);
498 if (ISSET(sc->sc_dev.dv_flags, DVF_ACTIVE) == 0)
499 return (ENXIO);
500
501 s = spltty();
502 if (port >= sc->sc_max_ports) {
503 splx(s);
504 return (ENXIO);
505 }
506 vp = sc->sc_ports[port];
507 tp = vp->vp_tty;
508#ifdef NOTYET
509 vp->vp_guest_open = 1;
510#endif
511 splx(s);
512
513 if (!ISSET(tp->t_state, TS_ISOPEN)) {
514 SET(tp->t_state, TS_WOPEN);
515 ttychars(tp);
516 tp->t_ispeed = 1000000;
517 tp->t_ospeed = 1000000;
518 tp->t_cflag = TTYDEF_CFLAG|CLOCAL|CRTSCTS;
519 tp->t_iflag = TTYDEF_IFLAG;
520 tp->t_oflag = TTYDEF_OFLAG;
521 tp->t_lflag = TTYDEF_LFLAG;
522 if (vp->vp_cols != 0) {
523 tp->t_winsize.ws_col = vp->vp_cols;
524 tp->t_winsize.ws_row = vp->vp_rows;
525 }
526
527 s = spltty();
528 vioconparam(tp, &tp->t_termios);
529 ttsetwater(tp);
530 splx(s);
531 }
532 else if (ISSET(tp->t_state, TS_XCLUDE) && suser(p) != 0) {
533 return (EBUSY);
534 }
535
536 error = linesw[tp->t_line].l_open(dev, tp, p);
537 return error;
538}
539
540int
541vioconclose(dev_t dev, int flag, int mode, struct proc *p)
542{
543 struct viocon_port *vp = dev2port(dev);
544 struct tty *tp = vp->vp_tty;
545 int s;
546
547 if (!ISSET(tp->t_state, TS_ISOPEN))
548 return 0;
549
550 linesw[tp->t_line].l_close(tp, flag, p);
551 s = spltty();
552#ifdef NOTYET
553 vp->vp_guest_open = 0;
554#endif
555 CLR(tp->t_state, TS_BUSY | TS_FLUSH);
556 ttyclose(tp);
557 splx(s);
558
559 return 0;
560}
561
562int
563vioconread(dev_t dev, struct uio *uio, int flag)
564{
565 struct viocon_port *vp = dev2port(dev);
566 struct tty *tp = vp->vp_tty;
567
568 return linesw[tp->t_line].l_read(tp, uio, flag);
569}
570
571int
572vioconwrite(dev_t dev, struct uio *uio, int flag)
573{
574 struct viocon_port *vp = dev2port(dev);
575 struct tty *tp = vp->vp_tty;
576
577 return linesw[tp->t_line].l_write(tp, uio, flag);
578}
579
580struct tty *
581viocontty(dev_t dev)
582{
583 struct viocon_port *vp = dev2port(dev);
584
585 return vp->vp_tty;
586}
587
588int
589vioconstop(struct tty *tp, int flag)
590{
591 int s;
592
593 s = spltty();
594 if (ISSET(tp->t_state, TS_BUSY))
595 if (!ISSET(tp->t_state, TS_TTSTOP))
596 SET(tp->t_state, TS_FLUSH);
597 splx(s);
598 return 0;
599}
600
601int
602vioconioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
603{
604 struct viocon_port *vp = dev2port(dev);
605 struct tty *tp;
606 int error1, error2;
607
608 tp = vp->vp_tty;
609
610 error1 = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
611 if (error1 >= 0)
612 return error1;
613 error2 = ttioctl(tp, cmd, data, flag, p);
614 if (error2 >= 0)
615 return error2;
616 return ENOTTY;
617}