| From 71f3871657370dbbaf942a1c758f64e49a36c70f Mon Sep 17 00:00:00 2001 |
| From: Stefan Haberland <sth@linux.ibm.com> |
| Date: Thu, 5 May 2022 16:17:30 +0200 |
| Subject: s390/dasd: prevent double format of tracks for ESE devices |
| |
| From: Stefan Haberland <sth@linux.ibm.com> |
| |
| 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 | 7 +++++++ |
| drivers/s390/block/dasd_eckd.c | 19 +++++++++++++++++-- |
| drivers/s390/block/dasd_int.h | 2 ++ |
| 3 files changed, 26 insertions(+), 2 deletions(-) |
| |
| --- a/drivers/s390/block/dasd.c |
| +++ b/drivers/s390/block/dasd.c |
| @@ -1462,6 +1462,13 @@ int dasd_start_IO(struct dasd_ccw_req *c |
| 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); |
| --- a/drivers/s390/block/dasd_eckd.c |
| +++ b/drivers/s390/block/dasd_eckd.c |
| @@ -3026,13 +3026,24 @@ static int dasd_eckd_format_device(struc |
| } |
| |
| 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; |
| @@ -3052,6 +3063,7 @@ static void clear_format_track(struct da |
| 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); |
| } |
| @@ -3113,8 +3125,11 @@ dasd_eckd_ese_format(struct dasd_device |
| } |
| 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; |
| --- a/drivers/s390/block/dasd_int.h |
| +++ b/drivers/s390/block/dasd_int.h |
| @@ -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 */ |
| }; |
| |
| /* |
| @@ -575,6 +576,7 @@ struct dasd_block { |
| |
| struct list_head format_list; |
| spinlock_t format_lock; |
| + atomic_t trkcount; |
| }; |
| |
| struct dasd_attention_data { |