diff mbox series

wifi: ath11k: fix IOMMU errors on buffer rings

Message ID 20231206024325.27283-1-imguzh@gmail.com
State New
Headers show
Series wifi: ath11k: fix IOMMU errors on buffer rings | expand

Commit Message

Zhenghao Gu Dec. 6, 2023, 2:43 a.m. UTC
virt_to_phys doesn't work on systems with IOMMU enabled,
which have non-identity physical-to-IOVA mappings.
It leads to IO_PAGE_FAULTs like this:
[IO_PAGE_FAULT domain=0x0023 address=0x1cce00000 flags=0x0020]
and no link can be established.

This patch changes that to dma_map_single(), which works correctly.

Tested-on: QCN9074 hw1.0 PCI WLAN.HK.2.7.0.1-01744-QCAHKSWPL_SILICONZ-1
Signed-off-by: Zhenghao Gu <imguzh@gmail.com>
---
 drivers/net/wireless/ath/ath11k/dp.c  | 19 ++++++++++++++++---
 drivers/net/wireless/ath/ath11k/hal.c | 20 ++++++++++++++++++--
 drivers/net/wireless/ath/ath11k/hal.h |  2 ++
 3 files changed, 36 insertions(+), 5 deletions(-)

Comments

Jeff Johnson Dec. 11, 2023, 2:54 p.m. UTC | #1
On 12/5/2023 6:43 PM, Zhenghao Gu wrote:
> virt_to_phys doesn't work on systems with IOMMU enabled,
> which have non-identity physical-to-IOVA mappings.
> It leads to IO_PAGE_FAULTs like this:
> [IO_PAGE_FAULT domain=0x0023 address=0x1cce00000 flags=0x0020]
> and no link can be established.
> 
> This patch changes that to dma_map_single(), which works correctly.
> 
> Tested-on: QCN9074 hw1.0 PCI WLAN.HK.2.7.0.1-01744-QCAHKSWPL_SILICONZ-1
> Signed-off-by: Zhenghao Gu <imguzh@gmail.com>
> ---
>  drivers/net/wireless/ath/ath11k/dp.c  | 19 ++++++++++++++++---
>  drivers/net/wireless/ath/ath11k/hal.c | 20 ++++++++++++++++++--
>  drivers/net/wireless/ath/ath11k/hal.h |  2 ++
>  3 files changed, 36 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/net/wireless/ath/ath11k/dp.c b/drivers/net/wireless/ath/ath11k/dp.c
> index d070bcb3f..c74230e4c 100644
> --- a/drivers/net/wireless/ath/ath11k/dp.c
> +++ b/drivers/net/wireless/ath/ath11k/dp.c
> @@ -104,11 +104,14 @@ void ath11k_dp_srng_cleanup(struct ath11k_base *ab, struct dp_srng *ring)
>  	if (!ring->vaddr_unaligned)
>  		return;
>  
> -	if (ring->cached)
> +	if (ring->cached) {
> +		dma_unmap_single(ab->dev, ring->paddr_unaligned, ring->size,
> +				 DMA_FROM_DEVICE);
>  		kfree(ring->vaddr_unaligned);
> -	else
> +	} else {
>  		dma_free_coherent(ab->dev, ring->size, ring->vaddr_unaligned,
>  				  ring->paddr_unaligned);
> +	}
>  
>  	ring->vaddr_unaligned = NULL;
>  }
> @@ -249,7 +252,17 @@ int ath11k_dp_srng_setup(struct ath11k_base *ab, struct dp_srng *ring,
>  
>  		if (cached) {
>  			ring->vaddr_unaligned = kzalloc(ring->size, GFP_KERNEL);
> -			ring->paddr_unaligned = virt_to_phys(ring->vaddr_unaligned);
> +			if (!ring->vaddr_unaligned)
> +				return -ENOMEM;
> +
> +			ring->paddr_unaligned =
> +				dma_map_single(ab->dev, ring->vaddr_unaligned,
> +					       ring->size, DMA_FROM_DEVICE);
> +			if (dma_mapping_error(ab->dev, ring->paddr_unaligned)) {
> +				kfree(ring->vaddr_unaligned);
> +				ring->vaddr_unaligned = NULL;
> +				return -ENOMEM;
> +			}
>  		}
>  	}
>  
> diff --git a/drivers/net/wireless/ath/ath11k/hal.c b/drivers/net/wireless/ath/ath11k/hal.c
> index 0a99aa7dd..bd4cccdba 100644
> --- a/drivers/net/wireless/ath/ath11k/hal.c
> +++ b/drivers/net/wireless/ath/ath11k/hal.c
> @@ -628,15 +628,31 @@ u32 *ath11k_hal_srng_dst_peek(struct ath11k_base *ab, struct hal_srng *srng)
>  	return NULL;
>  }
>  
> +u32 *ath11k_hal_srng_dst_peek_with_dma(struct ath11k_base *ab,
> +				       struct hal_srng *srng, dma_addr_t *paddr)

