summaryrefslogtreecommitdiffstats
path: root/drivers/md/dm-zoned-metadata.c
diff options
context:
space:
mode:
authorDamien Le Moal <damien.lemoal@wdc.com>2018-10-17 05:05:08 -0400
committerMike Snitzer <snitzer@redhat.com>2018-10-18 15:17:03 -0400
commit3d4e738311327bc4ba1d55fbe2f1da3de4a475f9 (patch)
treead665b95dd90cf7fdadce080ebbee76379c1cf8e /drivers/md/dm-zoned-metadata.c
parent33c2865f8d011a2ca9f67124ddab9dc89382e9f1 (diff)
dm zoned: fix various dmz_get_mblock() issues
dmz_fetch_mblock() called from dmz_get_mblock() has a race since the allocation of the new metadata block descriptor and its insertion in the cache rbtree with the READING state is not atomic. Two different contexts requesting the same block may end up each adding two different descriptors of the same block to the cache. Another problem for this function is that the BIO for processing the block read is allocated after the metadata block descriptor is inserted in the cache rbtree. If the BIO allocation fails, the metadata block descriptor is freed without first being removed from the rbtree. Fix the first problem by checking again if the requested block is not in the cache right before inserting the newly allocated descriptor, atomically under the mblk_lock spinlock. The second problem is fixed by simply allocating the BIO before inserting the new block in the cache. Finally, since dmz_fetch_mblock() also increments a block reference counter, rename the function to dmz_get_mblock_slow(). To be symmetric and clear, also rename dmz_lookup_mblock() to dmz_get_mblock_fast() and increment the block reference counter directly in that function rather than in dmz_get_mblock(). Fixes: 3b1a94c88b79 ("dm zoned: drive-managed zoned block device target") Cc: stable@vger.kernel.org Signed-off-by: Damien Le Moal <damien.lemoal@wdc.com> Signed-off-by: Mike Snitzer <snitzer@redhat.com>
Diffstat (limited to 'drivers/md/dm-zoned-metadata.c')
-rw-r--r--drivers/md/dm-zoned-metadata.c66
1 files changed, 42 insertions, 24 deletions
diff --git a/drivers/md/dm-zoned-metadata.c b/drivers/md/dm-zoned-metadata.c
index 67b71f6e3bda..fa68336560c3 100644
--- a/drivers/md/dm-zoned-metadata.c
+++ b/drivers/md/dm-zoned-metadata.c
@@ -339,10 +339,11 @@ static void dmz_insert_mblock(struct dmz_metadata *zmd, struct dmz_mblock *mblk)
339} 339}
340 340
341/* 341/*
342 * Lookup a metadata block in the rbtree. 342 * Lookup a metadata block in the rbtree. If the block is found, increment
343 * its reference count.
343 */ 344 */
344static struct dmz_mblock *dmz_lookup_mblock(struct dmz_metadata *zmd, 345static struct dmz_mblock *dmz_get_mblock_fast(struct dmz_metadata *zmd,
345 sector_t mblk_no) 346 sector_t mblk_no)
346{ 347{
347 struct rb_root *root = &zmd->mblk_rbtree; 348 struct rb_root *root = &zmd->mblk_rbtree;
348 struct rb_node *node = root->rb_node; 349 struct rb_node *node = root->rb_node;
@@ -350,8 +351,17 @@ static struct dmz_mblock *dmz_lookup_mblock(struct dmz_metadata *zmd,
350 351
351 while (node) { 352 while (node) {
352 mblk = container_of(node, struct dmz_mblock, node); 353 mblk = container_of(node, struct dmz_mblock, node);
353 if (mblk->no == mblk_no) 354 if (mblk->no == mblk_no) {
355 /*
356 * If this is the first reference to the block,
357 * remove it from the LRU list.
358 */
359 mblk->ref++;
360 if (mblk->ref == 1 &&
361 !test_bit(DMZ_META_DIRTY, &mblk->state))
362 list_del_init(&mblk->link);
354 return mblk; 363 return mblk;
364 }
355 node = (mblk->no < mblk_no) ? node->rb_left : node->rb_right; 365 node = (mblk->no < mblk_no) ? node->rb_left : node->rb_right;
356 } 366 }
357 367
@@ -382,32 +392,47 @@ static void dmz_mblock_bio_end_io(struct bio *bio)
382} 392}
383 393
384/* 394/*
385 * Read a metadata block from disk. 395 * Read an uncached metadata block from disk and add it to the cache.
386 */ 396 */
387static struct dmz_mblock *dmz_fetch_mblock(struct dmz_metadata *zmd, 397static struct dmz_mblock *dmz_get_mblock_slow(struct dmz_metadata *zmd,
388 sector_t mblk_no) 398 sector_t mblk_no)
389{ 399{
390 struct dmz_mblock *mblk; 400 struct dmz_mblock *mblk, *m;
391 sector_t block = zmd->sb[zmd->mblk_primary].block + mblk_no; 401 sector_t block = zmd->sb[zmd->mblk_primary].block + mblk_no;
392 struct bio *bio; 402 struct bio *bio;
393 403
394 /* Get block and insert it */ 404 /* Get a new block and a BIO to read it */
395 mblk = dmz_alloc_mblock(zmd, mblk_no); 405 mblk = dmz_alloc_mblock(zmd, mblk_no);
396 if (!mblk) 406 if (!mblk)
397 return NULL; 407 return NULL;
398 408
399 spin_lock(&zmd->mblk_lock);
400 mblk->ref++;
401 set_bit(DMZ_META_READING, &mblk->state);
402 dmz_insert_mblock(zmd, mblk);
403 spin_unlock(&zmd->mblk_lock);
404
405 bio = bio_alloc(GFP_NOIO, 1); 409 bio = bio_alloc(GFP_NOIO, 1);
406 if (!bio) { 410 if (!bio) {
407 dmz_free_mblock(zmd, mblk); 411 dmz_free_mblock(zmd, mblk);
408 return NULL; 412 return NULL;
409 } 413 }
410 414
415 spin_lock(&zmd->mblk_lock);
416
417 /*
418 * Make sure that another context did not start reading
419 * the block already.
420 */
421 m = dmz_get_mblock_fast(zmd, mblk_no);
422 if (m) {
423 spin_unlock(&zmd->mblk_lock);
424 dmz_free_mblock(zmd, mblk);
425 bio_put(bio);
426 return m;
427 }
428
429 mblk->ref++;
430 set_bit(DMZ_META_READING, &mblk->state);
431 dmz_insert_mblock(zmd, mblk);
432
433 spin_unlock(&zmd->mblk_lock);
434
435 /* Submit read BIO */
411 bio->bi_iter.bi_sector = dmz_blk2sect(block); 436 bio->bi_iter.bi_sector = dmz_blk2sect(block);
412 bio_set_dev(bio, zmd->dev->bdev); 437 bio_set_dev(bio, zmd->dev->bdev);
413 bio->bi_private = mblk; 438 bio->bi_private = mblk;
@@ -509,19 +534,12 @@ static struct dmz_mblock *dmz_get_mblock(struct dmz_metadata *zmd,
509 534
510 /* Check rbtree */ 535 /* Check rbtree */
511 spin_lock(&zmd->mblk_lock); 536 spin_lock(&zmd->mblk_lock);
512 mblk = dmz_lookup_mblock(zmd, mblk_no); 537 mblk = dmz_get_mblock_fast(zmd, mblk_no);
513 if (mblk) {
514 /* Cache hit: remove block from LRU list */
515 mblk->ref++;
516 if (mblk->ref == 1 &&
517 !test_bit(DMZ_META_DIRTY, &mblk->state))
518 list_del_init(&mblk->link);
519 }
520 spin_unlock(&zmd->mblk_lock); 538 spin_unlock(&zmd->mblk_lock);
521 539
522 if (!mblk) { 540 if (!mblk) {
523 /* Cache miss: read the block from disk */ 541 /* Cache miss: read the block from disk */
524 mblk = dmz_fetch_mblock(zmd, mblk_no); 542 mblk = dmz_get_mblock_slow(zmd, mblk_no);
525 if (!mblk) 543 if (!mblk)
526 return ERR_PTR(-ENOMEM); 544 return ERR_PTR(-ENOMEM);
527 } 545 }