diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index bb70b7177f94e7..56f1b9cd1cfa01 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -571,4 +571,12 @@ config LOONGSON_PCH_MSI help Support for the Loongson PCH MSI Controller. +config IMS_MSI + bool "IMS Interrupt Message Store MSI controller" + depends on PCI + select DEVICE_MSI + help + Support for IMS Interrupt Message Store MSI controller + with IMS slot storage in a slot array + endmenu diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 133f9c45744a2f..5ccd8336f8e3fb 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -111,3 +111,4 @@ obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o obj-$(CONFIG_LOONGSON_HTVEC) += irq-loongson-htvec.o obj-$(CONFIG_LOONGSON_PCH_PIC) += irq-loongson-pch-pic.o obj-$(CONFIG_LOONGSON_PCH_MSI) += irq-loongson-pch-msi.o +obj-$(CONFIG_IMS_MSI) += irq-ims-msi.o diff --git a/drivers/irqchip/irq-ims-msi.c b/drivers/irqchip/irq-ims-msi.c new file mode 100644 index 00000000000000..0bb5480a2fc193 --- /dev/null +++ b/drivers/irqchip/irq-ims-msi.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0 +// (C) Copyright 2020 Thomas Gleixner +/* + * Shared interrupt chip and irq domain for Intel IMS devices + */ +#include +#include +#include +#include + +#include + +struct ims_data { + struct ims_array_info info; + unsigned long map[0]; +}; + +static void ims_mask_irq(struct irq_data *data) +{ + struct msi_desc *desc = irq_data_get_msi_desc(data); + struct ims_array_slot __iomem *slot = desc->device_msi.priv_iomem; + u32 __iomem *ctrl = &slot->ctrl; + + iowrite32(ioread32(ctrl) & ~IMS_VECTOR_CTRL_UNMASK, ctrl); +} + +static void ims_unmask_irq(struct irq_data *data) +{ + struct msi_desc *desc = irq_data_get_msi_desc(data); + struct ims_array_slot __iomem *slot = desc->device_msi.priv_iomem; + u32 __iomem *ctrl = &slot->ctrl; + + iowrite32(ioread32(ctrl) | IMS_VECTOR_CTRL_UNMASK, ctrl); +} + +static void ims_write_msi_msg(struct irq_data *data, struct msi_msg *msg) +{ + struct msi_desc *desc = irq_data_get_msi_desc(data); + struct ims_array_slot __iomem *slot = desc->device_msi.priv_iomem; + + iowrite32(msg->address_lo, &slot->address_lo); + iowrite32(msg->address_hi, &slot->address_hi); + iowrite32(msg->data, &slot->data); +} + +static const struct irq_chip ims_msi_controller = { + .name = "IMS", + .irq_mask = ims_mask_irq, + .irq_unmask = ims_unmask_irq, + .irq_write_msi_msg = ims_write_msi_msg, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .flags = IRQCHIP_SKIP_SET_WAKE, +}; + +static void ims_reset_slot(struct ims_array_slot __iomem *slot) +{ + iowrite32(0, &slot->address_lo); + iowrite32(0, &slot->address_hi); + iowrite32(0, &slot->data); + iowrite32(0, &slot->ctrl); +} + +static void ims_free_msi_store(struct irq_domain *domain, struct device *dev) +{ + struct msi_domain_info *info = domain->host_data; + struct ims_data *ims = info->data; + struct msi_desc *entry; + + for_each_msi_entry(entry, dev) { + if (entry->device_msi.priv_iomem) { + clear_bit(entry->device_msi.hwirq, ims->map); + ims_reset_slot(entry->device_msi.priv_iomem); + entry->device_msi.priv_iomem = NULL; + entry->device_msi.hwirq = 0; + } + } +} + +static int ims_alloc_msi_store(struct irq_domain *domain, struct device *dev, + int nvec) +{ + struct msi_domain_info *info = domain->host_data; + struct ims_data *ims = info->data; + struct msi_desc *entry; + + for_each_msi_entry(entry, dev) { + unsigned int idx; + + idx = find_first_zero_bit(ims->map, ims->info.max_slots); + if (idx >= ims->info.max_slots) + goto fail; + set_bit(idx, ims->map); + entry->device_msi.priv_iomem = &ims->info.slots[idx]; + entry->device_msi.hwirq = idx; + } + return 0; + +fail: + ims_free_msi_store(domain, dev); + return -ENOSPC; +} + +struct ims_domain_template { + struct msi_domain_ops ops; + struct msi_domain_info info; +}; + +static const struct ims_domain_template ims_domain_template = { + .ops = { + .msi_alloc_store = ims_alloc_msi_store, + .msi_free_store = ims_free_msi_store, + }, + .info = { + .flags = MSI_FLAG_USE_DEF_DOM_OPS | + MSI_FLAG_USE_DEF_CHIP_OPS, + .handler = handle_edge_irq, + .handler_name = "edge", + }, +}; + +struct irq_domain * +pci_ims_create_msi_irq_domain(struct pci_dev *pdev, + struct ims_array_info *ims_info) +{ + struct ims_domain_template *info; + struct irq_domain *domain; + struct irq_chip *chip; + struct ims_data *data; + unsigned int size; + + /* Allocate new domain storage */ + info = kmemdup(&ims_domain_template, sizeof(ims_domain_template), + GFP_KERNEL); + if (!info) + return NULL; + /* Link the ops */ + info->info.ops = &info->ops; + + /* Allocate ims_info along with the bitmap */ + size = sizeof(*data); + size += BITS_TO_LONGS(ims_info->max_slots) * sizeof(unsigned long); + data = kzalloc(size, GFP_KERNEL); + if (!data) + goto err_info; + + data->info = *ims_info; + info->info.data = data; + + chip = kmemdup(&ims_msi_controller, sizeof(ims_msi_controller), + GFP_KERNEL); + if (!chip) + goto err_data; + info->info.chip = chip; + + domain = pci_subdevice_msi_create_irq_domain(pdev, &info->info); + if (!domain) + goto err_chip; + + return domain; + +err_chip: + kfree(chip); +err_data: + kfree(data); +err_info: + kfree(info); + return NULL; +} +EXPORT_SYMBOL_GPL(pci_ims_create_msi_irq_domain); diff --git a/include/linux/irqchip/irq-ims-msi.h b/include/linux/irqchip/irq-ims-msi.h new file mode 100644 index 00000000000000..b1ccc9dd2d4c11 --- /dev/null +++ b/include/linux/irqchip/irq-ims-msi.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* (C) Copyright 2020 Thomas Gleixner */ + +#ifndef _LINUX_IRQCHIP_IRQ_IMS_MSI_H +#define _LINUX_IRQCHIP_IRQ_IMS_MSI_H + +#include + +struct ims_array_slot { + u32 address_lo; + u32 address_hi; + u32 data; + u32 ctrl; +}; + +/* Bit to unmask the interrupt in slot->ctrl */ +#define IMS_VECTOR_CTRL_UNMASK 0x01 + +struct ims_array_info { + struct ims_array_slot __iomem *slots; + unsigned int max_slots; +}; + +/* Dummy forward declaration for illustration */ +struct ims_queue_slot; + +/** + * ims_msi_store - Interrupt Message Store descriptor data + * @array_slot: Pointer to a on device IMS storage array slot + * @queue_slot: Pointer to storage embedded in queue data + * @hw_irq: Index of the slot or queue. Also hardware irq number + */ +struct ims_msi_store { + union { + struct ims_array_slot __iomem *array_slot; + struct ims_queue_slot *queue_slot; + }; + unsigned int hw_irq; +}; + +#endif