diff mbox series

[v2,2/3] media: ipu6: optimize the IPU6 MMU mapping flow

Message ID 1729835552-14825-2-git-send-email-bingbu.cao@intel.com
State Superseded
Headers show
Series [v2,1/3] media: ipu6: move the l2_unmap() up before l2_map() | expand

Commit Message

Bingbu Cao Oct. 25, 2024, 5:52 a.m. UTC
ipu6_mmu_map() operated on a per-page basis, it leads frequent
spin_lock/unlock() and clflush_cache_range() for each page, it
will cause inefficiencies especially when handling dma-bufs
with large number of pages. However, the pages are likely concentrated
pages by IOMMU DMA driver, IPU MMU driver can map the concentrated
pages into less entries in l1 table.

This change enhances ipu6_mmu_map() with batching process multiple
contiguous pages. It significantly reduces calls for spin_lock/unlock
and clflush_cache_range() and improve the performance.

Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
Signed-off-by: Jianhui Dai <jianhui.j.dai@intel.com>
---
 drivers/media/pci/intel/ipu6/ipu6-mmu.c | 144 +++++++++++++++-----------------
 1 file changed, 69 insertions(+), 75 deletions(-)

Comments

Sakari Ailus Nov. 1, 2024, 1:16 p.m. UTC | #1
Hi Bingbu,

Thanks for the update.

