| /* SPDX-License-Identifier: GPL-2.0 */ | |
| #include "kublk.h" | |
| #ifndef IORING_NOP_INJECT_RESULT | |
| #define IORING_NOP_INJECT_RESULT (1U << 0) | |
| #endif | |
| #ifndef IORING_NOP_FIXED_BUFFER | |
| #define IORING_NOP_FIXED_BUFFER (1U << 3) | |
| #endif | |
| static int ublk_null_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev) | |
| { | |
| const struct ublksrv_ctrl_dev_info *info = &dev->dev_info; | |
| unsigned long dev_size = 250UL << 30; | |
| dev->tgt.dev_size = dev_size; | |
| dev->tgt.params = (struct ublk_params) { | |
| .types = UBLK_PARAM_TYPE_BASIC | UBLK_PARAM_TYPE_DMA_ALIGN | | |
| UBLK_PARAM_TYPE_SEGMENT, | |
| .basic = { | |
| .logical_bs_shift = 9, | |
| .physical_bs_shift = 12, | |
| .io_opt_shift = 12, | |
| .io_min_shift = 9, | |
| .max_sectors = info->max_io_buf_bytes >> 9, | |
| .dev_sectors = dev_size >> 9, | |
| }, | |
| .dma = { | |
| .alignment = 4095, | |
| }, | |
| .seg = { | |
| .seg_boundary_mask = 4095, | |
| .max_segment_size = 32 << 10, | |
| .max_segments = 32, | |
| }, | |
| }; | |
| ublk_set_integrity_params(ctx, &dev->tgt.params); | |
| if (info->flags & UBLK_F_SUPPORT_ZERO_COPY) | |
| dev->tgt.sq_depth = dev->tgt.cq_depth = 2 * info->queue_depth; | |
| return 0; | |
| } | |
| static void __setup_nop_io(int tag, const struct ublksrv_io_desc *iod, | |
| struct io_uring_sqe *sqe, int q_id, unsigned buf_idx) | |
| { | |
| unsigned ublk_op = ublksrv_get_op(iod); | |
| io_uring_prep_nop(sqe); | |
| sqe->buf_index = buf_idx; | |
| sqe->flags |= IOSQE_FIXED_FILE; | |
| sqe->rw_flags = IORING_NOP_FIXED_BUFFER | IORING_NOP_INJECT_RESULT; | |
| sqe->len = iod->nr_sectors << 9; /* injected result */ | |
| sqe->user_data = build_user_data(tag, ublk_op, 0, q_id, 1); | |
| } | |
| static int null_queue_zc_io(struct ublk_thread *t, struct ublk_queue *q, | |
| int tag) | |
| { | |
| const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag); | |
| struct io_uring_sqe *sqe[3]; | |
| unsigned short buf_idx = ublk_io_buf_idx(t, q, tag); | |
| ublk_io_alloc_sqes(t, sqe, 3); | |
| io_uring_prep_buf_register(sqe[0], q, tag, q->q_id, buf_idx); | |
| sqe[0]->user_data = build_user_data(tag, | |
| ublk_cmd_op_nr(sqe[0]->cmd_op), 0, q->q_id, 1); | |
| sqe[0]->flags |= IOSQE_CQE_SKIP_SUCCESS | IOSQE_IO_HARDLINK; | |
| __setup_nop_io(tag, iod, sqe[1], q->q_id, buf_idx); | |
| sqe[1]->flags |= IOSQE_IO_HARDLINK; | |
| io_uring_prep_buf_unregister(sqe[2], q, tag, q->q_id, buf_idx); | |
| sqe[2]->user_data = build_user_data(tag, ublk_cmd_op_nr(sqe[2]->cmd_op), 0, q->q_id, 1); | |
| // buf register is marked as IOSQE_CQE_SKIP_SUCCESS | |
| return 2; | |
| } | |
| static int null_queue_auto_zc_io(struct ublk_thread *t, struct ublk_queue *q, | |
| int tag) | |
| { | |
| const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag); | |
| struct io_uring_sqe *sqe[1]; | |
| ublk_io_alloc_sqes(t, sqe, 1); | |
| __setup_nop_io(tag, iod, sqe[0], q->q_id, ublk_io_buf_idx(t, q, tag)); | |
| return 1; | |
| } | |
| static void ublk_null_io_done(struct ublk_thread *t, struct ublk_queue *q, | |
| const struct io_uring_cqe *cqe) | |
| { | |
| unsigned tag = user_data_to_tag(cqe->user_data); | |
| unsigned op = user_data_to_op(cqe->user_data); | |
| struct ublk_io *io = ublk_get_io(q, tag); | |
| if (cqe->res < 0 || op != ublk_cmd_op_nr(UBLK_U_IO_UNREGISTER_IO_BUF)) { | |
| if (!io->result) | |
| io->result = cqe->res; | |
| if (cqe->res < 0) | |
| ublk_err("%s: io failed op %x user_data %lx\n", | |
| __func__, op, cqe->user_data); | |
| } | |
| /* buffer register op is IOSQE_CQE_SKIP_SUCCESS */ | |
| if (op == ublk_cmd_op_nr(UBLK_U_IO_REGISTER_IO_BUF)) | |
| io->tgt_ios += 1; | |
| if (ublk_completed_tgt_io(t, q, tag)) | |
| ublk_complete_io(t, q, tag, io->result); | |
| } | |
| static int ublk_null_queue_io(struct ublk_thread *t, struct ublk_queue *q, | |
| int tag) | |
| { | |
| const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag); | |
| unsigned auto_zc = ublk_queue_use_auto_zc(q); | |
| unsigned zc = ublk_queue_use_zc(q); | |
| int queued; | |
| if (auto_zc && !ublk_io_auto_zc_fallback(iod)) | |
| queued = null_queue_auto_zc_io(t, q, tag); | |
| else if (zc) | |
| queued = null_queue_zc_io(t, q, tag); | |
| else { | |
| ublk_complete_io(t, q, tag, iod->nr_sectors << 9); | |
| return 0; | |
| } | |
| ublk_queued_tgt_io(t, q, tag, queued); | |
| return 0; | |
| } | |
| /* | |
| * return invalid buffer index for triggering auto buffer register failure, | |
| * then UBLK_IO_RES_NEED_REG_BUF handling is covered | |
| */ | |
| static unsigned short ublk_null_buf_index(const struct ublk_thread *t, | |
| const struct ublk_queue *q, int tag) | |
| { | |
| if (ublk_queue_auto_zc_fallback(q)) | |
| return (unsigned short)-1; | |
| return ublk_io_buf_idx(t, q, tag); | |
| } | |
| const struct ublk_tgt_ops null_tgt_ops = { | |
| .name = "null", | |
| .init_tgt = ublk_null_tgt_init, | |
| .queue_io = ublk_null_queue_io, | |
| .tgt_io_done = ublk_null_io_done, | |
| .buf_index = ublk_null_buf_index, | |
| }; |