]> git.hungrycats.org Git - linux/commitdiff
net: dsa: mv88e6xxx: fix races between lock and irq freeing
authorUwe Kleine-König <u.kleine-koenig@pengutronix.de>
Fri, 20 Jul 2018 09:53:15 +0000 (11:53 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 28 Jul 2018 05:57:13 +0000 (07:57 +0200)
[ Upstream commit 3d82475ad46c0b65f2618b5f2bbb4cadbb5ac5d8 ]

free_irq() waits until all handlers for this IRQ have completed. As the
relevant handler (mv88e6xxx_g1_irq_thread_fn()) takes the chip's reg_lock
it might never return if the thread calling free_irq() holds this lock.

For the same reason kthread_cancel_delayed_work_sync() in the polling case
must not hold this lock.

Also first free the irq (or stop the worker respectively) such that
mv88e6xxx_g1_irq_thread_work() isn't called any more before the irq
mappings are dropped in mv88e6xxx_g1_irq_free_common() to prevent the
worker thread to call handle_nested_irq(0) which results in a NULL-pointer
exception.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/net/dsa/mv88e6xxx/chip.c

index 5b4374f21d76b873bff94afd466cb2a797025692..04371b0bba80ffed95cb330bf6a2e05e58b4b28d 100644 (file)
@@ -341,6 +341,7 @@ static const struct irq_domain_ops mv88e6xxx_g1_irq_domain_ops = {
        .xlate  = irq_domain_xlate_twocell,
 };
 
+/* To be called with reg_lock held */
 static void mv88e6xxx_g1_irq_free_common(struct mv88e6xxx_chip *chip)
 {
        int irq, virq;
@@ -360,9 +361,15 @@ static void mv88e6xxx_g1_irq_free_common(struct mv88e6xxx_chip *chip)
 
 static void mv88e6xxx_g1_irq_free(struct mv88e6xxx_chip *chip)
 {
-       mv88e6xxx_g1_irq_free_common(chip);
-
+       /*
+        * free_irq must be called without reg_lock taken because the irq
+        * handler takes this lock, too.
+        */
        free_irq(chip->irq, chip);
+
+       mutex_lock(&chip->reg_lock);
+       mv88e6xxx_g1_irq_free_common(chip);
+       mutex_unlock(&chip->reg_lock);
 }
 
 static int mv88e6xxx_g1_irq_setup_common(struct mv88e6xxx_chip *chip)
@@ -467,10 +474,12 @@ static int mv88e6xxx_irq_poll_setup(struct mv88e6xxx_chip *chip)
 
 static void mv88e6xxx_irq_poll_free(struct mv88e6xxx_chip *chip)
 {
-       mv88e6xxx_g1_irq_free_common(chip);
-
        kthread_cancel_delayed_work_sync(&chip->irq_poll_work);
        kthread_destroy_worker(chip->kworker);
+
+       mutex_lock(&chip->reg_lock);
+       mv88e6xxx_g1_irq_free_common(chip);
+       mutex_unlock(&chip->reg_lock);
 }
 
 int mv88e6xxx_wait(struct mv88e6xxx_chip *chip, int addr, int reg, u16 mask)
@@ -4286,12 +4295,10 @@ out_g2_irq:
        if (chip->info->g2_irqs > 0)
                mv88e6xxx_g2_irq_free(chip);
 out_g1_irq:
-       mutex_lock(&chip->reg_lock);
        if (chip->irq > 0)
                mv88e6xxx_g1_irq_free(chip);
        else
                mv88e6xxx_irq_poll_free(chip);
-       mutex_unlock(&chip->reg_lock);
 out:
        return err;
 }
@@ -4316,12 +4323,10 @@ static void mv88e6xxx_remove(struct mdio_device *mdiodev)
        if (chip->info->g2_irqs > 0)
                mv88e6xxx_g2_irq_free(chip);
 
-       mutex_lock(&chip->reg_lock);
        if (chip->irq > 0)
                mv88e6xxx_g1_irq_free(chip);
        else
                mv88e6xxx_irq_poll_free(chip);
-       mutex_unlock(&chip->reg_lock);
 }
 
 static const struct of_device_id mv88e6xxx_of_match[] = {