2023-12-06 02:43:53

by Zhenghao Gu

[permalink] [raw]
Subject: [PATCH] wifi: ath11k: fix IOMMU errors on buffer rings

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 <[email protected]>
---
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)
+{
+ 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);


2023-12-11 14:55:06

by Jeff Johnson

[permalink] [raw]
Subject: Re: [PATCH] wifi: ath11k: fix IOMMU errors on buffer rings

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 <[email protected]>
> ---
> 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);
>