diff mbox series

[1/5] iommu/tegra-smmu: Fix domain_alloc

Message ID 1547671814-30088-1-git-send-email-navneetk@nvidia.com
State Deferred
Headers show
Series [1/5] iommu/tegra-smmu: Fix domain_alloc | expand

Commit Message

navneet kumar Jan. 16, 2019, 8:50 p.m. UTC
* Allocate dma iova cookie for a domain while adding dma iommu
devices.
* Perform a stricter check for domain type parameter.

Signed-off-by: Navneet Kumar <navneetk@nvidia.com>
---
 drivers/iommu/tegra-smmu.c | 43 +++++++++++++++++++++++++++----------------
 1 file changed, 27 insertions(+), 16 deletions(-)

Comments

Dmitry Osipenko Jan. 17, 2019, 3:13 p.m. UTC | #1
16.01.2019 23:50, Navneet Kumar пишет:
> * Allocate dma iova cookie for a domain while adding dma iommu
> devices.
> * Perform a stricter check for domain type parameter.
> 

Commit message should tell what exactly is getting "fixed". Apparently you're trying to support T132 ARM64 here.

> Signed-off-by: Navneet Kumar <navneetk@nvidia.com>
> ---
>  drivers/iommu/tegra-smmu.c | 43 +++++++++++++++++++++++++++----------------
>  1 file changed, 27 insertions(+), 16 deletions(-)
> 
> diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c
> index 543f7c9..ee4d8a8 100644
> --- a/drivers/iommu/tegra-smmu.c
> +++ b/drivers/iommu/tegra-smmu.c
> @@ -16,6 +16,7 @@
>  #include <linux/platform_device.h>
>  #include <linux/slab.h>
>  #include <linux/dma-mapping.h>
> +#include <linux/dma-iommu.h>
>  
>  #include <soc/tegra/ahb.h>
>  #include <soc/tegra/mc.h>
> @@ -271,8 +272,10 @@ static bool tegra_smmu_capable(enum iommu_cap cap)
>  static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
>  {
>  	struct tegra_smmu_as *as;
> +	int ret;
>  
> -	if (type != IOMMU_DOMAIN_UNMANAGED)
> +	if (type != IOMMU_DOMAIN_UNMANAGED && type != IOMMU_DOMAIN_DMA &&
> +			type != IOMMU_DOMAIN_IDENTITY)
>  		return NULL;

Should be better to write these lines like this for the sake of readability:

	if (type != IOMMU_DOMAIN_UNMANAGED && 
	    type != IOMMU_DOMAIN_DMA &&
	    type != IOMMU_DOMAIN_IDENTITY)
		return NULL;


>  
>  	as = kzalloc(sizeof(*as), GFP_KERNEL);
> @@ -281,26 +284,22 @@ static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
>  
>  	as->attr = SMMU_PD_READABLE | SMMU_PD_WRITABLE | SMMU_PD_NONSECURE;
>  
> +	ret = (type == IOMMU_DOMAIN_DMA) ? iommu_get_dma_cookie(&as->domain) :
> +		-ENODEV;

This makes to fail allocation of domain that has type other than IOMMU_DOMAIN_DMA.

Should be:

	if (type == IOMMU_DOMAIN_DMA) {
		err = iommu_get_dma_cookie(&as->domain);
		if (err)
			goto free_as;
	}


> +	if (ret)
> +		goto free_as;
> +
>  	as->pd = alloc_page(GFP_KERNEL | __GFP_DMA | __GFP_ZERO);
> -	if (!as->pd) {
> -		kfree(as);
> -		return NULL;
> -	}
> +	if (!as->pd)
> +		goto put_dma_cookie;
>  
>  	as->count = kcalloc(SMMU_NUM_PDE, sizeof(u32), GFP_KERNEL);
> -	if (!as->count) {
> -		__free_page(as->pd);
> -		kfree(as);
> -		return NULL;
> -	}
> +	if (!as->count)
> +		goto free_pd_range;
>  
>  	as->pts = kcalloc(SMMU_NUM_PDE, sizeof(*as->pts), GFP_KERNEL);
> -	if (!as->pts) {
> -		kfree(as->count);
> -		__free_page(as->pd);
> -		kfree(as);
> -		return NULL;
> -	}
> +	if (!as->pts)
> +		goto free_pts;
>  
>  	/* setup aperture */
>  	as->domain.geometry.aperture_start = 0;
> @@ -308,6 +307,18 @@ static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
>  	as->domain.geometry.force_aperture = true;
>  
>  	return &as->domain;
> +
> +free_pts:
> +	kfree(as->pts);
> +free_pd_range:
> +	__free_page(as->pd);
> +put_dma_cookie:
> +	if (type == IOMMU_DOMAIN_DMA)
> +		iommu_put_dma_cookie(&as->domain);
> +free_as:
> +	kfree(as);
> +
> +	return NULL;
>  }
>  
>  static void tegra_smmu_domain_free(struct iommu_domain *domain)
>
Robin Murphy Jan. 17, 2019, 4:50 p.m. UTC | #2
On 17/01/2019 15:13, Dmitry Osipenko wrote:
> 16.01.2019 23:50, Navneet Kumar пишет:
>> * Allocate dma iova cookie for a domain while adding dma iommu
>> devices.
>> * Perform a stricter check for domain type parameter.
>>
> 
> Commit message should tell what exactly is getting "fixed". Apparently you're trying to support T132 ARM64 here.
> 
>> Signed-off-by: Navneet Kumar <navneetk@nvidia.com>
>> ---
>>   drivers/iommu/tegra-smmu.c | 43 +++++++++++++++++++++++++++----------------
>>   1 file changed, 27 insertions(+), 16 deletions(-)
>>
>> diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c
>> index 543f7c9..ee4d8a8 100644
>> --- a/drivers/iommu/tegra-smmu.c
>> +++ b/drivers/iommu/tegra-smmu.c
>> @@ -16,6 +16,7 @@
>>   #include <linux/platform_device.h>
>>   #include <linux/slab.h>
>>   #include <linux/dma-mapping.h>
>> +#include <linux/dma-iommu.h>
>>   
>>   #include <soc/tegra/ahb.h>
>>   #include <soc/tegra/mc.h>
>> @@ -271,8 +272,10 @@ static bool tegra_smmu_capable(enum iommu_cap cap)
>>   static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
>>   {
>>   	struct tegra_smmu_as *as;
>> +	int ret;
>>   
>> -	if (type != IOMMU_DOMAIN_UNMANAGED)
>> +	if (type != IOMMU_DOMAIN_UNMANAGED && type != IOMMU_DOMAIN_DMA &&
>> +			type != IOMMU_DOMAIN_IDENTITY)
>>   		return NULL;
> 
> Should be better to write these lines like this for the sake of readability:
> 
> 	if (type != IOMMU_DOMAIN_UNMANAGED &&
> 	    type != IOMMU_DOMAIN_DMA &&
> 	    type != IOMMU_DOMAIN_IDENTITY)
> 		return NULL;

Furthermore, AFAICS there's no special handling being added for identity 
domains - don't pretend to support them by giving back a regular 
translation domain, that's just misleading and broken.

> 
> 
>>   
>>   	as = kzalloc(sizeof(*as), GFP_KERNEL);
>> @@ -281,26 +284,22 @@ static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
>>   
>>   	as->attr = SMMU_PD_READABLE | SMMU_PD_WRITABLE | SMMU_PD_NONSECURE;
>>   
>> +	ret = (type == IOMMU_DOMAIN_DMA) ? iommu_get_dma_cookie(&as->domain) :
>> +		-ENODEV;
> 
> This makes to fail allocation of domain that has type other than IOMMU_DOMAIN_DMA.
> 
> Should be:
> 
> 	if (type == IOMMU_DOMAIN_DMA) {
> 		err = iommu_get_dma_cookie(&as->domain);
> 		if (err)
> 			goto free_as;
> 	}
> 
> 
>> +	if (ret)
>> +		goto free_as;
>> +
>>   	as->pd = alloc_page(GFP_KERNEL | __GFP_DMA | __GFP_ZERO);
>> -	if (!as->pd) {
>> -		kfree(as);
>> -		return NULL;
>> -	}
>> +	if (!as->pd)
>> +		goto put_dma_cookie;
>>   
>>   	as->count = kcalloc(SMMU_NUM_PDE, sizeof(u32), GFP_KERNEL);
>> -	if (!as->count) {
>> -		__free_page(as->pd);
>> -		kfree(as);
>> -		return NULL;
>> -	}
>> +	if (!as->count)
>> +		goto free_pd_range;
>>   
>>   	as->pts = kcalloc(SMMU_NUM_PDE, sizeof(*as->pts), GFP_KERNEL);
>> -	if (!as->pts) {
>> -		kfree(as->count);
>> -		__free_page(as->pd);
>> -		kfree(as);
>> -		return NULL;
>> -	}
>> +	if (!as->pts)
>> +		goto free_pts;
>>   
>>   	/* setup aperture */
>>   	as->domain.geometry.aperture_start = 0;
>> @@ -308,6 +307,18 @@ static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
>>   	as->domain.geometry.force_aperture = true;
>>   
>>   	return &as->domain;
>> +
>> +free_pts:
>> +	kfree(as->pts);
>> +free_pd_range:
>> +	__free_page(as->pd);
>> +put_dma_cookie:
>> +	if (type == IOMMU_DOMAIN_DMA)

FWIW you don't strictly need that check - since domain->iova_cookie 
won't be set for other domain types anyway, iommu_put_dma_cookie() will 
simply ignore them.

>> +		iommu_put_dma_cookie(&as->domain);
>> +free_as:
>> +	kfree(as);
>> +
>> +	return NULL;
>>   }
>>   
>>   static void tegra_smmu_domain_free(struct iommu_domain *domain)

