Merge tag 'for-7.4/block-20261021' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux

Pull block updates from Jens Axboe:

 - NVMe updates via Keith:
     - Apple M4 (t8132) ANS2 NVMe support
     - nvme-multipath sysfs attributes for path_state, iopolicies, and
       fail_on_ctrl_loss, plus path state evaluation fixes
     - nvme-tcp parallelized I/O queue allocation and startup, and using
       the caller's network namespace
     - nvmet passthru UAF fix and rework, PR preempt use-after-free
       fixes, auth hardening, and traffic based keep alive support
     - DH-HMAC-CHAP secret attributes readable by root only
     - Various other fixes and cleanups

 - swim driver overhaul and fixes, plus floppy fixes, via a pull from
   Denis

 - Block device buffered and direct I/O rework from Tal: use
   iomap_dirty_folio, take i_rwsem for the write fallback and splice
   read, honor IOCB_NOWAIT in the buffered read path, and reject atomic
   write fallback to buffered I/O

 - Large zone revalidation rework: serialize revalidation, handle
   capacity changes by dropping and retrying zone write plugs, propagate
   read-only and offline conditions, and treat offline and read-only
   zones as dead

 - ublk robustness work around canceled FETCH rounds and going live over
   canceled io commands, dropping the default I/O scheduler, and a batch
   of selftest additions

 - blk-iocost flush cost support and zone append accounting

 - rust block fixes for Send/Sync bounds on GenDisk and TagSet, and
   import style cleanups

 - Atomic write fixes capping size by the PI buffer constraints and
   failing short atomic pins

 - Skip the redundant flush for O_DSYNC direct writes, and only use
   REQ_FUA when the device supports it

 - Store GPT attributes as a raw value

 - Remove support for swapping loop fds

 - Various other fixes and cleanups all over

* tag 'for-7.4/block-20261021' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux: (144 commits)
  block: restore the iterator when rejecting a misaligned bio
  blk-mq: clear stale inactive flag on hctx reuse
  block, bfq: initialize active lists for all actuators
  zloop: print device that had errors
  ublk: pin module while release work is pending
  blk-mq: drop the tag_set argument from blk_mq_free_rq_map/tags
  blk-mq: share one tags_srcu instance across all tag sets
  blk-iocost: fix stale comment in ioc_rqos_throttle()
  blk-iocost: account zone append completions in latency stats
  blk-iocost: charge zone appends as writes
  blk-iocost: add flush cost support with the flushiops model parameter
  ublk: use global index for reported zones
  selftests: ublk: add test for going live over canceled io commands
  selftests: ublk: stop a batch thread when PREP_IO_CMDS fails
  selftests: ublk: move the control command helpers into ctrl.c
  ublk: refuse to go live after an io command was canceled
  ublk: let STOP_DEV cancel the server's commands before its release
  ublk: allow a NULL io->cmd in the cancel callback
  ublk: reset the FETCH round under ub->mutex
  ublk: reset the FETCH round in release also without a disk
  ...
This commit is contained in:
Linus Torvalds committed 2026-10-24 14:14:00 -10:00
commit b23545529c
66 files changed
+3788 -1536

No files matched your search

