summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-11-13 10:31:05 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2026-11-13 10:31:05 -0800
commite93fd809fb5692d1571bd008988ba734fd7fc976 (patch)
treef90dfe2773a029319cef7a5039ed4748d3ff1220
parent831615b085fa5b9c85c5a52dadd71ebcfb8d5985 (diff)
parent7cc2dda4f7aaf596c1796184649007151ff98f8d (diff)
downloadlinux-e93fd809fb5692d1571bd008988ba734fd7fc976.tar.gz
linux-e93fd809fb5692d1571bd008988ba734fd7fc976.zip
Merge tag 'block-7.4-20261112' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux
Pull block fixes from Jens Axboe: - ublk fixes: don't publish a new device in the idr until it's fully set up, and don't hold ublk_ctl_mutex across device removal, as a stuck filesystem on one device otherwise blocked ADD_DEV and DEL_DEV for every other ublk device. - s390/dasd fixes: re-initialize the RCD buffer magic before every request start, fix a wrong free and refcount leak in the check_tcw error path, don't trust the userspace-supplied result field in the format check, and a typo fix. * tag 'block-7.4-20261112' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux: s390/dasd: Don't trust userspace-supplied result field in format check s390/dasd: Fix wrong free and refcount leak in build_check_tcw error path s390/dasd: Re-initialize RCD buffer with "V1.0" before every start s390/dasd: Fix typo 'immediatly' -> 'immediately' ublk: don't take ublk_ctl_mutex in DEL_DEV ublk: publish a new device by its id only once it is set up
-rw-r--r--drivers/block/ublk_drv.c60
-rw-r--r--drivers/s390/block/dasd.c5
-rw-r--r--drivers/s390/block/dasd_alias.c2
-rw-r--r--drivers/s390/block/dasd_eckd.c69
-rw-r--r--drivers/s390/block/dasd_int.h17
-rw-r--r--drivers/s390/block/dasd_ioctl.c4
6 files changed, 87 insertions, 70 deletions
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 3d322fa1bc83..d415be37d5ed 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -881,7 +881,8 @@ static ssize_t ublk_batch_copy_io_tags(struct ublk_batch_fetch_cmd *fcmd,
* by cgroup.
*/
static unsigned int unprivileged_ublks_max = 64;
-static unsigned int unprivileged_ublks_added; /* protected by ublk_ctl_mutex */
+/* checked and incremented under ublk_ctl_mutex, decremented without it */
+static atomic_t unprivileged_ublks_added = ATOMIC_INIT(0);
static struct miscdevice ublk_misc;
@@ -4405,14 +4406,18 @@ static int ublk_alloc_dev_number(struct ublk_device *ub, int idx)
int i = idx;
int err;
+ /*
+ * Only reserve the id: the device is published by
+ * ublk_publish_dev_number() once it is fully set up.
+ */
spin_lock(&ublk_idr_lock);
/* allocate id, if @id >= 0, we're requesting that specific id */
if (i >= 0) {
- err = idr_alloc(&ublk_index_idr, ub, i, i + 1, GFP_NOWAIT);
+ err = idr_alloc(&ublk_index_idr, NULL, i, i + 1, GFP_NOWAIT);
if (err == -ENOSPC)
err = -EEXIST;
} else {
- err = idr_alloc(&ublk_index_idr, ub, 0, UBLK_MAX_UBLKS,
+ err = idr_alloc(&ublk_index_idr, NULL, 0, UBLK_MAX_UBLKS,
GFP_NOWAIT);
}
spin_unlock(&ublk_idr_lock);
@@ -4423,6 +4428,19 @@ static int ublk_alloc_dev_number(struct ublk_device *ub, int idx)
return err;
}
+/*
+ * Make the device visible to control commands by its id. Before its char
+ * device is added, DEL_DEV could find it and remove a char device that was
+ * never added, and drop the reference that ublk_add_chdev() drops on
+ * failure.
+ */
+static void ublk_publish_dev_number(struct ublk_device *ub)
+{
+ spin_lock(&ublk_idr_lock);
+ WARN_ON_ONCE(idr_replace(&ublk_index_idr, ub, ub->ub_number) != NULL);
+ spin_unlock(&ublk_idr_lock);
+}
+
static void ublk_free_dev_number(struct ublk_device *ub)
{
spin_lock(&ublk_idr_lock);
@@ -4469,7 +4487,7 @@ static int ublk_add_chdev(struct ublk_device *ub)
goto fail;
if (ub->dev_info.flags & UBLK_F_UNPRIVILEGED_DEV)
- unprivileged_ublks_added++;
+ atomic_inc(&unprivileged_ublks_added);
return 0;
fail:
put_device(dev);
@@ -4501,15 +4519,9 @@ static int ublk_add_tag_set(struct ublk_device *ub)
static void ublk_remove(struct ublk_device *ub)
{
- bool unprivileged;
-
ublk_stop_dev(ub);
cdev_device_del(&ub->cdev, &ub->cdev_dev);
- unprivileged = ub->dev_info.flags & UBLK_F_UNPRIVILEGED_DEV;
ublk_put_device(ub);
-
- if (unprivileged)
- unprivileged_ublks_added--;
}
static struct ublk_device *ublk_get_device_from_id(int idx)
@@ -4899,7 +4911,7 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header)
ret = -EACCES;
if ((info.flags & UBLK_F_UNPRIVILEGED_DEV) &&
- unprivileged_ublks_added >= unprivileged_ublks_max)
+ atomic_read(&unprivileged_ublks_added) >= unprivileged_ublks_max)
goto out_unlock;
ret = -ENOMEM;
@@ -4991,6 +5003,8 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header)
* ublk_add_chdev() will cleanup everything if it fails.
*/
ret = ublk_add_chdev(ub);
+ if (!ret)
+ ublk_publish_dev_number(ub);
goto out_unlock;
out_deinit_queues:
@@ -5023,23 +5037,23 @@ static int ublk_ctrl_del_dev(struct ublk_device **p_ub, bool wait)
{
struct ublk_device *ub = *p_ub;
int idx = ub->ub_number;
- int ret;
-
- ret = mutex_lock_killable(&ublk_ctl_mutex);
- if (ret)
- return ret;
+ bool unprivileged = ub->dev_info.flags & UBLK_F_UNPRIVILEGED_DEV;
- if (!test_bit(UB_STATE_DELETED, &ub->state)) {
+ /*
+ * Don't take ublk_ctl_mutex: stopping the device may wait for the
+ * filesystem on its disk indefinitely, and ADD_DEV and DEL_DEV of all
+ * other ublk devices would wait for that too. ADD_DEV is done with the
+ * device, it is found by its id only then.
+ */
+ if (!test_and_set_bit(UB_STATE_DELETED, &ub->state)) {
ublk_remove(ub);
- set_bit(UB_STATE_DELETED, &ub->state);
+ if (unprivileged)
+ atomic_dec(&unprivileged_ublks_added);
}
- mutex_unlock(&ublk_ctl_mutex);
-
/*
- * Drop the reference outside ublk_ctl_mutex: if it is the last one,
- * the release frees the tag set, which waits for an SRCU grace period,
- * and holding the mutex would serialize concurrent deletions on it.
+ * If this is the last reference, the release frees the tag set, which
+ * waits for an SRCU grace period.
*/
*p_ub = NULL;
ublk_put_device(ub);
diff --git a/drivers/s390/block/dasd.c b/drivers/s390/block/dasd.c
index 2ffe404b890f..3a55c937d0d6 100644
--- a/drivers/s390/block/dasd.c
+++ b/drivers/s390/block/dasd.c
@@ -1396,6 +1396,7 @@ int dasd_start_IO(struct dasd_ccw_req *cqr)
cqr->status = DASD_CQR_ERROR;
return -EIO;
}
+ dasd_start_IO_prepare(cqr);
cqr->startclk = get_tod_clock();
cqr->starttime = jiffies;
cqr->retries--;
@@ -2576,7 +2577,7 @@ static inline int _dasd_term_running_cqr(struct dasd_device *device)
return rc;
}
-int dasd_sleep_on_immediatly(struct dasd_ccw_req *cqr)
+int dasd_sleep_on_immediately(struct dasd_ccw_req *cqr)
{
struct dasd_device *device;
int rc;
@@ -2628,7 +2629,7 @@ int dasd_sleep_on_immediatly(struct dasd_ccw_req *cqr)
return rc;
}
-EXPORT_SYMBOL(dasd_sleep_on_immediatly);
+EXPORT_SYMBOL(dasd_sleep_on_immediately);
/*
* Cancels a request that was started with dasd_sleep_on_req.
diff --git a/drivers/s390/block/dasd_alias.c b/drivers/s390/block/dasd_alias.c
index 8d6c16d35c22..1ffcfcfe4565 100644
--- a/drivers/s390/block/dasd_alias.c
+++ b/drivers/s390/block/dasd_alias.c
@@ -750,7 +750,7 @@ static int reset_summary_unit_check(struct alias_lcu *lcu,
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
- rc = dasd_sleep_on_immediatly(cqr);
+ rc = dasd_sleep_on_immediately(cqr);
return rc;
}
diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c
index 9d1b11020b4e..7e60d5b3b343 100644
--- a/drivers/s390/block/dasd_eckd.c
+++ b/drivers/s390/block/dasd_eckd.c
@@ -845,20 +845,29 @@ static int dasd_eckd_compare_path_uid(struct dasd_device *device,
return memcmp(&device_uid, &path_uid, sizeof(struct dasd_uid));
}
+/*
+ * The RCD buffer has to start with EBCDIC "V1.0" to show support for
+ * virtual device SNEQ. The RCD data is written into the same buffer, so
+ * a failed attempt may already have overwritten the magic. Re-initialize
+ * it before every start, including restarts by ERP requests.
+ */
+static void dasd_eckd_rcd_prepare_io(struct dasd_ccw_req *cqr)
+{
+ struct ccw1 *ccw = cqr->cpaddr;
+ __u8 *rcd_buffer = dma32_to_virt(ccw->cda);
+
+ rcd_buffer[0] = 0xE5;
+ rcd_buffer[1] = 0xF1;
+ rcd_buffer[2] = 0x4B;
+ rcd_buffer[3] = 0xF0;
+}
+
static void dasd_eckd_fill_rcd_cqr(struct dasd_device *device,
struct dasd_ccw_req *cqr,
__u8 *rcd_buffer,
__u8 lpm)
{
struct ccw1 *ccw;
- /*
- * buffer has to start with EBCDIC "V1.0" to show
- * support for virtual device SNEQ
- */
- rcd_buffer[0] = 0xE5;
- rcd_buffer[1] = 0xF1;
- rcd_buffer[2] = 0x4B;
- rcd_buffer[3] = 0xF0;
ccw = cqr->cpaddr;
ccw->cmd_code = DASD_ECKD_CCW_RCD;
@@ -875,33 +884,10 @@ static void dasd_eckd_fill_rcd_cqr(struct dasd_device *device,
cqr->retries = 256;
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
+ cqr->prepare_io = dasd_eckd_rcd_prepare_io;
set_bit(DASD_CQR_VERIFY_PATH, &cqr->flags);
}
-/*
- * Wakeup helper for read_conf
- * if the cqr is not done and needs some error recovery
- * the buffer has to be re-initialized with the EBCDIC "V1.0"
- * to show support for virtual device SNEQ
- */
-static void read_conf_cb(struct dasd_ccw_req *cqr, void *data)
-{
- struct ccw1 *ccw;
- __u8 *rcd_buffer;
-
- if (cqr->status != DASD_CQR_DONE) {
- ccw = cqr->cpaddr;
- rcd_buffer = dma32_to_virt(ccw->cda);
- memset(rcd_buffer, 0, sizeof(*rcd_buffer));
-
- rcd_buffer[0] = 0xE5;
- rcd_buffer[1] = 0xF1;
- rcd_buffer[2] = 0x4B;
- rcd_buffer[3] = 0xF0;
- }
- dasd_wakeup_cb(cqr, data);
-}
-
static int dasd_eckd_read_conf_immediately(struct dasd_device *device,
struct dasd_ccw_req *cqr,
__u8 *rcd_buffer,
@@ -921,8 +907,7 @@ static int dasd_eckd_read_conf_immediately(struct dasd_device *device,
clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
set_bit(DASD_CQR_ALLOW_SLOCK, &cqr->flags);
cqr->retries = 5;
- cqr->callback = read_conf_cb;
- rc = dasd_sleep_on_immediatly(cqr);
+ rc = dasd_sleep_on_immediately(cqr);
return rc;
}
@@ -959,7 +944,6 @@ static int dasd_eckd_read_conf_lpm(struct dasd_device *device,
goto out_error;
}
dasd_eckd_fill_rcd_cqr(device, cqr, rcd_buf, lpm);
- cqr->callback = read_conf_cb;
ret = dasd_sleep_on(cqr);
/*
* on success we update the user input parms
@@ -2780,7 +2764,8 @@ dasd_eckd_build_check_tcw(struct dasd_device *base, struct format_data_t *fdata,
return cqr;
out_err:
- dasd_sfree_request(cqr, startdev);
+ start_priv->count--;
+ dasd_ffree_request(cqr, startdev);
return ERR_PTR(rc);
}
@@ -6008,7 +5993,7 @@ __context_unsafe(/* Conditional locking */)
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
- rc = dasd_sleep_on_immediatly(cqr);
+ rc = dasd_sleep_on_immediately(cqr);
if (!rc)
clear_bit(DASD_FLAG_IS_RESERVED, &device->flags);
@@ -6064,7 +6049,7 @@ __context_unsafe(/* Conditional locking */)
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
- rc = dasd_sleep_on_immediatly(cqr);
+ rc = dasd_sleep_on_immediately(cqr);
if (!rc)
set_bit(DASD_FLAG_IS_RESERVED, &device->flags);
@@ -6119,7 +6104,7 @@ __context_unsafe(/* Conditional locking */)
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
- rc = dasd_sleep_on_immediatly(cqr);
+ rc = dasd_sleep_on_immediately(cqr);
if (!rc)
set_bit(DASD_FLAG_IS_RESERVED, &device->flags);
@@ -6183,7 +6168,7 @@ __context_unsafe(/* Conditional locking */)
cqr->status = DASD_CQR_FILLED;
cqr->lpm = usrparm.path_mask;
- rc = dasd_sleep_on_immediatly(cqr);
+ rc = dasd_sleep_on_immediately(cqr);
/* verify that I/O processing didn't modify the path mask */
if (!rc && usrparm.path_mask && (cqr->lpm != usrparm.path_mask))
rc = -EIO;
@@ -6815,7 +6800,7 @@ retry:
cqr->block = NULL;
cqr->expires = 10 * HZ;
set_bit(DASD_CQR_VERIFY_PATH, &cqr->flags);
- /* dasd_sleep_on_immediatly does not do complex error
+ /* dasd_sleep_on_immediately does not do complex error
* recovery so clear erp flag and set retry counter to
* do basic erp */
clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
@@ -6847,7 +6832,7 @@ retry:
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
- rc = dasd_sleep_on_immediatly(cqr);
+ rc = dasd_sleep_on_immediately(cqr);
if (rc == 0) {
prssdp = (struct dasd_psf_prssd_data *) cqr->data;
message_buf = (struct dasd_rssd_messages *)
diff --git a/drivers/s390/block/dasd_int.h b/drivers/s390/block/dasd_int.h
index ef4930432c09..53c450036fe1 100644
--- a/drivers/s390/block/dasd_int.h
+++ b/drivers/s390/block/dasd_int.h
@@ -147,6 +147,8 @@ struct dasd_ccw_req {
void *data; /* pointer to data area */
struct irb irb; /* device status in case of an error */
struct dasd_ccw_req *refers; /* ERP-chain queueing. */
+ /* optional prepare function called before every start_io, must not sleep */
+ void (*prepare_io)(struct dasd_ccw_req *cqr);
void *function; /* originating ERP action */
void *mem_chunk;
@@ -892,6 +894,19 @@ static inline void *dasd_get_callback_data(struct dasd_ccw_req *cqr)
return cqr->callback_data;
}
+/*
+ * Walk down the ERP chain to the original CQR and call its
+ * prepare_io function if set.
+ */
+static inline void dasd_start_IO_prepare(struct dasd_ccw_req *cqr)
+{
+ /* ERP requests use the prepare function of the original request */
+ while (cqr->refers)
+ cqr = cqr->refers;
+ if (cqr->prepare_io)
+ cqr->prepare_io(cqr);
+}
+
static inline bool dasd_req_conflict(struct dasd_ccw_req *cqr1,
struct dasd_ccw_req *cqr2)
{
@@ -998,7 +1013,7 @@ void dasd_schedule_device_bh(struct dasd_device *);
void dasd_schedule_block_bh(struct dasd_block *);
int dasd_sleep_on(struct dasd_ccw_req *);
int dasd_sleep_on_queue(struct list_head *);
-int dasd_sleep_on_immediatly(struct dasd_ccw_req *);
+int dasd_sleep_on_immediately(struct dasd_ccw_req *);
int dasd_sleep_on_queue_interruptible(struct list_head *);
int dasd_sleep_on_interruptible(struct dasd_ccw_req *);
void dasd_device_set_timer(struct dasd_device *, int);
diff --git a/drivers/s390/block/dasd_ioctl.c b/drivers/s390/block/dasd_ioctl.c
index e5b8b413f5ab..2f2ec2bea333 100644
--- a/drivers/s390/block/dasd_ioctl.c
+++ b/drivers/s390/block/dasd_ioctl.c
@@ -303,7 +303,9 @@ static int dasd_ioctl_check_format(struct block_device *bdev, void __user *argp)
goto out_err;
}
- if (copy_from_user(&cdata, argp, sizeof(cdata))) {
+ /* copy only input fields, clear the rest */
+ memset(&cdata, 0, sizeof(cdata));
+ if (copy_from_user(&cdata.expect, argp, sizeof(cdata.expect))) {
rc = -EFAULT;
goto out_err;
}