How about not leaking the cookie when you free a DMA ops domain?

Robin.
navneet kumar Jan. 24, 2019, 10:15 p.m. UTC | #3
Thanks for the feedback, Robin, and, Dmitry.
I mostly agree with all your comments, pls see my responses inline.
I'll make these fixes in V2.

On 1/17/19 8:50 AM, Robin Murphy wrote:
> On 17/01/2019 15:13, Dmitry Osipenko wrote:
>> 16.01.2019 23:50, Navneet Kumar пишет:
>>> * Allocate dma iova cookie for a domain while adding dma iommu
>>> devices.
>>> * Perform a stricter check for domain type parameter.
>>>
>>
>> Commit message should tell what exactly is getting "fixed". Apparently you're trying to support T132 ARM64 here.

I'll fix it.

>>
>>> Signed-off-by: Navneet Kumar <navneetk@nvidia.com>
>>> ---
>>>   drivers/iommu/tegra-smmu.c | 43 +++++++++++++++++++++++++++----------------
>>>   1 file changed, 27 insertions(+), 16 deletions(-)
>>>
>>> diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c
>>> index 543f7c9..ee4d8a8 100644
>>> --- a/drivers/iommu/tegra-smmu.c
>>> +++ b/drivers/iommu/tegra-smmu.c
>>> @@ -16,6 +16,7 @@
>>>   #include <linux/platform_device.h>
>>>   #include <linux/slab.h>
>>>   #include <linux/dma-mapping.h>
>>> +#include <linux/dma-iommu.h>
>>>     #include <soc/tegra/ahb.h>
>>>   #include <soc/tegra/mc.h>
>>> @@ -271,8 +272,10 @@ static bool tegra_smmu_capable(enum iommu_cap cap)
>>>   static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
>>>   {
>>>       struct tegra_smmu_as *as;
>>> +    int ret;
>>>   -    if (type != IOMMU_DOMAIN_UNMANAGED)
>>> +    if (type != IOMMU_DOMAIN_UNMANAGED && type != IOMMU_DOMAIN_DMA &&
>>> +            type != IOMMU_DOMAIN_IDENTITY)
>>>           return NULL;
>>
>> Should be better to write these lines like this for the sake of readability:
>>
>>     if (type != IOMMU_DOMAIN_UNMANAGED &&
>>         type != IOMMU_DOMAIN_DMA &&
>>         type != IOMMU_DOMAIN_IDENTITY)
>>         return NULL;
> 
> Furthermore, AFAICS there's no special handling being added for identity domains - don't pretend to support them by giving back a regular translation domain, that's just misleading and broken.

Agreed.

> 
>>
>>
>>>         as = kzalloc(sizeof(*as), GFP_KERNEL);
>>> @@ -281,26 +284,22 @@ static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
>>>         as->attr = SMMU_PD_READABLE | SMMU_PD_WRITABLE | SMMU_PD_NONSECURE;
>>>   +    ret = (type == IOMMU_DOMAIN_DMA) ? iommu_get_dma_cookie(&as->domain) :
>>> +        -ENODEV;
>>
>> This makes to fail allocation of domain that has type other than IOMMU_DOMAIN_DMA.
>>
>> Should be:
>>
>>     if (type == IOMMU_DOMAIN_DMA) {
>>         err = iommu_get_dma_cookie(&as->domain);
>>         if (err)
>>             goto free_as;
>>     }
>>

Agreed.

>>
>>> +    if (ret)
>>> +        goto free_as;
>>> +
>>>       as->pd = alloc_page(GFP_KERNEL | __GFP_DMA | __GFP_ZERO);
>>> -    if (!as->pd) {
>>> -        kfree(as);
>>> -        return NULL;
>>> -    }
>>> +    if (!as->pd)
>>> +        goto put_dma_cookie;
>>>         as->count = kcalloc(SMMU_NUM_PDE, sizeof(u32), GFP_KERNEL);
>>> -    if (!as->count) {
>>> -        __free_page(as->pd);
>>> -        kfree(as);
>>> -        return NULL;
>>> -    }
>>> +    if (!as->count)
>>> +        goto free_pd_range;
>>>         as->pts = kcalloc(SMMU_NUM_PDE, sizeof(*as->pts), GFP_KERNEL);
>>> -    if (!as->pts) {
>>> -        kfree(as->count);
>>> -        __free_page(as->pd);
>>> -        kfree(as);
>>> -        return NULL;
>>> -    }
>>> +    if (!as->pts)
>>> +        goto free_pts;
>>>         /* setup aperture */
>>>       as->domain.geometry.aperture_start = 0;
>>> @@ -308,6 +307,18 @@ static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
>>>       as->domain.geometry.force_aperture = true;
>>>         return &as->domain;
>>> +
>>> +free_pts:
>>> +    kfree(as->pts);
>>> +free_pd_range:
>>> +    __free_page(as->pd);
>>> +put_dma_cookie:
>>> +    if (type == IOMMU_DOMAIN_DMA)
> 
> FWIW you don't strictly need that check - since domain->iova_cookie won't be set for other domain types anyway, iommu_put_dma_cookie() will simply ignore them.
> 

I'll still keep that check and not rely solely on the put_dma_cookie API to handle this case.
This will keep the code robust even if the put_dma_cookie API changes in future (for whatever reason).
It also looks like the canonical usage in other drivers.

>>> +        iommu_put_dma_cookie(&as->domain);
>>> +free_as:
>>> +    kfree(as);
>>> +
>>> +    return NULL;
>>>   }
>>>     static void tegra_smmu_domain_free(struct iommu_domain *domain)
> 
> How about not leaking the cookie when you free a DMA ops domain?
> 

