um: Fix IRQ controller regression on console read
authorJouni Malinen <j@w1.fi>
Mon, 6 May 2019 12:39:35 +0000 (14:39 +0200)
committerRichard Weinberger <richard@nod.at>
Tue, 2 Jul 2019 21:26:52 +0000 (23:26 +0200)
The conversion of UML to use epoll based IRQ controller claimed that
clone_one_chan() can safely call um_free_irq() while starting to ignore
the delay_free_irq parameter that explicitly noted that the IRQ cannot
be freed because this is being called from chan_interrupt(). This
resulted in free_irq() getting called in interrupt context ("Trying to
free IRQ 6 from IRQ context!").

Fix this by restoring previously used delay_free_irq processing.

Fixes: ff6a17989c08 ("Epoll based IRQ controller")
Signed-off-by: Jouni Malinen <j@w1.fi>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Richard Weinberger <richard@nod.at>
arch/um/drivers/chan_kern.c
arch/um/kernel/irq.c

index a4e64ed..749d2bf 100644 (file)
@@ -171,19 +171,55 @@ int enable_chan(struct line *line)
        return err;
 }
 
+/* Items are added in IRQ context, when free_irq can't be called, and
+ * removed in process context, when it can.
+ * This handles interrupt sources which disappear, and which need to
+ * be permanently disabled.  This is discovered in IRQ context, but
+ * the freeing of the IRQ must be done later.
+ */
+static DEFINE_SPINLOCK(irqs_to_free_lock);
+static LIST_HEAD(irqs_to_free);
+
+void free_irqs(void)
+{
+       struct chan *chan;
+       LIST_HEAD(list);
+       struct list_head *ele;
+       unsigned long flags;
+
+       spin_lock_irqsave(&irqs_to_free_lock, flags);
+       list_splice_init(&irqs_to_free, &list);
+       spin_unlock_irqrestore(&irqs_to_free_lock, flags);
+
+       list_for_each(ele, &list) {
+               chan = list_entry(ele, struct chan, free_list);
+
+               if (chan->input && chan->enabled)
+                       um_free_irq(chan->line->driver->read_irq, chan);
+               if (chan->output && chan->enabled)
+                       um_free_irq(chan->line->driver->write_irq, chan);
+               chan->enabled = 0;
+       }
+}
+
 static void close_one_chan(struct chan *chan, int delay_free_irq)
 {
+       unsigned long flags;
+
        if (!chan->opened)
                return;
 
-    /* we can safely call free now - it will be marked
-     *  as free and freed once the IRQ stopped processing
-     */
-       if (chan->input && chan->enabled)
-               um_free_irq(chan->line->driver->read_irq, chan);
-       if (chan->output && chan->enabled)
-               um_free_irq(chan->line->driver->write_irq, chan);
-       chan->enabled = 0;
+       if (delay_free_irq) {
+               spin_lock_irqsave(&irqs_to_free_lock, flags);
+               list_add(&chan->free_list, &irqs_to_free);
+               spin_unlock_irqrestore(&irqs_to_free_lock, flags);
+       } else {
+               if (chan->input && chan->enabled)
+                       um_free_irq(chan->line->driver->read_irq, chan);
+               if (chan->output && chan->enabled)
+                       um_free_irq(chan->line->driver->write_irq, chan);
+               chan->enabled = 0;
+       }
        if (chan->ops->close != NULL)
                (*chan->ops->close)(chan->fd, chan->data);
 
index 598d7b3..b40dac7 100644 (file)
@@ -21,6 +21,8 @@
 #include <irq_user.h>
 
 
+extern void free_irqs(void);
+
 /* When epoll triggers we do not know why it did so
  * we can also have different IRQs for read and write.
  * This is why we keep a small irq_fd array for each fd -
@@ -100,6 +102,8 @@ void sigio_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
                        }
                }
        }
+
+       free_irqs();
 }
 
 static int assign_epoll_events_to_irq(struct irq_entry *irq_entry)