>From 64c5440f685440bb7d92ab716013f9f54f21bca2 Mon Sep 17 00:00:00 2001 From: Yun Wu Date: Wed, 10 Dec 2014 10:32:58 +0800 Subject: [PATCH] MBI: initial support for message based interrupts This patch provides initial support for Message Based Interrupt (MBI), which is a write from the device to a special address which causes an interrupt to be received by the CPU. MBI is a generic mechanism not specific to any architectures or any subsystems. MSI/MSI-X defined in PCI specification are special MBIs. Signed-off-by: Yun Wu --- arch/arm64/Kconfig | 1 + include/linux/device.h | 3 + include/linux/irq.h | 22 ++++ include/linux/mbi.h | 95 ++++++++++++++++++ kernel/irq/Kconfig | 5 + kernel/irq/Makefile | 1 + kernel/irq/chip.c | 66 ++++++++++++ kernel/irq/mbi.c | 260 +++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 453 insertions(+) create mode 100644 include/linux/mbi.h create mode 100644 kernel/irq/mbi.c diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 1f49c288..ef55541 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -13,6 +13,7 @@ config ARM64 select ARM_GIC select AUDIT_ARCH_COMPAT_GENERIC select ARM_GIC_V3 + select MBI select ARM_GIC_V3_ITS if PCI_MSI select BUILDTIME_EXTABLE_SORT select CLONE_BACKWARDS diff --git a/include/linux/device.h b/include/linux/device.h index ce1f2160..e0618c2 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -715,6 +715,7 @@ struct acpi_dev_node { * gone away. This should be set by the allocator of the * device (i.e. the bus driver that discovered the device). * @iommu_group: IOMMU group the device belongs to. + * @mbi: Pointer to the data of message based interrupts. * * @offline_disabled: If set, the device is permanently online. * @offline: Set after successful invocation of bus type's .offline(). @@ -794,6 +795,8 @@ struct device { void (*release)(struct device *dev); struct iommu_group *iommu_group; + struct mbi_data *mbi; + bool offline_disabled:1; bool offline:1; }; diff --git a/include/linux/irq.h b/include/linux/irq.h index 8badf34..92e25e6 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -28,6 +28,7 @@ struct seq_file; struct module; +struct mbi_msg; struct msi_msg; /* @@ -120,6 +121,7 @@ enum { IRQ_SET_MASK_OK_DONE, }; +struct mbi_desc; struct msi_desc; struct irq_domain; @@ -139,6 +141,7 @@ struct irq_domain; * @handler_data: per-IRQ data for the irq_chip methods * @chip_data: platform-specific per-chip private data for the chip * methods, to allow shared chip implementations + * @mbi_desc: MBI descriptor * @msi_desc: MSI descriptor * @affinity: IRQ affinity on SMP * @@ -159,6 +162,7 @@ struct irq_data { #endif void *handler_data; void *chip_data; + struct mbi_desc *mbi_desc; struct msi_desc *msi_desc; cpumask_var_t affinity; }; @@ -323,6 +327,7 @@ static inline irq_hw_number_t irqd_to_hwirq(struct irq_data *d) * irq_request_resources * @irq_compose_msi_msg: optional to compose message content for MSI * @irq_write_msi_msg: optional to write message content for MSI + * @irq_compose_msg: optional to write message content for MBI * @flags: chip specific flags */ struct irq_chip { @@ -362,6 +367,8 @@ struct irq_chip { void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg); void (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg); + void (*irq_compose_msg)(struct irq_data *data, struct mbi_msg *msg); + unsigned long flags; }; @@ -567,10 +574,14 @@ extern int irq_set_chip(unsigned int irq, struct irq_chip *chip); extern int irq_set_handler_data(unsigned int irq, void *data); extern int irq_set_chip_data(unsigned int irq, void *data); extern int irq_set_irq_type(unsigned int irq, unsigned int type); +extern int irq_set_mbi_desc(unsigned int irq, struct mbi_desc *entry); +extern int irq_set_mbi_desc_range(unsigned int irq, struct mbi_desc *entry, + unsigned int count); extern int irq_set_msi_desc(unsigned int irq, struct msi_desc *entry); extern int irq_set_msi_desc_off(unsigned int irq_base, unsigned int irq_offset, struct msi_desc *entry); extern struct irq_data *irq_get_irq_data(unsigned int irq); +extern int irq_compose_mbi_msg(struct irq_data *data, struct mbi_msg *msg); static inline struct irq_chip *irq_get_chip(unsigned int irq) { @@ -605,6 +616,17 @@ static inline void *irq_data_get_irq_handler_data(struct irq_data *d) return d->handler_data; } +static inline struct mbi_desc *irq_get_mbi_desc(unsigned int irq) +{ + struct irq_data *d = irq_get_irq_data(irq); + return d ? d->mbi_desc : NULL; +} + +static inline struct mbi_desc *irq_data_get_mbi(struct irq_data *d) +{ + return d->mbi_desc; +} + static inline struct msi_desc *irq_get_msi_desc(unsigned int irq) { struct irq_data *d = irq_get_irq_data(irq); diff --git a/include/linux/mbi.h b/include/linux/mbi.h new file mode 100644 index 0000000..075a453 --- /dev/null +++ b/include/linux/mbi.h @@ -0,0 +1,95 @@ +#ifndef _LINUX_MBI_H +#define _LINUX_MBI_H + +#include +#include +#include + +struct mbi_data; + +/** + * struct mbi_msg - MBI message descriptor + * + * @address_lo: lower 32bit value of MBI address register + * @address_hi: higher 32bit value of MBI address register + * @data: data value of MBI data register + */ +struct mbi_msg { + u32 address_lo; + u32 address_hi; + u32 data; +}; + +/** + * struct mbi_desc - Message Based Interrupt (MBI) descriptor + * + * @entry: element in device MBI list + * @mbi: the device MBI data + * @data: message-specific unique data to identify an MBI + * @msg: message registers info of the MBI + * @irq: base linux interrupt number of the MBI + * @nvec: number of interrupts supported + */ +struct mbi_desc { + struct list_head entry; + struct mbi_data *mbi; + void *data; + struct mbi_msg msg; + unsigned int irq; + unsigned int nvec; +}; + +/** + * struct mbi_ops - MBI functions of MBI-capable device + * + * @write_msg: write message registers for an MBI + * @mask_irq: mask an MBI interrupt + * @unmask_irq: unmask an MBI interrupt + */ +struct mbi_ops { + void (*write_msg)(struct mbi_desc *desc, struct mbi_msg *msg); + void (*mask_irq)(struct mbi_desc *desc, int id); + void (*unmask_irq)(struct mbi_desc *desc, int id); +}; + +/** + * struct mbi_data - MBI information of a device + * + * @dev: the device owned the MBI data + * @ops: operations of the MBI-capable device + * @lock: spinlock for @mbi_list + * @mbi_list: list of MBI descriptors + * + * One MBI data for each MBI-capable device, holding device specific + * information. + */ +struct mbi_data { + struct device *dev; + struct mbi_ops *ops; + raw_spinlock_t lock; + struct list_head mbi_list; +}; + +/** + * struct mbi_domain_info - Infomation prepared for MBI domain + * + * @dev: the device owned the MBI data + * @ops: operations of the MBI-capable device + * @data: message-specific data to identify an MBI + * @nvec: maximum number of supported interrupts by the device + * + * The MBI-capable devices should provide this to their parent MBI + * domains when initializing MBIs. + */ +struct mbi_domain_info { + struct device *dev; + struct mbi_ops *ops; + void *data; + unsigned int nvec; +}; + +/* Create hierarchy MBI domain for interrupt controllers */ +struct irq_domain *mbi_create_irq_domain(struct device_node *of_node, + struct irq_domain *parent); + +#endif /* _LINUX_MBI_H */ diff --git a/kernel/irq/Kconfig b/kernel/irq/Kconfig index 9a76e3b..d1071cf 100644 --- a/kernel/irq/Kconfig +++ b/kernel/irq/Kconfig @@ -60,6 +60,11 @@ config IRQ_DOMAIN_HIERARCHY bool select IRQ_DOMAIN +# Support for Message Based Interrupt (MBI) +config MBI + bool "Support Message Based Interrupt (MBI)" + select IRQ_DOMAIN_HIERARCHY + # Generic MSI interrupt support config GENERIC_MSI_IRQ bool diff --git a/kernel/irq/Makefile b/kernel/irq/Makefile index d121235..1e5f4b8 100644 --- a/kernel/irq/Makefile +++ b/kernel/irq/Makefile @@ -6,4 +6,5 @@ obj-$(CONFIG_IRQ_DOMAIN) += irqdomain.o obj-$(CONFIG_PROC_FS) += proc.o obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o obj-$(CONFIG_PM_SLEEP) += pm.o +obj-$(CONFIG_MBI) += mbi.o obj-$(CONFIG_GENERIC_MSI_IRQ) += msi.o diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index 6f1c7a5..756d277 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -90,6 +90,45 @@ int irq_set_handler_data(unsigned int irq, void *data) EXPORT_SYMBOL(irq_set_handler_data); /** + * irq_set_mbi_desc - set MBI descriptor for an irq + * @irq: Interrupt number + * @entry: Pointer to MBI descriptor + */ +int irq_set_mbi_desc(unsigned int irq, struct mbi_desc *entry) +{ + unsigned long flags; + struct irq_desc *desc = irq_get_desc_lock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL); + + if (!desc) + return -EINVAL; + desc->irq_data.mbi_desc = entry; + irq_put_desc_unlock(desc, flags); + return 0; +} +EXPORT_SYMBOL(irq_set_mbi_desc); + +/** + * irq_set_mbi_desc_range - set MBI descriptor for a range of irqs + * @irq: Interrupt number + * @entry: Pointer to MBI descriptor + * @count: Number of interrupts to be set + */ +int irq_set_mbi_desc_range(unsigned int irq, struct mbi_desc *entry, + unsigned int count) +{ + int i, ret = 0; + + for (i = 0; i < count; i++) { + ret = irq_set_mbi_desc(irq + i, entry); + if (ret) + break; + } + + return ret; +} +EXPORT_SYMBOL(irq_set_mbi_desc_range); + +/** * irq_set_msi_desc_off - set MSI descriptor data for an irq at offset * @irq_base: Interrupt number base * @irq_offset: Interrupt number offset @@ -152,6 +191,33 @@ struct irq_data *irq_get_irq_data(unsigned int irq) } EXPORT_SYMBOL_GPL(irq_get_irq_data); +/** + * irq_compose_mbi_msg - Compose mbi message for an irq chip + * @data: Pointer to interrupt specific data + * @msg: Pointer to the MBI message + * + * For hierarchical domains we find the first chip in the hierarchy + * which implements the irq_compose_msg callback. For non hierarchical + * we use the top level chip. + */ +int irq_compose_mbi_msg(struct irq_data *data, struct mbi_msg *msg) +{ + struct irq_data *pos = NULL; + +#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY + for (; data; data = data->parent_data) +#endif + if (data->chip && data->chip->irq_compose_msg) + pos = data; + if (!pos) + return -ENOSYS; + + pos->chip->irq_compose_msg(pos, msg); + + return 0; +} +EXPORT_SYMBOL_GPL(irq_compose_mbi_msg); + static void irq_state_clr_disabled(struct irq_desc *desc) { irqd_clear(&desc->irq_data, IRQD_IRQ_DISABLED); diff --git a/kernel/irq/mbi.c b/kernel/irq/mbi.c new file mode 100644 index 0000000..1b1f108 --- /dev/null +++ b/kernel/irq/mbi.c @@ -0,0 +1,260 @@ +/** + * Copyright (C) 2014, Yun Wu + * + * This file contains the generic interfaces for Message Based + * Interrupts (MBI). + */ + +#include +#include +#include + +/** + * mbi_find_desc() - search an MBI descriptor from MBI data + * @mbi: MBI data of the device + * @data: the unique MBI-specific data + */ +static struct mbi_desc *mbi_find_desc(struct device *dev, void *data) +{ + struct mbi_desc *desc; + + list_for_each_entry(desc, &dev->mbi->mbi_list, entry) { + if (desc->data == data) + return desc; + } + + return NULL; +} + +/** + * mbi_init() - initialize MBI data + * @dev: the device owned the MBI data + * @ops: operations of the MBI-capable device + * @pmbi: pointer to the pointer of MBI data + */ +static int mbi_init(struct device *dev, struct mbi_ops *ops, + struct mbi_data **pmbi) +{ + struct mbi_data *mbi = dev->mbi; + + if (mbi) + goto out_done; + if (!ops) + return -EINVAL; + + mbi = kzalloc(sizeof(*mbi), GFP_KERNEL); + if (!mbi) + return -ENOMEM; + + mbi->dev = dev; + mbi->ops = ops; + raw_spin_lock_init(&mbi->lock); + INIT_LIST_HEAD(&mbi->mbi_list); + dev->mbi = mbi; + +out_done: + *pmbi = mbi; + return 0; +} + +/** + * mbi_free_desc() - free an MBI descriptor + * @desc: the MBI descriptor to be freed + */ +static void mbi_free_desc(struct mbi_desc *desc) +{ + struct mbi_data *mbi; + + mbi = desc ? desc->mbi : NULL; + BUG_ON(!mbi); + + raw_spin_lock(&mbi->lock); + list_del(&desc->entry); + if (list_empty(&mbi->mbi_list)) + mbi->dev->mbi = NULL; + raw_spin_unlock(&mbi->lock); + + kfree(desc); + kfree(mbi); +} + +/** + * mbi_alloc_desc() - allocate an MBI descriptor + * @info: infomation prepared for the MBI domain + * @virq: the base linux interrupt number of the MBI + * @pdesc: pointer to the pointer of MBI descriptor + */ +static int mbi_alloc_desc(struct mbi_domain_info *info, unsigned int virq, + struct mbi_desc **pdesc) +{ + struct mbi_data *mbi = NULL; + struct mbi_desc *desc; + int ret; + + ret = mbi_init(info->dev, info->ops, &mbi); + if (ret) + return ret; + + desc = mbi_find_desc(info->dev, info->data); + if (desc) + goto out_done; + + desc = kzalloc(sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + desc->mbi = mbi; + desc->irq = virq; + desc->data = info->data; + desc->nvec = info->nvec; + + raw_spin_lock(&mbi->lock); + list_add(&desc->entry, &mbi->mbi_list); + raw_spin_unlock(&mbi->lock); + +out_done: + *pdesc = desc; + return 0; +} + +/** + * mbi_config_irq() - config one MBI interrupt + * @data: irq_data of the MBI interrupt + * @mask: type of configuration, mask if true, otherwise unmask + */ +static void mbi_config_irq(struct irq_data *data, int mask) +{ + struct mbi_desc *desc = data->mbi_desc; + struct mbi_ops *ops = desc->mbi->ops; + unsigned int id = data->irq - desc->irq; + void (*fn)(struct mbi_desc *, int) = NULL; + + if (ops) + fn = mask ? ops->mask_irq : ops->unmask_irq; + if (fn) + fn(desc, id); +} + +static void mbi_mask_irq(struct irq_data *data) +{ + mbi_config_irq(data, 1); +} + +static void mbi_unmask_irq(struct irq_data *data) +{ + mbi_config_irq(data, 0); +} + +static void mbi_write_msg(struct mbi_desc *desc, struct mbi_msg *msg) +{ + desc->mbi->ops->write_msg(desc, msg); + desc->msg = *msg; +} + +static int mbi_set_affinity(struct irq_data *irq_data, + const struct cpumask *mask, bool force) +{ + struct irq_data *parent = irq_data->parent_data; + struct mbi_msg msg; + int ret; + + ret = parent->chip->irq_set_affinity(parent, mask, force); + if (ret >= 0 && ret != IRQ_SET_MASK_OK_DONE) { + BUG_ON(irq_compose_mbi_msg(irq_data, &msg)); + mbi_write_msg(irq_data->mbi_desc, &msg); + } + + return ret; +} + +static struct irq_chip mbi_irq_chip = { + .name = "MBI", + .irq_unmask = mbi_unmask_irq, + .irq_mask = mbi_mask_irq, + .irq_ack = irq_chip_ack_parent, + .irq_set_affinity = mbi_set_affinity, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .flags = IRQCHIP_SKIP_SET_WAKE, +}; + +static void mbi_domain_activate(struct irq_domain *domain, struct irq_data *data) +{ + struct mbi_desc *desc = data->mbi_desc; + struct mbi_msg msg; + + WARN_ON(domain != data->domain); + BUG_ON(irq_compose_mbi_msg(data, &msg)); + mbi_write_msg(desc, &msg); +} + +static void mbi_domain_deactivate(struct irq_domain *domain, struct irq_data *data) +{ + struct mbi_desc *desc = data->mbi_desc; + struct mbi_msg msg; + + WARN_ON(domain != data->domain); + memset(&msg, 0, sizeof(msg)); + mbi_write_msg(desc, &msg); +} + +static int mbi_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + struct mbi_domain_info *info = arg; + struct mbi_desc *desc; + int ret, i; + + ret = mbi_alloc_desc(info, virq, &desc); + if (ret) + return ret; + + ret = irq_set_mbi_desc_range(virq, desc, nr_irqs); + if (unlikely(ret)) + goto out_free_desc; + + for (i = 0; i < nr_irqs; i++) + irq_domain_set_hwirq_and_chip(domain, virq + i, virq + i, + &mbi_irq_chip, NULL); + + ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, NULL); + if (ret) + goto out_unset_desc; + + return 0; + +out_unset_desc: + irq_set_mbi_desc_range(virq, NULL, nr_irqs); +out_free_desc: + mbi_free_desc(desc); + return ret; +} + +static void mbi_domain_free(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs) +{ + struct mbi_desc *desc = irq_get_mbi_desc(virq); + + irq_set_mbi_desc_range(virq, NULL, nr_irqs); + mbi_free_desc(desc); + irq_domain_free_irqs_top(domain, virq, nr_irqs); +} + +static struct irq_domain_ops mbi_domain_ops = { + .alloc = mbi_domain_alloc, + .free = mbi_domain_free, + .activate = mbi_domain_activate, + .deactivate = mbi_domain_deactivate, +}; + +struct irq_domain *mbi_create_irq_domain(struct device_node *of_node, + struct irq_domain *parent) +{ + struct irq_domain *domain; + + domain = irq_domain_add_tree(of_node, &mbi_domain_ops, NULL); + if (!domain) + return NULL; + + domain->parent = parent; + return domain; +} -- 1.8.0