diff --git a/sys/dev/gpio/gpioc.c b/sys/dev/gpio/gpioc.c index f690140af97b..517f7752daad 100644 --- a/sys/dev/gpio/gpioc.c +++ b/sys/dev/gpio/gpioc.c @@ -1,1086 +1,1086 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009 Oleksandr Tymoshenko * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpiobus_if.h" #undef GPIOC_DEBUG #ifdef GPIOC_DEBUG #define dprintf printf #define ddevice_printf device_printf #else #define dprintf(x, arg...) #define ddevice_printf(dev, x, arg...) #endif struct gpioc_softc { device_t sc_dev; /* gpiocX dev */ device_t sc_pdev; /* gpiobusX dev */ struct cdev *sc_ctl_dev; /* controller device */ int sc_unit; int sc_npins; struct gpioc_pin_intr *sc_pin_intr; }; struct gpioc_pin_intr { struct gpioc_softc *sc; gpio_pin_t pin; uint32_t intr_mode; bool config_locked; int intr_rid; struct resource *intr_res; void *intr_cookie; struct mtx mtx; SLIST_HEAD(gpioc_privs_list, gpioc_privs) privs; }; struct gpioc_cdevpriv { struct gpioc_softc *sc; struct selinfo selinfo; bool async; uint8_t report_option; struct sigio *sigio; struct mtx mtx; struct gpioc_pin_event *events; int numevents; int evidx_head; int evidx_tail; SLIST_HEAD(gpioc_pins_list, gpioc_pins) pins; }; struct gpioc_privs { struct gpioc_cdevpriv *priv; SLIST_ENTRY(gpioc_privs) next; }; struct gpioc_pins { struct gpioc_pin_intr *pin; int eventcount; int firstevent; SLIST_ENTRY(gpioc_pins) next; }; struct gpioc_pin_event { struct gpioc_pins *privpin; sbintime_t event_time; bool event_pin_state; }; static MALLOC_DEFINE(M_GPIOC, "gpioc", "gpioc device data"); static int gpioc_allocate_pin_intr(struct gpioc_softc*, struct gpioc_pin_intr*, uint32_t, uint32_t); static int gpioc_release_pin_intr(struct gpioc_softc*, struct gpioc_pin_intr*); static int gpioc_attach_priv_pin(struct gpioc_cdevpriv*, struct gpioc_pin_intr*); static int gpioc_detach_priv_pin(struct gpioc_cdevpriv*, struct gpioc_pin_intr*); static bool gpioc_intr_reconfig_allowed(struct gpioc_cdevpriv*, struct gpioc_pin_intr *intr_conf); static uint32_t gpioc_get_intr_config(struct gpioc_softc*, struct gpioc_cdevpriv*, uint32_t pin); static int gpioc_set_intr_config(struct gpioc_softc*, struct gpioc_cdevpriv*, uint32_t, uint32_t); static void gpioc_interrupt_handler(void*); static int gpioc_kqread(struct knote*, long); static void gpioc_kqdetach(struct knote*); static int gpioc_probe(device_t dev); static int gpioc_attach(device_t dev); static int gpioc_detach(device_t dev); static void gpioc_cdevpriv_dtor(void*); static d_open_t gpioc_open; static d_read_t gpioc_read; static d_ioctl_t gpioc_ioctl; static d_poll_t gpioc_poll; static d_kqfilter_t gpioc_kqfilter; static struct cdevsw gpioc_cdevsw = { .d_version = D_VERSION, .d_open = gpioc_open, .d_read = gpioc_read, .d_ioctl = gpioc_ioctl, .d_poll = gpioc_poll, .d_kqfilter = gpioc_kqfilter, .d_name = "gpioc", }; static const struct filterops gpioc_read_filterops = { .f_isfd = true, .f_attach = NULL, .f_detach = gpioc_kqdetach, .f_event = gpioc_kqread, .f_touch = NULL, - .f_copy = knote_triv_copy, + .f_copy = knote_triv_copy, }; static struct gpioc_pin_event * next_head_event(struct gpioc_cdevpriv *priv) { struct gpioc_pin_event *rv; rv = &priv->events[priv->evidx_head++]; if (priv->evidx_head == priv->numevents) priv->evidx_head = 0; return (rv); } static struct gpioc_pin_event * next_tail_event(struct gpioc_cdevpriv *priv) { struct gpioc_pin_event *rv; rv = &priv->events[priv->evidx_tail++]; if (priv->evidx_tail == priv->numevents) priv->evidx_tail = 0; return (rv); } static size_t number_of_events(struct gpioc_cdevpriv *priv) { if (priv->evidx_head >= priv->evidx_tail) return (priv->evidx_head - priv->evidx_tail); else return (priv->numevents + priv->evidx_head - priv->evidx_tail); } static int gpioc_allocate_pin_intr(struct gpioc_softc *sc, struct gpioc_pin_intr *intr_conf, uint32_t pin, uint32_t flags) { int err; intr_conf->config_locked = true; mtx_unlock(&intr_conf->mtx); MPASS(intr_conf->pin == NULL); err = gpio_pin_get_by_bus_pinnum(sc->sc_pdev, pin, &intr_conf->pin); if (err != 0) goto error_exit; intr_conf->intr_res = gpio_alloc_intr_resource(sc->sc_dev, &intr_conf->intr_rid, RF_ACTIVE, intr_conf->pin, flags); if (intr_conf->intr_res == NULL) { err = ENXIO; goto error_pin; } err = bus_setup_intr(sc->sc_dev, intr_conf->intr_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, gpioc_interrupt_handler, intr_conf, &intr_conf->intr_cookie); if (err != 0) { bus_release_resource(sc->sc_dev, intr_conf->intr_res); intr_conf->intr_res = NULL; goto error_pin; } intr_conf->intr_mode = flags; error_exit: mtx_lock(&intr_conf->mtx); intr_conf->config_locked = false; wakeup(&intr_conf->config_locked); return (err); error_pin: gpio_pin_release(intr_conf->pin); intr_conf->pin = NULL; goto error_exit; } static int gpioc_release_pin_intr(struct gpioc_softc *sc, struct gpioc_pin_intr *intr_conf) { int err; intr_conf->config_locked = true; mtx_unlock(&intr_conf->mtx); if (intr_conf->intr_cookie != NULL) { err = bus_teardown_intr(sc->sc_dev, intr_conf->intr_res, intr_conf->intr_cookie); if (err != 0) goto error_exit; else intr_conf->intr_cookie = NULL; } if (intr_conf->intr_res != NULL) { err = bus_release_resource(sc->sc_dev, SYS_RES_IRQ, intr_conf->intr_rid, intr_conf->intr_res); if (err != 0) goto error_exit; else { intr_conf->intr_rid = 0; intr_conf->intr_res = NULL; } } gpio_pin_release(intr_conf->pin); intr_conf->pin = NULL; intr_conf->intr_mode = 0; err = 0; error_exit: mtx_lock(&intr_conf->mtx); intr_conf->config_locked = false; wakeup(&intr_conf->config_locked); return (err); } static int gpioc_attach_priv_pin(struct gpioc_cdevpriv *priv, struct gpioc_pin_intr *intr_conf) { struct gpioc_privs *priv_link; struct gpioc_pins *pin_link; unsigned int consistency_a __diagused; unsigned int consistency_b __diagused; consistency_a = 0; consistency_b = 0; mtx_assert(&intr_conf->mtx, MA_OWNED); mtx_lock(&priv->mtx); SLIST_FOREACH(priv_link, &intr_conf->privs, next) { if (priv_link->priv == priv) consistency_a++; } KASSERT(consistency_a <= 1, ("inconsistent links between pin config and cdevpriv")); SLIST_FOREACH(pin_link, &priv->pins, next) { if (pin_link->pin == intr_conf) consistency_b++; } KASSERT(consistency_a == consistency_b, ("inconsistent links between pin config and cdevpriv")); if (consistency_a == 1 && consistency_b == 1) { mtx_unlock(&priv->mtx); return (EEXIST); } priv_link = malloc(sizeof(struct gpioc_privs), M_GPIOC, M_NOWAIT | M_ZERO); if (priv_link == NULL) { mtx_unlock(&priv->mtx); return (ENOMEM); } pin_link = malloc(sizeof(struct gpioc_pins), M_GPIOC, M_NOWAIT | M_ZERO); if (pin_link == NULL) { mtx_unlock(&priv->mtx); return (ENOMEM); } priv_link->priv = priv; pin_link->pin = intr_conf; SLIST_INSERT_HEAD(&intr_conf->privs, priv_link, next); SLIST_INSERT_HEAD(&priv->pins, pin_link, next); mtx_unlock(&priv->mtx); return (0); } static int gpioc_detach_priv_pin(struct gpioc_cdevpriv *priv, struct gpioc_pin_intr *intr_conf) { struct gpioc_privs *priv_link, *priv_link_temp; struct gpioc_pins *pin_link, *pin_link_temp; unsigned int consistency_a __diagused; unsigned int consistency_b __diagused; consistency_a = 0; consistency_b = 0; mtx_assert(&intr_conf->mtx, MA_OWNED); mtx_lock(&priv->mtx); SLIST_FOREACH_SAFE(priv_link, &intr_conf->privs, next, priv_link_temp) { if (priv_link->priv == priv) { SLIST_REMOVE(&intr_conf->privs, priv_link, gpioc_privs, next); free(priv_link, M_GPIOC); consistency_a++; } } KASSERT(consistency_a <= 1, ("inconsistent links between pin config and cdevpriv")); SLIST_FOREACH_SAFE(pin_link, &priv->pins, next, pin_link_temp) { if (pin_link->pin == intr_conf) { /* * If the pin we're removing has events in the priv's * event fifo, we can't leave dangling pointers from * those events to the gpioc_pins struct we're about to * free. We also can't remove random items and leave * holes in the events fifo, so just empty it out. */ if (pin_link->eventcount > 0) { priv->evidx_head = priv->evidx_tail = 0; } SLIST_REMOVE(&priv->pins, pin_link, gpioc_pins, next); free(pin_link, M_GPIOC); consistency_b++; } } KASSERT(consistency_a == consistency_b, ("inconsistent links between pin config and cdevpriv")); mtx_unlock(&priv->mtx); return (0); } static bool gpioc_intr_reconfig_allowed(struct gpioc_cdevpriv *priv, struct gpioc_pin_intr *intr_conf) { struct gpioc_privs *priv_link; mtx_assert(&intr_conf->mtx, MA_OWNED); if (SLIST_EMPTY(&intr_conf->privs)) return (true); SLIST_FOREACH(priv_link, &intr_conf->privs, next) { if (priv_link->priv != priv) return (false); } return (true); } static uint32_t gpioc_get_intr_config(struct gpioc_softc *sc, struct gpioc_cdevpriv *priv, uint32_t pin) { struct gpioc_pin_intr *intr_conf = &sc->sc_pin_intr[pin]; struct gpioc_privs *priv_link; uint32_t flags; flags = intr_conf->intr_mode; if (flags == 0) return (0); mtx_lock(&intr_conf->mtx); SLIST_FOREACH(priv_link, &intr_conf->privs, next) { if (priv_link->priv == priv) { flags |= GPIO_INTR_ATTACHED; break; } } mtx_unlock(&intr_conf->mtx); return (flags); } static int gpioc_set_intr_config(struct gpioc_softc *sc, struct gpioc_cdevpriv *priv, uint32_t pin, uint32_t flags) { struct gpioc_pin_intr *intr_conf = &sc->sc_pin_intr[pin]; int res; res = 0; if (intr_conf->intr_mode == 0 && flags == 0) { /* No interrupt configured and none requested: Do nothing. */ return (0); } mtx_lock(&intr_conf->mtx); while (intr_conf->config_locked == true) mtx_sleep(&intr_conf->config_locked, &intr_conf->mtx, 0, "gpicfg", 0); if (intr_conf->intr_mode == 0 && flags != 0) { /* * No interrupt is configured, but one is requested: Allocate * and setup interrupt on the according pin. */ res = gpioc_allocate_pin_intr(sc, intr_conf, pin, flags); if (res == 0) res = gpioc_attach_priv_pin(priv, intr_conf); if (res == EEXIST) res = 0; } else if (intr_conf->intr_mode == flags) { /* * Same interrupt requested as already configured: Attach the * cdevpriv to the corresponding pin. */ res = gpioc_attach_priv_pin(priv, intr_conf); if (res == EEXIST) res = 0; } else if (intr_conf->intr_mode != 0 && flags == 0) { /* * Interrupt configured, but none requested: Teardown and * release the pin when no other cdevpriv is attached. Otherwise * just detach pin and cdevpriv from each other. */ if (gpioc_intr_reconfig_allowed(priv, intr_conf)) { res = gpioc_release_pin_intr(sc, intr_conf); } if (res == 0) res = gpioc_detach_priv_pin(priv, intr_conf); } else { /* * Other flag requested than configured: Reconfigure when no * other cdevpriv is are attached to the pin. */ if (!gpioc_intr_reconfig_allowed(priv, intr_conf)) res = EBUSY; else { res = gpioc_release_pin_intr(sc, intr_conf); if (res == 0) res = gpioc_allocate_pin_intr(sc, intr_conf, pin, flags); if (res == 0) res = gpioc_attach_priv_pin(priv, intr_conf); if (res == EEXIST) res = 0; } } mtx_unlock(&intr_conf->mtx); return (res); } static void gpioc_interrupt_handler(void *arg) { struct gpioc_pin_intr *intr_conf; struct gpioc_privs *privs; sbintime_t evtime; bool pin_state; intr_conf = arg; /* Capture time and pin state first. */ evtime = sbinuptime(); if (intr_conf->intr_mode & GPIO_INTR_EDGE_BOTH) gpio_pin_is_active(intr_conf->pin, &pin_state); else if (intr_conf->intr_mode & GPIO_INTR_EDGE_RISING) pin_state = true; else pin_state = false; mtx_lock(&intr_conf->mtx); if (intr_conf->config_locked == true) { ddevice_printf(sc->sc_dev, "Interrupt configuration in " "progress. Discarding interrupt on pin %d.\n", intr_conf->pin->pin); mtx_unlock(&intr_conf->mtx); return; } if (SLIST_EMPTY(&intr_conf->privs)) { ddevice_printf(sc->sc_dev, "No file descriptor associated with " "occurred interrupt on pin %d.\n", intr_conf->pin->pin); mtx_unlock(&intr_conf->mtx); return; } SLIST_FOREACH(privs, &intr_conf->privs, next) { struct gpioc_cdevpriv *priv = privs->priv; struct gpioc_pins *privpin; struct gpioc_pin_event *event; mtx_lock(&priv->mtx); SLIST_FOREACH(privpin, &priv->pins, next) { if (privpin->pin == intr_conf) break; } if (privpin == NULL) { /* Should be impossible. */ ddevice_printf(sc->sc_dev, "Cannot find privpin\n"); mtx_unlock(&priv->mtx); continue; } if (priv->report_option == GPIO_EVENT_REPORT_DETAIL) { event = next_head_event(priv); /* If head is overtaking tail, advance tail. */ if (priv->evidx_head == priv->evidx_tail) next_tail_event(priv); } else { if (privpin->eventcount > 0) event = &priv->events[privpin->firstevent + 1]; else { privpin->firstevent = priv->evidx_head; event = next_head_event(priv); event->privpin = privpin; event->event_time = evtime; event->event_pin_state = pin_state; event = next_head_event(priv); } ++privpin->eventcount; } event->privpin = privpin; event->event_time = evtime; event->event_pin_state = pin_state; wakeup(priv); selwakeup(&priv->selinfo); KNOTE_LOCKED(&priv->selinfo.si_note, 0); if (priv->async == true && priv->sigio != NULL) pgsigio(&priv->sigio, SIGIO, 0); mtx_unlock(&priv->mtx); } mtx_unlock(&intr_conf->mtx); } static int gpioc_probe(device_t dev) { device_set_desc(dev, "GPIO controller"); return (0); } static int gpioc_attach(device_t dev) { int err; struct gpioc_softc *sc; struct make_dev_args devargs; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_pdev = device_get_parent(dev); sc->sc_unit = device_get_unit(dev); sc->sc_npins = gpiobus_get_npins(dev); sc->sc_pin_intr = malloc(sizeof(struct gpioc_pin_intr) * sc->sc_npins, M_GPIOC, M_WAITOK | M_ZERO); for (int i = 0; i < sc->sc_npins; i++) { sc->sc_pin_intr[i].sc = sc; mtx_init(&sc->sc_pin_intr[i].mtx, "gpioc pin", NULL, MTX_DEF); SLIST_INIT(&sc->sc_pin_intr[i].privs); } make_dev_args_init(&devargs); devargs.mda_devsw = &gpioc_cdevsw; devargs.mda_uid = UID_ROOT; devargs.mda_gid = GID_WHEEL; devargs.mda_mode = 0600; devargs.mda_si_drv1 = sc; err = make_dev_s(&devargs, &sc->sc_ctl_dev, "gpioc%d", sc->sc_unit); if (err != 0) { device_printf(dev, "Failed to create gpioc%d", sc->sc_unit); return (ENXIO); } return (0); } static int gpioc_detach(device_t dev) { struct gpioc_softc *sc = device_get_softc(dev); if (sc->sc_ctl_dev) destroy_dev(sc->sc_ctl_dev); for (int i = 0; i < sc->sc_npins; i++) { mtx_destroy(&sc->sc_pin_intr[i].mtx); MPASS(sc->sc_pin_intr[i].pin == NULL); } free(sc->sc_pin_intr, M_GPIOC); return (0); } static void gpioc_cdevpriv_dtor(void *data) { struct gpioc_cdevpriv *priv; struct gpioc_privs *priv_link, *priv_link_temp; struct gpioc_pins *pin_link, *pin_link_temp; unsigned int consistency __diagused; priv = data; SLIST_FOREACH_SAFE(pin_link, &priv->pins, next, pin_link_temp) { consistency = 0; mtx_lock(&pin_link->pin->mtx); while (pin_link->pin->config_locked == true) mtx_sleep(&pin_link->pin->config_locked, &pin_link->pin->mtx, 0, "gpicfg", 0); SLIST_FOREACH_SAFE(priv_link, &pin_link->pin->privs, next, priv_link_temp) { if (priv_link->priv == priv) { SLIST_REMOVE(&pin_link->pin->privs, priv_link, gpioc_privs, next); free(priv_link, M_GPIOC); consistency++; } } KASSERT(consistency == 1, ("inconsistent links between pin config and cdevpriv")); if (gpioc_intr_reconfig_allowed(priv, pin_link->pin)) { gpioc_release_pin_intr(priv->sc, pin_link->pin); } mtx_unlock(&pin_link->pin->mtx); SLIST_REMOVE(&priv->pins, pin_link, gpioc_pins, next); free(pin_link, M_GPIOC); } wakeup(&priv); knlist_clear(&priv->selinfo.si_note, 0); seldrain(&priv->selinfo); knlist_destroy(&priv->selinfo.si_note); funsetown(&priv->sigio); mtx_destroy(&priv->mtx); free(priv->events, M_GPIOC); free(data, M_GPIOC); } static int gpioc_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { struct gpioc_cdevpriv *priv; int err = 0; priv = malloc(sizeof(*priv), M_GPIOC, M_WAITOK | M_ZERO); priv->sc = dev->si_drv1; mtx_init(&priv->mtx, "gpioc priv", NULL, MTX_DEF); knlist_init_mtx(&priv->selinfo.si_note, &priv->mtx); priv->async = false; priv->report_option = GPIO_EVENT_REPORT_DETAIL; priv->sigio = NULL; /* * Allocate a circular buffer for events. The scheme we use for summary * reporting assumes there will always be a pair of events available to * record the first/last events on any pin, so we allocate 2 * npins. * Even though we actually default to detailed event reporting, 2 * * npins isn't a horrible fifo size for that either. */ priv->numevents = priv->sc->sc_npins * 2; priv->events = malloc(priv->numevents * sizeof(struct gpioc_pin_event), M_GPIOC, M_WAITOK | M_ZERO); priv->evidx_head = priv->evidx_tail = 0; SLIST_INIT(&priv->pins); err = devfs_set_cdevpriv(priv, gpioc_cdevpriv_dtor); if (err != 0) gpioc_cdevpriv_dtor(priv); return (err); } static int gpioc_read(struct cdev *dev, struct uio *uio, int ioflag) { struct gpioc_cdevpriv *priv; struct gpioc_pin_event *event; union { struct gpio_event_summary sum; struct gpio_event_detail evt; uint8_t data[1]; } recbuf; size_t recsize; int err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); if (priv->report_option == GPIO_EVENT_REPORT_SUMMARY) recsize = sizeof(struct gpio_event_summary); else recsize = sizeof(struct gpio_event_detail); if (uio->uio_resid < recsize) return (EINVAL); mtx_lock(&priv->mtx); while (priv->evidx_head == priv->evidx_tail) { if (SLIST_EMPTY(&priv->pins)) { err = ENXIO; break; } else if (ioflag & O_NONBLOCK) { err = EWOULDBLOCK; break; } else { err = mtx_sleep(priv, &priv->mtx, PCATCH, "gpintr", 0); if (err != 0) break; } } while (err == 0 && uio->uio_resid >= recsize && priv->evidx_tail != priv->evidx_head) { event = next_tail_event(priv); if (priv->report_option == GPIO_EVENT_REPORT_SUMMARY) { recbuf.sum.gp_first_time = event->event_time; recbuf.sum.gp_pin = event->privpin->pin->pin->pin; recbuf.sum.gp_count = event->privpin->eventcount; recbuf.sum.gp_first_state = event->event_pin_state; event = next_tail_event(priv); recbuf.sum.gp_last_time = event->event_time; recbuf.sum.gp_last_state = event->event_pin_state; event->privpin->eventcount = 0; event->privpin->firstevent = 0; } else { recbuf.evt.gp_time = event->event_time; recbuf.evt.gp_pin = event->privpin->pin->pin->pin; recbuf.evt.gp_pinstate = event->event_pin_state; } mtx_unlock(&priv->mtx); err = uiomove(recbuf.data, recsize, uio); mtx_lock(&priv->mtx); } mtx_unlock(&priv->mtx); return (err); } static int gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg, int fflag, struct thread *td) { int max_pin, res; struct gpioc_softc *sc = cdev->si_drv1; struct gpioc_cdevpriv *priv; struct gpio_pin pin; struct gpio_req req; struct gpio_access_32 *a32; struct gpio_config_32 *c32; struct gpio_event_config *evcfg; struct gpioc_pin_event *tmp; uint32_t caps, intrflags; switch (cmd) { case GPIOMAXPIN: res = 0; max_pin = sc->sc_npins - 1; bcopy(&max_pin, arg, sizeof(max_pin)); break; case GPIOGETCONFIG: bcopy(arg, &pin, sizeof(pin)); dprintf("get config pin %d\n", pin.gp_pin); res = GPIOBUS_PIN_GETFLAGS(sc->sc_pdev, sc->sc_dev, pin.gp_pin, &pin.gp_flags); /* Fail early */ if (res != 0) break; res = devfs_get_cdevpriv((void **)&priv); if (res != 0) break; pin.gp_flags |= gpioc_get_intr_config(sc, priv, pin.gp_pin); res = GPIOBUS_PIN_GETCAPS(sc->sc_pdev, sc->sc_dev, pin.gp_pin, &pin.gp_caps); if (res != 0) break; res = GPIOBUS_PIN_GETNAME(sc->sc_pdev, pin.gp_pin, pin.gp_name); if (res != 0) break; bcopy(&pin, arg, sizeof(pin)); break; case GPIOSETCONFIG: bcopy(arg, &pin, sizeof(pin)); dprintf("set config pin %d\n", pin.gp_pin); res = devfs_get_cdevpriv((void **)&priv); if (res != 0) break; res = GPIOBUS_PIN_GETCAPS(sc->sc_pdev, sc->sc_dev, pin.gp_pin, &caps); if (res != 0) break; res = gpio_check_flags(caps, pin.gp_flags); if (res != 0) break; intrflags = pin.gp_flags & GPIO_INTR_MASK; /* * We can do only edge interrupts, and only if the * hardware supports that interrupt type on that pin. */ switch (intrflags) { case GPIO_INTR_NONE: break; case GPIO_INTR_EDGE_RISING: case GPIO_INTR_EDGE_FALLING: case GPIO_INTR_EDGE_BOTH: if ((intrflags & caps) == 0) res = EOPNOTSUPP; break; default: res = EINVAL; break; } if (res != 0) break; res = GPIOBUS_PIN_SETFLAGS(sc->sc_pdev, sc->sc_dev, pin.gp_pin, pin.gp_flags & ~GPIO_INTR_MASK); if (res != 0) break; res = gpioc_set_intr_config(sc, priv, pin.gp_pin, intrflags); break; case GPIOGET: bcopy(arg, &req, sizeof(req)); res = GPIOBUS_PIN_GET(sc->sc_pdev, sc->sc_dev, req.gp_pin, &req.gp_value); if (res != 0) break; dprintf("read pin %d -> %d\n", req.gp_pin, req.gp_value); bcopy(&req, arg, sizeof(req)); break; case GPIOSET: bcopy(arg, &req, sizeof(req)); res = GPIOBUS_PIN_SET(sc->sc_pdev, sc->sc_dev, req.gp_pin, req.gp_value); dprintf("write pin %d -> %d\n", req.gp_pin, req.gp_value); break; case GPIOTOGGLE: bcopy(arg, &req, sizeof(req)); dprintf("toggle pin %d\n", req.gp_pin); res = GPIOBUS_PIN_TOGGLE(sc->sc_pdev, sc->sc_dev, req.gp_pin); break; case GPIOSETNAME: bcopy(arg, &pin, sizeof(pin)); dprintf("set name on pin %d\n", pin.gp_pin); res = GPIOBUS_PIN_SETNAME(sc->sc_pdev, pin.gp_pin, pin.gp_name); break; case GPIOACCESS32: a32 = (struct gpio_access_32 *)arg; res = GPIOBUS_PIN_ACCESS_32(sc->sc_pdev, sc->sc_dev, a32->first_pin, a32->clear_pins, a32->change_pins, &a32->orig_pins); break; case GPIOCONFIG32: c32 = (struct gpio_config_32 *)arg; res = GPIOBUS_PIN_CONFIG_32(sc->sc_pdev, sc->sc_dev, c32->first_pin, c32->num_pins, c32->pin_flags); break; case GPIOCONFIGEVENTS: evcfg = (struct gpio_event_config *)arg; res = devfs_get_cdevpriv((void **)&priv); if (res != 0) break; if (evcfg->gp_report_type != GPIO_EVENT_REPORT_DETAIL && evcfg->gp_report_type != GPIO_EVENT_REPORT_SUMMARY) { res = EINVAL; break; } /* Reallocate the events buffer if the user wants it bigger. */ tmp = NULL; if (evcfg->gp_report_type == GPIO_EVENT_REPORT_DETAIL && priv->numevents < evcfg->gp_fifo_size) { tmp = malloc(evcfg->gp_fifo_size * sizeof(struct gpioc_pin_event), M_GPIOC, M_WAITOK | M_ZERO); } mtx_lock(&priv->mtx); /* If any pins have been configured, changes aren't allowed. */ if (!SLIST_EMPTY(&priv->pins)) { mtx_unlock(&priv->mtx); free(tmp, M_GPIOC); res = EINVAL; break; } if (tmp != NULL) { free(priv->events, M_GPIOC); priv->events = tmp; priv->numevents = evcfg->gp_fifo_size; priv->evidx_head = priv->evidx_tail = 0; } priv->report_option = evcfg->gp_report_type; mtx_unlock(&priv->mtx); break; case FIONBIO: /* * This dummy handler is necessary to prevent fcntl() * from failing. The actual handling of non-blocking IO * is done using the O_NONBLOCK ioflag passed to the * read() syscall. */ res = 0; break; case FIOASYNC: res = devfs_get_cdevpriv((void **)&priv); if (res == 0) { if (*(int *)arg == FASYNC) priv->async = true; else priv->async = false; } break; case FIOGETOWN: res = devfs_get_cdevpriv((void **)&priv); if (res == 0) *(int *)arg = fgetown(&priv->sigio); break; case FIOSETOWN: res = devfs_get_cdevpriv((void **)&priv); if (res == 0) res = fsetown(*(int *)arg, &priv->sigio); break; default: return (ENOTTY); break; } return (res); } static int gpioc_poll(struct cdev *dev, int events, struct thread *td) { struct gpioc_cdevpriv *priv; int err; int revents; revents = 0; err = devfs_get_cdevpriv((void **)&priv); if (err != 0) { revents = POLLERR; return (revents); } if (SLIST_EMPTY(&priv->pins)) { revents = POLLHUP; return (revents); } if (events & (POLLIN | POLLRDNORM)) { if (priv->evidx_head != priv->evidx_tail) revents |= events & (POLLIN | POLLRDNORM); else selrecord(td, &priv->selinfo); } return (revents); } static int gpioc_kqfilter(struct cdev *dev, struct knote *kn) { struct gpioc_cdevpriv *priv; struct knlist *knlist; int err; err = devfs_get_cdevpriv((void **)&priv); if (err != 0) return err; if (SLIST_EMPTY(&priv->pins)) return (ENXIO); switch(kn->kn_filter) { case EVFILT_READ: kn->kn_fop = &gpioc_read_filterops; kn->kn_hook = (void *)priv; break; default: return (EOPNOTSUPP); } knlist = &priv->selinfo.si_note; knlist_add(knlist, kn, 0); return (0); } static int gpioc_kqread(struct knote *kn, long hint) { struct gpioc_cdevpriv *priv = kn->kn_hook; size_t recsize; if (SLIST_EMPTY(&priv->pins)) { kn->kn_flags |= EV_EOF; return (1); } else { if (priv->evidx_head != priv->evidx_tail) { if (priv->report_option == GPIO_EVENT_REPORT_SUMMARY) recsize = sizeof(struct gpio_event_summary); else recsize = sizeof(struct gpio_event_detail); kn->kn_data = recsize * number_of_events(priv); return (1); } } return (0); } static void gpioc_kqdetach(struct knote *kn) { struct gpioc_cdevpriv *priv = kn->kn_hook; struct knlist *knlist = &priv->selinfo.si_note; knlist_remove(knlist, kn, 0); } static device_method_t gpioc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gpioc_probe), DEVMETHOD(device_attach, gpioc_attach), DEVMETHOD(device_detach, gpioc_detach), DEVMETHOD_END }; driver_t gpioc_driver = { "gpioc", gpioc_methods, sizeof(struct gpioc_softc) }; DRIVER_MODULE(gpioc, gpiobus, gpioc_driver, 0, 0); MODULE_VERSION(gpioc, 1); diff --git a/sys/dev/hid/hidraw.c b/sys/dev/hid/hidraw.c index d17356642042..5b5e9b58f8bd 100644 --- a/sys/dev/hid/hidraw.c +++ b/sys/dev/hid/hidraw.c @@ -1,1068 +1,1068 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * Copyright (c) 2020, 2025 Vladimir Kondratyev * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf */ #include #include "opt_hid.h" #include #ifdef COMPAT_FREEBSD32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR hidraw_debug #include #include #include #ifdef HID_DEBUG static int hidraw_debug = 0; static SYSCTL_NODE(_hw_hid, OID_AUTO, hidraw, CTLFLAG_RW, 0, "HID raw interface"); SYSCTL_INT(_hw_hid_hidraw, OID_AUTO, debug, CTLFLAG_RWTUN, &hidraw_debug, 0, "Debug level"); #endif #define HIDRAW_INDEX 0xFF /* Arbitrary high value */ #define HIDRAW_LOCAL_BUFSIZE 64 /* Size of on-stack buffer. */ #define HIDRAW_LOCAL_ALLOC(local_buf, size) \ (sizeof(local_buf) > (size) ? (local_buf) : \ malloc((size), M_DEVBUF, M_ZERO | M_WAITOK)) #define HIDRAW_LOCAL_FREE(local_buf, buf) \ if ((local_buf) != (buf)) { \ free((buf), M_DEVBUF); \ } #ifdef HIDRAW_MAKE_UHID_ALIAS #define HIDRAW_NAME "uhid" #else #define HIDRAW_NAME "hidraw" #endif struct hidraw_softc { device_t sc_dev; /* base device */ struct mtx sc_mtx; /* hidbus private mutex */ struct hid_rdesc_info *sc_rdesc; const struct hid_device_info *sc_hw; uint8_t *sc_q; hid_size_t *sc_qlen; int sc_head; int sc_tail; int sc_sleepcnt; struct selinfo sc_rsel; struct proc *sc_async; /* process that wants SIGIO */ struct { /* driver state */ bool open:1; /* device is open */ bool aslp:1; /* waiting for device data in read() */ bool sel:1; /* waiting for device data in poll() */ bool quiet:1; /* Ignore input data */ bool immed:1; /* return read data immediately */ bool uhid:1; /* driver switched in to uhid mode */ bool lock:1; /* input queue sleepable lock */ bool flush:1; /* do not wait for data in read() */ } sc_state; int sc_fflags; /* access mode for open lifetime */ struct cdev *dev; }; #ifdef COMPAT_FREEBSD32 struct hidraw_gen_descriptor32 { uint32_t hgd_data; /* void * */ uint16_t hgd_lang_id; uint16_t hgd_maxlen; uint16_t hgd_actlen; uint16_t hgd_offset; uint8_t hgd_config_index; uint8_t hgd_string_index; uint8_t hgd_iface_index; uint8_t hgd_altif_index; uint8_t hgd_endpt_index; uint8_t hgd_report_type; uint8_t reserved[8]; }; #define HIDRAW_GET_REPORT_DESC32 \ _IOC_NEWTYPE(HIDRAW_GET_REPORT_DESC, struct hidraw_gen_descriptor32) #define HIDRAW_GET_REPORT32 \ _IOC_NEWTYPE(HIDRAW_GET_REPORT, struct hidraw_gen_descriptor32) #define HIDRAW_SET_REPORT_DESC32 \ _IOC_NEWTYPE(HIDRAW_SET_REPORT_DESC, struct hidraw_gen_descriptor32) #define HIDRAW_SET_REPORT32 \ _IOC_NEWTYPE(HIDRAW_SET_REPORT, struct hidraw_gen_descriptor32) #endif static d_open_t hidraw_open; static d_read_t hidraw_read; static d_write_t hidraw_write; static d_ioctl_t hidraw_ioctl; static d_poll_t hidraw_poll; static d_kqfilter_t hidraw_kqfilter; static d_priv_dtor_t hidraw_dtor; static struct cdevsw hidraw_cdevsw = { .d_version = D_VERSION, .d_open = hidraw_open, .d_read = hidraw_read, .d_write = hidraw_write, .d_ioctl = hidraw_ioctl, .d_poll = hidraw_poll, .d_kqfilter = hidraw_kqfilter, .d_name = "hidraw", }; static hid_intr_t hidraw_intr; static device_identify_t hidraw_identify; static device_probe_t hidraw_probe; static device_attach_t hidraw_attach; static device_detach_t hidraw_detach; static int hidraw_kqread(struct knote *, long); static void hidraw_kqdetach(struct knote *); static void hidraw_notify(struct hidraw_softc *); static const struct filterops hidraw_filterops_read = { .f_isfd = 1, .f_detach = hidraw_kqdetach, .f_event = hidraw_kqread, - .f_copy = knote_triv_copy, + .f_copy = knote_triv_copy, }; static void hidraw_identify(driver_t *driver, device_t parent) { device_t child; if (device_find_child(parent, HIDRAW_NAME, DEVICE_UNIT_ANY) == NULL) { child = BUS_ADD_CHILD(parent, 0, HIDRAW_NAME, device_get_unit(parent)); if (child != NULL) hidbus_set_index(child, HIDRAW_INDEX); } } static int hidraw_probe(device_t self) { if (hidbus_get_index(self) != HIDRAW_INDEX) return (ENXIO); hidbus_set_desc(self, "Raw HID Device"); return (BUS_PROBE_GENERIC); } static int hidraw_attach(device_t self) { struct hidraw_softc *sc = device_get_softc(self); struct make_dev_args mda; int error; sc->sc_dev = self; sc->sc_rdesc = hidbus_get_rdesc_info(self); sc->sc_hw = hid_get_device_info(self); /* Hidraw mode does not require report descriptor to work */ if (sc->sc_rdesc->data == NULL || sc->sc_rdesc->len == 0) device_printf(self, "no report descriptor\n"); mtx_init(&sc->sc_mtx, "hidraw lock", NULL, MTX_DEF); knlist_init_mtx(&sc->sc_rsel.si_note, &sc->sc_mtx); make_dev_args_init(&mda); mda.mda_flags = MAKEDEV_WAITOK; mda.mda_devsw = &hidraw_cdevsw; mda.mda_uid = UID_ROOT; mda.mda_gid = GID_OPERATOR; mda.mda_mode = 0600; mda.mda_si_drv1 = sc; error = make_dev_s(&mda, &sc->dev, "hidraw%d", device_get_unit(self)); if (error) { device_printf(self, "Can not create character device\n"); hidraw_detach(self); return (error); } #ifdef HIDRAW_MAKE_UHID_ALIAS (void)make_dev_alias(sc->dev, "uhid%d", device_get_unit(self)); #endif hidbus_set_lock(self, &sc->sc_mtx); hidbus_set_intr(self, hidraw_intr, sc); return (0); } static int hidraw_detach(device_t self) { struct hidraw_softc *sc = device_get_softc(self); DPRINTF("sc=%p\n", sc); if (sc->dev != NULL) { mtx_lock(&sc->sc_mtx); sc->dev->si_drv1 = NULL; /* Wake everyone */ hidraw_notify(sc); mtx_unlock(&sc->sc_mtx); destroy_dev(sc->dev); } knlist_clear(&sc->sc_rsel.si_note, 0); knlist_destroy(&sc->sc_rsel.si_note); seldrain(&sc->sc_rsel); mtx_destroy(&sc->sc_mtx); return (0); } void hidraw_intr(void *context, void *buf, hid_size_t len) { struct hidraw_softc *sc = context; int next; DPRINTFN(5, "len=%d\n", len); DPRINTFN(5, "data = %*D\n", len, buf, " "); next = (sc->sc_tail + 1) % HIDRAW_BUFFER_SIZE; if (sc->sc_state.quiet || next == sc->sc_head) return; bcopy(buf, sc->sc_q + sc->sc_tail * sc->sc_rdesc->rdsize, len); /* Make sure we don't process old data */ if (len < sc->sc_rdesc->rdsize) bzero(sc->sc_q + sc->sc_tail * sc->sc_rdesc->rdsize + len, sc->sc_rdesc->isize - len); sc->sc_qlen[sc->sc_tail] = len; sc->sc_tail = next; hidraw_notify(sc); } static inline int hidraw_lock_queue(struct hidraw_softc *sc, bool flush) { int error = 0; mtx_assert(&sc->sc_mtx, MA_OWNED); if (flush) sc->sc_state.flush = true; ++sc->sc_sleepcnt; while (sc->sc_state.lock && error == 0) { /* Flush is requested. Wakeup all readers and forbid sleeps */ if (flush && sc->sc_state.aslp) { sc->sc_state.aslp = false; DPRINTFN(5, "waking %p\n", &sc->sc_q); wakeup(&sc->sc_q); } error = mtx_sleep(&sc->sc_sleepcnt, &sc->sc_mtx, PZERO | PCATCH, "hidrawio", 0); } --sc->sc_sleepcnt; if (flush) sc->sc_state.flush = false; if (error == 0) sc->sc_state.lock = true; return (error); } static inline void hidraw_unlock_queue(struct hidraw_softc *sc) { mtx_assert(&sc->sc_mtx, MA_OWNED); KASSERT(sc->sc_state.lock, ("input buffer is not locked")); if (sc->sc_sleepcnt != 0) wakeup_one(&sc->sc_sleepcnt); sc->sc_state.lock = false; } static int hidraw_open(struct cdev *dev, int flag, int mode, struct thread *td) { struct hidraw_softc *sc; int error; sc = dev->si_drv1; if (sc == NULL) return (ENXIO); DPRINTF("sc=%p\n", sc); mtx_lock(&sc->sc_mtx); if (sc->sc_state.open) { mtx_unlock(&sc->sc_mtx); return (EBUSY); } sc->sc_state.open = true; mtx_unlock(&sc->sc_mtx); error = devfs_set_cdevpriv(sc, hidraw_dtor); if (error != 0) { mtx_lock(&sc->sc_mtx); sc->sc_state.open = false; mtx_unlock(&sc->sc_mtx); return (error); } sc->sc_q = malloc(sc->sc_rdesc->rdsize * HIDRAW_BUFFER_SIZE, M_DEVBUF, M_ZERO | M_WAITOK); sc->sc_qlen = malloc(sizeof(hid_size_t) * HIDRAW_BUFFER_SIZE, M_DEVBUF, M_ZERO | M_WAITOK); /* Set up interrupt pipe. */ sc->sc_state.immed = false; sc->sc_async = 0; sc->sc_state.uhid = false; /* hidraw mode is default */ sc->sc_state.quiet = false; sc->sc_head = sc->sc_tail = 0; sc->sc_fflags = flag; hid_intr_start(sc->sc_dev); return (0); } static void hidraw_dtor(void *data) { struct hidraw_softc *sc = data; DPRINTF("sc=%p\n", sc); /* Disable interrupts. */ hid_intr_stop(sc->sc_dev); sc->sc_tail = sc->sc_head = 0; sc->sc_async = 0; free(sc->sc_q, M_DEVBUF); free(sc->sc_qlen, M_DEVBUF); sc->sc_q = NULL; mtx_lock(&sc->sc_mtx); sc->sc_state.open = false; mtx_unlock(&sc->sc_mtx); } static int hidraw_read(struct cdev *dev, struct uio *uio, int flag) { struct hidraw_softc *sc; size_t length; int error; DPRINTFN(1, "\n"); sc = dev->si_drv1; if (sc == NULL) return (EIO); mtx_lock(&sc->sc_mtx); error = dev->si_drv1 == NULL ? EIO : hidraw_lock_queue(sc, false); if (error != 0) { mtx_unlock(&sc->sc_mtx); return (error); } if (sc->sc_state.immed) { mtx_unlock(&sc->sc_mtx); DPRINTFN(1, "immed\n"); error = hid_get_report(sc->sc_dev, sc->sc_q, sc->sc_rdesc->isize, NULL, HID_INPUT_REPORT, sc->sc_rdesc->iid); if (error == 0) error = uiomove(sc->sc_q, sc->sc_rdesc->isize, uio); mtx_lock(&sc->sc_mtx); goto exit; } while (sc->sc_tail == sc->sc_head && !sc->sc_state.flush) { if (flag & O_NONBLOCK) { error = EWOULDBLOCK; goto exit; } sc->sc_state.aslp = true; DPRINTFN(5, "sleep on %p\n", &sc->sc_q); error = mtx_sleep(&sc->sc_q, &sc->sc_mtx, PZERO | PCATCH, "hidrawrd", 0); DPRINTFN(5, "woke, error=%d\n", error); if (dev->si_drv1 == NULL) error = EIO; if (error) { sc->sc_state.aslp = false; goto exit; } } while (sc->sc_tail != sc->sc_head && uio->uio_resid > 0) { length = min(uio->uio_resid, sc->sc_state.uhid ? sc->sc_rdesc->isize : sc->sc_qlen[sc->sc_head]); mtx_unlock(&sc->sc_mtx); /* Copy the data to the user process. */ DPRINTFN(5, "got %lu chars\n", (u_long)length); error = uiomove(sc->sc_q + sc->sc_head * sc->sc_rdesc->rdsize, length, uio); mtx_lock(&sc->sc_mtx); if (error != 0) goto exit; /* Remove a small chunk from the input queue. */ sc->sc_head = (sc->sc_head + 1) % HIDRAW_BUFFER_SIZE; /* * In uhid mode transfer as many chunks as possible. Hidraw * packets are transferred one by one due to different length. */ if (!sc->sc_state.uhid) goto exit; } exit: hidraw_unlock_queue(sc); mtx_unlock(&sc->sc_mtx); return (error); } static int hidraw_write(struct cdev *dev, struct uio *uio, int flag) { uint8_t local_buf[HIDRAW_LOCAL_BUFSIZE], *buf; struct hidraw_softc *sc; int error; int size; size_t buf_offset; uint8_t id = 0; DPRINTFN(1, "\n"); sc = dev->si_drv1; if (sc == NULL) return (EIO); if (sc->sc_rdesc->osize == 0) return (EOPNOTSUPP); buf_offset = 0; if (sc->sc_state.uhid) { size = sc->sc_rdesc->osize; if (uio->uio_resid != size) return (EINVAL); } else { size = uio->uio_resid; if (size < 2) return (EINVAL); /* Strip leading 0 if the device doesnt use numbered reports */ error = uiomove(&id, 1, uio); if (error) return (error); if (id != 0) buf_offset++; else size--; /* Check if underlying driver could process this request */ if (size > sc->sc_rdesc->wrsize) return (ENOBUFS); } buf = HIDRAW_LOCAL_ALLOC(local_buf, size); buf[0] = id; error = uiomove(buf + buf_offset, uio->uio_resid, uio); if (error == 0) error = hid_write(sc->sc_dev, buf, size); HIDRAW_LOCAL_FREE(local_buf, buf); return (error); } #ifdef COMPAT_FREEBSD32 static void update_hgd32(const struct hidraw_gen_descriptor *hgd, struct hidraw_gen_descriptor32 *hgd32) { /* Don't update hgd_data pointer */ CP(*hgd, *hgd32, hgd_lang_id); CP(*hgd, *hgd32, hgd_maxlen); CP(*hgd, *hgd32, hgd_actlen); CP(*hgd, *hgd32, hgd_offset); CP(*hgd, *hgd32, hgd_config_index); CP(*hgd, *hgd32, hgd_string_index); CP(*hgd, *hgd32, hgd_iface_index); CP(*hgd, *hgd32, hgd_altif_index); CP(*hgd, *hgd32, hgd_endpt_index); CP(*hgd, *hgd32, hgd_report_type); /* Don't update reserved */ } #endif static int hidraw_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { uint8_t local_buf[HIDRAW_LOCAL_BUFSIZE]; #ifdef COMPAT_FREEBSD32 struct hidraw_gen_descriptor local_hgd; struct hidraw_gen_descriptor32 *hgd32 = NULL; #endif void *buf; struct hidraw_softc *sc; struct hidraw_device_info *hdi; struct hidraw_gen_descriptor *hgd; struct hidraw_report_descriptor *hrd; struct hidraw_devinfo *hd; const char *devname; uint32_t size; hid_size_t actsize; int id, len; int error = 0; uint8_t reptype; DPRINTFN(2, "cmd=%lx\n", cmd); sc = dev->si_drv1; if (sc == NULL) return (EIO); hgd = (struct hidraw_gen_descriptor *)addr; #ifdef COMPAT_FREEBSD32 switch (cmd) { case HIDRAW_GET_REPORT_DESC32: case HIDRAW_GET_REPORT32: case HIDRAW_SET_REPORT_DESC32: case HIDRAW_SET_REPORT32: cmd = _IOC_NEWTYPE(cmd, struct hidraw_gen_descriptor); hgd32 = (struct hidraw_gen_descriptor32 *)addr; hgd = &local_hgd; PTRIN_CP(*hgd32, *hgd, hgd_data); CP(*hgd32, *hgd, hgd_lang_id); CP(*hgd32, *hgd, hgd_maxlen); CP(*hgd32, *hgd, hgd_actlen); CP(*hgd32, *hgd, hgd_offset); CP(*hgd32, *hgd, hgd_config_index); CP(*hgd32, *hgd, hgd_string_index); CP(*hgd32, *hgd, hgd_iface_index); CP(*hgd32, *hgd, hgd_altif_index); CP(*hgd32, *hgd, hgd_endpt_index); CP(*hgd32, *hgd, hgd_report_type); /* Don't copy reserved */ break; } #endif /* fixed-length ioctls handling */ switch (cmd) { case FIONBIO: /* All handled in the upper FS layer. */ return (0); case FIOASYNC: mtx_lock(&sc->sc_mtx); if (*(int *)addr) { if (sc->sc_async == NULL) { sc->sc_async = td->td_proc; DPRINTF("FIOASYNC %p\n", sc->sc_async); } else error = EBUSY; } else sc->sc_async = NULL; mtx_unlock(&sc->sc_mtx); return (error); /* XXX this is not the most general solution. */ case TIOCSPGRP: mtx_lock(&sc->sc_mtx); if (sc->sc_async == NULL) error = EINVAL; else if (*(int *)addr != sc->sc_async->p_pgid) error = EPERM; mtx_unlock(&sc->sc_mtx); return (error); case HIDRAW_GET_REPORT_DESC: if (sc->sc_rdesc->data == NULL || sc->sc_rdesc->len == 0) return (EOPNOTSUPP); mtx_lock(&sc->sc_mtx); sc->sc_state.uhid = true; mtx_unlock(&sc->sc_mtx); if (sc->sc_rdesc->len > hgd->hgd_maxlen) { size = hgd->hgd_maxlen; } else { size = sc->sc_rdesc->len; } hgd->hgd_actlen = size; #ifdef COMPAT_FREEBSD32 if (hgd32 != NULL) update_hgd32(hgd, hgd32); #endif if (hgd->hgd_data == NULL) return (0); /* descriptor length only */ return (copyout(sc->sc_rdesc->data, hgd->hgd_data, size)); case HIDRAW_SET_REPORT_DESC: if (!(sc->sc_fflags & FWRITE)) return (EPERM); /* check privileges */ error = priv_check(curthread, PRIV_DRIVER); if (error) return (error); /* Stop interrupts and clear input report buffer */ mtx_lock(&sc->sc_mtx); sc->sc_tail = sc->sc_head = 0; error = hidraw_lock_queue(sc, true); if (error == 0) sc->sc_state.quiet = true; mtx_unlock(&sc->sc_mtx); if (error != 0) return (error); buf = HIDRAW_LOCAL_ALLOC(local_buf, hgd->hgd_maxlen); error = copyin(hgd->hgd_data, buf, hgd->hgd_maxlen); if (error == 0) { bus_topo_lock(); error = hid_set_report_descr(sc->sc_dev, buf, hgd->hgd_maxlen); bus_topo_unlock(); } HIDRAW_LOCAL_FREE(local_buf, buf); /* Realloc hidraw input queue */ if (error == 0) sc->sc_q = realloc(sc->sc_q, sc->sc_rdesc->rdsize * HIDRAW_BUFFER_SIZE, M_DEVBUF, M_ZERO | M_WAITOK); /* Start interrupts again */ mtx_lock(&sc->sc_mtx); sc->sc_state.quiet = false; hidraw_unlock_queue(sc); mtx_unlock(&sc->sc_mtx); return (error); case HIDRAW_SET_IMMED: if (!(sc->sc_fflags & FREAD)) return (EPERM); if (*(int *)addr) { /* XXX should read into ibuf, but does it matter? */ size = sc->sc_rdesc->isize; buf = HIDRAW_LOCAL_ALLOC(local_buf, size); error = hid_get_report(sc->sc_dev, buf, size, NULL, HID_INPUT_REPORT, sc->sc_rdesc->iid); HIDRAW_LOCAL_FREE(local_buf, buf); if (error) return (EOPNOTSUPP); mtx_lock(&sc->sc_mtx); sc->sc_state.immed = true; mtx_unlock(&sc->sc_mtx); } else { mtx_lock(&sc->sc_mtx); sc->sc_state.immed = false; mtx_unlock(&sc->sc_mtx); } return (0); case HIDRAW_GET_REPORT: if (!(sc->sc_fflags & FREAD)) return (EPERM); switch (hgd->hgd_report_type) { case HID_INPUT_REPORT: size = sc->sc_rdesc->isize; id = sc->sc_rdesc->iid; break; case HID_OUTPUT_REPORT: size = sc->sc_rdesc->osize; id = sc->sc_rdesc->oid; break; case HID_FEATURE_REPORT: size = sc->sc_rdesc->fsize; id = sc->sc_rdesc->fid; break; default: return (EINVAL); } if (id != 0) { error = copyin(hgd->hgd_data, &id, 1); if (error != 0) return (error); } size = MIN(hgd->hgd_maxlen, size); buf = HIDRAW_LOCAL_ALLOC(local_buf, size); actsize = 0; error = hid_get_report(sc->sc_dev, buf, size, &actsize, hgd->hgd_report_type, id); if (!error) error = copyout(buf, hgd->hgd_data, actsize); HIDRAW_LOCAL_FREE(local_buf, buf); hgd->hgd_actlen = actsize; #ifdef COMPAT_FREEBSD32 if (hgd32 != NULL) update_hgd32(hgd, hgd32); #endif return (error); case HIDRAW_SET_REPORT: if (!(sc->sc_fflags & FWRITE)) return (EPERM); switch (hgd->hgd_report_type) { case HID_INPUT_REPORT: size = sc->sc_rdesc->isize; id = sc->sc_rdesc->iid; break; case HID_OUTPUT_REPORT: size = sc->sc_rdesc->osize; id = sc->sc_rdesc->oid; break; case HID_FEATURE_REPORT: size = sc->sc_rdesc->fsize; id = sc->sc_rdesc->fid; break; default: return (EINVAL); } size = MIN(hgd->hgd_maxlen, size); buf = HIDRAW_LOCAL_ALLOC(local_buf, size); error = copyin(hgd->hgd_data, buf, size); if (error == 0) { if (id != 0) id = *(uint8_t *)buf; error = hid_set_report(sc->sc_dev, buf, size, hgd->hgd_report_type, id); } HIDRAW_LOCAL_FREE(local_buf, buf); return (error); case HIDRAW_GET_REPORT_ID: *(int *)addr = 0; /* XXX: we only support reportid 0? */ return (0); case HIDRAW_GET_DEVICEINFO: hdi = (struct hidraw_device_info *)addr; bzero(hdi, sizeof(struct hidraw_device_info)); hdi->hdi_product = sc->sc_hw->idProduct; hdi->hdi_vendor = sc->sc_hw->idVendor; hdi->hdi_version = sc->sc_hw->idVersion; hdi->hdi_bustype = sc->sc_hw->idBus; strlcpy(hdi->hdi_name, sc->sc_hw->name, sizeof(hdi->hdi_name)); strlcpy(hdi->hdi_phys, device_get_nameunit(sc->sc_dev), sizeof(hdi->hdi_phys)); strlcpy(hdi->hdi_uniq, sc->sc_hw->serial, sizeof(hdi->hdi_uniq)); snprintf(hdi->hdi_release, sizeof(hdi->hdi_release), "%x.%02x", sc->sc_hw->idVersion >> 8, sc->sc_hw->idVersion & 0xff); return(0); case HIDIOCGRDESCSIZE: *(int *)addr = sc->sc_hw->rdescsize; return (0); case HIDIOCGRDESC: hrd = *(struct hidraw_report_descriptor **)addr; error = copyin(&hrd->size, &size, sizeof(uint32_t)); if (error) return (error); /* * HID_MAX_DESCRIPTOR_SIZE-1 is a limit of report descriptor * size in current Linux implementation. */ if (size >= HID_MAX_DESCRIPTOR_SIZE) return (EINVAL); mtx_lock(&sc->sc_mtx); sc->sc_state.uhid = false; mtx_unlock(&sc->sc_mtx); buf = HIDRAW_LOCAL_ALLOC(local_buf, size); error = hid_get_rdesc(sc->sc_dev, buf, size); if (error == 0) { size = MIN(size, sc->sc_rdesc->len); error = copyout(buf, hrd->value, size); } HIDRAW_LOCAL_FREE(local_buf, buf); return (error); case HIDIOCGRAWINFO: hd = (struct hidraw_devinfo *)addr; hd->bustype = sc->sc_hw->idBus; hd->vendor = sc->sc_hw->idVendor; hd->product = sc->sc_hw->idProduct; return (0); } /* variable-length ioctls handling */ len = IOCPARM_LEN(cmd); switch (IOCBASECMD(cmd)) { case HIDIOCGRAWNAME(0): strlcpy(addr, sc->sc_hw->name, len); td->td_retval[0] = min(strlen(sc->sc_hw->name) + 1, len); return (0); case HIDIOCGRAWPHYS(0): devname = device_get_nameunit(sc->sc_dev); strlcpy(addr, devname, len); td->td_retval[0] = min(strlen(devname) + 1, len); return (0); case HIDIOCSFEATURE(0): case HIDIOCSINPUT(0): case HIDIOCSOUTPUT(0): if (!(sc->sc_fflags & FWRITE)) return (EPERM); if (len < 2) return (EINVAL); id = *(uint8_t *)addr; if (id == 0) { addr = (uint8_t *)addr + 1; len--; } switch (IOCBASECMD(cmd)) { case HIDIOCSFEATURE(0): reptype = HID_FEATURE_REPORT; break; case HIDIOCSINPUT(0): reptype = HID_INPUT_REPORT; break; case HIDIOCSOUTPUT(0): reptype = HID_OUTPUT_REPORT; break; default: panic("Invalid report type"); } error = hid_set_report(sc->sc_dev, addr, len, reptype, id); if (error == 0) td->td_retval[0] = IOCPARM_LEN(cmd); return (error); case HIDIOCGFEATURE(0): case HIDIOCGINPUT(0): case HIDIOCGOUTPUT(0): if (!(sc->sc_fflags & FREAD)) return (EPERM); if (len < 2) return (EINVAL); id = *(uint8_t *)addr; if (id == 0) { addr = (uint8_t *)addr + 1; len--; } switch (IOCBASECMD(cmd)) { case HIDIOCGFEATURE(0): reptype = HID_FEATURE_REPORT; break; case HIDIOCGINPUT(0): reptype = HID_INPUT_REPORT; break; case HIDIOCGOUTPUT(0): reptype = HID_OUTPUT_REPORT; break; default: panic("Invalid report type"); } error = hid_get_report(sc->sc_dev, addr, len, &actsize, reptype, id); if (error == 0) { if (id == 0) actsize++; td->td_retval[0] = actsize; } return (error); case HIDIOCGRAWUNIQ(0): strlcpy(addr, sc->sc_hw->serial, len); td->td_retval[0] = min(strlen(sc->sc_hw->serial) + 1, len); return (0); } return (EINVAL); } static int hidraw_poll(struct cdev *dev, int events, struct thread *td) { struct hidraw_softc *sc; int revents = 0; sc = dev->si_drv1; if (sc == NULL) return (POLLHUP); if (events & (POLLOUT | POLLWRNORM) && (sc->sc_fflags & FWRITE)) revents |= events & (POLLOUT | POLLWRNORM); if (events & (POLLIN | POLLRDNORM) && (sc->sc_fflags & FREAD)) { mtx_lock(&sc->sc_mtx); if (sc->sc_head != sc->sc_tail) revents |= events & (POLLIN | POLLRDNORM); else { sc->sc_state.sel = true; selrecord(td, &sc->sc_rsel); } mtx_unlock(&sc->sc_mtx); } return (revents); } static int hidraw_kqfilter(struct cdev *dev, struct knote *kn) { struct hidraw_softc *sc; sc = dev->si_drv1; if (sc == NULL) return (ENXIO); switch(kn->kn_filter) { case EVFILT_READ: if (sc->sc_fflags & FREAD) { kn->kn_fop = &hidraw_filterops_read; break; } /* FALLTHROUGH */ default: return(EINVAL); } kn->kn_hook = sc; knlist_add(&sc->sc_rsel.si_note, kn, 0); return (0); } static int hidraw_kqread(struct knote *kn, long hint) { struct hidraw_softc *sc; int ret; sc = kn->kn_hook; mtx_assert(&sc->sc_mtx, MA_OWNED); if (sc->dev->si_drv1 == NULL) { kn->kn_flags |= EV_EOF; ret = 1; } else ret = (sc->sc_head != sc->sc_tail) ? 1 : 0; return (ret); } static void hidraw_kqdetach(struct knote *kn) { struct hidraw_softc *sc; sc = kn->kn_hook; knlist_remove(&sc->sc_rsel.si_note, kn, 0); } static void hidraw_notify(struct hidraw_softc *sc) { mtx_assert(&sc->sc_mtx, MA_OWNED); if (sc->sc_state.aslp) { sc->sc_state.aslp = false; DPRINTFN(5, "waking %p\n", &sc->sc_q); wakeup(&sc->sc_q); } if (sc->sc_state.sel) { sc->sc_state.sel = false; selwakeuppri(&sc->sc_rsel, PZERO); } if (sc->sc_async != NULL) { DPRINTFN(3, "sending SIGIO %p\n", sc->sc_async); PROC_LOCK(sc->sc_async); kern_psignal(sc->sc_async, SIGIO); PROC_UNLOCK(sc->sc_async); } KNOTE_LOCKED(&sc->sc_rsel.si_note, 0); } static device_method_t hidraw_methods[] = { /* Device interface */ DEVMETHOD(device_identify, hidraw_identify), DEVMETHOD(device_probe, hidraw_probe), DEVMETHOD(device_attach, hidraw_attach), DEVMETHOD(device_detach, hidraw_detach), DEVMETHOD_END }; static driver_t hidraw_driver = { HIDRAW_NAME, hidraw_methods, sizeof(struct hidraw_softc) }; DRIVER_MODULE(hidraw, hidbus, hidraw_driver, NULL, NULL); MODULE_DEPEND(hidraw, hidbus, 1, 1, 1); MODULE_DEPEND(hidraw, hid, 1, 1, 1); MODULE_VERSION(hidraw, 1); diff --git a/sys/dev/hid/u2f.c b/sys/dev/hid/u2f.c index 4232322c61df..e1f696d72f01 100644 --- a/sys/dev/hid/u2f.c +++ b/sys/dev/hid/u2f.c @@ -1,604 +1,604 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022-2023 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "opt_hid.h" #include #ifdef COMPAT_FREEBSD32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR u2f_debug #include #include #include #include #ifdef HID_DEBUG static int u2f_debug = 0; static SYSCTL_NODE(_hw_hid, OID_AUTO, u2f, CTLFLAG_RW, 0, "FIDO/U2F authenticator"); SYSCTL_INT(_hw_hid_u2f, OID_AUTO, debug, CTLFLAG_RWTUN, &u2f_debug, 0, "Debug level"); #endif #define U2F_MAX_REPORT_SIZE 64 /* A match on these entries will load u2f */ static const struct hid_device_id u2f_devs[] = { { HID_BUS(BUS_USB), HID_TLC(HUP_FIDO, HUF_U2FHID) }, }; struct u2f_softc { device_t sc_dev; /* base device */ struct cdev *dev; struct mtx sc_mtx; /* hidbus private mutex */ struct task sc_kqtask; /* kqueue task */ void *sc_rdesc; hid_size_t sc_rdesc_size; hid_size_t sc_isize; hid_size_t sc_osize; struct selinfo sc_rsel; struct { /* driver state */ bool open:1; /* device is open */ bool aslp:1; /* waiting for device data in read() */ bool sel:1; /* waiting for device data in poll() */ bool data:1; /* input report is stored in sc_buf */ int reserved:28; } sc_state; int sc_fflags; /* access mode for open lifetime */ uint8_t sc_buf[U2F_MAX_REPORT_SIZE]; }; static d_open_t u2f_open; static d_read_t u2f_read; static d_write_t u2f_write; static d_ioctl_t u2f_ioctl; static d_poll_t u2f_poll; static d_kqfilter_t u2f_kqfilter; static d_priv_dtor_t u2f_dtor; static struct cdevsw u2f_cdevsw = { .d_version = D_VERSION, .d_open = u2f_open, .d_read = u2f_read, .d_write = u2f_write, .d_ioctl = u2f_ioctl, .d_poll = u2f_poll, .d_kqfilter = u2f_kqfilter, .d_name = "u2f", }; static hid_intr_t u2f_intr; static device_probe_t u2f_probe; static device_attach_t u2f_attach; static device_detach_t u2f_detach; static void u2f_kqtask(void *context, int pending); static int u2f_kqread(struct knote *, long); static void u2f_kqdetach(struct knote *); static void u2f_notify(struct u2f_softc *); static struct filterops u2f_filterops_read = { .f_isfd = 1, .f_detach = u2f_kqdetach, .f_event = u2f_kqread, - .f_copy = knote_triv_copy, + .f_copy = knote_triv_copy, }; static int u2f_probe(device_t dev) { int error; error = HIDBUS_LOOKUP_DRIVER_INFO(dev, u2f_devs); if (error != 0) return (error); hidbus_set_desc(dev, "Authenticator"); return (BUS_PROBE_GENERIC); } static int u2f_attach(device_t dev) { struct u2f_softc *sc = device_get_softc(dev); struct hid_device_info *hw = __DECONST(struct hid_device_info *, hid_get_device_info(dev)); struct make_dev_args mda; int error; sc->sc_dev = dev; error = hid_get_report_descr(dev, &sc->sc_rdesc, &sc->sc_rdesc_size); if (error != 0) return (ENXIO); sc->sc_isize = hid_report_size_max(sc->sc_rdesc, sc->sc_rdesc_size, hid_input, NULL); if (sc->sc_isize > U2F_MAX_REPORT_SIZE) { device_printf(dev, "Input report size too large. Truncate.\n"); sc->sc_isize = U2F_MAX_REPORT_SIZE; } sc->sc_osize = hid_report_size_max(sc->sc_rdesc, sc->sc_rdesc_size, hid_output, NULL); if (sc->sc_osize > U2F_MAX_REPORT_SIZE) { device_printf(dev, "Output report size too large. Truncate.\n"); sc->sc_osize = U2F_MAX_REPORT_SIZE; } mtx_init(&sc->sc_mtx, "u2f lock", NULL, MTX_DEF); knlist_init_mtx(&sc->sc_rsel.si_note, &sc->sc_mtx); TASK_INIT(&sc->sc_kqtask, 0, u2f_kqtask, sc); make_dev_args_init(&mda); mda.mda_flags = MAKEDEV_WAITOK; mda.mda_devsw = &u2f_cdevsw; mda.mda_uid = UID_ROOT; mda.mda_gid = GID_U2F; mda.mda_mode = 0660; mda.mda_si_drv1 = sc; error = make_dev_s(&mda, &sc->dev, "u2f/%d", device_get_unit(dev)); if (error) { device_printf(dev, "Can not create character device\n"); u2f_detach(dev); return (error); } #ifndef U2F_DROP_UHID_ALIAS (void)make_dev_alias(sc->dev, "uhid%d", device_get_unit(dev)); #endif hid_add_dynamic_quirk(hw, HQ_NO_READAHEAD); hidbus_set_lock(dev, &sc->sc_mtx); hidbus_set_intr(dev, u2f_intr, sc); return (0); } static int u2f_detach(device_t dev) { struct u2f_softc *sc = device_get_softc(dev); DPRINTF("sc=%p\n", sc); if (sc->dev != NULL) { mtx_lock(&sc->sc_mtx); sc->dev->si_drv1 = NULL; /* Wake everyone */ u2f_notify(sc); mtx_unlock(&sc->sc_mtx); destroy_dev(sc->dev); } taskqueue_drain(taskqueue_thread, &sc->sc_kqtask); hid_intr_stop(sc->sc_dev); knlist_clear(&sc->sc_rsel.si_note, 0); knlist_destroy(&sc->sc_rsel.si_note); seldrain(&sc->sc_rsel); mtx_destroy(&sc->sc_mtx); return (0); } void u2f_intr(void *context, void *buf, hid_size_t len) { struct u2f_softc *sc = context; mtx_assert(&sc->sc_mtx, MA_OWNED); DPRINTFN(5, "len=%d\n", len); DPRINTFN(5, "data = %*D\n", len, buf, " "); if (sc->sc_state.data) return; if (len > sc->sc_isize) len = sc->sc_isize; bcopy(buf, sc->sc_buf, len); /* Make sure we don't process old data */ if (len < sc->sc_isize) bzero(sc->sc_buf + len, sc->sc_isize - len); sc->sc_state.data = true; u2f_notify(sc); } static int u2f_open(struct cdev *dev, int flag, int mode, struct thread *td) { struct u2f_softc *sc = dev->si_drv1; int error; if (sc == NULL) return (ENXIO); DPRINTF("sc=%p\n", sc); mtx_lock(&sc->sc_mtx); if (sc->sc_state.open) { mtx_unlock(&sc->sc_mtx); return (EBUSY); } sc->sc_state.open = true; mtx_unlock(&sc->sc_mtx); error = devfs_set_cdevpriv(sc, u2f_dtor); if (error != 0) { mtx_lock(&sc->sc_mtx); sc->sc_state.open = false; mtx_unlock(&sc->sc_mtx); return (error); } /* Set up interrupt pipe. */ sc->sc_state.data = false; sc->sc_fflags = flag; return (0); } static void u2f_dtor(void *data) { struct u2f_softc *sc = data; #ifdef NOT_YET /* Disable interrupts. */ hid_intr_stop(sc->sc_dev); #endif mtx_lock(&sc->sc_mtx); sc->sc_state.open = false; mtx_unlock(&sc->sc_mtx); } static int u2f_read(struct cdev *dev, struct uio *uio, int flag) { uint8_t buf[U2F_MAX_REPORT_SIZE]; struct u2f_softc *sc = dev->si_drv1; size_t length = 0; int error; DPRINTFN(1, "\n"); if (sc == NULL) return (EIO); if (!sc->sc_state.data) hid_intr_start(sc->sc_dev); mtx_lock(&sc->sc_mtx); if (dev->si_drv1 == NULL) { error = EIO; goto exit; } while (!sc->sc_state.data) { if (flag & O_NONBLOCK) { error = EWOULDBLOCK; goto exit; } sc->sc_state.aslp = true; DPRINTFN(5, "sleep on %p\n", &sc->sc_buf); error = mtx_sleep(&sc->sc_buf, &sc->sc_mtx, PZERO | PCATCH, "u2frd", 0); DPRINTFN(5, "woke, error=%d\n", error); if (dev->si_drv1 == NULL) error = EIO; if (error) { sc->sc_state.aslp = false; goto exit; } } if (sc->sc_state.data && uio->uio_resid > 0) { length = min(uio->uio_resid, sc->sc_isize); memcpy(buf, sc->sc_buf, length); sc->sc_state.data = false; } exit: mtx_unlock(&sc->sc_mtx); if (length != 0) { /* Copy the data to the user process. */ DPRINTFN(5, "got %lu chars\n", (u_long)length); error = uiomove(buf, length, uio); } return (error); } static int u2f_write(struct cdev *dev, struct uio *uio, int flag) { uint8_t buf[U2F_MAX_REPORT_SIZE]; struct u2f_softc *sc = dev->si_drv1; int error; DPRINTFN(1, "\n"); if (sc == NULL) return (EIO); if (uio->uio_resid != sc->sc_osize) return (EINVAL); error = uiomove(buf, uio->uio_resid, uio); if (error == 0) error = hid_write(sc->sc_dev, buf, sc->sc_osize); return (error); } #ifdef COMPAT_FREEBSD32 static void update_ugd32(const struct usb_gen_descriptor *ugd, struct usb_gen_descriptor32 *ugd32) { /* Don't update hgd_data pointer */ CP(*ugd, *ugd32, ugd_lang_id); CP(*ugd, *ugd32, ugd_maxlen); CP(*ugd, *ugd32, ugd_actlen); CP(*ugd, *ugd32, ugd_offset); CP(*ugd, *ugd32, ugd_config_index); CP(*ugd, *ugd32, ugd_string_index); CP(*ugd, *ugd32, ugd_iface_index); CP(*ugd, *ugd32, ugd_altif_index); CP(*ugd, *ugd32, ugd_endpt_index); CP(*ugd, *ugd32, ugd_report_type); /* Don't update reserved */ } #endif static int u2f_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { #ifdef COMPAT_FREEBSD32 struct usb_gen_descriptor local_ugd; struct usb_gen_descriptor32 *ugd32 = NULL; #endif struct u2f_softc *sc = dev->si_drv1; struct usb_gen_descriptor *ugd = (struct usb_gen_descriptor *)addr; uint32_t size; DPRINTFN(2, "cmd=%lx\n", cmd); if (sc == NULL) return (EIO); #ifdef COMPAT_FREEBSD32 switch (cmd) { case USB_GET_REPORT_DESC32: cmd = _IOC_NEWTYPE(cmd, struct usb_gen_descriptor); ugd32 = (struct usb_gen_descriptor32 *)addr; ugd = &local_ugd; PTRIN_CP(*ugd32, *ugd, ugd_data); CP(*ugd32, *ugd, ugd_lang_id); CP(*ugd32, *ugd, ugd_maxlen); CP(*ugd32, *ugd, ugd_actlen); CP(*ugd32, *ugd, ugd_offset); CP(*ugd32, *ugd, ugd_config_index); CP(*ugd32, *ugd, ugd_string_index); CP(*ugd32, *ugd, ugd_iface_index); CP(*ugd32, *ugd, ugd_altif_index); CP(*ugd32, *ugd, ugd_endpt_index); CP(*ugd32, *ugd, ugd_report_type); /* Don't copy reserved */ break; } #endif /* fixed-length ioctls handling */ switch (cmd) { case FIONBIO: /* All handled in the upper FS layer. */ return (0); case USB_GET_REPORT_DESC: size = MIN(sc->sc_rdesc_size, ugd->ugd_maxlen); ugd->ugd_actlen = size; #ifdef COMPAT_FREEBSD32 if (ugd32 != NULL) update_ugd32(ugd, ugd32); #endif if (ugd->ugd_data == NULL) return (0); /* descriptor length only */ return (copyout(sc->sc_rdesc, ugd->ugd_data, size)); case USB_GET_DEVICEINFO: return(hid_ioctl( sc->sc_dev, USB_GET_DEVICEINFO, (uintptr_t)addr)); } return (EINVAL); } static int u2f_poll(struct cdev *dev, int events, struct thread *td) { struct u2f_softc *sc = dev->si_drv1; int revents = 0; bool start_intr = false; if (sc == NULL) return (POLLHUP); if (events & (POLLOUT | POLLWRNORM) && (sc->sc_fflags & FWRITE)) revents |= events & (POLLOUT | POLLWRNORM); if (events & (POLLIN | POLLRDNORM) && (sc->sc_fflags & FREAD)) { mtx_lock(&sc->sc_mtx); if (sc->sc_state.data) revents |= events & (POLLIN | POLLRDNORM); else { sc->sc_state.sel = true; start_intr = true; selrecord(td, &sc->sc_rsel); } mtx_unlock(&sc->sc_mtx); if (start_intr) hid_intr_start(sc->sc_dev); } return (revents); } static int u2f_kqfilter(struct cdev *dev, struct knote *kn) { struct u2f_softc *sc = dev->si_drv1; if (sc == NULL) return (ENXIO); switch(kn->kn_filter) { case EVFILT_READ: if (sc->sc_fflags & FREAD) { kn->kn_fop = &u2f_filterops_read; break; } /* FALLTHROUGH */ default: return(EINVAL); } kn->kn_hook = sc; knlist_add(&sc->sc_rsel.si_note, kn, 0); return (0); } static void u2f_kqtask(void *context, int pending) { struct u2f_softc *sc = context; hid_intr_start(sc->sc_dev); } static int u2f_kqread(struct knote *kn, long hint) { struct u2f_softc *sc = kn->kn_hook; int ret; mtx_assert(&sc->sc_mtx, MA_OWNED); if (sc->dev->si_drv1 == NULL) { kn->kn_flags |= EV_EOF; ret = 1; } else { ret = sc->sc_state.data ? 1 : 0; if (!sc->sc_state.data) taskqueue_enqueue(taskqueue_thread, &sc->sc_kqtask); } return (ret); } static void u2f_kqdetach(struct knote *kn) { struct u2f_softc *sc = kn->kn_hook; knlist_remove(&sc->sc_rsel.si_note, kn, 0); } static void u2f_notify(struct u2f_softc *sc) { mtx_assert(&sc->sc_mtx, MA_OWNED); if (sc->sc_state.aslp) { sc->sc_state.aslp = false; DPRINTFN(5, "waking %p\n", &sc->sc_buf); wakeup(&sc->sc_buf); } if (sc->sc_state.sel) { sc->sc_state.sel = false; selwakeuppri(&sc->sc_rsel, PZERO); } KNOTE_LOCKED(&sc->sc_rsel.si_note, 0); } static device_method_t u2f_methods[] = { /* Device interface */ DEVMETHOD(device_probe, u2f_probe), DEVMETHOD(device_attach, u2f_attach), DEVMETHOD(device_detach, u2f_detach), DEVMETHOD_END }; static driver_t u2f_driver = { #ifdef U2F_DROP_UHID_ALIAS "uf2", #else "uhid", #endif u2f_methods, sizeof(struct u2f_softc) }; DRIVER_MODULE(u2f, hidbus, u2f_driver, NULL, NULL); MODULE_DEPEND(u2f, hidbus, 1, 1, 1); MODULE_DEPEND(u2f, hid, 1, 1, 1); MODULE_VERSION(u2f, 1); HID_PNP_INFO(u2f_devs); diff --git a/sys/dev/null/null.c b/sys/dev/null/null.c index c4f138b102c7..b5725de30bef 100644 --- a/sys/dev/null/null.c +++ b/sys/dev/null/null.c @@ -1,251 +1,251 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Mark R. V. Murray & Jeroen C. van Gelderen * Copyright (c) 2001-2004 Mark R. V. Murray * Copyright (c) 2014 Eitan Adler * Copyright (c) 2025 Pietro Cerutti * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* For use with destroy_dev(9). */ static struct cdev *full_dev; static struct cdev *null_dev; static struct cdev *zero_dev; static d_write_t full_write; static d_write_t null_write; static d_ioctl_t null_ioctl; static d_ioctl_t zero_ioctl; static d_read_t zero_read; static d_kqfilter_t kqfilter; static int one_ev(struct knote *kn, long hint); static int zero_ev(struct knote *kn, long hint); static const struct filterops one_fop = { .f_isfd = 1, .f_event = one_ev, - .f_copy = knote_triv_copy, + .f_copy = knote_triv_copy, }; static const struct filterops zero_fop = { .f_isfd = 1, .f_event = zero_ev, - .f_copy = knote_triv_copy, + .f_copy = knote_triv_copy, }; static struct cdevsw full_cdevsw = { .d_version = D_VERSION, .d_read = zero_read, .d_write = full_write, .d_ioctl = zero_ioctl, .d_kqfilter = kqfilter, .d_name = "full", }; static struct cdevsw null_cdevsw = { .d_version = D_VERSION, .d_read = (d_read_t *)nullop, .d_write = null_write, .d_ioctl = null_ioctl, .d_kqfilter = kqfilter, .d_name = "null", }; static struct cdevsw zero_cdevsw = { .d_version = D_VERSION, .d_read = zero_read, .d_write = null_write, .d_ioctl = zero_ioctl, .d_kqfilter = kqfilter, .d_name = "zero", .d_flags = D_MMAP_ANON, }; /* ARGSUSED */ static int full_write(struct cdev *dev __unused, struct uio *uio __unused, int flags __unused) { return (ENOSPC); } /* ARGSUSED */ static int null_write(struct cdev *dev __unused, struct uio *uio, int flags __unused) { uio->uio_resid = 0; return (0); } /* ARGSUSED */ static int null_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data __unused, int flags __unused, struct thread *td) { struct diocskerneldump_arg kda; int error; error = 0; switch (cmd) { case DIOCSKERNELDUMP: bzero(&kda, sizeof(kda)); kda.kda_index = KDA_REMOVE_ALL; error = dumper_remove(NULL, &kda); break; case FIONBIO: break; case FIOASYNC: if (*(int *)data != 0) error = EINVAL; break; default: error = ENOIOCTL; } return (error); } /* ARGSUSED */ static int zero_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data __unused, int flags __unused, struct thread *td) { int error; error = 0; switch (cmd) { case FIONBIO: break; case FIOASYNC: if (*(int *)data != 0) error = EINVAL; break; default: error = ENOIOCTL; } return (error); } /* ARGSUSED */ static int zero_read(struct cdev *dev __unused, struct uio *uio, int flags __unused) { void *zbuf; ssize_t len; int error = 0; KASSERT(uio->uio_rw == UIO_READ, ("Can't be in %s for write", __func__)); zbuf = __DECONST(void *, zero_region); while (uio->uio_resid > 0 && error == 0) { len = uio->uio_resid; if (len > ZERO_REGION_SIZE) len = ZERO_REGION_SIZE; error = uiomove(zbuf, len, uio); } return (error); } /* ARGSUSED */ static int null_modevent(module_t mod __unused, int type, void *data __unused) { switch(type) { case MOD_LOAD: if (bootverbose) printf("null: \n"); full_dev = make_dev_credf(MAKEDEV_ETERNAL_KLD, &full_cdevsw, 0, NULL, UID_ROOT, GID_WHEEL, 0666, "full"); null_dev = make_dev_credf(MAKEDEV_ETERNAL_KLD, &null_cdevsw, 0, NULL, UID_ROOT, GID_WHEEL, 0666, "null"); zero_dev = make_dev_credf(MAKEDEV_ETERNAL_KLD, &zero_cdevsw, 0, NULL, UID_ROOT, GID_WHEEL, 0666, "zero"); break; case MOD_UNLOAD: destroy_dev(full_dev); destroy_dev(null_dev); destroy_dev(zero_dev); break; case MOD_SHUTDOWN: break; default: return (EOPNOTSUPP); } return (0); } static int one_ev(struct knote *kn, long hint) { return (1); } static int zero_ev(struct knote *kn, long hint) { return (0); } static int kqfilter(struct cdev *dev, struct knote *kn) { switch (kn->kn_filter) { case EVFILT_READ: kn->kn_fop = dev->si_devsw == &null_cdevsw ? &zero_fop : &one_fop; return (0); case EVFILT_WRITE: kn->kn_fop = dev->si_devsw == &full_cdevsw ? &zero_fop : &one_fop; return (0); default: return (EOPNOTSUPP); } } DEV_MODULE(null, null_modevent, NULL); MODULE_VERSION(null, 1);