s390/dasd: prevent double format of tracks for ESE devices
authorStefan Haberland <sth@linux.ibm.com>
Thu, 5 May 2022 14:17:30 +0000 (16:17 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 12 May 2022 10:30:08 +0000 (12:30 +0200)
commit 71f3871657370dbbaf942a1c758f64e49a36c70f upstream.

For ESE devices we get an error for write operations on an unformatted
track. Afterwards the track will be formatted and the IO operation
restarted.
When using alias devices a track might be accessed by multiple requests
simultaneously and there is a race window that a track gets formatted
twice resulting in data loss.

Prevent this by remembering the amount of formatted tracks when starting
a request and comparing this number before actually formatting a track
on the fly. If the number has changed there is a chance that the current
track was finally formatted in between. As a result do not format the
track and restart the current IO to check.

The number of formatted tracks does not match the overall number of
formatted tracks on the device and it might wrap around but this is no
problem. It is only needed to recognize that a track has been formatted at
all in between.

Fixes: 5e2b17e712cf ("s390/dasd: Add dynamic formatting support for ESE volumes")
Cc: stable@vger.kernel.org # 5.3+
Signed-off-by: Stefan Haberland <sth@linux.ibm.com>
Reviewed-by: Jan Hoeppner <hoeppner@linux.ibm.com>
Link: https://lore.kernel.org/r/20220505141733.1989450-3-sth@linux.ibm.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/s390/block/dasd.c
drivers/s390/block/dasd_eckd.c
drivers/s390/block/dasd_int.h

index 21865af0135acf895e9dabbfb1a67940105f35a2..756a53be430b5cc18b1ff63f6dd22fbf14745210 100644 (file)
@@ -1422,6 +1422,13 @@ int dasd_start_IO(struct dasd_ccw_req *cqr)
                if (!cqr->lpm)
                        cqr->lpm = dasd_path_get_opm(device);
        }
+       /*
+        * remember the amount of formatted tracks to prevent double format on
+        * ESE devices
+        */
+       if (cqr->block)
+               cqr->trkcount = atomic_read(&cqr->block->trkcount);
+
        if (cqr->cpmode == 1) {
                rc = ccw_device_tm_start(device->cdev, cqr->cpaddr,
                                         (long) cqr, cqr->lpm);
index e50835c6776a77bdc9c9d20d2b3d3327899a220a..44f9a2fb9054e085f00a4fd91773569bcea89d2f 100644 (file)
@@ -3095,13 +3095,24 @@ static int dasd_eckd_format_device(struct dasd_device *base,
 }
 
 static bool test_and_set_format_track(struct dasd_format_entry *to_format,
-                                     struct dasd_block *block)
+                                     struct dasd_ccw_req *cqr)
 {
+       struct dasd_block *block = cqr->block;
        struct dasd_format_entry *format;
        unsigned long flags;
        bool rc = false;
 
        spin_lock_irqsave(&block->format_lock, flags);
+       if (cqr->trkcount != atomic_read(&block->trkcount)) {
+               /*
+                * The number of formatted tracks has changed after request
+                * start and we can not tell if the current track was involved.
+                * To avoid data corruption treat it as if the current track is
+                * involved
+                */
+               rc = true;
+               goto out;
+       }
        list_for_each_entry(format, &block->format_list, list) {
                if (format->track == to_format->track) {
                        rc = true;
@@ -3121,6 +3132,7 @@ static void clear_format_track(struct dasd_format_entry *format,
        unsigned long flags;
 
        spin_lock_irqsave(&block->format_lock, flags);
+       atomic_inc(&block->trkcount);
        list_del_init(&format->list);
        spin_unlock_irqrestore(&block->format_lock, flags);
 }
@@ -3182,8 +3194,11 @@ dasd_eckd_ese_format(struct dasd_device *startdev, struct dasd_ccw_req *cqr,
        }
        format->track = curr_trk;
        /* test if track is already in formatting by another thread */
-       if (test_and_set_format_track(format, block))
+       if (test_and_set_format_track(format, cqr)) {
+               /* this is no real error so do not count down retries */
+               cqr->retries++;
                return ERR_PTR(-EEXIST);
+       }
 
        fdata.start_unit = curr_trk;
        fdata.stop_unit = curr_trk;
index 2903e4eddf040f46905c945cb7edc2496a0708c7..d94ae067f085ef43752439e7063eb8ace016388f 100644 (file)
@@ -188,6 +188,7 @@ struct dasd_ccw_req {
        void (*callback)(struct dasd_ccw_req *, void *data);
        void *callback_data;
        unsigned int proc_bytes;        /* bytes for partial completion */
+       unsigned int trkcount;          /* count formatted tracks */
 };
 
 /*
@@ -611,6 +612,7 @@ struct dasd_block {
 
        struct list_head format_list;
        spinlock_t format_lock;
+       atomic_t trkcount;
 };
 
 struct dasd_attention_data {