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.
>
> 2. We have to pin scsit_pci_epf_poll_cfg_thread() on a CPU in order to
> respond fast enough to the host. This means we effectivly burn a CPU
> to read and write some values. But as there are no intterupts
> generated on these events and we need to be very quick there isn't
> another option.
>
> Assisted-by: Devin:claude-opus-4-8
> Signed-off-by: Alistair Francis <[email protected]>
> ---
(snip)
> +static int scsit_pci_epf_dma_transfer(struct scsit_pci_epf *scsi_epf,
> + struct scsit_pci_epf_segment *seg, enum dma_data_direction dir)
> +{
> + struct pci_epf *epf = scsi_epf->epf;
> + struct dma_async_tx_descriptor *desc;
> + struct dma_slave_config sconf = {};
> + struct device *dev = &epf->dev;
> + struct device *dma_dev;
> + struct dma_chan *chan;
> + dma_cookie_t cookie;
> + dma_addr_t dma_addr;
> + struct mutex *lock;
> + int ret;
> +
> + switch (dir) {
> + case DMA_FROM_DEVICE:
> + lock = &scsi_epf->dma_rx_lock;
> + chan = scsi_epf->dma_rx_chan;
> + sconf.direction = DMA_DEV_TO_MEM;
> + sconf.src_addr = seg->pci_addr;
> + break;
> + case DMA_TO_DEVICE:
> + lock = &scsi_epf->dma_tx_lock;
> + chan = scsi_epf->dma_tx_chan;
> + sconf.direction = DMA_MEM_TO_DEV;
> + sconf.dst_addr = seg->pci_addr;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + mutex_lock(lock);
You should not need to take the mutex if you instead use the
dmaengine_prep_config_single_safe() API, see:
a0fba0a49f77 ("nvmet: pci-epf: Use dmaengine_prep_config_single_safe() API")
> +
> + dma_dev = dmaengine_get_dma_device(chan);
> + dma_addr = dma_map_single(dma_dev, seg->buf, seg->length, dir);
> + ret = dma_mapping_error(dma_dev, dma_addr);
> + if (ret)
> + goto unlock;
> +
> + ret = dmaengine_slave_config(chan, &sconf);
> + if (ret) {
> + dev_err(dev, "Failed to configure DMA channel\n");
> + goto unmap;
> + }
> +
> + desc = dmaengine_prep_slave_single(chan, dma_addr, seg->length,
> + sconf.direction, DMA_CTRL_ACK);
> + if (!desc) {
> + dev_err(dev, "Failed to prepare DMA\n");
> + ret = -EIO;
> + goto unmap;
> + }
> +
> + cookie = dmaengine_submit(desc);
> + ret = dma_submit_error(cookie);
> + if (ret) {
> + dev_err(dev, "Failed to do DMA submit (err=%d)\n", ret);
> + goto unmap;
> + }
> +
> + if (dma_sync_wait(chan, cookie) != DMA_COMPLETE) {
> + dev_err(dev, "DMA transfer failed\n");
> + ret = -EIO;
> + }
> +
> + dmaengine_terminate_sync(chan);
I don't think dmaengine_terminate_sync() should be called on each DMA transfer,
see:
bd00d2c4a1b2 ("nvmet: pci-epf: Remove unnecessary dmaengine_terminate_sync() on
each DMA transfer")
Kind regards,
Niklas