Changeset View
Standalone View
./sys/dev/gpio/gpioc.c
Show All 29 Lines | |||||
__FBSDID("$FreeBSD: head/sys/dev/gpio/gpioc.c 326255 2017-11-27 14:52:40Z pfg $"); | __FBSDID("$FreeBSD: head/sys/dev/gpio/gpioc.c 326255 2017-11-27 14:52:40Z pfg $"); | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/systm.h> | #include <sys/systm.h> | ||||
#include <sys/bus.h> | #include <sys/bus.h> | ||||
#include <sys/conf.h> | #include <sys/conf.h> | ||||
#include <sys/gpio.h> | #include <sys/gpio.h> | ||||
#include <sys/ioccom.h> | #include <sys/ioccom.h> | ||||
#include <sys/filio.h> | |||||
#include <sys/fcntl.h> | |||||
#include <sys/sigio.h> | |||||
#include <sys/signalvar.h> | |||||
#include <sys/kernel.h> | #include <sys/kernel.h> | ||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/uio.h> | |||||
#include <sys/poll.h> | |||||
#include <sys/selinfo.h> | |||||
#include <sys/module.h> | #include <sys/module.h> | ||||
#include <dev/gpio/gpiobusvar.h> | #include <dev/gpio/gpiobusvar.h> | ||||
#include "gpio_if.h" | #include "gpio_if.h" | ||||
#include "gpiobus_if.h" | #include "gpiobus_if.h" | ||||
#undef GPIOC_DEBUG | #undef GPIOC_DEBUG | ||||
#ifdef GPIOC_DEBUG | #ifdef GPIOC_DEBUG | ||||
#define dprintf printf | #define dprintf printf | ||||
#else | #else | ||||
#define dprintf(x, arg...) | #define dprintf(x, arg...) | ||||
#endif | #endif | ||||
bobf_mrp3.com: gpioc_softc was moved from around line 66 in the original source. 'sc_npins' and 'sc_pin_intr'… | |||||
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; | |||||
Not Done Inline Actionsif more than one interrupt happens before the first one is processed, is there some way to hold off processing so you don't miss one? Or at least there should be a way of indicating multiple interrupts, and managing their individual states instead of causing an error later on by 'last_intr_pin' having an actual pin number in it for the last interrupt pin. Realistically I could configure many interrupt pins, something reasonable if I have a rotating quadrature encoder [which would have 4 of them]. bobf_mrp3.com: if more than one interrupt happens before the first one is processed, is there some way to hold… | |||||
uint32_t last_intr_pin; | |||||
struct selinfo selinfo; | |||||
bool async; | |||||
struct sigio *sigio; | |||||
struct mtx mtx; | |||||
SLIST_HEAD(gpioc_pins_list, gpioc_pins) pins; | |||||
}; | |||||
Not Done Inline Actionsobviously there's a collection of gpioc_cdevpriv structures being referred to here, but other functions later on make it look like there should be only one... so how many are there? TODO: check to see if the SLIST_ENTRY is something that's common for all devices or just this one... [as far as I can tell, only this one] bobf_mrp3.com: obviously there's a collection of gpioc_cdevpriv structures being referred to here, but other… | |||||
Not Done Inline Actionsadditionally, does this whole mod need '#ifdef INTRNG' around it? It seems to me that INTRNG is needed for this... especially since gpio_alloc_intr_resource() relies on it [otherwise it's really just a stub returning NULL]. bobf_mrp3.com: additionally, does this whole mod need '#ifdef INTRNG' around it? It seems to me that INTRNG… | |||||
struct gpioc_privs { | |||||
struct gpioc_cdevpriv *priv; | |||||
SLIST_ENTRY(gpioc_privs) next; | |||||
}; | |||||
struct gpioc_pins { | |||||
struct gpioc_pin_intr *pin; | |||||
SLIST_ENTRY(gpioc_pins) next; | |||||
}; | |||||
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_probe(device_t dev); | static int gpioc_probe(device_t dev); | ||||
static int gpioc_attach(device_t dev); | static int gpioc_attach(device_t dev); | ||||
static int gpioc_detach(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_ioctl_t gpioc_ioctl; | ||||
static d_poll_t gpioc_poll; | |||||
static d_kqfilter_t gpioc_kqfilter; | |||||
static int gpioc_kqread(struct knote*, long); | |||||
static void gpioc_kqdetach(struct knote*); | |||||
static struct cdevsw gpioc_cdevsw = { | static struct cdevsw gpioc_cdevsw = { | ||||
.d_version = D_VERSION, | .d_version = D_VERSION, | ||||
.d_open = gpioc_open, | |||||
.d_read = gpioc_read, | |||||
.d_ioctl = gpioc_ioctl, | .d_ioctl = gpioc_ioctl, | ||||
.d_poll = gpioc_poll, | |||||
.d_kqfilter = gpioc_kqfilter, | |||||
.d_name = "gpioc", | .d_name = "gpioc", | ||||
}; | }; | ||||
Not Done Inline Actionsgpioc_softc was moved to line ~60 bobf_mrp3.com: gpioc_softc was moved to line ~60 | |||||
struct gpioc_softc { | static struct filterops gpioc_read_filterops = { | ||||
device_t sc_dev; /* gpiocX dev */ | .f_isfd = true, | ||||
device_t sc_pdev; /* gpioX dev */ | .f_attach = NULL, | ||||
struct cdev *sc_ctl_dev; /* controller device */ | .f_detach = gpioc_kqdetach, | ||||
int sc_unit; | .f_event = gpioc_kqread, | ||||
.f_touch = NULL | |||||
}; | }; | ||||
static int | 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); | |||||
Not Done Inline Actionsworth point out, this function releases the mutex, calls some stuff, then re-locks the mutex. I don't like it when functions act this way, so I need to make sure that this is really 'best practices' here, and fix it if it is not. bobf_mrp3.com: worth point out, this function releases the mutex, calls some stuff, then re-locks the mutex. | |||||
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) | |||||
return (ENXIO); | |||||
bobf_mrp3.comAuthorUnsubmitted Not Done Inline ActionsI just noticed, the lock state isn't being restored on this error return. oops. bobf_mrp3.com: I just noticed, the lock state isn't being restored on this error return. oops. | |||||
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) | |||||
return (err); | |||||
intr_conf->pin->flags = flags; | |||||
mtx_lock(&intr_conf->mtx); | |||||
intr_conf->config_locked = false; | |||||
wakeup(&intr_conf->config_locked); | |||||
return (0); | |||||
} | |||||
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) | |||||
return (err); | |||||
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) | |||||
return (err); | |||||
else { | |||||
intr_conf->intr_rid = 0; | |||||
intr_conf->intr_res = NULL; | |||||
} | |||||
} | |||||
intr_conf->pin->flags = 0; | |||||
mtx_lock(&intr_conf->mtx); | |||||
intr_conf->config_locked = false; | |||||
wakeup(&intr_conf->config_locked); | |||||
return (0); | |||||
} | |||||
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) { | |||||
Not Done Inline Actionsthis part confuses me, why there are two separate structures, why the need for a consistency check in the first place. I'll need to see if anything similar to this is being done anyplace else in the kernel, but I doubt it. bobf_mrp3.com: this part confuses me, why there are two separate structures, why the need for a consistency… | |||||
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); | |||||
mtx_unlock(&intr_conf->mtx); | |||||
return (EEXIST); | |||||
} | |||||
priv_link = malloc(sizeof(struct gpioc_privs), M_GPIOC, | |||||
M_NOWAIT | M_ZERO); | |||||
if (priv_link == NULL) | |||||
{ | |||||
mtx_unlock(&priv->mtx); | |||||
mtx_unlock(&intr_conf->mtx); | |||||
return (ENOMEM); | |||||
} | |||||
pin_link = malloc(sizeof(struct gpioc_pins), M_GPIOC, | |||||
M_NOWAIT | M_ZERO); | |||||
if (pin_link == NULL) { | |||||
mtx_unlock(&priv->mtx); | |||||
mtx_unlock(&intr_conf->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) { | |||||
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); | |||||
SLIST_FOREACH(priv_link, &intr_conf->privs, next) { | |||||
if (priv_link->priv == priv) { | |||||
flags |= GPIO_INTR_ATTACHED; | |||||
break; | |||||
} | |||||
} | |||||
return (flags); | |||||
} | |||||
static int | |||||
gpioc_set_intr_config(struct gpioc_softc *sc, struct gpioc_cdevpriv *priv, | |||||
uint32_t pin, uint32_t flags) | |||||
{ | |||||
Not Done Inline ActionsI do not believe that this function will even work, other than allocating gpioc specific structures. Specifically, it's not calling PIC_SETUP_INTR or anything similar to actually set up a pin as an interrupt source. Is it necessary to use overlays for pins that can generate an interrupt for a user-space handler? bobf_mrp3.com: I do not believe that this function will even work, other than allocating gpioc specific… | |||||
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) | |||||
Not Done Inline Actionsa potentially infinite wait loop while the first mutex is locked - I think that could potentially deadlock. need to check best practices for having sequential mutex locking, if a wait loop is necessary, etc. etc. and whether or not deadlocking is likely. bobf_mrp3.com: a potentially infinite wait loop while the first mutex is locked - I think that could… | |||||
Not Done Inline Actionsmaybe not a problem, this function is only called from gpioc_ioctl and not with a mutex locked. it's using mutex_sleep to be woken up later once config_locked is false. This may not be the best practice, however it's being used elsewhere including gpiobus.c . the name 'gpicfg' may not be ideal, however. bobf_mrp3.com: maybe not a problem, this function is only called from gpioc_ioctl and not with a mutex locked. | |||||
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; | |||||
intr_conf = arg; | |||||
sc = intr_conf->sc; | |||||
mtx_lock(&intr_conf->mtx); | |||||
if (intr_conf->config_locked == true) { | |||||
device_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)) { | |||||
device_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) { | |||||
mtx_lock(&privs->priv->mtx); | |||||
if (privs->priv->last_intr_pin != -1) | |||||
device_printf(sc->sc_dev, "Unhandled interrupt on pin " | |||||
"%d.\n", intr_conf->pin->pin); | |||||
privs->priv->last_intr_pin = intr_conf->pin->pin; | |||||
wakeup(privs->priv); | |||||
selwakeup(&privs->priv->selinfo); | |||||
KNOTE_LOCKED(&privs->priv->selinfo.si_note, 0); | |||||
if (privs->priv->async == true && privs->priv->sigio != NULL) | |||||
pgsigio(&privs->priv->sigio, SIGIO, 0); | |||||
mtx_unlock(&privs->priv->mtx); | |||||
} | |||||
mtx_unlock(&intr_conf->mtx); | |||||
} | |||||
static int | |||||
gpioc_probe(device_t dev) | gpioc_probe(device_t dev) | ||||
{ | { | ||||
device_set_desc(dev, "GPIO controller"); | device_set_desc(dev, "GPIO controller"); | ||||
return (0); | return (0); | ||||
} | } | ||||
static int | static int | ||||
gpioc_attach(device_t dev) | gpioc_attach(device_t dev) | ||||
{ | { | ||||
int err; | int err; | ||||
struct gpioc_softc *sc; | struct gpioc_softc *sc; | ||||
struct make_dev_args devargs; | struct make_dev_args devargs; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
sc->sc_dev = dev; | sc->sc_dev = dev; | ||||
sc->sc_pdev = device_get_parent(dev); | sc->sc_pdev = device_get_parent(dev); | ||||
sc->sc_unit = device_get_unit(dev); | sc->sc_unit = device_get_unit(dev); | ||||
err = GPIO_PIN_MAX(sc->sc_pdev, &sc->sc_npins); | |||||
if (err != 0) | |||||
return (err); | |||||
sc->sc_pin_intr = malloc(sizeof(struct gpioc_pin_intr) * sc->sc_npins, | |||||
bobf_mrp3.comAuthorUnsubmitted Not Done Inline Actionsit occurs to me that all of these mallocs (in a loop, see below) are a waste of kernel resources. It would be a _LOT_ better to do a single malloc, then point the pointers at the apropriate place within the malloc'd block. This is a TODO item to improve the thing, once it's actually working. bobf_mrp3.com: it occurs to me that all of these mallocs (in a loop, see below) are a waste of kernel… | |||||
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); | make_dev_args_init(&devargs); | ||||
devargs.mda_devsw = &gpioc_cdevsw; | devargs.mda_devsw = &gpioc_cdevsw; | ||||
devargs.mda_uid = UID_ROOT; | devargs.mda_uid = UID_ROOT; | ||||
devargs.mda_gid = GID_WHEEL; | devargs.mda_gid = GID_WHEEL; | ||||
devargs.mda_mode = 0600; | devargs.mda_mode = 0600; | ||||
devargs.mda_si_drv1 = sc; | devargs.mda_si_drv1 = sc; | ||||
err = make_dev_s(&devargs, &sc->sc_ctl_dev, "gpioc%d", sc->sc_unit); | err = make_dev_s(&devargs, &sc->sc_ctl_dev, "gpioc%d", sc->sc_unit); | ||||
if (err != 0) { | if (err != 0) { | ||||
printf("Failed to create gpioc%d", sc->sc_unit); | printf("Failed to create gpioc%d", sc->sc_unit); | ||||
return (ENXIO); | return (ENXIO); | ||||
} | } | ||||
return (0); | return (0); | ||||
} | } | ||||
static int | static int | ||||
gpioc_detach(device_t dev) | gpioc_detach(device_t dev) | ||||
{ | { | ||||
struct gpioc_softc *sc = device_get_softc(dev); | struct gpioc_softc *sc = device_get_softc(dev); | ||||
int err; | int err; | ||||
if (sc->sc_ctl_dev) | if (sc->sc_ctl_dev) | ||||
destroy_dev(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) | if ((err = bus_generic_detach(dev)) != 0) | ||||
return (err); | return (err); | ||||
return (0); | 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(data, M_GPIOC); | |||||
} | |||||
static int | 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); | |||||
err = devfs_set_cdevpriv(priv, gpioc_cdevpriv_dtor); | |||||
if (err != 0) { | |||||
gpioc_cdevpriv_dtor(priv); | |||||
return (err); | |||||
} | |||||
priv->sc = dev->si_drv1; | |||||
priv->last_intr_pin = -1; | |||||
mtx_init(&priv->mtx, "gpioc priv", NULL, MTX_DEF); | |||||
knlist_init_mtx(&priv->selinfo.si_note, &priv->mtx); | |||||
return (0); | |||||
} | |||||
static int | |||||
gpioc_read(struct cdev *dev, struct uio *uio, int ioflag) | |||||
{ | |||||
struct gpioc_cdevpriv *priv; | |||||
uint32_t last_intr_pin; | |||||
int err; | |||||
if (uio->uio_resid < sizeof(priv->last_intr_pin)) | |||||
return EINVAL; | |||||
err = devfs_get_cdevpriv((void **)&priv); | |||||
if (err != 0) | |||||
return err; | |||||
mtx_lock(&priv->mtx); | |||||
while (priv->last_intr_pin == -1) { | |||||
if (SLIST_EMPTY(&priv->pins)) { | |||||
err = ENXIO; | |||||
Not Done Inline ActionsI'm not convinced that returning ENXIO here is the best idea. If no interrupts were configured, is it REALLY that bad of an error condition? As a character device it won't return an EOF but it could return a zero byte transfer like /dev/null probably does. bobf_mrp3.com: I'm not convinced that returning ENXIO here is the best idea. If no interrupts were… | |||||
break; | |||||
} else if (ioflag & O_NONBLOCK) { | |||||
err = EWOULDBLOCK; | |||||
break; | |||||
} else { | |||||
err = mtx_sleep(priv, &priv->mtx, PCATCH, "gpintr", 0); | |||||
if (err != 0) | |||||
break; | |||||
} | |||||
} | |||||
if (err == 0 && priv->last_intr_pin != -1) | |||||
{ | |||||
last_intr_pin = priv->last_intr_pin; | |||||
priv->last_intr_pin = -1; | |||||
mtx_unlock(&priv->mtx); | |||||
err = uiomove(&last_intr_pin, sizeof(last_intr_pin), uio); | |||||
} | |||||
else { | |||||
mtx_unlock(&priv->mtx); | |||||
} | |||||
return (err); | |||||
} | |||||
static int | |||||
gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg, int fflag, | gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg, int fflag, | ||||
struct thread *td) | struct thread *td) | ||||
{ | { | ||||
device_t bus; | device_t bus; | ||||
int max_pin, res; | int max_pin, res; | ||||
struct gpioc_softc *sc = cdev->si_drv1; | struct gpioc_softc *sc = cdev->si_drv1; | ||||
struct gpioc_cdevpriv *priv; | |||||
struct gpio_pin pin; | struct gpio_pin pin; | ||||
struct gpio_req req; | struct gpio_req req; | ||||
struct gpio_access_32 *a32; | struct gpio_access_32 *a32; | ||||
struct gpio_config_32 *c32; | struct gpio_config_32 *c32; | ||||
uint32_t caps; | uint32_t caps; | ||||
bus = GPIO_GET_BUS(sc->sc_pdev); | bus = GPIO_GET_BUS(sc->sc_pdev); | ||||
if (bus == NULL) | if (bus == NULL) | ||||
return (EINVAL); | return (EINVAL); | ||||
switch (cmd) { | switch (cmd) { | ||||
case GPIOMAXPIN: | case GPIOMAXPIN: | ||||
max_pin = -1; | max_pin = -1; | ||||
res = GPIO_PIN_MAX(sc->sc_pdev, &max_pin); | res = GPIO_PIN_MAX(sc->sc_pdev, &max_pin); | ||||
bcopy(&max_pin, arg, sizeof(max_pin)); | bcopy(&max_pin, arg, sizeof(max_pin)); | ||||
break; | break; | ||||
case GPIOGETCONFIG: | case GPIOGETCONFIG: | ||||
bcopy(arg, &pin, sizeof(pin)); | bcopy(arg, &pin, sizeof(pin)); | ||||
dprintf("get config pin %d\n", pin.gp_pin); | dprintf("get config pin %d\n", pin.gp_pin); | ||||
res = GPIO_PIN_GETFLAGS(sc->sc_pdev, pin.gp_pin, | res = GPIO_PIN_GETFLAGS(sc->sc_pdev, pin.gp_pin, | ||||
&pin.gp_flags); | &pin.gp_flags); | ||||
/* Fail early */ | /* Fail early */ | ||||
if (res) | if (res) | ||||
break; | 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); | GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &pin.gp_caps); | ||||
GPIOBUS_PIN_GETNAME(bus, pin.gp_pin, pin.gp_name); | GPIOBUS_PIN_GETNAME(bus, pin.gp_pin, pin.gp_name); | ||||
bcopy(&pin, arg, sizeof(pin)); | bcopy(&pin, arg, sizeof(pin)); | ||||
break; | break; | ||||
case GPIOSETCONFIG: | case GPIOSETCONFIG: | ||||
bcopy(arg, &pin, sizeof(pin)); | bcopy(arg, &pin, sizeof(pin)); | ||||
dprintf("set config pin %d\n", pin.gp_pin); | dprintf("set config pin %d\n", pin.gp_pin); | ||||
res = GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, &caps); | res = devfs_get_cdevpriv((void **)&priv); | ||||
if (res == 0) | if (res == 0) | ||||
res = GPIO_PIN_GETCAPS(sc->sc_pdev, pin.gp_pin, | |||||
&caps); | |||||
if (res == 0) | |||||
res = gpio_check_flags(caps, pin.gp_flags); | res = gpio_check_flags(caps, pin.gp_flags); | ||||
if (res == 0) | if (res == 0) | ||||
res = GPIO_PIN_SETFLAGS(sc->sc_pdev, pin.gp_pin, | res = GPIO_PIN_SETFLAGS(sc->sc_pdev, pin.gp_pin, | ||||
pin.gp_flags); | (pin.gp_flags & ~GPIO_INTR_MASK)); | ||||
if (res == 0) | |||||
res = gpioc_set_intr_config(sc, priv, | |||||
Not Done Inline Actionsunlike using GPIO_PIN_SETFLAGS the call to gpioc_set_intr_config doesn't do a call to PIC_SETUP_INTR or anything similar that's located within the actual hardware driver itself - so how can it enable inerrupts? How can I even get an interrupt generated? Maybe if the OFW/FDT info has an interrupt configured already, will THAT work? In any case there doesn't seem to be a way to toggle the interrupt state 'on the fly' at all... and requiring overlays to use user-space interrupt handling seems a bit too much. bobf_mrp3.com: unlike using GPIO_PIN_SETFLAGS the call to gpioc_set_intr_config doesn't do a call to… | |||||
pin.gp_pin, | |||||
(pin.gp_flags & GPIO_INTR_MASK)); | |||||
break; | break; | ||||
case GPIOGET: | case GPIOGET: | ||||
bcopy(arg, &req, sizeof(req)); | bcopy(arg, &req, sizeof(req)); | ||||
res = GPIO_PIN_GET(sc->sc_pdev, req.gp_pin, | res = GPIO_PIN_GET(sc->sc_pdev, req.gp_pin, | ||||
&req.gp_value); | &req.gp_value); | ||||
dprintf("read pin %d -> %d\n", | dprintf("read pin %d -> %d\n", | ||||
req.gp_pin, req.gp_value); | req.gp_pin, req.gp_value); | ||||
bcopy(&req, arg, sizeof(req)); | bcopy(&req, arg, sizeof(req)); | ||||
Show All 22 Lines | case GPIOACCESS32: | ||||
res = GPIO_PIN_ACCESS_32(sc->sc_pdev, a32->first_pin, | res = GPIO_PIN_ACCESS_32(sc->sc_pdev, a32->first_pin, | ||||
a32->clear_pins, a32->orig_pins, &a32->orig_pins); | a32->clear_pins, a32->orig_pins, &a32->orig_pins); | ||||
break; | break; | ||||
case GPIOCONFIG32: | case GPIOCONFIG32: | ||||
c32 = (struct gpio_config_32 *)arg; | c32 = (struct gpio_config_32 *)arg; | ||||
res = GPIO_PIN_CONFIG_32(sc->sc_pdev, c32->first_pin, | res = GPIO_PIN_CONFIG_32(sc->sc_pdev, c32->first_pin, | ||||
c32->num_pins, c32->pin_flags); | c32->num_pins, c32->pin_flags); | ||||
break; | 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: | default: | ||||
return (ENOTTY); | return (ENOTTY); | ||||
break; | break; | ||||
} | } | ||||
return (res); | return (res); | ||||
} | |||||
static int | |||||
gpioc_poll(struct cdev *dev, int events, struct thread *td) | |||||
{ | |||||
struct gpioc_cdevpriv *priv; | |||||
int err; | |||||
int revents; | |||||
revents = 0; | |||||
err = devfs_get_cdevpriv((void **)&priv); | |||||
if (err != 0) { | |||||
revents = POLLERR; | |||||
return (revents); | |||||
} | |||||
if (SLIST_EMPTY(&priv->pins)) { | |||||
revents = POLLHUP; | |||||
return (revents); | |||||
} | |||||
if (events & (POLLIN | POLLRDNORM)) { | |||||
if (priv->last_intr_pin != -1) | |||||
Not Done Inline Actionswhat happens if 2 interrupt pins trigger simultaneously, or very close to that bobf_mrp3.com: what happens if 2 interrupt pins trigger simultaneously, or very close to that | |||||
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; | |||||
if (SLIST_EMPTY(&priv->pins)) { | |||||
kn->kn_flags |= EV_EOF; | |||||
return (1); | |||||
} else { | |||||
impUnsubmitted Not Done Inline ActionsAfter an unconditional return, it's the style of FreeBSD to not use else {} but just have that code there. imp: After an unconditional return, it's the style of FreeBSD to not use else {} but just have that… | |||||
if (priv->last_intr_pin != -1) { | |||||
kn->kn_data = sizeof(priv->last_intr_pin); | |||||
Not Done Inline Actionswhy is this assigning 'sizeof()' and not the value of last_intr_pin ? bobf_mrp3.com: why is this assigning 'sizeof()' and not the value of last_intr_pin ? | |||||
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[] = { | static device_method_t gpioc_methods[] = { | ||||
/* Device interface */ | /* Device interface */ | ||||
DEVMETHOD(device_probe, gpioc_probe), | DEVMETHOD(device_probe, gpioc_probe), | ||||
DEVMETHOD(device_attach, gpioc_attach), | DEVMETHOD(device_attach, gpioc_attach), | ||||
DEVMETHOD(device_detach, gpioc_detach), | DEVMETHOD(device_detach, gpioc_detach), | ||||
DEVMETHOD(device_shutdown, bus_generic_shutdown), | DEVMETHOD(device_shutdown, bus_generic_shutdown), | ||||
Show All 16 Lines |
gpioc_softc was moved from around line 66 in the original source. 'sc_npins' and 'sc_pin_intr' were added.