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 #ifdef INTRNG #include +#else +#include +#include +#include +#include +#include +#include #endif #include #include @@ -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