On Wed, Sep 16, 2026 at 11:10:51PM +0200, Francesco Valla wrote:
> Depending on the driver originating them, data buffers used for virtio
> communication can either:
> 
>   - already be allocated from the coherent memory area that is
>     accessible by the remote processor; this is the case of rpmsg
>     and the rproc flavor of virtio-console;
>   - be allocated from generic kmem, and thus not accessible directly by
>     the remote processor.
> 
> Exploiting the map operations, which are used by the virtio framework
> when VIRTIO_F_ACCESS_PLATFORM is part of a vdev's feature flags, add
> bounce buffering for the second case: when the map() callback is called
> for a buffer, one or more pages of coherent memory are allocated and
> data is copied to them, then they are exposed to the remote processor;
> the data is  then bounced back on unmap().
> 
> The first case is not impacted, since buffers already suitable for
> remote transmission are passed through.
> 
> With the bounce buffering in place, any kind of virtio device can be
> supported through the remoteproc-virtio transport, at least from a
> data exchange standpoint.

Is this _necessary_ for the imx93 platform you are implementing feature for?

> 
> Signed-off-by: Francesco Valla <[email protected]>
> ---
>  drivers/remoteproc/remoteproc_virtio.c | 182 
> +++++++++++++++++++++++++++++++--
>  include/linux/remoteproc.h             |  14 +++
>  2 files changed, 190 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/remoteproc/remoteproc_virtio.c 
> b/drivers/remoteproc/remoteproc_virtio.c
> index cfd66d9d1c9e..d21b3b8044df 100644
> --- a/drivers/remoteproc/remoteproc_virtio.c
> +++ b/drivers/remoteproc/remoteproc_virtio.c
> @@ -241,7 +241,14 @@ static void rproc_virtio_reset(struct virtio_device 
> *vdev)
>       dev_dbg(&vdev->dev, "reset !\n");
>  }
>  
> -/* provide the vdev features as retrieved from the firmware */
> +/* Provide the vdev features as retrieved from the firmware, plus the 
> following
> + * additional ones:
> + *   - VIRTIO_F_VERSION_1 that is required by some non-rpmsg virtio devices
> + *   - VIRTIO_F_ACCESS_PLATFORM to force usage of the map operations
> + */
> +#define RPROC_VIRTIO_STATIC_FEATURES \
> +     ((1ULL << VIRTIO_F_VERSION_1) | (1ULL << VIRTIO_F_ACCESS_PLATFORM))
> +
>  static u64 rproc_virtio_get_features(struct virtio_device *vdev)
>  {
>       struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
> @@ -249,7 +256,7 @@ static u64 rproc_virtio_get_features(struct virtio_device 
> *vdev)
>  
>       rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
>  
> -     return rsc->dfeatures | (1ULL << VIRTIO_F_VERSION_1);
> +     return rsc->dfeatures | RPROC_VIRTIO_STATIC_FEATURES;
>  }
>  
>  static void rproc_transport_features(struct virtio_device *vdev)
> @@ -275,16 +282,16 @@ static int rproc_virtio_finalize_features(struct 
> virtio_device *vdev)
>       /* Give virtio_rproc a chance to accept features. */
>       rproc_transport_features(vdev);
>  
> -     /* Make sure we don't have any features > 32 bits except 
> VIRTIO_F_VERSION_1 */
> +     /* Make sure we don't have any features > 32 bits */
>       if (WARN_ON_ONCE((u32)vdev->features !=
> -                      (vdev->features & ~(1ULL << VIRTIO_F_VERSION_1))))
> +                      (vdev->features & ~RPROC_VIRTIO_STATIC_FEATURES)))
>               return -1;
>  
>       /*
>        * Remember the finalized features of our vdev, and provide it
>        * to the remote processor once it is powered on.
>        */
> -     rsc->gfeatures = vdev->features & ~(1ULL << VIRTIO_F_VERSION_1);
> +     rsc->gfeatures = vdev->features & ~RPROC_VIRTIO_STATIC_FEATURES;
>  
>       return 0;
>  }
> @@ -337,6 +344,151 @@ static const struct virtio_config_ops 
> rproc_virtio_config_ops = {
>       .set            = rproc_virtio_set,
>  };
>  
> +static inline unsigned int rproc_virtio_bounce_slot(struct device *dma_dev,
> +                                                 dma_addr_t dma_handle)
> +{
> +     const dma_addr_t dma_base = dma_dev_coherent_base(dma_dev);
> +
> +     return (dma_handle - dma_base) >> PAGE_SHIFT;
> +}
> +
> +static dma_addr_t rproc_virtio_map_page(union virtio_map map, struct page 
> *page,
> +                                     unsigned long offset, size_t size,
> +                                     enum dma_data_direction dir,
> +                                     unsigned long attrs)
> +{
> +     struct device *dev = map.dma_dev;
> +     struct rproc_vdev *rvdev = dev_get_drvdata(dev);
> +     dma_addr_t dma_base = dma_dev_coherent_base(dev);
> +     size_t dma_size = dma_dev_coherent_size(dev);
> +     phys_addr_t paddr = page_to_phys(page) + offset;
> +     void *vaddr = page_to_virt(page) + offset;
> +     struct rproc_map_record *record;
> +     dma_addr_t map_handle;
> +     void *bounce;
> +
> +     // No need to allocate a bounce buffer if the memory to map is already
> +     // part of the device's coherent pool.
> +     if (paddr >= dma_base && paddr < (dma_base + dma_size)) {
> +             // The allocation details will be recorded also in this case,
> +             // indicating that no bounce buffer was allocated.
> +             map_handle = (dma_addr_t)paddr;
> +             bounce = NULL;
> +     } else {
> +             // Allocate bounce buffer from device coherent memory
> +             bounce = dma_alloc_coherent(dev, size, &map_handle, GFP_KERNEL 
> | __GFP_ZERO);
> +             if (!bounce)
> +                     return DMA_MAPPING_ERROR;
> +
> +             // Copy data to bounce buffer
> +             memcpy(bounce, vaddr, size);
> +     }
> +
> +     // Save bounce details
> +     record = &rvdev->map_records[rproc_virtio_bounce_slot(dev, map_handle)];
> +
> +     record->original = vaddr;
> +     record->size = size;
> +     record->bounce = bounce;
> +
> +     return map_handle;
> +}
> +
> +static void rproc_virtio_unmap_page(union virtio_map map, dma_addr_t 
> map_handle,
> +                                 size_t size, enum dma_data_direction dir,
> +                                 unsigned long attrs)
> +{
> +     struct device *dev = map.dma_dev;
> +     struct rproc_vdev *rvdev = dev_get_drvdata(dev);
> +     unsigned int slot = rproc_virtio_bounce_slot(dev, map_handle);
> +     struct rproc_map_record *record = &rvdev->map_records[slot];
> +
> +     WARN_ON(size != record->size);
> +
> +     // If a bounce buffer was used, copy data back to original one
> +     if (record->bounce) {
> +             memcpy(record->original, record->bounce, record->size);
> +
> +             dma_free_coherent(dev, record->size, record->bounce, 
> map_handle);
> +     }
> +
> +     record->original = NULL;
> +     record->size = 0;
> +     record->bounce = NULL;
> +}
> +
> +static void rproc_virtio_sync_single_for_cpu(union virtio_map map,
> +                                          dma_addr_t map_handle,
> +                                          size_t size,
> +                                          enum dma_data_direction dir)
> +{
> +     struct device *dev = map.dma_dev;
> +
> +     dma_sync_single_range_for_cpu(dev, (map_handle & PAGE_MASK),
> +                                   offset_in_page(map_handle), size, dir);
> +}
> +
> +static void rproc_virtio_sync_single_for_device(union virtio_map map,
> +                                             dma_addr_t map_handle,
> +                                             size_t size,
> +                                             enum dma_data_direction dir)
> +{
> +     struct device *dev = map.dma_dev;
> +
> +     dma_sync_single_range_for_device(dev, (map_handle & PAGE_MASK),
> +                                      offset_in_page(map_handle), size, dir);
> +}
> +
> +static void *rproc_virtio_alloc(union virtio_map map, size_t size,
> +                             dma_addr_t *map_handle, gfp_t gfp)
> +{
> +     struct device *dev = map.dma_dev;
> +
> +     return dma_alloc_coherent(dev, size, map_handle, gfp);
> +}
> +
> +static void rproc_virtio_free(union virtio_map map, size_t size, void *vaddr,
> +                           dma_addr_t map_handle, unsigned long attrs)
> +{
> +     struct device *dev = map.dma_dev;
> +
> +     dma_free_coherent(dev, size, vaddr, map_handle);
> +}
> +
> +static bool rproc_virtio_need_sync(union virtio_map map, dma_addr_t 
> map_handle)
> +{
> +     struct device *dev = map.dma_dev;
> +
> +     return dma_need_sync(dev, map_handle);
> +}
> +
> +static int rproc_virtio_mapping_error(union virtio_map map, dma_addr_t 
> map_handle)
> +{
> +     if (unlikely(map_handle == DMA_MAPPING_ERROR))
> +             return -ENOMEM;
> +
> +     return 0;
> +}
> +
> +static inline size_t rproc_virtio_max_mapping_size(union virtio_map map)
> +{
> +     struct device *dev = map.dma_dev;
> +
> +     return dma_dev_coherent_size(dev);
> +}
> +
> +static const struct virtio_map_ops rproc_virtio_map_ops = {
> +     .map_page = rproc_virtio_map_page,
> +     .unmap_page = rproc_virtio_unmap_page,
> +     .sync_single_for_cpu = rproc_virtio_sync_single_for_cpu,
> +     .sync_single_for_device = rproc_virtio_sync_single_for_device,
> +     .alloc = rproc_virtio_alloc,
> +     .free = rproc_virtio_free,
> +     .need_sync = rproc_virtio_need_sync,
> +     .mapping_error = rproc_virtio_mapping_error,
> +     .max_mapping_size = rproc_virtio_max_mapping_size,
> +};
> +
>  /*
>   * This function is called whenever vdev is released, and is responsible
>   * to decrement the remote processor's refcount which was taken when vdev was
> @@ -355,6 +507,8 @@ static void rproc_virtio_dev_release(struct device *dev)
>       of_reserved_mem_device_release(&rvdev->pdev->dev);
>       dma_release_coherent_memory(&rvdev->pdev->dev);
>  
> +     kvfree(rvdev->map_records);
> +
>       put_device(&rvdev->pdev->dev);
>  }
>  
> @@ -429,13 +583,29 @@ static int rproc_add_virtio_dev(struct rproc_vdev 
> *rvdev, int id)
>               of_reserved_mem_device_init_by_idx(dev, np, 0);
>       }
>  
> +     /* Allocate one tracking record for each page of the device reserved
> +      * memory. Contiguous memory is not required for this array, which can
> +      * also be quite big (depending on the size of the coherent memory), so
> +      * let's use vmalloc for this allocation.
> +      */
> +     rvdev->map_records = kvcalloc(dma_dev_coherent_size(dev) >> PAGE_SHIFT,
> +                                   sizeof(*rvdev->map_records),
> +                                   GFP_KERNEL);
> +     if (!rvdev->map_records) {
> +             dev_err(dev, "failed to allocate memory for map records\n");
> +             return -ENOMEM;
> +     }
> +
>       /* Allocate virtio device */
>       vdev = kzalloc_obj(*vdev);
> -     if (!vdev)
> +     if (!vdev) {
> +             kvfree(rvdev->map_records);
>               return -ENOMEM;
> +     }
>  
>       vdev->id.device = id;
>       vdev->config = &rproc_virtio_config_ops;
> +     vdev->map = &rproc_virtio_map_ops;
>       vdev->dev.parent = dev;
>       vdev->dev.release = rproc_virtio_dev_release;
>  
> diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h
> index c3ba51fe9e54..2ff48b505ac0 100644
> --- a/include/linux/remoteproc.h
> +++ b/include/linux/remoteproc.h
> @@ -339,10 +339,23 @@ struct rproc_vring {
>       struct virtqueue *vq;
>  };
>  
> +/**
> + * struct rproc_map_record - remoteproc map record
> + * @original:        original virtual address
> + * @num: allocation size
> + * @bounce: bounce buffer virtual address (NULL if not used)
> + */
> +struct rproc_map_record {
> +     void *original;
> +     size_t size;
> +     void *bounce;
> +};
> +
>  /**
>   * struct rproc_vdev - remoteproc state for a supported virtio device
>   * @subdev: handle for registering the vdev as a rproc subdevice
>   * @pdev: remoteproc virtio platform device
> + * @map_records: array of map records
>   * @id: virtio device id (as in virtio_ids.h)
>   * @node: list node
>   * @rproc: the rproc handle
> @@ -358,6 +371,7 @@ struct rproc_vdev {
>       unsigned int id;
>       struct list_head node;
>       struct rproc *rproc;
> +     struct rproc_map_record *map_records;
>       u32 rsc_offset;
>       u32 index;
>       unsigned int num_vrings;
> 
> -- 
> 2.55.0
> 

Reply via email to