[PATCH v3 2/4] iommu: Implement common IOMMU ops for DMA mapping

Robin Murphy robin.murphy at arm.com
Wed Jul 15 08:50:08 PDT 2015


Hi Catalin,

Thanks for the review.

On 14/07/15 18:16, Catalin Marinas wrote:
> On Fri, Jul 10, 2015 at 08:19:33PM +0100, Robin Murphy wrote:
>> +/*
>> + * IOVAs are IOMMU _input_ addresses, so there still exists the possibility
>> + * for static bus translation between device output and IOMMU input (yuck).
>> + */
>> +static inline dma_addr_t dev_dma_addr(struct device *dev, dma_addr_t addr)
>> +{
>> +	dma_addr_t offset = (dma_addr_t)dev->dma_pfn_offset << PAGE_SHIFT;
>> +
>> +	BUG_ON(addr < offset);
>> +	return addr - offset;
>> +}
>
> Are these just theoretical or you expect to see some at some point? I
> think the dma_limit in __alloc_iova() may also need to take the offset
> into account (or just ignore them altogether for now).

Right now I'm not aware of any platform which has both DMA offsets and 
an IOMMU on the same bus, and I would really hope it stays that way. 
This is just extra complication out of attempting to cover every 
possibility, and you're right about the alloc_iova oversight. I'll rip 
it out for simplicity, and remain hopeful that nobody ever builds 
anything mad enough to need it put back.

>> +
>> +/**
>> + * dma_direction_to_prot - Translate DMA API directions to IOMMU API page flags
>> + * @dir: Direction of DMA transfer
>> + * @coherent: Is the DMA master cache-coherent?
>> + *
>> + * Return: corresponding IOMMU API page protection flags
>> + */
>> +int dma_direction_to_prot(enum dma_data_direction dir, bool coherent)
>> +{
>> +	int prot = coherent ? IOMMU_CACHE : 0;
>> +
>> +	switch (dir) {
>> +	case DMA_BIDIRECTIONAL:
>> +		return prot | IOMMU_READ | IOMMU_WRITE;
>> +	case DMA_TO_DEVICE:
>> +		return prot | IOMMU_READ;
>> +	case DMA_FROM_DEVICE:
>> +		return prot | IOMMU_WRITE;
>> +	default:
>> +		return 0;
>> +	}
>> +}
>> +
>> +static struct iova *__alloc_iova(struct device *dev, size_t size, bool coherent)
>> +{
>> +	struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
>> +	struct iova_domain *iovad = domain->dma_api_cookie;
>> +	unsigned long shift = iova_shift(iovad);
>> +	unsigned long length = iova_align(iovad, size) >> shift;
>> +	u64 dma_limit = coherent ? dev->coherent_dma_mask : dma_get_mask(dev);
>
> "coherent" and "coherent_dma_mask" have related meanings here. As I can
> see in patch 3, the coherent information passed all the way to this
> function states whether the device is cache coherent or not (and whether
> cache maintenance is needed). The coherent_dma_mask refers to an
> allocation mask for the dma_alloc_coherent() API but that doesn't
> necessarily mean that the device is cache coherent. Similarly, dma_mask
> is used for streaming DMA.
>
> You can rename it to coherent_api or simply pass a u64 dma_mask
> directly.

Bleh, it seems that at some point along the way I got confused and 
started mistakenly thinking the DMA masks were about the device's 
ability to issue coherent/non-coherent transactions. I'll clean up the 
mess...

> [...]
>> +/**
>> + * iommu_dma_alloc - Allocate and map a buffer contiguous in IOVA space
>> + * @dev: Device to allocate memory for. Must be a real device
>> + *	 attached to an iommu_dma_domain
>> + * @size: Size of buffer in bytes
>> + * @gfp: Allocation flags
>> + * @prot: IOMMU mapping flags
>> + * @coherent: Which dma_mask to base IOVA allocation on
>> + * @handle: Out argument for allocated DMA handle
>> + * @flush_page: Arch callback to flush a single page from caches as
>> + *		necessary. May be NULL for coherent allocations
>> + *
>> + * If @size is less than PAGE_SIZE, then a full CPU page will be allocated,
>> + * but an IOMMU which supports smaller pages might not map the whole thing.
>> + * For now, the buffer is unconditionally zeroed for compatibility
>> + *
>> + * Return: Array of struct page pointers describing the buffer,
>> + *	   or NULL on failure.
>> + */
>> +struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,
>> +		int prot, bool coherent, dma_addr_t *handle,
>> +		void (*flush_page)(const void *, phys_addr_t))
>
> So for this function, coherent should always be true since this is only
> used with the coherent DMA API AFAICT.

