jcs's openbsd hax
openbsd
at jcs 617 lines 15 kB view raw
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}