| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (C) 2018 Google, Inc. */ |
| |
| #include "gasket_interrupt.h" |
| |
| #include "gasket_constants.h" |
| #include "gasket_core.h" |
| #include "gasket_sysfs.h" |
| #include <linux/device.h> |
| #include <linux/interrupt.h> |
| #include <linux/printk.h> |
| #ifdef GASKET_KERNEL_TRACE_SUPPORT |
| #define CREATE_TRACE_POINTS |
| #include <trace/events/gasket_interrupt.h> |
| #else |
| #define trace_gasket_interrupt_event(x, ...) |
| #endif |
| /* Retry attempts if the requested number of interrupts aren't available. */ |
| #define MSIX_RETRY_COUNT 3 |
| |
| /* Instance interrupt management data. */ |
| struct gasket_interrupt_data { |
| /* The name associated with this interrupt data. */ |
| const char *name; |
| |
| /* Interrupt type. See gasket_interrupt_type in gasket_core.h */ |
| int type; |
| |
| /* The PCI device [if any] associated with the owning device. */ |
| struct pci_dev *pci_dev; |
| |
| /* Set to 1 if MSI-X has successfully been configred, 0 otherwise. */ |
| int msix_configured; |
| |
| /* The number of interrupts requested by the owning device. */ |
| int num_interrupts; |
| |
| /* A pointer to the interrupt descriptor struct for this device. */ |
| const struct gasket_interrupt_desc *interrupts; |
| |
| /* The index of the bar into which interrupts should be mapped. */ |
| int interrupt_bar_index; |
| |
| /* The width of a single interrupt in a packed interrupt register. */ |
| int pack_width; |
| |
| /* |
| * Design-wise, these elements should be bundled together, but |
| * pci_enable_msix's interface requires that they be managed |
| * individually (requires array of struct msix_entry). |
| */ |
| |
| /* The number of successfully configured interrupts. */ |
| int num_configured; |
| |
| /* The MSI-X data for each requested/configured interrupt. */ |
| struct msix_entry *msix_entries; |
| |
| /* The eventfd "callback" data for each interrupt. */ |
| struct eventfd_ctx **eventfd_ctxs; |
| |
| /* The number of times each interrupt has been called. */ |
| ulong *interrupt_counts; |
| |
| /* Linux IRQ number. */ |
| int irq; |
| }; |
| |
| /* Structures to display interrupt counts in sysfs. */ |
| enum interrupt_sysfs_attribute_type { |
| ATTR_INTERRUPT_COUNTS, |
| }; |
| |
| /* Set up device registers for interrupt handling. */ |
| static void gasket_interrupt_setup(struct gasket_dev *gasket_dev) |
| { |
| int i; |
| int pack_shift; |
| ulong mask; |
| ulong value; |
| struct gasket_interrupt_data *interrupt_data = |
| gasket_dev->interrupt_data; |
| |
| if (!interrupt_data) { |
| dev_dbg(gasket_dev->dev, "Interrupt data is not initialized\n"); |
| return; |
| } |
| |
| dev_dbg(gasket_dev->dev, "Running interrupt setup\n"); |
| |
| /* Setup the MSIX table. */ |
| |
| for (i = 0; i < interrupt_data->num_interrupts; i++) { |
| /* |
| * If the interrupt is not packed, we can write the index into |
| * the register directly. If not, we need to deal with a read- |
| * modify-write and shift based on the packing index. |
| */ |
| dev_dbg(gasket_dev->dev, |
| "Setting up interrupt index %d with index 0x%llx and " |
| "packing %d\n", |
| interrupt_data->interrupts[i].index, |
| interrupt_data->interrupts[i].reg, |
| interrupt_data->interrupts[i].packing); |
| if (interrupt_data->interrupts[i].packing == UNPACKED) { |
| value = interrupt_data->interrupts[i].index; |
| } else { |
| switch (interrupt_data->interrupts[i].packing) { |
| case PACK_0: |
| pack_shift = 0; |
| break; |
| case PACK_1: |
| pack_shift = interrupt_data->pack_width; |
| break; |
| case PACK_2: |
| pack_shift = 2 * interrupt_data->pack_width; |
| break; |
| case PACK_3: |
| pack_shift = 3 * interrupt_data->pack_width; |
| break; |
| default: |
| dev_dbg(gasket_dev->dev, |
| "Found interrupt description with " |
| "unknown enum %d\n", |
| interrupt_data->interrupts[i].packing); |
| return; |
| } |
| |
| mask = ~(0xFFFF << pack_shift); |
| value = gasket_dev_read_64(gasket_dev, |
| interrupt_data->interrupt_bar_index, |
| interrupt_data->interrupts[i].reg); |
| value &= mask; |
| value |= interrupt_data->interrupts[i].index |
| << pack_shift; |
| } |
| gasket_dev_write_64(gasket_dev, value, |
| interrupt_data->interrupt_bar_index, |
| interrupt_data->interrupts[i].reg); |
| } |
| } |
| |
| static void |
| gasket_handle_interrupt(struct gasket_interrupt_data *interrupt_data, |
| int interrupt_index) |
| { |
| struct eventfd_ctx *ctx; |
| |
| trace_gasket_interrupt_event(interrupt_data->name, interrupt_index); |
| ctx = interrupt_data->eventfd_ctxs[interrupt_index]; |
| if (ctx) |
| eventfd_signal(ctx, 1); |
| |
| ++(interrupt_data->interrupt_counts[interrupt_index]); |
| } |
| |
| static irqreturn_t gasket_msix_interrupt_handler(int irq, void *dev_id) |
| { |
| struct gasket_interrupt_data *interrupt_data = dev_id; |
| int interrupt = -1; |
| int i; |
| |
| /* If this linear lookup is a problem, we can maintain a map/hash. */ |
| for (i = 0; i < interrupt_data->num_interrupts; i++) { |
| if (interrupt_data->msix_entries[i].vector == irq) { |
| interrupt = interrupt_data->msix_entries[i].entry; |
| break; |
| } |
| } |
| if (interrupt == -1) { |
| pr_err("Received unknown irq %d\n", irq); |
| return IRQ_HANDLED; |
| } |
| gasket_handle_interrupt(interrupt_data, interrupt); |
| return IRQ_HANDLED; |
| } |
| |
| static int |
| gasket_interrupt_msix_init(struct gasket_interrupt_data *interrupt_data) |
| { |
| int ret = 1; |
| int i; |
| |
| interrupt_data->msix_entries = |
| kcalloc(interrupt_data->num_interrupts, |
| sizeof(*interrupt_data->msix_entries), GFP_KERNEL); |
| if (!interrupt_data->msix_entries) |
| return -ENOMEM; |
| |
| for (i = 0; i < interrupt_data->num_interrupts; i++) { |
| interrupt_data->msix_entries[i].entry = i; |
| interrupt_data->msix_entries[i].vector = 0; |
| interrupt_data->eventfd_ctxs[i] = NULL; |
| } |
| |
| /* Retry MSIX_RETRY_COUNT times if not enough IRQs are available. */ |
| for (i = 0; i < MSIX_RETRY_COUNT && ret > 0; i++) |
| ret = pci_enable_msix_exact(interrupt_data->pci_dev, |
| interrupt_data->msix_entries, |
| interrupt_data->num_interrupts); |
| |
| if (ret) |
| return ret > 0 ? -EBUSY : ret; |
| interrupt_data->msix_configured = 1; |
| |
| for (i = 0; i < interrupt_data->num_interrupts; i++) { |
| ret = request_irq(interrupt_data->msix_entries[i].vector, |
| gasket_msix_interrupt_handler, 0, |
| interrupt_data->name, interrupt_data); |
| |
| if (ret) { |
| dev_err(&interrupt_data->pci_dev->dev, |
| "Cannot get IRQ for interrupt %d, vector %d; " |
| "%d\n", |
| i, interrupt_data->msix_entries[i].vector, ret); |
| return ret; |
| } |
| |
| interrupt_data->num_configured++; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * On QCM DragonBoard, we exit gasket_interrupt_msix_init() and kernel interrupt |
| * setup code with MSIX vectors masked. This is wrong because nothing else in |
| * the driver will normally touch the MSIX vectors. |
| * |
| * As a temporary hack, force unmasking there. |
| * |
| * TODO: Figure out why QCM kernel doesn't unmask the MSIX vectors, after |
| * gasket_interrupt_msix_init(), and remove this code. |
| */ |
| static void force_msix_interrupt_unmasking(struct gasket_dev *gasket_dev) |
| { |
| int i; |
| #define MSIX_VECTOR_SIZE 16 |
| #define MSIX_MASK_BIT_OFFSET 12 |
| #define APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE 0x46800 |
| for (i = 0; i < gasket_dev->interrupt_data->num_configured; i++) { |
| /* Check if the MSIX vector is unmasked */ |
| ulong location = APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE + |
| MSIX_MASK_BIT_OFFSET + i * MSIX_VECTOR_SIZE; |
| u32 mask = |
| gasket_dev_read_32(gasket_dev, |
| gasket_dev->interrupt_data->interrupt_bar_index, |
| location); |
| if (!(mask & 1)) |
| continue; |
| /* Unmask the msix vector (clear 32 bits) */ |
| gasket_dev_write_32(gasket_dev, 0, |
| gasket_dev->interrupt_data->interrupt_bar_index, |
| location); |
| } |
| #undef MSIX_VECTOR_SIZE |
| #undef MSIX_MASK_BIT_OFFSET |
| #undef APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE |
| } |
| |
| static ssize_t interrupt_sysfs_show(struct device *device, |
| struct device_attribute *attr, char *buf) |
| { |
| int i, ret; |
| ssize_t written = 0, total_written = 0; |
| struct gasket_interrupt_data *interrupt_data; |
| struct gasket_dev *gasket_dev; |
| struct gasket_sysfs_attribute *gasket_attr; |
| enum interrupt_sysfs_attribute_type sysfs_type; |
| |
| gasket_dev = gasket_sysfs_get_device_data(device); |
| if (!gasket_dev) { |
| dev_dbg(device, "No sysfs mapping found for device\n"); |
| return 0; |
| } |
| |
| gasket_attr = gasket_sysfs_get_attr(device, attr); |
| if (!gasket_attr) { |
| dev_dbg(device, "No sysfs attr data found for device\n"); |
| gasket_sysfs_put_device_data(device, gasket_dev); |
| return 0; |
| } |
| |
| sysfs_type = (enum interrupt_sysfs_attribute_type) |
| gasket_attr->data.attr_type; |
| interrupt_data = gasket_dev->interrupt_data; |
| switch (sysfs_type) { |
| case ATTR_INTERRUPT_COUNTS: |
| for (i = 0; i < interrupt_data->num_interrupts; ++i) { |
| written = |
| scnprintf(buf, PAGE_SIZE - total_written, |
| "0x%02x: %ld\n", i, |
| interrupt_data->interrupt_counts[i]); |
| total_written += written; |
| buf += written; |
| } |
| ret = total_written; |
| break; |
| default: |
| dev_dbg(gasket_dev->dev, "Unknown attribute: %s\n", |
| attr->attr.name); |
| ret = 0; |
| break; |
| } |
| |
| gasket_sysfs_put_attr(device, gasket_attr); |
| gasket_sysfs_put_device_data(device, gasket_dev); |
| return ret; |
| } |
| |
| static struct gasket_sysfs_attribute interrupt_sysfs_attrs[] = { |
| GASKET_SYSFS_RO(interrupt_counts, interrupt_sysfs_show, |
| ATTR_INTERRUPT_COUNTS), |
| GASKET_END_OF_ATTR_ARRAY, |
| }; |
| |
| int gasket_interrupt_init(struct gasket_dev *gasket_dev) |
| { |
| int ret; |
| struct gasket_interrupt_data *interrupt_data; |
| const struct gasket_driver_desc *driver_desc = |
| gasket_get_driver_desc(gasket_dev); |
| |
| interrupt_data = kzalloc(sizeof(*interrupt_data), GFP_KERNEL); |
| if (!interrupt_data) |
| return -ENOMEM; |
| gasket_dev->interrupt_data = interrupt_data; |
| interrupt_data->name = driver_desc->name; |
| interrupt_data->type = driver_desc->interrupt_type; |
| interrupt_data->pci_dev = gasket_dev->pci_dev; |
| interrupt_data->num_interrupts = driver_desc->num_interrupts; |
| interrupt_data->interrupts = driver_desc->interrupts; |
| interrupt_data->interrupt_bar_index = driver_desc->interrupt_bar_index; |
| interrupt_data->pack_width = driver_desc->interrupt_pack_width; |
| interrupt_data->num_configured = 0; |
| |
| interrupt_data->eventfd_ctxs = |
| kcalloc(driver_desc->num_interrupts, |
| sizeof(*interrupt_data->eventfd_ctxs), GFP_KERNEL); |
| if (!interrupt_data->eventfd_ctxs) { |
| kfree(interrupt_data); |
| return -ENOMEM; |
| } |
| |
| interrupt_data->interrupt_counts = |
| kcalloc(driver_desc->num_interrupts, |
| sizeof(*interrupt_data->interrupt_counts), GFP_KERNEL); |
| if (!interrupt_data->interrupt_counts) { |
| kfree(interrupt_data->eventfd_ctxs); |
| kfree(interrupt_data); |
| return -ENOMEM; |
| } |
| |
| switch (interrupt_data->type) { |
| case PCI_MSIX: |
| ret = gasket_interrupt_msix_init(interrupt_data); |
| if (ret) |
| break; |
| force_msix_interrupt_unmasking(gasket_dev); |
| break; |
| |
| default: |
| ret = -EINVAL; |
| } |
| |
| if (ret) { |
| /* Failing to setup interrupts will cause the device to report |
| * GASKET_STATUS_LAMED. But it is not fatal. |
| */ |
| dev_warn(gasket_dev->dev, |
| "Couldn't initialize interrupts: %d\n", ret); |
| return 0; |
| } |
| |
| gasket_interrupt_setup(gasket_dev); |
| gasket_sysfs_create_entries(gasket_dev->dev_info.device, |
| interrupt_sysfs_attrs); |
| |
| return 0; |
| } |
| |
| static void |
| gasket_interrupt_msix_cleanup(struct gasket_interrupt_data *interrupt_data) |
| { |
| int i; |
| |
| for (i = 0; i < interrupt_data->num_configured; i++) |
| free_irq(interrupt_data->msix_entries[i].vector, |
| interrupt_data); |
| interrupt_data->num_configured = 0; |
| |
| if (interrupt_data->msix_configured) |
| pci_disable_msix(interrupt_data->pci_dev); |
| interrupt_data->msix_configured = 0; |
| kfree(interrupt_data->msix_entries); |
| } |
| |
| int gasket_interrupt_reinit(struct gasket_dev *gasket_dev) |
| { |
| int ret; |
| |
| if (!gasket_dev->interrupt_data) { |
| dev_dbg(gasket_dev->dev, |
| "Attempted to reinit uninitialized interrupt data\n"); |
| return -EINVAL; |
| } |
| |
| switch (gasket_dev->interrupt_data->type) { |
| case PCI_MSIX: |
| gasket_interrupt_msix_cleanup(gasket_dev->interrupt_data); |
| ret = gasket_interrupt_msix_init(gasket_dev->interrupt_data); |
| if (ret) |
| break; |
| force_msix_interrupt_unmasking(gasket_dev); |
| break; |
| |
| default: |
| ret = -EINVAL; |
| } |
| |
| if (ret) { |
| /* Failing to setup interrupts will cause the device |
| * to report GASKET_STATUS_LAMED, but is not fatal. |
| */ |
| dev_warn(gasket_dev->dev, "Couldn't reinit interrupts: %d\n", |
| ret); |
| return 0; |
| } |
| |
| gasket_interrupt_setup(gasket_dev); |
| |
| return 0; |
| } |
| |
| /* See gasket_interrupt.h for description. */ |
| int gasket_interrupt_reset_counts(struct gasket_dev *gasket_dev) |
| { |
| dev_dbg(gasket_dev->dev, "Clearing interrupt counts\n"); |
| memset(gasket_dev->interrupt_data->interrupt_counts, 0, |
| gasket_dev->interrupt_data->num_interrupts * |
| sizeof(*gasket_dev->interrupt_data->interrupt_counts)); |
| return 0; |
| } |
| |
| /* See gasket_interrupt.h for description. */ |
| void gasket_interrupt_cleanup(struct gasket_dev *gasket_dev) |
| { |
| struct gasket_interrupt_data *interrupt_data = |
| gasket_dev->interrupt_data; |
| /* |
| * It is possible to get an error code from gasket_interrupt_init |
| * before interrupt_data has been allocated, so check it. |
| */ |
| if (!interrupt_data) |
| return; |
| |
| switch (interrupt_data->type) { |
| case PCI_MSIX: |
| gasket_interrupt_msix_cleanup(interrupt_data); |
| break; |
| |
| default: |
| break; |
| } |
| |
| kfree(interrupt_data->interrupt_counts); |
| kfree(interrupt_data->eventfd_ctxs); |
| kfree(interrupt_data); |
| gasket_dev->interrupt_data = NULL; |
| } |
| |
| int gasket_interrupt_system_status(struct gasket_dev *gasket_dev) |
| { |
| if (!gasket_dev->interrupt_data) { |
| dev_dbg(gasket_dev->dev, "Interrupt data is null\n"); |
| return GASKET_STATUS_DEAD; |
| } |
| |
| if (gasket_dev->interrupt_data->num_configured != |
| gasket_dev->interrupt_data->num_interrupts) { |
| dev_dbg(gasket_dev->dev, |
| "Not all interrupts were configured\n"); |
| return GASKET_STATUS_LAMED; |
| } |
| |
| return GASKET_STATUS_ALIVE; |
| } |
| |
| int gasket_interrupt_set_eventfd(struct gasket_interrupt_data *interrupt_data, |
| int interrupt, int event_fd) |
| { |
| struct eventfd_ctx *ctx = eventfd_ctx_fdget(event_fd); |
| |
| if (IS_ERR(ctx)) |
| return PTR_ERR(ctx); |
| |
| if (interrupt < 0 || interrupt >= interrupt_data->num_interrupts) |
| return -EINVAL; |
| |
| interrupt_data->eventfd_ctxs[interrupt] = ctx; |
| return 0; |
| } |
| |
| int gasket_interrupt_clear_eventfd(struct gasket_interrupt_data *interrupt_data, |
| int interrupt) |
| { |
| if (interrupt < 0 || interrupt >= interrupt_data->num_interrupts) |
| return -EINVAL; |
| |
| interrupt_data->eventfd_ctxs[interrupt] = NULL; |
| return 0; |
| } |