On Tue, Sep 01, 2026 at 11:46:50AM +1000, [email protected] wrote:
> From: Alistair Francis <[email protected]>
>
> This patch adds a VirtIO SCSI endpoint build on top of the VirtIO PCIe
> endpoint. This is a similar approach to the NVMe PCIe Endpoint
> (drivers/nvme/target/pci-epf.c) but for SCSI.
>
> This does end up being somewhat similar to the pci-epf.c code, but
> re-written for SCSI.
>
> This approach allows a PCIe Endpoint device (tested on a
> radxa-rock5b) to setup what appears to be a SCSI device, using an
> existing SCSI backend (tested using scsi_debug).
>
> At this point a host can connect over PCIe, ensure virtio_pci and
> virtio_scsi is loaded and on PCIe rescan will see a scsi device.
>
> There are two main pain points with this approach though:
> 1. We have to use the Legacy SCSI VirtIO driver. This is because the
> Raxda Rock5b (and AFAIK all PCIe Endpoint hardware) can't add
> capabilities. So we can't advertise the VirtIO Common configuration
> capability, which means we can't be a modern VirtIO SCSI device.
>
> This is unfortunate, but there doesn't seem to be any way around
> this, at least with the current hardware.
This was discussed on the virtio list. The way around this is
to change virtio spec to allow common configuration
in memory. I don't think a patch like this was ever posted -
want to try?
....
> +
> +static int scsit_pci_epf_read_desc(struct scsit_pci_epf_vq *vq,
> + u16 idx, struct vring_desc *desc)
> +{
> + void __iomem *p;
> +
> + if (idx >= vq->depth)
> + return -EINVAL;
> +
> + p = vq->desc_map.virt_addr + (size_t)idx * sizeof(struct vring_desc);
> + desc->addr = cpu_to_le64(readq(p + offsetof(struct vring_desc, addr)));
> + desc->len = cpu_to_le32(readl(p + offsetof(struct vring_desc, len)));
> + desc->flags = cpu_to_le16(readw(p + offsetof(struct vring_desc,
> flags)));
> + desc->next = cpu_to_le16(readw(p + offsetof(struct vring_desc, next)));
> +
> + return 0;
> +}
> +
> +static int scsit_pci_epf_read_indirect(struct scsit_pci_epf_ctrl *ctrl,
> + const struct vring_desc *parent,
> + struct vring_desc **out_descs,
> + unsigned int *out_n)
> +{
> + struct vring_desc *descs;
> + u32 len = le32_to_cpu(parent->len);
> + unsigned int n;
> + int ret;
> +
> + if (len % sizeof(struct vring_desc) || !len)
> + return -EINVAL;
> +
> + n = len / sizeof(struct vring_desc);
> + descs = kmalloc_array(n, sizeof(*descs), GFP_KERNEL);
> + if (!descs)
> + return -ENOMEM;
> +
> + ret = scsit_pci_epf_transfer(ctrl, descs, le64_to_cpu(parent->addr),
> + len, DMA_FROM_DEVICE);
> + if (ret) {
> + kfree(descs);
> + return ret;
> + }
> +
> + *out_descs = descs;
> + *out_n = n;
> + return 0;
> +}
> +
> +struct scsit_pci_epf_chain {
> + struct vring_desc *ro;
> + unsigned int nr_ro;
> + u32 ro_len;
> + struct vring_desc *wo;
> + unsigned int nr_wo;
> + u32 wo_len;
> +
> + struct vring_desc *indirect;
> +};
> +
> +static void scsit_pci_epf_chain_free(struct scsit_pci_epf_chain *c)
> +{
> + kfree(c->ro);
> + c->ro = NULL;
> + kfree(c->wo);
> + c->wo = NULL;
> + kfree(c->indirect);
> + c->indirect = NULL;
> +}
> +
> +static int scsit_pci_epf_walk_chain(struct scsit_pci_epf_ctrl *ctrl,
> + struct scsit_pci_epf_vq *vq,
> + u16 head_idx,
> + struct scsit_pci_epf_chain *out)
> +{
> + struct vring_desc desc, *descs = NULL;
> + unsigned int n = 0, i = 0, cap_ro = 8, cap_wo = 8;
> + bool in_indirect = false;
> + int ret;
> +
> + memset(out, 0, sizeof(*out));
> +
> + out->ro = kmalloc_array(cap_ro, sizeof(*out->ro), GFP_KERNEL);
> + out->wo = kmalloc_array(cap_wo, sizeof(*out->wo), GFP_KERNEL);
> + if (!out->ro || !out->wo) {
> + ret = -ENOMEM;
> + goto err;
> + }
> +
> + ret = scsit_pci_epf_read_desc(vq, head_idx, &desc);
> + if (ret)
> + goto err;
> +
> + /* Reject obviously-invalid head descriptors */
> + if (!le64_to_cpu(desc.addr) && !le32_to_cpu(desc.len) &&
> + !le16_to_cpu(desc.flags)) {
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + for (;;) {
> + u16 flags = le16_to_cpu(desc.flags);
> +
> + if (!in_indirect && (flags & VRING_DESC_F_INDIRECT)) {
> + ret = scsit_pci_epf_read_indirect(ctrl, &desc,
> + &descs, &n);
> + if (ret)
> + goto err;
> + out->indirect = descs;
> + in_indirect = true;
> + i = 0;
> + desc = descs[0];
> + continue;
> + }
> +
> + if (flags & VRING_DESC_F_WRITE) {
> + if (out->nr_wo == cap_wo) {
> + cap_wo *= 2;
> + out->wo = krealloc_array(out->wo, cap_wo,
> + sizeof(*out->wo), GFP_KERNEL);
> + if (!out->wo) {
> + ret = -ENOMEM;
> + goto err;
> + }
> + }
> + out->wo[out->nr_wo++] = desc;
> + out->wo_len += le32_to_cpu(desc.len);
> + } else {
> + /* Read-only descriptors must precede write-only. */
> + if (out->nr_wo) {
> + ret = -EINVAL;
> + goto err;
> + }
> + if (out->nr_ro == cap_ro) {
> + cap_ro *= 2;
> + out->ro = krealloc_array(out->ro, cap_ro,
> + sizeof(*out->ro), GFP_KERNEL);
> + if (!out->ro) {
> + ret = -ENOMEM;
> + goto err;
> + }
> + }
> + out->ro[out->nr_ro++] = desc;
> + out->ro_len += le32_to_cpu(desc.len);
> + }
> +
> + if (!(flags & VRING_DESC_F_NEXT))
> + break;
> +
> + if (in_indirect) {
> + i = le16_to_cpu(desc.next);
> + if (i >= n) {
> + ret = -EINVAL;
> + goto err;
> + }
> + desc = descs[i];
> + } else {
> + u16 next = le16_to_cpu(desc.next);
> +
> + ret = scsit_pci_epf_read_desc(vq, next, &desc);
> + if (ret)
> + goto err;
> + }
> + }
> +
> + return 0;
> +
> +err:
> + scsit_pci_epf_chain_free(out);
> + return ret;
> +}
> +
> +static int scsit_pci_epf_chain_to_segs(struct scsit_pci_epf_cmd *cmd,
> + const struct vring_desc *descs,
> + unsigned int n)
> +{
> + struct scsit_pci_epf_segment *seg;
> + unsigned int i;
> + int ret;
> +
> + if (n == 0)
> + return 0;
> +
> + if (n == 1) {
> + cmd->nr_data_segs = 1;
> + cmd->data_segs = &cmd->data_seg;
> + seg = &cmd->data_segs[0];
> + seg->pci_addr = le64_to_cpu(descs[0].addr);
> + seg->length = le32_to_cpu(descs[0].len);
> + return 0;
> + }
> +
> + ret = scsit_pci_epf_alloc_cmd_data_segs(cmd, n);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < n; i++) {
> + seg = &cmd->data_segs[i];
> + seg->pci_addr = le64_to_cpu(descs[i].addr);
> + seg->length = le32_to_cpu(descs[i].len);
> + }
> +
> + return 0;
> +}
> +
> +static int scsit_pci_epf_alloc_cmd_data_buf(struct scsit_pci_epf_cmd *cmd)
> +{
> + struct scsit_pci_epf_ctrl *ctrl = cmd->ctrl;
> + struct scsit_pci_epf_segment *seg;
> + struct scatterlist *sg;
> + int ret, i;
> +
> + if (cmd->se_cmd.data_length > ctrl->mdts)
> + return -EINVAL;
> +
> + if (cmd->nr_data_segs == 1) {
> + sg_init_table(&cmd->data_sgl, 1);
> + cmd->data_sgt.sgl = &cmd->data_sgl;
> + cmd->data_sgt.nents = 1;
> + cmd->data_sgt.orig_nents = 1;
> + } else {
> + ret = sg_alloc_table(&cmd->data_sgt, cmd->nr_data_segs,
> + GFP_KERNEL);
> + if (ret)
> + return ret;
> + }
> +
> + for_each_sgtable_sg(&cmd->data_sgt, sg, i) {
> + seg = &cmd->data_segs[i];
> + seg->buf = kmalloc(seg->length, GFP_KERNEL);
> + if (!seg->buf)
> + return -ENOMEM;
> + sg_set_buf(sg, seg->buf, seg->length);
> + }
> +
> + return 0;
> +}
> +
> +static void scsit_pci_epf_post_used(struct scsit_pci_epf_vq *vq,
> + u16 head_idx, u32 len)
> +{
> + void __iomem *ring = vq->used_map.virt_addr +
> + offsetof(struct vring_used, ring) +
> + (size_t)(vq->next_used_idx & (vq->depth - 1)) *
> + sizeof(struct vring_used_elem);
> +
> + writel(head_idx, ring + offsetof(struct vring_used_elem, id));
> + writel(len, ring + offsetof(struct vring_used_elem, len));
> +
> + vq->next_used_idx++;
> + writew(vq->next_used_idx,
> + vq->used_map.virt_addr + offsetof(struct vring_used, idx));
> +}
So this is a reimplementation of virtio, and a weird one, for example
features are legacy but format is LE like modern. Packed ring is
not implemented. Uses strongly ordered ops for everything.
I don't have specific advice but there is some similarity here
to what Alex Graf is doing.
And I want to see a spec decumenting how all this is supposed
to work, please, and not just an ad hoc implementation.
--
MST