Agreed.

> Robin.
Thierry Reding Feb. 14, 2019, 11:12 a.m. UTC | #4
On Wed, Jan 16, 2019 at 12:50:10PM -0800, Navneet Kumar wrote:
> * Allocate dma iova cookie for a domain while adding dma iommu
> devices.
> * Perform a stricter check for domain type parameter.
> 
> Signed-off-by: Navneet Kumar <navneetk@nvidia.com>
> ---
>  drivers/iommu/tegra-smmu.c | 43 +++++++++++++++++++++++++++----------------
>  1 file changed, 27 insertions(+), 16 deletions(-)

I just gave this a quick spin because I was investigating how we could
potentially make use of the DMA API instead of the IOMMU API directly in
Tegra DRM. We currently rely on the fact that the Tegra SMMU driver only
supports unmanaged domains. Once we start supporting DMA domains all the
automatic machinery kicks in and there's lots of SMMU faults. I think at
least partially those faults point out bugs we currently have in the
code. From the looks of it, the display controller is running during
boot and happily fetching from whatever address it was configured with
in the bootloader, and when we enable the ASID for the display
controller as part of the DMA/IOMMU setup, the fetches from the display
controller will be accessing IOV addresses that don't have a mapping.

One one hand that's a good thing because it points out existing
weaknesses, but then it also means that we can't merge this series
because it causes bad regressions.

