Blame view

kernel/irq/msi.c 8.83 KB
f3cf8bb0d   Jiang Liu   genirq: Add gener...
1
2
3
4
5
6
7
8
9
10
11
  /*
   * linux/kernel/irq/msi.c
   *
   * Copyright (C) 2014 Intel Corp.
   * Author: Jiang Liu <jiang.liu@linux.intel.com>
   *
   * This file is licensed under GPLv2.
   *
   * This file contains common code to support Message Signalled Interrupt for
   * PCI compatible and non PCI compatible devices.
   */
aeeb59657   Jiang Liu   genirq: Provide d...
12
13
  #include <linux/types.h>
  #include <linux/device.h>
f3cf8bb0d   Jiang Liu   genirq: Add gener...
14
15
16
  #include <linux/irq.h>
  #include <linux/irqdomain.h>
  #include <linux/msi.h>
d9109698b   Jiang Liu   genirq: Introduce...
17
18
  /* Temparory solution for building, will be removed later */
  #include <linux/pci.h>
aa48b6f70   Jiang Liu   genirq/MSI: Move ...
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  struct msi_desc *alloc_msi_entry(struct device *dev)
  {
  	struct msi_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL);
  	if (!desc)
  		return NULL;
  
  	INIT_LIST_HEAD(&desc->list);
  	desc->dev = dev;
  
  	return desc;
  }
  
  void free_msi_entry(struct msi_desc *entry)
  {
  	kfree(entry);
  }
38b6a1cf3   Jiang Liu   PCI/MSI: Move cac...
35
36
37
38
39
40
41
42
43
44
45
46
  void __get_cached_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
  {
  	*msg = entry->msg;
  }
  
  void get_cached_msi_msg(unsigned int irq, struct msi_msg *msg)
  {
  	struct msi_desc *entry = irq_get_msi_desc(irq);
  
  	__get_cached_msi_msg(entry, msg);
  }
  EXPORT_SYMBOL_GPL(get_cached_msi_msg);
f3cf8bb0d   Jiang Liu   genirq: Add gener...
47
  #ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
74faaf7aa   Thomas Gleixner   genirq: Move irq_...
48
49
50
51
52
  static inline void irq_chip_write_msi_msg(struct irq_data *data,
  					  struct msi_msg *msg)
  {
  	data->chip->irq_write_msi_msg(data, msg);
  }
f3cf8bb0d   Jiang Liu   genirq: Add gener...
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
  /**
   * msi_domain_set_affinity - Generic affinity setter function for MSI domains
   * @irq_data:	The irq data associated to the interrupt
   * @mask:	The affinity mask to set
   * @force:	Flag to enforce setting (disable online checks)
   *
   * Intended to be used by MSI interrupt controllers which are
   * implemented with hierarchical domains.
   */
  int msi_domain_set_affinity(struct irq_data *irq_data,
  			    const struct cpumask *mask, bool force)
  {
  	struct irq_data *parent = irq_data->parent_data;
  	struct msi_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_chip_compose_msi_msg(irq_data, &msg));
  		irq_chip_write_msi_msg(irq_data, &msg);
  	}
  
  	return ret;
  }
  
  static void msi_domain_activate(struct irq_domain *domain,
  				struct irq_data *irq_data)
  {
  	struct msi_msg msg;
  
  	BUG_ON(irq_chip_compose_msi_msg(irq_data, &msg));
  	irq_chip_write_msi_msg(irq_data, &msg);
  }
  
  static void msi_domain_deactivate(struct irq_domain *domain,
  				  struct irq_data *irq_data)
  {
  	struct msi_msg msg;
  
  	memset(&msg, 0, sizeof(msg));
  	irq_chip_write_msi_msg(irq_data, &msg);
  }
  
  static int msi_domain_alloc(struct irq_domain *domain, unsigned int virq,
  			    unsigned int nr_irqs, void *arg)
  {
  	struct msi_domain_info *info = domain->host_data;
  	struct msi_domain_ops *ops = info->ops;
  	irq_hw_number_t hwirq = ops->get_hwirq(info, arg);
  	int i, ret;
  
  	if (irq_find_mapping(domain, hwirq) > 0)
  		return -EEXIST;
  
  	ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg);
  	if (ret < 0)
  		return ret;
  
  	for (i = 0; i < nr_irqs; i++) {
  		ret = ops->msi_init(domain, info, virq + i, hwirq + i, arg);
  		if (ret < 0) {
  			if (ops->msi_free) {
  				for (i--; i > 0; i--)
  					ops->msi_free(domain, info, virq + i);
  			}
  			irq_domain_free_irqs_top(domain, virq, nr_irqs);
  			return ret;
  		}
  	}
  
  	return 0;
  }
  
  static void msi_domain_free(struct irq_domain *domain, unsigned int virq,
  			    unsigned int nr_irqs)
  {
  	struct msi_domain_info *info = domain->host_data;
  	int i;
  
  	if (info->ops->msi_free) {
  		for (i = 0; i < nr_irqs; i++)
  			info->ops->msi_free(domain, info, virq + i);
  	}
  	irq_domain_free_irqs_top(domain, virq, nr_irqs);
  }
