diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-11-13 10:31:05 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-11-13 10:31:05 -0800 |
| commit | e93fd809fb5692d1571bd008988ba734fd7fc976 (patch) | |
| tree | f90dfe2773a029319cef7a5039ed4748d3ff1220 | |
| parent | 831615b085fa5b9c85c5a52dadd71ebcfb8d5985 (diff) | |
| parent | 7cc2dda4f7aaf596c1796184649007151ff98f8d (diff) | |
| download | linux-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.c | 60 | ||||
| -rw-r--r-- | drivers/s390/block/dasd.c | 5 | ||||
| -rw-r--r-- | drivers/s390/block/dasd_alias.c | 2 | ||||
| -rw-r--r-- | drivers/s390/block/dasd_eckd.c | 69 | ||||
| -rw-r--r-- | drivers/s390/block/dasd_int.h | 17 | ||||
| -rw-r--r-- | drivers/s390/block/dasd_ioctl.c | 4 |
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; } |