I also see failures from the GPU with this applied, which I think stem
from the fact that we're now transparently mapping allocations through
the SMMU without the Nouveau driver knowing that and setting the
appropriate bit when addressing memory. Or it could come from the SMMU
code in Nouveau trying to map an already mapped buffer, so effectively
creating an IOVA mapping to an address that is already a IOV address
rather than a physical address.

So I think before we can go ahead with this series we have a lot of
janitorial work to do first so that this won't cause any regressions
when applied.

Thierry
diff mbox series

Patch

diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c
index 543f7c9..ee4d8a8 100644
--- a/drivers/iommu/tegra-smmu.c
+++ b/drivers/iommu/tegra-smmu.c
@@ -16,6 +16,7 @@ 
 #include <linux/platform_device.h>
 #include <linux/slab.h>
 #include <linux/dma-mapping.h>
+#include <linux/dma-iommu.h>
 
 #include <soc/tegra/ahb.h>
 #include <soc/tegra/mc.h>
@@ -271,8 +272,10 @@  static bool tegra_smmu_capable(enum iommu_cap cap)
 static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
 {
 	struct tegra_smmu_as *as;
+	int ret;
 
-	if (type != IOMMU_DOMAIN_UNMANAGED)
+	if (type != IOMMU_DOMAIN_UNMANAGED && type != IOMMU_DOMAIN_DMA &&
+			type != IOMMU_DOMAIN_IDENTITY)
 		return NULL;
 
 	as = kzalloc(sizeof(*as), GFP_KERNEL);
@@ -281,26 +284,22 @@  static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
 
 	as->attr = SMMU_PD_READABLE | SMMU_PD_WRITABLE | SMMU_PD_NONSECURE;
 
+	ret = (type == IOMMU_DOMAIN_DMA) ? iommu_get_dma_cookie(&as->domain) :
+		-ENODEV;
+	if (ret)
+		goto free_as;
+
 	as->pd = alloc_page(GFP_KERNEL | __GFP_DMA | __GFP_ZERO);
-	if (!as->pd) {
-		kfree(as);
-		return NULL;
-	}
+	if (!as->pd)
+		goto put_dma_cookie;
 
 	as->count = kcalloc(SMMU_NUM_PDE, sizeof(u32), GFP_KERNEL);
-	if (!as->count) {
-		__free_page(as->pd);
-		kfree(as);
-		return NULL;
-	}
+	if (!as->count)
+		goto free_pd_range;
 
 	as->pts = kcalloc(SMMU_NUM_PDE, sizeof(*as->pts), GFP_KERNEL);
-	if (!as->pts) {
-		kfree(as->count);
-		__free_page(as->pd);
-		kfree(as);
-		return NULL;
-	}
+	if (!as->pts)
+		goto free_pts;
 
 	/* setup aperture */
 	as->domain.geometry.aperture_start = 0;
@@ -308,6 +307,18 @@  static struct iommu_domain *tegra_smmu_domain_alloc(unsigned type)
 	as->domain.geometry.force_aperture = true;
 
 	return &as->domain;
+
+free_pts:
+	kfree(as->pts);
+free_pd_range:
+	__free_page(as->pd);
+put_dma_cookie:
+	if (type == IOMMU_DOMAIN_DMA)
+		iommu_put_dma_cookie(&as->domain);
+free_as:
+	kfree(as);
+
+	return NULL;
 }
 
 static void tegra_smmu_domain_free(struct iommu_domain *domain)