01364028b   Krzysztof Kozlowski   genirq: MSI: Cons...
138
  static const struct irq_domain_ops msi_domain_ops = {
f3cf8bb0d   Jiang Liu   genirq: Add gener...
139
140
141
142
143
  	.alloc		= msi_domain_alloc,
  	.free		= msi_domain_free,
  	.activate	= msi_domain_activate,
  	.deactivate	= msi_domain_deactivate,
  };
aeeb59657   Jiang Liu   genirq: Provide d...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
  #ifdef GENERIC_MSI_DOMAIN_OPS
  static irq_hw_number_t msi_domain_ops_get_hwirq(struct msi_domain_info *info,
  						msi_alloc_info_t *arg)
  {
  	return arg->hwirq;
  }
  
  static int msi_domain_ops_prepare(struct irq_domain *domain, struct device *dev,
  				  int nvec, msi_alloc_info_t *arg)
  {
  	memset(arg, 0, sizeof(*arg));
  	return 0;
  }
  
  static void msi_domain_ops_set_desc(msi_alloc_info_t *arg,
  				    struct msi_desc *desc)
  {
  	arg->desc = desc;
  }
  #else
  #define msi_domain_ops_get_hwirq	NULL
  #define msi_domain_ops_prepare		NULL
  #define msi_domain_ops_set_desc		NULL
  #endif /* !GENERIC_MSI_DOMAIN_OPS */
  
  static int msi_domain_ops_init(struct irq_domain *domain,
  			       struct msi_domain_info *info,
  			       unsigned int virq, irq_hw_number_t hwirq,
  			       msi_alloc_info_t *arg)
  {
  	irq_domain_set_hwirq_and_chip(domain, virq, hwirq, info->chip,
  				      info->chip_data);
  	if (info->handler && info->handler_name) {
  		__irq_set_handler(virq, info->handler, 0, info->handler_name);
  		if (info->handler_data)
  			irq_set_handler_data(virq, info->handler_data);
  	}
  	return 0;
  }
  
  static int msi_domain_ops_check(struct irq_domain *domain,
  				struct msi_domain_info *info,
  				struct device *dev)
  {
  	return 0;
  }
  
  static struct msi_domain_ops msi_domain_ops_default = {
  	.get_hwirq	= msi_domain_ops_get_hwirq,
  	.msi_init	= msi_domain_ops_init,
  	.msi_check	= msi_domain_ops_check,
  	.msi_prepare	= msi_domain_ops_prepare,
  	.set_desc	= msi_domain_ops_set_desc,
  };
  
  static void msi_domain_update_dom_ops(struct msi_domain_info *info)
  {
  	struct msi_domain_ops *ops = info->ops;
  
  	if (ops == NULL) {
  		info->ops = &msi_domain_ops_default;
  		return;
  	}
  
  	if (ops->get_hwirq == NULL)
  		ops->get_hwirq = msi_domain_ops_default.get_hwirq;
  	if (ops->msi_init == NULL)
  		ops->msi_init = msi_domain_ops_default.msi_init;
  	if (ops->msi_check == NULL)
  		ops->msi_check = msi_domain_ops_default.msi_check;
  	if (ops->msi_prepare == NULL)
  		ops->msi_prepare = msi_domain_ops_default.msi_prepare;
  	if (ops->set_desc == NULL)
  		ops->set_desc = msi_domain_ops_default.set_desc;
  }
  
  static void msi_domain_update_chip_ops(struct msi_domain_info *info)
  {
  	struct irq_chip *chip = info->chip;
0701c53e4   Marc Zyngier   genirq/msi: Do no...
223
  	BUG_ON(!chip || !chip->irq_mask || !chip->irq_unmask);
aeeb59657   Jiang Liu   genirq: Provide d...
224
225
226
  	if (!chip->irq_set_affinity)
  		chip->irq_set_affinity = msi_domain_set_affinity;
  }
f3cf8bb0d   Jiang Liu   genirq: Add gener...
227
228
  /**
   * msi_create_irq_domain - Create a MSI interrupt domain
be5436c83   Marc Zyngier   irqdomain/msi: Us...
229
   * @fwnode:	Optional fwnode of the interrupt controller
f3cf8bb0d   Jiang Liu   genirq: Add gener...
230
231
232
   * @info:	MSI domain info
   * @parent:	Parent irq domain
   */
