]> Git Repo - linux.git/commitdiff
block: Prevent deadlocks when switching elevators
authorDamien Le Moal <[email protected]>
Sun, 8 Sep 2024 00:07:04 +0000 (09:07 +0900)
committerJens Axboe <[email protected]>
Tue, 10 Sep 2024 19:43:42 +0000 (13:43 -0600)
Commit af2814149883 ("block: freeze the queue in queue_attr_store")
changed queue_attr_store() to always freeze a sysfs attribute queue
before calling the attribute store() method, to ensure that no IOs are
in-flight when an attribute value is being updated.

However, this change created a potential deadlock situation for the
scheduler queue attribute as changing the queue elevator with
elv_iosched_store() can result in a call to request_module() if the user
requested module is not already registered. If the file of the requested
module is stored on the block device of the frozen queue, a deadlock
will happen as the read operations triggered by request_module() will
wait for the queue freeze to end.

Solve this issue by introducing the load_module method in struct
queue_sysfs_entry, and to calling this method function in
queue_attr_store() before freezing the attribute queue.
The macro definition QUEUE_RW_LOAD_MODULE_ENTRY() is added to define a
queue sysfs attribute that needs loading a module.

The definition of the scheduler atrribute is changed to using
QUEUE_RW_LOAD_MODULE_ENTRY(), with the function
elv_iosched_load_module() defined as the load_module method.
elv_iosched_store() can then be simplified to remove the call to
request_module().

Reported-by: Richard W.M. Jones <[email protected]>
Reported-by: Jiri Jaburek <[email protected]>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=219166
Fixes: af2814149883 ("block: freeze the queue in queue_attr_store")
Cc: [email protected]
Signed-off-by: Damien Le Moal <[email protected]>
Tested-by: Richard W.M. Jones <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Jens Axboe <[email protected]>
block/blk-sysfs.c
block/elevator.c
block/elevator.h

index 60116d13cb80436147109f4ec7f4cf14be92ae8f..e85941bec857b6a2c2901b687250f53316566b10 100644 (file)
@@ -23,6 +23,7 @@
 struct queue_sysfs_entry {
        struct attribute attr;
        ssize_t (*show)(struct gendisk *disk, char *page);
+       int (*load_module)(struct gendisk *disk, const char *page, size_t count);
        ssize_t (*store)(struct gendisk *disk, const char *page, size_t count);
 };
 
@@ -413,6 +414,14 @@ static struct queue_sysfs_entry _prefix##_entry = {        \
        .store  = _prefix##_store,                      \
 };
 
+#define QUEUE_RW_LOAD_MODULE_ENTRY(_prefix, _name)             \
+static struct queue_sysfs_entry _prefix##_entry = {            \
+       .attr           = { .name = _name, .mode = 0644 },      \
+       .show           = _prefix##_show,                       \
+       .load_module    = _prefix##_load_module,                \
+       .store          = _prefix##_store,                      \
+}
+
 QUEUE_RW_ENTRY(queue_requests, "nr_requests");
 QUEUE_RW_ENTRY(queue_ra, "read_ahead_kb");
 QUEUE_RW_ENTRY(queue_max_sectors, "max_sectors_kb");
@@ -420,7 +429,7 @@ QUEUE_RO_ENTRY(queue_max_hw_sectors, "max_hw_sectors_kb");
 QUEUE_RO_ENTRY(queue_max_segments, "max_segments");
 QUEUE_RO_ENTRY(queue_max_integrity_segments, "max_integrity_segments");
 QUEUE_RO_ENTRY(queue_max_segment_size, "max_segment_size");
-QUEUE_RW_ENTRY(elv_iosched, "scheduler");
+QUEUE_RW_LOAD_MODULE_ENTRY(elv_iosched, "scheduler");
 
 QUEUE_RO_ENTRY(queue_logical_block_size, "logical_block_size");
 QUEUE_RO_ENTRY(queue_physical_block_size, "physical_block_size");
@@ -670,6 +679,17 @@ queue_attr_store(struct kobject *kobj, struct attribute *attr,
        if (!entry->store)
                return -EIO;
 
+       /*
+        * If the attribute needs to load a module, do it before freezing the
+        * queue to ensure that the module file can be read when the request
+        * queue is the one for the device storing the module file.
+        */
+       if (entry->load_module) {
+               res = entry->load_module(disk, page, length);
+               if (res)
+                       return res;
+       }
+
        blk_mq_freeze_queue(q);
        mutex_lock(&q->sysfs_lock);
        res = entry->store(disk, page, length);
index f13d552a32c8b8305dcc7d68e4e211ada3545ddd..c355b55d010786077c3114e4589ddf905e4032ab 100644 (file)
@@ -698,17 +698,26 @@ static int elevator_change(struct request_queue *q, const char *elevator_name)
                return 0;
 
        e = elevator_find_get(q, elevator_name);
-       if (!e) {
-               request_module("%s-iosched", elevator_name);
-               e = elevator_find_get(q, elevator_name);
-               if (!e)
-                       return -EINVAL;
-       }
+       if (!e)
+               return -EINVAL;
        ret = elevator_switch(q, e);
        elevator_put(e);
        return ret;
 }
 
+int elv_iosched_load_module(struct gendisk *disk, const char *buf,
+                           size_t count)
+{
+       char elevator_name[ELV_NAME_MAX];
+
+       if (!elv_support_iosched(disk->queue))
+               return -EOPNOTSUPP;
+
+       strscpy(elevator_name, buf, sizeof(elevator_name));
+
+       return request_module("%s-iosched", strstrip(elevator_name));
+}
+
 ssize_t elv_iosched_store(struct gendisk *disk, const char *buf,
                          size_t count)
 {
index 3fe18e1a8692757181f36927cb1ce060224feac2..2a78544bf2018005e9bf96d24e62a0d07f31c1ad 100644 (file)
@@ -148,6 +148,8 @@ extern void elv_unregister(struct elevator_type *);
  * io scheduler sysfs switching
  */
 ssize_t elv_iosched_show(struct gendisk *disk, char *page);
+int elv_iosched_load_module(struct gendisk *disk, const char *page,
+                           size_t count);
 ssize_t elv_iosched_store(struct gendisk *disk, const char *page, size_t count);
 
 extern bool elv_bio_merge_ok(struct request *, struct bio *);
This page took 0.064764 seconds and 4 git commands to generate.