+52
View File
@@ -326,6 +326,27 @@ Description:
currently selected path. Returns empty if iopolicy is not
"numa". Requires CONFIG_NVME_MULTIPATH.
What: /sys/block/nvmeXcYnZ/path_state
Date: September 2026
KernelVersion: 7.4
Contact: Guixin Liu <kanie@linux.alibaba.com>
Description:
Shows the current multipath path state for this namespace
path device. Possible values:
"enabled": the path is usable for I/O.
"ctrl-down": the path is disabled because the controller
is not live.
"ana-pending": the path is disabled because an ANA state
change is pending.
"ns-not-ready": the path is disabled because the namespace
is not ready.
Requires CONFIG_NVME_MULTIPATH.
What: /sys/block/nvmeXnY/delayed_removal_secs
Date: May 2025
KernelVersion: 6.16
@@ -337,6 +358,22 @@ Description:
is deferred. Only visible on multipath head devices.
Requires CONFIG_NVME_MULTIPATH.
What: /sys/block/nvmeXnY/fail_on_ctrl_loss
Date: October 2026
KernelVersion: 7.4
Contact: Krishna Iyer <kiyer@crusoe.ai>
Description:
Shows or sets the fail-on-ctrl-loss policy of the multipath
head device ("on" or "off", default "off"). When on, I/O
queued or arriving while no usable path exists is failed
immediately instead of being queued. This is the opposite of
the queue-if-no-path behavior enabled by a nonzero
delayed_removal_secs: when both are set fail_on_ctrl_loss takes
precedence and the parked I/O is failed rather than held for
the delayed_removal_secs window. Reconnect attempts are not
affected. Only visible on multipath head devices.
Requires CONFIG_NVME_MULTIPATH.
What: /sys/block/nvmeXnY/csi
What: /sys/block/nvmeXnY/metadata_bytes
What: /sys/block/nvmeXnY/nuse
@@ -444,6 +481,21 @@ Description:
selections. Only available when CONFIG_NVME_MULTIPATH is
enabled.
A discovery subsystem has no namespaces and hence no I/O
paths to select from: reading returns "none" and writing
fails with EOPNOTSUPP.
What: /sys/class/nvme-subsystem/nvme-subsysX/iopolicies
Date: September 2026
KernelVersion: 7.4
Contact: Laurence Oberman <loberman@redhat.com>
Description:
Shows the I/O path selection policies that may be written
to the iopolicy attribute of this subsystem, separated by
spaces: "numa round-robin queue-depth". A discovery
subsystem accepts no policy at all and reports "none".
Only available when CONFIG_NVME_MULTIPATH is enabled.
What: /sys/class/nvme-subsystem/nvme-subsysX/subsystype
Date: September 2021
KernelVersion: 5.16
+7
View File
@@ -2131,6 +2131,7 @@ IO Interface Files
[r|w]bps The maximum sequential IO throughput
[r|w]seqiops The maximum 4k sequential IOs per second
[r|w]randiops The maximum 4k random IOs per second
flushiops The maximum flushes per second
============= ========================================
From the above, the builtin linear model determines the base
@@ -2138,6 +2139,12 @@ IO Interface Files
for the IO size. While simple, this model can cover most
common device classes acceptably.
"flushiops" determines the cost of a cache flush: a write with
a preceding cache flush is charged one flush on top of its data
cost, and a FUA write one more flush on devices without native
FUA support. It is zero in the builtin profiles, so flushes
stay free until it is configured.
The IO cost model isn't expected to be accurate in absolute
sense and is scaled to the device behavior dynamically.
+15 -2
View File
@@ -118,7 +118,11 @@ managing and controlling ublk devices with help of several control commands:
After the server prepares userspace resources (such as creating I/O handler
threads & io_uring for handling ublk IO), this command is sent to the
driver for allocating & exposing ``/dev/ublkb*``. Parameters set via
``UBLK_CMD_SET_PARAMS`` are applied for creating the device.
``UBLK_CMD_SET_PARAMS`` are applied for creating the device. The command
fails with ``-EBUSY`` if an I/O command fetched by the current server
was canceled, because its io_uring is gone. The server can't fetch it
again, and the device can be started again once the server has closed
``/dev/ublkc*``.
- ``UBLK_CMD_STOP_DEV``
@@ -126,6 +130,13 @@ managing and controlling ublk devices with help of several control commands:
ublk server will release resources (such as destroying I/O handler threads &
io_uring).
If a server has ``/dev/ublkc*`` open, its fetched I/O commands complete with
``UBLK_IO_RES_ABORT``. Until it closes ``/dev/ublkc*``, new
``UBLK_IO_FETCH_REQ`` and ``UBLK_U_IO_PREP_IO_CMDS`` commands complete with
``UBLK_IO_RES_ABORT`` too, and ``UBLK_CMD_START_DEV`` fails with ``-EBUSY``,
even if the server hadn't fetched anything. The device can be started again
by a server that opens ``/dev/ublkc*`` after that.
- ``UBLK_CMD_DEL_DEV``
Remove ``/dev/ublkc*``. When this command returns, the allocated ublk device
@@ -195,7 +206,9 @@ managing and controlling ublk devices with help of several control commands:
command is accepted after ublk device is quiesced and a new process has
opened ``/dev/ublkc*`` and get all ublk queues be ready. When this command
returns, ublk device is unquiesced and new I/O requests are passed to the
new process.
new process. It fails with ``-EBUSY`` if an I/O command of the new
process was canceled already. The recovery can be started over once the
new process has closed ``/dev/ublkc*``.
- user recovery feature description
@@ -16,6 +16,7 @@ properties:
- items:
- const: apple,t6020-nvme-ans2
- const: apple,t8103-nvme-ans2
- const: apple,t8132-nvme-ans2
- items:
- enum:
# Do not add additional SoC to this list.
+30 -1
View File
@@ -984,8 +984,37 @@ static int __init mac_platform_init(void)
.start = swim_base,
.end = swim_base + 0x1FFF,
};
bool fast_fclk = false;
platform_device_register_simple("swim", -1, &swim_rsrc, 1);
/* Models in this list are supposed to run FCLK at 32 MHz */
switch (macintosh_config->ident) {
case MAC_MODEL_CCL:
case MAC_MODEL_CCLII:
case MAC_MODEL_LCIII:
case MAC_MODEL_Q605:
case MAC_MODEL_Q605_ACC:
case MAC_MODEL_Q610:
case MAC_MODEL_Q630:
case MAC_MODEL_Q650:
case MAC_MODEL_Q800:
case MAC_MODEL_P460:
case MAC_MODEL_P475:
case MAC_MODEL_P475F:
case MAC_MODEL_P520:
case MAC_MODEL_P550:
case MAC_MODEL_P575:
case MAC_MODEL_P588:
case MAC_MODEL_TV:
case MAC_MODEL_C610:
case MAC_MODEL_C650:
case MAC_MODEL_PB190:
case MAC_MODEL_PB520:
fast_fclk = true;
break;
}
platform_device_register_resndata(NULL, "swim", -1, &swim_rsrc, 1,
&fast_fclk, sizeof(fast_fclk));
}
/*
+1 -1
View File
@@ -292,7 +292,7 @@ EXPORT_SYMBOL(sync_blockdev_range);
* The reference counter (bd_fsfreeze_count) guarantees that only the last
* unfreeze process can unfreeze the frozen filesystem actually when multiple
* freeze requests arrive simultaneously. It counts up in bdev_freeze() and
* count down in bdev_thaw(). When it becomes 0, thaw_bdev() will unfreeze
* count down in bdev_thaw(). When it becomes 0, bdev_thaw() will unfreeze
* actually.
*
* Return: On success zero is returned, negative error code on failure.
+2 -2
View File
@@ -7273,8 +7273,8 @@ static int bfq_init_queue(struct request_queue *q, struct elevator_queue *eq)
bfqd->num_groups_with_pending_reqs = 0;
#endif
INIT_LIST_HEAD(&bfqd->active_list[0]);
INIT_LIST_HEAD(&bfqd->active_list[1]);
for (i = 0; i < bfqd->num_actuators; i++)
INIT_LIST_HEAD(&bfqd->active_list[i]);
INIT_LIST_HEAD(&bfqd->idle_list);
INIT_HLIST_HEAD(&bfqd->burst_list);
+60 -17
View File
@@ -1217,6 +1217,21 @@ bool bio_iov_iter_set(struct bio *bio, const struct iov_iter *iter)
return true;
}
static unsigned int bvec_nr_pages(const struct bio_vec *bv)
{
return (bv->bv_offset + bv->bv_len - 1) / PAGE_SIZE -
bv->bv_offset / PAGE_SIZE + 1;
}
static void bvec_unpin(struct bio_vec *bv, bool mark_dirty)
{
struct folio *folio = bvec_folio(bv);
if (mark_dirty)
folio_mark_dirty_lock(folio);
unpin_user_folio(folio, bvec_nr_pages(bv));
}
/*
* Aligns the bio size to the len_align_mask, releasing excessive bio vecs that
* __bio_iov_iter_get_pages may have inserted, and reverts the trimmed length
@@ -1227,6 +1242,7 @@ static int bio_iov_iter_align_down(struct bio *bio, struct iov_iter *iter,
{
struct bio_vec *bv = &bio->bi_io_vec[bio->bi_vcnt - 1];
size_t nbytes = bio->bi_iter.bi_size & len_align_mask;
unsigned int npages;
if (!nbytes)
return 0;
@@ -1235,14 +1251,24 @@ static int bio_iov_iter_align_down(struct bio *bio, struct iov_iter *iter,
bio->bi_iter.bi_size -= nbytes;
while (nbytes >= bv->bv_len) {
if (bio_flagged(bio, BIO_PAGE_PINNED))
unpin_user_page(bv->bv_page);
bvec_unpin(bv, false);
if (!--bio->bi_vcnt)
return -EFAULT;
nbytes -= bv->bv_len;
bv--;
}
/*
* __bio_release_pages() only unpins the pages still covered by
* the trimmed bv_len. Count the pages spanned before and after
* the trim and unpin the difference.
*/
npages = bvec_nr_pages(bv);
bv->bv_len -= nbytes;
npages -= bvec_nr_pages(bv);
if (npages && bio_flagged(bio, BIO_PAGE_PINNED))
unpin_user_folio(bvec_folio(bv), npages);
return 0;
}
@@ -1308,6 +1334,8 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter,
unsigned len_align_mask)
{
iov_iter_extraction_t flags = 0;
struct iov_iter saved_iter;
int ret;
if (WARN_ON_ONCE(bio_flagged(bio, BIO_CLONED)))
return -EIO;
@@ -1328,34 +1356,49 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter,
if (bio->bi_bdev && blk_queue_pci_p2pdma(bio->bi_bdev->bd_disk->queue))
flags |= ITER_ALLOW_P2PDMA;
saved_iter = *iter;
do {
ssize_t ret;
ssize_t size;
ret = iov_iter_extract_bvecs(iter, bio->bi_io_vec,
size = iov_iter_extract_bvecs(iter, bio->bi_io_vec,
maxlen - bio->bi_iter.bi_size,
&bio->bi_vcnt, bio->bi_max_vecs,
mem_align_mask, flags);
if (ret <= 0) {
/*
* A misaligned vector fails the whole I/O. Release any
* pages pinned by earlier iterations before returning
* since this bio won't be submitted to release them.
*/
if (ret == -EINVAL) {
bio_release_pages(bio, false);
bio_clear_flag(bio, BIO_PAGE_PINNED);
bio->bi_vcnt = 0;
}
if (size <= 0) {
/* A misaligned vector fails the whole I/O */
if (size == -EINVAL)
goto out_release_pages;
if (!bio->bi_vcnt)
return ret;
return size;
break;
}
bio->bi_iter.bi_size += ret;
bio->bi_iter.bi_size += size;
} while (iov_iter_count(iter) && !bio_full(bio, 0));
if (is_pci_p2pdma_page(bio->bi_io_vec->bv_page))
bio->bi_opf |= REQ_NOMERGE;
return bio_iov_iter_align_down(bio, iter, len_align_mask);
ret = bio_iov_iter_align_down(bio, iter, len_align_mask);
if (ret)
return ret;
/*
* An atomic write is submitted as a single bio, so it has to cover
* the whole iterator or it would be torn.
*/
if ((bio->bi_opf & REQ_ATOMIC) && iov_iter_count(iter))
goto out_release_pages;
return 0;
out_release_pages:
/*
* Release the pages pinned so far before failing, since this bio won't
* be submitted to release them.
*/
bio_release_pages(bio, false);
bio_clear_flag(bio, BIO_PAGE_PINNED);
bio->bi_vcnt = 0;
*iter = saved_iter;
return -EINVAL;
}
static struct folio *folio_alloc_greedy(gfp_t gfp, size_t *size,
+5 -2
View File
@@ -902,8 +902,11 @@ void submit_bio_noacct(struct bio *bio)
case REQ_OP_ZONE_CLOSE:
case REQ_OP_ZONE_RESET:
case REQ_OP_ZONE_FINISH:
/* Zone management operations require sequential zones. */
if (!bdev_zone_is_seq(bio->bi_bdev, bio->bi_iter.bi_sector))
/*
* Zone management operations require sequential zones that are
* not offline nor read-only.
*/
if (!bdev_zone_mgmt_allowed(bdev, bio->bi_iter.bi_sector))
goto end_io;
break;
case REQ_OP_ZONE_RESET_ALL:
+31 -8
View File
@@ -353,6 +353,7 @@ enum {
I_LCOEF_WBPS,
I_LCOEF_WSEQIOPS,
I_LCOEF_WRANDIOPS,
I_LCOEF_FLUSHIOPS,
NR_I_LCOEFS,
};
@@ -363,6 +364,7 @@ enum {
LCOEF_WPAGE,
LCOEF_WSEQIO,
LCOEF_WRANDIO,
LCOEF_FLUSH,
NR_LCOEFS,
};
@@ -883,6 +885,9 @@ static void ioc_refresh_lcoefs(struct ioc *ioc)
&c[LCOEF_RPAGE], &c[LCOEF_RSEQIO], &c[LCOEF_RRANDIO]);
calc_lcoefs(u[I_LCOEF_WBPS], u[I_LCOEF_WSEQIOPS], u[I_LCOEF_WRANDIOPS],
&c[LCOEF_WPAGE], &c[LCOEF_WSEQIO], &c[LCOEF_WRANDIO]);
c[LCOEF_FLUSH] = u[I_LCOEF_FLUSHIOPS] ?
DIV64_U64_ROUND_UP(VTIME_PER_SEC, u[I_LCOEF_FLUSHIOPS]) : 0;
}
/*
@@ -2533,7 +2538,16 @@ static void calc_vtime_cost_builtin(struct bio *bio, struct ioc_gq *iocg,
u64 seek_pages = 0;
u64 cost = 0;
/* Can't calculate cost for empty bio */
/*
* FUA on a device without native support becomes a post-flush;
* charge it like PREFLUSH from the flush coefficient.
*/
if (bio->bi_opf & REQ_PREFLUSH)
cost += ioc->params.lcoefs[LCOEF_FLUSH];
if ((bio->bi_opf & REQ_FUA) && !bdev_fua(bio->bi_bdev))
cost += ioc->params.lcoefs[LCOEF_FLUSH];
/* Can't calculate data cost for empty bio */
if (!bio->bi_iter.bi_size)
goto out;
@@ -2543,6 +2557,7 @@ static void calc_vtime_cost_builtin(struct bio *bio, struct ioc_gq *iocg,
coef_randio = ioc->params.lcoefs[LCOEF_RRANDIO];
coef_page = ioc->params.lcoefs[LCOEF_RPAGE];
break;
case REQ_OP_ZONE_APPEND:
case REQ_OP_WRITE:
coef_seqio = ioc->params.lcoefs[LCOEF_WSEQIO];
coef_randio = ioc->params.lcoefs[LCOEF_WRANDIO];
@@ -2586,6 +2601,7 @@ static void calc_size_vtime_cost_builtin(struct request *rq, struct ioc *ioc,
case REQ_OP_READ:
*costp = pages * ioc->params.lcoefs[LCOEF_RPAGE];
break;
case REQ_OP_ZONE_APPEND:
case REQ_OP_WRITE:
*costp = pages * ioc->params.lcoefs[LCOEF_WPAGE];
break;
@@ -2708,7 +2724,9 @@ static void ioc_rqos_throttle(struct rq_qos *rqos, struct bio *bio)
if (!iocg_activate(iocg, &now))
return;
iocg->cursor = bio_end_sector(bio);
/* dataless bios have no meaningful position for seq/rand detection */
if (bio->bi_iter.bi_size)
iocg->cursor = bio_end_sector(bio);
vtime = atomic64_read(&iocg->vtime);
cost = adjust_inuse_and_calc_cost(iocg, vtime, abs_cost, &now);
@@ -2725,10 +2743,11 @@ static void ioc_rqos_throttle(struct rq_qos *rqos, struct bio *bio)
/*
* We're over budget. This can be handled in two ways. IOs which may
* cause priority inversions are punted to @ioc->aux_iocg and charged as
* debt. Otherwise, the issuer is blocked on @iocg->waitq. Debt handling
* requires @ioc->lock, waitq handling @iocg->waitq.lock. Determine
* whether debt handling is needed and acquire locks accordingly.
* cause priority inversions are issued regardless and charged against
* @iocg->abs_vdebt as debt. Otherwise, the issuer is blocked on
* @iocg->waitq. Debt handling requires @ioc->lock, waitq handling
* @iocg->waitq.lock. Determine whether debt handling is needed and
* acquire locks accordingly.
*/
use_debt = bio_issue_as_root_blkg(bio) || fatal_signal_pending(current);
ioc_locked = use_debt || READ_ONCE(iocg->abs_vdebt);
@@ -2854,6 +2873,7 @@ static void ioc_rqos_done(struct rq_qos *rqos, struct request *rq)
pidx = QOS_RLAT;
rw = READ;
break;
case REQ_OP_ZONE_APPEND:
case REQ_OP_WRITE:
pidx = QOS_WLAT;
rw = WRITE;
@@ -3440,10 +3460,12 @@ static u64 ioc_cost_model_prfill(struct seq_file *sf,
spin_lock_irq(&ioc->lock);
seq_printf(sf, "%s ctrl=%s model=linear "
"rbps=%llu rseqiops=%llu rrandiops=%llu "
"wbps=%llu wseqiops=%llu wrandiops=%llu\n",
"wbps=%llu wseqiops=%llu wrandiops=%llu "
"flushiops=%llu\n",
dname, ioc->user_cost_model ? "user" : "auto",
u[I_LCOEF_RBPS], u[I_LCOEF_RSEQIOPS], u[I_LCOEF_RRANDIOPS],
u[I_LCOEF_WBPS], u[I_LCOEF_WSEQIOPS], u[I_LCOEF_WRANDIOPS]);
u[I_LCOEF_WBPS], u[I_LCOEF_WSEQIOPS], u[I_LCOEF_WRANDIOPS],
u[I_LCOEF_FLUSHIOPS]);
spin_unlock_irq(&ioc->lock);
return 0;
}
@@ -3470,6 +3492,7 @@ static const match_table_t i_lcoef_tokens = {
{ I_LCOEF_WBPS, "wbps=%u" },
{ I_LCOEF_WSEQIOPS, "wseqiops=%u" },
{ I_LCOEF_WRANDIOPS, "wrandiops=%u" },
{ I_LCOEF_FLUSHIOPS, "flushiops=%u" },
{ NR_I_LCOEFS, NULL },
};
+13 -6
View File
@@ -18,6 +18,13 @@
#include "blk-mq.h"
#include "blk-mq-sched.h"
/*
* Protects tag iteration against the deferred freeing of tags->page_list and
* flush queues. The callbacks touch neither the tag set nor driver code, so
* one global instance is enough and freeing a tag set needn't wait for them.
*/
DEFINE_SRCU(blk_mq_tags_srcu);
/*
* Recalculate wakeup batch when tag is shared by hctx.
*/
@@ -443,7 +450,7 @@ void blk_mq_tagset_busy_iter(struct blk_mq_tag_set *tagset,
unsigned int flags = tagset->flags;
int i, nr_tags, srcu_idx;
srcu_idx = srcu_read_lock(&tagset->tags_srcu);
srcu_idx = srcu_read_lock(&blk_mq_tags_srcu);
nr_tags = blk_mq_is_shared_tags(flags) ? 1 : tagset->nr_hw_queues;
@@ -452,7 +459,7 @@ void blk_mq_tagset_busy_iter(struct blk_mq_tag_set *tagset,
__blk_mq_all_tag_iter(tagset->tags[i], fn, priv,
BT_TAG_ITER_STARTED);
}
srcu_read_unlock(&tagset->tags_srcu, srcu_idx);
srcu_read_unlock(&blk_mq_tags_srcu, srcu_idx);
}
EXPORT_SYMBOL(blk_mq_tagset_busy_iter);
@@ -512,7 +519,7 @@ void blk_mq_queue_tag_busy_iter(struct request_queue *q, busy_tag_iter_fn *fn,
if (!percpu_ref_tryget(&q->q_usage_counter))
return;
srcu_idx = srcu_read_lock(&q->tag_set->tags_srcu);
srcu_idx = srcu_read_lock(&blk_mq_tags_srcu);
if (blk_mq_is_shared_tags(q->tag_set->flags)) {
struct blk_mq_tags *tags = q->tag_set->shared_tags;
struct sbitmap_queue *bresv = &tags->breserved_tags;
@@ -542,7 +549,7 @@ void blk_mq_queue_tag_busy_iter(struct request_queue *q, busy_tag_iter_fn *fn,
bt_for_each(hctx, q, btags, fn, priv, false);
}
}
srcu_read_unlock(&q->tag_set->tags_srcu, srcu_idx);
srcu_read_unlock(&blk_mq_tags_srcu, srcu_idx);
blk_queue_exit(q);
}
@@ -607,7 +614,7 @@ static void blk_mq_free_tags_callback(struct rcu_head *head)
kfree(tags);
}
void blk_mq_free_tags(struct blk_mq_tag_set *set, struct blk_mq_tags *tags)
void blk_mq_free_tags(struct blk_mq_tags *tags)
{
sbitmap_queue_free(&tags->bitmap_tags);
sbitmap_queue_free(&tags->breserved_tags);
@@ -618,7 +625,7 @@ void blk_mq_free_tags(struct blk_mq_tag_set *set, struct blk_mq_tags *tags)
return;
}
call_srcu(&set->tags_srcu, &tags->rcu_head, blk_mq_free_tags_callback);
call_srcu(&blk_mq_tags_srcu, &tags->rcu_head, blk_mq_free_tags_callback);
}
void blk_mq_tag_resize_shared_tags(struct blk_mq_tag_set *set, unsigned int size)
+11 -16
View File
@@ -1397,6 +1397,7 @@ static void blk_add_rq_to_plug(struct blk_plug *plug, struct request *rq)
(!blk_queue_nomerges(rq->q) &&
blk_rq_bytes(last) >= BLK_PLUG_FLUSH_SIZE)) {
blk_mq_flush_plug_list(plug, false);
blk_plug_invalidate_ts();
last = NULL;
trace_block_plug(rq->q);
}
@@ -3507,14 +3508,14 @@ void blk_mq_free_rqs(struct blk_mq_tag_set *set, struct blk_mq_tags *tags,
*/
}
void blk_mq_free_rq_map(struct blk_mq_tag_set *set, struct blk_mq_tags *tags)
void blk_mq_free_rq_map(struct blk_mq_tags *tags)
{
kfree(tags->rqs);
tags->rqs = NULL;
kfree(tags->static_rqs);
tags->static_rqs = NULL;
blk_mq_free_tags(set, tags);
blk_mq_free_tags(tags);
}
static enum hctx_type hctx_idx_to_type(struct blk_mq_tag_set *set,
@@ -3576,7 +3577,7 @@ static struct blk_mq_tags *blk_mq_alloc_rq_map(struct blk_mq_tag_set *set,
err_free_rqs:
kfree(tags->rqs);
err_free_tags:
blk_mq_free_tags(set, tags);
blk_mq_free_tags(tags);
return NULL;
}
@@ -3694,9 +3695,9 @@ static bool blk_mq_hctx_has_requests(struct blk_mq_hw_ctx *hctx)
};
int srcu_idx;
srcu_idx = srcu_read_lock(&hctx->queue->tag_set->tags_srcu);
srcu_idx = srcu_read_lock(&blk_mq_tags_srcu);
blk_mq_all_tag_iter(tags, blk_mq_has_request, &data);
srcu_read_unlock(&hctx->queue->tag_set->tags_srcu, srcu_idx);
srcu_read_unlock(&blk_mq_tags_srcu, srcu_idx);
return data.has_rq;
}
@@ -3968,7 +3969,7 @@ static void blk_mq_exit_hctx(struct request_queue *q,
if (set->ops->exit_hctx)
set->ops->exit_hctx(hctx, hctx_idx);
call_srcu(&set->tags_srcu, &hctx->fq->rcu_head,
call_srcu(&blk_mq_tags_srcu, &hctx->fq->rcu_head,
blk_free_flush_queue_callback);
hctx->fq = NULL;
@@ -4002,6 +4003,7 @@ static int blk_mq_init_hctx(struct request_queue *q,
goto fail;
hctx->queue_num = hctx_idx;
clear_bit(BLK_MQ_S_INACTIVE, &hctx->state);
hctx->tags = set->tags[hctx_idx];
@@ -4129,7 +4131,7 @@ struct blk_mq_tags *blk_mq_alloc_map_and_rqs(struct blk_mq_tag_set *set,
ret = blk_mq_alloc_rqs(set, tags, hctx_idx, depth);
if (ret) {
blk_mq_free_rq_map(set, tags);
blk_mq_free_rq_map(tags);
return NULL;
}
@@ -4157,7 +4159,7 @@ void blk_mq_free_map_and_rqs(struct blk_mq_tag_set *set,
{
if (tags) {
blk_mq_free_rqs(set, tags, hctx_idx);
blk_mq_free_rq_map(set, tags);
blk_mq_free_rq_map(tags);
}
}
@@ -4895,9 +4897,6 @@ int blk_mq_alloc_tag_set(struct blk_mq_tag_set *set)
if (ret)
goto out_free_srcu;
}
ret = init_srcu_struct(&set->tags_srcu);
if (ret)
goto out_cleanup_srcu;
init_rwsem(&set->update_nr_hwq_lock);
@@ -4906,7 +4905,7 @@ int blk_mq_alloc_tag_set(struct blk_mq_tag_set *set)
sizeof(struct blk_mq_tags *), GFP_KERNEL,
set->numa_node);
if (!set->tags)
goto out_cleanup_tags_srcu;
goto out_cleanup_srcu;
for (i = 0; i < set->nr_maps; i++) {
set->map[i].mq_map = kcalloc_node(nr_cpu_ids,
@@ -4935,8 +4934,6 @@ out_free_mq_map:
}
kfree(set->tags);
set->tags = NULL;
out_cleanup_tags_srcu:
cleanup_srcu_struct(&set->tags_srcu);
out_cleanup_srcu:
if (set->flags & BLK_MQ_F_BLOCKING)
cleanup_srcu_struct(set->srcu);
@@ -4983,8 +4980,6 @@ void blk_mq_free_tag_set(struct blk_mq_tag_set *set)
kfree(set->tags);
set->tags = NULL;
srcu_barrier(&set->tags_srcu);
cleanup_srcu_struct(&set->tags_srcu);
if (set->flags & BLK_MQ_F_BLOCKING) {
srcu_barrier(set->srcu);
cleanup_srcu_struct(set->srcu);
+4 -2
View File
@@ -62,7 +62,7 @@ void blk_mq_put_rq_ref(struct request *rq);
*/
void blk_mq_free_rqs(struct blk_mq_tag_set *set, struct blk_mq_tags *tags,
unsigned int hctx_idx);
void blk_mq_free_rq_map(struct blk_mq_tag_set *set, struct blk_mq_tags *tags);
void blk_mq_free_rq_map(struct blk_mq_tags *tags);
struct blk_mq_tags *blk_mq_alloc_map_and_rqs(struct blk_mq_tag_set *set,
unsigned int hctx_idx, unsigned int depth);
void blk_mq_free_map_and_rqs(struct blk_mq_tag_set *set,
@@ -176,7 +176,9 @@ struct blk_mq_alloc_data {
struct blk_mq_tags *blk_mq_init_tags(unsigned int nr_tags,
unsigned int reserved_tags, unsigned int flags, int node);
void blk_mq_free_tags(struct blk_mq_tag_set *set, struct blk_mq_tags *tags);
void blk_mq_free_tags(struct blk_mq_tags *tags);
extern struct srcu_struct blk_mq_tags_srcu;
unsigned int blk_mq_get_tag(struct blk_mq_alloc_data *data);
unsigned long blk_mq_get_tags(struct blk_mq_alloc_data *data, int nr_tags,
+10 -8
View File
@@ -206,12 +206,6 @@ static int blk_validate_integrity_limits(struct queue_limits *lim)
lim->max_sectors = min(lim->max_sectors,
max_integrity_io_size(lim) >> SECTOR_SHIFT);
if (lim->features & BLK_FEAT_ATOMIC_WRITES) {
lim->atomic_write_max_sectors =
min(lim->atomic_write_max_sectors,
max_integrity_io_size(lim) >> SECTOR_SHIFT);
}
return 0;
}
@@ -236,14 +230,20 @@ static unsigned int blk_queue_max_guaranteed_bio(struct queue_limits *lim)
static void blk_atomic_writes_update_limits(struct queue_limits *lim)
{
unsigned int integrity_max = lim->integrity.metadata_size ?
max_integrity_io_size(lim) : 0;
unsigned int unit_limit = min(lim->max_hw_sectors << SECTOR_SHIFT,
blk_queue_max_guaranteed_bio(lim));
unit_limit = min_not_zero(unit_limit, integrity_max);
unit_limit = rounddown_pow_of_two(unit_limit);
lim->atomic_write_max_sectors =
min(lim->atomic_write_hw_max >> SECTOR_SHIFT,
lim->max_hw_sectors);
lim->atomic_write_max_sectors =
min_not_zero(lim->atomic_write_max_sectors,
integrity_max >> SECTOR_SHIFT);
lim->atomic_write_unit_min =
min(lim->atomic_write_hw_unit_min, unit_limit);
lim->atomic_write_unit_max =
@@ -511,11 +511,13 @@ int blk_validate_limits(struct queue_limits *lim)
if (!(lim->features & BLK_FEAT_WRITE_CACHE))
lim->features &= ~BLK_FEAT_FUA;
blk_validate_atomic_write_limits(lim);
err = blk_validate_integrity_limits(lim);
if (err)
return err;
/* atomics limits depend on integrity limits */
blk_validate_atomic_write_limits(lim);
return blk_validate_zoned_limits(lim);
}
EXPORT_SYMBOL_GPL(blk_validate_limits);
+550 -330
View File
File diff suppressed because it is too large. Load diff
+6
View File
@@ -577,6 +577,7 @@ int blkdev_report_zones_ioctl(struct block_device *bdev, unsigned int cmd,
unsigned long arg);
int blkdev_zone_mgmt_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int cmd, unsigned long arg);
bool bdev_zone_mgmt_allowed(struct block_device *bdev, sector_t sector);
#else /* CONFIG_BLK_DEV_ZONED */
static inline void disk_init_zone_resources(struct gendisk *disk)
{
@@ -619,6 +620,11 @@ static inline int blkdev_zone_mgmt_ioctl(struct block_device *bdev,
{
return -ENOTTY;
}
static inline bool bdev_zone_mgmt_allowed(struct block_device *bdev,
sector_t sector)
{
return false;
}
#endif /* CONFIG_BLK_DEV_ZONED */
struct block_device *bdev_alloc(struct gendisk *disk, u8 partno);
+104 -37
View File
@@ -26,12 +26,26 @@ static inline struct inode *bdev_file_inode(struct file *file)
return file->f_mapping->host;
}
static blk_opf_t dio_bio_write_op(struct kiocb *iocb)
static bool blkdev_dio_fua(struct kiocb *iocb, struct block_device *bdev)
{
if (!iocb_is_dsync(iocb))
return false;
/*
* Async writes cannot fall back to generic_write_sync(), so they must
* use FUA (emulated if needed); sync writes only need it when the
* device supports FUA natively.
*/
if (!is_sync_kiocb(iocb))
return true;
return bdev_fua(bdev);
}
static blk_opf_t dio_bio_write_op(struct kiocb *iocb, struct block_device *bdev)
{
blk_opf_t opf = REQ_OP_WRITE | REQ_SYNC | REQ_IDLE;
/* avoid the need for a I/O completion work item */
if (iocb_is_dsync(iocb))
if (blkdev_dio_fua(iocb, bdev))
opf |= REQ_FUA;
return opf;
}
@@ -76,7 +90,8 @@ static ssize_t __blkdev_direct_IO_simple(struct kiocb *iocb,
if (user_backed_iter(iter))
should_dirty = true;
} else {
bio_init(&bio, bdev, vecs, nr_pages, dio_bio_write_op(iocb));
bio_init(&bio, bdev, vecs, nr_pages,
dio_bio_write_op(iocb, bdev));
}
bio.bi_iter.bi_sector = pos >> SECTOR_SHIFT;
bio.bi_write_hint = file_inode(iocb->ki_filp)->i_write_hint;
@@ -180,7 +195,7 @@ static ssize_t __blkdev_direct_IO(struct kiocb *iocb, struct iov_iter *iter,
struct blkdev_dio *dio;
struct bio *bio;
bool is_read = (iov_iter_rw(iter) == READ), is_sync;
blk_opf_t opf = is_read ? REQ_OP_READ : dio_bio_write_op(iocb);
blk_opf_t opf = is_read ? REQ_OP_READ : dio_bio_write_op(iocb, bdev);
loff_t pos = iocb->ki_pos;
int ret = 0;
@@ -307,9 +322,6 @@ static void blkdev_bio_end_io_async(struct bio *bio)
ret = blk_status_to_errno(bio->bi_status);
}
if (bio_integrity(bio))
bio_integrity_unmap_user(bio);
iocb->ki_complete(iocb, ret);
if (dio->flags & DIO_SHOULD_DIRTY) {
@@ -326,7 +338,7 @@ static ssize_t __blkdev_direct_IO_async(struct kiocb *iocb,
unsigned int nr_pages)
{
bool is_read = iov_iter_rw(iter) == READ;
blk_opf_t opf = is_read ? REQ_OP_READ : dio_bio_write_op(iocb);
blk_opf_t opf = is_read ? REQ_OP_READ : dio_bio_write_op(iocb, bdev);
struct blkdev_dio *dio;
struct bio *bio;
loff_t pos = iocb->ki_pos;
@@ -343,6 +355,12 @@ static ssize_t __blkdev_direct_IO_async(struct kiocb *iocb,
bio->bi_end_io = blkdev_bio_end_io_async;
bio->bi_ioprio = iocb->ki_ioprio;
if (iocb->ki_flags & IOCB_ATOMIC)
bio->bi_opf |= REQ_ATOMIC;
if (iocb->ki_flags & IOCB_NOWAIT)
bio->bi_opf |= REQ_NOWAIT;
/*
* Users don't rely on the iterator being in any particular
* state for async I/O returning -EIOCBQUEUED, hence we can
@@ -365,19 +383,6 @@ static ssize_t __blkdev_direct_IO_async(struct kiocb *iocb,
task_io_account_write(bio->bi_iter.bi_size);
}
if (iocb->ki_flags & IOCB_HAS_METADATA) {
ret = bio_integrity_map_iter(bio, iocb->private);
WRITE_ONCE(iocb->private, NULL);
if (unlikely(ret))
goto out_bio_put;
}
if (iocb->ki_flags & IOCB_ATOMIC)
bio->bi_opf |= REQ_ATOMIC;
if (iocb->ki_flags & IOCB_NOWAIT)
bio->bi_opf |= REQ_NOWAIT;
if (iocb->ki_flags & IOCB_HIPRI) {
bio->bi_opf |= REQ_POLLED;
submit_bio(bio);
@@ -566,7 +571,7 @@ static int blkdev_writepages(struct address_space *mapping,
}
const struct address_space_operations def_blk_aops = {
.dirty_folio = filemap_dirty_folio,
.dirty_folio = iomap_dirty_folio,
.release_folio = iomap_release_folio,
.invalidate_folio = iomap_invalidate_folio,
.read_folio = blkdev_read_folio,
@@ -715,8 +720,22 @@ blkdev_direct_write(struct kiocb *iocb, struct iov_iter *from)
static ssize_t blkdev_buffered_write(struct kiocb *iocb, struct iov_iter *from)
{
return iomap_file_buffered_write(iocb, from, &blkdev_iomap_ops, NULL,
NULL);
struct inode *inode = bdev_file_inode(iocb->ki_filp);
ssize_t ret;
/* Buffered fallback waits for writeback, which NOWAIT cannot allow. */
if (iocb->ki_flags & IOCB_NOWAIT)
return -EAGAIN;
/*
* Take i_rwsem to avoid racing with set_blocksize() changing
* i_blkbits/folio order and truncating the page cache.
*/
inode_lock_shared(inode);
ret = iomap_file_buffered_write(iocb, from, &blkdev_iomap_ops, NULL,
NULL);
inode_unlock_shared(inode);
return ret;
}
/*
@@ -734,6 +753,7 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
bool atomic = iocb->ki_flags & IOCB_ATOMIC;
loff_t size = bdev_nr_bytes(bdev);
size_t shorted = 0;
bool need_sync = false;
ssize_t ret;
if (bdev_read_only(bdev))
@@ -771,21 +791,44 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
if (iocb->ki_flags & IOCB_DIRECT) {
ret = blkdev_direct_write(iocb, from);
if (ret >= 0 && iov_iter_count(from))
ret = direct_write_fallback(iocb, from, ret,
blkdev_buffered_write(iocb, from));
if (ret >= 0 && iov_iter_count(from)) {
if (iocb->ki_flags & (IOCB_NOWAIT | IOCB_ATOMIC)) {
/*
* The buffered fallback blocks on i_rwsem and
* on writeback of the data it copied, and
* can't provide torn-write protection: return
* the short direct write instead and let the
* caller retry. The bytes already written
* still need a flush if REQ_FUA was not set.
*/
if (!ret)
ret = -EAGAIN;
else if (!blkdev_dio_fua(iocb, bdev))
need_sync = true;
} else {
ssize_t ret2;
inode_lock_shared(bd_inode);
ret2 = blkdev_buffered_write(iocb, from);
inode_unlock_shared(bd_inode);
ret = direct_write_fallback(iocb, from, ret,
ret2);
need_sync = true;
}
} else if (ret > 0 && !blkdev_dio_fua(iocb, bdev)) {
/*
* The device does not support FUA, so REQ_FUA was not
* set and the O_DSYNC direct write still needs a flush
* to be made durable.
*/
need_sync = true;
}
} else {
/*
* Take i_rwsem and invalidate_lock to avoid racing with
* set_blocksize changing i_blkbits/folio order and punching
* out the pagecache.
*/
inode_lock_shared(bd_inode);
ret = blkdev_buffered_write(iocb, from);
inode_unlock_shared(bd_inode);
need_sync = true;
}
if (ret > 0)
if (ret > 0 && need_sync)
ret = generic_write_sync(iocb, ret);
iov_iter_reexpand(from, iov_iter_count(from) + shorted);
return ret;
@@ -834,7 +877,15 @@ static ssize_t blkdev_read_iter(struct kiocb *iocb, struct iov_iter *to)
* Take i_rwsem and invalidate_lock to avoid racing with set_blocksize
* changing i_blkbits/folio order and punching out the pagecache.
*/
inode_lock_shared(bd_inode);
if (iocb->ki_flags & IOCB_NOWAIT) {
if (!inode_trylock_shared(bd_inode)) {
if (!ret)
ret = -EAGAIN;
goto reexpand;
}
} else {
inode_lock_shared(bd_inode);
}
ret = filemap_read(iocb, to, ret);
inode_unlock_shared(bd_inode);
@@ -844,6 +895,22 @@ reexpand:
return ret;
}
/*
* Take i_rwsem to avoid racing with set_blocksize changing i_blkbits/folio
* order and punching out the pagecache.
*/
static ssize_t blkdev_splice_read(struct file *in, loff_t *ppos,
struct pipe_inode_info *pipe, size_t len, unsigned int flags)
{
struct inode *bd_inode = bdev_file_inode(in);
ssize_t ret;
inode_lock_shared(bd_inode);
ret = filemap_splice_read(in, ppos, pipe, len, flags);
inode_unlock_shared(bd_inode);
return ret;
}
#define BLKDEV_FALLOC_FL_SUPPORTED \
(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE | \
FALLOC_FL_ZERO_RANGE | FALLOC_FL_WRITE_ZEROES)
@@ -945,7 +1012,7 @@ const struct file_operations def_blk_fops = {
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_blkdev_ioctl,
#endif
.splice_read = filemap_splice_read,
.splice_read = blkdev_splice_read,
.splice_write = iter_file_splice_write,
.fallocate = blkdev_fallocate,
.uring_cmd = blkdev_uring_cmd,
+63 -8
View File
@@ -265,6 +265,8 @@ static struct fd_data_type data_types[] = {
static struct amiga_floppy_struct unit[FD_MAX_UNITS];
static struct timer_list flush_track_timer[FD_MAX_UNITS];
static DEFINE_SPINLOCK(flush_track_timer_lock);
static bool flush_track_timer_stopped[FD_MAX_UNITS];
static struct timer_list post_write_timer;
static unsigned long post_write_timer_drive;
static struct timer_list motor_on_timer;
@@ -1370,11 +1372,22 @@ static void flush_track_callback(struct timer_list *timer)
sizeof(flush_track_timer[0]);
nr&=3;
spin_lock(&flush_track_timer_lock);
if (flush_track_timer_stopped[nr]) {
spin_unlock(&flush_track_timer_lock);
return;
}
spin_unlock(&flush_track_timer_lock);
writefromint = 1;
if (!try_fdc(nr)) {
/* we might block in an interrupt, so try again later */
flush_track_timer[nr].expires = jiffies + 1;
add_timer(flush_track_timer + nr);
spin_lock(&flush_track_timer_lock);
if (!flush_track_timer_stopped[nr]) {
flush_track_timer[nr].expires = jiffies + 1;
add_timer(flush_track_timer + nr);
}
spin_unlock(&flush_track_timer_lock);
return;
}
get_fdc(nr);
@@ -1421,6 +1434,35 @@ static int non_int_flush_track (unsigned long nr)
return 1;
}
static void flush_track_timer_stop(unsigned int drive)
{
unsigned long flags;
spin_lock_irqsave(&flush_track_timer_lock, flags);
flush_track_timer_stopped[drive] = true;
spin_unlock_irqrestore(&flush_track_timer_lock, flags);
timer_delete_sync(flush_track_timer + drive);
}
static void flush_track_timer_start(unsigned int drive)
{
unsigned long flags;
spin_lock_irqsave(&flush_track_timer_lock, flags);
flush_track_timer_stopped[drive] = false;
if (unit[drive].dirty)
mod_timer(flush_track_timer + drive, jiffies + 1);
spin_unlock_irqrestore(&flush_track_timer_lock, flags);
}
static void flush_track_timer_sync(unsigned int drive)
{
flush_track_timer_stop(drive);
non_int_flush_track(drive);
flush_track_timer_start(drive);
}
static int get_track(int drive, int track)
{
int error, errcnt;
@@ -1435,8 +1477,7 @@ static int get_track(int drive, int track)
}
if (unit[drive].dirty == 1) {
timer_delete(flush_track_timer + drive);
non_int_flush_track (drive);
flush_track_timer_sync(drive);
}
errcnt = 0;
while (errcnt < MAX_ERRORS) {
@@ -1562,6 +1603,12 @@ static int fd_locked_ioctl(struct block_device *bdev, blk_mode_t mode,
case FDFMTTRK:
if (param < p->type->tracks * p->type->heads)
{
flush_track_timer_stop(drive);
if (unit[drive].dirty == 1 &&
!non_int_flush_track(drive)) {
flush_track_timer_start(drive);
return -EIO;
}
get_fdc(drive);
if (fd_seek(drive,param) != 0){
memset(p->trackbuf, FD_FILL_BYTE,
@@ -1570,6 +1617,7 @@ static int fd_locked_ioctl(struct block_device *bdev, blk_mode_t mode,
}
floppy_off(drive);
rel_fdc();
flush_track_timer_start(drive);
}
else
return -EINVAL;
@@ -1591,8 +1639,7 @@ static int fd_locked_ioctl(struct block_device *bdev, blk_mode_t mode,
case FDDEFPRM:
return -EINVAL;
case FDFLUSH: /* unconditionally, even if not needed */
timer_delete(flush_track_timer + drive);
non_int_flush_track(drive);
flush_track_timer_sync(drive);
break;
#ifdef RAW_IOCTL
case IOCTL_RAW_TRACK:
@@ -1611,9 +1658,15 @@ static int fd_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int cmd, unsigned long param)
{
int ret;
unsigned int memflags = 0;
bool freeze_queue = cmd == FDFMTTRK || cmd == FDFLUSH;
mutex_lock(&amiflop_mutex);
if (freeze_queue)
memflags = blk_mq_freeze_queue(bdev->bd_disk->queue);
ret = fd_locked_ioctl(bdev, mode, cmd, param);
if (freeze_queue)
blk_mq_unfreeze_queue(bdev->bd_disk->queue, memflags);
mutex_unlock(&amiflop_mutex);
return ret;
@@ -1711,11 +1764,12 @@ static void floppy_release(struct gendisk *disk)
{
struct amiga_floppy_struct *p = disk->private_data;
int drive = p - unit;
unsigned int memflags;
mutex_lock(&amiflop_mutex);
memflags = blk_mq_freeze_queue(disk->queue);
if (unit[drive].dirty == 1) {
timer_delete(flush_track_timer + drive);
non_int_flush_track (drive);
flush_track_timer_sync(drive);
}
if (!fd_ref[drive]--) {
@@ -1725,6 +1779,7 @@ static void floppy_release(struct gendisk *disk)
#ifdef MODULE
floppy_off (drive);
#endif
blk_mq_unfreeze_queue(disk->queue, memflags);
mutex_unlock(&amiflop_mutex);
}
+6 -8
View File
@@ -131,9 +131,7 @@ static int
aoenet_rcv(struct sk_buff *skb, struct net_device *ifp, struct packet_type *pt, struct net_device *orig_dev)
{
struct aoe_hdr *h;
struct aoe_atahdr *ah;
u32 n;
int sn;
if (dev_net(ifp) != &init_net)
goto exit;
@@ -144,12 +142,8 @@ aoenet_rcv(struct sk_buff *skb, struct net_device *ifp, struct packet_type *pt,
if (!is_aoe_netif(ifp))
goto exit;
skb_push(skb, ETH_HLEN); /* (1) */
sn = sizeof(*h) + sizeof(*ah);
if (skb->len >= sn) {
sn -= skb_headlen(skb);
if (sn > 0 && !__pskb_pull_tail(skb, sn))
goto exit;
}
if (!pskb_may_pull(skb, sizeof(*h)))
goto exit;
h = (struct aoe_hdr *) skb->data;
n = get_unaligned_be32(&h->tag);
if ((h->verfl & AOEFL_RSP) == 0 || (n & 1<<31))
@@ -171,10 +165,14 @@ aoenet_rcv(struct sk_buff *skb, struct net_device *ifp, struct packet_type *pt,
switch (h->cmd) {
case AOECMD_ATA:
if (!pskb_may_pull(skb, sizeof(*h) + sizeof(struct aoe_atahdr)))
goto exit;
/* ata_rsp may keep skb for later processing or give it back */
skb = aoecmd_ata_rsp(skb);
break;
case AOECMD_CFG:
if (!pskb_may_pull(skb, sizeof(*h) + sizeof(struct aoe_cfghdr)))
goto exit;
aoecmd_cfg_rsp(skb);
break;
default:
+34 -22
View File
@@ -500,7 +500,7 @@ static int probing;
#define FD_COMMAND_ERROR 2
#define FD_COMMAND_OKAY 3
static volatile int command_status = FD_COMMAND_NONE;
static int command_status = FD_COMMAND_NONE;
static unsigned long fdc_busy;
static DECLARE_WAIT_QUEUE_HEAD(fdc_wait);
static DECLARE_WAIT_QUEUE_HEAD(command_done);
@@ -599,7 +599,8 @@ static inline void fdc_outb(unsigned char value, int fdc, int reg)
static inline bool drive_no_geom(int drive)
{
return !current_type[drive] && !ITYPE(drive_state[drive].fd_device);
return !READ_ONCE(current_type[drive]) &&
!ITYPE(drive_state[drive].fd_device);
}
#ifndef fd_eject
@@ -638,7 +639,7 @@ static const char *timeout_message;
static void is_alive(const char *func, const char *message)
{
/* this routine checks whether the floppy driver is "alive" */
if (test_bit(0, &fdc_busy) && command_status < 2 &&
if (test_bit(0, &fdc_busy) && READ_ONCE(command_status) < 2 &&
!delayed_work_pending(&fd_timeout)) {
DPRINT("%s: timeout handler died. %s\n", func, message);
}
@@ -890,7 +891,7 @@ static int lock_fdc(int drive)
if (wait_event_interruptible(fdc_wait, !test_and_set_bit(0, &fdc_busy)))
return -EINTR;
command_status = FD_COMMAND_NONE;
WRITE_ONCE(command_status, FD_COMMAND_NONE);
reschedule_timeout(drive, "lock fdc");
set_fdc(drive);
@@ -904,7 +905,7 @@ static void unlock_fdc(void)
DPRINT("FDC access conflict!\n");
raw_cmd = NULL;
command_status = FD_COMMAND_NONE;
WRITE_ONCE(command_status, FD_COMMAND_NONE);
cancel_delayed_work(&fd_timeout);
do_floppy = NULL;
cont = NULL;
@@ -988,7 +989,9 @@ static void (*floppy_work_fn)(void);
static void floppy_work_workfn(struct work_struct *work)
{
floppy_work_fn();
void (*fn)(void) = READ_ONCE(floppy_work_fn);
fn();
}
static DECLARE_WORK(floppy_work, floppy_work_workfn);
@@ -997,7 +1000,7 @@ static void schedule_bh(void (*handler)(void))
{
WARN_ON(work_pending(&floppy_work));
floppy_work_fn = handler;
WRITE_ONCE(floppy_work_fn, handler);
queue_work(floppy_wq, &floppy_work);
}
@@ -1784,6 +1787,9 @@ static void reset_interrupt(void)
{
debugt(__func__, "");
result(current_fdc); /* get the status ready for set_fdc */
/* Stale work: unlock_fdc()/do_wakeup() may have cleared cont. */
if (!cont)
return;
if (fdc_state[current_fdc].reset) {
pr_info("reset set in interrupt, calling %ps\n", cont->error);
cont->error(); /* a reset just after a reset. BAD! */
@@ -1862,7 +1868,7 @@ static void show_floppy(int fdc)
pr_info("cont=%p\n", cont);
pr_info("current_req=%p\n", current_req);
pr_info("command_status=%d\n", command_status);
pr_info("command_status=%d\n", READ_ONCE(command_status));
pr_info("\n");
}
@@ -1989,7 +1995,7 @@ static void do_wakeup(void)
{
reschedule_timeout(MAXTIMEOUT, "do wakeup");
cont = NULL;
command_status += 2;
WRITE_ONCE(command_status, READ_ONCE(command_status) + 2);
wake_up(&command_done);
}
@@ -2017,11 +2023,12 @@ static int wait_til_done(void (*handler)(void), bool interruptible)
schedule_bh(handler);
if (interruptible)
wait_event_interruptible(command_done, command_status >= 2);
wait_event_interruptible(command_done,
READ_ONCE(command_status) >= 2);
else
wait_event(command_done, command_status >= 2);
wait_event(command_done, READ_ONCE(command_status) >= 2);
if (command_status < 2) {
if (READ_ONCE(command_status) < 2) {
cancel_activity();
cont = &intr_cont;
reset_fdc();
@@ -2029,18 +2036,18 @@ static int wait_til_done(void (*handler)(void), bool interruptible)
}
if (fdc_state[current_fdc].reset)
command_status = FD_COMMAND_ERROR;
if (command_status == FD_COMMAND_OKAY)
WRITE_ONCE(command_status, FD_COMMAND_ERROR);
if (READ_ONCE(command_status) == FD_COMMAND_OKAY)
ret = 0;
else
ret = -EIO;
command_status = FD_COMMAND_NONE;
WRITE_ONCE(command_status, FD_COMMAND_NONE);
return ret;
}
static void generic_done(int result)
{
command_status = result;
WRITE_ONCE(command_status, result);
cont = &wakeup_cont;
}
@@ -2276,11 +2283,10 @@ static void request_done(int uptodate)
{
struct request *req = current_req;
int block;
char msg[sizeof("request done ") + sizeof(int) * 3];
probing = 0;
snprintf(msg, sizeof(msg), "request done %d", uptodate);
reschedule_timeout(MAXTIMEOUT, msg);
reschedule_timeout(MAXTIMEOUT,
uptodate ? "request done 1" : "request done 0");
if (!req) {
pr_info("floppy.c: no request in request_done\n");
@@ -2871,7 +2877,7 @@ static blk_status_t floppy_queue_rq(struct blk_mq_hw_ctx *hctx,
list_add_tail(&bd->rq->queuelist, &floppy_reqs);
spin_unlock_irq(&floppy_lock);
command_status = FD_COMMAND_NONE;
WRITE_ONCE(command_status, FD_COMMAND_NONE);
__reschedule_timeout(MAXTIMEOUT, "fd_request");
set_fdc(0);
process_fd_request();
@@ -3601,7 +3607,7 @@ struct compat_floppy_drive_params {
};
struct compat_floppy_drive_struct {
signed char flags;
compat_ulong_t flags;
compat_ulong_t spinup_date;
compat_ulong_t select_date;
compat_ulong_t first_read_date;
@@ -3785,6 +3791,7 @@ static int compat_getdrvstat(int drive, bool poll,
goto Eintr;
process_fd_request();
}
v.flags = drive_state[drive].flags;
v.spinup_date = drive_state[drive].spinup_date;
v.select_date = drive_state[drive].select_date;
v.first_read_date = drive_state[drive].first_read_date;
@@ -4729,8 +4736,11 @@ static int __init do_floppy_init(void)
err = device_add_disk(&floppy_device[drive].dev,
disks[drive][0], NULL);
if (err)
if (err) {
platform_device_unregister(&floppy_device[drive]);
registered[drive] = false;
goto out_remove_drives;
}
}
return 0;
@@ -4744,6 +4754,8 @@ out_remove_drives:
}
}
out_release_dma:
/* Drain the queued redo_fd_request() before releasing IRQ and DMA. */
flush_workqueue(floppy_wq);
if (atomic_read(&usage_count))
floppy_release_irq_and_dma();
out_unreg_driver:
+4 -108
View File
@@ -105,7 +105,7 @@ static DEFINE_MUTEX(loop_validate_mutex);
*
* Since loop_validate_file() traverses on other "struct loop_device" if
* is_loop_device() is true, we need a global lock for serializing concurrent
* loop_configure()/loop_change_fd()/__loop_clr_fd() calls.
* loop_configure()/__loop_clr_fd() calls.
*/
static int loop_global_lock_killable(struct loop_device *lo, bool global)
{
@@ -537,109 +537,6 @@ static int loop_check_backing_file(struct file *file)
return 0;
}
/*
* loop_change_fd switched the backing store of a loopback device to
* a new file. This is useful for operating system installers to free up
* the original file and in High Availability environments to switch to
* an alternative location for the content in case of server meltdown.
* This can only work if the loop device is used read-only, and if the
* new backing store is the same size and type as the old backing store.
*/
static int loop_change_fd(struct loop_device *lo, struct block_device *bdev,
unsigned int arg)
{
struct file *file = fget(arg);
struct file *old_file;
unsigned int memflags;
int error;
bool partscan;
bool is_loop;
if (!file)
return -EBADF;
error = loop_check_backing_file(file);
if (error) {
fput(file);
return error;
}
/* suppress uevents while reconfiguring the device */
dev_set_uevent_suppress(disk_to_dev(lo->lo_disk), 1);
is_loop = is_loop_device(file);
error = loop_global_lock_killable(lo, is_loop);
if (error)
goto out_putf;
error = -ENXIO;
if (lo->lo_state != Lo_bound)
goto out_err;
/* the loop device has to be read-only */
error = -EINVAL;
if (!(lo->lo_flags & LO_FLAGS_READ_ONLY))
goto out_err;
error = loop_validate_file(file, bdev);
if (error)
goto out_err;
old_file = lo->lo_backing_file;
error = -EINVAL;
/* size of the new backing store needs to be the same */
if (lo_calculate_size(lo, file) != lo_calculate_size(lo, old_file))
goto out_err;
/*
* We might switch to direct I/O mode for the loop device, write back
* all dirty data the page cache now that so that the individual I/O
* operations don't have to do that.
*/
vfs_fsync(file, 0);
/* and ... switch */
disk_force_media_change(lo->lo_disk);
memflags = blk_mq_freeze_queue(lo->lo_queue);
mapping_set_gfp_mask(old_file->f_mapping, lo->old_gfp_mask);
loop_assign_backing_file(lo, file);
loop_update_dio(lo);
blk_mq_unfreeze_queue(lo->lo_queue, memflags);
partscan = lo->lo_flags & LO_FLAGS_PARTSCAN;
loop_global_unlock(lo, is_loop);
/*
* Flush loop_validate_file() before fput(), for l->lo_backing_file
* might be pointing at old_file which might be the last reference.
*/
if (!is_loop) {
mutex_lock(&loop_validate_mutex);
mutex_unlock(&loop_validate_mutex);
}
/*
* We must drop file reference outside of lo_mutex as dropping
* the file ref can take open_mutex which creates circular locking
* dependency.
*/
fput(old_file);
dev_set_uevent_suppress(disk_to_dev(lo->lo_disk), 0);
if (partscan)
loop_reread_partitions(lo);
error = 0;
done:
kobject_uevent(&disk_to_dev(lo->lo_disk)->kobj, KOBJ_CHANGE);
return error;
out_err:
loop_global_unlock(lo, is_loop);
out_putf:
fput(file);
dev_set_uevent_suppress(disk_to_dev(lo->lo_disk), 0);
goto done;
}
/* loop sysfs attributes */
static ssize_t loop_attr_show(struct device *dev, char *page,
@@ -1215,12 +1112,11 @@ static int loop_clr_fd(struct loop_device *lo)
/*
* Since lo_ioctl() is called without locks held, it is possible that
* loop_configure()/loop_change_fd() and loop_clr_fd() run in parallel.
* loop_configure() and loop_clr_fd() run in parallel.
*
* Therefore, use global lock when setting Lo_rundown state in order to
* make sure that loop_validate_file() will fail if the "struct file"
* which loop_configure()/loop_change_fd() found via fget() was this
* loop device.
* which loop_configure() found via fget() was this loop device.
*/
err = loop_global_lock_killable(lo, true);
if (err)
@@ -1566,7 +1462,7 @@ static int lo_ioctl(struct block_device *bdev, blk_mode_t mode,
return loop_configure(lo, mode, bdev, &config);
}
case LOOP_CHANGE_FD:
return loop_change_fd(lo, bdev, arg);
return -EOPNOTSUPP;
case LOOP_CLR_FD:
return loop_clr_fd(lo);
case LOOP_SET_STATUS:
+30 -14
View File
@@ -2,15 +2,27 @@
use super::NullBlkDevice;
use kernel::{
block::mq::gen_disk::{GenDisk, GenDiskBuilder},
configfs::{self, AttributeOperations},
block::mq::gen_disk::{
GenDisk,
GenDiskBuilder, //
},
configfs::{
self,
AttributeOperations, //
},
configfs_attrs,
fmt::{self, Write as _},
fmt::{
self,
Write as _, //
},
new_mutex,
page::PAGE_SIZE,
prelude::*,
str::{kstrtobool_bytes, CString},
sync::Mutex,
str::{
kstrtobool_bytes,
CString, //
},
sync::Mutex, //
};
pub(crate) fn subsystem() -> impl PinInit<kernel::configfs::Subsystem<Config>, Error> {
@@ -35,7 +47,7 @@ impl AttributeOperations<0> for Config {
fn show(_this: &Config, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
let mut writer = kernel::str::Formatter::new(page);
writer.write_str("blocksize,size,rotational,irqmode\n")?;
writer.write_str("blocksize,size,rotational,irqmode,power\n")?;
Ok(writer.bytes_written())
}
}
@@ -174,7 +186,8 @@ impl configfs::AttributeOperations<1> for DeviceConfig {
}
fn store(this: &DeviceConfig, page: &[u8]) -> Result {
if this.data.lock().powered {
let mut guard = this.data.lock();
if guard.powered {
return Err(EBUSY);
}
@@ -182,7 +195,7 @@ impl configfs::AttributeOperations<1> for DeviceConfig {
let value = text.parse::<u32>().map_err(|_| EINVAL)?;
GenDiskBuilder::validate_block_size(value)?;
this.data.lock().block_size = value;
guard.block_size = value;
Ok(())
}
}
@@ -204,11 +217,12 @@ impl configfs::AttributeOperations<2> for DeviceConfig {
}
fn store(this: &DeviceConfig, page: &[u8]) -> Result {
if this.data.lock().powered {
let mut guard = this.data.lock();
if guard.powered {
return Err(EBUSY);
}
this.data.lock().rotational = kstrtobool_bytes(page)?;
guard.rotational = kstrtobool_bytes(page)?;
Ok(())
}
@@ -225,14 +239,15 @@ impl configfs::AttributeOperations<3> for DeviceConfig {
}
fn store(this: &DeviceConfig, page: &[u8]) -> Result {
if this.data.lock().powered {
let mut guard = this.data.lock();
if guard.powered {
return Err(EBUSY);
}
let text = core::str::from_utf8(page)?.trim();
let value = text.parse::<u64>().map_err(|_| EINVAL)?;
this.data.lock().capacity_mib = value;
guard.capacity_mib = value;
Ok(())
}
}
@@ -248,14 +263,15 @@ impl configfs::AttributeOperations<4> for DeviceConfig {
}
fn store(this: &DeviceConfig, page: &[u8]) -> Result {
if this.data.lock().powered {
let mut guard = this.data.lock();
if guard.powered {
return Err(EBUSY);
}
let text = core::str::from_utf8(page)?.trim();
let value = text.parse::<u8>().map_err(|_| EINVAL)?;
this.data.lock().irq_mode = IRQMode::try_from(value)?;
guard.irq_mode = IRQMode::try_from(value)?;
Ok(())
}
}
+11 -4
View File
@@ -10,12 +10,19 @@ use kernel::{
self,
mq::{
self,
gen_disk::{self, GenDisk},
Operations, TagSet,
},
gen_disk::{
self,
GenDisk, //
},
Operations,
TagSet, //
}, //
},
prelude::*,
sync::{aref::ARef, Arc},
sync::{
aref::ARef,
Arc, //
}, //
};
module! {
+240 -217
View File
@@ -2,12 +2,20 @@
/*
* Driver for SWIM (Sander Woz Integrated Machine) floppy controller
*
* Copyright (C) 2004,2008 Laurent Vivier <Laurent@lvivier.info>
* Copyright (C) 2004, 2008 Laurent Vivier <Laurent@lvivier.info>
*
* based on Alastair Bridgewater SWIM analysis, 2001
* based on SWIM3 driver (c) Paul Mackerras, 1996
* based on netBSD IWM driver (c) 1997, 1998 Hauke Fath.
*
* See also:
* Inside Macintosh, vol. III, ch. 2
* https://archive.org/details/SWIMDesignDocs
* NEC uPD72070 FDC datasheet
* mkLinux source file swimiiicommonhal.c
* http://www.mac.linux-m68k.org/devel/iwm.php.html
* MAME source file sonydriv.cpp
*
* 2004-08-21 (lv) - Initial implementation
* 2008-10-30 (lv) - Port to 2.6
*/
@@ -19,6 +27,7 @@
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/hdreg.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
@@ -60,7 +69,7 @@ struct swim {
REG(read_handshake)
} __attribute__((packed));
#define swim_write(base, reg, v) out_8(&(base)->write_##reg, (v))
#define swim_write(base, reg, v) out_8(&(base)->write_##reg, (v))
#define swim_read(base, reg) in_8(&(base)->read_##reg)
/* IWM registers */
@@ -84,62 +93,54 @@ struct iwm {
REG(q7H)
} __attribute__((packed));
#define iwm_write(base, reg, v) out_8(&(base)->reg, (v))
#define iwm_write(base, reg, v) out_8(&(base)->reg, (v))
#define iwm_read(base, reg) in_8(&(base)->reg)
/* bits in phase register */
/* Bits in phase register */
#define SEEK_POSITIVE 0x070
#define SEEK_NEGATIVE 0x074
#define STEP 0x071
#define MOTOR_ON 0x072
#define MOTOR_OFF 0x076
#define INDEX 0x073
#define EJECT 0x077
#define SETMFM 0x171
#define SETGCR 0x175
#define RELAX 0x033
#define LSTRB 0x008
#define CA_MASK 0x077
#define RELAX 0x03
#define LSTRB 0x08
#define CA_MASK 0x07
#define PHASE_PIN_DIR 0xF0
/* Select values for swim_select and swim_readbit */
#define READ_DATA_0 0x074
#define ONEMEG_DRIVE 0x075
#define SINGLE_SIDED 0x076
#define DRIVE_PRESENT 0x077
#define DISK_IN 0x170
#define WRITE_PROT 0x171
#define TRACK_ZERO 0x172
#define TACHO 0x173
#define READ_DATA_1 0x174
#define GCR_MODE 0x175
#define SEEK_COMPLETE 0x176
#define TWOMEG_MEDIA 0x177
#define SEL_MASK 0x100
#define SEEK_POSITIVE 0x000
#define SEEK_NEGATIVE 0x004
#define STEP 0x001
#define MOTOR_ON 0x002
#define MOTOR_OFF 0x006
#define INDEX 0x003
#define EJECT 0x007
#define SETMFM 0x101
#define SETGCR 0x105
#define STEPPING 0x001
#define READ_DATA_0 0x004
#define ONEMEG_DRIVE 0x005
#define SINGLE_SIDED 0x006
#define DRIVE_PRESENT 0x007
#define DISK_IN 0x100
#define WRITE_PROT 0x101
#define TRACK_ZERO 0x102
#define TACHO 0x103
#define READ_DATA_1 0x104
#define GCR_MODE 0x105
#define READY 0x106
#define TWOMEG_MEDIA 0x107
/* Bits in handshake register */
#define MARK_BYTE 0x01
#define CRC_ZERO 0x02
#define RDDATA 0x04
#define SENSE 0x08
#define MOTEN 0x10
#define ERROR 0x20
#define DAT2BYTE 0x40
#define DAT1BYTE 0x80
/* bits in setup register */
#define S_INV_WDATA 0x01
#define S_3_5_SELECT 0x02
#define S_GCR 0x04
#define S_FCLK_DIV2 0x08
#define S_ERROR_CORR 0x10
#define S_IBM_DRIVE 0x20
#define S_GCR_WRITE 0x40
#define S_TIMEOUT 0x80
/* bits in mode register */
@@ -148,14 +149,13 @@ struct iwm {
#define ENBL2 0x04
#define ACTION 0x08
#define WRITE_MODE 0x10
#define HEDSEL 0x20
#define ISM_SELECT 0x40
#define MOTON 0x80
/*----------------------------------------------------------------------------*/
enum drive_location {
INTERNAL_DRIVE = 0x02,
EXTERNAL_DRIVE = 0x04,
NO_DRIVE = 0,
INTERNAL_DRIVE = BIT(1),
EXTERNAL_DRIVE = BIT(2),
};
enum media_type {
@@ -168,23 +168,23 @@ struct floppy_state {
/* physical properties */
enum drive_location location; /* internal or external drive */
int head_number; /* single- or double-sided drive */
int head_number; /* single- or double-sided drive */
/* media */
int disk_in;
int ejected;
enum media_type type;
int write_protected;
int disk_in;
int ejected;
enum media_type type;
int write_protected;
int total_secs;
int secpercyl;
int secpertrack;
unsigned int total_secs;
unsigned int secpercyl;
unsigned int secpertrack;
/* in-use information */
int track;
int ref_count;
int track;
int ref_count;
bool registered;
struct gendisk *disk;
@@ -209,7 +209,6 @@ enum head {
struct swim_priv {
struct swim __iomem *base;
spinlock_t lock;
int floppy_count;
struct floppy_state unit[FD_MAX_UNIT];
};
@@ -220,20 +219,24 @@ extern int swim_read_sector_data(struct swim __iomem *base,
unsigned char *data);
static DEFINE_MUTEX(swim_mutex);
static inline void set_swim_mode(struct swim __iomem *base, int enable)
static void set_swim_mode(struct swim __iomem *base, int enable)
{
struct iwm __iomem *iwm_base;
struct iwm __iomem *iwm_base = (struct iwm __iomem *)base;
unsigned long flags;
if (!enable) {
swim_write(base, mode0, 0xf8);
swim_write(base, mode0, ACTION);
swim_write(base, mode0, ENBL1 | ENBL2 | MOTON);
swim_write(base, mode0, ISM_SELECT);
iwm_read(iwm_base, q7L);
return;
}
iwm_base = (struct iwm __iomem *)base;
local_irq_save(flags);
iwm_read(iwm_base, q7L);
iwm_read(iwm_base, q6L);
iwm_read(iwm_base, mtrOff);
iwm_read(iwm_base, q6H);
@@ -245,7 +248,7 @@ static inline void set_swim_mode(struct swim __iomem *base, int enable)
local_irq_restore(flags);
}
static inline int get_swim_mode(struct swim __iomem *base)
static int get_swim_mode(struct swim __iomem *base)
{
unsigned long flags;
@@ -269,11 +272,11 @@ is_iwm:
static inline void swim_select(struct swim __iomem *base, int sel)
{
swim_write(base, phase, RELAX);
swim_write(base, phase, RELAX | PHASE_PIN_DIR);
via1_set_head(sel & 0x100);
via1_set_head(sel & SEL_MASK);
swim_write(base, phase, sel & CA_MASK);
swim_write(base, phase, (sel & CA_MASK) | PHASE_PIN_DIR);
}
static inline void swim_action(struct swim __iomem *base, int action)
@@ -284,15 +287,15 @@ static inline void swim_action(struct swim __iomem *base, int action)
swim_select(base, action);
udelay(1);
swim_write(base, phase, (LSTRB<<4) | LSTRB);
udelay(1);
swim_write(base, phase, (LSTRB<<4) | ((~LSTRB) & 0x0F));
swim_write(base, phase, LSTRB | action | PHASE_PIN_DIR);
udelay(2);
swim_write(base, phase, action | PHASE_PIN_DIR);
udelay(1);
local_irq_restore(flags);
}
static inline int swim_readbit(struct swim __iomem *base, int bit)
static inline bool swim_readbit(struct swim __iomem *base, int bit)
{
int stat;
@@ -305,125 +308,116 @@ static inline int swim_readbit(struct swim __iomem *base, int bit)
return (stat & SENSE) == 0;
}
static inline void swim_drive(struct swim __iomem *base,
#define swim_readbit_timeout(base, bit, val, timeout_us) \
poll_timeout_us(, swim_readbit(base, bit) == val, 1000, timeout_us, false)
#define swim_READY_timeout(base) \
({ \
int ret = swim_readbit_timeout(base, READY, true, 1000 * 1000); \
if (ret) \
printk(KERN_DEBUG "%s: drive not ready\n", __func__); \
ret; \
})
static void swim_drive(struct swim __iomem *base,
enum drive_location location)
{
if (location == INTERNAL_DRIVE) {
swim_write(base, mode0, EXTERNAL_DRIVE); /* clear drive 1 bit */
swim_write(base, mode1, INTERNAL_DRIVE); /* set drive 0 bit */
swim_write(base, mode1, MOTON);
} else if (location == EXTERNAL_DRIVE) {
swim_write(base, mode0, INTERNAL_DRIVE); /* clear drive 0 bit */
swim_write(base, mode1, EXTERNAL_DRIVE); /* set drive 1 bit */
swim_write(base, mode1, MOTON);
} else {
swim_write(base, mode0, INTERNAL_DRIVE);
swim_write(base, mode0, EXTERNAL_DRIVE);
swim_write(base, mode0, MOTON);
}
udelay(1);
}
static inline void swim_motor(struct swim __iomem *base,
enum motor_action action)
static void swim_motor(struct swim __iomem *base,
enum motor_action action)
{
if (action == ON) {
int i;
swim_action(base, MOTOR_ON);
for (i = 0; i < 2*HZ; i++) {
swim_select(base, RELAX);
if (swim_readbit(base, MOTOR_ON))
break;
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(1);
}
swim_readbit_timeout(base, MOTOR_ON, true, 2000 * 1000);
} else if (action == OFF) {
swim_action(base, MOTOR_OFF);
swim_select(base, RELAX);
swim_write(base, phase, RELAX | PHASE_PIN_DIR);
}
}
static inline void swim_eject(struct swim __iomem *base)
static void swim_eject(struct swim __iomem *base)
{
int i;
swim_action(base, EJECT);
for (i = 0; i < 2*HZ; i++) {
swim_select(base, RELAX);
if (!swim_readbit(base, DISK_IN))
break;
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(1);
}
swim_select(base, RELAX);
swim_readbit_timeout(base, DISK_IN, false, 2000 * 1000);
msleep(1);
}
static inline void swim_head(struct swim __iomem *base, enum head head)
{
/* wait drive is ready */
if (head == UPPER_HEAD)
swim_select(base, READ_DATA_1);
else if (head == LOWER_HEAD)
swim_select(base, READ_DATA_0);
}
static inline int swim_step(struct swim __iomem *base)
static int swim_step(struct swim __iomem *base)
{
int wait;
swim_action(base, STEP);
for (wait = 0; wait < HZ; wait++) {
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(1);
swim_select(base, RELAX);
if (!swim_readbit(base, STEP))
return 0;
}
return -1;
udelay(150);
return swim_readbit_timeout(base, STEPPING, false, 20 * 1000);
}
static inline int swim_track00(struct swim __iomem *base)
static int swim_track00(struct swim __iomem *base)
{
int try;
swim_action(base, SEEK_NEGATIVE);
for (try = 0; try < 100; try++) {
msleep(3);
swim_select(base, RELAX);
if (swim_readbit(base, TRACK_ZERO))
break;
return 0;
if (swim_step(base))
return -1;
break;
}
if (swim_readbit(base, TRACK_ZERO))
return 0;
pr_err("swim: track zero recalibration failed\n");
return -1;
}
static inline int swim_seek(struct swim __iomem *base, int step)
static int swim_seek(struct swim __iomem *base, int step)
{
if (step == 0)
return 0;
if (step < 0) {
swim_action(base, SEEK_NEGATIVE);
step = -step;
} else
} else if (step > 0)
swim_action(base, SEEK_POSITIVE);
udelay(1);
for ( ; step > 0; step--) {
swim_READY_timeout(base);
if (step == 0)
return 0;
for (; step > 0; step--) {
if (swim_step(base))
return -1;
}
msleep(30);
swim_READY_timeout(base);
return 0;
}
static inline int swim_track(struct floppy_state *fs, int track)
static int swim_track(struct floppy_state *fs, int track)
{
struct swim __iomem *base = fs->swd->base;
int ret;
@@ -445,8 +439,10 @@ static int floppy_eject(struct floppy_state *fs)
struct swim __iomem *base = fs->swd->base;
swim_drive(base, fs->location);
swim_track(fs, 40);
swim_motor(base, OFF);
swim_eject(base);
swim_drive(base, NO_DRIVE);
fs->disk_in = 0;
fs->ejected = 1;
@@ -454,70 +450,79 @@ static int floppy_eject(struct floppy_state *fs)
return 0;
}
static inline int swim_read_sector(struct floppy_state *fs,
int side, int track,
int sector, unsigned char *buffer)
static unsigned int swim_read_sector_range(struct floppy_state *fs,
unsigned int side, unsigned int track,
unsigned int start, unsigned int count,
unsigned char *buffer)
{
struct swim __iomem *base = fs->swd->base;
unsigned long flags;
struct sector_header header;
int ret = -1;
short i;
unsigned int i, bits = 0;
swim_track(fs, track);
swim_write(base, mode1, MOTON);
swim_head(base, side);
swim_write(base, mode0, side);
if (count > 0) {
count = min(count, fs->secpertrack);
bits = GENMASK(count - 1, 0);
}
local_irq_save(flags);
for (i = 0; i < 36; i++) {
ret = swim_read_sector_header(base, &header);
if (!ret && (header.sector == sector)) {
/* found */
ret = swim_read_sector_data(base, buffer);
for (i = 0; i < fs->secpertrack + 1; i++) {
if (bits == 0) /* All sectors were read ok */
break;
if (swim_read_sector_header(base, &header) == 0 &&
swim_read(base, error) == 0) {
unsigned int offset = header.sector - start;
unsigned char *buf = NULL;
int len;
if (header.track == track && header.side == side &&
header.size == 2 && header.sector >= start &&
header.sector < start + count &&
(bits & BIT(offset)))
buf = buffer + 512 * offset;
len = swim_read_sector_data(base, buf);
if (swim_read(base, error) == 0 && buf && len == 512)
bits &= ~BIT(offset);
}
}
local_irq_restore(flags);
swim_write(base, mode0, MOTON);
if ((header.side != side) || (header.track != track) ||
(header.sector != sector))
return 0;
return ret;
return bits ? ffs(bits) - 1 : count; /* No. of contiguous ok sectors */
}
static blk_status_t floppy_read_sectors(struct floppy_state *fs,
int req_sector, int sectors_nb,
unsigned char *buffer)
unsigned int req_sector,
unsigned int sectors_nb,
unsigned char *buffer)
{
struct swim __iomem *base = fs->swd->base;
int ret;
int side, track, sector;
int i, try;
unsigned int try = 0;
swim_drive(base, fs->location);
for (i = req_sector; i < req_sector + sectors_nb; i++) {
int x;
track = i / fs->secpercyl;
x = i % fs->secpercyl;
side = x / fs->secpertrack;
sector = x % fs->secpertrack + 1;
swim_READY_timeout(base);
try = 5;
do {
ret = swim_read_sector(fs, side, track, sector,
buffer);
if (try-- == 0)
return BLK_STS_IOERR;
} while (ret != 512);
while (sectors_nb) {
unsigned int cyl, x, head, sector, n, n_ok;
buffer += ret;
cyl = req_sector / fs->secpercyl;
x = req_sector % fs->secpercyl;
head = (x >= fs->secpertrack) ? 1 : 0;
sector = x % fs->secpertrack;
n = min(sectors_nb, fs->secpertrack - sector);
swim_track(fs, cyl);
swim_head(base, head);
n_ok = swim_read_sector_range(fs, head, cyl, sector + 1, n, buffer);
if (n_ok == n)
try = 0;
else if (++try >= 5)
return BLK_STS_IOERR;
buffer += 512 * n_ok;
sectors_nb -= n_ok;
req_sector += n_ok;
}
return 0;
@@ -527,12 +532,10 @@ static blk_status_t swim_queue_rq(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
struct floppy_state *fs = hctx->queue->queuedata;
struct swim_priv *swd = fs->swd;
struct request *req = bd->rq;
blk_status_t err;
if (!spin_trylock_irq(&swd->lock))
return BLK_STS_DEV_RESOURCE;
mutex_lock(&swim_mutex);
blk_mq_start_request(req);
@@ -550,16 +553,16 @@ static blk_status_t swim_queue_rq(struct blk_mq_hw_ctx *hctx,
err = BLK_STS_OK;
out:
spin_unlock_irq(&swd->lock);
mutex_unlock(&swim_mutex);
return err;
}
static struct floppy_struct floppy_type[4] = {
{ 0, 0, 0, 0, 0, 0x00, 0x00, 0x00, 0x00, NULL }, /* no testing */
{ 0, 0, 0, 0, 0, 0x00, 0x00, 0x00, 0x00, NULL }, /* no testing */
{ 720, 9, 1, 80, 0, 0x2A, 0x02, 0xDF, 0x50, NULL }, /* 360KB SS 3.5"*/
{ 1440, 9, 2, 80, 0, 0x2A, 0x02, 0xDF, 0x50, NULL }, /* 720KB 3.5" */
{ 2880, 18, 2, 80, 0, 0x1B, 0x00, 0xCF, 0x6C, NULL }, /* 1.44MB 3.5" */
{ 1440, 9, 2, 80, 0, 0x2A, 0x02, 0xDF, 0x50, NULL }, /* 720KB 3.5" */
{ 2880, 18, 2, 80, 0, 0x1B, 0x00, 0xCF, 0x6C, NULL }, /* 1.44MB 3.5" */
};
static int get_floppy_geometry(struct floppy_state *fs, int type,
@@ -586,23 +589,16 @@ static void setup_medium(struct floppy_state *fs)
if (swim_readbit(base, DISK_IN)) {
struct floppy_struct *g;
fs->disk_in = 1;
fs->write_protected = swim_readbit(base, WRITE_PROT);
if (swim_track00(base))
printk(KERN_ERR
"SWIM: cannot move floppy head to track 0\n");
swim_track00(base);
fs->type = swim_readbit(base, TWOMEG_MEDIA) ?
HD_MEDIA : DD_MEDIA;
HD_MEDIA : DD_MEDIA;
fs->head_number = swim_readbit(base, SINGLE_SIDED) ? 1 : 2;
get_floppy_geometry(fs, 0, &g);
fs->total_secs = g->size;
fs->secpercyl = g->head * g->sect;
fs->secpertrack = g->sect;
fs->track = 0;
} else {
fs->disk_in = 0;
}
@@ -620,11 +616,9 @@ static int floppy_open(struct gendisk *disk, blk_mode_t mode)
fs->ref_count = -1;
else
fs->ref_count++;
swim_write(base, setup, S_IBM_DRIVE | S_FCLK_DIV2);
udelay(10);
swim_drive(base, fs->location);
swim_motor(base, ON);
swim_action(base, SETMFM);
if (fs->ejected)
setup_medium(fs);
if (!fs->disk_in) {
@@ -632,6 +626,11 @@ static int floppy_open(struct gendisk *disk, blk_mode_t mode)
goto out;
}
swim_motor(base, ON);
swim_action(base, SETMFM);
msleep(30);
swim_READY_timeout(base);
set_capacity(fs->disk, fs->total_secs);
if (mode & BLK_OPEN_NDELAY)
@@ -640,7 +639,8 @@ static int floppy_open(struct gendisk *disk, blk_mode_t mode)
if (mode & (BLK_OPEN_READ | BLK_OPEN_WRITE)) {
if (disk_check_media_change(disk) && fs->disk_in)
fs->ejected = 0;
if ((mode & BLK_OPEN_WRITE) && fs->write_protected) {
if (mode & BLK_OPEN_WRITE) {
/* Write support isn't implemented. */
err = -EROFS;
goto out;
}
@@ -652,8 +652,10 @@ out:
else if (fs->ref_count > 0)
--fs->ref_count;
if (fs->ref_count == 0)
if (fs->ref_count == 0) {
swim_motor(base, OFF);
swim_drive(base, NO_DRIVE);
}
return err;
}
@@ -679,8 +681,11 @@ static void floppy_release(struct gendisk *disk)
else if (fs->ref_count > 0)
--fs->ref_count;
if (fs->ref_count == 0)
if (fs->ref_count == 0) {
swim_drive(base, fs->location);
swim_motor(base, OFF);
swim_drive(base, NO_DRIVE);
}
mutex_unlock(&swim_mutex);
}
@@ -691,14 +696,15 @@ static int floppy_ioctl(struct block_device *bdev, blk_mode_t mode,
int err;
if ((cmd & 0x80) && !capable(CAP_SYS_ADMIN))
return -EPERM;
return -EPERM;
switch (cmd) {
case FDEJECT:
if (fs->ref_count != 1)
return -EBUSY;
mutex_lock(&swim_mutex);
err = floppy_eject(fs);
if (fs->ref_count == -1 || fs->ref_count == 1)
err = floppy_eject(fs);
else
err = -EBUSY;
mutex_unlock(&swim_mutex);
return err;
@@ -745,26 +751,34 @@ static const struct block_device_operations floppy_fops = {
.check_events = floppy_check_events,
};
static int swim_add_floppy(struct swim_priv *swd, enum drive_location location)
static void swim_add_floppy(struct swim_priv *swd, enum drive_location location)
{
struct floppy_state *fs = &swd->unit[swd->floppy_count];
struct swim __iomem *base = swd->base;
fs->location = location;
swim_drive(base, location);
if (!swim_readbit(base, DRIVE_PRESENT) ||
swim_readbit(base, ONEMEG_DRIVE))
goto out;
if (swim_readbit(base, DISK_IN))
swim_motor(base, ON);
if (swim_track00(base))
goto out;
swim_motor(base, OFF);
fs->swd = swd;
fs->location = location;
fs->type = HD_MEDIA;
fs->head_number = 2;
fs->ref_count = 0;
fs->ejected = 1;
fs->track = 0;
swd->floppy_count++;
return 0;
out:
swim_motor(base, OFF);
}
static const struct blk_mq_ops swim_mq_ops = {
@@ -785,25 +799,38 @@ static void swim_cleanup_floppy_disk(struct floppy_state *fs)
blk_mq_free_tag_set(&fs->tag_set);
}
static int swim_floppy_init(struct swim_priv *swd)
static void swim_set_parameters(struct swim __iomem *base)
{
unsigned int i;
static const u8 mem[] = { 0x18, 0x41, 0x2e, 0x2e, 0x18, 0x18, 0x1b, 0x1b,
0x2f, 0x2f, 0x19, 0x19, 0x97, 0x1b, 0x57, 0x3b, };
swim_write(base, mode0, 0); /* reset parameter memory index */
for (i = 0; i < 16; ++i)
swim_write(base, parameter, mem[i]);
}
static int swim_floppy_init(struct platform_device *pdev)
{
struct swim_priv *swd = platform_get_drvdata(pdev);
bool *data = pdev->dev.platform_data;
bool fast_fclk = data && *data;
struct queue_limits lim = {
.features = BLK_FEAT_ROTATIONAL,
.features = BLK_FEAT_ROTATIONAL,
};
int err;
int drive;
struct swim __iomem *base = swd->base;
swim_write(base, setup, S_IBM_DRIVE | (fast_fclk ? S_FCLK_DIV2 : 0));
swim_set_parameters(base);
/* scan floppy drives */
swim_drive(base, INTERNAL_DRIVE);
if (swim_readbit(base, DRIVE_PRESENT) &&
!swim_readbit(base, ONEMEG_DRIVE))
swim_add_floppy(swd, INTERNAL_DRIVE);
swim_drive(base, EXTERNAL_DRIVE);
if (swim_readbit(base, DRIVE_PRESENT) &&
!swim_readbit(base, ONEMEG_DRIVE))
swim_add_floppy(swd, EXTERNAL_DRIVE);
swim_add_floppy(swd, INTERNAL_DRIVE);
swim_add_floppy(swd, EXTERNAL_DRIVE);
swim_drive(base, NO_DRIVE);
/* register floppy drives */
@@ -814,11 +841,9 @@ static int swim_floppy_init(struct swim_priv *swd)
return -EBUSY;
}
spin_lock_init(&swd->lock);
for (drive = 0; drive < swd->floppy_count; drive++) {
err = blk_mq_alloc_sq_tag_set(&swd->unit[drive].tag_set,
&swim_mq_ops, 2, 0);
&swim_mq_ops, 2, BLK_MQ_F_BLOCKING);
if (err)
goto exit_put_disks;
@@ -830,8 +855,6 @@ static int swim_floppy_init(struct swim_priv *swd)
err = PTR_ERR(swd->unit[drive].disk);
goto exit_put_disks;
}
swd->unit[drive].swd = swd;
}
for (drive = 0; drive < swd->floppy_count; drive++) {
@@ -905,7 +928,7 @@ static int swim_probe(struct platform_device *dev)
swd->base = swim_base;
ret = swim_floppy_init(swd);
ret = swim_floppy_init(dev);
if (ret)
goto out_kfree;
@@ -945,7 +968,7 @@ static void swim_remove(struct platform_device *dev)
static struct platform_driver swim_driver = {
.probe = swim_probe,
.remove = swim_remove,
.driver = {
.driver = {
.name = CARDNAME,
},
};
+3 -5
View File
@@ -887,7 +887,6 @@ static int floppy_locked_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int cmd, unsigned long param)
{
struct floppy_state *fs = bdev->bd_disk->private_data;
int err;
if ((cmd & 0x80) && !capable(CAP_SYS_ADMIN))
return -EPERM;
@@ -898,10 +897,9 @@ static int floppy_locked_ioctl(struct block_device *bdev, blk_mode_t mode,
switch (cmd) {
case FDEJECT:
if (fs->ref_count != 1)
return -EBUSY;
err = fd_eject(fs);
return err;
if (fs->ref_count == -1 || fs->ref_count == 1)
return fd_eject(fs);
return -EBUSY;
case FDGETPRM:
if (copy_to_user((void __user *) param, &floppy_type,
sizeof(struct floppy_struct)))
+178 -162
View File
@@ -5,7 +5,7 @@
* needs assembly language because is very timing dependent
* this controller exists only on macintosh 680x0 based
*
* Copyright (C) 2004,2008 Laurent Vivier <Laurent@lvivier.info>
* Copyright (C) 2004, 2008 Laurent Vivier <Laurent@lvivier.info>
*
* based on Alastair Bridgewater SWIM analysis, 2001
* based on netBSD IWM driver (c) 1997, 1998 Hauke Fath.
@@ -14,135 +14,160 @@
* 2008-11-05 (lv) - add get_swim_mode
*/
.equ write_data, 0x0000
.equ write_mark, 0x0200
.equ write_CRC, 0x0400
.equ write_parameter,0x0600
.equ write_phase, 0x0800
.equ write_setup, 0x0a00
.equ write_mode0, 0x0c00
.equ write_mode1, 0x0e00
.equ read_data, 0x1000
.equ read_mark, 0x1200
.equ read_error, 0x1400
.equ read_parameter, 0x1600
.equ read_phase, 0x1800
.equ read_setup, 0x1a00
.equ read_status, 0x1c00
.equ read_handshake, 0x1e00
.equ .Lwrite_data, 0x0000
.equ .Lwrite_mark, 0x0200
.equ .Lwrite_CRC, 0x0400
.equ .Lwrite_parameter, 0x0600
.equ .Lwrite_phase, 0x0800
.equ .Lwrite_setup, 0x0a00
.equ .Lwrite_mode0, 0x0c00
.equ .Lwrite_mode1, 0x0e00
.equ .Lread_data, 0x1000
.equ .Lread_mark, 0x1200
.equ .Lread_error, 0x1400
.equ .Lread_parameter, 0x1600
.equ .Lread_phase, 0x1800
.equ .Lread_setup, 0x1a00
.equ .Lread_status, 0x1c00
.equ .Lread_handshake, 0x1e00
.equ o_side, 0
.equ o_track, 1
.equ o_sector, 2
.equ o_size, 3
.equ o_crc0, 4
.equ o_crc1, 5
.equ .Lo_side, 0
.equ .Lo_track, 1
.equ .Lo_sector, 2
.equ .Lo_size, 3
.equ .Lo_crc0, 4
.equ .Lo_crc1, 5
.equ seek_time, 30000
.equ max_retry, 40
.equ sector_size, 512
.equ .Lseek_time, 30000
.equ .Lmax_retry, 40
.equ .Lsector_size, 512
.equ .Lmark_sequence_len, 4
.equ .Lmr_clear_fifo, 0x01
.equ .Lmr_action, 0x08
.equ .Lmr_write_action, 0x18
.equ .Lhr_crc_error, 0x02
.equ .Lhr_fifo_2bytes, 0x40
.equ .Lhr_fifo_1byte, 0x80
.Lmfm_mark_check:
/*
* This subroutine reads and validates a mark byte sequence.
* On entry, %a1 and %d4 shall hold the location and length (resp.)
* of the mark byte array.
* %a2 and %a3 shall hold the locations of the handshake and mark
* registers.
* Returns zero in %d1 for success.
*/
moveq #-1, %d1
subq #1, %d4
movew #.Lseek_time, %d2
5: tstb %a2@
dbmi %d2, 5b
bpl 6f
moveb %a3@, %d3
cmpb %a1@+, %d3
dbne %d4, 5b
bne 6f
moveq #0, %d1
6: rts
.global swim_read_sector_header
swim_read_sector_header:
link %a6, #0
moveml %d1-%d5/%a0-%a4,%sp@-
moveml %d1-%d5/%a0-%a5,%sp@-
movel %a6@(0x0c), %a4
bsr mfm_read_addrmark
moveml %sp@+, %d1-%d5/%a0-%a4
moveq #-1, %d0
bsr .Lmfm_read_header
moveml %sp@+, %d1-%d5/%a0-%a5
unlk %a6
rts
sector_address_mark:
.Lsector_address_mark:
.byte 0xa1, 0xa1, 0xa1, 0xfe
sector_data_mark:
.Lsector_data_mark:
.byte 0xa1, 0xa1, 0xa1, 0xfb
mfm_read_addrmark:
.Lmfm_read_header:
movel %a6@(0x08), %a3
lea %a3@(read_handshake), %a2
lea %a3@(read_mark), %a3
moveq #-1, %d0
movew #seek_time, %d2
lea %a3@(.Lread_handshake), %a2
lea %a3@(.Lread_data), %a5
lea %a3@(.Lread_mark), %a3
wait_header_init:
tstb %a3@(read_error - read_mark)
moveb #0x18, %a3@(write_mode0 - read_mark)
moveb #0x01, %a3@(write_mode1 - read_mark)
moveb #0x01, %a3@(write_mode0 - read_mark)
tstb %a3@(read_error - read_mark)
moveb #0x08, %a3@(write_mode1 - read_mark)
moveb #.Lmr_write_action, %a3@(.Lwrite_mode0 - .Lread_mark)
moveb #.Lmr_clear_fifo, %a3@(.Lwrite_mode1 - .Lread_mark)
moveb #.Lmr_clear_fifo, %a3@(.Lwrite_mode0 - .Lread_mark)
tstb %a3@(.Lread_error - .Lread_mark)
moveb #.Lmr_action, %a3@(.Lwrite_mode1 - .Lread_mark)
lea sector_address_mark, %a0
moveq #3, %d1
lea .Lsector_address_mark, %a1
moveq #.Lmark_sequence_len, %d4
bsr .Lmfm_mark_check
tstl %d1
bne .Lsignal_nonyb
wait_addr_mark_byte:
/* read header */
moveq #.Lmax_retry, %d2
.Lamark0:
tstb %a2@
dbmi %d2, wait_addr_mark_byte
bpl header_exit
dbmi %d2, .Lamark0
bpl .Lsignal_nonyb
moveb %a3@, %d3
cmpb %a0@+, %d3
dbne %d1, wait_addr_mark_byte
bne wait_header_init
moveb %a5@, %a4@(.Lo_track)
moveq #max_retry, %d2
moveq #.Lmax_retry, %d2
.Lamark1:
tstb %a2@
dbmi %d2, .Lamark1
bpl .Lsignal_nonyb
amark0: tstb %a2@
dbmi %d2, amark0
bpl signal_nonyb
moveb %a5@, %a4@(.Lo_side)
moveb %a3@, %a4@(o_track)
moveq #.Lmax_retry, %d2
.Lamark2:
tstb %a2@
dbmi %d2, .Lamark2
bpl .Lsignal_nonyb
moveq #max_retry, %d2
moveb %a5@, %a4@(.Lo_sector)
amark1: tstb %a2@
dbmi %d2, amark1
bpl signal_nonyb
moveq #.Lmax_retry, %d2
.Lamark3:
tstb %a2@
dbmi %d2, .Lamark3
bpl .Lsignal_nonyb
moveb %a3@, %a4@(o_side)
moveb %a5@, %a4@(.Lo_size)
moveq #max_retry, %d2
moveq #.Lmax_retry, %d2
.Lcrc0:
tstb %a2@
dbmi %d2, .Lcrc0
bpl .Lsignal_nonyb
amark2: tstb %a2@
dbmi %d2, amark2
bpl signal_nonyb
moveb %a5@, %a4@(.Lo_crc0)
moveb %a3@, %a4@(o_sector)
moveq #.Lmax_retry, %d2
.Lcrc1:
tstb %a2@
dbmi %d2, .Lcrc1
bpl .Lsignal_nonyb
moveq #max_retry, %d2
moveb %a5@, %a4@(.Lo_crc1)
amark3: tstb %a2@
dbmi %d2, amark3
bpl signal_nonyb
moveb %a2@, %d5
andb #.Lhr_crc_error, %d5
bne .Lsignal_nonyb
moveb %a3@, %a4@(o_size)
moveq #max_retry, %d2
crc0: tstb %a2@
dbmi %d2, crc0
bpl signal_nonyb
moveb %a3@, %a4@(o_crc0)
moveq #max_retry, %d2
crc1: tstb %a2@
dbmi %d2, crc1
bpl signal_nonyb
moveb %a3@, %a4@(o_crc1)
tstb %a3@(read_error - read_mark)
header_exit:
moveq #0, %d0
moveb #0x18, %a3@(write_mode0 - read_mark)
rts
signal_nonyb:
moveq #-1, %d0
moveb #0x18, %a3@(write_mode0 - read_mark)
.Lsignal_nonyb:
moveb #.Lmr_write_action, %a3@(.Lwrite_mode0 - .Lread_mark)
rts
.global swim_read_sector_data
@@ -150,94 +175,85 @@ swim_read_sector_data:
link %a6, #0
moveml %d1-%d5/%a0-%a5,%sp@-
movel %a6@(0x0c), %a4
bsr mfm_read_data
moveq #-1, %d0
bsr .Lmfm_read_data
moveml %sp@+, %d1-%d5/%a0-%a5
unlk %a6
rts
mfm_read_data:
.Lmfm_read_data:
movel %a6@(0x08), %a3
lea %a3@(read_handshake), %a2
lea %a3@(read_data), %a5
lea %a3@(read_mark), %a3
movew #seek_time, %d2
lea %a3@(.Lread_handshake), %a2
lea %a3@(.Lread_data), %a5
lea %a3@(.Lread_mark), %a3
wait_data_init:
tstb %a3@(read_error - read_mark)
moveb #0x18, %a3@(write_mode0 - read_mark)
moveb #0x01, %a3@(write_mode1 - read_mark)
moveb #0x01, %a3@(write_mode0 - read_mark)
tstb %a3@(read_error - read_mark)
moveb #0x08, %a3@(write_mode1 - read_mark)
moveb #.Lmr_write_action, %a3@(.Lwrite_mode0 - .Lread_mark)
moveb #.Lmr_clear_fifo, %a3@(.Lwrite_mode1 - .Lread_mark)
moveb #.Lmr_clear_fifo, %a3@(.Lwrite_mode0 - .Lread_mark)
tstb %a3@(.Lread_error - .Lread_mark)
moveb #.Lmr_action, %a3@(.Lwrite_mode1 - .Lread_mark)
lea sector_data_mark, %a0
moveq #3, %d1
/* wait data address mark */
wait_data_mark_byte:
tstb %a2@
dbmi %d2, wait_data_mark_byte
bpl data_exit
moveb %a3@, %d3
cmpb %a0@+, %d3
dbne %d1, wait_data_mark_byte
bne wait_data_init
lea .Lsector_data_mark, %a1
moveq #.Lmark_sequence_len, %d4
bsr .Lmfm_mark_check
tstl %d1
bne .Ldata_exit
/* read data */
tstb %a3@(read_error - read_mark)
movel #sector_size-1, %d4 /* sector size */
read_new_data:
movew #max_retry, %d2
read_data_loop:
movel #.Lsector_size - 1, %d4 /* sector size */
movew #.Lmax_retry, %d2
.Lread_data_loop:
moveb %a2@, %d5
andb #0xc0, %d5
dbne %d2, read_data_loop
beq data_exit
moveb %a5@, %a4@+
andb #0x40, %d5
dbne %d4, read_new_data
beq exit_loop
moveb %a5@, %a4@+
dbra %d4, read_new_data
exit_loop:
andb #(.Lhr_fifo_1byte + .Lhr_fifo_2bytes), %d5
dbne %d2, .Lread_data_loop
beq .Ldata_exit
moveq #.Lmax_retry, %d2
moveb %a5@, %d3
bsr .Lmaybe_store
dbra %d4, 1f
bra .Ldata_crc0
1: andb #.Lhr_fifo_2bytes, %d5
beq .Lread_data_loop
moveb %a5@, %d3
bsr .Lmaybe_store
dbra %d4, .Lread_data_loop
/* read CRC */
movew #max_retry, %d2
data_crc0:
movew #.Lmax_retry, %d2
.Ldata_crc0:
tstb %a2@
dbmi %d2, data_crc0
bpl data_exit
dbmi %d2, .Ldata_crc0
bpl .Ldata_exit
moveb %a3@, %d5
moveq #max_retry, %d2
data_crc1:
moveb %a5@, %d5
moveq #.Lmax_retry, %d2
.Ldata_crc1:
tstb %a2@
dbmi %d2, data_crc1
bpl data_exit
dbmi %d2, .Ldata_crc1
bpl .Ldata_exit
moveb %a3@, %d5
moveb %a5@, %d5
tstb %a3@(read_error - read_mark)
moveb #0x18, %a3@(write_mode0 - read_mark)
moveb %a2@, %d5
andb #.Lhr_crc_error, %d5
bne .Ldata_exit
/* return number of bytes read */
movel #sector_size, %d0
movel #.Lsector_size, %d0
addw #1, %d4
subl %d4, %d0
.Ldata_exit:
moveb #.Lmr_write_action, %a3@(.Lwrite_mode0 - .Lread_mark)
rts
data_exit:
moveb #0x18, %a3@(write_mode0 - read_mark)
moveq #-1, %d0
rts
.Lmaybe_store:
tstl %a4
beq 9f
moveb %d3, %a4@+
9: rts
+208 -53
View File
@@ -321,6 +321,8 @@ struct ublk_device {
#define UB_STATE_OPEN 0
#define UB_STATE_USED 1
#define UB_STATE_DELETED 2
/* STOP_DEV canceled the server's commands, until its release */
#define UB_STATE_STOPPING 3
unsigned long state;
int ub_number;
@@ -334,6 +336,13 @@ struct ublk_device {
u16 nr_queue_ready;
bool unprivileged_daemons;
struct mutex cancel_mutex;
/* the open /dev/ublkcN, protected by cancel_mutex */
struct file *ch_file;
/*
* A cancel started in this FETCH round. Set by ublk_set_canceling(),
* cleared only by ublk_reset_ch_dev() when a new round starts. While
* it is set, no queue clears its ->canceling.
*/
bool canceling;
pid_t ublksrv_tgid;
struct delayed_work exit_work;
@@ -687,7 +696,7 @@ free_req:
if (!zone->len)
break;
ret = disk_report_zone(disk, zone, i, args);
ret = disk_report_zone(disk, zone, done_zones, args);
if (ret)
goto out;
@@ -810,6 +819,8 @@ ublk_batch_alloc_fcmd(struct io_uring_cmd *cmd)
if (fcmd) {
fcmd->cmd = cmd;
fcmd->buf_group = READ_ONCE(cmd->sqe->buf_index);
/* a cancel may look at it before it is linked */
INIT_LIST_HEAD(&fcmd->node);
}
return fcmd;
}
@@ -2399,6 +2410,9 @@ static int ublk_ch_open(struct inode *inode, struct file *filp)
return -EBUSY;
filp->private_data = ub;
ub->ublksrv_tgid = current->tgid;
mutex_lock(&ub->cancel_mutex);
ub->ch_file = filp;
mutex_unlock(&ub->cancel_mutex);
return 0;
}
@@ -2415,11 +2429,17 @@ static void ublk_reset_ch_dev(struct ublk_device *ub)
spin_unlock(&ubq->cancel_lock);
}
/* a new FETCH round starts, the queues stay canceling until ready */
mutex_lock(&ub->cancel_mutex);
ub->canceling = false;
mutex_unlock(&ub->cancel_mutex);
/* set to NULL, otherwise new tasks cannot mmap io_cmd_buf */
ub->mm = NULL;
ub->nr_queue_ready = 0;
ub->unprivileged_daemons = false;
ub->ublksrv_tgid = -1;
clear_bit(UB_STATE_STOPPING, &ub->state);
}
static struct gendisk *ublk_get_disk(struct ublk_device *ub)
@@ -2539,12 +2559,15 @@ static void ublk_ch_release_work_fn(struct work_struct *work)
}
/*
* disk isn't attached yet, either device isn't live, or it has
* been removed already, so we needn't to do anything
* No disk: the device isn't live, or it has been removed already.
* There are no requests to abort, but the round still has to be
* reset, so that a new server can fetch and start the device.
*/
disk = ublk_get_disk(ub);
if (!disk)
goto out;
if (!disk) {
mutex_lock(&ub->mutex);
goto reset;
}
/*
* All uring_cmd are done now, so abort any request outstanding to
@@ -2576,7 +2599,7 @@ static void ublk_ch_release_work_fn(struct work_struct *work)
/* double check after grabbing lock */
if (!ub->ub_disk)
goto unlock;
goto reset;
/*
* Transition the device to the nosrv state. What exactly this
@@ -2602,29 +2625,36 @@ static void ublk_ch_release_work_fn(struct work_struct *work)
WRITE_ONCE(ublk_get_queue(ub, i)->fail_io, true);
}
}
unlock:
reset:
/*
* All uring_cmd has been done now, reset device & ubq. Under
* ub->mutex, so START_DEV sees the round either ready or reset.
*/
ublk_reset_ch_dev(ub);
mutex_unlock(&ub->mutex);
ublk_put_disk(disk);
/* all uring_cmd has been done now, reset device & ubq */
ublk_reset_ch_dev(ub);
out:
clear_bit(UB_STATE_OPEN, &ub->state);
/* put the reference grabbed in ublk_ch_release() */
ublk_put_device(ub);
module_put(THIS_MODULE);
}
static int ublk_ch_release(struct inode *inode, struct file *filp)
{
struct ublk_device *ub = filp->private_data;
mutex_lock(&ub->cancel_mutex);
ub->ch_file = NULL;
mutex_unlock(&ub->cancel_mutex);
/*
* Grab ublk device reference, so it won't be gone until we are
* really released from work function.
*/
ublk_get_device(ub);
/* Keep the worker's callback and final device release code resident. */
__module_get(THIS_MODULE);
INIT_DELAYED_WORK(&ub->exit_work, ublk_ch_release_work_fn);
schedule_delayed_work(&ub->exit_work, 0);
return 0;
@@ -2732,9 +2762,11 @@ static void ublk_abort_queue(struct ublk_device *ub, struct ublk_queue *ubq)
static void ublk_start_cancel(struct ublk_device *ub)
{
struct gendisk *disk = ublk_get_disk(ub);
struct gendisk *disk;
/* sync with ublk_ctrl_start_dev() publishing the disk */
mutex_lock(&ub->cancel_mutex);
disk = ublk_get_disk(ub);
if (ub->canceling)
goto out;
@@ -2790,7 +2822,8 @@ static void ublk_cancel_cmd(struct ublk_queue *ubq, u16 tag,
done = !!(io->flags & UBLK_IO_FLAG_CANCELED);
if (!done) {
io->flags |= UBLK_IO_FLAG_CANCELED;
cmd = io->cmd;
/* dependency ordered against smp_wmb() in ublk_prep_cancel() */
cmd = READ_ONCE(io->cmd);
io->cmd = NULL;
}
spin_unlock(&ubq->cancel_lock);
@@ -2879,6 +2912,7 @@ static void ublk_uring_cmd_cancel_fn(struct io_uring_cmd *cmd,
{
struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(cmd);
struct ublk_queue *ubq = pdu->ubq;
struct io_uring_cmd *cur;
struct task_struct *task;
struct ublk_io *io;
@@ -2895,7 +2929,9 @@ static void ublk_uring_cmd_cancel_fn(struct io_uring_cmd *cmd,
ublk_start_cancel(ubq->dev);
WARN_ON_ONCE(io->cmd != cmd);
/* NULL if a cancel from the control path took it meanwhile */
cur = READ_ONCE(io->cmd);
WARN_ON_ONCE(cur && cur != cmd);
ublk_cancel_cmd(ubq, pdu->tag, issue_flags);
}
@@ -3006,13 +3042,54 @@ static void ublk_stop_dev_unlocked(struct ublk_device *ub)
put_disk(disk);
}
static struct file *ublk_get_ch_file(struct ublk_device *ub)
{
struct file *file;
mutex_lock(&ub->cancel_mutex);
file = ub->ch_file;
if (file && !file_ref_get(&file->f_ref))
file = NULL;
mutex_unlock(&ub->cancel_mutex);
return file;
}
static void ublk_stop_dev(struct ublk_device *ub)
{
struct file *file;
/*
* FETCH, PREP and START_DEV take ub->mutex. If a server has
* /dev/ublkcN open, set STOPPING, which turns it away, and hold a
* reference on the file: the server's release, whose reset clears
* STOPPING and lets a new server attach, can't run before we drop it.
* So the cancel can run after the unlock and only meets this server's
* commands; it has to: io_uring_cmd_done() may take uring_lock, under
* which FETCH takes ub->mutex.
*/
mutex_lock(&ub->mutex);
ublk_stop_dev_unlocked(ub);
mutex_unlock(&ub->mutex);
cancel_work_sync(&ub->partition_scan_work);
file = ublk_get_ch_file(ub);
if (file) {
/*
* The server has to close /dev/ublkcN before this device can
* be started again: the reset in its release clears STOPPING.
*/
set_bit(UB_STATE_STOPPING, &ub->state);
/* for wake_up_var() below, see wake_up_bit() */
smp_mb__after_atomic();
}
mutex_unlock(&ub->mutex);
/* no server: nothing to cancel */
if (!file)
return;
/* wake a START_DEV waiting for the device to get ready */
wake_up_var(&ub->nr_queue_ready);
ublk_cancel_dev(ub);
__fput_sync(file);
}
static void ublk_reset_io_flags(struct ublk_queue *ubq, struct ublk_io *io)
@@ -3024,11 +3101,19 @@ static void ublk_reset_io_flags(struct ublk_queue *ubq, struct ublk_io *io)
}
/* reset per-queue io flags */
static void ublk_queue_reset_io_flags(struct ublk_queue *ubq)
static void ublk_queue_reset_io_flags(struct ublk_device *ub,
struct ublk_queue *ubq)
{
spin_lock(&ubq->cancel_lock);
ubq->canceling = false;
spin_unlock(&ubq->cancel_lock);
/*
* A cancel in this FETCH round took a command which still counts as
* ready, so the queue has to stay canceling. ub->canceling is set
* under cancel_mutex before any command is taken: either we see it
* here, or the cancel marks this queue again later.
*/
mutex_lock(&ub->cancel_mutex);
if (!ub->canceling)
ubq->canceling = false;
mutex_unlock(&ub->cancel_mutex);
ubq->fail_io = false;
ubq->force_abort = false;
}
@@ -3051,24 +3136,17 @@ static void ublk_mark_io_ready(struct ublk_device *ub, u16 q_id,
ub->nr_queue_ready++;
/*
* Reset queue flags as soon as this queue is ready.
* This clears the canceling flag, allowing batch FETCH commands
* to succeed during recovery without waiting for all queues.
* Reset queue flags as soon as this queue is ready. Unless
* this round saw a cancel, this clears the canceling flag,
* allowing batch FETCH commands to succeed during recovery
* without waiting for all queues.
*/
ublk_queue_reset_io_flags(ubq);
ublk_queue_reset_io_flags(ub, ubq);
}
/* Check if all queues are ready */
if (ublk_dev_ready(ub)) {
/*
* All queues ready - clear device-level canceling flag
* and wake ublk_dev_ready() waiters.
*/
mutex_lock(&ub->cancel_mutex);
ub->canceling = false;
mutex_unlock(&ub->cancel_mutex);
/* All queues ready - wake ublk_dev_ready() waiters */
if (ublk_dev_ready(ub))
wake_up_var(&ub->nr_queue_ready);
}
}
static inline int ublk_check_cmd_op(u32 cmd_op)
@@ -3152,6 +3230,12 @@ ublk_fill_io_cmd(struct ublk_io *io, struct io_uring_cmd *cmd)
return req;
}
/*
* Call before ublk_fill_io_cmd() publishes @cmd in io->cmd: a control-path
* cancel may complete any command found there, and io_uring_cmd_done() only
* takes it off the cancelable list if it is marked already. The handlers
* hold uring_lock, so marking takes no lock.
*/
static inline void ublk_prep_cancel(struct io_uring_cmd *cmd,
unsigned int issue_flags,
struct ublk_queue *ubq, u16 tag)
@@ -3165,6 +3249,8 @@ static inline void ublk_prep_cancel(struct io_uring_cmd *cmd,
pdu->ubq = ubq;
pdu->tag = tag;
io_uring_cmd_mark_cancelable(cmd, issue_flags);
/* pairs with the cancel loading cmd from io->cmd, then cmd->flags */
smp_wmb();
}
static void ublk_io_release(void *priv)
@@ -3269,6 +3355,9 @@ static int ublk_check_fetch_buf(const struct ublk_device *ub, __u64 buf_addr)
static int __ublk_fetch(struct io_uring_cmd *cmd, struct ublk_device *ub,
struct ublk_io *io, u16 q_id)
{
if (test_bit(UB_STATE_STOPPING, &ub->state))
return UBLK_IO_RES_ABORT;
/* UBLK_IO_FETCH_REQ is only allowed before dev is setup */
if (ublk_dev_ready(ub))
return -EBUSY;
@@ -3412,11 +3501,11 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd,
ret = ublk_check_fetch_buf(ub, addr);
if (ret)
goto out;
/* before ublk_fetch() publishes io->cmd, see ublk_prep_cancel() */
ublk_prep_cancel(cmd, issue_flags, ubq, tag);
ret = ublk_fetch(cmd, ub, io, addr, q_id);
if (ret)
goto out;
ublk_prep_cancel(cmd, issue_flags, ubq, tag);
goto out_done;
return -EIOCBQUEUED;
}
@@ -3460,6 +3549,7 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd,
if (ret)
goto out;
io->res = result;
ublk_prep_cancel(cmd, issue_flags, ubq, tag);
req = ublk_fill_io_cmd(io, cmd);
ublk_apply_io_buf(ub, io, cmd, addr, &auto_buf, &buf_idx);
if (buf_idx != UBLK_INVALID_BUF_IDX)
@@ -3478,19 +3568,24 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd,
* uring_cmd active first and prepare for handling new requeued
* request
*/
ublk_prep_cancel(cmd, issue_flags, ubq, tag);
req = ublk_fill_io_cmd(io, cmd);
io->buf.addr = addr;
if (likely(ublk_get_data(ubq, io, req))) {
__ublk_prep_compl_io_cmd(io, req);
return UBLK_IO_RES_OK;
ret = UBLK_IO_RES_OK;
goto out_done;
}
break;
default:
goto out;
}
ublk_prep_cancel(cmd, issue_flags, ubq, tag);
return -EIOCBQUEUED;
out_done:
/* marked cancelable: complete through io_uring_cmd_done() */
io_uring_cmd_done(cmd, ret, issue_flags);
return -EIOCBQUEUED;
out:
pr_devel("%s: complete: cmd op %d, tag %d ret %x io_flags %x\n",
__func__, cmd_op, tag, ret, io ? io->flags : 0);
@@ -3889,6 +3984,15 @@ static int ublk_batch_attach(struct ublk_queue *ubq,
bool free = false;
struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(data->cmd);
/*
* Mark it cancelable before linking it into fcmd_head, where a cancel
* from the control path can take and complete it: see
* ublk_prep_cancel(). evts_lock orders the mark before the link.
*/
pdu->ubq = ubq;
pdu->fcmd = fcmd;
io_uring_cmd_mark_cancelable(fcmd->cmd, data->issue_flags);
spin_lock(&ubq->evts_lock);
if (unlikely(ubq->force_abort || ubq->canceling)) {
free = true;
@@ -3899,14 +4003,12 @@ static int ublk_batch_attach(struct ublk_queue *ubq,
spin_unlock(&ubq->evts_lock);
if (unlikely(free)) {
/* off the cancelable list first, then nothing can see fcmd */
io_uring_cmd_done(data->cmd, -ENODEV, data->issue_flags);
ublk_batch_free_fcmd(fcmd);
return -ENODEV;
return -EIOCBQUEUED;
}
pdu->ubq = ubq;
pdu->fcmd = fcmd;
io_uring_cmd_mark_cancelable(fcmd->cmd, data->issue_flags);
if (!new_fcmd)
goto out;
@@ -3914,9 +4016,12 @@ static int ublk_batch_attach(struct ublk_queue *ubq,
* If the two fetch commands are originated from same io_ring_ctx,
* run batch dispatch directly. Otherwise, schedule task work for
* doing it.
*
* Use data->cmd, not fcmd->cmd: once fcmd is linked and not active,
* a cancel from the control path may complete and free it.
*/
if (io_uring_cmd_ctx_handle(new_fcmd->cmd) ==
io_uring_cmd_ctx_handle(fcmd->cmd)) {
io_uring_cmd_ctx_handle(data->cmd)) {
data->cmd = new_fcmd->cmd;
ublk_batch_dispatch(ubq, data, new_fcmd);
} else {
@@ -4389,6 +4494,7 @@ static int ublk_add_tag_set(struct ublk_device *ub)
ub->tag_set.nr_hw_queues = ub->dev_info.nr_hw_queues;
ub->tag_set.queue_depth = ub->dev_info.queue_depth;
ub->tag_set.numa_node = NUMA_NO_NODE;
ub->tag_set.flags = BLK_MQ_F_NO_SCHED_BY_DEFAULT;
ub->tag_set.driver_data = ub;
return blk_mq_alloc_tag_set(&ub->tag_set);
}
@@ -4431,21 +4537,27 @@ static bool ublk_validate_user_pid(struct ublk_device *ub, pid_t ublksrv_pid)
return ub->ublksrv_tgid == ublksrv_pid;
}
static bool ublk_dev_ready_or_stopping(const struct ublk_device *ub)
{
return ublk_dev_ready(ub) || test_bit(UB_STATE_STOPPING, &ub->state);
}
/*
* Wait until all queues have fetched their I/O commands, and return with
* ub->mutex held and readiness guaranteed: then every queue's ->canceling
* is cleared. Ready may regress between wakeup and mutex_lock() (F_BATCH
* UNPREP, daemon death), so re-check it under the mutex and wait again.
* Wait until all queues have fetched their I/O commands, or STOP_DEV set
* UB_STATE_STOPPING, and return with ub->mutex held. The queues stay
* canceling if this round saw a cancel, see ublk_queue_reset_io_flags().
* Ready may regress between wakeup and mutex_lock() (F_BATCH UNPREP,
* daemon death), so re-check it under the mutex and wait again.
*/
static int ublk_wait_dev_ready_and_lock(struct ublk_device *ub)
{
while (true) {
if (wait_var_event_interruptible(&ub->nr_queue_ready,
ublk_dev_ready(ub)))
ublk_dev_ready_or_stopping(ub)))
return -EINTR;
mutex_lock(&ub->mutex);
if (ublk_dev_ready(ub))
if (ublk_dev_ready_or_stopping(ub))
return 0;
mutex_unlock(&ub->mutex);
}
@@ -4469,6 +4581,7 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub,
.dma_alignment = 3,
};
struct gendisk *disk;
bool canceled;
int ret = -EINVAL;
if (ublksrv_pid <= 0)
@@ -4545,6 +4658,10 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub,
ret = -EEXIST;
goto out_unlock;
}
if (test_bit(UB_STATE_STOPPING, &ub->state)) {
ret = -EBUSY;
goto out_unlock;
}
disk = blk_mq_alloc_disk(&ub->tag_set, &lim, NULL);
if (IS_ERR(disk)) {
@@ -4555,8 +4672,24 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub,
disk->fops = &ub_fops;
disk->private_data = ub;
/*
* A command of this FETCH round was canceled and can't be fetched
* again, don't bring up a disk over it. Check and publish the disk
* in one cancel_mutex section: either this sees ub->canceling, or
* ublk_start_cancel() sees the disk and quiesces it before marking
* the queues.
*/
mutex_lock(&ub->cancel_mutex);
canceled = ub->canceling;
if (!canceled)
ub->ub_disk = disk;
mutex_unlock(&ub->cancel_mutex);
if (canceled) {
put_disk(disk);
ret = -EBUSY;
goto out_unlock;
}
ub->dev_info.ublksrv_pid = ub->ublksrv_tgid;
ub->ub_disk = disk;
ublk_apply_params(ub);
@@ -4901,10 +5034,15 @@ static int ublk_ctrl_del_dev(struct ublk_device **p_ub, bool wait)
set_bit(UB_STATE_DELETED, &ub->state);
}
/* Mark the reference as consumed */
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.
*/
*p_ub = NULL;
ublk_put_device(ub);
mutex_unlock(&ublk_ctl_mutex);
/*
* Wait until the idr is removed, then it can be reused after
@@ -5128,6 +5266,7 @@ static int ublk_ctrl_end_recovery(struct ublk_device *ub,
const struct ublksrv_ctrl_cmd *header)
{
int ublksrv_pid = (int)header->data[0];
bool canceled;
int ret = -EINVAL;
pr_devel("%s: Waiting for all FETCH_REQs, dev id %d...\n", __func__,
@@ -5151,6 +5290,20 @@ static int ublk_ctrl_end_recovery(struct ublk_device *ub,
ret = -EBUSY;
goto out_unlock;
}
/*
* As in ublk_ctrl_start_dev(), a canceled command can't be fetched
* again. Best effort: the disk exists here, and a cancel after this
* check is the ordinary death of the new server, which
* ublk_start_cancel() handles by quiescing and marking.
*/
mutex_lock(&ub->cancel_mutex);
canceled = ub->canceling;
mutex_unlock(&ub->cancel_mutex);
if (canceled) {
ret = -EBUSY;
goto out_unlock;
}
ub->dev_info.ublksrv_pid = ub->ublksrv_tgid;
ub->dev_info.state = UBLK_S_DEV_LIVE;
pr_devel("%s: new ublksrv_pid %d, dev id %d\n",
@@ -5628,6 +5781,8 @@ static bool ublk_try_buf_match(struct ublk_device *ub,
unsigned long expected_offset = 0;
bool first = true;
guard(rcu)();
rq_for_each_bvec(bv, rq, iter) {
unsigned long pfn = page_to_pfn(bv.bv_page);
unsigned long end_pfn = pfn +
+24 -14
View File
@@ -318,22 +318,23 @@ static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no)
ret = vfs_getattr(&zone->file->f_path, &stat, STATX_SIZE, 0);
if (ret < 0) {
pr_err("Failed to get zone %u file stat (err=%d)\n",
zone_no, ret);
pr_err("%pg: Failed to get zone %u file stat (err=%d)\n",
zlo->disk->part0, zone_no, ret);
set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
return ret;
}
file_sectors = stat.size >> SECTOR_SHIFT;
if (file_sectors > zlo->zone_capacity) {
pr_err("Zone %u file too large (%llu sectors > %llu)\n",
zone_no, file_sectors, zlo->zone_capacity);
pr_err("%pg: Zone %u file too large (%llu sectors > %llu)\n",
zlo->disk->part0, zone_no, file_sectors,
zlo->zone_capacity);
return -EINVAL;
}
if (!IS_ALIGNED(stat.size, zlo->block_size)) {
pr_err("Zone %u file size (%llu) not aligned to block size %u\n",
zone_no, stat.size, zlo->block_size);
pr_err("%pg: Zone %u file size (%llu) not aligned to block size %u\n",
zlo->disk->part0, zone_no, stat.size, zlo->block_size);
return -EINVAL;
}
@@ -603,8 +604,15 @@ static int zloop_seq_write_prep(struct zloop_cmd *cmd)
}
} else {
if (cmd->sector != zone->wp) {
pr_err("Zone %u: unaligned write: sect %llu, wp %llu\n",
zone_no, cmd->sector, zone->wp);
if (zone->wp == ULLONG_MAX)
pr_err("%pg: Zone %u: write to %s zone: sect %llu\n",
zlo->disk->part0, zone_no,
blk_zone_cond_str(zone->cond),
cmd->sector);
else
pr_err("%pg: Zone %u: unaligned write: sect %llu, wp %llu\n",
zlo->disk->part0, zone_no, cmd->sector,
zone->wp);
ret = -EIO;
goto out_unlock;
}
@@ -832,8 +840,9 @@ static void zloop_complete_rq(struct request *rq)
switch (req_op(rq)) {
case REQ_OP_READ:
if (cmd->ret < 0)
pr_err("Zone %u: failed read sector %llu, %llu sectors\n",
zone_no, cmd->sector, cmd->nr_sectors);
pr_err("%pg: Zone %u: failed read sector %llu, %llu sectors\n",
zlo->disk->part0, zone_no, cmd->sector,
cmd->nr_sectors);
/* Short read. Zero out the unread tail beyond cmd->ret. */
if (cmd->ret >= 0 && cmd->ret != blk_rq_bytes(rq))
@@ -842,14 +851,15 @@ static void zloop_complete_rq(struct request *rq)
case REQ_OP_WRITE:
case REQ_OP_ZONE_APPEND:
if (cmd->ret < 0)
pr_err("Zone %u: failed %swrite sector %llu, %llu sectors\n",
zone_no,
pr_err("%pg: Zone %u: failed %swrite sector %llu, %llu sectors\n",
zlo->disk->part0, zone_no,
req_op(rq) == REQ_OP_WRITE ? "" : "append ",
cmd->sector, cmd->nr_sectors);
if (cmd->ret >= 0 && cmd->ret != blk_rq_bytes(rq)) {
pr_err("Zone %u: partial write %ld/%u B\n",
zone_no, cmd->ret, blk_rq_bytes(rq));
pr_err("%pg: Zone %u: partial write %ld/%u B\n",
zlo->disk->part0, zone_no, cmd->ret,
blk_rq_bytes(rq));
cmd->ret = -EIO;
}
+35 -4
View File
@@ -42,7 +42,11 @@
#define APPLE_ANS_ACQ_DB 0x1004
#define APPLE_ANS_IOCQ_DB 0x100c
#define APPLE_ANS_MAX_PEND_CMDS_CTRL 0x1210
#define APPLE_ANS_T8132_IOQ_CMDS 0x1200
#define APPLE_ANS_T8132_IOQ_CQES 0x1208
#define APPLE_ANS_T8132_IOQ_SIZE 0x1210
#define APPLE_ANS_T8103_MAX_PEND_CMDS_CTRL 0x1210
#define APPLE_ANS_BOOT_STATUS 0x1300
#define APPLE_ANS_BOOT_STATUS_OK 0xde71ce55
@@ -184,6 +188,7 @@ struct apple_nvme_iod {
struct apple_nvme_hw {
bool has_lsq_nvmmu;
bool needs_ioq_register;
u32 max_queue_depth;
};
@@ -1133,9 +1138,16 @@ static void apple_nvme_reset_work(struct work_struct *work)
anv->mmio_nvme + APPLE_ANS_LINEAR_SQ_CTRL);
/* Allow as many pending command as possible for both queues */
writel(anv->hw->max_queue_depth
| (anv->hw->max_queue_depth << 16), anv->mmio_nvme
+ APPLE_ANS_MAX_PEND_CMDS_CTRL);
if (!anv->hw->needs_ioq_register) {
/*
* Since T8132, this is replaced by IOQ_SIZE which is
* located at the same offset and is handled with the
* IO queue setup further below.
*/
writel(anv->hw->max_queue_depth
| (anv->hw->max_queue_depth << 16), anv->mmio_nvme
+ APPLE_ANS_T8103_MAX_PEND_CMDS_CTRL);
}
/* Setup the NVMMU for the maximum admin and IO queue depth */
writel(anv->hw->max_queue_depth - 1,
@@ -1190,6 +1202,16 @@ static void apple_nvme_reset_work(struct work_struct *work)
if (ret)
goto out_remove_cq;
if (anv->hw->needs_ioq_register) {
writeq(anv->ioq.cq_dma_addr,
anv->mmio_nvme + APPLE_ANS_T8132_IOQ_CQES);
writeq(anv->ioq.sq_dma_addr,
anv->mmio_nvme + APPLE_ANS_T8132_IOQ_CMDS);
writel((anv->hw->max_queue_depth - 1)
| ((anv->hw->max_queue_depth - 1) << 16), anv->mmio_nvme
+ APPLE_ANS_T8132_IOQ_SIZE);
}
apple_nvme_init_queue(&anv->ioq);
nr_io_queues = 1;
ret = nvme_set_queue_count(&anv->ctrl, &nr_io_queues);
@@ -1699,17 +1721,26 @@ static DEFINE_SIMPLE_DEV_PM_OPS(apple_nvme_pm_ops, apple_nvme_suspend,
static const struct apple_nvme_hw apple_nvme_t8015_hw = {
.has_lsq_nvmmu = false,
.needs_ioq_register = false,
.max_queue_depth = 16,
};
static const struct apple_nvme_hw apple_nvme_t8103_hw = {
.has_lsq_nvmmu = true,
.needs_ioq_register = false,
.max_queue_depth = 64,
};
static const struct apple_nvme_hw apple_nvme_t8132_hw = {
.has_lsq_nvmmu = true,
.needs_ioq_register = true,
.max_queue_depth = 64,
};
static const struct of_device_id apple_nvme_of_match[] = {
{ .compatible = "apple,t8015-nvme-ans2", .data = &apple_nvme_t8015_hw },
{ .compatible = "apple,t8103-nvme-ans2", .data = &apple_nvme_t8103_hw },
{ .compatible = "apple,t8132-nvme-ans2", .data = &apple_nvme_t8132_hw },
{ .compatible = "apple,nvme-ans2", .data = &apple_nvme_t8103_hw },
{},
};
+14 -1
View File
@@ -358,8 +358,21 @@ static int nvme_auth_process_dhchap_success1(struct nvme_ctrl *ctrl,
nvme_auth_hmac_name(chap->hash_id),
nvme_auth_dhgroup_name(chap->dhgroup_id));
if (!data->rvalid)
if (!data->rvalid) {
/*
* The controller did not return a response. If we asked it to
* authenticate itself the session must not be used, otherwise
* mutual authentication would silently degrade to one-way.
*/
if (chap->bi_directional) {
dev_warn(ctrl->device,
"qid %d: controller did not authenticate\n",
chap->qid);
chap->status = NVME_AUTH_DHCHAP_FAILURE_FAILED;
return -ECONNREFUSED;
}
return 0;
}
/* Validate controller response */
if (crypto_memneq(chap->response, data->rval, data->hl)) {
+10 -5
View File
@@ -2273,13 +2273,18 @@ static int nvme_query_fdp_granularity(struct nvme_ctrl *ctrl,
}
size = le32_to_cpu(hdr.sze);
if (size < sizeof(*h) + sizeof(*desc)) {
dev_warn(ctrl->device, "FDP config size too small:%zu\n",
size);
return 0;
}
if (size > PAGE_SIZE * MAX_ORDER_NR_PAGES) {
dev_warn(ctrl->device, "FDP config size too large:%zu\n",
size);
return 0;
}
h = kvmalloc(size, GFP_KERNEL);
h = kvzalloc(size, GFP_KERNEL);
if (!h)
return -ENOMEM;
@@ -2305,9 +2310,9 @@ static int nvme_query_fdp_granularity(struct nvme_ctrl *ctrl,
desc = log;
end = log + size - sizeof(*h);
for (i = 0; i < fdp_idx; i++) {
u16 dsze = le16_to_cpu(desc->dsze);
u16 dsze = get_unaligned_le16(&desc->dsze);
if (!dsze || log + dsze > end) {
if (dsze < sizeof(*desc) || log + dsze + sizeof(*desc) > end) {
dev_warn(ctrl->device,
"FDP invalid config descriptor at index %d\n", i);
ret = 0;
@@ -2317,13 +2322,13 @@ static int nvme_query_fdp_granularity(struct nvme_ctrl *ctrl,
desc = log;
}
if (le32_to_cpu(desc->nrg) > 1) {
if (get_unaligned_le32(&desc->nrg) > 1) {
dev_warn(ctrl->device, "FDP NRG > 1 not supported\n");
ret = 0;
goto out;
}
info->runs = le64_to_cpu(desc->runs);
info->runs = get_unaligned_le64(&desc->runs);
out:
kvfree(h);
return ret;
+1 -1
View File
@@ -343,7 +343,7 @@ static int nvme_user_cmd(struct nvme_ctrl *ctrl, struct nvme_ns *ns,
c.common.cdw14 = cpu_to_le32(cmd.cdw14);
c.common.cdw15 = cpu_to_le32(cmd.cdw15);
if (!nvme_cmd_allowed(ctrl, ns, &c, 0, open_for_write))
if (!nvme_cmd_allowed(ctrl, ns, &c, flags, open_for_write))
return -EACCES;
if (cmd.timeout_ms)
+136 -19
View File
@@ -307,7 +307,14 @@ int nvme_mpath_revalidate_zones(struct nvme_ns_head *head)
}
#endif /* CONFIG_BLK_DEV_ZONED */
static bool nvme_path_is_disabled(struct nvme_ns *ns)
enum nvme_path_state {
NVME_PATH_ENABLED,
NVME_PATH_DISABLED_CTRL_DOWN,
NVME_PATH_DISABLED_ANA_PENDING,
NVME_PATH_DISABLED_NS_NOT_READY,
};
static enum nvme_path_state nvme_path_get_state(struct nvme_ns *ns)
{
enum nvme_ctrl_state state = nvme_ctrl_state(ns->ctrl);
@@ -317,11 +324,17 @@ static bool nvme_path_is_disabled(struct nvme_ns *ns)
* Otherwise it will fail immediately and return to the requeue list.
*/
if (state != NVME_CTRL_LIVE && state != NVME_CTRL_DELETING)
return true;
if (test_bit(NVME_NS_ANA_PENDING, &ns->flags) ||
!test_bit(NVME_NS_READY, &ns->flags))
return true;
return false;
return NVME_PATH_DISABLED_CTRL_DOWN;
if (test_bit(NVME_NS_ANA_PENDING, &ns->flags))
return NVME_PATH_DISABLED_ANA_PENDING;
if (!test_bit(NVME_NS_READY, &ns->flags))
return NVME_PATH_DISABLED_NS_NOT_READY;
return NVME_PATH_ENABLED;
}
static bool nvme_path_is_disabled(struct nvme_ns *ns)
{
return nvme_path_get_state(ns) != NVME_PATH_ENABLED;
}
static struct nvme_ns *__nvme_find_path(struct nvme_ns_head *head, int node)
@@ -496,9 +509,17 @@ inline struct nvme_ns *nvme_find_path(struct nvme_ns_head *head)
}
}
static inline bool nvme_state_is_live(enum nvme_ana_state state)
{
return state == NVME_ANA_OPTIMIZED || state == NVME_ANA_NONOPTIMIZED;
}
static bool nvme_available_path(struct nvme_ns_head *head)
__must_hold_shared(&head->srcu)
{
bool fail_on_ctrl_loss = test_bit(NVME_NSHEAD_FAIL_ON_CTRL_LOSS,
&head->flags);
bool failfast = false;
struct nvme_ns *ns;
if (!test_bit(NVME_NSHEAD_DISK_LIVE, &head->flags))
@@ -506,18 +527,30 @@ static bool nvme_available_path(struct nvme_ns_head *head)
list_for_each_entry_srcu(ns, &head->list, siblings,
srcu_read_lock_held(&head->srcu)) {
if (test_bit(NVME_CTRL_FAILFAST_EXPIRED, &ns->ctrl->flags))
if (test_bit(NVME_CTRL_FAILFAST_EXPIRED, &ns->ctrl->flags)) {
failfast = true;
continue;
}
switch (nvme_ctrl_state(ns->ctrl)) {
case NVME_CTRL_LIVE:
case NVME_CTRL_RESETTING:
case NVME_CTRL_CONNECTING:
if (!nvme_state_is_live(ns->ana_state) &&
ns->ana_state != NVME_ANA_CHANGE)
continue;
return true;
case NVME_CTRL_RESETTING:
return true;
case NVME_CTRL_CONNECTING:
if (!fail_on_ctrl_loss)
return true;
continue;
default:
break;
}
}
if (failfast || fail_on_ctrl_loss)
return false;
/*
* If "head->delayed_removal_secs" is configured (i.e., non-zero), do
* not immediately fail I/O. Instead, requeue the I/O for the configured
@@ -871,11 +904,6 @@ static int nvme_parse_ana_log(struct nvme_ctrl *ctrl, void *data,
return 0;
}
static inline bool nvme_state_is_live(enum nvme_ana_state state)
{
return state == NVME_ANA_OPTIMIZED || state == NVME_ANA_NONOPTIMIZED;
}
static void nvme_update_ns_ana_state(struct nvme_ana_group_desc *desc,
struct nvme_ns *ns)
{
@@ -910,8 +938,10 @@ static void nvme_update_ns_ana_state(struct nvme_ana_group_desc *desc,
* is not live but still create the sysfs link to this path from
* head node if head node of the path has already come alive.
*/
if (test_bit(NVME_NSHEAD_DISK_LIVE, &ns->head->flags))
if (test_bit(NVME_NSHEAD_DISK_LIVE, &ns->head->flags)) {
nvme_mpath_add_sysfs_link(ns->head);
kblockd_schedule_work(&ns->head->requeue_work);
}
}
}
@@ -1038,6 +1068,10 @@ static ssize_t nvme_subsys_iopolicy_show(struct device *dev,
struct nvme_subsystem *subsys =
container_of(dev, struct nvme_subsystem, dev);
/* A discovery subsystem has no namespaces and hence no I/O paths */
if (subsys->subtype == NVME_NQN_DISC)
return sysfs_emit(buf, "none\n");
return sysfs_emit(buf, "%s\n",
nvme_iopolicy_names[READ_ONCE(subsys->iopolicy)]);
}
@@ -1072,6 +1106,9 @@ static ssize_t nvme_subsys_iopolicy_store(struct device *dev,
container_of(dev, struct nvme_subsystem, dev);
int policy;
if (subsys->subtype == NVME_NQN_DISC)
return -EOPNOTSUPP;
policy = nvme_iopolicy_parse(buf);
if (policy < 0)
return policy;
@@ -1082,6 +1119,26 @@ static ssize_t nvme_subsys_iopolicy_store(struct device *dev,
SUBSYS_ATTR_RW(iopolicy, S_IRUGO | S_IWUSR,
nvme_subsys_iopolicy_show, nvme_subsys_iopolicy_store);
static ssize_t iopolicies_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct nvme_subsystem *subsys =
container_of(dev, struct nvme_subsystem, dev);
int i, len = 0;
if (subsys->subtype == NVME_NQN_DISC)
return sysfs_emit(buf, "none\n");
for (i = 0; i < ARRAY_SIZE(nvme_iopolicy_names); i++)
len += sysfs_emit_at(buf, len, "%s%s", i ? " " : "",
nvme_iopolicy_names[i]);
len += sysfs_emit_at(buf, len, "\n");
return len;
}
struct device_attribute subsys_attr_iopolicies = __ATTR_RO(iopolicies);
static ssize_t ana_grpid_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -1110,6 +1167,28 @@ static ssize_t queue_depth_show(struct device *dev,
}
DEVICE_ATTR_RO(queue_depth);
static const char * const nvme_path_state_names[] = {
[NVME_PATH_ENABLED] = "enabled",
[NVME_PATH_DISABLED_CTRL_DOWN] = "ctrl-down",
[NVME_PATH_DISABLED_ANA_PENDING] = "ana-pending",
[NVME_PATH_DISABLED_NS_NOT_READY] = "ns-not-ready",
};
static ssize_t path_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct nvme_ns *ns = nvme_get_ns_from_dev(dev);
enum nvme_path_state state = nvme_path_get_state(ns);
const char *name = "unknown";
if (state < ARRAY_SIZE(nvme_path_state_names) &&
nvme_path_state_names[state])
name = nvme_path_state_names[state];
return sysfs_emit(buf, "%s\n", name);
}
DEVICE_ATTR_RO(path_state);
static ssize_t numa_nodes_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -1164,10 +1243,7 @@ static ssize_t delayed_removal_secs_store(struct device *dev,
mutex_lock(&head->subsys->lock);
head->delayed_removal_secs = sec;
if (sec)
set_bit(NVME_NSHEAD_QUEUE_IF_NO_PATH, &head->flags);
else
clear_bit(NVME_NSHEAD_QUEUE_IF_NO_PATH, &head->flags);
assign_bit(NVME_NSHEAD_QUEUE_IF_NO_PATH, &head->flags, sec);
mutex_unlock(&head->subsys->lock);
/*
* Ensure that update to NVME_NSHEAD_QUEUE_IF_NO_PATH is seen
@@ -1180,6 +1256,47 @@ static ssize_t delayed_removal_secs_store(struct device *dev,
DEVICE_ATTR_RW(delayed_removal_secs);
static ssize_t fail_on_ctrl_loss_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct gendisk *disk = dev_to_disk(dev);
struct nvme_ns_head *head = disk->private_data;
return sysfs_emit(buf, test_bit(NVME_NSHEAD_FAIL_ON_CTRL_LOSS,
&head->flags) ? "on\n" : "off\n");
}
static ssize_t fail_on_ctrl_loss_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct gendisk *disk = dev_to_disk(dev);
struct nvme_ns_head *head = disk->private_data;
bool enable;
int ret;
ret = kstrtobool(buf, &enable);
if (ret < 0)
return ret;
if (enable) {
if (test_and_set_bit(NVME_NSHEAD_FAIL_ON_CTRL_LOSS, &head->flags))
return count;
} else {
if (!test_and_clear_bit(NVME_NSHEAD_FAIL_ON_CTRL_LOSS,
&head->flags))
return count;
}
synchronize_srcu(&head->srcu);
if (enable)
kblockd_schedule_work(&head->requeue_work);
return count;
}
DEVICE_ATTR_RW(fail_on_ctrl_loss);
static ssize_t multipath_failover_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
+4
View File
@@ -590,6 +590,7 @@ struct nvme_ns_head {
#define NVME_NSHEAD_DISK_LIVE 0
#define NVME_NSHEAD_QUEUE_IF_NO_PATH 1
#define NVME_NSHEAD_CDEV_LIVE 2
#define NVME_NSHEAD_FAIL_ON_CTRL_LOSS 3
struct nvme_ns __rcu_guarded *current_path[];
#endif
};
@@ -1096,11 +1097,14 @@ extern struct device_attribute dev_attr_ana_grpid;
extern struct device_attribute dev_attr_ana_state;
extern struct device_attribute dev_attr_queue_depth;
extern struct device_attribute dev_attr_numa_nodes;
extern struct device_attribute dev_attr_path_state;
extern struct device_attribute dev_attr_delayed_removal_secs;
extern struct device_attribute dev_attr_fail_on_ctrl_loss;
extern struct device_attribute dev_attr_multipath_failover_count;
extern struct device_attribute dev_attr_io_requeue_no_usable_path_count;
extern struct device_attribute dev_attr_io_fail_no_available_path_count;
extern struct device_attribute subsys_attr_iopolicy;
extern struct device_attribute subsys_attr_iopolicies;
static inline bool nvme_disk_is_ns_head(struct gendisk *disk)
{
+1 -1
View File
@@ -3747,7 +3747,7 @@ static struct nvme_dev *nvme_pci_alloc_dev(struct pci_dev *pdev,
*/
dev->ctrl.max_hw_sectors = min_t(u32,
NVME_MAX_BYTES >> SECTOR_SHIFT,
dma_max_opt_mapping_size(&pdev->dev) >> SECTOR_SHIFT);
dma_max_mapping_size(&pdev->dev) >> SECTOR_SHIFT);
dev->ctrl.max_segments = NVME_MAX_SEGS;
dev->ctrl.max_integrity_segments = 1;
return dev;
+1
View File
@@ -1748,6 +1748,7 @@ static void nvme_rdma_process_nvme_rsp(struct nvme_rdma_queue *queue,
"Bogus remote invalidation for rkey %#x\n",
req->mr ? req->mr->rkey : 0);
nvme_rdma_error_recovery(queue->ctrl);
return;
}
} else if (req->mr) {
int ret;
+13 -10
View File
@@ -263,7 +263,9 @@ static struct attribute *nvme_ns_attrs[] = {
&dev_attr_ana_state.attr,
&dev_attr_queue_depth.attr,
&dev_attr_numa_nodes.attr,
&dev_attr_path_state.attr,
&dev_attr_delayed_removal_secs.attr,
&dev_attr_fail_on_ctrl_loss.attr,
#endif
&dev_attr_io_passthru_err_log_enabled.attr,
NULL,
@@ -296,11 +298,13 @@ static umode_t nvme_ns_attrs_are_visible(struct kobject *kobj,
if (!nvme_ctrl_use_ana(nvme_get_ns_from_dev(dev)->ctrl))
return 0;
}
if (a == &dev_attr_queue_depth.attr || a == &dev_attr_numa_nodes.attr) {
if (a == &dev_attr_queue_depth.attr || a == &dev_attr_numa_nodes.attr ||
a == &dev_attr_path_state.attr) {
if (nvme_disk_is_ns_head(dev_to_disk(dev)))
return 0;
}
if (a == &dev_attr_delayed_removal_secs.attr) {
if (a == &dev_attr_delayed_removal_secs.attr ||
a == &dev_attr_fail_on_ctrl_loss.attr) {
struct gendisk *disk = dev_to_disk(dev);
if (!nvme_disk_is_ns_head(disk))
@@ -809,7 +813,7 @@ static DEVICE_ATTR(io_timeout, S_IRUGO | S_IWUSR,
nvme_io_timeout_show, nvme_io_timeout_store);
#ifdef CONFIG_NVME_HOST_AUTH
static ssize_t nvme_ctrl_dhchap_secret_show(struct device *dev,
static ssize_t dhchap_secret_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct nvme_ctrl *ctrl = dev_get_drvdata(dev);
@@ -820,7 +824,7 @@ static ssize_t nvme_ctrl_dhchap_secret_show(struct device *dev,
return sysfs_emit(buf, "%s\n", opts->dhchap_secret);
}
static ssize_t nvme_ctrl_dhchap_secret_store(struct device *dev,
static ssize_t dhchap_secret_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct nvme_ctrl *ctrl = dev_get_drvdata(dev);
@@ -864,10 +868,9 @@ static ssize_t nvme_ctrl_dhchap_secret_store(struct device *dev,
return count;
}
static DEVICE_ATTR(dhchap_secret, S_IRUGO | S_IWUSR,
nvme_ctrl_dhchap_secret_show, nvme_ctrl_dhchap_secret_store);
static DEVICE_ATTR_ADMIN_RW(dhchap_secret);
static ssize_t nvme_ctrl_dhchap_ctrl_secret_show(struct device *dev,
static ssize_t dhchap_ctrl_secret_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct nvme_ctrl *ctrl = dev_get_drvdata(dev);
@@ -878,7 +881,7 @@ static ssize_t nvme_ctrl_dhchap_ctrl_secret_show(struct device *dev,
return sysfs_emit(buf, "%s\n", opts->dhchap_ctrl_secret);
}
static ssize_t nvme_ctrl_dhchap_ctrl_secret_store(struct device *dev,
static ssize_t dhchap_ctrl_secret_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct nvme_ctrl *ctrl = dev_get_drvdata(dev);
@@ -922,8 +925,7 @@ static ssize_t nvme_ctrl_dhchap_ctrl_secret_store(struct device *dev,
return count;
}
static DEVICE_ATTR(dhchap_ctrl_secret, S_IRUGO | S_IWUSR,
nvme_ctrl_dhchap_ctrl_secret_show, nvme_ctrl_dhchap_ctrl_secret_store);
static DEVICE_ATTR_ADMIN_RW(dhchap_ctrl_secret);
#endif
static struct attribute *nvme_dev_attrs[] = {
@@ -1298,6 +1300,7 @@ static struct attribute *nvme_subsys_attrs[] = {
&subsys_attr_subsystype.attr,
#ifdef CONFIG_NVME_MULTIPATH
&subsys_attr_iopolicy.attr,
&subsys_attr_iopolicies.attr,
#endif
NULL,
};
+96 -43
View File
@@ -7,6 +7,7 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/async.h>
#include <linux/err.h>
#include <linux/crc32.h>
#include <linux/nvme-tcp.h>
@@ -54,7 +55,8 @@ MODULE_PARM_DESC(tls_handshake_timeout,
"nvme TLS handshake timeout in seconds (default 10)");
#endif
static atomic_t nvme_tcp_cpu_queues[NR_CPUS];
static int nvme_tcp_cpu_queues[NR_CPUS];
static DEFINE_SPINLOCK(nvme_tcp_cpu_queues_lock);
enum nvme_tcp_send_state {
NVME_TCP_SEND_CMD_PDU = 0,
@@ -149,6 +151,12 @@ struct nvme_tcp_queue {
static DEFINE_MUTEX(nvme_tcp_ctrl_mutex);
static LIST_HEAD_GUARDED(nvme_tcp_ctrl_list, nvme_tcp_ctrl_mutex);
struct nvme_tcp_queue_setup_ctx {
struct nvme_ctrl *ctrl;
int qid;
int *err;
};
struct nvme_tcp_ctrl {
/* read only in the hot path */
struct nvme_tcp_queue *queues;
@@ -166,6 +174,7 @@ struct nvme_tcp_ctrl {
struct delayed_work connect_work;
struct nvme_tcp_request async_req;
u32 io_queues[HCTX_MAX_TYPES];
struct net *net;
};
static struct workqueue_struct *nvme_tcp_wq;
@@ -1725,9 +1734,10 @@ static void nvme_tcp_set_queue_io_cpu(struct nvme_tcp_queue *queue)
goto out;
/* Search for the least used cpu from the mq_map */
spin_lock(&nvme_tcp_cpu_queues_lock);
io_cpu = WORK_CPU_UNBOUND;
for_each_online_cpu(cpu) {
int num_queues = atomic_read(&nvme_tcp_cpu_queues[cpu]);
int num_queues = nvme_tcp_cpu_queues[cpu];
if (mq_map[cpu] != qid)
continue;
@@ -1738,9 +1748,10 @@ static void nvme_tcp_set_queue_io_cpu(struct nvme_tcp_queue *queue)
}
if (io_cpu != WORK_CPU_UNBOUND) {
queue->io_cpu = io_cpu;
atomic_inc(&nvme_tcp_cpu_queues[io_cpu]);
nvme_tcp_cpu_queues[io_cpu]++;
set_bit(NVME_TCP_Q_IO_CPU_SET, &queue->flags);
}
spin_unlock(&nvme_tcp_cpu_queues_lock);
out:
dev_dbg(ctrl->ctrl.device, "queue %d: using cpu %d\n",
qid, queue->io_cpu);
@@ -1853,7 +1864,7 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid,
queue->cmnd_capsule_len = sizeof(struct nvme_command) +
NVME_TCP_ADMIN_CCSZ;
ret = sock_create_kern(current->nsproxy->net_ns,
ret = sock_create_kern(ctrl->net,
ctrl->addr.ss_family, SOCK_STREAM,
IPPROTO_TCP, &queue->sock);
if (ret) {
@@ -2009,8 +2020,11 @@ static void nvme_tcp_stop_queue_nowait(struct nvme_ctrl *nctrl, int qid)
if (!test_bit(NVME_TCP_Q_ALLOCATED, &queue->flags))
return;
if (test_and_clear_bit(NVME_TCP_Q_IO_CPU_SET, &queue->flags))
atomic_dec(&nvme_tcp_cpu_queues[queue->io_cpu]);
if (test_and_clear_bit(NVME_TCP_Q_IO_CPU_SET, &queue->flags)) {
spin_lock(&nvme_tcp_cpu_queues_lock);
nvme_tcp_cpu_queues[queue->io_cpu]--;
spin_unlock(&nvme_tcp_cpu_queues_lock);
}
mutex_lock(&queue->queue_lock);
if (test_and_clear_bit(NVME_TCP_Q_LIVE, &queue->flags))
@@ -2117,25 +2131,6 @@ static void nvme_tcp_stop_io_queues(struct nvme_ctrl *ctrl)
nvme_tcp_wait_queue(ctrl, i);
}
static int nvme_tcp_start_io_queues(struct nvme_ctrl *ctrl,
int first, int last)
{
int i, ret;
for (i = first; i < last; i++) {
ret = nvme_tcp_start_queue(ctrl, i);
if (ret)
goto out_stop_queues;
}
return 0;
out_stop_queues:
for (i--; i >= first; i--)
nvme_tcp_stop_queue(ctrl, i);
return ret;
}
static int nvme_tcp_alloc_admin_queue(struct nvme_ctrl *ctrl)
{
int ret;
@@ -2170,10 +2165,8 @@ out_free_queue:
return ret;
}
static int __nvme_tcp_alloc_io_queues(struct nvme_ctrl *ctrl)
static int nvme_tcp_tls_check_psk(struct nvme_ctrl *ctrl)
{
int i, ret;
if (nvme_tcp_tls_configured(ctrl)) {
if (ctrl->opts->concat) {
/*
@@ -2195,23 +2188,75 @@ static int __nvme_tcp_alloc_io_queues(struct nvme_ctrl *ctrl)
}
}
for (i = 1; i < ctrl->queue_count; i++) {
ret = nvme_tcp_alloc_queue(ctrl, i,
ctrl->tls_pskid);
if (ret)
goto out_free_queues;
return 0;
}
static void nvme_tcp_setup_queue_async(void *data, async_cookie_t cookie)
{
struct nvme_tcp_queue_setup_ctx *ctx = data;
struct nvme_ctrl *ctrl = ctx->ctrl;
int ret;
ret = nvme_tcp_alloc_queue(ctrl, ctx->qid, ctrl->tls_pskid);
if (ret)
goto out_err;
ret = nvme_tcp_start_queue(ctrl, ctx->qid);
if (ret)
goto out_err;
return;
out_err:
WRITE_ONCE(*ctx->err, ret);
}
static int nvme_tcp_setup_io_queues(struct nvme_ctrl *ctrl, unsigned int first,
unsigned int last)
{
ASYNC_DOMAIN_EXCLUSIVE(queue_domain);
struct nvme_tcp_queue_setup_ctx *ctxs;
int nr_queues = last - first;
int err = 0, i, ret;
if (first >= last)
return 0;
ctxs = kmalloc_objs(*ctxs, nr_queues);
if (!ctxs)
return -ENOMEM;
for (i = 0; i < nr_queues; i++) {
ctxs[i].ctrl = ctrl;
ctxs[i].qid = first + i;
ctxs[i].err = &err;
async_schedule_domain(nvme_tcp_setup_queue_async, &ctxs[i],
&queue_domain);
}
async_synchronize_full_domain(&queue_domain);
kfree(ctxs);
ret = READ_ONCE(err);
if (ret)
goto out_free_queues;
return 0;
out_free_queues:
for (i--; i >= 1; i--)
nvme_tcp_free_queue(ctrl, i);
for (i = first; i < last; i++) {
struct nvme_tcp_queue *queue = &to_tcp_ctrl(ctrl)->queues[i];
if (test_bit(NVME_TCP_Q_LIVE, &queue->flags))
nvme_tcp_stop_queue(ctrl, i);
if (test_bit(NVME_TCP_Q_ALLOCATED, &queue->flags))
nvme_tcp_free_queue(ctrl, i);
}
return ret;
}
static int nvme_tcp_alloc_io_queues(struct nvme_ctrl *ctrl)
static int nvme_tcp_set_io_queue_count(struct nvme_ctrl *ctrl)
{
unsigned int nr_io_queues;
int ret;
@@ -2233,14 +2278,18 @@ static int nvme_tcp_alloc_io_queues(struct nvme_ctrl *ctrl)
nvmf_set_io_queues(ctrl->opts, nr_io_queues,
to_tcp_ctrl(ctrl)->io_queues);
return __nvme_tcp_alloc_io_queues(ctrl);
return 0;
}
static int nvme_tcp_configure_io_queues(struct nvme_ctrl *ctrl, bool new)
{
int ret, nr_queues;
ret = nvme_tcp_alloc_io_queues(ctrl);
ret = nvme_tcp_set_io_queue_count(ctrl);
if (ret)
return ret;
ret = nvme_tcp_tls_check_psk(ctrl);
if (ret)
return ret;
@@ -2254,12 +2303,12 @@ static int nvme_tcp_configure_io_queues(struct nvme_ctrl *ctrl, bool new)
}
/*
* Only start IO queues for which we have allocated the tagset
* Only setup IO queues for which we have allocated the tagset
* and limited it to the available queues. On reconnects, the
* queue number might have changed.
*/
nr_queues = min(ctrl->tagset->nr_hw_queues + 1, ctrl->queue_count);
ret = nvme_tcp_start_io_queues(ctrl, 1, nr_queues);
ret = nvme_tcp_setup_io_queues(ctrl, 1, nr_queues);
if (ret)
goto out_cleanup_connect_q;
@@ -2283,9 +2332,9 @@ static int nvme_tcp_configure_io_queues(struct nvme_ctrl *ctrl, bool new)
/*
* If the number of queues has increased (reconnect case)
* start all new queues now.
* setup all new queues now.
*/
ret = nvme_tcp_start_io_queues(ctrl, nr_queues,
ret = nvme_tcp_setup_io_queues(ctrl, nr_queues,
ctrl->tagset->nr_hw_queues + 1);
if (ret)
goto out_wait_freeze_timed_out;
@@ -2637,6 +2686,7 @@ static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl)
nvmf_free_options(nctrl->opts);
free_ctrl:
put_net(ctrl->net);
kfree(ctrl->queues);
kfree(ctrl);
}
@@ -3035,12 +3085,15 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev,
goto out_free_ctrl;
}
ctrl->net = get_net(current->nsproxy->net_ns);
ret = nvme_init_ctrl(&ctrl->ctrl, dev, &nvme_tcp_ctrl_ops, 0);
if (ret)
goto out_kfree_queues;
return ctrl;
out_kfree_queues:
put_net(ctrl->net);
kfree(ctrl->queues);
out_free_ctrl:
kfree(ctrl);
@@ -3126,7 +3179,7 @@ static int __init nvme_tcp_init_module(void)
return -ENOMEM;
for_each_possible_cpu(cpu)
atomic_set(&nvme_tcp_cpu_queues[cpu], 0);
nvme_tcp_cpu_queues[cpu] = 0;
nvmf_register_transport(&nvme_tcp_transport);
return 0;
+3 -1
View File
@@ -696,7 +696,9 @@ static void nvmet_execute_identify_ctrl(struct nvmet_req *req)
/* XXX: figure out what to do about RTD3R/RTD3 */
id->oaes = cpu_to_le32(NVMET_AEN_CFG_OPTIONAL);
ctratt = NVME_CTRL_ATTR_HID_128_BIT | NVME_CTRL_ATTR_TBKAS;
ctratt = NVME_CTRL_ATTR_HID_128_BIT;
if (ctrl->tbkas)
ctratt |= NVME_CTRL_ATTR_TBKAS;
if (nvmet_is_pci_ctrl(ctrl))
ctratt |= NVME_CTRL_ATTR_RHII;
id->ctratt = cpu_to_le32(ctratt);
+1
View File
@@ -229,6 +229,7 @@ out_unlock:
void nvmet_auth_sq_free(struct nvmet_sq *sq)
{
sq->auth_expiry_active = false;
cancel_delayed_work(&sq->auth_expired_work);
kfree(sq->dhchap_c1);
sq->dhchap_c1 = NULL;
+107 -12
View File
@@ -905,21 +905,34 @@ static ssize_t nvmet_passthru_device_path_show(struct config_item *item,
char *page)
{
struct nvmet_subsys *subsys = to_subsys(item->ci_parent);
ssize_t ret;
return snprintf(page, PAGE_SIZE, "%s\n", subsys->passthru_ctrl_path);
mutex_lock(&subsys->lock);
ret = snprintf(page, PAGE_SIZE, "%s\n",
subsys->passthru && subsys->passthru->ctrl_path ?
subsys->passthru->ctrl_path : "");
mutex_unlock(&subsys->lock);
return ret;
}
static ssize_t nvmet_passthru_device_path_store(struct config_item *item,
const char *page, size_t count)
{
struct nvmet_subsys *subsys = to_subsys(item->ci_parent);
struct nvmet_passthru *passthru;
size_t len;
int ret;
mutex_lock(&subsys->lock);
passthru = nvmet_subsys_passthru(subsys);
if (!passthru) {
ret = -ENOMEM;
goto out_unlock;
}
ret = -EBUSY;
if (subsys->passthru_ctrl)
if (passthru->ctrl)
goto out_unlock;
ret = -EINVAL;
@@ -927,10 +940,10 @@ static ssize_t nvmet_passthru_device_path_store(struct config_item *item,
if (!len)
goto out_unlock;
kfree(subsys->passthru_ctrl_path);
kfree(passthru->ctrl_path);
ret = -ENOMEM;
subsys->passthru_ctrl_path = kstrndup(page, len, GFP_KERNEL);
if (!subsys->passthru_ctrl_path)
passthru->ctrl_path = kstrndup(page, len, GFP_KERNEL);
if (!passthru->ctrl_path)
goto out_unlock;
mutex_unlock(&subsys->lock);
@@ -946,8 +959,13 @@ static ssize_t nvmet_passthru_enable_show(struct config_item *item,
char *page)
{
struct nvmet_subsys *subsys = to_subsys(item->ci_parent);
ssize_t ret;
return sprintf(page, "%d\n", subsys->passthru_ctrl ? 1 : 0);
mutex_lock(&subsys->lock);
ret = sprintf(page, "%d\n",
subsys->passthru && subsys->passthru->ctrl ? 1 : 0);
mutex_unlock(&subsys->lock);
return ret;
}
static ssize_t nvmet_passthru_enable_store(struct config_item *item,
@@ -972,18 +990,34 @@ CONFIGFS_ATTR(nvmet_passthru_, enable);
static ssize_t nvmet_passthru_admin_timeout_show(struct config_item *item,
char *page)
{
return sprintf(page, "%u\n", to_subsys(item->ci_parent)->admin_timeout);
struct nvmet_subsys *subsys = to_subsys(item->ci_parent);
ssize_t ret;
mutex_lock(&subsys->lock);
ret = sprintf(page, "%u\n", subsys->passthru ?
subsys->passthru->admin_timeout : 0);
mutex_unlock(&subsys->lock);
return ret;
}
static ssize_t nvmet_passthru_admin_timeout_store(struct config_item *item,
const char *page, size_t count)
{
struct nvmet_subsys *subsys = to_subsys(item->ci_parent);
struct nvmet_passthru *passthru;
unsigned int timeout;
if (kstrtouint(page, 0, &timeout))
return -EINVAL;
subsys->admin_timeout = timeout;
mutex_lock(&subsys->lock);
passthru = nvmet_subsys_passthru(subsys);
if (!passthru) {
mutex_unlock(&subsys->lock);
return -ENOMEM;
}
passthru->admin_timeout = timeout;
mutex_unlock(&subsys->lock);
return count;
}
CONFIGFS_ATTR(nvmet_passthru_, admin_timeout);
@@ -991,18 +1025,34 @@ CONFIGFS_ATTR(nvmet_passthru_, admin_timeout);
static ssize_t nvmet_passthru_io_timeout_show(struct config_item *item,
char *page)
{
return sprintf(page, "%u\n", to_subsys(item->ci_parent)->io_timeout);
struct nvmet_subsys *subsys = to_subsys(item->ci_parent);
ssize_t ret;
mutex_lock(&subsys->lock);
ret = sprintf(page, "%u\n",
subsys->passthru ? subsys->passthru->io_timeout : 0);
mutex_unlock(&subsys->lock);
return ret;
}
static ssize_t nvmet_passthru_io_timeout_store(struct config_item *item,
const char *page, size_t count)
{
struct nvmet_subsys *subsys = to_subsys(item->ci_parent);
struct nvmet_passthru *passthru;
unsigned int timeout;
if (kstrtouint(page, 0, &timeout))
return -EINVAL;
subsys->io_timeout = timeout;
mutex_lock(&subsys->lock);
passthru = nvmet_subsys_passthru(subsys);
if (!passthru) {
mutex_unlock(&subsys->lock);
return -ENOMEM;
}
passthru->io_timeout = timeout;
mutex_unlock(&subsys->lock);
return count;
}
CONFIGFS_ATTR(nvmet_passthru_, io_timeout);
@@ -1010,18 +1060,34 @@ CONFIGFS_ATTR(nvmet_passthru_, io_timeout);
static ssize_t nvmet_passthru_clear_ids_show(struct config_item *item,
char *page)
{
return sprintf(page, "%u\n", to_subsys(item->ci_parent)->clear_ids);
struct nvmet_subsys *subsys = to_subsys(item->ci_parent);
ssize_t ret;
mutex_lock(&subsys->lock);
ret = sprintf(page, "%u\n",
subsys->passthru ? subsys->passthru->clear_ids : 0);
mutex_unlock(&subsys->lock);
return ret;
}
static ssize_t nvmet_passthru_clear_ids_store(struct config_item *item,
const char *page, size_t count)
{
struct nvmet_subsys *subsys = to_subsys(item->ci_parent);
struct nvmet_passthru *passthru;
unsigned int clear_ids;
if (kstrtouint(page, 0, &clear_ids))
return -EINVAL;
subsys->clear_ids = clear_ids;
mutex_lock(&subsys->lock);
passthru = nvmet_subsys_passthru(subsys);
if (!passthru) {
mutex_unlock(&subsys->lock);
return -ENOMEM;
}
passthru->clear_ids = clear_ids;
mutex_unlock(&subsys->lock);
return count;
}
CONFIGFS_ATTR(nvmet_passthru_, clear_ids);
@@ -1372,6 +1438,34 @@ static ssize_t nvmet_subsys_attr_serial_store(struct config_item *item,
}
CONFIGFS_ATTR(nvmet_subsys_, attr_serial);
static ssize_t nvmet_subsys_attr_tbkas_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%d\n", to_subsys(item)->tbkas);
}
static ssize_t nvmet_subsys_attr_tbkas_store(struct config_item *item,
const char *page, size_t count)
{
struct nvmet_subsys *subsys = to_subsys(item);
struct nvmet_ctrl *ctrl;
bool tbkas;
if (kstrtobool(page, &tbkas))
return -EINVAL;
down_write(&nvmet_config_sem);
subsys->tbkas = tbkas;
/* Force reconnect */
list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry)
ctrl->ops->delete_ctrl(ctrl);
up_write(&nvmet_config_sem);
return count;
}
CONFIGFS_ATTR(nvmet_subsys_, attr_tbkas);
static ssize_t nvmet_subsys_attr_cntlid_min_show(struct config_item *item,
char *page)
{
@@ -1709,6 +1803,7 @@ static struct configfs_attribute *nvmet_subsys_attrs[] = {
&nvmet_subsys_attr_attr_qid_max,
&nvmet_subsys_attr_attr_ieee_oui,
&nvmet_subsys_attr_attr_firmware,
&nvmet_subsys_attr_attr_tbkas,
#ifdef CONFIG_BLK_DEV_INTEGRITY
&nvmet_subsys_attr_attr_pi_enable,
#endif
+16 -3
View File
@@ -819,6 +819,8 @@ static void __nvmet_req_complete(struct nvmet_req *req, u16 status)
nvmet_pr_put_ns_pc_ref(pc_ref);
if (ns)
nvmet_put_namespace(ns);
nvmet_put_passthru_ref(req);
}
void nvmet_req_complete(struct nvmet_req *req, u16 status)
@@ -1195,6 +1197,7 @@ bool nvmet_req_init(struct nvmet_req *req, struct nvmet_sq *sq,
req->error_loc = NVMET_NO_ERROR_LOC;
req->error_slba = 0;
req->pc_ref = NULL;
req->p.ref_held = false;
/* no support for fused commands yet */
if (unlikely(flags & (NVME_CMD_FUSE_FIRST | NVME_CMD_FUSE_SECOND))) {
@@ -1235,7 +1238,7 @@ bool nvmet_req_init(struct nvmet_req *req, struct nvmet_sq *sq,
goto fail;
}
if (sq->ctrl)
if (sq->ctrl && sq->ctrl->tbkas)
sq->ctrl->reset_tbkas = true;
return true;
@@ -1645,8 +1648,16 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args)
#ifdef CONFIG_NVME_TARGET_PASSTHRU
/* By default, set loop targets to clear IDS by default */
if (ctrl->port->disc_addr.trtype == NVMF_TRTYPE_LOOP)
subsys->clear_ids = 1;
if (ctrl->port->disc_addr.trtype == NVMF_TRTYPE_LOOP) {
mutex_lock(&subsys->lock);
subsys->passthru = nvmet_subsys_passthru(subsys);
if (!subsys->passthru) {
mutex_unlock(&subsys->lock);
goto out_free_ctrl;
}
subsys->passthru->clear_ids = 1;
mutex_unlock(&subsys->lock);
}
#endif
INIT_WORK(&ctrl->async_event_work, nvmet_async_event_work);
@@ -1687,6 +1698,7 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args)
mutex_lock(&subsys->lock);
ctrl->max_qid = subsys->max_qid;
ctrl->tbkas = subsys->tbkas;
ctrl->sqs = kzalloc_objs(struct nvmet_sq *, ctrl->max_qid + 1);
if (!ctrl->sqs)
@@ -1872,6 +1884,7 @@ struct nvmet_subsys *nvmet_subsys_alloc(const char *subsysnqn,
}
subsys->ieee_oui = 0;
subsys->tbkas = true;
subsys->firmware_rev = kstrndup(UTS_RELEASE, NVMET_FR_MAX_SIZE, GFP_KERNEL);
if (!subsys->firmware_rev) {
+34 -4
View File
@@ -6,6 +6,7 @@
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/blkdev.h>
#include <linux/jiffies.h>
#include <linux/random.h>
#include <linux/nvme-auth.h>
#include <crypto/kpp.h>
@@ -17,16 +18,27 @@ static void nvmet_auth_expired_work(struct work_struct *work)
struct nvmet_sq *sq = container_of(to_delayed_work(work),
struct nvmet_sq, auth_expired_work);
mutex_lock(&sq->auth_lock);
/* A command may have cancelled or rearmed this timeout. */
if (!sq->auth_expiry_active ||
delayed_work_pending(&sq->auth_expired_work))
goto out_unlock;
sq->auth_expiry_active = false;
pr_debug("%s: ctrl %d qid %d transaction %u expired, resetting\n",
__func__, sq->ctrl->cntlid, sq->qid, sq->dhchap_tid);
sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
sq->dhchap_tid = -1;
out_unlock:
mutex_unlock(&sq->auth_lock);
}
void nvmet_auth_sq_init(struct nvmet_sq *sq)
{
/* Initialize in-band authentication */
mutex_init(&sq->auth_lock);
INIT_DELAYED_WORK(&sq->auth_expired_work, nvmet_auth_expired_work);
sq->auth_expiry_active = false;
sq->authenticated = false;
sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
}
@@ -256,6 +268,8 @@ void nvmet_execute_auth_send(struct nvmet_req *req)
u16 status = 0;
u8 dhchap_status;
mutex_lock(&req->sq->auth_lock);
if (req->cmd->auth_send.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc =
@@ -281,10 +295,18 @@ void nvmet_execute_auth_send(struct nvmet_req *req)
offsetof(struct nvmf_auth_send_command, tl);
goto done;
}
if (!nvmet_check_transfer_len(req, tl)) {
if (unlikely(tl != req->transfer_len)) {
pr_debug("%s: transfer length mismatch (%u)\n", __func__, tl);
mutex_unlock(&req->sq->auth_lock);
nvmet_check_transfer_len(req, tl);
return;
}
if (tl < offsetof(struct nvmf_auth_dhchap_success2_data, rsvd1)) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc =
offsetof(struct nvmf_auth_send_command, tl);
goto done;
}
d = kmalloc(tl, GFP_KERNEL);
if (!d) {
@@ -406,10 +428,12 @@ done:
status, req->error_loc);
if (req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2 &&
req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_FAILURE2) {
unsigned long auth_expire_secs = ctrl->kato ? ctrl->kato : 120;
unsigned long auth_expire = min_t(u64, MAX_JIFFY_OFFSET,
(u64)(ctrl->kato ? ctrl->kato : 120) * HZ);
req->sq->auth_expiry_active = true;
mod_delayed_work(system_percpu_wq, &req->sq->auth_expired_work,
auth_expire_secs * HZ);
auth_expire);
goto complete;
}
/* Final states, clear up variables */
@@ -418,6 +442,7 @@ done:
nvmet_ctrl_fatal_error(ctrl);
complete:
mutex_unlock(&req->sq->auth_lock);
nvmet_req_complete(req, status);
}
@@ -536,6 +561,8 @@ void nvmet_execute_auth_receive(struct nvmet_req *req)
u32 al;
u16 status = 0;
mutex_lock(&req->sq->auth_lock);
if (req->cmd->auth_receive.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc =
@@ -561,8 +588,10 @@ void nvmet_execute_auth_receive(struct nvmet_req *req)
offsetof(struct nvmf_auth_receive_command, al);
goto done;
}
if (!nvmet_check_transfer_len(req, al)) {
if (unlikely(al != req->transfer_len)) {
pr_debug("%s: transfer length mismatch (%u)\n", __func__, al);
mutex_unlock(&req->sq->auth_lock);
nvmet_check_transfer_len(req, al);
return;
}
@@ -621,5 +650,6 @@ done:
nvmet_auth_sq_free(req->sq);
nvmet_ctrl_fatal_error(ctrl);
}
mutex_unlock(&req->sq->auth_lock);
nvmet_req_complete(req, status);
}
+4
View File
@@ -14,6 +14,10 @@ static void nvmet_execute_prop_set(struct nvmet_req *req)
if (!nvmet_check_transfer_len(req, 0))
return;
if (!nvmet_check_auth_status(req)) {
status = NVME_SC_AUTH_REQUIRED | NVME_STATUS_DNR;
goto out;
}
if (req->cmd->prop_set.attrib & 1) {
req->error_loc =
+9 -6
View File
@@ -1567,8 +1567,14 @@ nvmet_fc_delete_ctrl(struct nvmet_ctrl *ctrl)
list_for_each_entry_rcu(assoc, &tgtport->assoc_list, a_list) {
queue = assoc->queues[0];
if (queue && queue->nvme_sq.ctrl == ctrl) {
if (nvmet_fc_tgt_a_get(assoc))
found_ctrl = true;
/*
* No assoc reference here: we're called with
* subsys->lock held, and if del_work beat us,
* our put would free the ctrl under that lock.
* RCU keeps the assoc alive until unlock.
*/
nvmet_fc_schedule_delete_assoc(assoc);
found_ctrl = true;
break;
}
}
@@ -1576,11 +1582,8 @@ nvmet_fc_delete_ctrl(struct nvmet_ctrl *ctrl)
nvmet_fc_tgtport_put(tgtport);
if (found_ctrl) {
nvmet_fc_schedule_delete_assoc(assoc);
nvmet_fc_tgt_a_put(assoc);
if (found_ctrl)
return;
}
spin_lock_irqsave(&nvmet_fc_tgtlock, flags);
}
+2 -2
View File
@@ -133,8 +133,8 @@ static bool nvmet_file_execute_io(struct nvmet_req *req, int ki_flags)
pos = le64_to_cpu(req->cmd->rw.slba) << req->ns->blksize_shift;
if (unlikely(pos + req->transfer_len > req->ns->size)) {
nvmet_req_complete(req, errno_to_nvme_status(req, -ENOSPC));
return true;
ret = -ENOSPC;
goto complete;
}
memset(&req->f.iocb, 0, sizeof(struct kiocb));
+64 -6
View File
@@ -161,8 +161,10 @@ struct nvmet_sq {
u32 sqhd;
bool sqhd_disabled;
#ifdef CONFIG_NVME_TARGET_AUTH
struct mutex auth_lock;
bool authenticated;
struct delayed_work auth_expired_work;
bool auth_expiry_active;
u16 dhchap_tid;
u8 sc_c;
u8 dhchap_status;
@@ -272,6 +274,7 @@ struct nvmet_ctrl {
u16 cntlid;
u16 max_qid;
u32 kato;
bool tbkas;
struct nvmet_port *port;
@@ -319,6 +322,19 @@ struct nvmet_ctrl {
struct nvmet_pr_log_mgr pr_log_mgr;
};
struct nvmet_passthru {
struct percpu_ref ref;
struct completion disable_done;
#define NVMET_PASSTHRU_ENABLED 0
unsigned long flags;
struct nvme_ctrl *ctrl;
char *ctrl_path;
unsigned int admin_timeout;
unsigned int io_timeout;
unsigned int clear_ids;
};
struct nvmet_subsys {
enum nvme_subsys_type type;
@@ -345,6 +361,7 @@ struct nvmet_subsys {
bool subsys_discovered;
char *subsysnqn;
bool pi_support;
bool tbkas;
struct config_group group;
@@ -358,12 +375,8 @@ struct nvmet_subsys {
char *firmware_rev;
#ifdef CONFIG_NVME_TARGET_PASSTHRU
struct nvme_ctrl *passthru_ctrl;
char *passthru_ctrl_path;
struct nvmet_passthru *passthru;
struct config_group passthru_group;
unsigned int admin_timeout;
unsigned int io_timeout;
unsigned int clear_ids;
#endif /* CONFIG_NVME_TARGET_PASSTHRU */
#ifdef CONFIG_BLK_DEV_ZONED
@@ -474,6 +487,7 @@ struct nvmet_req {
struct request *rq;
struct work_struct work;
bool use_workqueue;
bool ref_held;
} p;
#ifdef CONFIG_BLK_DEV_ZONED
struct {
@@ -793,7 +807,39 @@ u16 nvmet_parse_passthru_admin_cmd(struct nvmet_req *req);
u16 nvmet_parse_passthru_io_cmd(struct nvmet_req *req);
static inline bool nvmet_is_passthru_subsys(struct nvmet_subsys *subsys)
{
return subsys->passthru_ctrl;
return subsys->passthru &&
test_bit(NVMET_PASSTHRU_ENABLED, &subsys->passthru->flags);
}
static inline struct nvmet_passthru *nvmet_subsys_passthru(
struct nvmet_subsys *subsys)
{
lockdep_assert_held(&subsys->lock);
if (!subsys->passthru) {
subsys->passthru = kzalloc_obj(*subsys->passthru);
if (!subsys->passthru)
return NULL;
}
return subsys->passthru;
}
static inline bool nvmet_get_passthru_ref(struct nvmet_req *req)
{
if (!percpu_ref_tryget_live(&nvmet_req_subsys(req)->passthru->ref))
return false;
req->p.ref_held = true;
return true;
}
static inline void nvmet_put_passthru_ref(struct nvmet_req *req)
{
if (!req->p.ref_held)
return;
req->p.ref_held = false;
percpu_ref_put(&nvmet_req_subsys(req)->passthru->ref);
}
#else /* CONFIG_NVME_TARGET_PASSTHRU */
static inline void nvmet_passthru_subsys_free(struct nvmet_subsys *subsys)
@@ -814,6 +860,18 @@ static inline bool nvmet_is_passthru_subsys(struct nvmet_subsys *subsys)
{
return NULL;
}
static inline struct nvmet_passthru *nvmet_subsys_passthru(
struct nvmet_subsys *subsys)
{
return NULL;
}
static inline bool nvmet_get_passthru_ref(struct nvmet_req *req)
{
return false;
}
static inline void nvmet_put_passthru_ref(struct nvmet_req *req)
{
}
#endif /* CONFIG_NVME_TARGET_PASSTHRU */
static inline bool nvmet_is_passthru_req(struct nvmet_req *req)
+81 -22
View File
@@ -26,7 +26,7 @@ void nvmet_passthrough_override_cap(struct nvmet_ctrl *ctrl)
* Multiple command set support can only be declared if the underlying
* controller actually supports it.
*/
if (!nvme_multi_css(ctrl->subsys->passthru_ctrl))
if (!nvme_multi_css(ctrl->subsys->passthru->ctrl))
ctrl->cap &= ~(1ULL << 43);
}
@@ -39,7 +39,7 @@ static u16 nvmet_passthru_override_id_descs(struct nvmet_req *req)
void *data;
u8 csi;
if (!ctrl->subsys->clear_ids)
if (!ctrl->subsys->passthru->clear_ids)
return status;
data = kzalloc(NVME_IDENTIFY_DATA_SIZE, GFP_KERNEL);
@@ -89,7 +89,7 @@ out_free:
static u16 nvmet_passthru_override_id_ctrl(struct nvmet_req *req)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
struct nvme_ctrl *pctrl = ctrl->subsys->passthru_ctrl;
struct nvme_ctrl *pctrl = ctrl->subsys->passthru->ctrl;
u16 status = NVME_SC_SUCCESS;
struct nvme_id_ctrl *id;
unsigned int max_hw_sectors;
@@ -208,7 +208,7 @@ static u16 nvmet_passthru_override_id_ns(struct nvmet_req *req)
*/
id->mc = 0;
if (req->sq->ctrl->subsys->clear_ids) {
if (req->sq->ctrl->subsys->passthru->clear_ids) {
memset(id->nguid, 0, NVME_NIDT_NGUID_LEN);
memset(id->eui64, 0, NVME_NIDT_EUI64_LEN);
}
@@ -305,8 +305,9 @@ out_bio_put:
static void nvmet_passthru_execute_cmd(struct nvmet_req *req)
{
struct nvme_ctrl *ctrl = nvmet_req_subsys(req)->passthru_ctrl;
struct request_queue *q = ctrl->admin_q;
struct nvmet_passthru *passthru;
struct nvme_ctrl *ctrl;
struct request_queue *q;
struct nvme_ns *ns = NULL;
struct request *rq = NULL;
unsigned int timeout;
@@ -314,6 +315,15 @@ static void nvmet_passthru_execute_cmd(struct nvmet_req *req)
u16 status;
int ret;
if (!nvmet_get_passthru_ref(req)) {
status = NVME_SC_INTERNAL | NVME_STATUS_DNR;
goto out;
}
passthru = nvmet_req_subsys(req)->passthru;
ctrl = passthru->ctrl;
q = ctrl->admin_q;
if (likely(req->sq->qid != 0)) {
u32 nsid = le32_to_cpu(req->cmd->common.nsid);
@@ -325,9 +335,9 @@ static void nvmet_passthru_execute_cmd(struct nvmet_req *req)
}
q = ns->queue;
timeout = nvmet_req_subsys(req)->io_timeout;
timeout = passthru->io_timeout;
} else {
timeout = nvmet_req_subsys(req)->admin_timeout;
timeout = passthru->admin_timeout;
}
rq = blk_mq_alloc_request(q, nvme_req_op(req->cmd), 0);
@@ -386,11 +396,17 @@ out:
*/
static void nvmet_passthru_set_host_behaviour(struct nvmet_req *req)
{
struct nvme_ctrl *ctrl = nvmet_req_subsys(req)->passthru_ctrl;
struct nvme_ctrl *ctrl;
struct nvme_feat_host_behavior *host;
u16 status = NVME_SC_INTERNAL;
int ret;
if (!nvmet_get_passthru_ref(req)) {
status |= NVME_STATUS_DNR;
goto out_complete_req;
}
ctrl = nvmet_req_subsys(req)->passthru->ctrl;
host = kzalloc(sizeof(*host) * 2, GFP_KERNEL);
if (!host)
goto out_complete_req;
@@ -584,17 +600,27 @@ u16 nvmet_parse_passthru_admin_cmd(struct nvmet_req *req)
}
}
static void nvmet_release_passthru_ctrl(struct percpu_ref *ref)
{
struct nvmet_passthru *passthru = container_of(ref,
struct nvmet_passthru, ref);
complete(&passthru->disable_done);
}
int nvmet_passthru_ctrl_enable(struct nvmet_subsys *subsys)
{
struct nvmet_passthru *passthru;
struct nvme_ctrl *ctrl;
struct file *file;
int ret = -EINVAL;
void *old;
mutex_lock(&subsys->lock);
if (!subsys->passthru_ctrl_path)
passthru = subsys->passthru;
if (!passthru || !passthru->ctrl_path)
goto out_unlock;
if (subsys->passthru_ctrl)
if (passthru->ctrl)
goto out_unlock;
if (subsys->nr_namespaces) {
@@ -602,7 +628,7 @@ int nvmet_passthru_ctrl_enable(struct nvmet_subsys *subsys)
goto out_unlock;
}
file = filp_open(subsys->passthru_ctrl_path, O_RDWR, 0);
file = filp_open(passthru->ctrl_path, O_RDWR, 0);
if (IS_ERR(file)) {
ret = PTR_ERR(file);
goto out_unlock;
@@ -611,7 +637,7 @@ int nvmet_passthru_ctrl_enable(struct nvmet_subsys *subsys)
ctrl = nvme_ctrl_from_file(file);
if (!ctrl) {
pr_err("failed to open nvme controller %s\n",
subsys->passthru_ctrl_path);
passthru->ctrl_path);
goto out_put_file;
}
@@ -626,7 +652,14 @@ int nvmet_passthru_ctrl_enable(struct nvmet_subsys *subsys)
if (old)
goto out_put_file;
subsys->passthru_ctrl = ctrl;
ret = percpu_ref_init(&passthru->ref, nvmet_release_passthru_ctrl,
0, GFP_KERNEL);
if (ret) {
xa_erase(&passthru_subsystems, ctrl->instance);
goto out_put_file;
}
init_completion(&passthru->disable_done);
passthru->ctrl = ctrl;
subsys->ver = ctrl->vs;
if (subsys->ver < NVME_VS(1, 2, 1)) {
@@ -636,7 +669,8 @@ int nvmet_passthru_ctrl_enable(struct nvmet_subsys *subsys)
subsys->ver = NVME_VS(1, 2, 1);
}
nvme_get_ctrl(ctrl);
__module_get(subsys->passthru_ctrl->ops->module);
__module_get(passthru->ctrl->ops->module);
set_bit(NVMET_PASSTHRU_ENABLED, &passthru->flags);
ret = 0;
out_put_file:
@@ -648,12 +682,33 @@ out_unlock:
static void __nvmet_passthru_ctrl_disable(struct nvmet_subsys *subsys)
{
if (subsys->passthru_ctrl) {
xa_erase(&passthru_subsystems, subsys->passthru_ctrl->instance);
module_put(subsys->passthru_ctrl->ops->module);
nvme_put_ctrl(subsys->passthru_ctrl);
}
subsys->passthru_ctrl = NULL;
struct nvmet_passthru *passthru = subsys->passthru;
lockdep_assert_held(&subsys->lock);
if (!passthru)
return;
if (!test_and_clear_bit(NVMET_PASSTHRU_ENABLED, &passthru->flags))
return;
mutex_unlock(&subsys->lock);
/*
* Now new I/Os should not enter passthru hotpath as we cleared the
* enabled flag. Kill percpu reference and wait for in-flight I/Os
* to drain.
*/
percpu_ref_kill(&passthru->ref);
wait_for_completion(&passthru->disable_done);
percpu_ref_exit(&passthru->ref);
mutex_lock(&subsys->lock);
xa_erase(&passthru_subsystems, passthru->ctrl->instance);
module_put(passthru->ctrl->ops->module);
nvme_put_ctrl(passthru->ctrl);
passthru->ctrl = NULL;
subsys->ver = NVMET_DEFAULT_VS;
}
@@ -668,6 +723,10 @@ void nvmet_passthru_subsys_free(struct nvmet_subsys *subsys)
{
mutex_lock(&subsys->lock);
__nvmet_passthru_ctrl_disable(subsys);
if (subsys->passthru) {
kfree(subsys->passthru->ctrl_path);
kfree(subsys->passthru);
subsys->passthru = NULL;
}
mutex_unlock(&subsys->lock);
kfree(subsys->passthru_ctrl_path);
}
+2 -2
View File
@@ -589,11 +589,11 @@ static u16 nvmet_pr_preempt(struct nvmet_req *req,
&ctrl->hostid, abort);
}
if (holder == reg) {
if (holder == reg && prkey == holder->rkey) {
status = nvmet_pr_update_reg_attr(pr, holder,
nvmet_pr_update_holder_rtype, &rtype);
if (!status && original_rtype != rtype)
nvmet_pr_resv_released(pr, &reg->hostid);
nvmet_pr_resv_released(pr, &ctrl->hostid);
return status;
}
+10
View File
@@ -282,6 +282,11 @@ static void nvmet_bdev_zone_zmgmt_recv_work(struct work_struct *w)
if (status)
goto out;
/* Only the zone count and matching descriptors are written below. */
status = nvmet_zero_sgl(req, 0, out_bufsize);
if (status)
goto out;
if (!req_slba_nr_zones) {
status = NVME_SC_SUCCESS;
goto out;
@@ -317,6 +322,11 @@ out:
void nvmet_bdev_execute_zone_mgmt_recv(struct nvmet_req *req)
{
u32 out_bufsize = (le32_to_cpu(req->cmd->zmr.numd) + 1) << 2;
if (!nvmet_check_transfer_len(req, out_bufsize))
return;
INIT_WORK(&req->z.zmgmt_work, nvmet_bdev_zone_zmgmt_recv_work);
queue_work(zbd_wq, &req->z.zmgmt_work);
}
+4
View File
@@ -1304,6 +1304,10 @@ iomap_file_buffered_write(struct kiocb *iocb, struct iov_iter *i,
};
ssize_t ret;
/* Buffered atomic writes are not supported */
if (iocb->ki_flags & IOCB_ATOMIC)
return -EOPNOTSUPP;
if (iocb->ki_flags & IOCB_NOWAIT)
iter.flags |= IOMAP_NOWAIT;
if (iocb->ki_flags & IOCB_DONTCACHE)
-3
View File
@@ -525,8 +525,6 @@ enum hctx_type {
* request_queue.tag_set_list.
* @srcu: Use as lock when type of the request queue is blocking
* (BLK_MQ_F_BLOCKING).
* @tags_srcu: SRCU used to defer freeing of tags page_list to prevent
* use-after-free when iterating tags.
* @update_nr_hwq_lock:
* Synchronize updating nr_hw_queues with add/del disk &
* switching elevator.
@@ -551,7 +549,6 @@ struct blk_mq_tag_set {
struct mutex tag_list_lock;
struct list_head tag_list;
struct srcu_struct *srcu;
struct srcu_struct tags_srcu;
struct rw_semaphore update_nr_hwq_lock;
};
+7 -4
View File
@@ -191,14 +191,17 @@ struct gendisk {
#ifdef CONFIG_BLK_DEV_ZONED
/*
* Zoned block device information. Reads of this information must be
* protected with blk_queue_enter() / blk_queue_exit(). Modifying this
* information is only allowed while no requests are being processed.
* See also blk_mq_freeze_queue() and blk_mq_unfreeze_queue().
* protected with blk_queue_enter() / blk_queue_exit() or by holding a
* lock on zone_revalidate_mutex. blk_revalidate_disk_zones() may modify
* this information while no requests are being processed (disk queue
* frozen with blk_mq_freeze_queue()) and while holding a lock on
* zone_revalidate_mutex.
*/
struct mutex zone_revalidate_mutex;
unsigned int nr_zones;
unsigned int zone_capacity;
unsigned int last_zone_capacity;
u8 __rcu *zones_cond;
u8 __rcu *zones_state;
unsigned int zone_wplugs_hash_bits;
atomic_t nr_zone_wplugs;
spinlock_t zone_wplugs_hash_lock;
+38 -34
View File
@@ -7,14 +7,26 @@
use crate::{
bindings,
block::mq::{Operations, TagSet},
error::{self, from_err_ptr, Result},
fmt::{self, Write},
block::mq::{
Operations,
TagSet, //
},
error::{
from_err_ptr,
to_result, //
},
fmt::{
self,
Write, //
},
prelude::*,
static_lock_class,
str::NullTerminatedFormatter,
sync::Arc,
types::{ForeignOwnable, ScopeGuard},
types::{
ForeignOwnable,
ScopeGuard, //
}, //
};
/// A builder for [`GenDisk`].
@@ -54,7 +66,7 @@ impl GenDiskBuilder {
/// and that it is a power of two.
pub fn validate_block_size(size: u32) -> Result {
if !(512..=bindings::PAGE_SIZE as u32).contains(&size) || !size.is_power_of_two() {
Err(error::code::EINVAL)
Err(EINVAL)
} else {
Ok(())
}
@@ -63,7 +75,7 @@ impl GenDiskBuilder {
/// Set the logical block size of the device to be built.
///
/// This method will check that block size is a power of two and between 512
/// and 4096. If not, an error is returned and the block size is not set.
/// and `PAGE_SIZE`. If not, an error is returned and the block size is not set.
///
/// This is the smallest unit the storage device can address. It is
/// typically 4096 bytes.
@@ -76,7 +88,7 @@ impl GenDiskBuilder {
/// Set the physical block size of the device to be built.
///
/// This method will check that block size is a power of two and between 512
/// and 4096. If not, an error is returned and the block size is not set.
/// and `PAGE_SIZE`. If not, an error is returned and the block size is not set.
///
/// This is the smallest unit a physical storage device can write
/// atomically. It is usually the same as the logical block size but may be
@@ -125,30 +137,9 @@ impl GenDiskBuilder {
)
})?;
const TABLE: bindings::block_device_operations = bindings::block_device_operations {
submit_bio: None,
open: None,
release: None,
ioctl: None,
compat_ioctl: None,
check_events: None,
unlock_native_capacity: None,
getgeo: None,
set_read_only: None,
swap_slot_free_notify: None,
report_zones: None,
devnode: None,
alternative_gpt_sector: None,
get_unique_id: None,
// TODO: Set to `THIS_MODULE`.
owner: core::ptr::null_mut(),
pr_ops: core::ptr::null_mut(),
free_disk: None,
poll_bio: None,
};
// SAFETY: `gendisk` is a valid pointer as we initialized it above
unsafe { (*gendisk).fops = &TABLE };
// SAFETY: `gendisk` is a valid pointer. We have exclusive access,
// since the disk is not added to the VFS yet.
unsafe { (*gendisk).fops = &GenDisk::<T>::VTABLE };
let cleanup_failure = ScopeGuard::new_with_data((gendisk, data), |(gendisk, data)| {
// SAFETY: `gendisk` came from `__blk_mq_alloc_disk()` above and
@@ -177,7 +168,7 @@ impl GenDiskBuilder {
// operation, so we will not race.
unsafe { bindings::set_capacity(gendisk, self.capacity_sectors) };
crate::error::to_result(
to_result(
// SAFETY: `gendisk` points to a valid and initialized instance of
// `struct gendisk`.
unsafe {
@@ -211,9 +202,22 @@ pub struct GenDisk<T: Operations> {
gendisk: *mut bindings::gendisk,
}
impl<T: Operations> GenDisk<T> {
const VTABLE: bindings::block_device_operations = bindings::block_device_operations {
owner: crate::module::this_module::<T::OwnerModule>().as_ptr(),
..pin_init::zeroed()
};
}
// SAFETY: `GenDisk` is an owned pointer to a `struct gendisk` and an `Arc` to a
// `TagSet` It is safe to send this to other threads as long as T is Send.
unsafe impl<T: Operations + Send> Send for GenDisk<T> {}
// `TagSet`. It is safe to send this to other threads as long as these two are `Send`.
unsafe impl<T> Send for GenDisk<T>
where
T: Operations,
T::QueueData: Send,
Arc<TagSet<T>>: Send,
{
}
impl<T: Operations> Drop for GenDisk<T> {
fn drop(&mut self) {
+11 -5
View File
@@ -6,11 +6,17 @@
use crate::{
bindings,
block::mq::{request::RequestDataWrapper, Request},
error::{from_result, Result},
block::mq::{
request::RequestDataWrapper,
Request, //
},
error::from_result,
prelude::*,
sync::{aref::ARef, Refcount},
types::ForeignOwnable,
sync::{
aref::ARef,
Refcount, //
},
types::ForeignOwnable, //
};
use core::marker::PhantomData;
@@ -30,7 +36,7 @@ type ForeignBorrowed<'a, T> = <T as ForeignOwnable>::Borrowed<'a>;
pub trait Operations: Sized {
/// Data associated with the `struct request_queue` that is allocated for
/// the `GenDisk` associated with this `Operations` implementation.
type QueueData: ForeignOwnable;
type QueueData: ForeignOwnable + Sync;
/// Called by the kernel to queue a request with the driver. If `is_last` is
/// `false`, the driver is allowed to defer committing the request.
+11 -5
View File
@@ -7,15 +7,21 @@
use crate::{
bindings,
block::mq::Operations,
error::Result,
prelude::*,
sync::{
aref::{ARef, AlwaysRefCounted},
aref::{
ARef,
AlwaysRefCounted, //
},
atomic::Relaxed,
Refcount,
Refcount, //
},
types::Opaque,
types::Opaque, //
};
use core::{
marker::PhantomData,
ptr::NonNull, //
};
use core::{marker::PhantomData, ptr::NonNull};
/// A wrapper around a blk-mq [`struct request`]. This represents an IO request.
///
+21 -14
View File
@@ -4,17 +4,21 @@
//!
//! C header: [`include/linux/blk-mq.h`](srctree/include/linux/blk-mq.h)
use core::pin::Pin;
use crate::{
bindings,
block::mq::{operations::OperationsVTable, request::RequestDataWrapper, Operations},
error::{self, Result},
prelude::try_pin_init,
types::Opaque,
block::mq::{
operations::OperationsVTable,
request::RequestDataWrapper,
Operations, //
},
error::to_result,
prelude::*,
types::Opaque, //
};
use core::{
convert::TryInto,
marker::PhantomData, //
};
use core::{convert::TryInto, marker::PhantomData};
use pin_init::{pin_data, pinned_drop, PinInit};
/// A wrapper for the C `struct blk_mq_tag_set`.
///
@@ -33,11 +37,7 @@ pub struct TagSet<T: Operations> {
impl<T: Operations> TagSet<T> {
/// Try to create a new tag set
pub fn new(
nr_hw_queues: u32,
num_tags: u32,
num_maps: u32,
) -> impl PinInit<Self, error::Error> {
pub fn new(nr_hw_queues: u32, num_tags: u32, num_maps: u32) -> impl PinInit<Self, Error> {
let tag_set: bindings::blk_mq_tag_set = pin_init::zeroed();
let tag_set: Result<_> = core::mem::size_of::<RequestDataWrapper>()
.try_into()
@@ -63,7 +63,7 @@ impl<T: Operations> TagSet<T> {
// SAFETY: we do not move out of `tag_set`.
let tag_set: &mut Opaque<_> = unsafe { Pin::get_unchecked_mut(tag_set) };
// SAFETY: `tag_set` is a reference to an initialized `blk_mq_tag_set`.
error::to_result( unsafe { bindings::blk_mq_alloc_tag_set(tag_set.get())})
to_result( unsafe { bindings::blk_mq_alloc_tag_set(tag_set.get())})
}),
_p: PhantomData,
})
@@ -83,3 +83,10 @@ impl<T: Operations> PinnedDrop for TagSet<T> {
unsafe { bindings::blk_mq_free_tag_set(self.inner.get()) };
}
}
// SAFETY: It is safe to transfer ownership of `TagSet` between threads.
unsafe impl<T: Operations> Send for TagSet<T> {}
// SAFETY: It is safe to share `&TagSet` across threads. `TagSet` exposes no
// `&self` methods that mutate the contained `blk_mq_tag_set`.
unsafe impl<T: Operations> Sync for TagSet<T> {}
+1
View File
@@ -3,3 +3,4 @@
/tools
kublk
metadata_size
ublk_cancel_ready
+4 -2
View File
@@ -19,6 +19,7 @@ TEST_PROGS += test_generic_12.sh
TEST_PROGS += test_generic_13.sh
TEST_PROGS += test_generic_16.sh
TEST_PROGS += test_generic_17.sh
TEST_PROGS += test_generic_18.sh
TEST_PROGS += test_batch_01.sh
TEST_PROGS += test_batch_02.sh
@@ -76,13 +77,14 @@ TEST_FILES := settings
TEST_FILES += test_common.sh
TEST_FILES += trace
TEST_GEN_PROGS_EXTENDED = kublk metadata_size
STANDALONE_UTILS := metadata_size.c
TEST_GEN_PROGS_EXTENDED = kublk metadata_size ublk_cancel_ready
STANDALONE_UTILS := metadata_size.c ublk_cancel_ready.c
LOCAL_HDRS += $(wildcard *.h)
include ../lib.mk
$(OUTPUT)/kublk: $(filter-out $(STANDALONE_UTILS),$(wildcard *.c))
$(OUTPUT)/ublk_cancel_ready: ublk_cancel_ready.c ctrl.c
check:
shellcheck -x -f gcc *.sh
+10 -3
View File
@@ -410,9 +410,16 @@ static void ublk_batch_compl_commit_cmd(struct ublk_thread *t,
{
unsigned short buf_idx = user_data_to_tag(cqe->user_data);
if (op == _IOC_NR(UBLK_U_IO_PREP_IO_CMDS))
ublk_assert(cqe->res == 0);
else if (op == _IOC_NR(UBLK_U_IO_COMMIT_IO_CMDS)) {
if (op == _IOC_NR(UBLK_U_IO_PREP_IO_CMDS)) {
/* e.g. UBLK_IO_RES_ABORT after STOP_DEV: stop like a FETCH */
if (cqe->res < 0) {
ublk_dbg(UBLK_DBG_IO_CMD, "%s: prep failed %d\n",
__func__, cqe->res);
t->state |= UBLKS_T_STOPPING;
} else {
ublk_assert(cqe->res == 0);
}
} else if (op == _IOC_NR(UBLK_U_IO_COMMIT_IO_CMDS)) {
int nr_elem = user_data_to_tgt_data(cqe->user_data);
ublk_assert(cqe->res == t->commit_buf_elem_size * nr_elem);
+268
View File
@@ -0,0 +1,268 @@
// SPDX-License-Identifier: GPL-2.0
/* ublk control commands, shared by kublk and ublk_cancel_ready */
#include "kublk.h"
static void ublk_ctrl_init_cmd(struct ublk_dev *dev,
struct io_uring_sqe *sqe,
struct ublk_ctrl_cmd_data *data)
{
struct ublksrv_ctrl_dev_info *info = &dev->dev_info;
struct ublksrv_ctrl_cmd *cmd = (struct ublksrv_ctrl_cmd *)ublk_get_sqe_cmd(sqe);
sqe->fd = dev->ctrl_fd;
sqe->opcode = IORING_OP_URING_CMD;
sqe->ioprio = 0;
if (data->flags & CTRL_CMD_HAS_BUF) {
cmd->addr = data->addr;
cmd->len = data->len;
}
if (data->flags & CTRL_CMD_HAS_DATA)
cmd->data[0] = data->data[0];
cmd->dev_id = info->dev_id;
cmd->queue_id = -1;
ublk_set_sqe_cmd_op(sqe, data->cmd_op);
io_uring_sqe_set_data(sqe, cmd);
}
int __ublk_ctrl_cmd(struct ublk_dev *dev,
struct ublk_ctrl_cmd_data *data)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
int ret = -EINVAL;
sqe = io_uring_get_sqe(&dev->ring);
if (!sqe) {
ublk_err("%s: can't get sqe ret %d\n", __func__, ret);
return ret;
}
ublk_ctrl_init_cmd(dev, sqe, data);
ret = io_uring_submit(&dev->ring);
if (ret < 0) {
ublk_err("uring submit ret %d\n", ret);
return ret;
}
ret = io_uring_wait_cqe(&dev->ring, &cqe);
if (ret < 0) {
ublk_err("wait cqe: %s\n", strerror(-ret));
return ret;
}
io_uring_cqe_seen(&dev->ring, cqe);
return cqe->res;
}
void ublk_ctrl_deinit(struct ublk_dev *dev)
{
io_uring_queue_exit(&dev->ring);
close(dev->ctrl_fd);
free(dev);
}
struct ublk_dev *ublk_ctrl_init(void)
{
struct ublk_dev *dev = (struct ublk_dev *)calloc(1, sizeof(*dev));
struct ublksrv_ctrl_dev_info *info;
int ret;
if (!dev)
return NULL;
info = &dev->dev_info;
dev->ctrl_fd = open(CTRL_DEV, O_RDWR);
if (dev->ctrl_fd < 0) {
free(dev);
return NULL;
}
info->max_io_buf_bytes = UBLK_IO_MAX_BYTES;
ret = ublk_setup_ring(&dev->ring, UBLK_CTRL_RING_DEPTH,
UBLK_CTRL_RING_DEPTH, IORING_SETUP_SQE128);
if (ret < 0) {
ublk_err("queue_init: %s\n", strerror(-ret));
close(dev->ctrl_fd);
free(dev);
return NULL;
}
dev->nr_fds = 1;
return dev;
}
int ublk_ctrl_stop_dev(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_STOP_DEV,
};
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_try_stop_dev(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_TRY_STOP_DEV,
};
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_start_dev(struct ublk_dev *dev,
int daemon_pid)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_START_DEV,
.flags = CTRL_CMD_HAS_DATA,
};
dev->dev_info.ublksrv_pid = data.data[0] = daemon_pid;
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_start_user_recovery(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_START_USER_RECOVERY,
};
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_end_user_recovery(struct ublk_dev *dev, int daemon_pid)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_END_USER_RECOVERY,
.flags = CTRL_CMD_HAS_DATA,
};
dev->dev_info.ublksrv_pid = data.data[0] = daemon_pid;
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_add_dev(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_ADD_DEV,
.flags = CTRL_CMD_HAS_BUF,
.addr = (__u64) (uintptr_t) &dev->dev_info,
.len = sizeof(struct ublksrv_ctrl_dev_info),
};
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_del_dev(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_DEL_DEV,
.flags = 0,
};
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_get_info(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_GET_DEV_INFO,
.flags = CTRL_CMD_HAS_BUF,
.addr = (__u64) (uintptr_t) &dev->dev_info,
.len = sizeof(struct ublksrv_ctrl_dev_info),
};
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_set_params(struct ublk_dev *dev,
struct ublk_params *params)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_SET_PARAMS,
.flags = CTRL_CMD_HAS_BUF,
.addr = (__u64) (uintptr_t) params,
.len = sizeof(*params),
};
params->len = sizeof(*params);
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_get_params(struct ublk_dev *dev,
struct ublk_params *params)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_GET_PARAMS,
.flags = CTRL_CMD_HAS_BUF,
.addr = (__u64)params,
.len = sizeof(*params),
};
params->len = sizeof(*params);
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_get_features(struct ublk_dev *dev,
__u64 *features)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_GET_FEATURES,
.flags = CTRL_CMD_HAS_BUF,
.addr = (__u64) (uintptr_t) features,
.len = sizeof(*features),
};
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_update_size(struct ublk_dev *dev,
__u64 nr_sects)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_UPDATE_SIZE,
.flags = CTRL_CMD_HAS_DATA,
};
data.data[0] = nr_sects;
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_quiesce_dev(struct ublk_dev *dev, unsigned int timeout_ms)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_QUIESCE_DEV,
.flags = CTRL_CMD_HAS_DATA,
};
data.data[0] = timeout_ms;
return __ublk_ctrl_cmd(dev, &data);
}
int ublk_ctrl_reg_buf(struct ublk_dev *dev, void *addr, size_t size,
__u32 flags)
{
struct ublk_shmem_buf_reg buf_reg = {
.addr = (unsigned long)addr,
.len = size,
.flags = flags,
};
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_REG_BUF,
.flags = CTRL_CMD_HAS_BUF,
.addr = (unsigned long)&buf_reg,
.len = sizeof(buf_reg),
};
return __ublk_ctrl_cmd(dev, &data);
}
+8 -6
View File
@@ -11,7 +11,7 @@
#include "kublk.h"
struct fi_opts {
long long delay_ns;
struct __kernel_timespec delay;
bool die_during_fetch;
};
@@ -47,7 +47,8 @@ static int ublk_fault_inject_tgt_init(const struct dev_ctx *ctx,
return -ENOMEM;
}
opts->delay_ns = ctx->fault_inject.delay_us * 1000;
opts->delay.tv_sec = ctx->fault_inject.delay_us / 1000000;
opts->delay.tv_nsec = ctx->fault_inject.delay_us % 1000000 * 1000;
opts->die_during_fetch = ctx->fault_inject.die_during_fetch;
dev->private_data = opts;
@@ -85,12 +86,13 @@ static int ublk_fault_inject_queue_io(struct ublk_thread *t,
const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
struct io_uring_sqe *sqe;
struct fi_opts *opts = q->dev->private_data;
struct __kernel_timespec ts = {
.tv_nsec = opts->delay_ns,
};
/*
* Pass a timespec that outlives this function, since io_uring
* reads it only when the SQE is submitted.
*/
ublk_io_alloc_sqes(t, &sqe, 1);
io_uring_prep_timeout(sqe, &ts, 1, 0);
io_uring_prep_timeout(sqe, &opts->delay, 0, 0);
sqe->user_data = build_user_data(tag, ublksrv_get_op(iod), 0, q->q_id, 1);
ublk_queued_tgt_io(t, q, tag, 1);
-271
View File
@@ -37,203 +37,6 @@ static const struct ublk_tgt_ops *ublk_find_tgt(const char *name)
return NULL;
}
static inline int ublk_setup_ring(struct io_uring *r, int depth,
int cq_depth, unsigned flags)
{
struct io_uring_params p;
memset(&p, 0, sizeof(p));
p.flags = flags | IORING_SETUP_CQSIZE;
p.cq_entries = cq_depth;
return io_uring_queue_init_params(depth, r, &p);
}
static void ublk_ctrl_init_cmd(struct ublk_dev *dev,
struct io_uring_sqe *sqe,
struct ublk_ctrl_cmd_data *data)
{
struct ublksrv_ctrl_dev_info *info = &dev->dev_info;
struct ublksrv_ctrl_cmd *cmd = (struct ublksrv_ctrl_cmd *)ublk_get_sqe_cmd(sqe);
sqe->fd = dev->ctrl_fd;
sqe->opcode = IORING_OP_URING_CMD;
sqe->ioprio = 0;
if (data->flags & CTRL_CMD_HAS_BUF) {
cmd->addr = data->addr;
cmd->len = data->len;
}
if (data->flags & CTRL_CMD_HAS_DATA)
cmd->data[0] = data->data[0];
cmd->dev_id = info->dev_id;
cmd->queue_id = -1;
ublk_set_sqe_cmd_op(sqe, data->cmd_op);
io_uring_sqe_set_data(sqe, cmd);
}
static int __ublk_ctrl_cmd(struct ublk_dev *dev,
struct ublk_ctrl_cmd_data *data)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
int ret = -EINVAL;
sqe = io_uring_get_sqe(&dev->ring);
if (!sqe) {
ublk_err("%s: can't get sqe ret %d\n", __func__, ret);
return ret;
}
ublk_ctrl_init_cmd(dev, sqe, data);
ret = io_uring_submit(&dev->ring);
if (ret < 0) {
ublk_err("uring submit ret %d\n", ret);
return ret;
}
ret = io_uring_wait_cqe(&dev->ring, &cqe);
if (ret < 0) {
ublk_err("wait cqe: %s\n", strerror(-ret));
return ret;
}
io_uring_cqe_seen(&dev->ring, cqe);
return cqe->res;
}
static int ublk_ctrl_stop_dev(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_STOP_DEV,
};
return __ublk_ctrl_cmd(dev, &data);
}
static int ublk_ctrl_try_stop_dev(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_TRY_STOP_DEV,
};
return __ublk_ctrl_cmd(dev, &data);
}
static int ublk_ctrl_start_dev(struct ublk_dev *dev,
int daemon_pid)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_START_DEV,
.flags = CTRL_CMD_HAS_DATA,
};
dev->dev_info.ublksrv_pid = data.data[0] = daemon_pid;
return __ublk_ctrl_cmd(dev, &data);
}
static int ublk_ctrl_start_user_recovery(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_START_USER_RECOVERY,
};
return __ublk_ctrl_cmd(dev, &data);
}
static int ublk_ctrl_end_user_recovery(struct ublk_dev *dev, int daemon_pid)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_END_USER_RECOVERY,
.flags = CTRL_CMD_HAS_DATA,
};
dev->dev_info.ublksrv_pid = data.data[0] = daemon_pid;
return __ublk_ctrl_cmd(dev, &data);
}
static int ublk_ctrl_add_dev(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_ADD_DEV,
.flags = CTRL_CMD_HAS_BUF,
.addr = (__u64) (uintptr_t) &dev->dev_info,
.len = sizeof(struct ublksrv_ctrl_dev_info),
};
return __ublk_ctrl_cmd(dev, &data);
}
static int ublk_ctrl_del_dev(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_DEL_DEV,
.flags = 0,
};
return __ublk_ctrl_cmd(dev, &data);
}
static int ublk_ctrl_get_info(struct ublk_dev *dev)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_GET_DEV_INFO,
.flags = CTRL_CMD_HAS_BUF,
.addr = (__u64) (uintptr_t) &dev->dev_info,
.len = sizeof(struct ublksrv_ctrl_dev_info),
};
return __ublk_ctrl_cmd(dev, &data);
}
static int ublk_ctrl_set_params(struct ublk_dev *dev,
struct ublk_params *params)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_SET_PARAMS,
.flags = CTRL_CMD_HAS_BUF,
.addr = (__u64) (uintptr_t) params,
.len = sizeof(*params),
};
params->len = sizeof(*params);
return __ublk_ctrl_cmd(dev, &data);
}
static int ublk_ctrl_get_params(struct ublk_dev *dev,
struct ublk_params *params)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_GET_PARAMS,
.flags = CTRL_CMD_HAS_BUF,
.addr = (__u64)params,
.len = sizeof(*params),
};
params->len = sizeof(*params);
return __ublk_ctrl_cmd(dev, &data);
}
static int ublk_ctrl_get_features(struct ublk_dev *dev,
__u64 *features)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_GET_FEATURES,
.flags = CTRL_CMD_HAS_BUF,
.addr = (__u64) (uintptr_t) features,
.len = sizeof(*features),
};
return __ublk_ctrl_cmd(dev, &data);
}
static int parse_param_types(const char *arg, __u32 *types)
{
char buf[128], *save = NULL, *tok;
@@ -283,30 +86,6 @@ static void ublk_init_params_from_ctx(const struct dev_ctx *ctx,
};
}
static int ublk_ctrl_update_size(struct ublk_dev *dev,
__u64 nr_sects)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_UPDATE_SIZE,
.flags = CTRL_CMD_HAS_DATA,
};
data.data[0] = nr_sects;
return __ublk_ctrl_cmd(dev, &data);
}
static int ublk_ctrl_quiesce_dev(struct ublk_dev *dev,
unsigned int timeout_ms)
{
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_QUIESCE_DEV,
.flags = CTRL_CMD_HAS_DATA,
};
data.data[0] = timeout_ms;
return __ublk_ctrl_cmd(dev, &data);
}
static const char *ublk_dev_state_desc(struct ublk_dev *dev)
{
switch (dev->dev_info.state) {
@@ -426,38 +205,6 @@ static void ublk_ctrl_dump(struct ublk_dev *dev)
fflush(stdout);
}
static void ublk_ctrl_deinit(struct ublk_dev *dev)
{
close(dev->ctrl_fd);
free(dev);
}
static struct ublk_dev *ublk_ctrl_init(void)
{
struct ublk_dev *dev = (struct ublk_dev *)calloc(1, sizeof(*dev));
struct ublksrv_ctrl_dev_info *info = &dev->dev_info;
int ret;
dev->ctrl_fd = open(CTRL_DEV, O_RDWR);
if (dev->ctrl_fd < 0) {
free(dev);
return NULL;
}
info->max_io_buf_bytes = UBLK_IO_MAX_BYTES;
ret = ublk_setup_ring(&dev->ring, UBLK_CTRL_RING_DEPTH,
UBLK_CTRL_RING_DEPTH, IORING_SETUP_SQE128);
if (ret < 0) {
ublk_err("queue_init: %s\n", strerror(-ret));
free(dev);
return NULL;
}
dev->nr_fds = 1;
return dev;
}
static size_t __ublk_queue_cmd_buf_sz(const struct ublk_queue *q, __u16 depth)
{
size_t size = depth * (size_t)q->io_desc_size;
@@ -1283,24 +1030,6 @@ static void ublk_shmem_unregister_all(void)
shmem_count = 0;
}
static int ublk_ctrl_reg_buf(struct ublk_dev *dev, void *addr, size_t size,
__u32 flags)
{
struct ublk_shmem_buf_reg buf_reg = {
.addr = (unsigned long)addr,
.len = size,
.flags = flags,
};
struct ublk_ctrl_cmd_data data = {
.cmd_op = UBLK_U_CMD_REG_BUF,
.flags = CTRL_CMD_HAS_BUF,
.addr = (unsigned long)&buf_reg,
.len = sizeof(buf_reg),
};
return __ublk_ctrl_cmd(dev, &data);
}
/*
* Handle one client connection: receive memfd, mmap it, register
* the VA range with kernel, send back the assigned index.
+32
View File
@@ -294,6 +294,38 @@ struct ublk_dev {
extern int ublk_queue_io_cmd(struct ublk_thread *t, struct ublk_io *io);
static inline int ublk_setup_ring(struct io_uring *r, int depth,
int cq_depth, unsigned int flags)
{
struct io_uring_params p;
memset(&p, 0, sizeof(p));
p.flags = flags | IORING_SETUP_CQSIZE;
p.cq_entries = cq_depth;
return io_uring_queue_init_params(depth, r, &p);
}
/* ctrl.c: control commands */
struct ublk_dev *ublk_ctrl_init(void);
void ublk_ctrl_deinit(struct ublk_dev *dev);
int __ublk_ctrl_cmd(struct ublk_dev *dev, struct ublk_ctrl_cmd_data *data);
int ublk_ctrl_add_dev(struct ublk_dev *dev);
int ublk_ctrl_del_dev(struct ublk_dev *dev);
int ublk_ctrl_get_info(struct ublk_dev *dev);
int ublk_ctrl_set_params(struct ublk_dev *dev, struct ublk_params *params);
int ublk_ctrl_get_params(struct ublk_dev *dev, struct ublk_params *params);
int ublk_ctrl_get_features(struct ublk_dev *dev, __u64 *features);
int ublk_ctrl_start_dev(struct ublk_dev *dev, int daemon_pid);
int ublk_ctrl_stop_dev(struct ublk_dev *dev);
int ublk_ctrl_try_stop_dev(struct ublk_dev *dev);
int ublk_ctrl_start_user_recovery(struct ublk_dev *dev);
int ublk_ctrl_end_user_recovery(struct ublk_dev *dev, int daemon_pid);
int ublk_ctrl_update_size(struct ublk_dev *dev, __u64 nr_sects);
int ublk_ctrl_quiesce_dev(struct ublk_dev *dev, unsigned int timeout_ms);
int ublk_ctrl_reg_buf(struct ublk_dev *dev, void *addr, size_t size,
__u32 flags);
static inline int __ublk_use_batch_io(__u64 flags)
{
return flags & UBLK_F_BATCH_IO;
+69
View File
@@ -0,0 +1,69 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
ERR_CODE=0
CANCEL_PROG="$(_ublk_test_top_dir)/ublk_cancel_ready"
_prep_test "generic" "start device over canceled io commands"
# STOP_DEV while kublk's UBLK_F_BATCH_IO server prepares and fetches: the
# server has to fail cleanly when PREP or FETCH is aborted, so `kublk add`
# must return either way.
race_batch_stop()
{
local id=9999 i res started=0
_have_feature "BATCH_IO" || return "$UBLK_SKIP_CODE"
for i in $(seq 1 20); do
timeout 20 "$UBLK_PROG" add -t null -q 2 -b -n "$id" > /dev/null 2>&1 &
local pid=$!
# vary where STOP_DEV lands relative to the batch PREP and FETCH
sleep "0.$((i % 10))"
"$UBLK_PROG" stop -n "$id" > /dev/null 2>&1
wait "$pid"
res=$?
"$UBLK_PROG" del -n "$id" > /dev/null 2>&1
if [ "$res" -eq 124 ]; then
echo "run $i: kublk add -b hung after STOP_DEV"
return 255
fi
[ "$res" -eq 0 ] && started=$((started + 1))
done
echo "20 runs: started $started, stopped before start $((20 - started))"
}
# race_batch_stop is above, the other modes are described in
# ublk_cancel_ready.c
for mode in stop_start partial_fetch recovery stop_restart stop_attached \
stop_live_restart race_start race_fetch race_async_fetch \
race_batch_stop; do
dmesg_before=$(dmesg | wc -l)
if [ "$mode" = race_batch_stop ]; then
race_batch_stop > "$UBLK_TMP" 2>&1
else
timeout 60 "$CANCEL_PROG" "$mode" > "$UBLK_TMP" 2>&1
fi
res=$?
msg=""
if dmesg | tail -n +"$((dmesg_before + 1))" | \
grep -q -e "BUG:" -e "Oops" -e "WARNING:"; then
msg="$mode: kernel oops/warning"
ERR_CODE=255
elif [ "$res" -eq "$UBLK_SKIP_CODE" ]; then
[ "$ERR_CODE" -eq 0 ] && ERR_CODE=$UBLK_SKIP_CODE
elif [ "$res" -ne 0 ]; then
msg="$mode: failed ($res)"
ERR_CODE=255
fi
# the output once: on failure, or always when not quiet
[ -n "$msg" ] && echo "$msg"
if [ -n "$msg" ] || [ "$UBLK_TEST_QUIET" -eq 0 ]; then
cat "$UBLK_TMP"
fi
done
_cleanup_test
_show_result $TID $ERR_CODE
@@ -0,0 +1,957 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Bring a ublk device live while some of its fetched io commands are
* canceled.
*
* A cancel completes a fetched command and clears io->cmd, but the io
* still counts as ready. Only ubq->canceling keeps ublk_queue_rq() away
* from the NULL io->cmd. Each mode below loses ->canceling in another way:
*
* stop_start: fetch every tag, STOP_DEV before START_DEV, START_DEV.
* STOP_DEV cancels the commands of the attached server,
* so START_DEV must get -EBUSY until that server is gone.
* An unfixed kernel starts the device over them.
*
* partial_fetch: task A fetches tags 0..depth-2 and dies, so its
* commands are canceled. Task B fetches the last tag, and
* an unfixed kernel clears ->canceling when the queue gets
* ready. /dev/ublkcN stays open all the time, so
* ublk_ch_release() never resets the queue.
*
* recovery: a UBLK_F_USER_RECOVERY device with two queues loses its
* server. During recovery task Q0 fetches queue 0, which
* clears q0->canceling, then dies. An unfixed kernel still
* has ub->canceling set because queue 1 is not ready, so
* ublk_start_cancel() does not mark queue 0 again. Reads
* are issued on a CPU mapped to queue 0.
*
* In partial_fetch and recovery, START_DEV / END_USER_RECOVERY must refuse
* with -EBUSY. An unfixed kernel oopses in ublk_queue_cmd() on a NULL
* io->cmd.
*
* stop_restart: control mode. STOP_DEV on a new device, before any
* server opened it, takes no command. A server started
* afterwards has to start the device and serve I/O.
*
* stop_attached: STOP_DEV on a device whose server opened it but fetched
* nothing stops that server too: FETCH gets ABORT and
* START_DEV -EBUSY, until a new server opens the device.
*
* stop_live_restart: STOP_DEV on a live device; once its server is gone,
* a new server has to fetch and start it again. An unfixed
* ublk_ch_release() skips the reset once the disk is gone,
* so the new FETCH gets -EBUSY.
*
* race_start: STOP_DEV and START_DEV at the same time on a device whose
* server fetched every tag. START_DEV either wins, and
* reads complete (they fail once STOP_DEV removes the
* disk), or gets -EBUSY. An unfixed kernel cancels the
* commands of the live disk.
*
* race_fetch: STOP_DEV while a server opens the device and fetches,
* then START_DEV. If STOP_DEV came before the open, it must
* not take any command, so START_DEV works and every read
* succeeds; otherwise START_DEV gets -EBUSY. An unfixed
* kernel takes commands fetched after its unlock and goes
* live over them.
*
* race_async_fetch: FETCH with IOSQE_ASYNC while STOP_DEV cancels,
* then close the ring. An unfixed FETCH marks its command
* cancelable only after publishing it and dropping
* ub->mutex; a cancel in between completes a command which
* is then put on io_uring's cancelable list. Closing the
* ring walks that list: KASAN reports a use after free.
* Needs a KASAN kernel to see the bug; otherwise it must
* just not crash.
*
* A dying task is a child process which fetches and then calls exec().
* exec() cancels the task's uring_cmds before it returns, so the cancel
* is done once the child is reaped. A thread exit does not cancel them,
* and closing the ring cancels them later, from io_ring_exit_work().
*/
#include <sched.h>
#include "kublk.h"
#include "../kselftest.h"
#define NR_QUEUES 2
#define DEPTH 4
#define BUF_SIZE (64 << 10)
#define DEV_SECTORS (64 << 11) /* 64MB */
#define NR_READS (DEPTH * 2)
#define SERVE_DELAY_US 200000
#define RACE_LOOPS 50
#define ASYNC_LOOPS 300
/* one control handle per thread, the race modes send commands from two */
static __thread struct ublk_dev *ctrl_dev;
static int cdev_fd = -1;
static int dev_id = -1;
static int nr_queues;
static void *bufs[NR_QUEUES][DEPTH];
static const struct ublksrv_io_desc *iods[NR_QUEUES];
static size_t iods_len;
static struct io_uring_sqe *get_sqe(struct io_uring *ring)
{
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
if (!sqe) {
fprintf(stderr, "out of sqes\n");
exit(KSFT_FAIL);
}
return sqe;
}
static struct ublk_dev *ctrl(void)
{
if (!ctrl_dev) {
ctrl_dev = ublk_ctrl_init();
if (!ctrl_dev) {
fprintf(stderr, "ublk_ctrl_init failed\n");
exit(KSFT_FAIL);
}
}
ctrl_dev->dev_info.dev_id = dev_id;
return ctrl_dev;
}
static void ctrl_put(void)
{
if (ctrl_dev)
ublk_ctrl_deinit(ctrl_dev);
ctrl_dev = NULL;
}
static int dev_state(void)
{
struct ublk_dev *dev = ctrl();
int ret = ublk_ctrl_get_info(dev);
return ret ? ret : dev->dev_info.state;
}
static int open_cdev(void)
{
size_t max_len = UBLK_MAX_QUEUE_DEPTH * sizeof(struct ublksrv_io_desc);
int pg = getpagesize();
char path[64];
snprintf(path, sizeof(path), "/dev/ublkc%d", dev_id);
for (int i = 0; i < 100 && cdev_fd < 0; i++) {
cdev_fd = open(path, O_RDWR);
if (cdev_fd < 0)
usleep(50000);
}
if (cdev_fd < 0)
return -errno;
/* queue q's descriptors start at q * the size for the max depth */
max_len = (max_len + pg - 1) & ~(size_t)(pg - 1);
iods_len = (DEPTH * sizeof(struct ublksrv_io_desc) + pg - 1) &
~(size_t)(pg - 1);
for (int q = 0; q < nr_queues; q++) {
void *p = mmap(NULL, iods_len, PROT_READ,
MAP_SHARED | MAP_POPULATE, cdev_fd,
UBLKSRV_CMD_BUF_OFFSET + q * max_len);
if (p == MAP_FAILED)
return -errno;
iods[q] = p;
}
return 0;
}
/* the last reference to /dev/ublkcN runs ublk_ch_release() */
static void close_cdev(void)
{
for (int q = 0; q < nr_queues; q++) {
if (iods[q])
munmap((void *)iods[q], iods_len);
iods[q] = NULL;
}
if (cdev_fd >= 0)
close(cdev_fd);
cdev_fd = -1;
}
/* ADD_DEV and SET_PARAMS, without opening /dev/ublkcN */
static int add_dev_noopen(int queues, __u64 flags)
{
struct ublk_dev *dev = ctrl();
struct ublksrv_ctrl_dev_info info = {
.nr_hw_queues = queues,
.queue_depth = DEPTH,
.max_io_buf_bytes = BUF_SIZE,
.dev_id = -1,
.flags = UBLK_F_NO_AUTO_PART_SCAN | flags,
};
struct ublk_params p = {
.types = UBLK_PARAM_TYPE_BASIC,
.basic = {
.logical_bs_shift = 9,
.physical_bs_shift = 12,
.io_opt_shift = 12,
.io_min_shift = 9,
.max_sectors = BUF_SIZE >> 9,
.dev_sectors = DEV_SECTORS,
},
};
int ret;
nr_queues = queues;
dev->dev_info = info;
ret = ublk_ctrl_add_dev(dev);
if (ret)
return ret;
dev_id = dev->dev_info.dev_id;
ret = ublk_ctrl_set_params(ctrl(), &p);
if (ret)
return ret;
for (int q = 0; q < queues; q++)
for (int i = 0; i < DEPTH; i++)
if (!bufs[q][i] &&
posix_memalign(&bufs[q][i], getpagesize(), BUF_SIZE))
return -ENOMEM;
return 0;
}
static int add_dev(int queues, __u64 flags)
{
return add_dev_noopen(queues, flags) ?: open_cdev();
}
static void cleanup(void)
{
if (dev_id < 0)
return;
ublk_ctrl_stop_dev(ctrl());
close_cdev();
ublk_ctrl_del_dev(ctrl());
dev_id = -1;
}
/* race_async_fetch sets IOSQE_ASYNC on the io commands */
static int io_cmd_sqe_flags;
static void queue_io_cmd(struct io_uring *ring, __u32 op, int q, int tag,
int res)
{
struct io_uring_sqe *sqe = get_sqe(ring);
struct ublksrv_io_cmd *cmd = (struct ublksrv_io_cmd *)sqe->cmd;
memset(sqe, 0, sizeof(*sqe));
sqe->fd = cdev_fd;
sqe->opcode = IORING_OP_URING_CMD;
sqe->flags = io_cmd_sqe_flags;
ublk_set_sqe_cmd_op(sqe, op);
cmd->q_id = q;
cmd->tag = tag;
cmd->result = res;
cmd->addr = (__u64)(uintptr_t)bufs[q][tag];
io_uring_sqe_set_data64(sqe, (q << 16) | tag);
}
struct async_arg {
pthread_barrier_t go, stopped;
};
/*
* FETCH every tag with IOSQE_ASYNC, racing STOP_DEV, then close the ring once
* STOP_DEV returned: that walks io_uring's list of cancelable commands.
*/
static void *race_async_fn(void *data)
{
struct async_arg *a = data;
struct io_uring ring;
int ok = !io_uring_queue_init(DEPTH, &ring, 0);
if (ok)
for (int tag = 0; tag < DEPTH; tag++)
queue_io_cmd(&ring, UBLK_U_IO_FETCH_REQ, 0, tag, 0);
pthread_barrier_wait(&a->go);
if (ok)
io_uring_submit(&ring);
pthread_barrier_wait(&a->stopped);
if (ok)
io_uring_queue_exit(&ring);
return NULL;
}
/* reap @nr completions of canceled fetch commands, return how many */
static int reap_aborts(struct io_uring *ring, int nr)
{
struct __kernel_timespec ts = { .tv_sec = 2 };
struct io_uring_cqe *cqe;
int aborted = 0;
while (nr--) {
if (io_uring_wait_cqe_timeout(ring, &cqe, &ts))
break;
if (cqe->res == UBLK_IO_RES_ABORT)
aborted++;
io_uring_cqe_seen(ring, cqe);
}
return aborted;
}
/*
* A server thread fetches @nr_tags tags of queue @q, then completes each
* request after @delay_us, until its commands are aborted.
*/
struct server {
int q, first_tag, nr_tags, delay_us;
int failed; /* result which ended the serve loop, 0 if none */
pthread_t thread;
pthread_barrier_t fetched;
/* race_fetch: wait for @go and @race_delay_us, open, fetch one by one */
pthread_barrier_t go;
int race, race_delay_us;
};
static void *server_fn(void *data)
{
struct server *s = data;
struct io_uring ring;
struct io_uring_cqe *cqe;
if (io_uring_queue_init(DEPTH, &ring, 0)) {
pthread_barrier_wait(&s->fetched);
return NULL;
}
if (s->race) {
pthread_barrier_wait(&s->go);
usleep(s->race_delay_us);
if (open_cdev()) {
pthread_barrier_wait(&s->fetched);
io_uring_queue_exit(&ring);
return NULL;
}
}
for (int tag = s->first_tag; tag < s->first_tag + s->nr_tags; tag++) {
queue_io_cmd(&ring, UBLK_U_IO_FETCH_REQ, s->q, tag, 0);
if (s->race)
io_uring_submit(&ring);
}
io_uring_submit(&ring);
pthread_barrier_wait(&s->fetched);
while (!io_uring_wait_cqe(&ring, &cqe)) {
int tag = cqe->user_data & 0xffff;
const struct ublksrv_io_desc *iod = &iods[s->q][tag];
int res = cqe->res;
io_uring_cqe_seen(&ring, cqe);
if (res != UBLK_IO_RES_OK) {
__atomic_store_n(&s->failed, res, __ATOMIC_RELEASE);
break;
}
usleep(s->delay_us);
res = ublksrv_get_op(iod) <= UBLK_IO_OP_WRITE ?
iod->nr_sectors << 9 : 0;
queue_io_cmd(&ring, UBLK_U_IO_COMMIT_AND_FETCH_REQ, s->q, tag,
res);
io_uring_submit(&ring);
}
io_uring_queue_exit(&ring);
return NULL;
}
/* returns once the fetch commands are issued; with @race, call race_go() */
static void server_start(struct server *s)
{
pthread_barrier_init(&s->fetched, NULL, 2);
pthread_barrier_init(&s->go, NULL, 2);
pthread_create(&s->thread, NULL, server_fn, s);
if (!s->race)
pthread_barrier_wait(&s->fetched);
}
/* wait up to 5s for the serve loop of @s to end, return its result */
static int server_wait_failed(struct server *s)
{
int res = 0;
for (int i = 0; i < 500 && !res; i++) {
res = __atomic_load_n(&s->failed, __ATOMIC_ACQUIRE);
if (!res)
usleep(10000);
}
return res;
}
/*
* A task fetches @nr_tags tags of queue @q and dies. Returns once its
* commands are canceled, see the top of this file.
*/
static int fetch_and_die(int q, int first_tag, int nr_tags)
{
int status;
pid_t pid = fork();
if (pid < 0)
return -1;
if (!pid) {
struct io_uring ring;
if (io_uring_queue_init(DEPTH, &ring, 0))
_exit(1);
for (int tag = first_tag; tag < first_tag + nr_tags; tag++)
queue_io_cmd(&ring, UBLK_U_IO_FETCH_REQ, q, tag, 0);
if (io_uring_submit(&ring) != nr_tags)
_exit(1);
execlp("true", "true", NULL);
_exit(1);
}
if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) ||
WEXITSTATUS(status))
return -1;
printf("q%d: task died with %d fetch cmds in flight\n", q, nr_tags);
return 0;
}
struct reads {
struct io_uring ring;
void *buf;
int fd, done, ok, eio, other;
};
/*
* Issue NR_READS reads at once, from @cpu if it is not negative: blk-mq
* maps the submitting CPU to the hw queue. A server holding its live
* tags for a while makes the other reads take the canceled tags.
*/
static int open_tries = 100; /* 50ms each */
static int reads_submit(struct reads *r, int cpu)
{
char path[64];
cpu_set_t set, old;
snprintf(path, sizeof(path), "/dev/ublkb%d", dev_id);
r->fd = -1;
for (int i = 0; i < open_tries && r->fd < 0; i++) {
r->fd = open(path, O_RDONLY | O_DIRECT);
if (r->fd < 0)
usleep(50000);
}
if (r->fd < 0) {
fprintf(stderr, "open %s: %s\n", path, strerror(errno));
return -1;
}
if (posix_memalign(&r->buf, 4096, NR_READS * 4096) ||
io_uring_queue_init(NR_READS, &r->ring, 0))
return -1;
for (int i = 0; i < NR_READS; i++)
io_uring_prep_read(get_sqe(&r->ring), r->fd,
r->buf + i * 4096, 4096, i * 4096);
if (cpu >= 0) {
sched_getaffinity(0, sizeof(old), &old);
CPU_ZERO(&set);
CPU_SET(cpu, &set);
sched_setaffinity(0, sizeof(set), &set);
}
io_uring_submit(&r->ring);
if (cpu >= 0)
sched_setaffinity(0, sizeof(old), &old);
return 0;
}
static void reads_reap(struct reads *r, int timeout_s)
{
struct __kernel_timespec ts = { .tv_sec = timeout_s };
struct io_uring_cqe *cqe;
while (r->done < NR_READS &&
!io_uring_wait_cqe_timeout(&r->ring, &cqe, &ts)) {
if (cqe->res == 4096)
r->ok++;
else if (cqe->res == -EIO)
r->eio++;
else
r->other++;
r->done++;
io_uring_cqe_seen(&r->ring, cqe);
}
}
static void reads_put(struct reads *r)
{
io_uring_queue_exit(&r->ring);
close(r->fd);
free(r->buf);
}
static int reads_result(struct reads *r)
{
printf("reads: %d ok, %d -EIO, %d other, %d not completed\n",
r->ok, r->eio, r->other, NR_READS - r->done);
reads_put(r);
return r->other || r->done < NR_READS ? KSFT_FAIL : KSFT_PASS;
}
static int start_dev(void)
{
int ret = ublk_ctrl_start_dev(ctrl(), getpid());
printf("START_DEV: %d\n", ret);
return ret;
}
static int expect_err(const char *what, int ret, int want)
{
if (ret == want)
return KSFT_PASS;
fprintf(stderr, "%s: %d, expected %d\n", what, ret, want);
return KSFT_FAIL;
}
/* START_DEV must fail with @want; if it went live, show what a read does */
static int start_dev_expect(int want)
{
struct reads r = {};
int ret = start_dev();
if (!ret && !reads_submit(&r, -1)) {
reads_reap(&r, 10);
reads_result(&r);
}
return expect_err("START_DEV", ret, want);
}
static int test_stop_start(void)
{
struct io_uring ring;
int ret;
if (add_dev(1, 0))
return KSFT_FAIL;
if (io_uring_queue_init(DEPTH, &ring, 0))
return KSFT_FAIL;
for (int tag = 0; tag < DEPTH; tag++)
queue_io_cmd(&ring, UBLK_U_IO_FETCH_REQ, 0, tag, 0);
io_uring_submit(&ring);
/* device is ready but not started: state is UBLK_S_DEV_DEAD */
ret = ublk_ctrl_stop_dev(ctrl());
printf("STOP_DEV: %d, canceled fetch cmds: %d/%d\n", ret,
reap_aborts(&ring, DEPTH), DEPTH);
/* STOP_DEV canceled the attached server: no start until it exits */
ret = start_dev_expect(-EBUSY);
io_uring_queue_exit(&ring);
return ret;
}
static int test_partial_fetch(void)
{
struct server b = { .q = 0, .first_tag = DEPTH - 1, .nr_tags = 1,
.delay_us = SERVE_DELAY_US };
int ret;
if (add_dev(1, 0) || fetch_and_die(0, 0, DEPTH - 1))
return KSFT_FAIL;
/* the last FETCH makes the queue ready and clears ->canceling */
server_start(&b);
ret = start_dev_expect(-EBUSY);
cleanup();
pthread_join(b.thread, NULL);
return ret;
}
static int test_stop_restart(void)
{
struct server s = { .q = 0, .nr_tags = DEPTH };
struct reads r = {};
int ret;
if (add_dev_noopen(1, 0))
return KSFT_FAIL;
/* no server is attached, so there is nothing to cancel */
ret = ublk_ctrl_stop_dev(ctrl());
printf("STOP_DEV: %d\n", ret);
if (open_cdev())
return KSFT_FAIL;
server_start(&s);
if (start_dev()) {
ret = KSFT_FAIL;
} else if (reads_submit(&r, -1)) {
ret = KSFT_FAIL;
} else {
reads_reap(&r, 10);
ret = reads_result(&r);
if (r.ok != NR_READS)
ret = KSFT_FAIL;
}
cleanup();
pthread_join(s.thread, NULL);
return ret;
}
struct start_arg {
pthread_barrier_t go;
int ret, reads_ok;
};
static void *race_start_fn(void *data)
{
struct start_arg *a = data;
struct reads r = {};
pthread_barrier_wait(&a->go);
a->ret = ublk_ctrl_start_dev(ctrl(), getpid());
a->reads_ok = 1;
/*
* The disk may be gone already if STOP_DEV came right after, and
* reads may fail then: they only have to complete.
*/
if (!a->ret && !reads_submit(&r, -1)) {
reads_reap(&r, 5);
a->reads_ok = r.done == NR_READS;
if (a->reads_ok)
reads_put(&r);
else
reads_result(&r);
}
ctrl_put();
return NULL;
}
static int test_race_start(void)
{
int live = 0, ebusy = 0;
open_tries = 4;
for (int i = 0; i < RACE_LOOPS; i++) {
struct server s = { .q = 0, .nr_tags = DEPTH };
struct start_arg a = {};
pthread_t t;
if (add_dev(1, 0))
return KSFT_FAIL;
server_start(&s);
pthread_barrier_init(&a.go, NULL, 2);
pthread_create(&t, NULL, race_start_fn, &a);
pthread_barrier_wait(&a.go);
ublk_ctrl_stop_dev(ctrl());
pthread_join(t, NULL);
cleanup();
pthread_join(s.thread, NULL);
if (a.ret == 0)
live++;
else if (a.ret == -EBUSY)
ebusy++;
if ((a.ret && a.ret != -EBUSY) || !a.reads_ok) {
fprintf(stderr, "loop %d: START_DEV %d, reads %s\n",
i, a.ret, a.reads_ok ? "ok" : "failed");
return KSFT_FAIL;
}
}
printf("%d loops: START_DEV won %d, got -EBUSY %d\n", RACE_LOOPS,
live, ebusy);
return KSFT_PASS;
}
static int test_race_fetch(void)
{
int live = 0, ebusy = 0;
for (int i = 0; i < RACE_LOOPS; i++) {
/* vary who goes first: STOP_DEV, or the server's open */
struct server s = { .q = 0, .nr_tags = DEPTH, .race = 1,
.race_delay_us = (i % 10) * 50 };
struct reads r = {};
int ret;
if (add_dev_noopen(1, 0))
return KSFT_FAIL;
server_start(&s);
pthread_barrier_wait(&s.go);
ublk_ctrl_stop_dev(ctrl());
pthread_barrier_wait(&s.fetched);
ret = ublk_ctrl_start_dev(ctrl(), getpid());
if (!ret) {
/* nothing was taken, so every read has to succeed */
live++;
if (reads_submit(&r, -1))
return KSFT_FAIL;
reads_reap(&r, 5);
if (r.ok != NR_READS) {
fprintf(stderr, "loop %d: live, but ", i);
reads_result(&r);
return KSFT_FAIL;
}
reads_put(&r);
} else if (ret == -EBUSY) {
ebusy++;
} else {
fprintf(stderr, "loop %d: START_DEV %d\n", i, ret);
return KSFT_FAIL;
}
cleanup();
pthread_join(s.thread, NULL);
}
printf("%d loops: START_DEV worked %d, got -EBUSY %d\n", RACE_LOOPS,
live, ebusy);
return KSFT_PASS;
}
static int test_race_async_fetch(void)
{
io_cmd_sqe_flags = IOSQE_ASYNC;
for (int i = 0; i < ASYNC_LOOPS; i++) {
struct async_arg a;
pthread_t t;
if (add_dev(1, 0))
return KSFT_FAIL;
pthread_barrier_init(&a.go, NULL, 2);
pthread_barrier_init(&a.stopped, NULL, 2);
pthread_create(&t, NULL, race_async_fn, &a);
pthread_barrier_wait(&a.go);
/* vary where STOP_DEV lands relative to the FETCHes */
usleep((i % 5) * 1000);
ublk_ctrl_stop_dev(ctrl());
pthread_barrier_wait(&a.stopped);
pthread_join(t, NULL);
cleanup();
}
printf("%d loops done\n", ASYNC_LOOPS);
return KSFT_PASS;
}
/*
* The stopped server is gone: reopen /dev/ublkcN, start a new server and
* the device, and read from it.
*/
static int restart_server(void)
{
struct server s = { .q = 0, .nr_tags = DEPTH };
struct reads r = {};
int ret;
close_cdev();
if (open_cdev())
return KSFT_FAIL;
server_start(&s);
usleep(200000);
if (s.failed) {
fprintf(stderr, "new server: FETCH failed %d\n", s.failed);
cleanup();
pthread_join(s.thread, NULL);
return KSFT_FAIL;
}
/* -EEXIST until the old disk is freed, e.g. after a udev probe */
for (int i = 0; i < 100; i++) {
ret = start_dev();
if (ret != -EEXIST)
break;
usleep(50000);
}
if (ret || reads_submit(&r, -1)) {
fprintf(stderr, "new server: START_DEV %d\n", ret);
ret = KSFT_FAIL;
} else {
reads_reap(&r, 10);
ret = reads_result(&r);
if (r.ok != NR_READS)
ret = KSFT_FAIL;
}
cleanup();
pthread_join(s.thread, NULL);
return ret;
}
/*
* stop_attached: STOP_DEV while a server has the device open but has
* fetched nothing stops that server too: its FETCH gets ABORT and
* START_DEV -EBUSY. Once it is gone, a new server can start the device.
*/
static int test_stop_attached(void)
{
struct server s = { .q = 0, .nr_tags = DEPTH };
int ret;
if (add_dev(1, 0))
return KSFT_FAIL;
ret = ublk_ctrl_stop_dev(ctrl());
printf("STOP_DEV: %d\n", ret);
server_start(&s);
ret = server_wait_failed(&s);
printf("FETCH after STOP_DEV: %d\n", ret);
if (ret != UBLK_IO_RES_ABORT) {
cleanup();
pthread_join(s.thread, NULL);
return KSFT_FAIL;
}
pthread_join(s.thread, NULL);
if (expect_err("START_DEV", start_dev(), -EBUSY))
return KSFT_FAIL;
return restart_server();
}
/*
* stop_live_restart: STOP_DEV on a live device; once its server is gone,
* a new server has to fetch and start it again.
*/
static int test_stop_live_restart(void)
{
struct server s = { .q = 0, .nr_tags = DEPTH };
int ret;
if (add_dev(1, 0))
return KSFT_FAIL;
server_start(&s);
if (expect_err("START_DEV", start_dev(), 0))
return KSFT_FAIL;
ret = ublk_ctrl_stop_dev(ctrl());
printf("STOP_DEV: %d\n", ret);
pthread_join(s.thread, NULL);
return restart_server();
}
/* first CPU which blk-mq maps to hw queue @q */
static int queue_cpu(int q)
{
char path[96];
FILE *f;
int cpu = -1;
snprintf(path, sizeof(path), "/sys/block/ublkb%d/mq/%d/cpu_list",
dev_id, q);
for (int i = 0; i < 100 && !(f = fopen(path, "r")); i++)
usleep(50000);
if (!f)
return -1;
if (fscanf(f, "%d", &cpu) != 1)
cpu = -1;
fclose(f);
return cpu;
}
static int test_recovery(void)
{
struct server q1 = { .q = 1, .nr_tags = DEPTH };
struct io_uring ring;
struct reads r = {};
int ret, cpu;
if (add_dev(NR_QUEUES, UBLK_F_USER_RECOVERY))
return KSFT_FAIL;
/* the first server: fetch everything, start, then die */
if (io_uring_queue_init(NR_QUEUES * DEPTH, &ring, 0))
return KSFT_FAIL;
for (int q = 0; q < NR_QUEUES; q++)
for (int tag = 0; tag < DEPTH; tag++)
queue_io_cmd(&ring, UBLK_U_IO_FETCH_REQ, q, tag, 0);
io_uring_submit(&ring);
if (start_dev())
return KSFT_FAIL;
cpu = queue_cpu(0);
if (cpu < 0) {
printf("no CPU maps to queue 0, can't aim the reads\n");
return KSFT_SKIP;
}
io_uring_queue_exit(&ring);
close_cdev();
for (int i = 0; i < 100 && dev_state() != UBLK_S_DEV_QUIESCED; i++)
usleep(50000);
printf("server exited, state %d (QUIESCED is %d)\n", dev_state(),
UBLK_S_DEV_QUIESCED);
for (int i = 0; i < 100; i++) {
ret = ublk_ctrl_start_user_recovery(ctrl());
if (ret != -EBUSY)
break;
usleep(50000);
}
printf("START_USER_RECOVERY: %d\n", ret);
if (ret || open_cdev())
return KSFT_FAIL;
/* queue 0 gets ready, then its task dies before queue 1 is ready */
if (fetch_and_die(0, 0, DEPTH))
return KSFT_FAIL;
printf("reads on cpu %d, which maps to queue 0\n", cpu);
if (reads_submit(&r, cpu))
return KSFT_FAIL;
server_start(&q1);
ret = ublk_ctrl_end_user_recovery(ctrl(), getpid());
printf("END_USER_RECOVERY: %d\n", ret);
ret = expect_err("END_USER_RECOVERY", ret, -EBUSY);
/*
* Requests held back on queue 0 wait for STOP_DEV to fail them.
* Close the disk before DEL_DEV, which waits for its last reference.
*/
reads_reap(&r, 1);
ublk_ctrl_stop_dev(ctrl());
reads_reap(&r, 5);
if (reads_result(&r))
ret = KSFT_FAIL;
cleanup();
pthread_join(q1.thread, NULL);
return ret;
}
static const struct {
const char *name;
int (*fn)(void);
} modes[] = {
{ "stop_start", test_stop_start },
{ "partial_fetch", test_partial_fetch },
{ "recovery", test_recovery },
{ "stop_restart", test_stop_restart },
{ "race_start", test_race_start },
{ "race_fetch", test_race_fetch },
{ "race_async_fetch", test_race_async_fetch },
{ "stop_attached", test_stop_attached },
{ "stop_live_restart", test_stop_live_restart },
};
int main(int argc, char **argv)
{
int (*fn)(void) = NULL;
int ret;
for (int i = 0; argc == 2 && i < ARRAY_SIZE(modes); i++)
if (!strcmp(argv[1], modes[i].name))
fn = modes[i].fn;
if (!fn) {
fprintf(stderr, "usage: %s MODE, modes:", argv[0]);
for (int i = 0; i < ARRAY_SIZE(modes); i++)
fprintf(stderr, " %s", modes[i].name);
fprintf(stderr, "\n");
return KSFT_FAIL;
}
/* keep the output of a run that ends in an oops */
setvbuf(stdout, NULL, _IOLBF, 0);
if (access(CTRL_DEV, F_OK)) {
perror(CTRL_DEV);
return KSFT_SKIP;
}
printf("%s\n", argv[1]);
ret = fn();
cleanup();
ctrl_put();
return ret;
}