soc: qcom: smem: map only partitions used by local HOST
authorDeepak Kumar Singh <quic_deesin@quicinc.com>
Tue, 1 Mar 2022 15:18:32 +0000 (20:48 +0530)
committerBjorn Andersson <bjorn.andersson@linaro.org>
Wed, 13 Apr 2022 02:29:49 +0000 (21:29 -0500)
SMEM driver is IO mapping complete region and CPU is doing a speculative
read into a partition where local HOST does not have permission resulting
in a NOC error.

Map only those partitions which are accessibly to local HOST.

Signed-off-by: Deepak Kumar Singh <quic_deesin@quicinc.com>
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
Link: https://lore.kernel.org/r/1646147913-15791-1-git-send-email-quic_deesin@quicinc.com
drivers/soc/qcom/smem.c

index e2057d8..fcf8e40 100644 (file)
@@ -195,6 +195,20 @@ struct smem_partition_header {
        __le32 reserved[3];
 };
 
+/**
+ * struct smem_partition - describes smem partition
+ * @virt_base: starting virtual address of partition
+ * @phys_base: starting physical address of partition
+ * @cacheline: alignment for "cached" entries
+ * @size:      size of partition
+ */
+struct smem_partition {
+       void __iomem *virt_base;
+       phys_addr_t phys_base;
+       size_t cacheline;
+       size_t size;
+};
+
 static const u8 SMEM_PART_MAGIC[] = { 0x24, 0x50, 0x52, 0x54 };
 
 /**
@@ -250,11 +264,9 @@ struct smem_region {
  * struct qcom_smem - device data for the smem device
  * @dev:       device pointer
  * @hwlock:    reference to a hwspinlock
- * @global_partition:  pointer to global partition when in use
- * @global_cacheline:  cacheline size for global partition
- * @partitions:        list of pointers to partitions affecting the current
- *             processor/host
- * @cacheline: list of cacheline sizes for each host
+ * @ptable: virtual base of partition table
+ * @global_partition: describes for global partition when in use
+ * @partitions: list of partitions of current processor/host
  * @item_count: max accepted item number
  * @socinfo:   platform device pointer
  * @num_regions: number of @regions
@@ -265,12 +277,11 @@ struct qcom_smem {
 
        struct hwspinlock *hwlock;
 
-       struct smem_partition_header *global_partition;
-       size_t global_cacheline;
-       struct smem_partition_header *partitions[SMEM_HOST_COUNT];
-       size_t cacheline[SMEM_HOST_COUNT];
        u32 item_count;
        struct platform_device *socinfo;
+       struct smem_ptable *ptable;
+       struct smem_partition global_partition;
+       struct smem_partition partitions[SMEM_HOST_COUNT];
 
        unsigned num_regions;
        struct smem_region regions[];
@@ -348,14 +359,17 @@ static struct qcom_smem *__smem;
 #define HWSPINLOCK_TIMEOUT     1000
 
 static int qcom_smem_alloc_private(struct qcom_smem *smem,
-                                  struct smem_partition_header *phdr,
+                                  struct smem_partition *part,
                                   unsigned item,
                                   size_t size)
 {
        struct smem_private_entry *hdr, *end;
+       struct smem_partition_header *phdr;
        size_t alloc_size;
        void *cached;
 
+       phdr = (struct smem_partition_header __force *)part->virt_base;
+
        hdr = phdr_to_first_uncached_entry(phdr);
        end = phdr_to_last_uncached_entry(phdr);
        cached = phdr_to_last_cached_entry(phdr);
@@ -442,7 +456,7 @@ static int qcom_smem_alloc_global(struct qcom_smem *smem,
  */
 int qcom_smem_alloc(unsigned host, unsigned item, size_t size)
 {
-       struct smem_partition_header *phdr;
+       struct smem_partition *part;
        unsigned long flags;
        int ret;
 
@@ -464,12 +478,12 @@ int qcom_smem_alloc(unsigned host, unsigned item, size_t size)
        if (ret)
                return ret;
 
-       if (host < SMEM_HOST_COUNT && __smem->partitions[host]) {
-               phdr = __smem->partitions[host];
-               ret = qcom_smem_alloc_private(__smem, phdr, item, size);
-       } else if (__smem->global_partition) {
-               phdr = __smem->global_partition;
-               ret = qcom_smem_alloc_private(__smem, phdr, item, size);
+       if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) {
+               part = &__smem->partitions[host];
+               ret = qcom_smem_alloc_private(__smem, part, item, size);
+       } else if (__smem->global_partition.virt_base) {
+               part = &__smem->global_partition;
+               ret = qcom_smem_alloc_private(__smem, part, item, size);
        } else {
                ret = qcom_smem_alloc_global(__smem, item, size);
        }
@@ -511,12 +525,14 @@ static void *qcom_smem_get_global(struct qcom_smem *smem,
 }
 
 static void *qcom_smem_get_private(struct qcom_smem *smem,
-                                  struct smem_partition_header *phdr,
-                                  size_t cacheline,
+                                  struct smem_partition *part,
                                   unsigned item,
                                   size_t *size)
 {
        struct smem_private_entry *e, *end;
+       struct smem_partition_header *phdr;
+
+       phdr = (struct smem_partition_header __force *)part->virt_base;
 
        e = phdr_to_first_uncached_entry(phdr);
        end = phdr_to_last_uncached_entry(phdr);
@@ -538,7 +554,7 @@ static void *qcom_smem_get_private(struct qcom_smem *smem,
 
        /* Item was not found in the uncached list, search the cached list */
 
-       e = phdr_to_first_cached_entry(phdr, cacheline);
+       e = phdr_to_first_cached_entry(phdr, part->cacheline);
        end = phdr_to_last_cached_entry(phdr);
 
        while (e > end) {
@@ -553,7 +569,7 @@ static void *qcom_smem_get_private(struct qcom_smem *smem,
                        return cached_entry_to_item(e);
                }
 
-               e = cached_entry_next(e, cacheline);
+               e = cached_entry_next(e, part->cacheline);
        }
 
        return ERR_PTR(-ENOENT);
@@ -576,9 +592,8 @@ invalid_canary:
  */
 void *qcom_smem_get(unsigned host, unsigned item, size_t *size)
 {
-       struct smem_partition_header *phdr;
+       struct smem_partition *part;
        unsigned long flags;
-       size_t cacheln;
        int ret;
        void *ptr = ERR_PTR(-EPROBE_DEFER);
 
@@ -594,14 +609,12 @@ void *qcom_smem_get(unsigned host, unsigned item, size_t *size)
        if (ret)
                return ERR_PTR(ret);
 
-       if (host < SMEM_HOST_COUNT && __smem->partitions[host]) {
-               phdr = __smem->partitions[host];
-               cacheln = __smem->cacheline[host];
-               ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size);
-       } else if (__smem->global_partition) {
-               phdr = __smem->global_partition;
-               cacheln = __smem->global_cacheline;
-               ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size);
+       if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) {
+               part = &__smem->partitions[host];
+               ptr = qcom_smem_get_private(__smem, part, item, size);
+       } else if (__smem->global_partition.virt_base) {
+               part = &__smem->global_partition;
+               ptr = qcom_smem_get_private(__smem, part, item, size);
        } else {
                ptr = qcom_smem_get_global(__smem, item, size);
        }
@@ -622,6 +635,7 @@ EXPORT_SYMBOL(qcom_smem_get);
  */
 int qcom_smem_get_free_space(unsigned host)
 {
+       struct smem_partition *part;
        struct smem_partition_header *phdr;
        struct smem_header *header;
        unsigned ret;
@@ -629,12 +643,14 @@ int qcom_smem_get_free_space(unsigned host)
        if (!__smem)
                return -EPROBE_DEFER;
 
-       if (host < SMEM_HOST_COUNT && __smem->partitions[host]) {
-               phdr = __smem->partitions[host];
+       if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) {
+               part = &__smem->partitions[host];
+               phdr = part->virt_base;
                ret = le32_to_cpu(phdr->offset_free_cached) -
                      le32_to_cpu(phdr->offset_free_uncached);
-       } else if (__smem->global_partition) {
-               phdr = __smem->global_partition;
+       } else if (__smem->global_partition.virt_base) {
+               part = &__smem->global_partition;
+               phdr = part->virt_base;
                ret = le32_to_cpu(phdr->offset_free_cached) -
                      le32_to_cpu(phdr->offset_free_uncached);
        } else {
@@ -646,6 +662,11 @@ int qcom_smem_get_free_space(unsigned host)
 }
 EXPORT_SYMBOL(qcom_smem_get_free_space);
 
+static bool addr_in_range(void __iomem *base, size_t size, void *addr)
+{
+       return base && (addr >= base && addr < base + size);
+}
+
 /**
  * qcom_smem_virt_to_phys() - return the physical address associated
  * with an smem item pointer (previously returned by qcom_smem_get()
@@ -655,17 +676,36 @@ EXPORT_SYMBOL(qcom_smem_get_free_space);
  */
 phys_addr_t qcom_smem_virt_to_phys(void *p)
 {
-       unsigned i;
+       struct smem_partition *part;
+       struct smem_region *area;
+       u64 offset;
+       u32 i;
+
+       for (i = 0; i < SMEM_HOST_COUNT; i++) {
+               part = &__smem->partitions[i];
+
+               if (addr_in_range(part->virt_base, part->size, p)) {
+                       offset = p - part->virt_base;
+
+                       return (phys_addr_t)part->phys_base + offset;
+               }
+       }
+
+       part = &__smem->global_partition;
+
+       if (addr_in_range(part->virt_base, part->size, p)) {
+               offset = p - part->virt_base;
+
+               return (phys_addr_t)part->phys_base + offset;
+       }
 
        for (i = 0; i < __smem->num_regions; i++) {
-               struct smem_region *region = &__smem->regions[i];
+               area = &__smem->regions[i];
 
-               if (p < region->virt_base)
-                       continue;
-               if (p < region->virt_base + region->size) {
-                       u64 offset = p - region->virt_base;
+               if (addr_in_range(area->virt_base, area->size, p)) {
+                       offset = p - area->virt_base;
 
-                       return region->aux_base + offset;
+                       return (phys_addr_t)area->aux_base + offset;
                }
        }
 
@@ -689,7 +729,7 @@ static struct smem_ptable *qcom_smem_get_ptable(struct qcom_smem *smem)
        struct smem_ptable *ptable;
        u32 version;
 
-       ptable = smem->regions[0].virt_base + smem->regions[0].size - SZ_4K;
+       ptable = smem->ptable;
        if (memcmp(ptable->magic, SMEM_PTABLE_MAGIC, sizeof(ptable->magic)))
                return ERR_PTR(-ENOENT);
 
@@ -728,9 +768,14 @@ qcom_smem_partition_header(struct qcom_smem *smem,
                struct smem_ptable_entry *entry, u16 host0, u16 host1)
 {
        struct smem_partition_header *header;
+       u32 phys_addr;
        u32 size;
 
-       header = smem->regions[0].virt_base + le32_to_cpu(entry->offset);
+       phys_addr = smem->regions[0].aux_base + le32_to_cpu(entry->offset);
+       header = devm_ioremap_wc(smem->dev, phys_addr, le32_to_cpu(entry->size));
+
+       if (!header)
+               return NULL;
 
        if (memcmp(header->magic, SMEM_PART_MAGIC, sizeof(header->magic))) {
                dev_err(smem->dev, "bad partition magic %4ph\n", header->magic);
@@ -772,7 +817,7 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem)
        bool found = false;
        int i;
 
-       if (smem->global_partition) {
+       if (smem->global_partition.virt_base) {
                dev_err(smem->dev, "Already found the global partition\n");
                return -EINVAL;
        }
@@ -807,8 +852,11 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem)
        if (!header)
                return -EINVAL;
 
-       smem->global_partition = header;
-       smem->global_cacheline = le32_to_cpu(entry->cacheline);
+       smem->global_partition.virt_base = (void __iomem *)header;
+       smem->global_partition.phys_base = smem->regions[0].aux_base +
+                                                               le32_to_cpu(entry->offset);
+       smem->global_partition.size = le32_to_cpu(entry->size);
+       smem->global_partition.cacheline = le32_to_cpu(entry->cacheline);
 
        return 0;
 }
@@ -848,7 +896,7 @@ qcom_smem_enumerate_partitions(struct qcom_smem *smem, u16 local_host)
                        return -EINVAL;
                }
 
-               if (smem->partitions[remote_host]) {
+               if (smem->partitions[remote_host].virt_base) {
                        dev_err(smem->dev, "duplicate host %hu\n", remote_host);
                        return -EINVAL;
                }
@@ -857,13 +905,47 @@ qcom_smem_enumerate_partitions(struct qcom_smem *smem, u16 local_host)
                if (!header)
                        return -EINVAL;
 
-               smem->partitions[remote_host] = header;
-               smem->cacheline[remote_host] = le32_to_cpu(entry->cacheline);
+               smem->partitions[remote_host].virt_base = (void __iomem *)header;
+               smem->partitions[remote_host].phys_base = smem->regions[0].aux_base +
+                                                                               le32_to_cpu(entry->offset);
+               smem->partitions[remote_host].size = le32_to_cpu(entry->size);
+               smem->partitions[remote_host].cacheline = le32_to_cpu(entry->cacheline);
        }
 
        return 0;
 }
 
+static int qcom_smem_map_toc(struct qcom_smem *smem, struct smem_region *region)
+{
+       u32 ptable_start;
+
+       /* map starting 4K for smem header */
+       region->virt_base = devm_ioremap_wc(smem->dev, region->aux_base, SZ_4K);
+       ptable_start = region->aux_base + region->size - SZ_4K;
+       /* map last 4k for toc */
+       smem->ptable = devm_ioremap_wc(smem->dev, ptable_start, SZ_4K);
+
+       if (!region->virt_base || !smem->ptable)
+               return -ENOMEM;
+
+       return 0;
+}
+
+static int qcom_smem_map_global(struct qcom_smem *smem, u32 size)
+{
+       u32 phys_addr;
+
+       phys_addr = smem->regions[0].aux_base;
+
+       smem->regions[0].size = size;
+       smem->regions[0].virt_base = devm_ioremap_wc(smem->dev, phys_addr, size);
+
+       if (!smem->regions[0].virt_base)
+               return -ENOMEM;
+
+       return 0;
+}
+
 static int qcom_smem_resolve_mem(struct qcom_smem *smem, const char *name,
                                 struct smem_region *region)
 {
@@ -894,10 +976,12 @@ static int qcom_smem_probe(struct platform_device *pdev)
        struct smem_header *header;
        struct reserved_mem *rmem;
        struct qcom_smem *smem;
+       unsigned long flags;
        size_t array_size;
        int num_regions;
        int hwlock_id;
        u32 version;
+       u32 size;
        int ret;
        int i;
 
@@ -933,7 +1017,12 @@ static int qcom_smem_probe(struct platform_device *pdev)
                        return ret;
        }
 
-       for (i = 0; i < num_regions; i++) {
+
+       ret = qcom_smem_map_toc(smem, &smem->regions[0]);
+       if (ret)
+               return ret;
+
+       for (i = 1; i < num_regions; i++) {
                smem->regions[i].virt_base = devm_ioremap_wc(&pdev->dev,
                                                             smem->regions[i].aux_base,
                                                             smem->regions[i].size);
@@ -950,7 +1039,30 @@ static int qcom_smem_probe(struct platform_device *pdev)
                return -EINVAL;
        }
 
+       hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0);
+       if (hwlock_id < 0) {
+               if (hwlock_id != -EPROBE_DEFER)
+                       dev_err(&pdev->dev, "failed to retrieve hwlock\n");
+               return hwlock_id;
+       }
+
+       smem->hwlock = hwspin_lock_request_specific(hwlock_id);
+       if (!smem->hwlock)
+               return -ENXIO;
+
+       ret = hwspin_lock_timeout_irqsave(smem->hwlock, HWSPINLOCK_TIMEOUT, &flags);
+       if (ret)
+               return ret;
+       size = readl_relaxed(&header->available) + readl_relaxed(&header->free_offset);
+       hwspin_unlock_irqrestore(smem->hwlock, &flags);
+
        version = qcom_smem_get_sbl_version(smem);
+       /*
+        * smem header mapping is required only in heap version scheme, so unmap
+        * it here. It will be remapped in qcom_smem_map_global() when whole
+        * partition is mapped again.
+        */
+       devm_iounmap(smem->dev, smem->regions[0].virt_base);
        switch (version >> 16) {
        case SMEM_GLOBAL_PART_VERSION:
                ret = qcom_smem_set_global_partition(smem);
@@ -959,6 +1071,7 @@ static int qcom_smem_probe(struct platform_device *pdev)
                smem->item_count = qcom_smem_get_item_count(smem);
                break;
        case SMEM_GLOBAL_HEAP_VERSION:
+               qcom_smem_map_global(smem, size);
                smem->item_count = SMEM_ITEM_COUNT;
                break;
        default:
@@ -971,17 +1084,6 @@ static int qcom_smem_probe(struct platform_device *pdev)
        if (ret < 0 && ret != -ENOENT)
                return ret;
 
-       hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0);
-       if (hwlock_id < 0) {
-               if (hwlock_id != -EPROBE_DEFER)
-                       dev_err(&pdev->dev, "failed to retrieve hwlock\n");
-               return hwlock_id;
-       }
-
-       smem->hwlock = hwspin_lock_request_specific(hwlock_id);
-       if (!smem->hwlock)
-               return -ENXIO;
-
        __smem = smem;
 
        smem->socinfo = platform_device_register_data(&pdev->dev, "qcom-socinfo",