since this is only used from within hal.c make it static

> +{
> +	lockdep_assert_held(&srng->lock);
> +
> +	if (srng->u.dst_ring.tp != srng->u.dst_ring.cached_hp) {
> +		*paddr = (srng->ring_base_paddr +
> +			  sizeof(*srng->ring_base_vaddr) * srng->u.dst_ring.tp);
> +		return (srng->ring_base_vaddr + srng->u.dst_ring.tp);
> +	}
> +
> +	return NULL;
> +}
> +
>  static void ath11k_hal_srng_prefetch_desc(struct ath11k_base *ab,
>  					  struct hal_srng *srng)
>  {
>  	u32 *desc;
> +	dma_addr_t desc_paddr;
> +
>  
>  	/* prefetch only if desc is available */
> -	desc = ath11k_hal_srng_dst_peek(ab, srng);
> +	desc = ath11k_hal_srng_dst_peek_with_dma(ab, srng, &desc_paddr);
>  	if (likely(desc)) {
> -		dma_sync_single_for_cpu(ab->dev, virt_to_phys(desc),
> +		dma_sync_single_for_cpu(ab->dev, desc_paddr,
>  					(srng->entry_size * sizeof(u32)),
>  					DMA_FROM_DEVICE);
>  		prefetch(desc);
> diff --git a/drivers/net/wireless/ath/ath11k/hal.h b/drivers/net/wireless/ath/ath11k/hal.h
> index 1942d41d6..facaf8fe0 100644
> --- a/drivers/net/wireless/ath/ath11k/hal.h
> +++ b/drivers/net/wireless/ath/ath11k/hal.h
> @@ -943,6 +943,8 @@ void ath11k_hal_srng_get_params(struct ath11k_base *ab, struct hal_srng *srng,
>  u32 *ath11k_hal_srng_dst_get_next_entry(struct ath11k_base *ab,
>  					struct hal_srng *srng);
>  u32 *ath11k_hal_srng_dst_peek(struct ath11k_base *ab, struct hal_srng *srng);
> +u32 *ath11k_hal_srng_dst_peek_with_dma(struct ath11k_base *ab,
> +				       struct hal_srng *srng, dma_addr_t *paddr);

should be static so remove this

>  int ath11k_hal_srng_dst_num_free(struct ath11k_base *ab, struct hal_srng *srng,
>  				 bool sync_hw_ptr);
>  u32 *ath11k_hal_srng_src_peek(struct ath11k_base *ab, struct hal_srng *srng);
>
diff mbox series

Patch

diff --git a/drivers/net/wireless/ath/ath11k/dp.c b/drivers/net/wireless/ath/ath11k/dp.c
index d070bcb3f..c74230e4c 100644
--- a/drivers/net/wireless/ath/ath11k/dp.c
+++ b/drivers/net/wireless/ath/ath11k/dp.c
@@ -104,11 +104,14 @@  void ath11k_dp_srng_cleanup(struct ath11k_base *ab, struct dp_srng *ring)
 	if (!ring->vaddr_unaligned)
 		return;
 
-	if (ring->cached)
+	if (ring->cached) {
+		dma_unmap_single(ab->dev, ring->paddr_unaligned, ring->size,
+				 DMA_FROM_DEVICE);
 		kfree(ring->vaddr_unaligned);
-	else
+	} else {
 		dma_free_coherent(ab->dev, ring->size, ring->vaddr_unaligned,
 				  ring->paddr_unaligned);
+	}
 
 	ring->vaddr_unaligned = NULL;
 }
@@ -249,7 +252,17 @@  int ath11k_dp_srng_setup(struct ath11k_base *ab, struct dp_srng *ring,
 
 		if (cached) {
 			ring->vaddr_unaligned = kzalloc(ring->size, GFP_KERNEL);
-			ring->paddr_unaligned = virt_to_phys(ring->vaddr_unaligned);
+			if (!ring->vaddr_unaligned)
+				return -ENOMEM;
+
+			ring->paddr_unaligned =
+				dma_map_single(ab->dev, ring->vaddr_unaligned,
+					       ring->size, DMA_FROM_DEVICE);
+			if (dma_mapping_error(ab->dev, ring->paddr_unaligned)) {
+				kfree(ring->vaddr_unaligned);
+				ring->vaddr_unaligned = NULL;
+				return -ENOMEM;
+			}
 		}
 	}
 
diff --git a/drivers/net/wireless/ath/ath11k/hal.c b/drivers/net/wireless/ath/ath11k/hal.c
index 0a99aa7dd..bd4cccdba 100644
--- a/drivers/net/wireless/ath/ath11k/hal.c
+++ b/drivers/net/wireless/ath/ath11k/hal.c
@@ -628,15 +628,31 @@  u32 *ath11k_hal_srng_dst_peek(struct ath11k_base *ab, struct hal_srng *srng)
 	return NULL;
 }
 
+u32 *ath11k_hal_srng_dst_peek_with_dma(struct ath11k_base *ab,
+				       struct hal_srng *srng, dma_addr_t *paddr)
+{
+	lockdep_assert_held(&srng->lock);
+
+	if (srng->u.dst_ring.tp != srng->u.dst_ring.cached_hp) {
+		*paddr = (srng->ring_base_paddr +
+			  sizeof(*srng->ring_base_vaddr) * srng->u.dst_ring.tp);
+		return (srng->ring_base_vaddr + srng->u.dst_ring.tp);
+	}
+
+	return NULL;
+}
+
 static void ath11k_hal_srng_prefetch_desc(struct ath11k_base *ab,
 					  struct hal_srng *srng)
 {
 	u32 *desc;
+	dma_addr_t desc_paddr;
+
 
 	/* prefetch only if desc is available */
-	desc = ath11k_hal_srng_dst_peek(ab, srng);
+	desc = ath11k_hal_srng_dst_peek_with_dma(ab, srng, &desc_paddr);
 	if (likely(desc)) {
-		dma_sync_single_for_cpu(ab->dev, virt_to_phys(desc),
+		dma_sync_single_for_cpu(ab->dev, desc_paddr,
 					(srng->entry_size * sizeof(u32)),
 					DMA_FROM_DEVICE);
 		prefetch(desc);
diff --git a/drivers/net/wireless/ath/ath11k/hal.h b/drivers/net/wireless/ath/ath11k/hal.h
index 1942d41d6..facaf8fe0 100644
--- a/drivers/net/wireless/ath/ath11k/hal.h
+++ b/drivers/net/wireless/ath/ath11k/hal.h
@@ -943,6 +943,8 @@  void ath11k_hal_srng_get_params(struct ath11k_base *ab, struct hal_srng *srng,
 u32 *ath11k_hal_srng_dst_get_next_entry(struct ath11k_base *ab,
 					struct hal_srng *srng);
 u32 *ath11k_hal_srng_dst_peek(struct ath11k_base *ab, struct hal_srng *srng);
+u32 *ath11k_hal_srng_dst_peek_with_dma(struct ath11k_base *ab,
+				       struct hal_srng *srng, dma_addr_t *paddr);
 int ath11k_hal_srng_dst_num_free(struct ath11k_base *ab, struct hal_srng *srng,
 				 bool sync_hw_ptr);
 u32 *ath11k_hal_srng_src_peek(struct ath11k_base *ab, struct hal_srng *srng);