REQ_FUA did not flush metadata in SSD mode, this commit fixes it.

Signed-off-by: Mikulas Patocka <[email protected]>
Cc: [email protected]
Fixes: c1005322ff02 ("dm writecache: handle REQ_FUA")
Assisted-by: Claude:claude-opus-5
Assisted-by: Codex:gpt-6-sol

---
 drivers/md/dm-writecache.c |   45 +++++++++++++++++++++++++++++++++++----------
 1 file changed, 35 insertions(+), 10 deletions(-)

Index: linux-2.6/drivers/md/dm-writecache.c
===================================================================
--- linux-2.6.orig/drivers/md/dm-writecache.c
+++ linux-2.6/drivers/md/dm-writecache.c
@@ -194,6 +194,7 @@ struct dm_writecache {
        struct task_struct *endio_thread;
 
        struct task_struct *flush_thread;
+       spinlock_t flush_list_lock;
        struct bio_list flush_list;
 
        struct dm_kcopyd_client *dm_kcopyd;
@@ -1288,9 +1289,11 @@ static int writecache_flush_thread(void
                struct bio *bio;
 
                wc_lock(wc);
+               spin_lock_irq(&wc->flush_list_lock);
                bio = bio_list_pop(&wc->flush_list);
                if (!bio) {
                        set_current_state(TASK_INTERRUPTIBLE);
+                       spin_unlock_irq(&wc->flush_list_lock);
                        wc_unlock(wc);
 
                        if (unlikely(kthread_should_stop())) {
@@ -1301,6 +1304,7 @@ static int writecache_flush_thread(void
                        schedule();
                        continue;
                }
+               spin_unlock_irq(&wc->flush_list_lock);
 
                if (bio_op(bio) == REQ_OP_DISCARD) {
                        writecache_discard(wc, bio->bi_iter.bi_sector,
@@ -1322,9 +1326,12 @@ static int writecache_flush_thread(void
 
 static void writecache_offload_bio(struct dm_writecache *wc, struct bio *bio)
 {
+       unsigned long flags;
+       spin_lock_irqsave(&wc->flush_list_lock, flags);
        if (bio_list_empty(&wc->flush_list))
                wake_up_process(wc->flush_thread);
        bio_list_add(&wc->flush_list, bio);
+       spin_unlock_irqrestore(&wc->flush_list_lock, flags);
 }
 
 enum wc_map_op {
@@ -1436,6 +1443,8 @@ static void writecache_bio_copy_ssd(stru
 static enum wc_map_op writecache_map_write(struct dm_writecache *wc, struct 
bio *bio)
 {
        struct wc_entry *e;
+       enum wc_map_op ret = WC_MAP_SUBMIT;
+       bool need_flush = false;
 
        do {
                bool found_entry = false;
@@ -1472,7 +1481,8 @@ direct_write:
                                writecache_map_remap_origin(wc, bio, e);
                                wc->stats.writes_around += bio->bi_iter.bi_size 
>> wc->block_size_bits;
                                wc->stats.writes += bio->bi_iter.bi_size >> 
wc->block_size_bits;
-                               return WC_MAP_REMAP_ORIGIN;
+                               ret = WC_MAP_REMAP_ORIGIN;
+                               goto flush_pmem_and_ret;
                        }
                        wc->stats.writes_blocked_on_freelist++;
                        writecache_wait_on_freelist(wc);
@@ -1486,21 +1496,25 @@ bio_copy:
                if (WC_MODE_PMEM(wc)) {
                        bio_copy_block(wc, bio, memory_data(wc, e));
                        wc->stats.writes++;
+                       need_flush = true;
                } else {
                        writecache_bio_copy_ssd(wc, bio, e, search_used);
                        return WC_MAP_REMAP;
                }
        } while (bio->bi_iter.bi_size);
 
-       if (unlikely(bio->bi_opf & REQ_FUA || wc->uncommitted_blocks >= 
wc->autocommit_blocks)) {
-               writecache_flush(wc);
-               if (writecache_has_error(wc))
-                       bio->bi_status = BLK_STS_IOERR;
-       } else {
-               writecache_schedule_autocommit(wc);
+flush_pmem_and_ret:
+       if (need_flush) {
+               if (unlikely(bio->bi_opf & REQ_FUA || wc->uncommitted_blocks >= 
wc->autocommit_blocks)) {
+                       writecache_flush(wc);
+                       if (writecache_has_error(wc))
+                               ret = WC_MAP_ERROR;
+               } else {
+                       writecache_schedule_autocommit(wc);
+               }
        }
 
-       return WC_MAP_SUBMIT;
+       return ret;
 }
 
 static enum wc_map_op writecache_map_flush(struct dm_writecache *wc, struct 
bio *bio)
@@ -1590,7 +1604,10 @@ done:
 
        case WC_MAP_REMAP:
                /* make sure that writecache_end_io decrements bio_in_progress: 
*/
-               bio->bi_private = (void *)1;
+               if (unlikely(bio->bi_opf & REQ_FUA) && bio_op(bio) == 
REQ_OP_WRITE)
+                       bio->bi_private = (void *)3;
+               else
+                       bio->bi_private = (void *)1;
                atomic_inc(&wc->bio_in_progress[bio_data_dir(bio)]);
                wc_unlock(wc);
                return DM_MAPIO_REMAPPED;
@@ -1620,12 +1637,19 @@ static int writecache_end_io(struct dm_t
 {
        struct dm_writecache *wc = ti->private;
 
-       if (bio->bi_private == (void *)1) {
+       if (bio->bi_private == (void *)1 || bio->bi_private == (void *)3) {
                int dir = bio_data_dir(bio);
+               bool fua = bio->bi_private == (void *)3;
 
                if (atomic_dec_and_test(&wc->bio_in_progress[dir]))
                        if 
(unlikely(waitqueue_active(&wc->bio_in_progress_wait[dir])))
                                wake_up(&wc->bio_in_progress_wait[dir]);
+
+               if (fua && likely(*status == BLK_STS_OK)) {
+                       bio->bi_private = NULL;
+                       writecache_offload_bio(wc, bio);
+                       return DM_ENDIO_INCOMPLETE;
+               }
        } else if (bio->bi_private == (void *)2) {
                dm_iot_io_end(&wc->iot, 1);
        }
@@ -2522,6 +2546,7 @@ invalid_optional:
 
                wc->memory_map_size -= (uint64_t)wc->start_sector << 
SECTOR_SHIFT;
 
+               spin_lock_init(&wc->flush_list_lock);
                bio_list_init(&wc->flush_list);
                wc->flush_thread = kthread_run(writecache_flush_thread, wc, 
"dm_writecache_flush");
                if (IS_ERR(wc->flush_thread)) {


Reply via email to