]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
bcon: add a release work struct
authorJoern Engel <joern@logfs.org>
Wed, 20 Feb 2013 20:25:12 +0000 (12:25 -0800)
committerJoern Engel <joern@logfs.org>
Wed, 20 Feb 2013 20:25:12 +0000 (12:25 -0800)
The final bcon_put() can be called from atomic context, by way of
bio_endio().  In that case we would sleep in invalidate_mapping_pages(),
with the usual unhappy results.

In nearly a year of production use, I have only seen a matching
backtrace once.  There was a second known issue that could be reproduced
by "yes h > /proc/sysrq-trigger" and concurrently pulling and replugging
the blockconsole device.  It took be somewhere around 30 pulls and sore
thumbs to reproduce and I never found the time to get to the bottom of
it.  Quite likely the two issues are identical.

Signed-off-by: Joern Engel <joern@logfs.org>
drivers/block/blockconsole.c

index 32f6c62d5bde38cd0fec7430024f0b9d7d012a22..b4730f868573274338652600d32d99774b127a66 100644 (file)
@@ -65,6 +65,7 @@ struct blockconsole {
        struct block_device *bdev;
        struct console console;
        struct work_struct unregister_work;
+       struct work_struct release_work;
        struct task_struct *writeback_thread;
        struct notifier_block panic_block;
 };
@@ -74,9 +75,10 @@ static void bcon_get(struct blockconsole *bc)
        kref_get(&bc->kref);
 }
 
-static void bcon_release(struct kref *kref)
+static void __bcon_release(struct work_struct *work)
 {
-       struct blockconsole *bc = container_of(kref, struct blockconsole, kref);
+       struct blockconsole *bc = container_of(work, struct blockconsole,
+                       release_work);
 
        __free_pages(bc->zero_page, 0);
        __free_pages(bc->pages, 8);
@@ -85,6 +87,14 @@ static void bcon_release(struct kref *kref)
        kfree(bc);
 }
 
+static void bcon_release(struct kref *kref)
+{
+       struct blockconsole *bc = container_of(kref, struct blockconsole, kref);
+
+       /* bcon_release can be called from atomic context */
+       schedule_work(&bc->release_work);
+}
+
 static void bcon_put(struct blockconsole *bc)
 {
        kref_put(&bc->kref, bcon_release);
@@ -512,6 +522,7 @@ static int bcon_create(const char *devname)
        if (IS_ERR(bc->writeback_thread))
                goto out2;
        INIT_WORK(&bc->unregister_work, bcon_unregister);
+       INIT_WORK(&bc->release_work, __bcon_release);
        register_console(&bc->console);
        bc->panic_block.notifier_call = blockconsole_panic;
        bc->panic_block.priority = INT_MAX;