From c9ebbc31544202515d53644a0a48f2cfc0df4d4c Mon Sep 17 00:00:00 2001 From: Thomas Tai Date: Thu, 10 Nov 2016 12:10:17 -0800 Subject: [PATCH] sparc64: fix sun4v_build_irq NULL pointer dereference sun4v_build_irq assume the given irq number is valid and use it to get the handler pointer, the pointer is dereference without being checked and cause kernel panic. The cause of the invalid irq is that the tx/rx irq have never been free during device removal. irq number end up exhausted during continuous device add/removal test. tx/rx irq is allocated during vio_device_probe() using irq_alloc() and cookie_assign(). To free the tx/rx irq, cookie_unassign() and irq_free() is called when the device is removed. Orabug: 23082240 Signed-off-by: Thomas Tai Reviewed-by: Chris Hyser (cherry picked from commit 80043637b8fb1eabc16ab5947019f4dcdbb8c79f) Signed-off-by: Allen Pais --- arch/sparc/include/asm/irq_64.h | 1 + arch/sparc/kernel/irq_64.c | 56 +++++++++++++++++++++++++++++++++ arch/sparc/kernel/vio.c | 22 ++++++++++--- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/arch/sparc/include/asm/irq_64.h b/arch/sparc/include/asm/irq_64.h index c3c24f02a6ac..e9760de55db6 100644 --- a/arch/sparc/include/asm/irq_64.h +++ b/arch/sparc/include/asm/irq_64.h @@ -46,6 +46,7 @@ void irq_install_pre_handler(int irq, unsigned int build_irq(int inofixup, unsigned long iclr, unsigned long imap); unsigned int sun4v_build_irq(u32 devhandle, unsigned int devino); unsigned int sun4v_build_virq(u32 devhandle, unsigned int devino); +void sun4v_free_virq(int irq, u32 devhandle, unsigned int devino); unsigned int sun4v_build_msi(u32 devhandle, unsigned int *irq_p, unsigned int msi_devino_start, unsigned int msi_devino_end); diff --git a/arch/sparc/kernel/irq_64.c b/arch/sparc/kernel/irq_64.c index 7c15386e9201..787076a752c7 100644 --- a/arch/sparc/kernel/irq_64.c +++ b/arch/sparc/kernel/irq_64.c @@ -285,6 +285,15 @@ static void set_cookie(unsigned int devhandle, unsigned int devino) set_bit(nr, cookie_bitmap); } +static void clear_cookie(unsigned int devhandle, unsigned int devino) +{ + int nr = (int) (devhandle | devino); + + if (nr >= NR_COOKIE_BITS) + return; + clear_bit(nr, cookie_bitmap); +} + static unsigned int cookie_exists(u32 devhandle, unsigned int devino) { unsigned long hv_err, cookie; @@ -752,6 +761,25 @@ static unsigned long cookie_assign(unsigned int irq, u32 devhandle, return hv_error; } +static unsigned long cookie_unassign(u32 devhandle, unsigned int devino) +{ + unsigned long hv_error; + + /* set the cookie value to 0, so that the interrupt source is + * returned to the state of having no cookie assigned, + * and interrupts are explicitly disabled for the device. + */ + hv_error = sun4v_vintr_set_cookie(devhandle, devino, 0); + + /* clear the cookie bitmap */ + if (hv_error) + pr_err("HV vintr set cookie failed = %ld\n", hv_error); + else + clear_cookie(devhandle, devino); + + return hv_error; +} + static void cookie_handler_data(struct irq_handler_data *data, u32 devhandle, unsigned int devino) { @@ -767,6 +795,10 @@ static unsigned int cookie_build_irq(u32 devhandle, unsigned int devino, irq = sun4v_build_common(devhandle, devino, cookie_handler_data, chip); + /* ensure irq is valid */ + if (!irq) + return 0; + hv_error = cookie_assign(irq, devhandle, devino); if (hv_error) { irq_free(irq); @@ -776,6 +808,18 @@ static unsigned int cookie_build_irq(u32 devhandle, unsigned int devino, return irq; } +/* cookie_free_irq will reverse the effect of cookie_build_irq sequence, + * by unassign the cookie and free the irq resource + */ +static void cookie_free_irq(int irq, u32 devhandle, unsigned int devino) +{ + /* set vintr cookie to 0 and clear cookie bitmap */ + cookie_unassign(devhandle, devino); + + /* free irq_handler_data and irq desc */ + irq_free(irq); +} + static unsigned int sun4v_build_cookie(u32 devhandle, unsigned int devino) { unsigned int irq; @@ -866,6 +910,18 @@ out: return irq; } +void sun4v_free_virq(int irq, u32 devhandle, unsigned int devino) +{ + if (!irq) + return; + + /* clear IRQ_NOAUTOGEN which is set in sun4v_build_virq */ + irq_clear_status_flags(irq, IRQ_NOAUTOEN); + + /* undo the effect of sun4v_build_virq() */ + cookie_free_irq(irq, devhandle, devino); +} + void *hardirq_stack[NR_CPUS]; void *softirq_stack[NR_CPUS]; diff --git a/arch/sparc/kernel/vio.c b/arch/sparc/kernel/vio.c index 4e02b3df6d57..093efaea75f3 100644 --- a/arch/sparc/kernel/vio.c +++ b/arch/sparc/kernel/vio.c @@ -94,16 +94,30 @@ static int vio_device_remove(struct device *dev) { struct vio_dev *vdev = to_vio_dev(dev); struct vio_driver *drv = to_vio_driver(dev->driver); + int rc; if (drv->remove) { - return drv->remove(vdev); + rc = drv->remove(vdev); /* - * Ideally, we would remove/deallocate tx/rx virqs - * here - however, there are currently no support - * routines to do so at the moment. TBD + * Remove/deallocate tx/rx virqs unless the + * driver specified not to. */ + if (!drv->no_irq) { + if (vdev->tx_irq != 0) { + sun4v_free_virq(vdev->tx_irq, vdev->dev_handle, + vdev->tx_ino); + vdev->tx_irq = 0; + } + if (vdev->rx_irq != 0) { + sun4v_free_virq(vdev->rx_irq, vdev->dev_handle, + vdev->rx_ino); + vdev->rx_irq = 0; + } + } + + return rc; } return 1; -- 2.50.1