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 | |
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')
-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 */ | ||