Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

uio: disable lazy irq disable to avoid double fire

uio_pdrv_genirq and uio_dmem_genirq interrupts are handled in
userspace. So the condition for the interrupt hasn't normally not been
cleared when top half returns. disable_irq_nosync is called in top half,
but since that normally is lazy the irq isn't actually disabled.

For level triggered interrupts this will always result in a spurious
additional fire since the level in to the interrupt controller still is
active. The actual interrupt handler isn't run though since this
spurious irq is just recorded, and later on discared (for level).

This commit disables lazy masking for level triggered interrupts. It
leaves edge triggered interrupts as before, because they work with the
lazy scheme.

All other UIO drivers already seem to clear the interrupt cause at
driver levels.

Example of double fire. First goes all the way up to
uio_pdrv_genirq_handler, second is terminated in handle_fasteoi_irq and
marked as pending.

<idle>-0 [000] d... 8.245870: gic_handle_irq: irq 29
<idle>-0 [000] d.h. 8.245873: uio_pdrv_genirq_handler: disable irq 29
<idle>-0 [000] d... 8.245878: gic_handle_irq: irq 29
<idle>-0 [000] d.h. 8.245880: handle_fasteoi_irq: irq 29 PENDING
HInt-34 [001] d... 8.245897: uio_pdrv_genirq_irqcontrol: enable irq 29

Tested on 5.7rc2 using uio_pdrv_genirq and a custom Xilinx MPSoC board.

Signed-off-by: Thommy Jakobsson <thommyj@gmail.com>
Link: https://lore.kernel.org/r/20200628141229.16121-1-thommyj@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Thommy Jakobsson and committed by
Greg Kroah-Hartman
415abcdf 7aca462b

+37
+19
drivers/uio/uio_dmem_genirq.c
··· 20 20 #include <linux/pm_runtime.h> 21 21 #include <linux/dma-mapping.h> 22 22 #include <linux/slab.h> 23 + #include <linux/irq.h> 23 24 24 25 #include <linux/of.h> 25 26 #include <linux/of_platform.h> ··· 200 199 goto bad1; 201 200 uioinfo->irq = ret; 202 201 } 202 + 203 + if (uioinfo->irq) { 204 + struct irq_data *irq_data = irq_get_irq_data(uioinfo->irq); 205 + 206 + /* 207 + * If a level interrupt, dont do lazy disable. Otherwise the 208 + * irq will fire again since clearing of the actual cause, on 209 + * device level, is done in userspace 210 + * irqd_is_level_type() isn't used since isn't valid until 211 + * irq is configured. 212 + */ 213 + if (irq_data && 214 + irqd_get_trigger_type(irq_data) & IRQ_TYPE_LEVEL_MASK) { 215 + dev_dbg(&pdev->dev, "disable lazy unmask\n"); 216 + irq_set_status_flags(uioinfo->irq, IRQ_DISABLE_UNLAZY); 217 + } 218 + } 219 + 203 220 uiomem = &uioinfo->mem[0]; 204 221 205 222 for (i = 0; i < pdev->num_resources; ++i) {
+18
drivers/uio/uio_pdrv_genirq.c
··· 20 20 #include <linux/stringify.h> 21 21 #include <linux/pm_runtime.h> 22 22 #include <linux/slab.h> 23 + #include <linux/irq.h> 23 24 24 25 #include <linux/of.h> 25 26 #include <linux/of_platform.h> ··· 169 168 else if (ret < 0) { 170 169 dev_err(&pdev->dev, "failed to get IRQ\n"); 171 170 return ret; 171 + } 172 + } 173 + 174 + if (uioinfo->irq) { 175 + struct irq_data *irq_data = irq_get_irq_data(uioinfo->irq); 176 + 177 + /* 178 + * If a level interrupt, dont do lazy disable. Otherwise the 179 + * irq will fire again since clearing of the actual cause, on 180 + * device level, is done in userspace 181 + * irqd_is_level_type() isn't used since isn't valid until 182 + * irq is configured. 183 + */ 184 + if (irq_data && 185 + irqd_get_trigger_type(irq_data) & IRQ_TYPE_LEVEL_MASK) { 186 + dev_dbg(&pdev->dev, "disable lazy unmask\n"); 187 + irq_set_status_flags(uioinfo->irq, IRQ_DISABLE_UNLAZY); 172 188 } 173 189 } 174 190