summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/net/qcom,ipa.yaml10
-rw-r--r--drivers/net/ipa/ipa.h10
-rw-r--r--drivers/net/ipa/ipa_data-sc7180.c14
-rw-r--r--drivers/net/ipa/ipa_data-sdm845.c14
-rw-r--r--drivers/net/ipa/ipa_data.h23
-rw-r--r--drivers/net/ipa/ipa_main.c2
-rw-r--r--drivers/net/ipa/ipa_mem.c209
-rw-r--r--drivers/net/ipa/ipa_mem.h3
8 files changed, 263 insertions, 22 deletions
diff --git a/Documentation/devicetree/bindings/net/qcom,ipa.yaml b/Documentation/devicetree/bindings/net/qcom,ipa.yaml
index 140f15245654..7b749fc04c32 100644
--- a/Documentation/devicetree/bindings/net/qcom,ipa.yaml
+++ b/Documentation/devicetree/bindings/net/qcom,ipa.yaml
@@ -20,7 +20,10 @@ description:
The GSI is an integral part of the IPA, but it is logically isolated
and has a distinct interrupt and a separately-defined address space.
- See also soc/qcom/qcom,smp2p.txt and interconnect/interconnect.txt.
+ See also soc/qcom/qcom,smp2p.txt and interconnect/interconnect.txt. See
+ iommu/iommu.txt and iommu/arm,smmu.yaml for more information about SMMU
+ bindings.
+
- |
-------- ---------
@@ -54,6 +57,9 @@ properties:
- const: ipa-shared
- const: gsi
+ iommus:
+ maxItems: 1
+
clocks:
maxItems: 1
@@ -126,6 +132,7 @@ properties:
required:
- compatible
+ - iommus
- reg
- clocks
- interrupts
@@ -164,6 +171,7 @@ examples:
modem-init;
modem-remoteproc = <&mss_pil>;
+ iommus = <&apps_smmu 0x720 0x3>;
reg = <0 0x1e40000 0 0x7000>,
<0 0x1e47000 0 0x2000>,
<0 0x1e04000 0 0x2c000>;
diff --git a/drivers/net/ipa/ipa.h b/drivers/net/ipa/ipa.h
index 23fb29889e5a..b10a85392952 100644
--- a/drivers/net/ipa/ipa.h
+++ b/drivers/net/ipa/ipa.h
@@ -47,6 +47,10 @@ struct ipa_interrupt;
* @mem_offset: Offset from @mem_virt used for access to IPA memory
* @mem_size: Total size (bytes) of memory at @mem_virt
* @mem: Array of IPA-local memory region descriptors
+ * @imem_iova: I/O virtual address of IPA region in IMEM
+ * @imem_size; Size of IMEM region
+ * @smem_iova: I/O virtual address of IPA region in SMEM
+ * @smem_size; Size of SMEM region
* @zero_addr: DMA address of preallocated zero-filled memory
* @zero_virt: Virtual address of preallocated zero-filled memory
* @zero_size: Size (bytes) of preallocated zero-filled memory
@@ -88,6 +92,12 @@ struct ipa {
u32 mem_size;
const struct ipa_mem *mem;
+ unsigned long imem_iova;
+ size_t imem_size;
+
+ unsigned long smem_iova;
+ size_t smem_size;
+
dma_addr_t zero_addr;
void *zero_virt;
size_t zero_size;
diff --git a/drivers/net/ipa/ipa_data-sc7180.c b/drivers/net/ipa/ipa_data-sc7180.c
index 042b5fc3c135..43faa35ae726 100644
--- a/drivers/net/ipa/ipa_data-sc7180.c
+++ b/drivers/net/ipa/ipa_data-sc7180.c
@@ -193,7 +193,7 @@ static const struct ipa_resource_data ipa_resource_data = {
};
/* IPA-resident memory region configuration for the SC7180 SoC. */
-static const struct ipa_mem ipa_mem_data[] = {
+static const struct ipa_mem ipa_mem_local_data[] = {
[IPA_MEM_UC_SHARED] = {
.offset = 0x0000,
.size = 0x0080,
@@ -296,12 +296,20 @@ static const struct ipa_mem ipa_mem_data[] = {
},
};
+static struct ipa_mem_data ipa_mem_data = {
+ .local_count = ARRAY_SIZE(ipa_mem_local_data),
+ .local = ipa_mem_local_data,
+ .imem_addr = 0x146a8000,
+ .imem_size = 0x00002000,
+ .smem_id = 497,
+ .smem_size = 0x00002000,
+};
+
/* Configuration data for the SC7180 SoC. */
const struct ipa_data ipa_data_sc7180 = {
.version = IPA_VERSION_4_2,
.endpoint_count = ARRAY_SIZE(ipa_gsi_endpoint_data),
.endpoint_data = ipa_gsi_endpoint_data,
.resource_data = &ipa_resource_data,
- .mem_count = ARRAY_SIZE(ipa_mem_data),
- .mem_data = ipa_mem_data,
+ .mem_data = &ipa_mem_data,
};
diff --git a/drivers/net/ipa/ipa_data-sdm845.c b/drivers/net/ipa/ipa_data-sdm845.c
index 0d9c36e1e806..f7ba85717edf 100644
--- a/drivers/net/ipa/ipa_data-sdm845.c
+++ b/drivers/net/ipa/ipa_data-sdm845.c
@@ -235,7 +235,7 @@ static const struct ipa_resource_data ipa_resource_data = {
};
/* IPA-resident memory region configuration for the SDM845 SoC. */
-static const struct ipa_mem ipa_mem_data[] = {
+static const struct ipa_mem ipa_mem_local_data[] = {
[IPA_MEM_UC_SHARED] = {
.offset = 0x0000,
.size = 0x0080,
@@ -318,12 +318,20 @@ static const struct ipa_mem ipa_mem_data[] = {
},
};
+static struct ipa_mem_data ipa_mem_data = {
+ .local_count = ARRAY_SIZE(ipa_mem_local_data),
+ .local = ipa_mem_local_data,
+ .imem_addr = 0x146bd000,
+ .imem_size = 0x00002000,
+ .smem_id = 497,
+ .smem_size = 0x00002000,
+};
+
/* Configuration data for the SDM845 SoC. */
const struct ipa_data ipa_data_sdm845 = {
.version = IPA_VERSION_3_5_1,
.endpoint_count = ARRAY_SIZE(ipa_gsi_endpoint_data),
.endpoint_data = ipa_gsi_endpoint_data,
.resource_data = &ipa_resource_data,
- .mem_count = ARRAY_SIZE(ipa_mem_data),
- .mem_data = ipa_mem_data,
+ .mem_data = &ipa_mem_data,
};
diff --git a/drivers/net/ipa/ipa_data.h b/drivers/net/ipa/ipa_data.h
index 7110de2de817..16dfd74717b1 100644
--- a/drivers/net/ipa/ipa_data.h
+++ b/drivers/net/ipa/ipa_data.h
@@ -245,15 +245,21 @@ struct ipa_resource_data {
};
/**
- * struct ipa_mem - IPA-local memory region description
- * @offset: offset in IPA memory space to base of the region
- * @size: size in bytes base of the region
- * @canary_count: number of 32-bit "canary" values that precede region
+ * struct ipa_mem - description of IPA memory regions
+ * @local_count: number of regions defined in the local[] array
+ * @local: array of IPA-local memory region descriptors
+ * @imem_addr: physical address of IPA region within IMEM
+ * @imem_size: size in bytes of IPA IMEM region
+ * @smem_id: item identifier for IPA region within SMEM memory
+ * @imem_size: size in bytes of the IPA SMEM region
*/
struct ipa_mem_data {
- u32 offset;
- u16 size;
- u16 canary_count;
+ u32 local_count;
+ const struct ipa_mem *local;
+ u32 imem_addr;
+ u32 imem_size;
+ u32 smem_id;
+ u32 smem_size;
};
/**
@@ -270,8 +276,7 @@ struct ipa_data {
u32 endpoint_count; /* # entries in endpoint_data[] */
const struct ipa_gsi_endpoint_data *endpoint_data;
const struct ipa_resource_data *resource_data;
- u32 mem_count; /* # entries in mem_data[] */
- const struct ipa_mem *mem_data;
+ const struct ipa_mem_data *mem_data;
};
extern const struct ipa_data ipa_data_sdm845;
diff --git a/drivers/net/ipa/ipa_main.c b/drivers/net/ipa/ipa_main.c
index 28998dcce3d2..9295a9122e8e 100644
--- a/drivers/net/ipa/ipa_main.c
+++ b/drivers/net/ipa/ipa_main.c
@@ -778,7 +778,7 @@ static int ipa_probe(struct platform_device *pdev)
if (ret)
goto err_kfree_ipa;
- ret = ipa_mem_init(ipa, data->mem_count, data->mem_data);
+ ret = ipa_mem_init(ipa, data->mem_data);
if (ret)
goto err_reg_exit;
diff --git a/drivers/net/ipa/ipa_mem.c b/drivers/net/ipa/ipa_mem.c
index 42d2c29d9f0c..aa8f6b0f3d50 100644
--- a/drivers/net/ipa/ipa_mem.c
+++ b/drivers/net/ipa/ipa_mem.c
@@ -8,10 +8,13 @@
#include <linux/bitfield.h>
#include <linux/bug.h>
#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
#include <linux/io.h>
+#include <linux/soc/qcom/smem.h>
#include "ipa.h"
#include "ipa_reg.h"
+#include "ipa_data.h"
#include "ipa_cmd.h"
#include "ipa_mem.h"
#include "ipa_data.h"
@@ -21,6 +24,9 @@
/* "Canary" value placed between memory regions to detect overflow */
#define IPA_MEM_CANARY_VAL cpu_to_le32(0xdeadbeef)
+/* SMEM host id representing the modem. */
+#define QCOM_SMEM_HOST_MODEM 1
+
/* Add an immediate command to a transaction that zeroes a memory region */
static void
ipa_mem_zero_region_add(struct gsi_trans *trans, const struct ipa_mem *mem)
@@ -265,16 +271,194 @@ int ipa_mem_zero_modem(struct ipa *ipa)
return 0;
}
+/**
+ * ipa_imem_init() - Initialize IMEM memory used by the IPA
+ * @ipa: IPA pointer
+ * @addr: Physical address of the IPA region in IMEM
+ * @size: Size (bytes) of the IPA region in IMEM
+ *
+ * IMEM is a block of shared memory separate from system DRAM, and
+ * a portion of this memory is available for the IPA to use. The
+ * modem accesses this memory directly, but the IPA accesses it
+ * via the IOMMU, using the AP's credentials.
+ *
+ * If this region exists (size > 0) we map it for read/write access
+ * through the IOMMU using the IPA device.
+ *
+ * Note: @addr and @size are not guaranteed to be page-aligned.
+ */
+static int ipa_imem_init(struct ipa *ipa, unsigned long addr, size_t size)
+{
+ struct device *dev = &ipa->pdev->dev;
+ struct iommu_domain *domain;
+ unsigned long iova;
+ phys_addr_t phys;
+ int ret;
+
+ if (!size)
+ return 0; /* IMEM memory not used */
+
+ domain = iommu_get_domain_for_dev(dev);
+ if (!domain) {
+ dev_err(dev, "no IOMMU domain found for IMEM\n");
+ return -EINVAL;
+ }
+
+ /* Align the address down and the size up to page boundaries */
+ phys = addr & PAGE_MASK;
+ size = PAGE_ALIGN(size + addr - phys);
+ iova = phys; /* We just want a direct mapping */
+
+ ret = iommu_map(domain, iova, phys, size, IOMMU_READ | IOMMU_WRITE);
+ if (ret)
+ return ret;
+
+ ipa->imem_iova = iova;
+ ipa->imem_size = size;
+
+ return 0;
+}
+
+static void ipa_imem_exit(struct ipa *ipa)
+{
+ struct iommu_domain *domain;
+ struct device *dev;
+
+ if (!ipa->imem_size)
+ return;
+
+ dev = &ipa->pdev->dev;
+ domain = iommu_get_domain_for_dev(dev);
+ if (domain) {
+ size_t size;
+
+ size = iommu_unmap(domain, ipa->imem_iova, ipa->imem_size);
+ if (size != ipa->imem_size)
+ dev_warn(dev, "unmapped %zu IMEM bytes, expected %lu\n",
+ size, ipa->imem_size);
+ } else {
+ dev_err(dev, "couldn't get IPA IOMMU domain for IMEM\n");
+ }
+
+ ipa->imem_size = 0;
+ ipa->imem_iova = 0;
+}
+
+/**
+ * ipa_smem_init() - Initialize SMEM memory used by the IPA
+ * @ipa: IPA pointer
+ * @item: Item ID of SMEM memory
+ * @size: Size (bytes) of SMEM memory region
+ *
+ * SMEM is a managed block of shared DRAM, from which numbered "items"
+ * can be allocated. One item is designated for use by the IPA.
+ *
+ * The modem accesses SMEM memory directly, but the IPA accesses it
+ * via the IOMMU, using the AP's credentials.
+ *
+ * If size provided is non-zero, we allocate it and map it for
+ * access through the IOMMU.
+ *
+ * Note: @size and the item address are is not guaranteed to be page-aligned.
+ */
+static int ipa_smem_init(struct ipa *ipa, u32 item, size_t size)
+{
+ struct device *dev = &ipa->pdev->dev;
+ struct iommu_domain *domain;
+ unsigned long iova;
+ phys_addr_t phys;
+ phys_addr_t addr;
+ size_t actual;
+ void *virt;
+ int ret;
+
+ if (!size)
+ return 0; /* SMEM memory not used */
+
+ /* SMEM is memory shared between the AP and another system entity
+ * (in this case, the modem). An allocation from SMEM is persistent
+ * until the AP reboots; there is no way to free an allocated SMEM
+ * region. Allocation only reserves the space; to use it you need
+ * to "get" a pointer it (this implies no reference counting).
+ * The item might have already been allocated, in which case we
+ * use it unless the size isn't what we expect.
+ */
+ ret = qcom_smem_alloc(QCOM_SMEM_HOST_MODEM, item, size);
+ if (ret && ret != -EEXIST) {
+ dev_err(dev, "error %d allocating size %zu SMEM item %u\n",
+ ret, size, item);
+ return ret;
+ }
+
+ /* Now get the address of the SMEM memory region */
+ virt = qcom_smem_get(QCOM_SMEM_HOST_MODEM, item, &actual);
+ if (IS_ERR(virt)) {
+ ret = PTR_ERR(virt);
+ dev_err(dev, "error %d getting SMEM item %u\n", ret, item);
+ return ret;
+ }
+
+ /* In case the region was already allocated, verify the size */
+ if (ret && actual != size) {
+ dev_err(dev, "SMEM item %u has size %zu, expected %zu\n",
+ item, actual, size);
+ return -EINVAL;
+ }
+
+ domain = iommu_get_domain_for_dev(dev);
+ if (!domain) {
+ dev_err(dev, "no IOMMU domain found for SMEM\n");
+ return -EINVAL;
+ }
+
+ /* Align the address down and the size up to a page boundary */
+ addr = qcom_smem_virt_to_phys(virt) & PAGE_MASK;
+ phys = addr & PAGE_MASK;
+ size = PAGE_ALIGN(size + addr - phys);
+ iova = phys; /* We just want a direct mapping */
+
+ ret = iommu_map(domain, iova, phys, size, IOMMU_READ | IOMMU_WRITE);
+ if (ret)
+ return ret;
+
+ ipa->smem_iova = iova;
+ ipa->smem_size = size;
+
+ return 0;
+}
+
+static void ipa_smem_exit(struct ipa *ipa)
+{
+ struct device *dev = &ipa->pdev->dev;
+ struct iommu_domain *domain;
+
+ domain = iommu_get_domain_for_dev(dev);
+ if (domain) {
+ size_t size;
+
+ size = iommu_unmap(domain, ipa->smem_iova, ipa->smem_size);
+ if (size != ipa->smem_size)
+ dev_warn(dev, "unmapped %zu SMEM bytes, expected %lu\n",
+ size, ipa->smem_size);
+
+ } else {
+ dev_err(dev, "couldn't get IPA IOMMU domain for SMEM\n");
+ }
+
+ ipa->smem_size = 0;
+ ipa->smem_iova = 0;
+}
+
/* Perform memory region-related initialization */
-int ipa_mem_init(struct ipa *ipa, u32 count, const struct ipa_mem *mem)
+int ipa_mem_init(struct ipa *ipa, const struct ipa_mem_data *mem_data)
{
struct device *dev = &ipa->pdev->dev;
struct resource *res;
int ret;
- if (count > IPA_MEM_COUNT) {
+ if (mem_data->local_count > IPA_MEM_COUNT) {
dev_err(dev, "to many memory regions (%u > %u)\n",
- count, IPA_MEM_COUNT);
+ mem_data->local_count, IPA_MEM_COUNT);
return -EINVAL;
}
@@ -302,13 +486,30 @@ int ipa_mem_init(struct ipa *ipa, u32 count, const struct ipa_mem *mem)
ipa->mem_size = resource_size(res);
/* The ipa->mem[] array is indexed by enum ipa_mem_id values */
- ipa->mem = mem;
+ ipa->mem = mem_data->local;
+
+ ret = ipa_imem_init(ipa, mem_data->imem_addr, mem_data->imem_size);
+ if (ret)
+ goto err_unmap;
+
+ ret = ipa_smem_init(ipa, mem_data->smem_id, mem_data->smem_size);
+ if (ret)
+ goto err_imem_exit;
return 0;
+
+err_imem_exit:
+ ipa_imem_exit(ipa);
+err_unmap:
+ memunmap(ipa->mem_virt);
+
+ return ret;
}
/* Inverse of ipa_mem_init() */
void ipa_mem_exit(struct ipa *ipa)
{
+ ipa_smem_exit(ipa);
+ ipa_imem_exit(ipa);
memunmap(ipa->mem_virt);
}
diff --git a/drivers/net/ipa/ipa_mem.h b/drivers/net/ipa/ipa_mem.h
index 065cb499ebe5..f99180f84f0d 100644
--- a/drivers/net/ipa/ipa_mem.h
+++ b/drivers/net/ipa/ipa_mem.h
@@ -7,6 +7,7 @@
#define _IPA_MEM_H_
struct ipa;
+struct ipa_mem_data;
/**
* DOC: IPA Local Memory
@@ -84,7 +85,7 @@ void ipa_mem_teardown(struct ipa *ipa);
int ipa_mem_zero_modem(struct ipa *ipa);
-int ipa_mem_init(struct ipa *ipa, u32 count, const struct ipa_mem *mem);
+int ipa_mem_init(struct ipa *ipa, const struct ipa_mem_data *mem_data);
void ipa_mem_exit(struct ipa *ipa);
#endif /* _IPA_MEM_H_ */