freebsd-dev/sys/dev/gpio/gpioc.c

1066 lines
27 KiB
C
Raw Normal View History

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2009 Oleksandr Tymoshenko <gonzo@freebsd.org>
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/gpio.h>
#include <sys/ioccom.h>
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
2020-12-12 18:34:15 +00:00
#include <sys/filio.h>
#include <sys/fcntl.h>
#include <sys/sigio.h>
#include <sys/signalvar.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
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
2020-12-12 18:34:15 +00:00
#include <sys/uio.h>
#include <sys/poll.h>
#include <sys/selinfo.h>
#include <sys/module.h>
#include <dev/gpio/gpiobusvar.h>
#include "gpio_if.h"
#include "gpiobus_if.h"
#undef GPIOC_DEBUG
#ifdef GPIOC_DEBUG
#define dprintf printf
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
2020-12-12 18:34:15 +00:00
#define ddevice_printf device_printf
#else
#define dprintf(x, arg...)
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
2020-12-12 18:34:15 +00:00
#define ddevice_printf(dev, x, arg...)
#endif
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
2020-12-12 18:34:15 +00:00
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");
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
2020-12-12 18:34:15 +00:00
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;
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
2020-12-12 18:34:15 +00:00
static d_poll_t gpioc_poll;
static d_kqfilter_t gpioc_kqfilter;
static struct cdevsw gpioc_cdevsw = {
.d_version = D_VERSION,
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
2020-12-12 18:34:15 +00:00
.d_open = gpioc_open,
.d_read = gpioc_read,
.d_ioctl = gpioc_ioctl,
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
2020-12-12 18:34:15 +00:00
.d_poll = gpioc_poll,
.d_kqfilter = gpioc_kqfilter,
.d_name = "gpioc",
};
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
2020-12-12 18:34:15 +00:00
static struct filterops gpioc_read_filterops = {
.f_isfd = true,
.f_attach = NULL,
.f_detach = gpioc_kqdetach,
.f_event = gpioc_kqread,
.f_touch = NULL
};
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
2020-12-12 18:34:15 +00:00
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");
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);
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
2020-12-12 18:34:15 +00:00
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++) {
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
2020-12-12 18:34:15 +00:00
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) {
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
2020-12-12 18:34:15 +00:00
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++) {
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
2020-12-12 18:34:15 +00:00
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);
}
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
2020-12-12 18:34:15 +00:00
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)
{
device_t bus;
int max_pin, res;
struct gpioc_softc *sc = cdev->si_drv1;
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
2020-12-12 18:34:15 +00:00
struct gpioc_cdevpriv *priv;
struct gpio_pin pin;
struct gpio_req req;
Add gpio methods to read/write/configure up to 32 pins simultaneously. Sometimes it is necessary to combine several gpio pins into an ad-hoc bus and manipulate the pins as a group. In such cases manipulating the pins individualy is not an option, because the value on the "bus" assumes potentially-invalid intermediate values as each pin is changed in turn. Note that the "bus" may be something as simple as a bi-color LED where changing colors requires changing both gpio pins at once, or something as complex as a bitbanged multiplexed address/data bus connected to a microcontroller. In addition to the absolute requirement of simultaneously changing the output values of driven pins, a desirable feature of these new methods is to provide a higher-performance mechanism for reading and writing multiple pins, especially from userland where pin-at-a-time access incurs a noticible syscall time penalty. These new interfaces are NOT intended to abstract away all the ugly details of how gpio is implemented on any given platform. In fact, to use these properly you absolutely must know something about how the gpio hardware is organized. Typically there are "banks" of gpio pins controlled by registers which group several pins together. A bank may be as small as 2 pins or as big as "all the pins on the device, hundreds of them." In the latter case, a driver might support this interface by allowing access to any 32 adjacent pins within the overall collection. Or, more likely, any 32 adjacent pins starting at any multiple of 32. Whatever the hardware restrictions may be, you would need to understand them to use this interface. In additional to defining the interfaces, two example implementations are included here, for imx5/6, and allwinner. These represent the two primary types of gpio hardware drivers. imx6 has multiple gpio devices, each implementing a single bank of 32 pins. Allwinner implements a single large gpio number space from 1-n pins, and the driver internally translates that linear number space to a bank+pin scheme based on how the pins are grouped into control registers. The allwinner implementation imposes the restriction that the first_pin argument to the new functions must always be pin 0 of a bank. Differential Revision: https://reviews.freebsd.org/D11810
2017-09-10 18:08:25 +00:00
struct gpio_access_32 *a32;
struct gpio_config_32 *c32;
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
2020-12-12 18:34:15 +00:00
struct gpio_event_config *evcfg;
uint32_t caps, intrflags;
bus = GPIO_GET_BUS(sc->sc_pdev);
if (bus == NULL)
return (EINVAL);
switch (cmd) {
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
2020-12-12 18:34:15 +00:00
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;
Add gpio methods to read/write/configure up to 32 pins simultaneously. Sometimes it is necessary to combine several gpio pins into an ad-hoc bus and manipulate the pins as a group. In such cases manipulating the pins individualy is not an option, because the value on the "bus" assumes potentially-invalid intermediate values as each pin is changed in turn. Note that the "bus" may be something as simple as a bi-color LED where changing colors requires changing both gpio pins at once, or something as complex as a bitbanged multiplexed address/data bus connected to a microcontroller. In addition to the absolute requirement of simultaneously changing the output values of driven pins, a desirable feature of these new methods is to provide a higher-performance mechanism for reading and writing multiple pins, especially from userland where pin-at-a-time access incurs a noticible syscall time penalty. These new interfaces are NOT intended to abstract away all the ugly details of how gpio is implemented on any given platform. In fact, to use these properly you absolutely must know something about how the gpio hardware is organized. Typically there are "banks" of gpio pins controlled by registers which group several pins together. A bank may be as small as 2 pins or as big as "all the pins on the device, hundreds of them." In the latter case, a driver might support this interface by allowing access to any 32 adjacent pins within the overall collection. Or, more likely, any 32 adjacent pins starting at any multiple of 32. Whatever the hardware restrictions may be, you would need to understand them to use this interface. In additional to defining the interfaces, two example implementations are included here, for imx5/6, and allwinner. These represent the two primary types of gpio hardware drivers. imx6 has multiple gpio devices, each implementing a single bank of 32 pins. Allwinner implements a single large gpio number space from 1-n pins, and the driver internally translates that linear number space to a bank+pin scheme based on how the pins are grouped into control registers. The allwinner implementation imposes the restriction that the first_pin argument to the new functions must always be pin 0 of a bank. Differential Revision: https://reviews.freebsd.org/D11810
2017-09-10 18:08:25 +00:00
break;
default:
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
2020-12-12 18:34:15 +00:00
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;
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
2020-12-12 18:34:15 +00:00
}
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);
}
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
2020-12-12 18:34:15 +00:00
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)
};
devclass_t gpioc_devclass;
DRIVER_MODULE(gpioc, gpio, gpioc_driver, gpioc_devclass, 0, 0);
MODULE_VERSION(gpioc, 1);