Indeed.

>> +{
>> +	struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
>> +	struct iova_domain *iovad = domain->dma_api_cookie;
>> +	struct iova *iova;
>> +	struct page **pages;
>> +	struct sg_table sgt;
>> +	struct sg_mapping_iter miter;
>> +	dma_addr_t dma_addr;
>> +	unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
>> +
>> +	*handle = DMA_ERROR_CODE;
>> +
>> +	pages = __iommu_dma_alloc_pages(count, gfp);
>> +	if (!pages)
>> +		return NULL;
>> +
>> +	iova = __alloc_iova(dev, size, coherent);
>
> And here just __alloc_iova(dev, size, true);

In fact, everything it wanted dev for is now available at all the 
callsites, so I'll rejig the whole interface.

Robin.

> [...]
>> +dma_addr_t iommu_dma_map_page(struct device *dev, struct page *page,
>> +		unsigned long offset, size_t size, int prot, bool coherent)
>> +{
>> +	dma_addr_t dma_addr;
>> +	struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
>> +	struct iova_domain *iovad = domain->dma_api_cookie;
>> +	phys_addr_t phys = page_to_phys(page) + offset;
>> +	size_t iova_off = iova_offset(iovad, phys);
>> +	size_t len = iova_align(iovad, size + iova_off);
>> +	struct iova *iova = __alloc_iova(dev, len, coherent);
>
> Here __alloc_iova(dev, len, false);
>
> [...]
>> +/*
>> + * The DMA API client is passing in a scatterlist which could describe
>> + * any old buffer layout, but the IOMMU API requires everything to be
>> + * aligned to IOMMU pages. Hence the need for this complicated bit of
>> + * impedance-matching, to be able to hand off a suitably-aligned list,
>> + * but still preserve the original offsets and sizes for the caller.
>> + */
>> +int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg,
>> +		int nents, int prot, bool coherent)
>> +{
>> +	struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
>> +	struct iova_domain *iovad = domain->dma_api_cookie;
>> +	struct iova *iova;
>> +	struct scatterlist *s;
>> +	dma_addr_t dma_addr;
>> +	size_t iova_len = 0;
>> +	int i;
>> +
>> +	/*
>> +	 * Work out how much IOVA space we need, and align the segments to
>> +	 * IOVA granules for the IOMMU driver to handle. With some clever
>> +	 * trickery we can modify the list in-place, but reversibly, by
>> +	 * hiding the original data in the as-yet-unused DMA fields.
>> +	 */
>> +	for_each_sg(sg, s, nents, i) {
>> +		size_t s_offset = iova_offset(iovad, s->offset);
>> +		size_t s_length = s->length;
>> +
>> +		sg_dma_address(s) = s->offset;
>> +		sg_dma_len(s) = s_length;
>> +		s->offset -= s_offset;
>> +		s_length = iova_align(iovad, s_length + s_offset);
>> +		s->length = s_length;
>> +
>> +		iova_len += s_length;
>> +	}
>> +
>> +	iova = __alloc_iova(dev, iova_len, coherent);
>
> __alloc_iova(dev, iova_len, false);
>




More information about the linux-arm-kernel mailing list