aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/md/linear.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/md/linear.c')
-rw-r--r--drivers/md/linear.c39
1 files changed, 34 insertions, 5 deletions
diff --git a/drivers/md/linear.c b/drivers/md/linear.c
index 86f5d435901d..b0c0aef92a37 100644
--- a/drivers/md/linear.c
+++ b/drivers/md/linear.c
@@ -52,18 +52,26 @@ static inline struct dev_info *which_dev(struct mddev *mddev, sector_t sector)
52 return conf->disks + lo; 52 return conf->disks + lo;
53} 53}
54 54
55/*
56 * In linear_congested() conf->raid_disks is used as a copy of
57 * mddev->raid_disks to iterate conf->disks[], because conf->raid_disks
58 * and conf->disks[] are created in linear_conf(), they are always
59 * consitent with each other, but mddev->raid_disks does not.
60 */
55static int linear_congested(struct mddev *mddev, int bits) 61static int linear_congested(struct mddev *mddev, int bits)
56{ 62{
57 struct linear_conf *conf; 63 struct linear_conf *conf;
58 int i, ret = 0; 64 int i, ret = 0;
59 65
60 conf = mddev->private; 66 rcu_read_lock();
67 conf = rcu_dereference(mddev->private);
61 68
62 for (i = 0; i < mddev->raid_disks && !ret ; i++) { 69 for (i = 0; i < conf->raid_disks && !ret ; i++) {
63 struct request_queue *q = bdev_get_queue(conf->disks[i].rdev->bdev); 70 struct request_queue *q = bdev_get_queue(conf->disks[i].rdev->bdev);
64 ret |= bdi_congested(&q->backing_dev_info, bits); 71 ret |= bdi_congested(&q->backing_dev_info, bits);
65 } 72 }
66 73
74 rcu_read_unlock();
67 return ret; 75 return ret;
68} 76}
69 77
@@ -143,6 +151,19 @@ static struct linear_conf *linear_conf(struct mddev *mddev, int raid_disks)
143 conf->disks[i-1].end_sector + 151 conf->disks[i-1].end_sector +
144 conf->disks[i].rdev->sectors; 152 conf->disks[i].rdev->sectors;
145 153
154 /*
155 * conf->raid_disks is copy of mddev->raid_disks. The reason to
156 * keep a copy of mddev->raid_disks in struct linear_conf is,
157 * mddev->raid_disks may not be consistent with pointers number of
158 * conf->disks[] when it is updated in linear_add() and used to
159 * iterate old conf->disks[] earray in linear_congested().
160 * Here conf->raid_disks is always consitent with number of
161 * pointers in conf->disks[] array, and mddev->private is updated
162 * with rcu_assign_pointer() in linear_addr(), such race can be
163 * avoided.
164 */
165 conf->raid_disks = raid_disks;
166
146 return conf; 167 return conf;
147 168
148out: 169out:
@@ -195,15 +216,23 @@ static int linear_add(struct mddev *mddev, struct md_rdev *rdev)
195 if (!newconf) 216 if (!newconf)
196 return -ENOMEM; 217 return -ENOMEM;
197 218
219 /* newconf->raid_disks already keeps a copy of * the increased
220 * value of mddev->raid_disks, WARN_ONCE() is just used to make
221 * sure of this. It is possible that oldconf is still referenced
222 * in linear_congested(), therefore kfree_rcu() is used to free
223 * oldconf until no one uses it anymore.
224 */
198 mddev_suspend(mddev); 225 mddev_suspend(mddev);
199 oldconf = mddev->private; 226 oldconf = rcu_dereference(mddev->private);
200 mddev->raid_disks++; 227 mddev->raid_disks++;
201 mddev->private = newconf; 228 WARN_ONCE(mddev->raid_disks != newconf->raid_disks,
229 "copied raid_disks doesn't match mddev->raid_disks");
230 rcu_assign_pointer(mddev->private, newconf);
202 md_set_array_sectors(mddev, linear_size(mddev, 0, 0)); 231 md_set_array_sectors(mddev, linear_size(mddev, 0, 0));
203 set_capacity(mddev->gendisk, mddev->array_sectors); 232 set_capacity(mddev->gendisk, mddev->array_sectors);
204 mddev_resume(mddev); 233 mddev_resume(mddev);
205 revalidate_disk(mddev->gendisk); 234 revalidate_disk(mddev->gendisk);
206 kfree(oldconf); 235 kfree_rcu(oldconf, rcu);
207 return 0; 236 return 0;
208} 237}
209 238