Page MenuHomeFreeBSD

D57968.id187249.diff
No OneTemporary

D57968.id187249.diff

diff --git a/sys/dev/amdgpio/amdgpio.h b/sys/dev/amdgpio/amdgpio.h
--- a/sys/dev/amdgpio/amdgpio.h
+++ b/sys/dev/amdgpio/amdgpio.h
@@ -29,7 +29,7 @@
#ifdef DEBUG
#define dprintf(fmt, args...) do { \
printf("%s(): ", __func__); \
- printf(fmt,##args); \
+ printf(fmt, ##args); \
} while (0)
#else
#define dprintf(fmt, args...)
@@ -41,6 +41,14 @@
#define AMD_GPIO_PINS_PER_BANK 64
#define AMD_GPIO_PINS_MAX 256 /* 4 banks * 64 pins */
+#define AMD_GPIO_PINS_PER_INTR_BIT 4
+#define AMD_GPIO_NUM_INTR_BITS 46
+#define AMD_GPIO_RESERVED_INTR_BIT 15 /* pins 60 - 63 */
+#define AMD_GPIO_INTR_MASK \
+ (((1ul << AMD_GPIO_NUM_INTR_BITS) - 1) & \
+ ~(1ul << AMD_GPIO_RESERVED_INTR_BIT))
+
+
/* Number of pins in each bank */
#define AMD_GPIO_PINS_BANK0 63
#define AMD_GPIO_PINS_BANK1 64
@@ -50,8 +58,9 @@
AMD_GPIO_PINS_BANK1 + \
AMD_GPIO_PINS_BANK2 + \
AMD_GPIO_PINS_BANK3)
-#define AMDGPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \
- GPIO_PIN_PULLDOWN | GPIO_PIN_PULLUP)
+#define AMDGPIO_DEFAULT_CAPS \
+ (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)
+#define AMDGPIO_INTR_CAPS GPIO_INTR_MASK
/* Register related macros */
#define AMDGPIO_PIN_REGISTER(pin) (pin * 4)
@@ -60,6 +69,7 @@
#define EOI_MASK (1 << 29)
#define WAKE_INT_STATUS_REG0 0x2f8
#define WAKE_INT_STATUS_REG1 0x2fc
+#define INTR_EN_MASK (1 << 30)
/* Bit definition of 32 bits of each pin register */
#define DB_TMR_OUT_OFF 0
@@ -304,7 +314,7 @@
/* Macros for driver mutex locking */
#define AMDGPIO_LOCK_INIT(_sc) \
mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \
- "amdgpio", MTX_SPIN)
+ "amdgpio", MTX_SPIN | MTX_RECURSE)
#define AMDGPIO_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx)
#define AMDGPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx)
#define AMDGPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx)
@@ -315,7 +325,7 @@
ACPI_HANDLE sc_handle;
device_t sc_dev;
device_t sc_busdev;
- const char* sc_bank_prefix;
+ const char *sc_bank_prefix;
int sc_nbanks;
int sc_npins;
int sc_ngroups;
@@ -327,6 +337,7 @@
const struct pin_info *sc_pin_info;
const struct amd_pingroup *sc_groups;
void *sc_intr_handle;
+ int sc_intr_en_count;
};
struct amdgpio_sysctl {
diff --git a/sys/dev/amdgpio/amdgpio.c b/sys/dev/amdgpio/amdgpio.c
--- a/sys/dev/amdgpio/amdgpio.c
+++ b/sys/dev/amdgpio/amdgpio.c
@@ -148,8 +148,7 @@
return (EINVAL);
/* Set a very simple name */
- snprintf(name, GPIOMAXNAME, "%s", sc->sc_gpio_pins[pin].gp_name);
- name[GPIOMAXNAME - 1] = '\0';
+ strlcpy(name, sc->sc_gpio_pins[pin].gp_name, GPIOMAXNAME);
dprintf("pin %d name %s\n", pin, name);
@@ -223,6 +222,11 @@
(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT))
return (EINVAL);
+ /* No pull-up and pull-down at the same time. */
+ if ((flags & (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)) ==
+ (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN))
+ return (EINVAL);
+
/* Set the GPIO mode and state */
AMDGPIO_LOCK(sc);
@@ -252,6 +256,186 @@
return (0);
}
+static bool
+amdgpio_valid_intr_pin(struct amdgpio_softc *sc, int pin)
+{
+ if (!amdgpio_valid_pin(sc, pin))
+ return (false);
+ if ((sc->sc_gpio_pins[pin].gp_caps & GPIO_INTR_MASK) == GPIO_INTR_NONE)
+ return (false);
+ return (true);
+}
+
+static void
+amdgpio_pin_config_intr(device_t dev, uint32_t pin, uint32_t intr_mode)
+{
+ struct amdgpio_softc *sc;
+ uint32_t reg, val;
+ int trig, act;
+
+ sc = device_get_softc(dev);
+
+ dprintf("pin %d intr_mode 0x%x\n", pin, intr_mode);
+
+ if (!amdgpio_valid_intr_pin(sc, pin))
+ dprintf("invalid pin %d\n", pin);
+
+ /* XXX Linux also sets various debounce modes. */
+ switch (intr_mode) {
+ case GPIO_INTR_EDGE_FALLING:
+ trig = EDGE_TRIGGER;
+ act = ACTIVE_LOW;
+ break;
+ case GPIO_INTR_EDGE_RISING:
+ trig = EDGE_TRIGGER;
+ act = ACTIVE_HIGH;
+ break;
+ case GPIO_INTR_EDGE_BOTH:
+ trig = EDGE_TRIGGER;
+ act = BOTH_EDGE;
+ break;
+ case GPIO_INTR_LEVEL_LOW:
+ trig = LEVEL_TRIGGER;
+ act = ACTIVE_LOW;
+ break;
+ case GPIO_INTR_LEVEL_HIGH:
+ trig = LEVEL_TRIGGER;
+ act = ACTIVE_HIGH;
+ break;
+ default:
+ device_printf(dev, "invalid pin %d intr_mode 0x%x\n",
+ pin, intr_mode);
+ return;
+ }
+
+ reg = AMDGPIO_PIN_REGISTER(pin);
+
+ /* Set the GPIO mode and state */
+ AMDGPIO_LOCK(sc);
+ val = amdgpio_read_4(sc, reg);
+ val &= ~(1 << LEVEL_TRIG_OFF);
+ val |= trig << LEVEL_TRIG_OFF;
+ val &= ~(3 << ACTIVE_LEVEL_OFF);
+ val |= act << ACTIVE_LEVEL_OFF;
+ amdgpio_write_4(sc, reg, val);
+ AMDGPIO_UNLOCK(sc);
+}
+
+static void
+amdgpio_pin_enable_intr(device_t dev, uint32_t pin)
+{
+ struct amdgpio_softc *sc;
+ uint32_t reg, val;
+
+ sc = device_get_softc(dev);
+
+ dprintf("pin %d enable intr\n", pin);
+
+ if (!amdgpio_valid_intr_pin(sc, pin))
+ dprintf("invalid pin %d\n", pin);
+
+ reg = AMDGPIO_PIN_REGISTER(pin);
+
+ AMDGPIO_LOCK(sc);
+ val = amdgpio_read_4(sc, reg);
+ val |= BIT(INTERRUPT_STS_OFF); /* clear previous status as well */
+ val |= BIT(INTERRUPT_ENABLE_OFF);
+ amdgpio_write_4(sc, reg, val);
+ sc->sc_intr_en_count++;
+ AMDGPIO_UNLOCK(sc);
+}
+
+static void
+amdgpio_pin_disable_intr(device_t dev, uint32_t pin)
+{
+ struct amdgpio_softc *sc;
+ uint32_t reg, val;
+
+ sc = device_get_softc(dev);
+
+ dprintf("pin %d disable intr\n", pin);
+
+ if (!amdgpio_valid_intr_pin(sc, pin))
+ dprintf("invalid pin %d\n", pin);
+
+ reg = AMDGPIO_PIN_REGISTER(pin);
+
+ AMDGPIO_LOCK(sc);
+ val = amdgpio_read_4(sc, reg);
+ val &= ~BIT(INTERRUPT_ENABLE_OFF);
+ amdgpio_write_4(sc, reg, val);
+ sc->sc_intr_en_count--;
+ AMDGPIO_UNLOCK(sc);
+}
+
+static void
+amdgpio_pin_unmask_intr(device_t dev, uint32_t pin)
+{
+ struct amdgpio_softc *sc;
+ uint32_t reg, val;
+
+ sc = device_get_softc(dev);
+
+ dprintf("pin %d unmask intr\n", pin);
+
+ if (!amdgpio_valid_intr_pin(sc, pin))
+ dprintf("invalid pin %d\n", pin);
+
+ reg = AMDGPIO_PIN_REGISTER(pin);
+
+ AMDGPIO_LOCK(sc);
+ val = amdgpio_read_4(sc, reg);
+ val |= BIT(INTERRUPT_MASK_OFF);
+ amdgpio_write_4(sc, reg, val);
+ AMDGPIO_UNLOCK(sc);
+}
+
+static void
+amdgpio_pin_mask_intr(device_t dev, uint32_t pin)
+{
+ struct amdgpio_softc *sc;
+ uint32_t reg, val;
+
+ sc = device_get_softc(dev);
+
+ dprintf("pin %d mask intr\n", pin);
+
+ if (!amdgpio_valid_intr_pin(sc, pin))
+ dprintf("invalid pin %d\n", pin);
+
+ reg = AMDGPIO_PIN_REGISTER(pin);
+
+ AMDGPIO_LOCK(sc);
+ val = amdgpio_read_4(sc, reg);
+ val &= ~BIT(INTERRUPT_MASK_OFF);
+ val |= BIT(INTERRUPT_STS_OFF); /* clear status as well */
+ amdgpio_write_4(sc, reg, val);
+ AMDGPIO_UNLOCK(sc);
+}
+
+static void
+amdgpio_pin_eoi(device_t dev, uint32_t pin)
+{
+ struct amdgpio_softc *sc;
+ uint32_t reg, val;
+
+ sc = device_get_softc(dev);
+
+ dprintf("pin %d eoi intr\n", pin);
+
+ if (!amdgpio_valid_intr_pin(sc, pin))
+ dprintf("invalid pin %d\n", pin);
+
+ reg = AMDGPIO_PIN_REGISTER(pin);
+
+ AMDGPIO_LOCK(sc);
+ /* XXX Linux does EOI via WAKE_INT_MASTER_REG and EIO_MASK. */
+ val = amdgpio_read_4(sc, reg);
+ val |= BIT(INTERRUPT_STS_OFF);
+ amdgpio_write_4(sc, reg, val);
+ AMDGPIO_UNLOCK(sc);
+}
+
static int
amdgpio_pin_get(device_t dev, uint32_t pin, unsigned int *value)
{
@@ -391,51 +575,72 @@
amdgpio_intr_filter(void *arg)
{
struct amdgpio_softc *sc = arg;
- int off, rv = FILTER_STRAY;
- uint32_t reg;
+ uint64_t status;
+ uint32_t status0, status1;
+ uint32_t reg, val;
+ int ret = FILTER_STRAY;
+ int i, j;
- /* We can lock in the filter routine as it is MTX_SPIN. */
AMDGPIO_LOCK(sc);
+ if (sc->sc_busdev == NULL) {
+ /* Too early interrupt ? */
+ AMDGPIO_UNLOCK(sc);
+ return (ret);
+ }
- /*
- * TODO Instead of just reading the registers of all pins, we should
- * read WAKE_INT_STATUS_REG0/1. A bit set in here denotes a group of
- * 4 pins where at least one has an interrupt for us. Then we can just
- * iterate over those 4 pins.
- *
- * See GPIO_Interrupt_Status_Index_0 in BKDG.
- */
- for (size_t pin = 0; pin < AMD_GPIO_PINS_EXPOSED; pin++) {
- off = AMDGPIO_PIN_REGISTER(pin);
- reg = amdgpio_read_4(sc, off);
- if ((reg & UNSERVICED_INTERRUPT_MASK) == 0)
+ /* Merge into a single 46-bit status. */
+ status0 = amdgpio_read_4(sc, WAKE_INT_STATUS_REG0);
+ status1 = amdgpio_read_4(sc, WAKE_INT_STATUS_REG1);
+ status = status1;
+ status <<= 32;
+ status |= status0;
+ status &= AMD_GPIO_INTR_MASK;
+ for (i = 0; i < AMD_GPIO_NUM_INTR_BITS; i++) {
+ if ((status & (1 << i)) == 0)
continue;
- /*
- * Must write 1's to wake/interrupt status bits to clear them.
- * We can do this simply by writing back to the register.
- */
- amdgpio_write_4(sc, off, reg);
+ for (j = 0; j < AMD_GPIO_PINS_PER_INTR_BIT; j++) {
+ uint32_t pin;
+
+ pin = i * AMD_GPIO_PINS_PER_INTR_BIT + j;
+ reg = AMDGPIO_PIN_REGISTER(pin);
+ val = amdgpio_read_4(sc, reg);
+
+ /*
+ * XXX consider that WAKE_STS_OFF can be set but
+ * INTERRUPT_STS_OFF unset after a wakeup.
+ */
+ if ((val & BIT(INTERRUPT_STS_OFF)) == 0)
+ continue;
+
+ /* Ignore masked pins as well. */
+ if ((val & BIT(INTERRUPT_MASK_OFF)) == 0)
+ continue;
+ gpiobus_handle_intr(sc->sc_busdev, pin);
+ ret = FILTER_HANDLED;
+ }
}
+ /* Ready for more interrupts. */
amdgpio_eoi_locked(sc);
- AMDGPIO_UNLOCK(sc);
- rv = FILTER_HANDLED;
- return (rv);
+ AMDGPIO_UNLOCK(sc);
+ return (ret);
}
static void
amdgpio_intr_handler(void *arg)
{
/* TODO */
+ dprintf("%s: dummy handler, arg = %p\n", __func__, arg);
}
static int
amdgpio_attach(device_t dev)
{
struct amdgpio_softc *sc;
- int i, pin, bank, reg;
- uint32_t flags;
+ device_t busdev;
+ int i, pin, bank, intrbit;
+ int err;
sc = device_get_softc(dev);
sc->sc_dev = dev;
@@ -459,10 +664,10 @@
sc->sc_bsh = rman_get_bushandle(sc->sc_res[0]);
/* Set up interrupt handler. */
- if (bus_setup_intr(dev, sc->sc_res[1], INTR_TYPE_MISC | INTR_MPSAFE,
- amdgpio_intr_filter, amdgpio_intr_handler, sc, &sc->sc_intr_handle)
- != 0) {
- device_printf(dev, "couldn't set up interrupt\n");
+ err = bus_setup_intr(dev, sc->sc_res[1], INTR_TYPE_MISC | INTR_MPSAFE,
+ amdgpio_intr_filter, amdgpio_intr_handler, sc, &sc->sc_intr_handle);
+ if (err != 0) {
+ device_printf(dev, "couldn't set up interrupt, %d\n", err);
goto err_intr;
}
@@ -475,40 +680,39 @@
sc->sc_gpio_pins[i].gp_flags = 0;
}
- /*
- * Initialize only driver exposed pins with appropriate capabilities.
- *
- * XXX Also mask and disable interrupts on all pins, since we don't
- * support them at the moment.
- */
+ /* Initialize only driver exposed pins with appropriate capabilities. */
for (i = 0; i < AMD_GPIO_PINS_EXPOSED ; i++) {
pin = kernzp_pins[i].pin_num;
- bank = pin/AMD_GPIO_PINS_PER_BANK;
+ bank = pin / AMD_GPIO_PINS_PER_BANK;
+ intrbit = pin / AMD_GPIO_PINS_PER_INTR_BIT;
snprintf(sc->sc_gpio_pins[pin].gp_name, GPIOMAXNAME, "%s%d_%s",
AMD_GPIO_PREFIX, bank, kernzp_pins[i].pin_name);
sc->sc_gpio_pins[pin].gp_pin = pin;
sc->sc_gpio_pins[pin].gp_caps = AMDGPIO_DEFAULT_CAPS;
+ if (sc->sc_intr_handle != NULL &&
+ intrbit < AMD_GPIO_NUM_INTR_BITS &&
+ intrbit != AMD_GPIO_RESERVED_INTR_BIT)
+ sc->sc_gpio_pins[pin].gp_caps |= AMDGPIO_INTR_CAPS;
sc->sc_gpio_pins[pin].gp_flags =
amdgpio_is_pin_output(sc, pin) ?
GPIO_PIN_OUTPUT : GPIO_PIN_INPUT;
-
- reg = AMDGPIO_PIN_REGISTER(pin);
- flags = amdgpio_read_4(sc, reg);
- flags &= ~(1 << INTERRUPT_ENABLE_OFF);
- flags &= ~(1 << INTERRUPT_MASK_OFF);
- amdgpio_write_4(sc, reg, flags);
}
- amdgpio_eoi(sc);
- sc->sc_busdev = gpiobus_add_bus(dev);
- if (sc->sc_busdev == NULL) {
+ busdev = gpiobus_add_bus(dev);
+ if (busdev == NULL) {
device_printf(dev, "could not attach gpiobus\n");
goto err_bus;
}
+ AMDGPIO_LOCK(sc);
+ sc->sc_busdev = busdev;
+ AMDGPIO_UNLOCK(sc);
+
bus_attach_children(dev);
- return (0);
+ amdgpio_eoi(sc);
+
+ return (0);
err_bus:
bus_teardown_intr(dev, sc->sc_res[1], sc->sc_intr_handle);
err_intr:
@@ -553,6 +757,14 @@
DEVMETHOD(gpio_pin_set, amdgpio_pin_set),
DEVMETHOD(gpio_pin_toggle, amdgpio_pin_toggle),
+ /* GPIO interrupt controller interface. */
+ DEVMETHOD(gpio_pin_config_intr, amdgpio_pin_config_intr),
+ DEVMETHOD(gpio_pin_enable_intr, amdgpio_pin_enable_intr),
+ DEVMETHOD(gpio_pin_disable_intr, amdgpio_pin_disable_intr),
+ DEVMETHOD(gpio_pin_mask_intr, amdgpio_pin_mask_intr),
+ DEVMETHOD(gpio_pin_unmask_intr, amdgpio_pin_unmask_intr),
+ DEVMETHOD(gpio_pin_eoi, amdgpio_pin_eoi),
+
DEVMETHOD_END
};
diff --git a/sys/dev/gpio/gpio_if.m b/sys/dev/gpio/gpio_if.m
--- a/sys/dev/gpio/gpio_if.m
+++ b/sys/dev/gpio/gpio_if.m
@@ -202,6 +202,40 @@
uint32_t *pin_flags;
} DEFAULT gpio_default_nosupport;
+#
+# Methods for interrupt handling on !INTRNG platforms.
+#
+METHOD void pin_config_intr {
+ device_t dev;
+ uint32_t pin_num;
+ uint32_t intr_mode;
+};
+
+METHOD void pin_enable_intr {
+ device_t dev;
+ uint32_t pin_num;
+};
+
+METHOD void pin_disable_intr {
+ device_t dev;
+ uint32_t pin_num;
+};
+
+METHOD void pin_eoi {
+ device_t dev;
+ uint32_t pin_num;
+};
+
+METHOD void pin_mask_intr {
+ device_t dev;
+ uint32_t pin_num;
+};
+
+METHOD void pin_unmask_intr {
+ device_t dev;
+ uint32_t pin_num;
+};
+
#
# Get the controller's pin numbers. pin_list is expected to be an array with at
# least GPIO_PIN_MAX() elements. Populates pin_list from 0 to GPIO_PIN_MAX() by
diff --git a/sys/dev/gpio/gpiobus.c b/sys/dev/gpio/gpiobus.c
--- a/sys/dev/gpio/gpiobus.c
+++ b/sys/dev/gpio/gpiobus.c
@@ -32,6 +32,13 @@
#include <sys/gpio.h>
#ifdef INTRNG
#include <sys/intr.h>
+#else
+#include <sys/interrupt.h>
+#include <sys/proc.h>
+#include <sys/sbuf.h>
+#include <sys/sx.h>
+#include <sys/sysctl.h>
+#include <sys/syslog.h>
#endif
#include <sys/kernel.h>
#include <sys/malloc.h>
@@ -59,6 +66,11 @@
static int gpiobus_print_child(device_t, device_t);
static device_t gpiobus_add_child(device_t, u_int, const char *, int);
static void gpiobus_hinted_child(device_t, const char *, int);
+#ifndef INTRNG
+static void gpiobus_pic_init(struct gpiobus_softc *);
+static void gpiobus_pic_destroy(struct gpiopic *);
+static void gpiobus_pic_resume(struct gpiopic *);
+#endif
/*
* GPIOBUS interface
@@ -114,14 +126,6 @@
}
return (res);
}
-#else
-struct resource *
-gpio_alloc_intr_resource(device_t consumer_dev, int rid, u_int alloc_flags,
- gpio_pin_t pin, uint32_t intr_mode)
-{
-
- return (NULL);
-}
#endif
int
@@ -342,11 +346,6 @@
sc = GPIOBUS_SOFTC(dev);
sc->sc_busdev = dev;
sc->sc_dev = device_get_parent(dev);
- sc->sc_intr_rman.rm_type = RMAN_ARRAY;
- sc->sc_intr_rman.rm_descr = "GPIO Interrupts";
- if (rman_init(&sc->sc_intr_rman) != 0 ||
- rman_manage_region(&sc->sc_intr_rman, 0, ~0) != 0)
- panic("%s: failed to set up rman.", __func__);
if (GPIO_PIN_MAX(sc->sc_dev, &sc->sc_npins) != 0)
return (ENXIO);
@@ -356,6 +355,21 @@
/* Pins = GPIO_PIN_MAX() + 1 */
sc->sc_npins++;
+ sc->sc_intr_rman.rm_type = RMAN_ARRAY;
+ sc->sc_intr_rman.rm_descr = "GPIO Interrupts";
+#ifndef INTRNG
+ sc->sc_intr_rman.rm_start = 0;
+ sc->sc_intr_rman.rm_end = sc->sc_npins - 1;
+#endif
+ if (rman_init(&sc->sc_intr_rman) != 0)
+ panic("%s: failed to set up rman.", __func__);
+#ifdef INTRNG
+ if (rman_manage_region(&sc->sc_intr_rman, 0, ~0) != 0)
+#else
+ if (rman_manage_region(&sc->sc_intr_rman, 0, sc->sc_npins - 1) != 0)
+#endif
+ panic("%s: failed to set up rman.", __func__);
+
sc->sc_pins = malloc(sizeof(*sc->sc_pins) * sc->sc_npins, M_DEVBUF,
M_NOWAIT | M_ZERO);
if (sc->sc_pins == NULL)
@@ -364,6 +378,9 @@
/* Initialize the bus lock. */
GPIOBUS_LOCK_INIT(sc);
+#ifndef INTRNG
+ gpiobus_pic_init(sc);
+#endif
return (0);
}
@@ -485,6 +502,10 @@
GPIOBUS_PIN_SETNAME(dev, devi->pins[i],
device_get_nameunit(child));
+ /* Set pin as a potential interrupt resource for the child. */
+ resource_list_add(&devi->rl, SYS_RES_IRQ, i, devi->pins[i],
+ devi->pins[i], 1);
+
}
return (0);
}
@@ -616,7 +637,6 @@
sc = GPIOBUS_SOFTC(dev);
KASSERT(mtx_initialized(&sc->sc_mtx),
("gpiobus mutex not initialized"));
- GPIOBUS_LOCK_DESTROY(sc);
if ((err = bus_generic_detach(dev)) != 0)
return (err);
@@ -632,6 +652,10 @@
sc->sc_pins = NULL;
}
+#ifndef INTRNG
+ gpiobus_pic_destroy(sc->sc_pic);
+#endif
+ GPIOBUS_LOCK_DESTROY(sc);
return (0);
}
@@ -645,6 +669,10 @@
static int
gpiobus_resume(device_t dev)
{
+ struct gpiobus_softc *sc;
+
+ sc = GPIOBUS_SOFTC(dev);
+ gpiobus_pic_resume(sc->sc_pic);
return (bus_generic_resume(dev));
}
@@ -863,6 +891,16 @@
}
}
+/*
+ * For now, this method supports only requests for GPIO IRQ resources.
+ * The requesting device, a consumer, does not have to be a child or
+ * a descendant of the bus device.
+ *
+ * FIXME
+ * The method does not support non-IRQ resources or non-GPIO IRQ resources
+ * even for its children. This should be fixed as soon as there is
+ * a child driver needs access to system resources.
+ */
static struct resource *
gpiobus_alloc_resource(device_t bus, device_t child, int type, int rid,
rman_res_t start, rman_res_t end, rman_res_t count, u_int flags)
@@ -871,7 +909,18 @@
struct resource_list_entry *rle;
int isdefault;
- isdefault = (RMAN_IS_DEFAULT_RANGE(start, end) && count == 1);
+ dprintf("%s: entry (%p, %p, %d, %d, %p, %ld)\n",
+ __func__, bus, child, type, rid, (void *)(intptr_t)start, count);
+
+ /*
+ * FIXME support indirect discendants and non-IRQ resources.
+ * The request should be forwarded upwards for those.
+ */
+ if (type != SYS_RES_IRQ)
+ return (NULL);
+
+ isdefault = RMAN_IS_DEFAULT_RANGE(start, end) && count == 1 &&
+ device_get_parent(child) == bus;
if (isdefault) {
rl = BUS_GET_RESOURCE_LIST(bus, child);
if (rl == NULL)
@@ -1133,6 +1182,548 @@
return (0);
}
+#ifndef INTRNG
+
+struct gpiopic;
+
+struct gpiopic_intsrc {
+ struct gpiopic *is_pic;
+ struct intr_event *is_event;
+ u_long *is_count; /* TODO expose to userland. */
+ u_long *is_straycount; /* TODO expose to userland. */
+ int is_handlers;
+ u_int is_pin;
+ u_int is_mode;
+ u_int is_enabled:1;
+ u_int is_masked:1;
+};
+
+struct gpiopic {
+ struct sx intrsrc_lock;
+ struct gpiobus_softc *sc;
+ struct gpiopic_intsrc *intr_srcs;
+ u_long *intr_counts;
+
+};
+
+static int
+sysctl_gpio_intrs(SYSCTL_HANDLER_ARGS)
+{
+ struct sbuf sbuf;
+ struct gpiopic *pic = arg1;
+ struct gpiopic_intsrc *isrc;
+ u_int i;
+ int error;
+
+ error = sysctl_wire_old_buffer(req, 0);
+ if (error != 0)
+ return (error);
+
+ sbuf_new_for_sysctl(&sbuf, NULL, 128, req);
+ sx_slock(&pic->intrsrc_lock);
+ for (i = 0; i < pic->sc->sc_npins; i++) {
+ isrc = &pic->intr_srcs[i];
+ if (isrc->is_event == NULL)
+ continue;
+ sbuf_printf(&sbuf, "%s:%u (mode 0x%08x): %lu\n",
+ isrc->is_event->ie_fullname,
+ isrc->is_pin,
+ isrc->is_mode,
+ *isrc->is_count);
+ }
+
+ sx_sunlock(&pic->intrsrc_lock);
+ error = sbuf_finish(&sbuf);
+ sbuf_delete(&sbuf);
+ return (error);
+}
+
+static int
+gpiopic_assign_cpu(void *arg, int cpu)
+{
+ /*
+ * This could be made to work if only a single pin is configured
+ * to be an interrupt source but not in general case.
+ * At this time there does not appear to be a case for binding
+ * GPIO interrupts, so not bothering with the only case that could
+ * be made to work.
+ */
+ return (EOPNOTSUPP);
+}
+
+static void
+gpiopic_unmask_intr(void *arg)
+{
+ struct gpiopic_intsrc *gisrc = arg;
+
+ /* To pereven stray interrupts after event handler removed */
+ if (gisrc->is_handlers == 0) {
+ dprintf("%s: skip unmasking pin %d for zero handlers\n",
+ __func__, gisrc->is_pin);
+ return;
+ }
+
+ gisrc->is_masked = 0;
+ GPIO_PIN_UNMASK_INTR(gisrc->is_pic->sc->sc_dev, gisrc->is_pin);
+}
+
+static void
+gpiopic_mask_intr(void *arg)
+{
+ struct gpiopic_intsrc *gisrc = arg;
+
+ gisrc->is_masked = 1;
+ GPIO_PIN_MASK_INTR(gisrc->is_pic->sc->sc_dev, gisrc->is_pin);
+}
+
+static void
+gpiopic_eoi(void *arg)
+{
+ struct gpiopic_intsrc *gisrc = arg;
+
+ GPIO_PIN_EOI(gisrc->is_pic->sc->sc_dev, gisrc->is_pin);
+}
+
+static void
+gpiopic_enable_intr(struct gpiopic_intsrc *gisrc)
+{
+ gisrc->is_enabled = 1;
+ GPIO_PIN_ENABLE_INTR(gisrc->is_pic->sc->sc_dev, gisrc->is_pin);
+}
+
+static void
+gpiopic_disable_intr(struct gpiopic_intsrc *gisrc)
+{
+ gisrc->is_enabled = 0;
+ GPIO_PIN_DISABLE_INTR(gisrc->is_pic->sc->sc_dev, gisrc->is_pin);
+}
+
+static void
+gpiopic_config_intr(struct gpiopic_intsrc *gisrc, uint32_t intr_mode) {
+ gisrc->is_mode = intr_mode;
+ GPIO_PIN_CONFIG_INTR(gisrc->is_pic->sc->sc_dev, gisrc->is_pin,
+ intr_mode);
+}
+
+static int
+gpiopic_check_intr_pin(struct gpiopic *gpiopic, uint32_t pin)
+{
+ struct gpiopic_intsrc *gisrc;
+
+ if (gpiopic == NULL)
+ return (ENOENT);
+ if (pin >= gpiopic->sc->sc_npins)
+ return (ENOENT);
+ gisrc = &gpiopic->intr_srcs[pin];
+ if (gisrc->is_event == NULL)
+ return (ENOENT);
+ return (0);
+}
+
+static int
+gpiopic_register_sources(struct gpiopic *gpiopic)
+{
+ char name[GPIOMAXNAME];
+ struct gpiobus_softc *sc = gpiopic->sc;
+ struct gpiopic_intsrc *gisrc;
+ int i, count;
+ int err;
+
+ count = 0;
+ for (i = 0; i < sc->sc_npins; i++) {
+ uint32_t pincaps;
+
+ err = GPIO_PIN_GETCAPS(sc->sc_dev, i, &pincaps);
+ if (err != 0)
+ continue;
+ if ((pincaps & GPIO_INTR_MASK) == GPIO_INTR_NONE)
+ continue;
+
+ gisrc = &gpiopic->intr_srcs[i];
+ gisrc->is_pic = gpiopic;
+ gisrc->is_pin = i;
+ gisrc->is_count = &gpiopic->intr_counts[i * 2];
+ gisrc->is_straycount = &gpiopic->intr_counts[i * 2 + 1];
+ gisrc->is_enabled = 0;
+ gisrc->is_mode = GPIO_INTR_CONFORM;
+
+ /*
+ * We use IE_BUS_PRIV to indicate that this interrupt event is
+ * completely private to this bus. It's managed by the bus and
+ * it is not visible to the global interrupt management.
+ * As a consequence, its number / vector is in the private
+ * space and is meaningless in the global space.
+ */
+ (void)gpiobus_pin_getname(gpiopic->sc->sc_busdev, i, name);
+ err = intr_event_create(&gisrc->is_event, gisrc, IE_BUS_PRIV,
+ i,
+ gpiopic_mask_intr, /* pre_ithread */
+ gpiopic_unmask_intr, /* post_ithread */
+ gpiopic_eoi, /* post_filter */
+ gpiopic_assign_cpu,
+ "%s", name);
+ if (err != 0) {
+ device_printf(sc->sc_busdev, "gpiopic failed to "
+ "create interrupt event for pin %u: %d\n", i, err);
+ continue;
+ }
+ count++;
+ }
+ return (count);
+}
+
+static void
+gpiobus_pic_init(struct gpiobus_softc *sc)
+{
+
+ struct sysctl_ctx_list *ctx;
+ struct sysctl_oid *tree_node;
+ struct sysctl_oid_list *tree;
+ struct gpiopic *gpiopic;
+ int i, count;
+ int err;
+
+ for (i = 0; i < sc->sc_npins; i++) {
+ uint32_t pincaps;
+
+ err = GPIO_PIN_GETCAPS(sc->sc_dev, i, &pincaps);
+ if (err != 0)
+ continue;
+ if ((pincaps & GPIO_INTR_MASK) != GPIO_INTR_NONE)
+ break;
+ }
+ if (i == sc->sc_npins)
+ return;
+
+ gpiopic = malloc(sizeof(struct gpiopic), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (gpiopic == NULL) {
+ device_printf(sc->sc_busdev, "gpiopic allocation failed\n");
+ return;
+ }
+
+ gpiopic->intr_srcs = malloc(sc->sc_npins *
+ sizeof(struct gpiopic_intsrc), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (gpiopic->intr_srcs == NULL) {
+ free(gpiopic, M_DEVBUF);
+ device_printf(sc->sc_busdev, "gpiopic allocation failed\n");
+ return;
+ }
+
+ gpiopic->intr_counts = malloc(2 * sc->sc_npins *
+ sizeof(*gpiopic->intr_counts), M_DEVBUF, M_NOWAIT | M_ZERO);
+ if (gpiopic->intr_counts == NULL) {
+ free(gpiopic->intr_srcs, M_DEVBUF);
+ free(gpiopic, M_DEVBUF);
+ device_printf(sc->sc_busdev, "gpiopic allocation failed\n");
+ return;
+ }
+
+ sx_init(&gpiopic->intrsrc_lock, "gpiopic lock");
+ gpiopic->sc = sc;
+ count = gpiopic_register_sources(gpiopic);
+ sc->sc_pic = gpiopic;
+ device_printf(sc->sc_busdev, "initialized %d interrupt sources\n",
+ count);
+
+ ctx = device_get_sysctl_ctx(sc->sc_busdev);
+ tree_node = device_get_sysctl_tree(sc->sc_busdev);
+ tree = SYSCTL_CHILDREN(tree_node);
+ SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "interrupts",
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ gpiopic, 0, sysctl_gpio_intrs, "A",
+ "interrupt:pin (current mode): count");
+}
+
+static void
+gpiobus_pic_destroy(struct gpiopic *gpiopic)
+{
+ struct gpiopic_intsrc *gisrc;
+ int i;
+
+ if (gpiopic == NULL)
+ return;
+
+ sx_xlock(&gpiopic->intrsrc_lock);
+ for (i = 0; i < gpiopic->sc->sc_npins; i++) {
+ gisrc = &gpiopic->intr_srcs[i];
+ if (gisrc->is_event == NULL)
+ continue;
+ /* XXX what to do if there is a busy event? */
+ (void)intr_event_destroy(gisrc->is_event);
+ }
+ sx_xunlock(&gpiopic->intrsrc_lock);
+
+ sx_destroy(&gpiopic->intrsrc_lock);
+ free(gpiopic->intr_counts, M_DEVBUF);
+ free(gpiopic->intr_srcs, M_DEVBUF);
+ free(gpiopic, M_DEVBUF);
+}
+
+
+static void
+gpiobus_pic_resume(struct gpiopic *gpiopic)
+{
+ struct gpiopic_intsrc *gisrc;
+ int i;
+
+ if (gpiopic == NULL)
+ return;
+
+ sx_xlock(&gpiopic->intrsrc_lock);
+ for (i = 0; i < gpiopic->sc->sc_npins; i++) {
+ gisrc = &gpiopic->intr_srcs[i];
+ if (gisrc->is_event == NULL)
+ continue;
+
+ /* Just in case. */
+ gpiopic_disable_intr(gisrc);
+
+ if (!gisrc->is_enabled)
+ continue;
+
+ if (gisrc->is_mode != GPIO_INTR_CONFORM)
+ gpiopic_config_intr(gisrc, gisrc->is_mode);
+ if (gisrc->is_masked)
+ gpiopic_mask_intr(gisrc);
+ else
+ gpiopic_unmask_intr(gisrc);
+ gpiopic_enable_intr(gisrc);
+ }
+ sx_xunlock(&gpiopic->intrsrc_lock);
+}
+
+#define GPIO_MAX_STRAY 10
+
+void
+gpiobus_handle_intr(device_t busdev, uint32_t pin)
+{
+ struct gpiobus_softc *sc;
+ struct gpiopic *gpiopic;
+ struct gpiopic_intsrc *gisrc;
+ struct intr_event *ie;
+
+ sc = device_get_softc(busdev);
+ gpiopic = sc->sc_pic;
+
+ KASSERT(pin < sc->sc_npins,
+ ("%s for unsupported pin %u", __func__, pin));
+ gisrc = &gpiopic->intr_srcs[pin];
+ ie = gisrc->is_event;
+
+ (*gisrc->is_count)++;
+
+ /*
+ * For stray interrupts, mask the source, bump the
+ * stray count, and log the condition.
+ */
+ if (intr_event_handle(ie, curthread->td_intr_frame) != 0) {
+ gpiopic_mask_intr(gisrc);
+ gpiopic_eoi(gisrc);
+ (*gisrc->is_straycount)++;
+ if (*gisrc->is_straycount < GPIO_MAX_STRAY) {
+ log(LOG_ERR, "stray irq on pin %u\n", pin);
+ } else if (*gisrc->is_straycount == GPIO_MAX_STRAY) {
+ log(LOG_CRIT, "too many stray irq's on pin %u: "
+ "not logging anymore\n", pin);
+ }
+ }
+}
+
+static int
+gpiopic_add_handler(const char *name, struct gpiopic *gpiopic, uint32_t pin,
+ driver_filter_t filter, driver_intr_t handler,
+ void *arg, enum intr_type flags, void **cookiep)
+{
+ struct gpiopic_intsrc *gisrc;
+ int err;
+
+ KASSERT(gpiopic_check_intr_pin(gpiopic, pin) == 0,
+ ("setup_intr for unsupported pin %u", pin));
+ gisrc = &gpiopic->intr_srcs[pin];
+ err = intr_event_add_handler(gisrc->is_event,
+ name, filter, handler, arg,
+ intr_priority(flags), flags, cookiep);
+ if (err == 0) {
+ sx_xlock(&gpiopic->intrsrc_lock);
+ gisrc->is_handlers++;
+ if (gisrc->is_handlers == 1) {
+ gpiopic_enable_intr(gisrc);
+ gpiopic_unmask_intr(gisrc); /* unmask source */
+ }
+ sx_xunlock(&gpiopic->intrsrc_lock);
+ }
+ return (err);
+}
+
+static int
+gpiobus_setup_intr(device_t bus, device_t dev, struct resource *irq,
+ int flags, driver_filter_t filter, void (*ihand)(void *),
+ void *arg, void **cookiep)
+{
+ struct gpiobus_softc *sc;
+ int err;
+
+ sc = device_get_softc(bus);
+ if (!rman_is_region_manager(irq, &sc->sc_intr_rman))
+ return (EINVAL);
+
+ *cookiep = NULL;
+ if ((rman_get_flags(irq) & RF_SHAREABLE) == 0)
+ flags |= INTR_EXCL;
+
+ err = rman_activate_resource(irq);
+ if (err != 0)
+ return (err);
+
+ err = gpiopic_add_handler(device_get_nameunit(dev), sc->sc_pic,
+ rman_get_start(irq), filter, ihand, arg, flags, cookiep);
+ return (err);
+}
+
+static int
+gpiopic_remove_handler(void *cookie)
+{
+ struct gpiopic *gpiopic;
+ struct gpiopic_intsrc *gisrc;
+ int err;
+
+ gisrc = intr_handler_source(cookie);
+ if (gisrc == NULL)
+ return (EINVAL);
+
+ gpiopic = gisrc->is_pic;
+
+ /*
+ * Decrease is_handlers counter upfront to prevent unnecessary unmasking
+ *
+ * It appears that event handler is called one last time inside
+ * intr_event_remove_handler() to let it remove itself, which
+ * consequently calls per_ and post_ithreads, the latter is causing call
+ * to gpiopic_unmask_pin(). Sometimes, delayed ithread execution leads
+ * to unmasking pin that already has no handlers installed.
+ * So, we need a bit of checking here: we decrease is_handlers before
+ * calling to intr_event_remove_handler(), then check if it's non-zero
+ * in gpiopic_unmask_pin() before doing actual unmask, thus preventing
+ * stray interrupts to appear on no longer used pin.
+ */
+
+ sx_xlock(&gpiopic->intrsrc_lock);
+ gisrc->is_handlers--;
+ sx_xunlock(&gpiopic->intrsrc_lock);
+
+ err = intr_event_remove_handler(cookie);
+ if (err == 0) {
+ sx_xlock(&gpiopic->intrsrc_lock);
+ if (gisrc->is_handlers == 0) {
+ gpiopic_mask_intr(gisrc); /* mask source */
+ gpiopic_disable_intr(gisrc);
+ }
+ sx_xunlock(&gpiopic->intrsrc_lock);
+ }
+ return (err);
+}
+
+static int
+gpiobus_teardown_intr(device_t bus, device_t dev, struct resource *r,
+ void *ih)
+{
+ struct gpiobus_softc *sc;
+ int err;
+
+ sc = device_get_softc(bus);
+ if (!rman_is_region_manager(r, &sc->sc_intr_rman))
+ return (EINVAL);
+ err = gpiopic_remove_handler(ih);
+ return (err);
+}
+
+static int
+gpiopic_describe(void *ih, const char *descr)
+{
+ struct gpiopic_intsrc *gisrc;
+ int err;
+
+ gisrc = intr_handler_source(ih);
+ if (gisrc == NULL)
+ return (EINVAL);
+ err = intr_event_describe_handler(gisrc->is_event, ih, descr);
+ return (err);
+}
+
+static int
+gpiobus_describe_intr(device_t bus, device_t child, struct resource *irq,
+ void *cookie, const char *descr)
+{
+ struct gpiobus_softc *sc;
+ int err;
+
+ sc = device_get_softc(bus);
+ if (!rman_is_region_manager(irq, &sc->sc_intr_rman))
+ return (EINVAL);
+ err = gpiopic_describe(cookie, descr);
+ return (err);
+}
+
+static int
+gpiobus_config_intr(device_t dev, int irq, enum intr_trigger trig,
+ enum intr_polarity pol)
+{
+ return (EOPNOTSUPP);
+}
+
+struct resource *
+gpio_alloc_intr_resource(device_t consumer_dev, int rid, u_int alloc_flags,
+ gpio_pin_t pin, uint32_t intr_mode)
+{
+ struct resource *res;
+ struct gpiobus_softc *sc;
+ struct gpiopic_intsrc *gisrc;
+ device_t busdev;
+ uint32_t caps;
+ int err;
+
+ switch (intr_mode) {
+ case GPIO_INTR_EDGE_FALLING:
+ case GPIO_INTR_EDGE_RISING:
+ case GPIO_INTR_EDGE_BOTH:
+ case GPIO_INTR_LEVEL_LOW:
+ case GPIO_INTR_LEVEL_HIGH:
+ case GPIO_INTR_CONFORM:
+ break;
+ default:
+ return (NULL);
+ }
+
+ err = GPIO_PIN_GETCAPS(pin->dev, pin->pin, &caps);
+ if (err != 0)
+ return (NULL);
+ if ((intr_mode & caps) == 0) {
+ dprintf("%s: intr_mode (0x%x) and caps (0x%x) mismatch!\n",
+ __func__, intr_mode, caps);
+ return (NULL);
+ }
+
+ busdev = GPIO_GET_BUS(pin->dev);
+ sc = device_get_softc(busdev);
+ err = gpiopic_check_intr_pin(sc->sc_pic, pin->pin);
+ if (err != 0) {
+ device_printf(busdev, "PIC not set up or bad pin %u\n",
+ pin->pin);
+ return (NULL);
+ }
+
+ KASSERT(sc->sc_pins[pin->pin].mapped,
+ ("%s: unmapped pin %u", __func__, pin->pin));
+ res = BUS_ALLOC_RESOURCE(busdev, consumer_dev, SYS_RES_IRQ, rid,
+ pin->pin, pin->pin, 1, alloc_flags);
+ if (res != NULL && intr_mode != GPIO_INTR_CONFORM) {
+ gisrc = &sc->sc_pic->intr_srcs[pin->pin];
+ gpiopic_config_intr(gisrc, intr_mode);
+ }
+ return (res);
+}
+#endif /* !INTRNG */
+
static device_method_t gpiobus_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, gpiobus_probe),
@@ -1143,9 +1734,16 @@
DEVMETHOD(device_resume, gpiobus_resume),
/* Bus interface */
+#ifdef INTRNG
DEVMETHOD(bus_setup_intr, bus_generic_setup_intr),
DEVMETHOD(bus_config_intr, bus_generic_config_intr),
DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr),
+#else
+ DEVMETHOD(bus_setup_intr, gpiobus_setup_intr),
+ DEVMETHOD(bus_config_intr, gpiobus_config_intr),
+ DEVMETHOD(bus_describe_intr, gpiobus_describe_intr),
+ DEVMETHOD(bus_teardown_intr, gpiobus_teardown_intr),
+#endif
DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource),
DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource),
DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource),
diff --git a/sys/dev/gpio/gpiobusvar.h b/sys/dev/gpio/gpiobusvar.h
--- a/sys/dev/gpio/gpiobusvar.h
+++ b/sys/dev/gpio/gpiobusvar.h
@@ -82,10 +82,13 @@
};
#endif
+struct gpiopic;
+
struct gpiobus_softc
{
struct mtx sc_mtx; /* bus mutex */
struct rman sc_intr_rman; /* isr resources */
+ struct gpiopic *sc_pic; /* interrupt controller logic */
device_t sc_busdev; /* bus device */
device_t sc_owner; /* bus owner */
device_t sc_dev; /* driver device */
@@ -172,5 +175,6 @@
int gpio_check_flags(uint32_t, uint32_t);
device_t gpiobus_add_bus(device_t);
int gpiobus_detach_bus(device_t);
+void gpiobus_handle_intr(device_t, uint32_t);
#endif /* __GPIOBUS_H__ */
diff --git a/sys/kern/kern_intr.c b/sys/kern/kern_intr.c
--- a/sys/kern/kern_intr.c
+++ b/sys/kern/kern_intr.c
@@ -284,8 +284,8 @@
struct intr_event *ie;
va_list ap;
- /* The only valid flag during creation is IE_SOFT. */
- if ((flags & ~IE_SOFT) != 0)
+ /* Check for internal flags. */
+ if ((flags & ~(IE_SOFT | IE_BUS_PRIV)) != 0)
return (EINVAL);
ie = malloc(sizeof(struct intr_event), M_ITHREAD, M_WAITOK | M_ZERO);
ie->ie_source = source;
@@ -436,6 +436,7 @@
TAILQ_FOREACH(ie, &event_list, ie_list)
if (ie->ie_irq == irq &&
(ie->ie_flags & IE_SOFT) == 0 &&
+ (ie->ie_flags & IE_BUS_PRIV) == 0 &&
CK_SLIST_FIRST(&ie->ie_handlers) != NULL)
break;
mtx_unlock(&event_lock);
diff --git a/sys/sys/interrupt.h b/sys/sys/interrupt.h
--- a/sys/sys/interrupt.h
+++ b/sys/sys/interrupt.h
@@ -131,6 +131,9 @@
#define IE_SOFT 0x000001 /* Software interrupt. */
#define IE_SLEEPABLE 0x000002 /* Sleepable ithread */
#define IE_ADDING_THREAD 0x000004 /* Currently building an ithread. */
+#define IE_BUS_PRIV 0x000008 /* Interrupt is handled at bus level,
+ invisible to nexus and MD interrupt
+ code. */
/* Flags to pass to swi_sched. */
#define SWI_FROMNMI 0x1

File Metadata

Mime Type
text/plain
Expires
Fri, Oct 2, 12:31 PM (17 h, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40048484
Default Alt Text
D57968.id187249.diff (33 KB)

Event Timeline