/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #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; /* gpioX 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; 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_pin_intr*, uint32_t); static int gpioc_release_pin_intr(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 struct filterops gpioc_read_filterops = { .f_isfd = true, .f_attach = NULL, .f_detach = gpioc_kqdetach, .f_event = gpioc_kqread, .f_touch = NULL }; 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_pin_intr *intr_conf, uint32_t flags) { int err; intr_conf->config_locked = true; mtx_unlock(&intr_conf->mtx); intr_conf->intr_res = gpio_alloc_intr_resource(intr_conf->pin->dev, &intr_conf->intr_rid, RF_ACTIVE, intr_conf->pin, flags); if (intr_conf->intr_res == NULL) { err = ENXIO; goto error_exit; } err = bus_setup_intr(intr_conf->pin->dev, intr_conf->intr_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, gpioc_interrupt_handler, intr_conf, &intr_conf->intr_cookie); if (err != 0) goto error_exit; intr_conf->pin->flags = flags; error_exit: mtx_lock(&intr_conf->mtx); intr_conf->config_locked = false; wakeup(&intr_conf->config_locked); return (err); } static int gpioc_release_pin_intr(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(intr_conf->pin->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(intr_conf->pin->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; } } intr_conf->pin->flags = 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->pin->flags; 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->pin->flags == 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->pin->flags == 0 && flags != 0) { /* * No interrupt is configured, but one is requested: Allocate * and setup interrupt on the according pin. */ res = gpioc_allocate_pin_intr(intr_conf, flags); if (res == 0) res = gpioc_attach_priv_pin(priv, intr_conf); if (res == EEXIST) res = 0; } else if (intr_conf->pin->flags == 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->pin->flags != 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(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(intr_conf); if (res == 0) res = gpioc_allocate_pin_intr(intr_conf, 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; struct gpioc_softc *sc; sbintime_t evtime; uint32_t pin_state; intr_conf = arg; sc = intr_conf->sc; /* Capture time and pin state first. */ evtime = sbinuptime(); if (intr_conf->pin->flags & GPIO_INTR_EDGE_BOTH) GPIO_PIN_GET(sc->sc_pdev, intr_conf->pin->pin, &pin_state); else if (intr_conf->pin->flags & 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); err = GPIO_PIN_MAX(sc->sc_pdev, &sc->sc_npins); sc->sc_npins++; /* Number of pins is one more than max pin number. */ if (err != 0) return (err); 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].pin = malloc(sizeof(struct gpiobus_pin), M_GPIOC, M_WAITOK | M_ZERO); sc->sc_pin_intr[i].sc = sc; sc->sc_pin_intr[i].pin->pin = i; sc->sc_pin_intr[i].pin->dev = sc->sc_pdev; 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); int err; 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); free(sc->sc_pin_intr[i].pin, M_GPIOC); } free(sc->sc_pin_intr, M_GPIOC); if ((err = bus_generic_detach(dev)) != 0) return (err); 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(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; priv = malloc(sizeof(*priv), M_GPIOC, M_WAITOK | M_ZERO); priv->sc = dev->si_drv1; priv->report_option = GPIO_EVENT_REPORT_DETAIL; err = devfs_set_cdevpriv(priv, gpioc_cdevpriv_dtor); if (err != 0) { gpioc_cdevpriv_dtor(priv); return (err); } mtx_init(&priv->mtx, "gpioc priv", NULL, MTX_DEF); knlist_init_mtx(&priv->selinfo.si_note, &priv->mtx); /* * 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 gpio_event_detail), M_GPIOC, M_WAITOK | M_ZERO); return (0); } 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) { device_t bus; 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; uint32_t caps, intrflags; bus = GPIO_GET_BUS(sc->sc_pdev); if (bus == NULL) return (EINVAL); switch (cmd) { case GPIOMAXPIN: max_pin = -1; res = GPIO_PIN_MAX(sc->sc_pdev, &max_pin); 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 = GPIO_PIN_GETFLAGS(sc->sc_pdev, pin.gp_pin, &pin.gp_flags); /* Fail early */ if (res) break; res = devfs_get_cdevpriv((void **)&priv); if (res) break; pin.gp_flags |= gpioc_get_intr_config(sc, priv, pin.gp_pin); GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &pin.gp_caps); GPIOBUS_PIN_GETNAME(bus, pin.gp_pin, pin.gp_name); 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 = GPIO_PIN_GETCAPS(sc->sc_pdev, 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 = GPIO_PIN_SETFLAGS(sc->sc_pdev, 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 = GPIO_PIN_GET(sc->sc_pdev, req.gp_pin, &req.gp_value); 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 = GPIO_PIN_SET(sc->sc_pdev, 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 = GPIO_PIN_TOGGLE(sc->sc_pdev, 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(bus, pin.gp_pin, pin.gp_name); break; case GPIOACCESS32: a32 = (struct gpio_access_32 *)arg; res = GPIO_PIN_ACCESS_32(sc->sc_pdev, a32->first_pin, a32->clear_pins, a32->change_pins, &a32->orig_pins); break; case GPIOCONFIG32: c32 = (struct gpio_config_32 *)arg; res = GPIO_PIN_CONFIG_32(sc->sc_pdev, 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 any pins have been configured, changes aren't allowed. */ if (!SLIST_EMPTY(&priv->pins)) { res = EINVAL; break; } if (evcfg->gp_report_type != GPIO_EVENT_REPORT_DETAIL && evcfg->gp_report_type != GPIO_EVENT_REPORT_SUMMARY) { res = EINVAL; break; } priv->report_option = evcfg->gp_report_type; /* Reallocate the events buffer if the user wants it bigger. */ if (priv->report_option == GPIO_EVENT_REPORT_DETAIL && priv->numevents < evcfg->gp_fifo_size) { free(priv->events, M_GPIOC); priv->numevents = evcfg->gp_fifo_size; priv->events = malloc(priv->numevents * sizeof(struct gpio_event_detail), M_GPIOC, M_WAITOK | M_ZERO); priv->evidx_head = priv->evidx_tail = 0; } 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(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD_END }; driver_t gpioc_driver = { "gpioc", gpioc_methods, sizeof(struct gpioc_softc) }; DRIVER_MODULE(gpioc, gpio, gpioc_driver, 0, 0); MODULE_VERSION(gpioc, 1);