aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pci/host
diff options
context:
space:
mode:
authorBjørn Erik Nilsen <ben@datarespons.no>2013-11-29 08:35:24 -0500
committerBjorn Helgaas <bhelgaas@google.com>2013-12-09 17:01:42 -0500
commitbe3f48cb21c1ca4907a0822eea406c8dd4a73ddb (patch)
tree656732d5a3b8a157dc1a37a205602829229d5690 /drivers/pci/host
parent6ce4eac1f600b34f2f7f58f9cd8f0503d79e42ae (diff)
PCI: designware: Fix crash in dw_msi_teardown_irq()
904d0e788993 ("PCI: designware: Add irq_create_mapping()") resulted in pre-allocated irq descs. Problem was that in assign_irq() these descs were explicitly allocated and hence also freed, resulting in a crash. We also need to clear the entire irq range in teardown. With this commit the teardown basically does exactly the opposite of what was done in setup. The crash this fixes looks like: Unable to handle kernel NULL pointer dereference at virtual address 00000020 PC is at dw_msi_teardown_irq+0x40/0x118 LR is at trace_hardirqs_on_caller+0xf4/0x1c0 Backtrace: [<802c401c>] (dw_msi_teardown_irq+0x0/0x118) from [<802c1844>] (arch_teardown_msi_irq+0x3c/0x40) [<802c1808>] (arch_teardown_msi_irq+0x0/0x40) from [<802c1a08>] (default_teardown_msi_irqs+0x68/0x84) [<802c19a0>] (default_teardown_msi_irqs+0x0/0x84) from [<802c1a34>] (arch_teardown_msi_irqs+0x10/0x14) [<802c1a24>] (arch_teardown_msi_irqs+0x0/0x14) from [<802c1ad0>] (free_msi_irqs+0x98/0x144) [<802c1a38>] (free_msi_irqs+0x0/0x144) from [<802c2570>] (pci_disable_msi+0x48/0x60) [<802c2528>] (pci_disable_msi+0x0/0x60) from [<7f0057d4>] (sxdma_irq_free+0x44/0x48 [sxdma]) [bhelgaas: add crash info] Tested-by: Mohit Kumar <mohit.kumar@st.com> Signed-off-by: Bjørn Erik Nilsen <ben@datarespons.no> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Acked-by: Marek Vasut <marex@denx.de> Acked-by: Jingoo Han <jg1.han@samsung.com>
Diffstat (limited to 'drivers/pci/host')
-rw-r--r--drivers/pci/host/pcie-designware.c49
1 files changed, 37 insertions, 12 deletions
diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c
index e33b68be0391..61345a18a6d1 100644
--- a/drivers/pci/host/pcie-designware.c
+++ b/drivers/pci/host/pcie-designware.c
@@ -209,6 +209,25 @@ static int find_valid_pos0(struct pcie_port *pp, int msgvec, int pos, int *pos0)
209 return 0; 209 return 0;
210} 210}
211 211
212static void clear_irq_range(struct pcie_port *pp, unsigned int irq_base,
213 unsigned int nvec, unsigned int pos)
214{
215 unsigned int i, res, bit, val;
216
217 i = 0;
218 while (i < nvec) {
219 irq_set_msi_desc_off(irq_base, i, NULL);
220 clear_bit(pos + i, pp->msi_irq_in_use);
221 /* Disable corresponding interrupt on MSI interrupt controller */
222 res = ((pos + i) / 32) * 12;
223 bit = (pos + i) % 32;
224 dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, &val);
225 val &= ~(1 << bit);
226 dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, val);
227 ++i;
228 }
229}
230
212static int assign_irq(int no_irqs, struct msi_desc *desc, int *pos) 231static int assign_irq(int no_irqs, struct msi_desc *desc, int *pos)
213{ 232{
214 int res, bit, irq, pos0, pos1, i; 233 int res, bit, irq, pos0, pos1, i;
@@ -242,11 +261,20 @@ static int assign_irq(int no_irqs, struct msi_desc *desc, int *pos)
242 if (!irq) 261 if (!irq)
243 goto no_valid_irq; 262 goto no_valid_irq;
244 263
264 /*
265 * irq_create_mapping (called from dw_pcie_host_init) pre-allocates
266 * descs so there is no need to allocate descs here. We can therefore
267 * assume that if irq_find_mapping above returns non-zero, then the
268 * descs are also successfully allocated.
269 */
270
245 i = 0; 271 i = 0;
246 while (i < no_irqs) { 272 while (i < no_irqs) {
273 if (irq_set_msi_desc_off(irq, i, desc) != 0) {
274 clear_irq_range(pp, irq, i, pos0);
275 goto no_valid_irq;
276 }
247 set_bit(pos0 + i, pp->msi_irq_in_use); 277 set_bit(pos0 + i, pp->msi_irq_in_use);
248 irq_alloc_descs((irq + i), (irq + i), 1, 0);
249 irq_set_msi_desc(irq + i, desc);
250 /*Enable corresponding interrupt in MSI interrupt controller */ 278 /*Enable corresponding interrupt in MSI interrupt controller */
251 res = ((pos0 + i) / 32) * 12; 279 res = ((pos0 + i) / 32) * 12;
252 bit = (pos0 + i) % 32; 280 bit = (pos0 + i) % 32;
@@ -266,7 +294,7 @@ no_valid_irq:
266 294
267static void clear_irq(unsigned int irq) 295static void clear_irq(unsigned int irq)
268{ 296{
269 int res, bit, val, pos; 297 unsigned int pos, nvec;
270 struct irq_desc *desc; 298 struct irq_desc *desc;
271 struct msi_desc *msi; 299 struct msi_desc *msi;
272 struct pcie_port *pp; 300 struct pcie_port *pp;
@@ -281,18 +309,15 @@ static void clear_irq(unsigned int irq)
281 return; 309 return;
282 } 310 }
283 311
312 /* undo what was done in assign_irq */
284 pos = data->hwirq; 313 pos = data->hwirq;
314 nvec = 1 << msi->msi_attrib.multiple;
285 315
286 irq_free_desc(irq); 316 clear_irq_range(pp, irq, nvec, pos);
287
288 clear_bit(pos, pp->msi_irq_in_use);
289 317
290 /* Disable corresponding interrupt on MSI interrupt controller */ 318 /* all irqs cleared; reset attributes */
291 res = (pos / 32) * 12; 319 msi->irq = 0;
292 bit = pos % 32; 320 msi->msi_attrib.multiple = 0;
293 dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, &val);
294 val &= ~(1 << bit);
295 dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, val);
296} 321}
297 322
298static int dw_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev, 323static int dw_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev,