On Fri, 18 Jul 2025 17:16:13 +0800, Jason Wang <jasow...@redhat.com> wrote:
> This patch introduces map operations for virtio device. Virtio use to
> use DMA API which is not necessarily the case since some devices
> doesn't do DMA. Instead of using tricks and abusing DMA API, let's
> simply abstract the current mapping logic into a virtio specific
> mapping operations. For the device or transport that doesn't do DMA,
> they can implement their own mapping logic without the need to trick
> DMA core. In this case the map_token is opaque to the virtio core that
> will be passed back to the transport or device specific map
> operations. For other devices, DMA API will still be used, so map
> token will still be the dma device to minimize the changeset and
> performance impact.
>
> The mapping operations are abstract as a independent structure instead
> of reusing virtio_config_ops. This allows the transport can simply
> reuse the structure for lower layers.
>
> A set of new mapping helpers were introduced for the device that want
> to do mapping by themselves.
>
> Reviewed-by: Christoph Hellwig <h...@lst.de>
> Signed-off-by: Jason Wang <jasow...@redhat.com>
> ---
>  drivers/virtio/virtio_ring.c  | 174 +++++++++++++++++++++++++++++-----
>  include/linux/virtio.h        |  22 +++++
>  include/linux/virtio_config.h |  68 +++++++++++++
>  3 files changed, 238 insertions(+), 26 deletions(-)
>
> diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c
> index 26ce8a3d6927..3f86e74dd79f 100644
> --- a/drivers/virtio/virtio_ring.c
> +++ b/drivers/virtio/virtio_ring.c
> @@ -298,8 +298,14 @@ size_t virtio_max_dma_size(const struct virtio_device 
> *vdev)
>  {
>       size_t max_segment_size = SIZE_MAX;
>
> -     if (vring_use_map_api(vdev))
> -             max_segment_size = dma_max_mapping_size(vdev->dev.parent);
> +     if (vring_use_map_api(vdev)) {
> +             if (vdev->map)
> +                     max_segment_size =
> +                             vdev->map->max_mapping_size(vdev->dev.parent);
> +             else
> +                     max_segment_size =
> +                             dma_max_mapping_size(vdev->dev.parent);
> +     }
>
>       return max_segment_size;
>  }
> @@ -310,8 +316,8 @@ static void *vring_alloc_queue(struct virtio_device 
> *vdev, size_t size,
>                              void *map_token)
>  {
>       if (vring_use_map_api(vdev)) {
> -             return dma_alloc_coherent(map_token, size,
> -                                       map_handle, flag);
> +             return virtqueue_map_alloc_coherent(vdev, map_token, size,
> +                                                 map_handle, flag);
>       } else {
>               void *queue = alloc_pages_exact(PAGE_ALIGN(size), flag);
>
> @@ -344,7 +350,8 @@ static void vring_free_queue(struct virtio_device *vdev, 
> size_t size,
>                            void *map_token)
>  {
>       if (vring_use_map_api(vdev))
> -             dma_free_coherent(map_token, size, queue, map_handle);
> +             virtqueue_map_free_coherent(vdev, map_token, size,
> +                                         queue, map_handle);
>       else
>               free_pages_exact(queue, PAGE_ALIGN(size));
>  }
> @@ -388,9 +395,9 @@ static int vring_map_one_sg(const struct vring_virtqueue 
> *vq, struct scatterlist
>        * the way it expects (we don't guarantee that the scatterlist
>        * will exist for the lifetime of the mapping).
>        */
> -     *addr = dma_map_page(vring_map_token(vq),
> -                         sg_page(sg), sg->offset, sg->length,
> -                         direction);
> +     *addr = virtqueue_map_page_attrs(&vq->vq, sg_page(sg),
> +                                      sg->offset, sg->length,
> +                                      direction, 0);
>
>       if (dma_mapping_error(vring_map_token(vq), *addr))
>               return -ENOMEM;
> @@ -454,11 +461,12 @@ static unsigned int vring_unmap_one_split(const struct 
> vring_virtqueue *vq,
>       } else if (!vring_need_unmap_buffer(vq, extra))
>               goto out;
>
> -     dma_unmap_page(vring_map_token(vq),
> -                    extra->addr,
> -                    extra->len,
> -                    (flags & VRING_DESC_F_WRITE) ?
> -                    DMA_FROM_DEVICE : DMA_TO_DEVICE);
> +     virtqueue_unmap_page_attrs(&vq->vq,
> +                                extra->addr,
> +                                extra->len,
> +                                (flags & VRING_DESC_F_WRITE) ?
> +                                DMA_FROM_DEVICE : DMA_TO_DEVICE,
> +                                0);
>
>  out:
>       return extra->next;
> @@ -1271,10 +1279,11 @@ static void vring_unmap_extra_packed(const struct 
> vring_virtqueue *vq,
>       } else if (!vring_need_unmap_buffer(vq, extra))
>               return;
>
> -     dma_unmap_page(vring_map_token(vq),
> -                    extra->addr, extra->len,
> -                    (flags & VRING_DESC_F_WRITE) ?
> -                    DMA_FROM_DEVICE : DMA_TO_DEVICE);
> +     virtqueue_unmap_page_attrs(&vq->vq,
> +                                extra->addr, extra->len,
> +                                (flags & VRING_DESC_F_WRITE) ?
> +                                DMA_FROM_DEVICE : DMA_TO_DEVICE,
> +                                0);
>  }
>
>  static struct vring_packed_desc *alloc_indirect_packed(unsigned int total_sg,
> @@ -3121,6 +3130,105 @@ const struct vring *virtqueue_get_vring(const struct 
> virtqueue *vq)
>  }
>  EXPORT_SYMBOL_GPL(virtqueue_get_vring);
>
> +/**
> + * virtqueue_map_alloc_coherent - alloc coherent mapping
> + * @vdev: the virtio device we are talking to
> + * @token: device specific mapping token
> + * @size: the size of the buffer
> + * @map_handle: the pointer to the mapped adress
> + * @gfp: allocation flag (GFP_XXX)
> + *
> + * return virtual address or NULL on error
> + */
> +void *virtqueue_map_alloc_coherent(struct virtio_device *vdev,
> +                                void *map_token, size_t size,
> +                                dma_addr_t *map_handle, gfp_t gfp)
> +{
> +     if (vdev->map)
> +             return vdev->map->alloc(map_token, size, map_handle, gfp);
> +     else
> +             return dma_alloc_coherent(map_token, size,
> +                                       map_handle, gfp);
> +}
> +EXPORT_SYMBOL_GPL(virtqueue_map_alloc_coherent);
> +
> +/**
> + * virtqueue_map_free_coherent - free coherent mapping
> + * @vdev: the virtio device we are talking to
> + * @token: device specific mapping token
> + * @size: the size of the buffer
> + * @map_handle: the mapped address that needs to be freed
> + *
> + */
> +void virtqueue_map_free_coherent(struct virtio_device *vdev,
> +                              void *map_token, size_t size, void *vaddr,
> +                              dma_addr_t map_handle)
> +{
> +     if (vdev->map)
> +             vdev->map->free(map_token, size, vaddr, map_handle, 0);
> +     else
> +             dma_free_coherent(map_token, size, vaddr, map_handle);
> +}
> +EXPORT_SYMBOL_GPL(virtqueue_map_free_coherent);
> +
> +/**
> + * virtqueue_map_page_attrs - map a page to the device
> + * @_vq: the virtqueue we are talking to
> + * @page: the page that will be mapped by the device
> + * @offset: the offset in the page for a buffer
> + * @size: the buffer size
> + * @dir: mapping direction
> + * @attrs: mapping attributes
> + *
> + * Returns mapped address. Caller should check that by 
> virtqueue_mapping_error().
> + */
> +dma_addr_t virtqueue_map_page_attrs(const struct virtqueue *_vq,
> +                                 struct page *page,
> +                                 unsigned long offset,
> +                                 size_t size,
> +                                 enum dma_data_direction dir,
> +                                 unsigned long attrs)
> +{
> +     const struct vring_virtqueue *vq = to_vvq(_vq);
> +     struct virtio_device *vdev = _vq->vdev;
> +     void *map_token = vring_map_token(vq);
> +
> +     if (vdev->map)
> +             return vdev->map->map_page(map_token,
> +                                        page, offset, size,
> +                                        dir, attrs);
> +
> +     return dma_map_page_attrs(map_token,
> +                               page, offset, size,
> +                               dir, attrs);
> +}
> +EXPORT_SYMBOL_GPL(virtqueue_map_page_attrs);
> +
> +/**
> + * virtqueue_unmap_page_attrs - map a page to the device
> + * @_vq: the virtqueue we are talking to
> + * @map_handle: the mapped address
> + * @size: the buffer size
> + * @dir: mapping direction
> + * @attrs: unmapping attributes
> + */
> +void virtqueue_unmap_page_attrs(const struct virtqueue *_vq,
> +                             dma_addr_t map_handle,
> +                             size_t size, enum dma_data_direction dir,
> +                             unsigned long attrs)
> +{
> +     const struct vring_virtqueue *vq = to_vvq(_vq);
> +     struct virtio_device *vdev = _vq->vdev;
> +     void *map_token = vring_map_token(vq);
> +
> +     if (vdev->map)
> +             vdev->map->unmap_page(map_token, map_handle,
> +                                   size, dir, attrs);
> +     else
> +             dma_unmap_page_attrs(map_token, map_handle, size, dir, attrs);


How about setting dma_unmap_page_attrs as the default value of 
vdev->map->unmap_page?

Then we can call vdev->map->unmap_page directly.

Thanks.


> +}
> +EXPORT_SYMBOL_GPL(virtqueue_unmap_page_attrs);
> +
>  /**
>   * virtqueue_map_single_attrs - map DMA for _vq
>   * @_vq: the struct virtqueue we're talking about.
> @@ -3132,7 +3240,7 @@ EXPORT_SYMBOL_GPL(virtqueue_get_vring);
>   * The caller calls this to do dma mapping in advance. The DMA address can be
>   * passed to this _vq when it is in pre-mapped mode.
>   *
> - * return DMA address. Caller should check that by virtqueue_mapping_error().
> + * return mapped address. Caller should check that by 
> virtqueue_mapping_error().
>   */
>  dma_addr_t virtqueue_map_single_attrs(const struct virtqueue *_vq, void *ptr,
>                                     size_t size,
> @@ -3151,8 +3259,8 @@ dma_addr_t virtqueue_map_single_attrs(const struct 
> virtqueue *_vq, void *ptr,
>                         "rejecting DMA map of vmalloc memory\n"))
>               return DMA_MAPPING_ERROR;
>
> -     return dma_map_page_attrs(vring_map_token(vq), virt_to_page(ptr),
> -                               offset_in_page(ptr), size, dir, attrs);
> +     return virtqueue_map_page_attrs(&vq->vq, virt_to_page(ptr),
> +                                     offset_in_page(ptr), size, dir, attrs);
>  }
>  EXPORT_SYMBOL_GPL(virtqueue_map_single_attrs);
>
> @@ -3177,7 +3285,7 @@ void virtqueue_unmap_single_attrs(const struct 
> virtqueue *_vq,
>       if (!vq->use_map_api)
>               return;
>
> -     dma_unmap_page_attrs(vring_map_token(vq), addr, size, dir, attrs);
> +     virtqueue_unmap_page_attrs(_vq, addr, size, dir, attrs);
>  }
>  EXPORT_SYMBOL_GPL(virtqueue_unmap_single_attrs);
>
> @@ -3212,11 +3320,16 @@ EXPORT_SYMBOL_GPL(virtqueue_map_mapping_error);
>  bool virtqueue_map_need_sync(const struct virtqueue *_vq, dma_addr_t addr)
>  {
>       const struct vring_virtqueue *vq = to_vvq(_vq);
> +     struct virtio_device *vdev = _vq->vdev;
> +     void *token = vring_map_token(vq);
>
>       if (!vq->use_map_api)
>               return false;
>
> -     return dma_need_sync(vring_map_token(vq), addr);
> +     if (vdev->map)
> +             return vdev->map->need_sync(token, addr);
> +     else
> +             return dma_need_sync(token, addr);
>  }
>  EXPORT_SYMBOL_GPL(virtqueue_map_need_sync);
>
> @@ -3238,12 +3351,16 @@ void virtqueue_map_sync_single_range_for_cpu(const 
> struct virtqueue *_vq,
>                                            enum dma_data_direction dir)
>  {
>       const struct vring_virtqueue *vq = to_vvq(_vq);
> -     struct device *dev = vring_map_token(vq);
> +     struct virtio_device *vdev = _vq->vdev;
> +     void *token = vring_map_token(vq);
>
>       if (!vq->use_map_api)
>               return;
>
> -     dma_sync_single_range_for_cpu(dev, addr, offset, size, dir);
> +     if (vdev->map)
> +             vdev->map->sync_single_for_cpu(token, addr + offset, size, dir);
> +     else
> +             dma_sync_single_range_for_cpu(token, addr, offset, size, dir);
>  }
>  EXPORT_SYMBOL_GPL(virtqueue_map_sync_single_range_for_cpu);
>
> @@ -3264,12 +3381,17 @@ void virtqueue_map_sync_single_range_for_device(const 
> struct virtqueue *_vq,
>                                               enum dma_data_direction dir)
>  {
>       const struct vring_virtqueue *vq = to_vvq(_vq);
> -     struct device *dev = vring_map_token(vq);
> +     struct virtio_device *vdev = _vq->vdev;
> +     void *token = vring_map_token(vq);
>
>       if (!vq->use_map_api)
>               return;
>
> -     dma_sync_single_range_for_device(dev, addr, offset, size, dir);
> +     if (vdev->map)
> +             vdev->map->sync_single_for_device(token, addr + offset,
> +                                               size, dir);
> +     else
> +             dma_sync_single_range_for_device(token, addr, offset, size, 
> dir);
>  }
>  EXPORT_SYMBOL_GPL(virtqueue_map_sync_single_range_for_device);
>
> diff --git a/include/linux/virtio.h b/include/linux/virtio.h
> index 88de0e368e01..d252939d83dc 100644
> --- a/include/linux/virtio.h
> +++ b/include/linux/virtio.h
> @@ -159,6 +159,7 @@ struct virtio_device {
>       struct virtio_device_id id;
>       const struct virtio_config_ops *config;
>       const struct vringh_config_ops *vringh_config;
> +     const struct virtio_map_ops *map;
>       struct list_head vqs;
>       u64 features;
>       void *priv;
> @@ -264,6 +265,27 @@ void unregister_virtio_driver(struct virtio_driver *drv);
>       module_driver(__virtio_driver, register_virtio_driver, \
>                       unregister_virtio_driver)
>
> +
> +void *virtqueue_map_alloc_coherent(struct virtio_device *vdev,
> +                                void *map_token, size_t size,
> +                                dma_addr_t *dma_handle, gfp_t gfp);
> +
> +void virtqueue_map_free_coherent(struct virtio_device *vdev,
> +                              void *map_token, size_t size, void *vaddr,
> +                              dma_addr_t dma_handle);
> +
> +dma_addr_t virtqueue_map_page_attrs(const struct virtqueue *_vq,
> +                                 struct page *page,
> +                                 unsigned long offset,
> +                                 size_t size,
> +                                 enum dma_data_direction dir,
> +                                 unsigned long attrs);
> +
> +void virtqueue_unmap_page_attrs(const struct virtqueue *_vq,
> +                             dma_addr_t dma_handle,
> +                             size_t size, enum dma_data_direction dir,
> +                             unsigned long attrs);
> +
>  dma_addr_t virtqueue_map_single_attrs(const struct virtqueue *_vq, void 
> *ptr, size_t size,
>                                         enum dma_data_direction dir, unsigned 
> long attrs);
>  void virtqueue_unmap_single_attrs(const struct virtqueue *_vq, dma_addr_t 
> addr,
> diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h
> index 861198a74be2..b4f31afa0726 100644
> --- a/include/linux/virtio_config.h
> +++ b/include/linux/virtio_config.h
> @@ -139,6 +139,74 @@ struct virtio_config_ops {
>       int (*enable_vq_after_reset)(struct virtqueue *vq);
>  };
>
> +/**
> + * struct virtio_map_ops - operations for mapping buffer for a virtio device
> + * Note: For transport that has its own mapping logic it must
> + * implements all of the operations
> + * @map_page: map a buffer to the device
> + *      token: device specific mapping token
> + *      page: the page that will be mapped by the device
> + *      offset: the offset in the page for a buffer
> + *      size: the buffer size
> + *      dir: mapping direction
> + *      attrs: mapping attributes
> + *      Returns: the mapped address
> + * @unmap_page: unmap a buffer from the device
> + *      token: device specific mapping token
> + *      map_handle: the mapped address
> + *      size: the buffer size
> + *      dir: mapping direction
> + *      attrs: unmapping attributes
> + * @sync_single_for_cpu: sync a single buffer from device to cpu
> + *      token: device specific mapping token
> + *      map_handle: the mapping adress to sync
> + *      size: the size of the buffer
> + *      dir: synchronization direction
> + * @sync_single_for_device: sync a single buffer from cpu to device
> + *      token: device specific mapping token
> + *      map_handle: the mapping adress to sync
> + *      size: the size of the buffer
> + *      dir: synchronization direction
> + * @alloc: alloc a coherent buffer mapping
> + *      token: device specific mapping token
> + *      size: the size of the buffer
> + *      map_handle: the mapping adress to sync
> + *      gfp: allocation flag (GFP_XXX)
> + *      Returns: virtual address of the allocated buffer
> + * @free: free a coherent buffer mapping
> + *      token: device specific mapping token
> + *      size: the size of the buffer
> + *      vaddr: virtual address of the buffer
> + *      map_handle: the mapping adress to sync
> + *      attrs: unmapping attributes
> + * @need_sync: if the buffer needs synchronization
> + *      token: device specific mapping token
> + *      map_handle: the mapped address
> + *      Returns: whether the buffer needs synchronization
> + * @max_mapping_size: get the maximum buffer size that can be mapped
> + *      token: device specific mapping token
> + *      Returns: the maximum buffer size that can be mapped
> + */
> +struct virtio_map_ops {
> +     dma_addr_t (*map_page)(void *token, struct page *page,
> +                            unsigned long offset, size_t size,
> +                            enum dma_data_direction dir, unsigned long 
> attrs);
> +     void (*unmap_page)(void *token, dma_addr_t map_handle,
> +                        size_t size, enum dma_data_direction dir,
> +                        unsigned long attrs);
> +     void (*sync_single_for_cpu)(void *token, dma_addr_t map_handle,
> +                                 size_t size, enum dma_data_direction dir);
> +     void (*sync_single_for_device)(void *token,
> +                                    dma_addr_t map_handle, size_t size,
> +                                    enum dma_data_direction dir);
> +     void *(*alloc)(void *token, size_t size,
> +                    dma_addr_t *map_handle, gfp_t gfp);
> +     void (*free)(void *token, size_t size, void *vaddr,
> +                  dma_addr_t map_handle, unsigned long attrs);
> +     bool (*need_sync)(void *token, dma_addr_t map_handle);
> +     size_t (*max_mapping_size)(void *token);
> +};
> +
>  /* If driver didn't advertise the feature, it will never appear. */
>  void virtio_check_driver_offered_feature(const struct virtio_device *vdev,
>                                        unsigned int fbit);
> --
> 2.47.3
>

Reply via email to