be5436c83   Marc Zyngier   irqdomain/msi: Us...
233
  struct irq_domain *msi_create_irq_domain(struct fwnode_handle *fwnode,
f3cf8bb0d   Jiang Liu   genirq: Add gener...
234
235
236
  					 struct msi_domain_info *info,
  					 struct irq_domain *parent)
  {
aeeb59657   Jiang Liu   genirq: Provide d...
237
238
239
240
  	if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS)
  		msi_domain_update_dom_ops(info);
  	if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS)
  		msi_domain_update_chip_ops(info);
f3cf8bb0d   Jiang Liu   genirq: Add gener...
241

be5436c83   Marc Zyngier   irqdomain/msi: Us...
242
243
  	return irq_domain_create_hierarchy(parent, 0, 0, fwnode,
  					   &msi_domain_ops, info);
f3cf8bb0d   Jiang Liu   genirq: Add gener...
244
245
246
  }
  
  /**
d9109698b   Jiang Liu   genirq: Introduce...
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
   * msi_domain_alloc_irqs - Allocate interrupts from a MSI interrupt domain
   * @domain:	The domain to allocate from
   * @dev:	Pointer to device struct of the device for which the interrupts
   *		are allocated
   * @nvec:	The number of interrupts to allocate
   *
   * Returns 0 on success or an error code.
   */
  int msi_domain_alloc_irqs(struct irq_domain *domain, struct device *dev,
  			  int nvec)
  {
  	struct msi_domain_info *info = domain->host_data;
  	struct msi_domain_ops *ops = info->ops;
  	msi_alloc_info_t arg;
  	struct msi_desc *desc;
  	int i, ret, virq = -1;
  
  	ret = ops->msi_check(domain, info, dev);
  	if (ret == 0)
  		ret = ops->msi_prepare(domain, dev, nvec, &arg);
  	if (ret)
  		return ret;
  
  	for_each_msi_entry(desc, dev) {
  		ops->set_desc(&arg, desc);
aeeb59657   Jiang Liu   genirq: Provide d...
272
273
274
275
  		if (info->flags & MSI_FLAG_IDENTITY_MAP)
  			virq = (int)ops->get_hwirq(info, &arg);
  		else
  			virq = -1;
d9109698b   Jiang Liu   genirq: Introduce...
276

aeeb59657   Jiang Liu   genirq: Provide d...
277
  		virq = __irq_domain_alloc_irqs(domain, virq, desc->nvec_used,
d9109698b   Jiang Liu   genirq: Introduce...
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
  					       dev_to_node(dev), &arg, false);
  		if (virq < 0) {
  			ret = -ENOSPC;
  			if (ops->handle_error)
  				ret = ops->handle_error(domain, desc, ret);
  			if (ops->msi_finish)
  				ops->msi_finish(&arg, ret);
  			return ret;
  		}
  
  		for (i = 0; i < desc->nvec_used; i++)
  			irq_set_msi_desc_off(virq, i, desc);
  	}
  
  	if (ops->msi_finish)
  		ops->msi_finish(&arg, 0);
  
  	for_each_msi_entry(desc, dev) {
  		if (desc->nvec_used == 1)
  			dev_dbg(dev, "irq %d for MSI
  ", virq);
  		else
  			dev_dbg(dev, "irq [%d-%d] for MSI
  ",
  				virq, virq + desc->nvec_used - 1);
  	}
  
  	return 0;
  }
  
  /**
   * msi_domain_free_irqs - Free interrupts from a MSI interrupt @domain associated tp @dev
   * @domain:	The domain to managing the interrupts
   * @dev:	Pointer to device struct of the device for which the interrupts
   *		are free
   */
  void msi_domain_free_irqs(struct irq_domain *domain, struct device *dev)
  {
  	struct msi_desc *desc;
  
  	for_each_msi_entry(desc, dev) {
fe0c52fc0   Marc Zyngier   genirq: MSI: Fix ...
319
320
321
322
323
324
325
326
327
  		/*
  		 * We might have failed to allocate an MSI early
  		 * enough that there is no IRQ associated to this
  		 * entry. If that's the case, don't do anything.
  		 */
  		if (desc->irq) {
  			irq_domain_free_irqs(desc->irq, desc->nvec_used);
  			desc->irq = 0;
  		}
d9109698b   Jiang Liu   genirq: Introduce...
328
329
330
331
  	}
  }
  
  /**
f3cf8bb0d   Jiang Liu   genirq: Add gener...
332
333
334
335
336
337
338
339
340
341
342
343
   * msi_get_domain_info - Get the MSI interrupt domain info for @domain
   * @domain:	The interrupt domain to retrieve data from
   *
   * Returns the pointer to the msi_domain_info stored in
   * @domain->host_data.
   */
  struct msi_domain_info *msi_get_domain_info(struct irq_domain *domain)
  {
  	return (struct msi_domain_info *)domain->host_data;
  }
  
  #endif /* CONFIG_GENERIC_MSI_IRQ_DOMAIN */