On Fri, Oct 25, 2024 at 01:52:31PM +0800, Bingbu Cao wrote:
> ipu6_mmu_map() operated on a per-page basis, it leads frequent
> spin_lock/unlock() and clflush_cache_range() for each page, it
> will cause inefficiencies especially when handling dma-bufs
> with large number of pages. However, the pages are likely concentrated
> pages by IOMMU DMA driver, IPU MMU driver can map the concentrated
> pages into less entries in l1 table.
> 
> This change enhances ipu6_mmu_map() with batching process multiple
> contiguous pages. It significantly reduces calls for spin_lock/unlock
> and clflush_cache_range() and improve the performance.
> 
> Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
> Signed-off-by: Jianhui Dai <jianhui.j.dai@intel.com>
> ---
>  drivers/media/pci/intel/ipu6/ipu6-mmu.c | 144 +++++++++++++++-----------------
>  1 file changed, 69 insertions(+), 75 deletions(-)
> 
> diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c
> index 9ea6789bca5e..e957ccb4691d 100644
> --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.c
> +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c
> @@ -295,72 +295,90 @@ static size_t l2_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
>  static int l2_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
>  		  phys_addr_t paddr, size_t size)
>  {
> -	u32 l1_idx = iova >> ISP_L1PT_SHIFT;
> -	u32 iova_start = iova;
> +	struct device *dev = mmu_info->dev;
> +	unsigned int l2_entries;
>  	u32 *l2_pt, *l2_virt;
>  	unsigned int l2_idx;
>  	unsigned long flags;
> +	size_t mapped = 0;
>  	dma_addr_t dma;
>  	u32 l1_entry;
> -
> -	dev_dbg(mmu_info->dev,
> -		"mapping l2 page table for l1 index %u (iova %8.8x)\n",
> -		l1_idx, (u32)iova);
> +	u32 l1_idx;
> +	int err = 0;
>  
>  	spin_lock_irqsave(&mmu_info->lock, flags);
> -	l1_entry = mmu_info->l1_pt[l1_idx];
> -	if (l1_entry == mmu_info->dummy_l2_pteval) {
> -		l2_virt = mmu_info->l2_pts[l1_idx];
> -		if (likely(!l2_virt)) {
> -			l2_virt = alloc_l2_pt(mmu_info);
> -			if (!l2_virt) {
> -				spin_unlock_irqrestore(&mmu_info->lock, flags);
> -				return -ENOMEM;
> -			}
> -		}
> -
> -		dma = map_single(mmu_info, l2_virt);
> -		if (!dma) {
> -			dev_err(mmu_info->dev, "Failed to map l2pt page\n");
> -			free_page((unsigned long)l2_virt);
> -			spin_unlock_irqrestore(&mmu_info->lock, flags);
> -			return -EINVAL;
> -		}
> -
> -		l1_entry = dma >> ISP_PADDR_SHIFT;
> -
> -		dev_dbg(mmu_info->dev, "page for l1_idx %u %p allocated\n",
> -			l1_idx, l2_virt);
> -		mmu_info->l1_pt[l1_idx] = l1_entry;
> -		mmu_info->l2_pts[l1_idx] = l2_virt;
> -		clflush_cache_range((void *)&mmu_info->l1_pt[l1_idx],
> -				    sizeof(mmu_info->l1_pt[l1_idx]));
> -	}
> -
> -	l2_pt = mmu_info->l2_pts[l1_idx];
> -
> -	dev_dbg(mmu_info->dev, "l2_pt at %p with dma 0x%x\n", l2_pt, l1_entry);
>  
>  	paddr = ALIGN(paddr, ISP_PAGE_SIZE);
> +	for (l1_idx = iova >> ISP_L1PT_SHIFT;
> +	     size > 0 && l1_idx < ISP_L1PT_PTES; l1_idx++) {
> +		dev_dbg(dev,
> +			"mapping l2 page table for l1 index %u (iova %8.8x)\n",
> +			l1_idx, (u32)iova);
>  
> -	l2_idx = (iova_start & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
> +		l1_entry = mmu_info->l1_pt[l1_idx];
> +		if (l1_entry == mmu_info->dummy_l2_pteval) {
> +			l2_virt = mmu_info->l2_pts[l1_idx];
> +			if (likely(!l2_virt)) {

likely() should only be used if it's really, really uncommon on a hot path.
I don't think that's the case here.

Can it even happen l2_virt is NULL here? l1_entry has been assigned while
the l2 PT page has been mapped so both are either dummy / NULL or valid
values. The l2 pages aren't unmapped in unmap, but only in
ipu6_mmu_destroy(). Same goes for the map_single() if l2_virt was valid
already, the page has been mapped, too.

This seems like a pre-existing problem. If there is no actual bug here but
just inconsistent implementation (so it would seem), I think it's fine to
address this on top.

Can you conform this?

> +				l2_virt = alloc_l2_pt(mmu_info);
> +				if (!l2_virt) {
> +					err = -ENOMEM;
> +					goto error;
> +				}
> +			}
>  
> -	dev_dbg(mmu_info->dev, "l2_idx %u, phys 0x%8.8x\n", l2_idx,
> -		l2_pt[l2_idx]);
> -	if (l2_pt[l2_idx] != mmu_info->dummy_page_pteval) {
> -		spin_unlock_irqrestore(&mmu_info->lock, flags);
> -		return -EINVAL;
> +			dma = map_single(mmu_info, l2_virt);
> +			if (!dma) {
> +				dev_err(dev, "Failed to map l2pt page\n");
> +				free_page((unsigned long)l2_virt);
> +				err = -EINVAL;
> +				goto error;
> +			}
> +
> +			l1_entry = dma >> ISP_PADDR_SHIFT;
> +
> +			dev_dbg(dev, "page for l1_idx %u %p allocated\n",
> +				l1_idx, l2_virt);
> +			mmu_info->l1_pt[l1_idx] = l1_entry;
> +			mmu_info->l2_pts[l1_idx] = l2_virt;
> +
> +			clflush_cache_range(&mmu_info->l1_pt[l1_idx],
> +					    sizeof(mmu_info->l1_pt[l1_idx]));
> +		}
> +
> +		l2_pt = mmu_info->l2_pts[l1_idx];
> +		l2_entries = 0;
> +
> +		for (l2_idx = (iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
> +		     size > 0 && l2_idx < ISP_L2PT_PTES; l2_idx++) {
> +			l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT;
> +
> +			dev_dbg(dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx,
> +				l2_pt[l2_idx]);
> +
> +			iova += ISP_PAGE_SIZE;
> +			paddr += ISP_PAGE_SIZE;
> +			mapped += ISP_PAGE_SIZE;
> +			size -= ISP_PAGE_SIZE;
> +
> +			l2_entries++;
> +		}
> +
> +		WARN_ON_ONCE(!l2_entries);
> +		clflush_cache_range(&l2_pt[l2_idx - l2_entries],
> +				    sizeof(l2_pt[0]) * l2_entries);
>  	}
>  
> -	l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT;
> -
> -	clflush_cache_range((void *)&l2_pt[l2_idx], sizeof(l2_pt[l2_idx]));
>  	spin_unlock_irqrestore(&mmu_info->lock, flags);
>  
> -	dev_dbg(mmu_info->dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx,
> -		l2_pt[l2_idx]);
> -
>  	return 0;
> +
> +error:
> +	spin_unlock_irqrestore(&mmu_info->lock, flags);
> +	/* unroll mapping in case something went wrong */
> +	if (mapped)
> +		l2_unmap(mmu_info, iova - mapped, paddr - mapped, mapped);
> +
> +	return err;
>  }
>  
>  static int __ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
> @@ -692,10 +710,7 @@ size_t ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
>  int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
>  		 phys_addr_t paddr, size_t size)
>  {
> -	unsigned long orig_iova = iova;
>  	unsigned int min_pagesz;
> -	size_t orig_size = size;
> -	int ret = 0;
>  
>  	if (mmu_info->pgsize_bitmap == 0UL)
>  		return -ENODEV;
> @@ -718,28 +733,7 @@ int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
>  	dev_dbg(mmu_info->dev, "map: iova 0x%lx pa %pa size 0x%zx\n",
>  		iova, &paddr, size);
>  
> -	while (size) {
> -		size_t pgsize = ipu6_mmu_pgsize(mmu_info->pgsize_bitmap,
> -						iova | paddr, size);
> -
> -		dev_dbg(mmu_info->dev,
> -			"mapping: iova 0x%lx pa %pa pgsize 0x%zx\n",
> -			iova, &paddr, pgsize);
> -
> -		ret = __ipu6_mmu_map(mmu_info, iova, paddr, pgsize);
> -		if (ret)
> -			break;
> -
> -		iova += pgsize;
> -		paddr += pgsize;
> -		size -= pgsize;
> -	}
> -
> -	/* unroll mapping in case something went wrong */
> -	if (ret)
> -		ipu6_mmu_unmap(mmu_info, orig_iova, orig_size - size);
> -
> -	return ret;
> +	return __ipu6_mmu_map(mmu_info, iova, paddr, size);
>  }
>  
>  static void ipu6_mmu_destroy(struct ipu6_mmu *mmu)
Bingbu Cao Nov. 4, 2024, 4:04 a.m. UTC | #2
Sakari,

On 11/1/24 9:16 PM, Sakari Ailus wrote:
> Hi Bingbu,
> 
> Thanks for the update.
> 
> On Fri, Oct 25, 2024 at 01:52:31PM +0800, Bingbu Cao wrote:
>> ipu6_mmu_map() operated on a per-page basis, it leads frequent
>> spin_lock/unlock() and clflush_cache_range() for each page, it
>> will cause inefficiencies especially when handling dma-bufs
>> with large number of pages. However, the pages are likely concentrated
>> pages by IOMMU DMA driver, IPU MMU driver can map the concentrated
>> pages into less entries in l1 table.
>>
>> This change enhances ipu6_mmu_map() with batching process multiple
>> contiguous pages. It significantly reduces calls for spin_lock/unlock
>> and clflush_cache_range() and improve the performance.
>>
>> Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
>> Signed-off-by: Jianhui Dai <jianhui.j.dai@intel.com>
>> ---
>>  drivers/media/pci/intel/ipu6/ipu6-mmu.c | 144 +++++++++++++++-----------------
>>  1 file changed, 69 insertions(+), 75 deletions(-)
>>
>> diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c
>> index 9ea6789bca5e..e957ccb4691d 100644
>> --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.c
>> +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c
>> @@ -295,72 +295,90 @@ static size_t l2_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
>>  static int l2_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
>>  		  phys_addr_t paddr, size_t size)
>>  {
>> -	u32 l1_idx = iova >> ISP_L1PT_SHIFT;
>> -	u32 iova_start = iova;
>> +	struct device *dev = mmu_info->dev;
>> +	unsigned int l2_entries;
>>  	u32 *l2_pt, *l2_virt;
>>  	unsigned int l2_idx;
>>  	unsigned long flags;
>> +	size_t mapped = 0;
>>  	dma_addr_t dma;
>>  	u32 l1_entry;
>> -
>> -	dev_dbg(mmu_info->dev,
>> -		"mapping l2 page table for l1 index %u (iova %8.8x)\n",
>> -		l1_idx, (u32)iova);
>> +	u32 l1_idx;
>> +	int err = 0;
>>  
>>  	spin_lock_irqsave(&mmu_info->lock, flags);
>> -	l1_entry = mmu_info->l1_pt[l1_idx];
>> -	if (l1_entry == mmu_info->dummy_l2_pteval) {
>> -		l2_virt = mmu_info->l2_pts[l1_idx];
>> -		if (likely(!l2_virt)) {
>> -			l2_virt = alloc_l2_pt(mmu_info);
>> -			if (!l2_virt) {
>> -				spin_unlock_irqrestore(&mmu_info->lock, flags);
>> -				return -ENOMEM;
>> -			}
>> -		}
>> -
>> -		dma = map_single(mmu_info, l2_virt);
>> -		if (!dma) {
>> -			dev_err(mmu_info->dev, "Failed to map l2pt page\n");
>> -			free_page((unsigned long)l2_virt);
>> -			spin_unlock_irqrestore(&mmu_info->lock, flags);
>> -			return -EINVAL;
>> -		}
>> -
>> -		l1_entry = dma >> ISP_PADDR_SHIFT;
>> -
>> -		dev_dbg(mmu_info->dev, "page for l1_idx %u %p allocated\n",
>> -			l1_idx, l2_virt);
>> -		mmu_info->l1_pt[l1_idx] = l1_entry;
>> -		mmu_info->l2_pts[l1_idx] = l2_virt;
>> -		clflush_cache_range((void *)&mmu_info->l1_pt[l1_idx],
>> -				    sizeof(mmu_info->l1_pt[l1_idx]));
>> -	}
>> -
>> -	l2_pt = mmu_info->l2_pts[l1_idx];
>> -
>> -	dev_dbg(mmu_info->dev, "l2_pt at %p with dma 0x%x\n", l2_pt, l1_entry);
>>  
>>  	paddr = ALIGN(paddr, ISP_PAGE_SIZE);
>> +	for (l1_idx = iova >> ISP_L1PT_SHIFT;
>> +	     size > 0 && l1_idx < ISP_L1PT_PTES; l1_idx++) {
>> +		dev_dbg(dev,
>> +			"mapping l2 page table for l1 index %u (iova %8.8x)\n",
>> +			l1_idx, (u32)iova);
>>  
>> -	l2_idx = (iova_start & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
>> +		l1_entry = mmu_info->l1_pt[l1_idx];
>> +		if (l1_entry == mmu_info->dummy_l2_pteval) {
>> +			l2_virt = mmu_info->l2_pts[l1_idx];
>> +			if (likely(!l2_virt)) {
> 
> likely() should only be used if it's really, really uncommon on a hot path.
> I don't think that's the case here.
> 
> Can it even happen l2_virt is NULL here? l1_entry has been assigned while
> the l2 PT page has been mapped so both are either dummy / NULL or valid
> values. The l2 pages aren't unmapped in unmap, but only in
> ipu6_mmu_destroy(). Same goes for the map_single() if l2_virt was valid
> already, the page has been mapped, too.
> 
> This seems like a pre-existing problem. If there is no actual bug here but
> just inconsistent implementation (so it would seem), I think it's fine to
> address this on top.
> 
> Can you conform this?

I would like to address this in another patch instead of this series.
What do you think?

> 
>> +				l2_virt = alloc_l2_pt(mmu_info);
>> +				if (!l2_virt) {
>> +					err = -ENOMEM;
>> +					goto error;
>> +				}
>> +			}
>>  
>> -	dev_dbg(mmu_info->dev, "l2_idx %u, phys 0x%8.8x\n", l2_idx,
>> -		l2_pt[l2_idx]);
>> -	if (l2_pt[l2_idx] != mmu_info->dummy_page_pteval) {
>> -		spin_unlock_irqrestore(&mmu_info->lock, flags);
>> -		return -EINVAL;
>> +			dma = map_single(mmu_info, l2_virt);
>> +			if (!dma) {
>> +				dev_err(dev, "Failed to map l2pt page\n");
>> +				free_page((unsigned long)l2_virt);
>> +				err = -EINVAL;
>> +				goto error;
>> +			}
>> +
>> +			l1_entry = dma >> ISP_PADDR_SHIFT;
>> +
>> +			dev_dbg(dev, "page for l1_idx %u %p allocated\n",
>> +				l1_idx, l2_virt);
>> +			mmu_info->l1_pt[l1_idx] = l1_entry;
>> +			mmu_info->l2_pts[l1_idx] = l2_virt;
>> +
>> +			clflush_cache_range(&mmu_info->l1_pt[l1_idx],
>> +					    sizeof(mmu_info->l1_pt[l1_idx]));
>> +		}
>> +
>> +		l2_pt = mmu_info->l2_pts[l1_idx];
>> +		l2_entries = 0;
>> +
>> +		for (l2_idx = (iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
>> +		     size > 0 && l2_idx < ISP_L2PT_PTES; l2_idx++) {
>> +			l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT;
>> +
>> +			dev_dbg(dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx,
>> +				l2_pt[l2_idx]);
>> +
>> +			iova += ISP_PAGE_SIZE;
>> +			paddr += ISP_PAGE_SIZE;
>> +			mapped += ISP_PAGE_SIZE;
>> +			size -= ISP_PAGE_SIZE;
>> +
>> +			l2_entries++;
>> +		}
>> +
>> +		WARN_ON_ONCE(!l2_entries);
>> +		clflush_cache_range(&l2_pt[l2_idx - l2_entries],
>> +				    sizeof(l2_pt[0]) * l2_entries);
>>  	}
>>  
>> -	l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT;
>> -
>> -	clflush_cache_range((void *)&l2_pt[l2_idx], sizeof(l2_pt[l2_idx]));
>>  	spin_unlock_irqrestore(&mmu_info->lock, flags);
>>  
>> -	dev_dbg(mmu_info->dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx,
>> -		l2_pt[l2_idx]);
>> -
>>  	return 0;
>> +
>> +error:
>> +	spin_unlock_irqrestore(&mmu_info->lock, flags);
>> +	/* unroll mapping in case something went wrong */
>> +	if (mapped)
>> +		l2_unmap(mmu_info, iova - mapped, paddr - mapped, mapped);
>> +
>> +	return err;
>>  }
>>  
>>  static int __ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
>> @@ -692,10 +710,7 @@ size_t ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
>>  int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
>>  		 phys_addr_t paddr, size_t size)
>>  {
>> -	unsigned long orig_iova = iova;
>>  	unsigned int min_pagesz;
>> -	size_t orig_size = size;
>> -	int ret = 0;
>>  
>>  	if (mmu_info->pgsize_bitmap == 0UL)
>>  		return -ENODEV;
>> @@ -718,28 +733,7 @@ int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
>>  	dev_dbg(mmu_info->dev, "map: iova 0x%lx pa %pa size 0x%zx\n",
>>  		iova, &paddr, size);
>>  
>> -	while (size) {
>> -		size_t pgsize = ipu6_mmu_pgsize(mmu_info->pgsize_bitmap,
>> -						iova | paddr, size);
>> -
>> -		dev_dbg(mmu_info->dev,
>> -			"mapping: iova 0x%lx pa %pa pgsize 0x%zx\n",
>> -			iova, &paddr, pgsize);
>> -
>> -		ret = __ipu6_mmu_map(mmu_info, iova, paddr, pgsize);
>> -		if (ret)
>> -			break;
>> -
>> -		iova += pgsize;
>> -		paddr += pgsize;
>> -		size -= pgsize;
>> -	}
>> -
>> -	/* unroll mapping in case something went wrong */
>> -	if (ret)
>> -		ipu6_mmu_unmap(mmu_info, orig_iova, orig_size - size);
>> -
>> -	return ret;
>> +	return __ipu6_mmu_map(mmu_info, iova, paddr, size);
>>  }
>>  
>>  static void ipu6_mmu_destroy(struct ipu6_mmu *mmu)
>
Sakari Ailus Nov. 4, 2024, 10:19 a.m. UTC | #3
Hi Bingbu,

On Mon, Nov 04, 2024 at 12:04:29PM +0800, Bingbu Cao wrote:
> Sakari,
> 
> On 11/1/24 9:16 PM, Sakari Ailus wrote:
> > Hi Bingbu,
> > 
> > Thanks for the update.
> > 
> > On Fri, Oct 25, 2024 at 01:52:31PM +0800, Bingbu Cao wrote:
> >> ipu6_mmu_map() operated on a per-page basis, it leads frequent
> >> spin_lock/unlock() and clflush_cache_range() for each page, it
> >> will cause inefficiencies especially when handling dma-bufs
> >> with large number of pages. However, the pages are likely concentrated
> >> pages by IOMMU DMA driver, IPU MMU driver can map the concentrated
> >> pages into less entries in l1 table.
> >>
> >> This change enhances ipu6_mmu_map() with batching process multiple
> >> contiguous pages. It significantly reduces calls for spin_lock/unlock
> >> and clflush_cache_range() and improve the performance.
> >>
> >> Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
> >> Signed-off-by: Jianhui Dai <jianhui.j.dai@intel.com>
> >> ---
> >>  drivers/media/pci/intel/ipu6/ipu6-mmu.c | 144 +++++++++++++++-----------------
> >>  1 file changed, 69 insertions(+), 75 deletions(-)
> >>
> >> diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c
> >> index 9ea6789bca5e..e957ccb4691d 100644
> >> --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.c
> >> +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c
> >> @@ -295,72 +295,90 @@ static size_t l2_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
> >>  static int l2_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
> >>  		  phys_addr_t paddr, size_t size)
> >>  {
> >> -	u32 l1_idx = iova >> ISP_L1PT_SHIFT;
> >> -	u32 iova_start = iova;
> >> +	struct device *dev = mmu_info->dev;
> >> +	unsigned int l2_entries;
> >>  	u32 *l2_pt, *l2_virt;
> >>  	unsigned int l2_idx;
> >>  	unsigned long flags;
> >> +	size_t mapped = 0;
> >>  	dma_addr_t dma;
> >>  	u32 l1_entry;
> >> -
> >> -	dev_dbg(mmu_info->dev,
> >> -		"mapping l2 page table for l1 index %u (iova %8.8x)\n",
> >> -		l1_idx, (u32)iova);
> >> +	u32 l1_idx;
> >> +	int err = 0;
> >>  
> >>  	spin_lock_irqsave(&mmu_info->lock, flags);
> >> -	l1_entry = mmu_info->l1_pt[l1_idx];
> >> -	if (l1_entry == mmu_info->dummy_l2_pteval) {
> >> -		l2_virt = mmu_info->l2_pts[l1_idx];
> >> -		if (likely(!l2_virt)) {
> >> -			l2_virt = alloc_l2_pt(mmu_info);
> >> -			if (!l2_virt) {
> >> -				spin_unlock_irqrestore(&mmu_info->lock, flags);
> >> -				return -ENOMEM;
> >> -			}
> >> -		}
> >> -
> >> -		dma = map_single(mmu_info, l2_virt);
> >> -		if (!dma) {
> >> -			dev_err(mmu_info->dev, "Failed to map l2pt page\n");
> >> -			free_page((unsigned long)l2_virt);
> >> -			spin_unlock_irqrestore(&mmu_info->lock, flags);
> >> -			return -EINVAL;
> >> -		}
> >> -
> >> -		l1_entry = dma >> ISP_PADDR_SHIFT;
> >> -
> >> -		dev_dbg(mmu_info->dev, "page for l1_idx %u %p allocated\n",
> >> -			l1_idx, l2_virt);
> >> -		mmu_info->l1_pt[l1_idx] = l1_entry;
> >> -		mmu_info->l2_pts[l1_idx] = l2_virt;
> >> -		clflush_cache_range((void *)&mmu_info->l1_pt[l1_idx],
> >> -				    sizeof(mmu_info->l1_pt[l1_idx]));
> >> -	}
> >> -
> >> -	l2_pt = mmu_info->l2_pts[l1_idx];
> >> -
> >> -	dev_dbg(mmu_info->dev, "l2_pt at %p with dma 0x%x\n", l2_pt, l1_entry);
> >>  
> >>  	paddr = ALIGN(paddr, ISP_PAGE_SIZE);
> >> +	for (l1_idx = iova >> ISP_L1PT_SHIFT;
> >> +	     size > 0 && l1_idx < ISP_L1PT_PTES; l1_idx++) {
> >> +		dev_dbg(dev,
> >> +			"mapping l2 page table for l1 index %u (iova %8.8x)\n",
> >> +			l1_idx, (u32)iova);
> >>  
> >> -	l2_idx = (iova_start & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
> >> +		l1_entry = mmu_info->l1_pt[l1_idx];
> >> +		if (l1_entry == mmu_info->dummy_l2_pteval) {
> >> +			l2_virt = mmu_info->l2_pts[l1_idx];
> >> +			if (likely(!l2_virt)) {
> > 
> > likely() should only be used if it's really, really uncommon on a hot path.
> > I don't think that's the case here.
> > 
> > Can it even happen l2_virt is NULL here? l1_entry has been assigned while
> > the l2 PT page has been mapped so both are either dummy / NULL or valid
> > values. The l2 pages aren't unmapped in unmap, but only in
> > ipu6_mmu_destroy(). Same goes for the map_single() if l2_virt was valid
> > already, the page has been mapped, too.
> > 
> > This seems like a pre-existing problem. If there is no actual bug here but
> > just inconsistent implementation (so it would seem), I think it's fine to
> > address this on top.
> > 
> > Can you conform this?
> 
> I would like to address this in another patch instead of this series.
> What do you think?

Works for me.
diff mbox series

Patch

diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c
index 9ea6789bca5e..e957ccb4691d 100644
--- a/drivers/media/pci/intel/ipu6/ipu6-mmu.c
+++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c
@@ -295,72 +295,90 @@  static size_t l2_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
 static int l2_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
 		  phys_addr_t paddr, size_t size)
 {
-	u32 l1_idx = iova >> ISP_L1PT_SHIFT;
-	u32 iova_start = iova;
+	struct device *dev = mmu_info->dev;
+	unsigned int l2_entries;
 	u32 *l2_pt, *l2_virt;
 	unsigned int l2_idx;
 	unsigned long flags;
+	size_t mapped = 0;
 	dma_addr_t dma;
 	u32 l1_entry;
-
-	dev_dbg(mmu_info->dev,
-		"mapping l2 page table for l1 index %u (iova %8.8x)\n",
-		l1_idx, (u32)iova);
+	u32 l1_idx;
+	int err = 0;
 
 	spin_lock_irqsave(&mmu_info->lock, flags);
-	l1_entry = mmu_info->l1_pt[l1_idx];
-	if (l1_entry == mmu_info->dummy_l2_pteval) {
-		l2_virt = mmu_info->l2_pts[l1_idx];
-		if (likely(!l2_virt)) {
-			l2_virt = alloc_l2_pt(mmu_info);
-			if (!l2_virt) {
-				spin_unlock_irqrestore(&mmu_info->lock, flags);
-				return -ENOMEM;
-			}
-		}
-
-		dma = map_single(mmu_info, l2_virt);
-		if (!dma) {
-			dev_err(mmu_info->dev, "Failed to map l2pt page\n");
-			free_page((unsigned long)l2_virt);
-			spin_unlock_irqrestore(&mmu_info->lock, flags);
-			return -EINVAL;
-		}
-
-		l1_entry = dma >> ISP_PADDR_SHIFT;
-
-		dev_dbg(mmu_info->dev, "page for l1_idx %u %p allocated\n",
-			l1_idx, l2_virt);
-		mmu_info->l1_pt[l1_idx] = l1_entry;
-		mmu_info->l2_pts[l1_idx] = l2_virt;
-		clflush_cache_range((void *)&mmu_info->l1_pt[l1_idx],
-				    sizeof(mmu_info->l1_pt[l1_idx]));
-	}
-
-	l2_pt = mmu_info->l2_pts[l1_idx];
-
-	dev_dbg(mmu_info->dev, "l2_pt at %p with dma 0x%x\n", l2_pt, l1_entry);
 
 	paddr = ALIGN(paddr, ISP_PAGE_SIZE);
+	for (l1_idx = iova >> ISP_L1PT_SHIFT;
+	     size > 0 && l1_idx < ISP_L1PT_PTES; l1_idx++) {
+		dev_dbg(dev,
+			"mapping l2 page table for l1 index %u (iova %8.8x)\n",
+			l1_idx, (u32)iova);
 
-	l2_idx = (iova_start & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
+		l1_entry = mmu_info->l1_pt[l1_idx];
+		if (l1_entry == mmu_info->dummy_l2_pteval) {
+			l2_virt = mmu_info->l2_pts[l1_idx];
+			if (likely(!l2_virt)) {
+				l2_virt = alloc_l2_pt(mmu_info);
+				if (!l2_virt) {
+					err = -ENOMEM;
+					goto error;
+				}
+			}
 
-	dev_dbg(mmu_info->dev, "l2_idx %u, phys 0x%8.8x\n", l2_idx,
-		l2_pt[l2_idx]);
-	if (l2_pt[l2_idx] != mmu_info->dummy_page_pteval) {
-		spin_unlock_irqrestore(&mmu_info->lock, flags);
-		return -EINVAL;
+			dma = map_single(mmu_info, l2_virt);
+			if (!dma) {
+				dev_err(dev, "Failed to map l2pt page\n");
+				free_page((unsigned long)l2_virt);
+				err = -EINVAL;
+				goto error;
+			}
+
+			l1_entry = dma >> ISP_PADDR_SHIFT;
+
+			dev_dbg(dev, "page for l1_idx %u %p allocated\n",
+				l1_idx, l2_virt);
+			mmu_info->l1_pt[l1_idx] = l1_entry;
+			mmu_info->l2_pts[l1_idx] = l2_virt;
+
+			clflush_cache_range(&mmu_info->l1_pt[l1_idx],
+					    sizeof(mmu_info->l1_pt[l1_idx]));
+		}
+
+		l2_pt = mmu_info->l2_pts[l1_idx];
+		l2_entries = 0;
+
+		for (l2_idx = (iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
+		     size > 0 && l2_idx < ISP_L2PT_PTES; l2_idx++) {
+			l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT;
+
+			dev_dbg(dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx,
+				l2_pt[l2_idx]);
+
+			iova += ISP_PAGE_SIZE;
+			paddr += ISP_PAGE_SIZE;
+			mapped += ISP_PAGE_SIZE;
+			size -= ISP_PAGE_SIZE;
+
+			l2_entries++;
+		}
+
+		WARN_ON_ONCE(!l2_entries);
+		clflush_cache_range(&l2_pt[l2_idx - l2_entries],
+				    sizeof(l2_pt[0]) * l2_entries);
 	}
 
-	l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT;
-
-	clflush_cache_range((void *)&l2_pt[l2_idx], sizeof(l2_pt[l2_idx]));
 	spin_unlock_irqrestore(&mmu_info->lock, flags);
 
-	dev_dbg(mmu_info->dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx,
-		l2_pt[l2_idx]);
-
 	return 0;
+
+error:
+	spin_unlock_irqrestore(&mmu_info->lock, flags);
+	/* unroll mapping in case something went wrong */
+	if (mapped)
+		l2_unmap(mmu_info, iova - mapped, paddr - mapped, mapped);
+
+	return err;
 }
 
 static int __ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
@@ -692,10 +710,7 @@  size_t ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
 int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
 		 phys_addr_t paddr, size_t size)
 {
-	unsigned long orig_iova = iova;
 	unsigned int min_pagesz;
-	size_t orig_size = size;
-	int ret = 0;
 
 	if (mmu_info->pgsize_bitmap == 0UL)
 		return -ENODEV;
@@ -718,28 +733,7 @@  int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
 	dev_dbg(mmu_info->dev, "map: iova 0x%lx pa %pa size 0x%zx\n",
 		iova, &paddr, size);
 
-	while (size) {
-		size_t pgsize = ipu6_mmu_pgsize(mmu_info->pgsize_bitmap,
-						iova | paddr, size);
-
-		dev_dbg(mmu_info->dev,
-			"mapping: iova 0x%lx pa %pa pgsize 0x%zx\n",
-			iova, &paddr, pgsize);
-
-		ret = __ipu6_mmu_map(mmu_info, iova, paddr, pgsize);
-		if (ret)
-			break;
-
-		iova += pgsize;
-		paddr += pgsize;
-		size -= pgsize;
-	}
-
-	/* unroll mapping in case something went wrong */
-	if (ret)
-		ipu6_mmu_unmap(mmu_info, orig_iova, orig_size - size);
-
-	return ret;
+	return __ipu6_mmu_map(mmu_info, iova, paddr, size);
 }
 
 static void ipu6_mmu_destroy(struct ipu6_mmu *mmu)