/* $OpenBSD: switchctl.c,v 1.23 2020/12/25 12:59:53 visa Exp $ */ /* * Copyright (c) 2016 Kazuya GODA * Copyright (c) 2015, 2016 YASUOKA Masahiko * Copyright (c) 2015, 2016 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include extern struct rwlock switch_ifs_lk; /* * device part of switch(4) */ #include #include #include struct switch_softc *switch_dev2sc(dev_t); int switchopen(dev_t, int, int, struct proc *); int switchread(dev_t, struct uio *, int); int switchwrite(dev_t, struct uio *, int); int switchioctl(dev_t, u_long, caddr_t, int, struct proc *); int switchclose(dev_t, int, int, struct proc *); int switchpoll(dev_t, int, struct proc *); int switchkqfilter(dev_t, struct knote *); void filt_switch_rdetach(struct knote *); int filt_switch_read(struct knote *, long); void filt_switch_wdetach(struct knote *); int filt_switch_write(struct knote *, long); int switch_dev_output(struct switch_softc *, struct mbuf *); void switch_dev_wakeup(struct switch_softc *); const struct filterops switch_rd_filtops = { .f_flags = FILTEROP_ISFD, .f_attach = NULL, .f_detach = filt_switch_rdetach, .f_event = filt_switch_read, }; const struct filterops switch_wr_filtops = { .f_flags = FILTEROP_ISFD, .f_attach = NULL, .f_detach = filt_switch_wdetach, .f_event = filt_switch_write, }; struct switch_softc * switch_dev2sc(dev_t dev) { struct switch_softc *sc; rw_enter_read(&switch_ifs_lk); sc = switch_lookup(minor(dev)); rw_exit_read(&switch_ifs_lk); return (sc); } int switchopen(dev_t dev, int flags, int mode, struct proc *p) { struct switch_softc *sc; char name[IFNAMSIZ]; int rv, s, error = 0; unsigned int rdomain = rtable_l2(p->p_p->ps_rtableid); if ((sc = switch_dev2sc(dev)) == NULL) { snprintf(name, sizeof(name), "switch%d", minor(dev)); rv = if_clone_create(name, rdomain); if (rv != 0) return (rv); if ((sc = switch_dev2sc(dev)) == NULL) return (ENXIO); } rw_enter_write(&switch_ifs_lk); if (sc->sc_swdev != NULL) { error = EBUSY; goto failed; } if ((sc->sc_swdev = malloc(sizeof(struct switch_dev), M_DEVBUF, M_DONTWAIT|M_ZERO)) == NULL ) { error = ENOBUFS; goto failed; } s = splnet(); mq_init(&sc->sc_swdev->swdev_outq, 128, IPL_NET); sc->sc_swdev->swdev_output = switch_dev_output; if (sc->sc_capabilities & SWITCH_CAP_OFP) swofp_init(sc); splx(s); failed: rw_exit_write(&switch_ifs_lk); return (error); } int switchread(dev_t dev, struct uio *uio, int ioflag) { struct switch_softc *sc; struct mbuf *m; u_int len; int s, error = 0; sc = switch_dev2sc(dev); if (sc == NULL) return (ENXIO); if (sc->sc_swdev->swdev_lastm != NULL) { m = sc->sc_swdev->swdev_lastm; sc->sc_swdev->swdev_lastm = NULL; goto skip_dequeue; } dequeue_next: s = splnet(); while ((m = mq_dequeue(&sc->sc_swdev->swdev_outq)) == NULL) { if (ISSET(ioflag, IO_NDELAY)) { error = EWOULDBLOCK; goto failed; } sc->sc_swdev->swdev_waiting = 1; error = tsleep_nsec(sc, (PZERO + 1)|PCATCH, "switchread", INFSLP); if (error != 0) goto failed; /* sc might be deleted while sleeping */ sc = switch_dev2sc(dev); if (sc == NULL) { error = ENXIO; goto failed; } } splx(s); skip_dequeue: while (uio->uio_resid > 0) { len = ulmin(uio->uio_resid, m->m_len); if ((error = uiomove(mtod(m, caddr_t), len, uio)) != 0) { /* Save it so user can recover from EFAULT. */ sc->sc_swdev->swdev_lastm = m; return (error); } /* Handle partial reads. */ if (uio->uio_resid == 0) { if (len < m->m_len) m_adj(m, len); else m = m_free(m); sc->sc_swdev->swdev_lastm = m; break; } /* * After consuming data from this mbuf test if we * have to dequeue a new chain. */ m = m_free(m); if (m == NULL) goto dequeue_next; } return (0); failed: splx(s); return (error); } int switchwrite(dev_t dev, struct uio *uio, int ioflag) { struct switch_softc *sc = NULL; struct mbuf *m, *n, *mhead, *mtail = NULL; int s, error, trailing; size_t len; if (uio->uio_resid == 0) return (0); len = uio->uio_resid; sc = switch_dev2sc(dev); if (sc == NULL) return (ENXIO); if (sc->sc_swdev->swdev_inputm == NULL) { MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return (ENOBUFS); if (len >= MHLEN) { MCLGETL(m, M_DONTWAIT, MIN(MAXMCLBYTES, len)); if ((m->m_flags & M_EXT) == 0) { m_free(m); return (ENOBUFS); } } mhead = m; /* m_trailingspace() uses this to calculate space. */ m->m_len = 0; } else { /* Recover the mbuf from the last write and get its tail. */ mhead = sc->sc_swdev->swdev_inputm; for (m = mhead; m->m_next != NULL; m = m->m_next) /* NOTHING */; sc->sc_swdev->swdev_inputm = NULL; } KASSERT(mhead->m_flags & M_PKTHDR); while (len) { trailing = ulmin(m_trailingspace(m), len); if ((error = uiomove(mtod(m, caddr_t) + m->m_len, trailing, uio)) != 0) goto save_return; len -= trailing; mhead->m_pkthdr.len += trailing; m->m_len += trailing; if (len == 0) break; MGET(n, M_DONTWAIT, MT_DATA); if (n == NULL) { error = ENOBUFS; goto save_return; } if (len >= MLEN) { MCLGETL(n, M_DONTWAIT, MIN(MAXMCLBYTES, len)); if ((n->m_flags & M_EXT) == 0) { m_free(n); error = ENOBUFS; goto save_return; } } n->m_len = 0; m->m_next = n; m = n; } /* Loop until there is no more complete OFP packets. */ while (ofp_split_mbuf(mhead, &mtail) == 0) { s = splnet(); sc->sc_swdev->swdev_input(sc, mhead); splx(s); /* We wrote everything, just quit. */ if (mtail == NULL) return (0); mhead = mtail; } /* Save the head, because ofp_split_mbuf failed. */ sc->sc_swdev->swdev_inputm = mhead; return (0); save_return: /* Save it so user can recover from errors later. */ sc->sc_swdev->swdev_inputm = mhead; return (error); } int switchioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p) { int error; switch (cmd) { case FIONBIO: case FIOASYNC: case FIONREAD: return (0); default: error = ENOTTY; break; } return (error); } int switchclose(dev_t dev, int flags, int mode, struct proc *p) { struct switch_softc *sc; rw_enter_write(&switch_ifs_lk); sc = switch_lookup(minor(dev)); if (sc != NULL && sc->sc_swdev != NULL) { m_freem(sc->sc_swdev->swdev_lastm); m_freem(sc->sc_swdev->swdev_inputm); mq_purge(&sc->sc_swdev->swdev_outq); free(sc->sc_swdev, M_DEVBUF, sizeof(struct switch_dev)); sc->sc_swdev = NULL; } rw_exit_write(&switch_ifs_lk); return (0); } void switch_dev_destroy(struct switch_softc *sc) { int s; if (sc->sc_swdev == NULL) return; rw_enter_write(&switch_ifs_lk); if (sc->sc_swdev != NULL) { switch_dev_wakeup(sc); s = splhigh(); klist_invalidate(&sc->sc_swdev->swdev_rsel.si_note); klist_invalidate(&sc->sc_swdev->swdev_wsel.si_note); splx(s); m_freem(sc->sc_swdev->swdev_lastm); m_freem(sc->sc_swdev->swdev_inputm); mq_purge(&sc->sc_swdev->swdev_outq); free(sc->sc_swdev, M_DEVBUF, sizeof(struct switch_dev)); sc->sc_swdev = NULL; } rw_exit_write(&switch_ifs_lk); } int switchpoll(dev_t dev, int events, struct proc *p) { int revents = 0; struct switch_softc *sc = switch_dev2sc(dev); if (sc == NULL) return (POLLERR); if (events & (POLLIN | POLLRDNORM)) { if (!mq_empty(&sc->sc_swdev->swdev_outq) || sc->sc_swdev->swdev_lastm != NULL) revents |= events & (POLLIN | POLLRDNORM); } if (events & (POLLOUT | POLLWRNORM)) revents |= events & (POLLOUT | POLLWRNORM); if (revents == 0) { if (events & (POLLIN | POLLRDNORM)) selrecord(p, &sc->sc_swdev->swdev_rsel); } return (revents); } int switchkqfilter(dev_t dev, struct knote *kn) { struct switch_softc *sc = switch_dev2sc(dev); struct klist *klist; if (sc == NULL) return (ENXIO); switch (kn->kn_filter) { case EVFILT_READ: klist = &sc->sc_swdev->swdev_rsel.si_note; kn->kn_fop = &switch_rd_filtops; break; case EVFILT_WRITE: klist = &sc->sc_swdev->swdev_wsel.si_note; kn->kn_fop = &switch_wr_filtops; break; default: return (EINVAL); } kn->kn_hook = (caddr_t)sc; klist_insert_locked(klist, kn); return (0); } void filt_switch_rdetach(struct knote *kn) { struct switch_softc *sc = (struct switch_softc *)kn->kn_hook; struct klist *klist = &sc->sc_swdev->swdev_rsel.si_note; klist_remove_locked(klist, kn); } int filt_switch_read(struct knote *kn, long hint) { struct switch_softc *sc = (struct switch_softc *)kn->kn_hook; if (!mq_empty(&sc->sc_swdev->swdev_outq) || sc->sc_swdev->swdev_lastm != NULL) { kn->kn_data = mq_len(&sc->sc_swdev->swdev_outq) + (sc->sc_swdev->swdev_lastm != NULL); return (1); } return (0); } void filt_switch_wdetach(struct knote *kn) { struct switch_softc *sc = (struct switch_softc *)kn->kn_hook; struct klist *klist = &sc->sc_swdev->swdev_wsel.si_note; klist_remove_locked(klist, kn); } int filt_switch_write(struct knote *kn, long hint) { /* Always writable */ return (1); } int switch_dev_output(struct switch_softc *sc, struct mbuf *m) { if (mq_enqueue(&sc->sc_swdev->swdev_outq, m) != 0) return (-1); switch_dev_wakeup(sc); return (0); } void switch_dev_wakeup(struct switch_softc *sc) { if (sc->sc_swdev->swdev_waiting) { sc->sc_swdev->swdev_waiting = 0; wakeup((caddr_t)sc); } selwakeup(&sc->sc_swdev->swdev_rsel); }