diff options
author | Jens Axboe <jens.axboe@oracle.com> | 2008-09-14 08:55:09 -0400 |
---|---|---|
committer | Jens Axboe <jens.axboe@oracle.com> | 2008-10-09 02:56:13 -0400 |
commit | 242f9dcb8ba6f68fcd217a119a7648a4f69290e9 (patch) | |
tree | 1bfe245ffbc50d204d76665cd8f90d85100f86a1 /block | |
parent | 608aeef17a91747d6303de4df5e2c2e6899a95e8 (diff) |
block: unify request timeout handling
Right now SCSI and others do their own command timeout handling.
Move those bits to the block layer.
Instead of having a timer per command, we try to be a bit more clever
and simply have one per-queue. This avoids the overhead of having to
tear down and setup a timer for each command, so it will result in a lot
less timer fiddling.
Signed-off-by: Mike Anderson <andmike@linux.vnet.ibm.com>
Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
Diffstat (limited to 'block')
-rw-r--r-- | block/Makefile | 4 | ||||
-rw-r--r-- | block/blk-core.c | 7 | ||||
-rw-r--r-- | block/blk-settings.c | 12 | ||||
-rw-r--r-- | block/blk-softirq.c | 30 | ||||
-rw-r--r-- | block/blk-timeout.c | 155 | ||||
-rw-r--r-- | block/blk.h | 24 | ||||
-rw-r--r-- | block/elevator.c | 8 |
7 files changed, 226 insertions, 14 deletions
diff --git a/block/Makefile b/block/Makefile index 0da976ce67dd..bfe73049f939 100644 --- a/block/Makefile +++ b/block/Makefile | |||
@@ -4,8 +4,8 @@ | |||
4 | 4 | ||
5 | obj-$(CONFIG_BLOCK) := elevator.o blk-core.o blk-tag.o blk-sysfs.o \ | 5 | obj-$(CONFIG_BLOCK) := elevator.o blk-core.o blk-tag.o blk-sysfs.o \ |
6 | blk-barrier.o blk-settings.o blk-ioc.o blk-map.o \ | 6 | blk-barrier.o blk-settings.o blk-ioc.o blk-map.o \ |
7 | blk-exec.o blk-merge.o blk-softirq.o ioctl.o genhd.o \ | 7 | blk-exec.o blk-merge.o blk-softirq.o blk-timeout.o \ |
8 | scsi_ioctl.o cmd-filter.o | 8 | ioctl.o genhd.o scsi_ioctl.o cmd-filter.o |
9 | 9 | ||
10 | obj-$(CONFIG_BLK_DEV_BSG) += bsg.o | 10 | obj-$(CONFIG_BLK_DEV_BSG) += bsg.o |
11 | obj-$(CONFIG_IOSCHED_NOOP) += noop-iosched.o | 11 | obj-$(CONFIG_IOSCHED_NOOP) += noop-iosched.o |
diff --git a/block/blk-core.c b/block/blk-core.c index f25eb9786d94..d768a8ddc173 100644 --- a/block/blk-core.c +++ b/block/blk-core.c | |||
@@ -110,6 +110,7 @@ void blk_rq_init(struct request_queue *q, struct request *rq) | |||
110 | memset(rq, 0, sizeof(*rq)); | 110 | memset(rq, 0, sizeof(*rq)); |
111 | 111 | ||
112 | INIT_LIST_HEAD(&rq->queuelist); | 112 | INIT_LIST_HEAD(&rq->queuelist); |
113 | INIT_LIST_HEAD(&rq->timeout_list); | ||
113 | rq->cpu = -1; | 114 | rq->cpu = -1; |
114 | rq->q = q; | 115 | rq->q = q; |
115 | rq->sector = rq->hard_sector = (sector_t) -1; | 116 | rq->sector = rq->hard_sector = (sector_t) -1; |
@@ -490,6 +491,8 @@ struct request_queue *blk_alloc_queue_node(gfp_t gfp_mask, int node_id) | |||
490 | } | 491 | } |
491 | 492 | ||
492 | init_timer(&q->unplug_timer); | 493 | init_timer(&q->unplug_timer); |
494 | setup_timer(&q->timeout, blk_rq_timed_out_timer, (unsigned long) q); | ||
495 | INIT_LIST_HEAD(&q->timeout_list); | ||
493 | 496 | ||
494 | kobject_init(&q->kobj, &blk_queue_ktype); | 497 | kobject_init(&q->kobj, &blk_queue_ktype); |
495 | 498 | ||
@@ -897,6 +900,8 @@ EXPORT_SYMBOL(blk_start_queueing); | |||
897 | */ | 900 | */ |
898 | void blk_requeue_request(struct request_queue *q, struct request *rq) | 901 | void blk_requeue_request(struct request_queue *q, struct request *rq) |
899 | { | 902 | { |
903 | blk_delete_timer(rq); | ||
904 | blk_clear_rq_complete(rq); | ||
900 | blk_add_trace_rq(q, rq, BLK_TA_REQUEUE); | 905 | blk_add_trace_rq(q, rq, BLK_TA_REQUEUE); |
901 | 906 | ||
902 | if (blk_rq_tagged(rq)) | 907 | if (blk_rq_tagged(rq)) |
@@ -1650,6 +1655,8 @@ static void end_that_request_last(struct request *req, int error) | |||
1650 | { | 1655 | { |
1651 | struct gendisk *disk = req->rq_disk; | 1656 | struct gendisk *disk = req->rq_disk; |
1652 | 1657 | ||
1658 | blk_delete_timer(req); | ||
1659 | |||
1653 | if (blk_rq_tagged(req)) | 1660 | if (blk_rq_tagged(req)) |
1654 | blk_queue_end_tag(req->q, req); | 1661 | blk_queue_end_tag(req->q, req); |
1655 | 1662 | ||
diff --git a/block/blk-settings.c b/block/blk-settings.c index d70692badcdb..1d0330d0b40a 100644 --- a/block/blk-settings.c +++ b/block/blk-settings.c | |||
@@ -77,6 +77,18 @@ void blk_queue_softirq_done(struct request_queue *q, softirq_done_fn *fn) | |||
77 | } | 77 | } |
78 | EXPORT_SYMBOL(blk_queue_softirq_done); | 78 | EXPORT_SYMBOL(blk_queue_softirq_done); |
79 | 79 | ||
80 | void blk_queue_rq_timeout(struct request_queue *q, unsigned int timeout) | ||
81 | { | ||
82 | q->rq_timeout = timeout; | ||
83 | } | ||
84 | EXPORT_SYMBOL_GPL(blk_queue_rq_timeout); | ||
85 | |||
86 | void blk_queue_rq_timed_out(struct request_queue *q, rq_timed_out_fn *fn) | ||
87 | { | ||
88 | q->rq_timed_out_fn = fn; | ||
89 | } | ||
90 | EXPORT_SYMBOL_GPL(blk_queue_rq_timed_out); | ||
91 | |||
80 | /** | 92 | /** |
81 | * blk_queue_make_request - define an alternate make_request function for a device | 93 | * blk_queue_make_request - define an alternate make_request function for a device |
82 | * @q: the request queue for the device to be affected | 94 | * @q: the request queue for the device to be affected |
diff --git a/block/blk-softirq.c b/block/blk-softirq.c index 3a1af551191e..7ab344afb16f 100644 --- a/block/blk-softirq.c +++ b/block/blk-softirq.c | |||
@@ -101,18 +101,7 @@ static struct notifier_block __cpuinitdata blk_cpu_notifier = { | |||
101 | .notifier_call = blk_cpu_notify, | 101 | .notifier_call = blk_cpu_notify, |
102 | }; | 102 | }; |
103 | 103 | ||
104 | /** | 104 | void __blk_complete_request(struct request *req) |
105 | * blk_complete_request - end I/O on a request | ||
106 | * @req: the request being processed | ||
107 | * | ||
108 | * Description: | ||
109 | * Ends all I/O on a request. It does not handle partial completions, | ||
110 | * unless the driver actually implements this in its completion callback | ||
111 | * through requeueing. The actual completion happens out-of-order, | ||
112 | * through a softirq handler. The user must have registered a completion | ||
113 | * callback through blk_queue_softirq_done(). | ||
114 | **/ | ||
115 | void blk_complete_request(struct request *req) | ||
116 | { | 105 | { |
117 | struct request_queue *q = req->q; | 106 | struct request_queue *q = req->q; |
118 | unsigned long flags; | 107 | unsigned long flags; |
@@ -151,6 +140,23 @@ do_local: | |||
151 | 140 | ||
152 | local_irq_restore(flags); | 141 | local_irq_restore(flags); |
153 | } | 142 | } |
143 | |||
144 | /** | ||
145 | * blk_complete_request - end I/O on a request | ||
146 | * @req: the request being processed | ||
147 | * | ||
148 | * Description: | ||
149 | * Ends all I/O on a request. It does not handle partial completions, | ||
150 | * unless the driver actually implements this in its completion callback | ||
151 | * through requeueing. The actual completion happens out-of-order, | ||
152 | * through a softirq handler. The user must have registered a completion | ||
153 | * callback through blk_queue_softirq_done(). | ||
154 | **/ | ||
155 | void blk_complete_request(struct request *req) | ||
156 | { | ||
157 | if (!blk_mark_rq_complete(req)) | ||
158 | __blk_complete_request(req); | ||
159 | } | ||
154 | EXPORT_SYMBOL(blk_complete_request); | 160 | EXPORT_SYMBOL(blk_complete_request); |
155 | 161 | ||
156 | __init int blk_softirq_init(void) | 162 | __init int blk_softirq_init(void) |
diff --git a/block/blk-timeout.c b/block/blk-timeout.c new file mode 100644 index 000000000000..b36d07bf0afb --- /dev/null +++ b/block/blk-timeout.c | |||
@@ -0,0 +1,155 @@ | |||
1 | /* | ||
2 | * Functions related to generic timeout handling of requests. | ||
3 | */ | ||
4 | #include <linux/kernel.h> | ||
5 | #include <linux/module.h> | ||
6 | #include <linux/blkdev.h> | ||
7 | |||
8 | #include "blk.h" | ||
9 | |||
10 | /* | ||
11 | * blk_delete_timer - Delete/cancel timer for a given function. | ||
12 | * @req: request that we are canceling timer for | ||
13 | * | ||
14 | */ | ||
15 | void blk_delete_timer(struct request *req) | ||
16 | { | ||
17 | struct request_queue *q = req->q; | ||
18 | |||
19 | /* | ||
20 | * Nothing to detach | ||
21 | */ | ||
22 | if (!q->rq_timed_out_fn || !req->deadline) | ||
23 | return; | ||
24 | |||
25 | list_del_init(&req->timeout_list); | ||
26 | |||
27 | if (list_empty(&q->timeout_list)) | ||
28 | del_timer(&q->timeout); | ||
29 | } | ||
30 | |||
31 | static void blk_rq_timed_out(struct request *req) | ||
32 | { | ||
33 | struct request_queue *q = req->q; | ||
34 | enum blk_eh_timer_return ret; | ||
35 | |||
36 | ret = q->rq_timed_out_fn(req); | ||
37 | switch (ret) { | ||
38 | case BLK_EH_HANDLED: | ||
39 | __blk_complete_request(req); | ||
40 | break; | ||
41 | case BLK_EH_RESET_TIMER: | ||
42 | blk_clear_rq_complete(req); | ||
43 | blk_add_timer(req); | ||
44 | break; | ||
45 | case BLK_EH_NOT_HANDLED: | ||
46 | /* | ||
47 | * LLD handles this for now but in the future | ||
48 | * we can send a request msg to abort the command | ||
49 | * and we can move more of the generic scsi eh code to | ||
50 | * the blk layer. | ||
51 | */ | ||
52 | break; | ||
53 | default: | ||
54 | printk(KERN_ERR "block: bad eh return: %d\n", ret); | ||
55 | break; | ||
56 | } | ||
57 | } | ||
58 | |||
59 | void blk_rq_timed_out_timer(unsigned long data) | ||
60 | { | ||
61 | struct request_queue *q = (struct request_queue *) data; | ||
62 | unsigned long flags, uninitialized_var(next), next_set = 0; | ||
63 | struct request *rq, *tmp; | ||
64 | |||
65 | spin_lock_irqsave(q->queue_lock, flags); | ||
66 | |||
67 | list_for_each_entry_safe(rq, tmp, &q->timeout_list, timeout_list) { | ||
68 | if (time_after_eq(jiffies, rq->deadline)) { | ||
69 | list_del_init(&rq->timeout_list); | ||
70 | |||
71 | /* | ||
72 | * Check if we raced with end io completion | ||
73 | */ | ||
74 | if (blk_mark_rq_complete(rq)) | ||
75 | continue; | ||
76 | blk_rq_timed_out(rq); | ||
77 | } | ||
78 | if (!next_set) { | ||
79 | next = rq->deadline; | ||
80 | next_set = 1; | ||
81 | } else if (time_after(next, rq->deadline)) | ||
82 | next = rq->deadline; | ||
83 | } | ||
84 | |||
85 | if (next_set && !list_empty(&q->timeout_list)) | ||
86 | mod_timer(&q->timeout, round_jiffies(next)); | ||
87 | |||
88 | spin_unlock_irqrestore(q->queue_lock, flags); | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * blk_abort_request -- Request request recovery for the specified command | ||
93 | * @req: pointer to the request of interest | ||
94 | * | ||
95 | * This function requests that the block layer start recovery for the | ||
96 | * request by deleting the timer and calling the q's timeout function. | ||
97 | * LLDDs who implement their own error recovery MAY ignore the timeout | ||
98 | * event if they generated blk_abort_req. Must hold queue lock. | ||
99 | */ | ||
100 | void blk_abort_request(struct request *req) | ||
101 | { | ||
102 | blk_delete_timer(req); | ||
103 | blk_rq_timed_out(req); | ||
104 | } | ||
105 | EXPORT_SYMBOL_GPL(blk_abort_request); | ||
106 | |||
107 | /** | ||
108 | * blk_add_timer - Start timeout timer for a single request | ||
109 | * @req: request that is about to start running. | ||
110 | * | ||
111 | * Notes: | ||
112 | * Each request has its own timer, and as it is added to the queue, we | ||
113 | * set up the timer. When the request completes, we cancel the timer. | ||
114 | */ | ||
115 | void blk_add_timer(struct request *req) | ||
116 | { | ||
117 | struct request_queue *q = req->q; | ||
118 | unsigned long expiry; | ||
119 | |||
120 | if (!q->rq_timed_out_fn) | ||
121 | return; | ||
122 | |||
123 | BUG_ON(!list_empty(&req->timeout_list)); | ||
124 | BUG_ON(test_bit(REQ_ATOM_COMPLETE, &req->atomic_flags)); | ||
125 | |||
126 | if (req->timeout) | ||
127 | req->deadline = jiffies + req->timeout; | ||
128 | else { | ||
129 | req->deadline = jiffies + q->rq_timeout; | ||
130 | /* | ||
131 | * Some LLDs, like scsi, peek at the timeout to prevent | ||
132 | * a command from being retried forever. | ||
133 | */ | ||
134 | req->timeout = q->rq_timeout; | ||
135 | } | ||
136 | list_add_tail(&req->timeout_list, &q->timeout_list); | ||
137 | |||
138 | /* | ||
139 | * If the timer isn't already pending or this timeout is earlier | ||
140 | * than an existing one, modify the timer. Round to next nearest | ||
141 | * second. | ||
142 | */ | ||
143 | expiry = round_jiffies(req->deadline); | ||
144 | |||
145 | /* | ||
146 | * We use ->deadline == 0 to detect whether a timer was added or | ||
147 | * not, so just increase to next jiffy for that specific case | ||
148 | */ | ||
149 | if (unlikely(!req->deadline)) | ||
150 | req->deadline = 1; | ||
151 | |||
152 | if (!timer_pending(&q->timeout) || | ||
153 | time_before(expiry, q->timeout.expires)) | ||
154 | mod_timer(&q->timeout, expiry); | ||
155 | } | ||
diff --git a/block/blk.h b/block/blk.h index de74254cb916..a4f4a50aefaa 100644 --- a/block/blk.h +++ b/block/blk.h | |||
@@ -17,6 +17,30 @@ void __blk_queue_free_tags(struct request_queue *q); | |||
17 | 17 | ||
18 | void blk_unplug_work(struct work_struct *work); | 18 | void blk_unplug_work(struct work_struct *work); |
19 | void blk_unplug_timeout(unsigned long data); | 19 | void blk_unplug_timeout(unsigned long data); |
20 | void blk_rq_timed_out_timer(unsigned long data); | ||
21 | void blk_delete_timer(struct request *); | ||
22 | void blk_add_timer(struct request *); | ||
23 | |||
24 | /* | ||
25 | * Internal atomic flags for request handling | ||
26 | */ | ||
27 | enum rq_atomic_flags { | ||
28 | REQ_ATOM_COMPLETE = 0, | ||
29 | }; | ||
30 | |||
31 | /* | ||
32 | * EH timer and IO completion will both attempt to 'grab' the request, make | ||
33 | * sure that only one of them suceeds | ||
34 | */ | ||
35 | static inline int blk_mark_rq_complete(struct request *rq) | ||
36 | { | ||
37 | return test_and_set_bit(REQ_ATOM_COMPLETE, &rq->atomic_flags); | ||
38 | } | ||
39 | |||
40 | static inline void blk_clear_rq_complete(struct request *rq) | ||
41 | { | ||
42 | clear_bit(REQ_ATOM_COMPLETE, &rq->atomic_flags); | ||
43 | } | ||
20 | 44 | ||
21 | struct io_context *current_io_context(gfp_t gfp_flags, int node); | 45 | struct io_context *current_io_context(gfp_t gfp_flags, int node); |
22 | 46 | ||
diff --git a/block/elevator.c b/block/elevator.c index 8e3fc3afc77b..a91fc59edd01 100644 --- a/block/elevator.c +++ b/block/elevator.c | |||
@@ -36,6 +36,8 @@ | |||
36 | #include <linux/hash.h> | 36 | #include <linux/hash.h> |
37 | #include <linux/uaccess.h> | 37 | #include <linux/uaccess.h> |
38 | 38 | ||
39 | #include "blk.h" | ||
40 | |||
39 | static DEFINE_SPINLOCK(elv_list_lock); | 41 | static DEFINE_SPINLOCK(elv_list_lock); |
40 | static LIST_HEAD(elv_list); | 42 | static LIST_HEAD(elv_list); |
41 | 43 | ||
@@ -771,6 +773,12 @@ struct request *elv_next_request(struct request_queue *q) | |||
771 | */ | 773 | */ |
772 | rq->cmd_flags |= REQ_STARTED; | 774 | rq->cmd_flags |= REQ_STARTED; |
773 | blk_add_trace_rq(q, rq, BLK_TA_ISSUE); | 775 | blk_add_trace_rq(q, rq, BLK_TA_ISSUE); |
776 | |||
777 | /* | ||
778 | * We are now handing the request to the hardware, | ||
779 | * add the timeout handler | ||
780 | */ | ||
781 | blk_add_timer(rq); | ||
774 | } | 782 | } |
775 | 783 | ||
776 | if (!q->boundary_rq || q->boundary_rq == rq) { | 784 | if (!q->boundary_rq || q->boundary_rq == rq) { |