// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2020 Samsung Electronics Co., Ltd. */ #define pr_fmt(fmt) "sysmmu: " fmt #include #include #include #include #include #include #include #include #include #include "samsung-iommu.h" #define FLPD_SHAREABLE_FLAG BIT(6) #define SLPD_SHAREABLE_FLAG BIT(4) #define FLPD_PBHA_SHIFT 4 #define SLPD_PBHA_SHIFT 2 #define REG_MMU_INV_ALL 0x010 #define REG_MMU_INV_RANGE 0x018 #define REG_MMU_INV_START 0x020 #define REG_MMU_INV_END 0x024 #define MMU_TLB_CFG_MASK(reg) ((reg) & (GENMASK(7, 5) | GENMASK(3, 2) | GENMASK(1, 1))) #define MMU_TLB_MATCH_CFG_MASK(reg) ((reg) & (GENMASK(31, 16) | GENMASK(9, 8))) #define REG_MMU_TLB_CFG(n) (0x2000 + ((n) * 0x20) + 0x4) #define REG_MMU_TLB_MATCH_CFG(n) (0x2000 + ((n) * 0x20) + 0x8) #define REG_MMU_TLB_MATCH_ID(n) (0x2000 + ((n) * 0x20) + 0x14) #define DEFAULT_QOS_VALUE -1 #define DEFAULT_VMID_MASK 0x1 #define DEFAULT_TLB_NONE ~0U #define UNUSED_TLB_INDEX ~0U static const unsigned int sysmmu_reg_set[MAX_SET_IDX][MAX_REG_IDX] = { /* Default without VM */ { /* FLPT base, TLB invalidation, Fault information */ 0x000C, 0x0010, 0x0014, 0x0018, 0x0020, 0x0024, 0x0070, 0x0078, /* TLB information */ 0x8000, 0x8004, 0x8008, 0x800C, /* SBB information */ 0x8020, 0x8024, 0x8028, 0x802C, /* secure FLPT base (same as non-secure) */ 0x000C, }, /* VM */ { /* FLPT base, TLB invalidation, Fault information */ 0x800C, 0x8010, 0x8014, 0x8018, 0x8020, 0x8024, 0x1000, 0x1004, /* TLB information */ 0x3000, 0x3004, 0x3008, 0x300C, /* SBB information */ 0x3020, 0x3024, 0x3028, 0x302C, /* secure FLPT base */ 0x000C, }, }; static struct iommu_ops samsung_sysmmu_ops; static struct platform_driver samsung_sysmmu_driver; struct samsung_sysmmu_domain { struct iommu_domain domain; struct samsung_iommu_log log; struct iommu_group *group; sysmmu_pte_t *page_table; atomic_t *lv2entcnt; spinlock_t pgtablelock; /* serialize races to page table updates */ }; static inline void samsung_iommu_write_event(struct samsung_iommu_log *iommu_log, enum sysmmu_event_type type, u32 start, u32 end) { struct sysmmu_log *log; unsigned int index = (unsigned int)atomic_inc_return(&iommu_log->index) - 1; log = &iommu_log->log[index % iommu_log->len]; log->time = sched_clock(); log->type = type; log->start = start; log->end = end; } #define SYSMMU_EVENT_LOG(data, type) samsung_iommu_write_event(&(data)->log, type, 0, 0) #define SYSMMU_EVENT_LOG_RANGE(data, type, s, e) \ samsung_iommu_write_event(&(data)->log, type, s, e) static bool sysmmu_global_init_done; static DEFINE_MUTEX(sysmmu_global_mutex); static struct device sync_dev; static struct kmem_cache *flpt_cache, *slpt_cache; static inline u32 __sysmmu_get_tlb_num(struct sysmmu_drvdata *data) { return MMU_CAPA1_NUM_TLB(readl_relaxed(data->sfrbase + REG_MMU_CAPA1_V7)); } static inline u32 __sysmmu_get_hw_version(struct sysmmu_drvdata *data) { return MMU_RAW_VER(readl_relaxed(data->sfrbase + REG_MMU_VERSION)); } static inline bool __sysmmu_has_capa1(struct sysmmu_drvdata *data) { return MMU_CAPA1_EXIST(readl_relaxed(data->sfrbase + REG_MMU_CAPA0_V7)); } static inline int __sysmmu_get_capa_max_page_table(struct sysmmu_drvdata *data) { return MMU_CAPA_NUM_PAGE_TABLE(readl_relaxed(data->sfrbase + REG_MMU_CAPA0_V7)); } static inline u32 __sysmmu_get_capa_type(struct sysmmu_drvdata *data) { return MMU_CAPA1_TYPE(readl_relaxed(data->sfrbase + REG_MMU_CAPA1_V7)); } static inline bool __sysmmu_get_capa_no_block_mode(struct sysmmu_drvdata *data) { return MMU_CAPA1_NO_BLOCK_MODE(readl_relaxed(data->sfrbase + REG_MMU_CAPA1_V7)); } static inline bool __sysmmu_get_capa_vcr_enabled(struct sysmmu_drvdata *data) { return MMU_CAPA1_VCR_ENABLED(readl_relaxed(data->sfrbase + REG_MMU_CAPA1_V7)); } static inline void __sysmmu_write_all_vm(struct sysmmu_drvdata *data, u32 value, void __iomem *addr) { int i; for (i = 0; i < data->max_vm; i++) { if (data->vmid_mask & (1 << i)) writel(value, addr + (i * SYSMMU_VM_OFFSET)); } } static inline void __sysmmu_tlb_invalidate_all(struct sysmmu_drvdata *data) { __sysmmu_write_all_vm(data, 0x1, MMU_REG(data, IDX_ALL_INV)); SYSMMU_EVENT_LOG(data, SYSMMU_EVENT_TLB_ALL); } static inline void __sysmmu_tlb_invalidate(struct sysmmu_drvdata *data, dma_addr_t start, dma_addr_t end) { __sysmmu_write_all_vm(data, ALIGN_DOWN(start, SPAGE_SIZE), MMU_REG(data, IDX_RANGE_INV_START)); __sysmmu_write_all_vm(data, ALIGN_DOWN(end - 1, SPAGE_SIZE), MMU_REG(data, IDX_RANGE_INV_END)); __sysmmu_write_all_vm(data, 0x1, MMU_REG(data, IDX_RANGE_INV)); SYSMMU_EVENT_LOG_RANGE(data, SYSMMU_EVENT_TLB_RANGE, start, end); } static inline void __sysmmu_disable(struct sysmmu_drvdata *data) { if (data->no_block_mode) { __sysmmu_tlb_invalidate_all(data); } else { u32 ctrl_val = readl_relaxed(data->sfrbase + REG_MMU_CTRL); ctrl_val &= ~CTRL_MMU_ENABLE; writel(ctrl_val | CTRL_MMU_BLOCK, data->sfrbase + REG_MMU_CTRL); } SYSMMU_EVENT_LOG(data, SYSMMU_EVENT_DISABLE); } static inline void __sysmmu_set_tlb(struct sysmmu_drvdata *data) { struct tlb_props *tlb_props = &data->tlb_props; struct tlb_config *cfg = tlb_props->cfg; int id_cnt = tlb_props->id_cnt; unsigned int i, index; if (tlb_props->default_cfg != DEFAULT_TLB_NONE) writel_relaxed(MMU_TLB_CFG_MASK(tlb_props->default_cfg), data->sfrbase + REG_MMU_TLB_CFG(0)); for (i = 0; i < id_cnt; i++) { if (cfg[i].index == UNUSED_TLB_INDEX) continue; index = cfg[i].index; writel_relaxed(MMU_TLB_CFG_MASK(cfg[i].cfg), data->sfrbase + REG_MMU_TLB_CFG(index)); writel_relaxed(MMU_TLB_MATCH_CFG_MASK(cfg[i].match_cfg), data->sfrbase + REG_MMU_TLB_MATCH_CFG(index)); writel_relaxed(cfg[i].match_id, data->sfrbase + REG_MMU_TLB_MATCH_ID(index)); } } static inline void __sysmmu_init_config(struct sysmmu_drvdata *data) { u32 cfg = readl_relaxed(data->sfrbase + REG_MMU_CFG); if (data->qos != DEFAULT_QOS_VALUE) { cfg &= ~CFG_QOS(0xF); cfg |= CFG_QOS_OVRRIDE | CFG_QOS(data->qos); } __sysmmu_set_tlb(data); writel_relaxed(cfg, data->sfrbase + REG_MMU_CFG); } static inline void __sysmmu_enable(struct sysmmu_drvdata *data) { u32 ctrl_val = readl_relaxed(data->sfrbase + REG_MMU_CTRL); if (!data->no_block_mode) writel_relaxed(ctrl_val | CTRL_MMU_BLOCK, data->sfrbase + REG_MMU_CTRL); __sysmmu_init_config(data); __sysmmu_write_all_vm(data, data->pgtable / SPAGE_SIZE, MMU_REG(data, IDX_FLPT_BASE)); __sysmmu_tlb_invalidate_all(data); writel(ctrl_val | CTRL_MMU_ENABLE, data->sfrbase + REG_MMU_CTRL); if (data->has_vcr) { ctrl_val = readl_relaxed(data->sfrbase + REG_MMU_CTRL_VM(0)); if (!data->async_fault_mode) ctrl_val |= CTRL_FAULT_STALL_MODE; __sysmmu_write_all_vm(data, ctrl_val | CTRL_MMU_ENABLE, data->sfrbase + REG_MMU_CTRL_VM(0)); } SYSMMU_EVENT_LOG(data, SYSMMU_EVENT_ENABLE); } static struct samsung_sysmmu_domain *to_sysmmu_domain(struct iommu_domain *dom) { return container_of(dom, struct samsung_sysmmu_domain, domain); } static inline void pgtable_flush(void *vastart, void *vaend) { dma_sync_single_for_device(&sync_dev, virt_to_phys(vastart), vaend - vastart, DMA_TO_DEVICE); } static bool samsung_sysmmu_capable(enum iommu_cap cap) { return cap == IOMMU_CAP_CACHE_COHERENCY; } static inline size_t get_log_fit_pages(struct samsung_iommu_log *log, int len) { return DIV_ROUND_UP(sizeof(*(log->log)) * len, PAGE_SIZE); } static void samsung_iommu_deinit_log(struct samsung_iommu_log *log) { struct page *page; int i, fit_pages = get_log_fit_pages(log, log->len); page = virt_to_page(log->log); for (i = 0; i < fit_pages; i++) __free_page(page + i); } static int samsung_iommu_init_log(struct samsung_iommu_log *log, int len) { struct page *page; int order, order_pages, fit_pages = get_log_fit_pages(log, len); atomic_set(&log->index, 0); order = get_order(fit_pages * PAGE_SIZE); page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order); if (!page) return -ENOMEM; split_page(page, order); order_pages = 1 << order; if (order_pages > fit_pages) { int i; for (i = fit_pages; i < order_pages; i++) __free_page(page + i); } log->log = page_address(page); log->len = len; return 0; } static struct iommu_domain *samsung_sysmmu_domain_alloc(unsigned int type) { struct samsung_sysmmu_domain *domain; if (type != IOMMU_DOMAIN_UNMANAGED && type != IOMMU_DOMAIN_DMA && type != IOMMU_DOMAIN_IDENTITY) { pr_err("invalid domain type %u\n", type); return NULL; } domain = kzalloc(sizeof(*domain), GFP_KERNEL); if (!domain) return NULL; domain->page_table = (sysmmu_pte_t *)kmem_cache_alloc(flpt_cache, GFP_KERNEL | __GFP_ZERO); if (!domain->page_table) goto err_pgtable; domain->lv2entcnt = kcalloc(NUM_LV1ENTRIES, sizeof(*domain->lv2entcnt), GFP_KERNEL); if (!domain->lv2entcnt) goto err_counter; if (type == IOMMU_DOMAIN_DMA) { int ret = iommu_get_dma_cookie(&domain->domain); if (ret) { pr_err("failed to get dma cookie (%d)\n", ret); goto err_get_dma_cookie; } } if (samsung_iommu_init_log(&domain->log, SYSMMU_EVENT_MAX)) { pr_err("failed to init domain logging\n"); goto err_init_log; } pgtable_flush(domain->page_table, domain->page_table + NUM_LV1ENTRIES); spin_lock_init(&domain->pgtablelock); return &domain->domain; err_init_log: iommu_put_dma_cookie(&domain->domain); err_get_dma_cookie: kfree(domain->lv2entcnt); err_counter: kmem_cache_free(flpt_cache, domain->page_table); err_pgtable: kfree(domain); return NULL; } static void samsung_sysmmu_domain_free(struct iommu_domain *dom) { struct samsung_sysmmu_domain *domain = to_sysmmu_domain(dom); samsung_iommu_deinit_log(&domain->log); iommu_put_dma_cookie(dom); kmem_cache_free(flpt_cache, domain->page_table); kfree(domain->lv2entcnt); kfree(domain); } static inline void samsung_sysmmu_detach_drvdata(struct sysmmu_drvdata *data) { unsigned long flags; spin_lock_irqsave(&data->lock, flags); if (--data->attached_count == 0) { if (pm_runtime_active(data->dev)) __sysmmu_disable(data); list_del(&data->list); data->pgtable = 0; data->group = NULL; } spin_unlock_irqrestore(&data->lock, flags); } static int samsung_sysmmu_set_domain_range(struct iommu_domain *dom, struct device *dev) { struct iommu_domain_geometry *geom = &dom->geometry; dma_addr_t start, end; size_t size; if (of_get_dma_window(dev->of_node, NULL, 0, NULL, &start, &size)) return 0; end = start + size; if (end > DMA_BIT_MASK(32)) end = DMA_BIT_MASK(32); if (geom->force_aperture) { dma_addr_t d_start, d_end; d_start = max(start, geom->aperture_start); d_end = min(end, geom->aperture_end); if (d_start >= d_end) { dev_err(dev, "current range is [%pad..%pad]\n", &geom->aperture_start, &geom->aperture_end); dev_err(dev, "requested range [%zx @ %pad] is not allowed\n", size, &start); return -ERANGE; } geom->aperture_start = d_start; geom->aperture_end = d_end; } else { geom->aperture_start = start; geom->aperture_end = end; /* * All CPUs should observe the change of force_aperture after * updating aperture_start and aperture_end because dma-iommu * restricts dma virtual memory by this aperture when * force_aperture is set. * We allow allocating dma virtual memory during changing the * aperture range because the current allocation is free from * the new restricted range. */ smp_wmb(); geom->force_aperture = true; } dev_info(dev, "changed DMA range [%pad..%pad] successfully.\n", &geom->aperture_start, &geom->aperture_end); return 0; } static int samsung_sysmmu_attach_dev(struct iommu_domain *dom, struct device *dev) { struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); struct sysmmu_clientdata *client; struct samsung_sysmmu_domain *domain; struct list_head *group_list; struct sysmmu_drvdata *drvdata; struct iommu_group *group = dev->iommu_group; unsigned long flags; phys_addr_t page_table; int i, ret = -EINVAL; if (!fwspec || fwspec->ops != &samsung_sysmmu_ops) { dev_err(dev, "failed to attach, IOMMU instance data %s.\n", !fwspec ? "is not initialized" : "has different ops"); return -ENXIO; } if (!dev_iommu_priv_get(dev)) { dev_err(dev, "has no IOMMU\n"); return -ENODEV; } domain = to_sysmmu_domain(dom); domain->group = group; group_list = iommu_group_get_iommudata(group); page_table = virt_to_phys(domain->page_table); client = dev_iommu_priv_get(dev); for (i = 0; i < client->sysmmu_count; i++) { drvdata = client->sysmmus[i]; spin_lock_irqsave(&drvdata->lock, flags); if (drvdata->attached_count++ == 0) { list_add(&drvdata->list, group_list); drvdata->group = group; drvdata->pgtable = page_table; if (pm_runtime_active(drvdata->dev)) __sysmmu_enable(drvdata); } else if (drvdata->pgtable != page_table) { dev_err(dev, "%s is already attached to other domain\n", dev_name(drvdata->dev)); spin_unlock_irqrestore(&drvdata->lock, flags); goto err_drvdata_add; } spin_unlock_irqrestore(&drvdata->lock, flags); } ret = samsung_sysmmu_set_domain_range(dom, dev); if (ret) goto err_drvdata_add; dev_info(dev, "attached with pgtable %pa\n", &domain->page_table); return 0; err_drvdata_add: while (i-- > 0) { drvdata = client->sysmmus[i]; samsung_sysmmu_detach_drvdata(drvdata); } return ret; } static void samsung_sysmmu_detach_dev(struct iommu_domain *dom, struct device *dev) { struct sysmmu_clientdata *client; struct samsung_sysmmu_domain *domain; struct list_head *group_list; struct sysmmu_drvdata *drvdata; struct iommu_group *group = dev->iommu_group; int i; domain = to_sysmmu_domain(dom); group_list = iommu_group_get_iommudata(group); client = dev_iommu_priv_get(dev); for (i = 0; i < client->sysmmu_count; i++) { drvdata = client->sysmmus[i]; samsung_sysmmu_detach_drvdata(drvdata); } dev_info(dev, "detached from pgtable %pa\n", &domain->page_table); } static inline sysmmu_pte_t make_sysmmu_pte(phys_addr_t paddr, int pgsize, int attr) { return ((sysmmu_pte_t)((paddr) >> PG_ENT_SHIFT)) | pgsize | attr; } static sysmmu_pte_t *alloc_lv2entry(struct samsung_sysmmu_domain *domain, sysmmu_pte_t *sent, sysmmu_iova_t iova, atomic_t *pgcounter) { if (lv1ent_section(sent)) { WARN(1, "trying mapping on %#08x mapped with 1MiB page", iova); return ERR_PTR(-EADDRINUSE); } if (lv1ent_unmapped(sent)) { unsigned long flags; sysmmu_pte_t *pent; pent = kmem_cache_zalloc(slpt_cache, GFP_KERNEL); if (!pent) return ERR_PTR(-ENOMEM); spin_lock_irqsave(&domain->pgtablelock, flags); if (lv1ent_unmapped(sent)) { *sent = make_sysmmu_pte(virt_to_phys(pent), SLPD_FLAG, 0); kmemleak_ignore(pent); atomic_set(pgcounter, 0); pgtable_flush(pent, pent + NUM_LV2ENTRIES); pgtable_flush(sent, sent + 1); } else { /* allocated entry is not used, so free it. */ kmem_cache_free(slpt_cache, pent); } spin_unlock_irqrestore(&domain->pgtablelock, flags); } return page_entry(sent, iova); } static inline void clear_lv2_page_table(sysmmu_pte_t *ent, int n) { memset(ent, 0, sizeof(*ent) * n); } #define IOMMU_PRIV_PROT_TO_PBHA(val) (((val) >> IOMMU_PRIV_SHIFT) & 0x3) static int lv1set_section(struct samsung_sysmmu_domain *domain, sysmmu_pte_t *sent, sysmmu_iova_t iova, phys_addr_t paddr, int prot, atomic_t *pgcnt) { int attr = !!(prot & IOMMU_CACHE) ? FLPD_SHAREABLE_FLAG : 0; bool need_sync = false; int pbha = IOMMU_PRIV_PROT_TO_PBHA(prot); if (lv1ent_section(sent)) { WARN(1, "Trying mapping 1MB@%#08x on valid FLPD", iova); return -EADDRINUSE; } if (lv1ent_page(sent)) { if (WARN_ON(atomic_read(pgcnt) != 0)) { WARN(1, "Trying mapping 1MB@%#08x on valid SLPD", iova); return -EADDRINUSE; } kmem_cache_free(slpt_cache, page_entry(sent, 0)); atomic_set(pgcnt, NUM_LV2ENTRIES); need_sync = true; } attr |= pbha << FLPD_PBHA_SHIFT; *sent = make_sysmmu_pte(paddr, SECT_FLAG, attr); pgtable_flush(sent, sent + 1); if (need_sync) { struct iommu_iotlb_gather gather = { .start = iova, .end = iova + SECT_SIZE, }; iommu_iotlb_sync(&domain->domain, &gather); } return 0; } static int lv2set_page(sysmmu_pte_t *pent, phys_addr_t paddr, size_t size, int prot, atomic_t *pgcnt) { int attr = !!(prot & IOMMU_CACHE) ? SLPD_SHAREABLE_FLAG : 0; int pbha = IOMMU_PRIV_PROT_TO_PBHA(prot); attr |= pbha << SLPD_PBHA_SHIFT; if (size == SPAGE_SIZE) { if (WARN_ON(!lv2ent_unmapped(pent))) return -EADDRINUSE; *pent = make_sysmmu_pte(paddr, SPAGE_FLAG, attr); pgtable_flush(pent, pent + 1); atomic_inc(pgcnt); } else { /* size == LPAGE_SIZE */ unsigned long i; for (i = 0; i < SPAGES_PER_LPAGE; i++, pent++) { if (WARN_ON(!lv2ent_unmapped(pent))) { clear_lv2_page_table(pent - i, i); return -EADDRINUSE; } *pent = make_sysmmu_pte(paddr, LPAGE_FLAG, attr); } pgtable_flush(pent - SPAGES_PER_LPAGE, pent); atomic_add(SPAGES_PER_LPAGE, pgcnt); } return 0; } static int samsung_sysmmu_map(struct iommu_domain *dom, unsigned long l_iova, phys_addr_t paddr, size_t size, int prot, gfp_t unused) { struct samsung_sysmmu_domain *domain = to_sysmmu_domain(dom); sysmmu_iova_t iova = (sysmmu_iova_t)l_iova; atomic_t *lv2entcnt = &domain->lv2entcnt[lv1ent_offset(iova)]; sysmmu_pte_t *entry; int ret = -ENOMEM; /* Do not use IO coherency if iOMMU_PRIV exists */ if (!!(prot & IOMMU_PRIV)) prot &= ~IOMMU_CACHE; entry = section_entry(domain->page_table, iova); if (size == SECT_SIZE) { ret = lv1set_section(domain, entry, iova, paddr, prot, lv2entcnt); } else { sysmmu_pte_t *pent; pent = alloc_lv2entry(domain, entry, iova, lv2entcnt); if (IS_ERR(pent)) ret = PTR_ERR(pent); else ret = lv2set_page(pent, paddr, size, prot, lv2entcnt); } if (ret) pr_err("failed to map %#zx @ %#x, ret:%d\n", size, iova, ret); else SYSMMU_EVENT_LOG_RANGE(domain, SYSMMU_EVENT_MAP, iova, iova + size); return ret; } static size_t samsung_sysmmu_unmap(struct iommu_domain *dom, unsigned long l_iova, size_t size, struct iommu_iotlb_gather *gather) { struct samsung_sysmmu_domain *domain = to_sysmmu_domain(dom); sysmmu_iova_t iova = (sysmmu_iova_t)l_iova; atomic_t *lv2entcnt = &domain->lv2entcnt[lv1ent_offset(iova)]; sysmmu_pte_t *sent, *pent; size_t err_pgsize; sent = section_entry(domain->page_table, iova); if (lv1ent_section(sent)) { if (WARN_ON(size < SECT_SIZE)) { err_pgsize = SECT_SIZE; goto err; } *sent = 0; pgtable_flush(sent, sent + 1); size = SECT_SIZE; goto done; } if (unlikely(lv1ent_unmapped(sent))) { if (size > SECT_SIZE) size = SECT_SIZE; goto done; } /* lv1ent_page(sent) == true here */ pent = page_entry(sent, iova); if (unlikely(lv2ent_unmapped(pent))) { size = SPAGE_SIZE; goto done; } if (lv2ent_small(pent)) { *pent = 0; size = SPAGE_SIZE; pgtable_flush(pent, pent + 1); atomic_dec(lv2entcnt); goto done; } /* lv1ent_large(pent) == true here */ if (WARN_ON(size < LPAGE_SIZE)) { err_pgsize = LPAGE_SIZE; goto err; } clear_lv2_page_table(pent, SPAGES_PER_LPAGE); pgtable_flush(pent, pent + SPAGES_PER_LPAGE); size = LPAGE_SIZE; atomic_sub(SPAGES_PER_LPAGE, lv2entcnt); done: iommu_iotlb_gather_add_page(dom, gather, iova, size); SYSMMU_EVENT_LOG_RANGE(domain, SYSMMU_EVENT_UNMAP, iova, iova + size); return size; err: pr_err("failed: size(%#zx) @ %#x is smaller than page size %#zx\n", size, iova, err_pgsize); return 0; } static void samsung_sysmmu_flush_iotlb_all(struct iommu_domain *dom) { unsigned long flags; struct samsung_sysmmu_domain *domain = to_sysmmu_domain(dom); struct list_head *sysmmu_list; struct sysmmu_drvdata *drvdata; /* * domain->group might be NULL if flush_iotlb_all is called * before attach_dev. Just ignore it. */ if (!domain->group) return; sysmmu_list = iommu_group_get_iommudata(domain->group); list_for_each_entry(drvdata, sysmmu_list, list) { spin_lock_irqsave(&drvdata->lock, flags); if (drvdata->attached_count && drvdata->rpm_count > 0) __sysmmu_tlb_invalidate_all(drvdata); spin_unlock_irqrestore(&drvdata->lock, flags); } } static void samsung_sysmmu_iotlb_sync(struct iommu_domain *dom, struct iommu_iotlb_gather *gather) { unsigned long flags; struct samsung_sysmmu_domain *domain = to_sysmmu_domain(dom); struct list_head *sysmmu_list; struct sysmmu_drvdata *drvdata; /* * domain->group might be NULL if iotlb_sync is called * before attach_dev. Just ignore it. */ if (!domain->group) return; sysmmu_list = iommu_group_get_iommudata(domain->group); list_for_each_entry(drvdata, sysmmu_list, list) { spin_lock_irqsave(&drvdata->lock, flags); if (drvdata->attached_count && drvdata->rpm_count > 0) __sysmmu_tlb_invalidate(drvdata, gather->start, gather->end); SYSMMU_EVENT_LOG_RANGE(drvdata, SYSMMU_EVENT_IOTLB_SYNC, gather->start, gather->end); spin_unlock_irqrestore(&drvdata->lock, flags); } } static phys_addr_t samsung_sysmmu_iova_to_phys(struct iommu_domain *dom, dma_addr_t d_iova) { struct samsung_sysmmu_domain *domain = to_sysmmu_domain(dom); sysmmu_iova_t iova = (sysmmu_iova_t)d_iova; sysmmu_pte_t *entry; phys_addr_t phys = 0; entry = section_entry(domain->page_table, iova); if (lv1ent_section(entry)) { phys = section_phys(entry) + section_offs(iova); } else if (lv1ent_page(entry)) { entry = page_entry(entry, iova); if (lv2ent_large(entry)) phys = lpage_phys(entry) + lpage_offs(iova); else if (lv2ent_small(entry)) phys = spage_phys(entry) + spage_offs(iova); } return phys; } void samsung_sysmmu_dump_pagetable(struct device *dev, dma_addr_t iova) { } static struct iommu_device *samsung_sysmmu_probe_device(struct device *dev) { struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); struct sysmmu_clientdata *client; int i; if (!fwspec) { dev_dbg(dev, "IOMMU instance data is not initialized\n"); return ERR_PTR(-ENODEV); } if (fwspec->ops != &samsung_sysmmu_ops) { dev_err(dev, "has different IOMMU ops\n"); return ERR_PTR(-ENODEV); } client = (struct sysmmu_clientdata *) dev_iommu_priv_get(dev); if (client->dev_link) { dev_info(dev, "is already added. It's okay.\n"); return 0; } client->dev_link = kcalloc(client->sysmmu_count, sizeof(*client->dev_link), GFP_KERNEL); if (!client->dev_link) return ERR_PTR(-ENOMEM); for (i = 0; i < client->sysmmu_count; i++) { client->dev_link[i] = device_link_add(dev, client->sysmmus[i]->dev, DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME); if (!client->dev_link[i]) { dev_err(dev, "failed to add device link of %s\n", dev_name(client->sysmmus[i]->dev)); while (i-- > 0) device_link_del(client->dev_link[i]); return ERR_PTR(-EINVAL); } dev_info(dev, "device link to %s\n", dev_name(client->sysmmus[i]->dev)); } return &client->sysmmus[0]->iommu; } static void samsung_sysmmu_release_device(struct device *dev) { struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); struct sysmmu_clientdata *client; int i; if (!fwspec || fwspec->ops != &samsung_sysmmu_ops) return; client = (struct sysmmu_clientdata *) dev_iommu_priv_get(dev); for (i = 0; i < client->sysmmu_count; i++) device_link_del(client->dev_link[i]); kfree(client->dev_link); iommu_fwspec_free(dev); } static void samsung_sysmmu_group_data_release(void *iommu_data) { kfree(iommu_data); } static struct iommu_group *samsung_sysmmu_device_group(struct device *dev) { struct iommu_group *group; struct device_node *np; struct platform_device *pdev; struct list_head *list; if (device_iommu_mapped(dev)) return iommu_group_get(dev); np = of_parse_phandle(dev->of_node, "samsung,iommu-group", 0); if (!np) { dev_err(dev, "group is not registered\n"); return ERR_PTR(-ENODEV); } pdev = of_find_device_by_node(np); if (!pdev) { dev_err(dev, "no device in device_node[%s]\n", np->name); of_node_put(np); return ERR_PTR(-ENODEV); } of_node_put(np); group = platform_get_drvdata(pdev); if (!group) { dev_err(dev, "no group in device_node[%s]\n", np->name); return ERR_PTR(-EPROBE_DEFER); } mutex_lock(&sysmmu_global_mutex); if (iommu_group_get_iommudata(group)) { mutex_unlock(&sysmmu_global_mutex); return group; } list = kzalloc(sizeof(*list), GFP_KERNEL); if (!list) { mutex_unlock(&sysmmu_global_mutex); return ERR_PTR(-ENOMEM); } INIT_LIST_HEAD(list); iommu_group_set_iommudata(group, list, samsung_sysmmu_group_data_release); mutex_unlock(&sysmmu_global_mutex); return group; } static void samsung_sysmmu_clientdata_release(struct device *dev, void *res) { struct sysmmu_clientdata *client = res; kfree(client->sysmmus); } static int samsung_sysmmu_of_xlate(struct device *dev, struct of_phandle_args *args) { struct platform_device *sysmmu = of_find_device_by_node(args->np); struct sysmmu_drvdata *data = platform_get_drvdata(sysmmu); struct sysmmu_drvdata **new_link; struct sysmmu_clientdata *client; struct iommu_fwspec *fwspec; unsigned int fwid = 0; int ret; ret = iommu_fwspec_add_ids(dev, &fwid, 1); if (ret) { dev_err(dev, "failed to add fwspec. (err:%d)\n", ret); iommu_device_unlink(&data->iommu, dev); return ret; } fwspec = dev_iommu_fwspec_get(dev); if (!dev_iommu_priv_get(dev)) { client = devres_alloc(samsung_sysmmu_clientdata_release, sizeof(*client), GFP_KERNEL); if (!client) return -ENOMEM; client->dev = dev; dev_iommu_priv_set(dev, client); devres_add(dev, client); } client = (struct sysmmu_clientdata *) dev_iommu_priv_get(dev); new_link = krealloc(client->sysmmus, sizeof(data) * (client->sysmmu_count + 1), GFP_KERNEL); if (!new_link) return -ENOMEM; client->sysmmus = new_link; client->sysmmus[client->sysmmu_count++] = data; dev_info(dev, "has sysmmu %s (total count:%d)\n", dev_name(data->dev), client->sysmmu_count); return ret; } static void samsung_sysmmu_put_resv_regions(struct device *dev, struct list_head *head) { struct iommu_resv_region *entry, *next; list_for_each_entry_safe(entry, next, head, list) kfree(entry); } #define NR_IOMMU_RESERVED_TYPES 2 static int samsung_sysmmu_get_resv_regions_by_node(struct device_node *np, struct device *dev, struct list_head *head) { const char *propname[NR_IOMMU_RESERVED_TYPES] = { "samsung,iommu-identity-map", "samsung,iommu-reserved-map" }; enum iommu_resv_type resvtype[NR_IOMMU_RESERVED_TYPES] = { IOMMU_RESV_DIRECT, IOMMU_RESV_RESERVED }; int n_addr_cells = of_n_addr_cells(dev->of_node); int n_size_cells = of_n_size_cells(dev->of_node); int n_all_cells = n_addr_cells + n_size_cells; int type; for (type = 0; type < 2; type++) { const __be32 *prop; u64 base, size; int i, cnt; prop = of_get_property(dev->of_node, propname[type], &cnt); if (!prop) continue; cnt /= sizeof(u32); if (cnt % n_all_cells != 0) { dev_err(dev, "Invalid number(%d) of values in %s\n", cnt, propname[type]); return -EINVAL; } for (i = 0; i < cnt; i += n_all_cells) { struct iommu_resv_region *region; base = of_read_number(prop + i, n_addr_cells); size = of_read_number(prop + i + n_addr_cells, n_size_cells); if (base & ~dma_get_mask(dev) || (base + size) & ~dma_get_mask(dev)) { dev_err(dev, "Unreachable DMA region in %s, [%#x..%#x)\n", propname[type], base, base + size); return -EINVAL; } region = iommu_alloc_resv_region(base, size, 0, resvtype[type]); if (!region) return -ENOMEM; list_add_tail(®ion->list, head); dev_info(dev, "Reserved IOMMU mapping [%#x..%#x)\n", base, base + size); } } return 0; } static void samsung_sysmmu_get_resv_regions(struct device *dev, struct list_head *head) { struct device_node *curr_node, *target_node, *node; struct platform_device *pdev; int ret; target_node = of_parse_phandle(dev->of_node, "samsung,iommu-group", 0); if (!target_node) { dev_err(dev, "doesn't have iommu-group property\n"); return; } for_each_node_with_property(node, "samsung,iommu-group") { curr_node = of_parse_phandle(node, "samsung,iommu-group", 0); if (!curr_node || curr_node != target_node) { of_node_put(curr_node); continue; } pdev = of_find_device_by_node(node); if (!pdev) { of_node_put(curr_node); continue; } ret = samsung_sysmmu_get_resv_regions_by_node(dev->of_node, &pdev->dev, head); put_device(&pdev->dev); of_node_put(curr_node); if (ret) goto err; } of_node_put(target_node); return; err: of_node_put(target_node); samsung_sysmmu_put_resv_regions(dev, head); INIT_LIST_HEAD(head); } static int samsung_sysmmu_def_domain_type(struct device *dev) { struct device_node *np; int ret = 0; np = of_parse_phandle(dev->of_node, "samsung,iommu-group", 0); if (!np) { dev_err(dev, "group is not registered\n"); return 0; } if (of_property_read_bool(np, "samsung,unmanaged-domain")) ret = IOMMU_DOMAIN_UNMANAGED; of_node_put(np); return ret; } static struct iommu_ops samsung_sysmmu_ops = { .capable = samsung_sysmmu_capable, .domain_alloc = samsung_sysmmu_domain_alloc, .domain_free = samsung_sysmmu_domain_free, .attach_dev = samsung_sysmmu_attach_dev, .detach_dev = samsung_sysmmu_detach_dev, .map = samsung_sysmmu_map, .unmap = samsung_sysmmu_unmap, .flush_iotlb_all = samsung_sysmmu_flush_iotlb_all, .iotlb_sync = samsung_sysmmu_iotlb_sync, .iova_to_phys = samsung_sysmmu_iova_to_phys, .probe_device = samsung_sysmmu_probe_device, .release_device = samsung_sysmmu_release_device, .device_group = samsung_sysmmu_device_group, .of_xlate = samsung_sysmmu_of_xlate, .get_resv_regions = samsung_sysmmu_get_resv_regions, .put_resv_regions = samsung_sysmmu_put_resv_regions, .def_domain_type = samsung_sysmmu_def_domain_type, .pgsize_bitmap = SECT_SIZE | LPAGE_SIZE | SPAGE_SIZE, }; static int sysmmu_get_hw_info(struct sysmmu_drvdata *data) { int ret; ret = pm_runtime_get_sync(data->dev); if (ret < 0) { pm_runtime_put_noidle(data->dev); return ret; } data->version = __sysmmu_get_hw_version(data); data->num_tlb = __sysmmu_get_tlb_num(data); data->max_vm = __sysmmu_get_capa_max_page_table(data); /* Default value */ data->reg_set = sysmmu_reg_set[REG_IDX_DEFAULT]; if (__sysmmu_get_capa_vcr_enabled(data)) { data->reg_set = sysmmu_reg_set[REG_IDX_VM]; data->has_vcr = true; } if (__sysmmu_get_capa_no_block_mode(data)) data->no_block_mode = true; pm_runtime_put(data->dev); return 0; } static int sysmmu_parse_tlb_property(struct device *dev, struct sysmmu_drvdata *drvdata) { const char *default_props_name = "sysmmu,default_tlb"; const char *props_name = "sysmmu,tlb_property"; struct tlb_props *tlb_props = &drvdata->tlb_props; struct tlb_config *cfg; int i, readsize, cnt, ret; if (of_property_read_u32(dev->of_node, default_props_name, &tlb_props->default_cfg)) tlb_props->default_cfg = DEFAULT_TLB_NONE; cnt = of_property_count_elems_of_size(dev->of_node, props_name, sizeof(*cfg)); if (cnt <= 0) return 0; cfg = devm_kcalloc(dev, cnt, sizeof(*cfg), GFP_KERNEL); if (!cfg) return -ENOMEM; readsize = cnt * sizeof(*cfg) / sizeof(u32); ret = of_property_read_variable_u32_array(dev->of_node, props_name, (u32 *)cfg, readsize, readsize); if (ret < 0) { dev_err(dev, "failed to get tlb property, return %d\n", ret); return ret; } for (i = 0; i < cnt; i++) { if (cfg[i].index >= drvdata->num_tlb) { dev_err(dev, "invalid index %d is ignored. (max:%d)\n", cfg[i].index, drvdata->num_tlb); cfg[i].index = UNUSED_TLB_INDEX; } } tlb_props->id_cnt = cnt; tlb_props->cfg = cfg; return 0; } static int __sysmmu_secure_irq_init(struct device *sysmmu, struct sysmmu_drvdata *data) { struct platform_device *pdev = to_platform_device(sysmmu); int ret; ret = platform_get_irq(pdev, 1); if (ret <= 0) { dev_err(sysmmu, "unable to find secure IRQ resource\n"); return -EINVAL; } data->secure_irq = ret; ret = devm_request_threaded_irq(sysmmu, data->secure_irq, samsung_sysmmu_irq, samsung_sysmmu_irq_thread, IRQF_ONESHOT, dev_name(sysmmu), data); if (ret) { dev_err(sysmmu, "failed to set secure irq handler %d, ret:%d\n", data->secure_irq, ret); return ret; } ret = of_property_read_u32(sysmmu->of_node, "sysmmu,secure_base", &data->secure_base); if (ret) { dev_err(sysmmu, "failed to get secure base\n"); return ret; } dev_info(sysmmu, "secure base = %#x\n", data->secure_base); return ret; } static int sysmmu_parse_dt(struct device *sysmmu, struct sysmmu_drvdata *data) { unsigned int qos = DEFAULT_QOS_VALUE, mask = 0; int ret; /* Parsing QoS */ ret = of_property_read_u32_index(sysmmu->of_node, "qos", 0, &qos); if (!ret && qos > 15) { dev_err(sysmmu, "Invalid QoS value %d, use default.\n", qos); qos = DEFAULT_QOS_VALUE; } data->qos = qos; /* Secure IRQ */ if (of_find_property(sysmmu->of_node, "sysmmu,secure-irq", NULL)) { ret = __sysmmu_secure_irq_init(sysmmu, data); if (ret) { dev_err(sysmmu, "failed to init secure irq\n"); return ret; } } /* use async fault mode */ data->async_fault_mode = of_property_read_bool(sysmmu->of_node, "sysmmu,async-fault"); data->vmid_mask = DEFAULT_VMID_MASK; ret = of_property_read_u32_index(sysmmu->of_node, "vmid_mask", 0, &mask); if (!ret && (mask & ((1 << data->max_vm) - 1))) data->vmid_mask = mask; ret = sysmmu_parse_tlb_property(sysmmu, data); if (ret) dev_err(sysmmu, "Failed to parse TLB property\n"); return ret; } static int samsung_sysmmu_init_global(void) { int ret = 0; flpt_cache = kmem_cache_create("samsung-iommu-lv1table", LV1TABLE_SIZE, LV1TABLE_SIZE, 0, NULL); if (!flpt_cache) return -ENOMEM; slpt_cache = kmem_cache_create("samsung-iommu-lv2table", LV2TABLE_SIZE, LV2TABLE_SIZE, 0, NULL); if (!slpt_cache) { ret = -ENOMEM; goto err_init_slpt_fail; } bus_set_iommu(&platform_bus_type, &samsung_sysmmu_ops); device_initialize(&sync_dev); sysmmu_global_init_done = true; return 0; err_init_slpt_fail: kmem_cache_destroy(flpt_cache); return ret; } static int samsung_sysmmu_device_probe(struct platform_device *pdev) { struct sysmmu_drvdata *data; struct device *dev = &pdev->dev; struct resource *res; int irq, ret, err = 0; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(dev, "failed to get resource info\n"); return -ENOENT; } data->sfrbase = devm_ioremap_resource(dev, res); if (IS_ERR(data->sfrbase)) return PTR_ERR(data->sfrbase); irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; ret = devm_request_threaded_irq(dev, irq, samsung_sysmmu_irq, samsung_sysmmu_irq_thread, IRQF_ONESHOT, dev_name(dev), data); if (ret) { dev_err(dev, "unabled to register handler of irq %d\n", irq); return ret; } data->clk = devm_clk_get(dev, "gate"); if (PTR_ERR(data->clk) == -ENOENT) { dev_info(dev, "no gate clock exists. it's okay.\n"); data->clk = NULL; } else if (IS_ERR(data->clk)) { dev_err(dev, "failed to get clock!\n"); return PTR_ERR(data->clk); } INIT_LIST_HEAD(&data->list); spin_lock_init(&data->lock); data->dev = dev; platform_set_drvdata(pdev, data); err = samsung_iommu_init_log(&data->log, SYSMMU_EVENT_MAX); if (err) { dev_err(dev, "failed to initialize log\n"); return err; } pm_runtime_enable(dev); ret = sysmmu_get_hw_info(data); if (ret) { dev_err(dev, "failed to get h/w info\n"); goto err_get_hw_info; } ret = sysmmu_parse_dt(data->dev, data); if (ret) goto err_get_hw_info; ret = iommu_device_sysfs_add(&data->iommu, data->dev, NULL, dev_name(dev)); if (ret) { dev_err(dev, "failed to register iommu in sysfs\n"); goto err_get_hw_info; } iommu_device_set_ops(&data->iommu, &samsung_sysmmu_ops); iommu_device_set_fwnode(&data->iommu, dev->fwnode); err = iommu_device_register(&data->iommu); if (err) { dev_err(dev, "failed to register iommu\n"); goto err_iommu_register; } mutex_lock(&sysmmu_global_mutex); if (!sysmmu_global_init_done) { err = samsung_sysmmu_init_global(); if (err) { dev_err(dev, "failed to initialize global data\n"); mutex_unlock(&sysmmu_global_mutex); goto err_global_init; } } mutex_unlock(&sysmmu_global_mutex); dev_info(dev, "initialized IOMMU. Ver %d.%d.%d\n", MMU_MAJ_VER(data->version), MMU_MIN_VER(data->version), MMU_REV_VER(data->version)); return 0; err_global_init: iommu_device_unregister(&data->iommu); err_iommu_register: iommu_device_sysfs_remove(&data->iommu); err_get_hw_info: pm_runtime_disable(dev); samsung_iommu_deinit_log(&data->log); return err; } static void samsung_sysmmu_device_shutdown(struct platform_device *pdev) { } static int __maybe_unused samsung_sysmmu_runtime_suspend(struct device *sysmmu) { unsigned long flags; struct sysmmu_drvdata *drvdata = dev_get_drvdata(sysmmu); spin_lock_irqsave(&drvdata->lock, flags); drvdata->rpm_count--; if (drvdata->attached_count > 0) __sysmmu_disable(drvdata); spin_unlock_irqrestore(&drvdata->lock, flags); SYSMMU_EVENT_LOG_RANGE(drvdata, SYSMMU_EVENT_POWEROFF, drvdata->rpm_count, drvdata->attached_count); return 0; } static int __maybe_unused samsung_sysmmu_runtime_resume(struct device *sysmmu) { unsigned long flags; struct sysmmu_drvdata *drvdata = dev_get_drvdata(sysmmu); spin_lock_irqsave(&drvdata->lock, flags); drvdata->rpm_count++; if (drvdata->attached_count > 0) __sysmmu_enable(drvdata); spin_unlock_irqrestore(&drvdata->lock, flags); SYSMMU_EVENT_LOG_RANGE(drvdata, SYSMMU_EVENT_POWERON, drvdata->rpm_count, drvdata->attached_count); return 0; } static int __maybe_unused samsung_sysmmu_suspend(struct device *dev) { dev->power.must_resume = true; if (pm_runtime_status_suspended(dev)) return 0; return samsung_sysmmu_runtime_suspend(dev); } static int __maybe_unused samsung_sysmmu_resume(struct device *dev) { if (pm_runtime_status_suspended(dev)) return 0; return samsung_sysmmu_runtime_resume(dev); } static const struct dev_pm_ops samsung_sysmmu_pm_ops = { SET_RUNTIME_PM_OPS(samsung_sysmmu_runtime_suspend, samsung_sysmmu_runtime_resume, NULL) SET_LATE_SYSTEM_SLEEP_PM_OPS(samsung_sysmmu_suspend, samsung_sysmmu_resume) }; static const struct of_device_id sysmmu_of_match[] = { { .compatible = "samsung,sysmmu-v8" }, { } }; static struct platform_driver samsung_sysmmu_driver = { .driver = { .name = "samsung-sysmmu", .of_match_table = of_match_ptr(sysmmu_of_match), .pm = &samsung_sysmmu_pm_ops, .suppress_bind_attrs = true, }, .probe = samsung_sysmmu_device_probe, .shutdown = samsung_sysmmu_device_shutdown, }; module_platform_driver(samsung_sysmmu_driver); MODULE_SOFTDEP("pre: samsung-iommu-group"); MODULE_LICENSE("GPL v2");