diff options
| author | Jiang Liu <jiang.liu@linux.intel.com> | 2014-11-12 05:39:03 -0500 |
|---|---|---|
| committer | Thomas Gleixner <tglx@linutronix.de> | 2014-11-23 07:01:47 -0500 |
| commit | f3cf8bb0d6c3c11ddedf01f02f856f2ae8c33aa4 (patch) | |
| tree | f61cecf6747f73d028a5f5e87f25a5cede929c39 /kernel/irq | |
| parent | 9dde55b72dc80bfae4280ddce5dbd69ba8240813 (diff) | |
genirq: Add generic msi irq domain support
Implement the basic functions for MSI interrupt support with
hierarchical interrupt domains.
[ tglx: Extracted and combined from several patches ]
Signed-off-by: Jiang Liu <jiang.liu@linux.intel.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Grant Likely <grant.likely@linaro.org>
Cc: Marc Zyngier <marc.zyngier@arm.com>
Cc: Yingjoe Chen <yingjoe.chen@mediatek.com>
Cc: Yijing Wang <wangyijing@huawei.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Diffstat (limited to 'kernel/irq')
| -rw-r--r-- | kernel/irq/Kconfig | 10 | ||||
| -rw-r--r-- | kernel/irq/Makefile | 1 | ||||
| -rw-r--r-- | kernel/irq/msi.c | 141 |
3 files changed, 152 insertions, 0 deletions
diff --git a/kernel/irq/Kconfig b/kernel/irq/Kconfig index 4f2eb2b1f23b..9a76e3beda54 100644 --- a/kernel/irq/Kconfig +++ b/kernel/irq/Kconfig | |||
| @@ -60,6 +60,16 @@ config IRQ_DOMAIN_HIERARCHY | |||
| 60 | bool | 60 | bool |
| 61 | select IRQ_DOMAIN | 61 | select IRQ_DOMAIN |
| 62 | 62 | ||
| 63 | # Generic MSI interrupt support | ||
| 64 | config GENERIC_MSI_IRQ | ||
| 65 | bool | ||
| 66 | |||
| 67 | # Generic MSI hierarchical interrupt domain support | ||
| 68 | config GENERIC_MSI_IRQ_DOMAIN | ||
| 69 | bool | ||
| 70 | select IRQ_DOMAIN_HIERARCHY | ||
| 71 | select GENERIC_MSI_IRQ | ||
| 72 | |||
| 63 | config HANDLE_DOMAIN_IRQ | 73 | config HANDLE_DOMAIN_IRQ |
| 64 | bool | 74 | bool |
| 65 | 75 | ||
diff --git a/kernel/irq/Makefile b/kernel/irq/Makefile index fff17381f0af..d12123526e2b 100644 --- a/kernel/irq/Makefile +++ b/kernel/irq/Makefile | |||
| @@ -6,3 +6,4 @@ obj-$(CONFIG_IRQ_DOMAIN) += irqdomain.o | |||
| 6 | obj-$(CONFIG_PROC_FS) += proc.o | 6 | obj-$(CONFIG_PROC_FS) += proc.o |
| 7 | obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o | 7 | obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o |
| 8 | obj-$(CONFIG_PM_SLEEP) += pm.o | 8 | obj-$(CONFIG_PM_SLEEP) += pm.o |
| 9 | obj-$(CONFIG_GENERIC_MSI_IRQ) += msi.o | ||
diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c new file mode 100644 index 000000000000..5e0cef4741d9 --- /dev/null +++ b/kernel/irq/msi.c | |||
| @@ -0,0 +1,141 @@ | |||
| 1 | /* | ||
| 2 | * linux/kernel/irq/msi.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 2014 Intel Corp. | ||
| 5 | * Author: Jiang Liu <jiang.liu@linux.intel.com> | ||
| 6 | * | ||
| 7 | * This file is licensed under GPLv2. | ||
| 8 | * | ||
| 9 | * This file contains common code to support Message Signalled Interrupt for | ||
| 10 | * PCI compatible and non PCI compatible devices. | ||
| 11 | */ | ||
| 12 | #include <linux/irq.h> | ||
| 13 | #include <linux/irqdomain.h> | ||
| 14 | #include <linux/msi.h> | ||
| 15 | |||
| 16 | #ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN | ||
| 17 | /** | ||
| 18 | * msi_domain_set_affinity - Generic affinity setter function for MSI domains | ||
| 19 | * @irq_data: The irq data associated to the interrupt | ||
| 20 | * @mask: The affinity mask to set | ||
| 21 | * @force: Flag to enforce setting (disable online checks) | ||
| 22 | * | ||
| 23 | * Intended to be used by MSI interrupt controllers which are | ||
| 24 | * implemented with hierarchical domains. | ||
| 25 | */ | ||
| 26 | int msi_domain_set_affinity(struct irq_data *irq_data, | ||
| 27 | const struct cpumask *mask, bool force) | ||
| 28 | { | ||
| 29 | struct irq_data *parent = irq_data->parent_data; | ||
| 30 | struct msi_msg msg; | ||
| 31 | int ret; | ||
| 32 | |||
| 33 | ret = parent->chip->irq_set_affinity(parent, mask, force); | ||
| 34 | if (ret >= 0 && ret != IRQ_SET_MASK_OK_DONE) { | ||
| 35 | BUG_ON(irq_chip_compose_msi_msg(irq_data, &msg)); | ||
| 36 | irq_chip_write_msi_msg(irq_data, &msg); | ||
| 37 | } | ||
| 38 | |||
| 39 | return ret; | ||
| 40 | } | ||
| 41 | |||
| 42 | static void msi_domain_activate(struct irq_domain *domain, | ||
| 43 | struct irq_data *irq_data) | ||
| 44 | { | ||
| 45 | struct msi_msg msg; | ||
| 46 | |||
| 47 | BUG_ON(irq_chip_compose_msi_msg(irq_data, &msg)); | ||
| 48 | irq_chip_write_msi_msg(irq_data, &msg); | ||
| 49 | } | ||
| 50 | |||
| 51 | static void msi_domain_deactivate(struct irq_domain *domain, | ||
| 52 | struct irq_data *irq_data) | ||
| 53 | { | ||
| 54 | struct msi_msg msg; | ||
| 55 | |||
| 56 | memset(&msg, 0, sizeof(msg)); | ||
| 57 | irq_chip_write_msi_msg(irq_data, &msg); | ||
| 58 | } | ||
| 59 | |||
| 60 | static int msi_domain_alloc(struct irq_domain *domain, unsigned int virq, | ||
| 61 | unsigned int nr_irqs, void *arg) | ||
| 62 | { | ||
| 63 | struct msi_domain_info *info = domain->host_data; | ||
| 64 | struct msi_domain_ops *ops = info->ops; | ||
| 65 | irq_hw_number_t hwirq = ops->get_hwirq(info, arg); | ||
| 66 | int i, ret; | ||
| 67 | |||
| 68 | if (irq_find_mapping(domain, hwirq) > 0) | ||
| 69 | return -EEXIST; | ||
| 70 | |||
| 71 | ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg); | ||
| 72 | if (ret < 0) | ||
| 73 | return ret; | ||
| 74 | |||
| 75 | for (i = 0; i < nr_irqs; i++) { | ||
| 76 | ret = ops->msi_init(domain, info, virq + i, hwirq + i, arg); | ||
| 77 | if (ret < 0) { | ||
| 78 | if (ops->msi_free) { | ||
| 79 | for (i--; i > 0; i--) | ||
| 80 | ops->msi_free(domain, info, virq + i); | ||
| 81 | } | ||
| 82 | irq_domain_free_irqs_top(domain, virq, nr_irqs); | ||
| 83 | return ret; | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | return 0; | ||
| 88 | } | ||
| 89 | |||
| 90 | static void msi_domain_free(struct irq_domain *domain, unsigned int virq, | ||
| 91 | unsigned int nr_irqs) | ||
| 92 | { | ||
| 93 | struct msi_domain_info *info = domain->host_data; | ||
| 94 | int i; | ||
| 95 | |||
| 96 | if (info->ops->msi_free) { | ||
| 97 | for (i = 0; i < nr_irqs; i++) | ||
| 98 | info->ops->msi_free(domain, info, virq + i); | ||
| 99 | } | ||
| 100 | irq_domain_free_irqs_top(domain, virq, nr_irqs); | ||
| 101 | } | ||
| 102 | |||
| 103 | static struct irq_domain_ops msi_domain_ops = { | ||
| 104 | .alloc = msi_domain_alloc, | ||
| 105 | .free = msi_domain_free, | ||
| 106 | .activate = msi_domain_activate, | ||
| 107 | .deactivate = msi_domain_deactivate, | ||
| 108 | }; | ||
| 109 | |||
| 110 | /** | ||
| 111 | * msi_create_irq_domain - Create a MSI interrupt domain | ||
| 112 | * @of_node: Optional device-tree node of the interrupt controller | ||
| 113 | * @info: MSI domain info | ||
| 114 | * @parent: Parent irq domain | ||
| 115 | */ | ||
| 116 | struct irq_domain *msi_create_irq_domain(struct device_node *of_node, | ||
| 117 | struct msi_domain_info *info, | ||
| 118 | struct irq_domain *parent) | ||
| 119 | { | ||
| 120 | struct irq_domain *domain; | ||
| 121 | |||
| 122 | domain = irq_domain_add_tree(of_node, &msi_domain_ops, info); | ||
| 123 | if (domain) | ||
| 124 | domain->parent = parent; | ||
| 125 | |||
| 126 | return domain; | ||
| 127 | } | ||
| 128 | |||
| 129 | /** | ||
| 130 | * msi_get_domain_info - Get the MSI interrupt domain info for @domain | ||
| 131 | * @domain: The interrupt domain to retrieve data from | ||
| 132 | * | ||
| 133 | * Returns the pointer to the msi_domain_info stored in | ||
| 134 | * @domain->host_data. | ||
| 135 | */ | ||
| 136 | struct msi_domain_info *msi_get_domain_info(struct irq_domain *domain) | ||
| 137 | { | ||
| 138 | return (struct msi_domain_info *)domain->host_data; | ||
| 139 | } | ||
| 140 | |||
| 141 | #endif /* CONFIG_GENERIC_MSI_IRQ_DOMAIN */ | ||
