From owner-svn-src-all@freebsd.org Sat Dec 12 20:34:32 2020 Return-Path: Delivered-To: svn-src-all@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id C7F554C05B4 for ; Sat, 12 Dec 2020 20:34:32 +0000 (UTC) (envelope-from wlosh@bsdimp.com) Received: from mail-qv1-xf29.google.com (mail-qv1-xf29.google.com [IPv6:2607:f8b0:4864:20::f29]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (2048 bits) client-digest SHA256) (Client CN "smtp.gmail.com", Issuer "GTS CA 1O1" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4CtfX84KXhz3vfR for ; Sat, 12 Dec 2020 20:34:32 +0000 (UTC) (envelope-from wlosh@bsdimp.com) Received: by mail-qv1-xf29.google.com with SMTP id 4so5969894qvh.1 for ; Sat, 12 Dec 2020 12:34:32 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bsdimp-com.20150623.gappssmtp.com; s=20150623; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=mvgikb6l+qA6kxWzvO68ZKqFzaZtzww1z/jhBxTVcvM=; b=TU2Q88BrHySM2NWg1uJQnGTfeBTbWlJRCBt7MiYl5opOSo4BeRZz4TiLBibyLC5vVT sGKl5nLJaaIM7e60ihe4lnsLqGNXuY7Th+qRLGmm4+7tnFe/7oN/MQX/HaQQmczIFjh9 NonlInB7W3mKvDIFAqMorvEHWOFNSBj2kmPa4DuDNS8czL7adrTIgkYgFNb8FPqgbWP+ v4tOYUgS3Cc2kVsXMf3A0FKkaxyjoscLWGtv5kjmT0l/C6IIQ9hpoLYVG0laNY4eWUhd z5zAd/dnojFS2FLz/4GL+j6eIk95jHXTlL6pibclbsMKtkDrt5w+HzSX+e+CGgCug7tW mDGA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=mvgikb6l+qA6kxWzvO68ZKqFzaZtzww1z/jhBxTVcvM=; b=VFOFsKkUNBUKLJnSYbjQM7AntkC8jUqJzl95Ucm1YYPoYasR6ssd/fvodVmUSuwXO4 ch3G3U5fhwaM1j6T7NJtj8MofFd9xd32Vv7iHWJl+HOH/XRQTl5cLj50ay15zzZDQ/QJ hTAc1toQexxqMxfwgDxIWB/4hJR5rOHVUC6AZ8541e9A3PCoPKHqBs5jG6jqtXhn9IKa X6qeZJ3qMbpaG/IDfRBRP4WWXsEcG8sLbcDb6Hf5EJVCURGvo8rxOioQOqTWx4mjHX9t WpAM+EWluVHXh89Ip958SpGuQLMhs5UOvx8Nge7cFYZWHZam1GZQBitNQn+g8Q4qVSP1 i89g== X-Gm-Message-State: AOAM530I+beU67LUlFHdNUh7UEKzbe2UCJUA+Jc6AgRBhnBo6NJl6MEO Zridrz/O06qy0R1BK2jvAe9Wqj82JLYkg5lxYWq+Tg== X-Google-Smtp-Source: ABdhPJwTXOy2wSB4iVvDq3+ZcIgH3rQAaUP3vxDvefdY3GSxNyXZBMqf0D8O6mg2QhXAWGoGikX+UKrJc5AfUfkAdWk= X-Received: by 2002:a05:6214:8c9:: with SMTP id da9mr8593528qvb.29.1607805270973; Sat, 12 Dec 2020 12:34:30 -0800 (PST) MIME-Version: 1.0 References: <202012121834.0BCIYFAZ076662@repo.freebsd.org> In-Reply-To: <202012121834.0BCIYFAZ076662@repo.freebsd.org> From: Warner Losh Date: Sat, 12 Dec 2020 13:34:18 -0700 Message-ID: Subject: Re: svn commit: r368585 - in head: sys/dev/gpio sys/sys tools/test tools/test/gpioevents usr.sbin/gpioctl To: Ian Lepore Cc: src-committers , svn-src-all , svn-src-head@freebsd.org X-Rspamd-Queue-Id: 4CtfX84KXhz3vfR X-Spamd-Bar: ---- Authentication-Results: mx1.freebsd.org; none X-Spamd-Result: default: False [-4.00 / 15.00]; REPLY(-4.00)[] Content-Type: text/plain; charset="UTF-8" X-Content-Filtered-By: Mailman/MimeDel 2.1.34 X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sat, 12 Dec 2020 20:34:32 -0000 FWIW I generally approved the code, but didn't have the time to review it in detail. I got the impression people were at least generally happy from the review. Thanks so much for picking this up. Warner On Sat, Dec 12, 2020, 11:34 AM Ian Lepore wrote: > Author: ian > Date: Sat Dec 12 18:34:15 2020 > New Revision: 368585 > URL: https://svnweb.freebsd.org/changeset/base/368585 > > Log: > Provide userland notification of gpio pin changes ("userland gpio > interrupts"). > > This is an import of the Google Summer of Code 2018 project completed by > Christian Kramer (and, sadly, ignored by us for two years now). The > goals > stated for that project were: > > FreeBSD already has support for interrupts implemented in the GPIO > controller drivers of several SoCs, but there are no interfaces to > take > advantage of them out of user space yet. The goal of this work is to > implement such an interface by providing descriptors which integrate > with the common I/O system calls and multiplexing mechanisms. > > The initial imported code supports the following functionality: > > - A kernel driver that provides an interface to the user space; the > existing gpioc(4) driver was enhanced with this functionality. > - Implement support for the most common I/O system calls / multiplexing > mechanisms: > - read() Places the pin number on which the interrupt occurred in > the > buffer. Blocking and non-blocking behaviour supported. > - poll()/select() > - kqueue() > - signal driven I/O. Posting SIGIO when the O_ASYNC was set. > - Many-to-many relationship between pins and file descriptors. > - A file descriptor can monitor several GPIO pins. > - A GPIO pin can be monitored by multiple file descriptors. > - Integration with gpioctl and libgpio. > > I added some fixes (mostly to locking) and feature enhancements on top of > the original gsoc code. The feature ehancements allow the user to choose > between detailed and summary event reporting. Detailed reporting > provides > a record describing each pin change event. Summary reporting provides > the > time of the first and last change of each pin, and a count of how many > times > it changed state since the last read(2) call. Another enhancement allows > the recording of multiple state change events on multiple pins between > each > call to read(2) (the original code would track only a single event at a > time). > > The phabricator review for these changes timed out without approval, but > I > cite it below anyway, because the review contains a series of diffs that > show how I evolved the code from its original state in Christian's github > repo for the gsoc project to what is being commited here. (In effect, > the phab review extends the VC history back to the original code.) > > Submitted by: Christian Kramer > Obtained from: https://github.com/ckraemer/freebsd/tree/gsoc2018 > Differential Revision: https://reviews.freebsd.org/D27398 > > Added: > head/tools/test/gpioevents/ > head/tools/test/gpioevents/Makefile (contents, props changed) > head/tools/test/gpioevents/gpioevents.c (contents, props changed) > Modified: > head/sys/dev/gpio/gpiobus.c > head/sys/dev/gpio/gpioc.c > head/sys/sys/gpio.h > head/tools/test/README > head/usr.sbin/gpioctl/gpioctl.c > > Modified: head/sys/dev/gpio/gpiobus.c > > ============================================================================== > --- head/sys/dev/gpio/gpiobus.c Sat Dec 12 17:11:22 2020 (r368584) > +++ head/sys/dev/gpio/gpiobus.c Sat Dec 12 18:34:15 2020 (r368585) > @@ -143,6 +143,15 @@ gpio_check_flags(uint32_t caps, uint32_t flags) > /* Cannot mix pull-up/pull-down together. */ > if (flags & GPIO_PIN_PULLUP && flags & GPIO_PIN_PULLDOWN) > return (EINVAL); > + /* Cannot mix output and interrupt flags together */ > + if (flags & GPIO_PIN_OUTPUT && flags & GPIO_INTR_MASK) > + return (EINVAL); > + /* Only one interrupt flag can be defined at once */ > + if ((flags & GPIO_INTR_MASK) & ((flags & GPIO_INTR_MASK) - 1)) > + return (EINVAL); > + /* The interrupt attached flag cannot be set */ > + if (flags & GPIO_INTR_ATTACHED) > + return (EINVAL); > > return (0); > } > > Modified: head/sys/dev/gpio/gpioc.c > > ============================================================================== > --- head/sys/dev/gpio/gpioc.c Sat Dec 12 17:11:22 2020 (r368584) > +++ head/sys/dev/gpio/gpioc.c Sat Dec 12 18:34:15 2020 (r368585) > @@ -35,8 +35,15 @@ __FBSDID("$FreeBSD$"); > #include > #include > #include > +#include > +#include > +#include > +#include > #include > #include > +#include > +#include > +#include > #include > > #include > @@ -47,30 +54,510 @@ __FBSDID("$FreeBSD$"); > #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 > > -static int gpioc_probe(device_t dev); > -static int gpioc_attach(device_t dev); > -static int gpioc_detach(device_t dev); > +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", > }; > > -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; > +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, consistency_b; > + > + 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, consistency_b; > + > + 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"); > @@ -88,6 +575,23 @@ gpioc_attach(device_t 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; > @@ -96,7 +600,7 @@ gpioc_attach(device_t dev) > devargs.mda_si_drv1 = sc; > err = make_dev_s(&devargs, &sc->sc_ctl_dev, "gpioc%d", > sc->sc_unit); > if (err != 0) { > - printf("Failed to create gpioc%d", sc->sc_unit); > + device_printf(dev, "Failed to create gpioc%d", > sc->sc_unit); > return (ENXIO); > } > > @@ -112,12 +616,160 @@ gpioc_detach(device_t 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); > + 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; > + > + 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) > @@ -125,86 +777,268 @@ gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t > arg > 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; > - uint32_t caps; > + 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)); > + 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; > - 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; > - 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)); > + res = devfs_get_cdevpriv((void **)&priv); > + if (res) > break; > - case GPIOSETCONFIG: > - bcopy(arg, &pin, sizeof(pin)); > - dprintf("set config pin %d\n", pin.gp_pin); > - res = GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, > &caps); > - if (res == 0) > - res = gpio_check_flags(caps, pin.gp_flags); > - if (res == 0) > - res = GPIO_PIN_SETFLAGS(sc->sc_pdev, > pin.gp_pin, > - pin.gp_flags); > + 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; > - 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)); > + res = GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &caps); > + if (res != 0) > 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); > + res = gpio_check_flags(caps, pin.gp_flags); > + if (res != 0) > 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); > + 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 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); > + case GPIO_INTR_EDGE_RISING: > + case GPIO_INTR_EDGE_FALLING: > + case GPIO_INTR_EDGE_BOTH: > + if ((intrflags & caps) == 0) > + res = EOPNOTSUPP; > 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); > + default: > + res = EINVAL; > 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); > + } > + if (res != 0) > break; > - default: > - return (ENOTTY); > + 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)) { > > *** DIFF OUTPUT TRUNCATED AT 1000 LINES *** >