blob: 90046da681f12b009dcefdd59db5e63ea8b0f678 [file] [edit]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021 Samsung Electronics Co., Ltd.
*/
#include <linux/kmemleak.h>
#include <linux/module.h>
#include <linux/of_iommu.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/dma-mapping.h>
#include <linux/sched/clock.h>
#include <linux/slab.h>
#include "samsung-iommu-v9.h"
#include <soc/google/debug-snapshot.h>
#include <soc/google/pkvm-s2mpu.h>
#define REG_MMU_NUM_CONTEXT 0x0100
#define MMU_NUM_CONTEXT(reg) ((reg) & 0x1F)
#define REG_MMU_NUM_CONTEXT_PER_VM(n) (0x140 + (n) * 0x4)
#define MMU_NUM_CONTEXT_PER_VM(reg) ((reg) & 0x1F)
#define REG_MMU_ALL_INV_VM 0x8010
#define REG_MMU_RANGE_INV_START_VPN_VM 0x8020
#define REG_MMU_RANGE_INV_END_VPN_AND_TRIG_VM 0x8024
#define MMU_PMMU_INFO_VA_WIDTH(reg) ((reg) & 0x1)
#define MMU_SWALKER_INFO_NUM_PMMU(reg) ((reg) & 0xFFFF)
#define MMU_PMMU_INFO_NUM_STREAM_TABLE(reg) (((reg) >> 16) & 0xFFFF)
#define FLPD_AP_READ BIT(4)
#define FLPD_AP_WRITE BIT(5)
#define FLPD_SHAREABLE_FLAG BIT(6)
#define SLPD_AP_READ BIT(2)
#define SLPD_AP_WRITE BIT(3)
#define SLPD_SHAREABLE_FLAG BIT(4)
#define DEFAULT_QOS_VALUE -1
#define DEFAULT_STREAM_NONE ~0U
#define UNUSED_STREAM_INDEX ~0U
#define MMU_STREAM_CFG_MASK(reg) ((reg) & (GENMASK(31, 16) | GENMASK(8, 8) | \
GENMASK(6, 0)))
#define MMU_STREAM_MATCH_CFG_MASK(reg) ((reg) & (GENMASK(9, 8) | GENMASK(0, 0)))
static struct iommu_ops samsung_sysmmu_ops;
static struct platform_driver samsung_sysmmu_driver_v9;
struct samsung_sysmmu_domain {
struct iommu_domain domain;
struct iommu_group *group;
struct sysmmu_drvdata *vm_sysmmu; /* valid only if vid != 0 */
/* if vid != 0, domain is a pasid domain attached to exactly one device and sysmmu */
unsigned int vid;
sysmmu_pte_t *page_table;
atomic_t *lv2entcnt;
spinlock_t pgtablelock; /* spinlock to access pagetable */
bool ap_read_implies_write;
bool ap_permissive;
};
static bool sysmmu_global_init_done;
static struct device sync_dev;
static struct kmem_cache *flpt_cache, *slpt_cache;
static inline u32 __sysmmu_get_hw_version(struct sysmmu_drvdata *data)
{
return MMU_VERSION_RAW(readl_relaxed(data->sfrbase + REG_MMU_VERSION));
}
static inline u32 __sysmmu_get_num_vm(struct sysmmu_drvdata *data)
{
return MMU_NUM_CONTEXT(readl_relaxed(data->sfrbase + REG_MMU_NUM_CONTEXT));
}
static inline int __sysmmu_verify_num_context_per_vm(struct sysmmu_drvdata *data)
{
int i;
int ret = 0;
for (i = 0; i < data->max_vm; i++) {
unsigned int num_context;
num_context = MMU_NUM_CONTEXT_PER_VM(readl_relaxed(data->sfrbase +
REG_MMU_NUM_CONTEXT_PER_VM(i)));
if (num_context == 0) {
dev_err(data->dev, "No contexts allocated for vm %d", i);
ret = -ENODEV;
}
if (num_context > 1) {
dev_warn(data->dev,
"%u contexts allocated for vm %d (expected only 1 context)",
num_context, i);
}
}
return ret;
}
static inline u32 __sysmmu_get_num_pmmu(struct sysmmu_drvdata *data)
{
return MMU_SWALKER_INFO_NUM_PMMU(readl_relaxed(data->sfrbase + REG_MMU_SWALKER_INFO));
}
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 & BIT(i)))
continue;
writel_relaxed(value, MMU_VM_ADDR(addr, i));
}
}
static inline void __sysmmu_modify_bits_all_vm(struct sysmmu_drvdata *data, u32 mask,
u32 value, void __iomem *addr)
{
int i;
u32 reg;
for (i = 0; i < data->max_vm; i++) {
if (!(data->vmid_mask & BIT(i)))
continue;
reg = readl_relaxed(MMU_VM_ADDR(addr, i));
reg &= ~mask;
reg |= (value & mask);
writel_relaxed(reg, MMU_VM_ADDR(addr, i));
}
}
static inline void __sysmmu_invalidate_all(struct sysmmu_drvdata *data)
{
__sysmmu_write_all_vm(data, 0x1, data->sfrbase + REG_MMU_ALL_INV_VM);
}
static inline void __sysmmu_invalidate_all_vid(struct sysmmu_drvdata *data, unsigned int vid)
{
writel_relaxed(0x1, MMU_VM_ADDR(data->sfrbase + REG_MMU_ALL_INV_VM, vid));
}
static inline void __sysmmu_invalidate(struct sysmmu_drvdata *data,
dma_addr_t start, dma_addr_t end)
{
__sysmmu_write_all_vm(data, (ALIGN_DOWN(start, SPAGE_SIZE) >> 4),
data->sfrbase + REG_MMU_RANGE_INV_START_VPN_VM);
__sysmmu_write_all_vm(data, ((ALIGN_DOWN(end, (SPAGE_SIZE)) >> 4) | 0x1),
data->sfrbase + REG_MMU_RANGE_INV_END_VPN_AND_TRIG_VM);
}
static inline void __sysmmu_invalidate_vid(struct sysmmu_drvdata *data, unsigned int vid,
dma_addr_t start, dma_addr_t end)
{
writel_relaxed(ALIGN_DOWN(start, SPAGE_SIZE) >> 4,
MMU_VM_ADDR(data->sfrbase + REG_MMU_RANGE_INV_START_VPN_VM, vid));
writel_relaxed((ALIGN_DOWN(end, SPAGE_SIZE) >> 4) | 0x1,
MMU_VM_ADDR(data->sfrbase + REG_MMU_RANGE_INV_END_VPN_AND_TRIG_VM, vid));
}
static inline void __sysmmu_disable_vid(struct sysmmu_drvdata *data, unsigned int vid)
{
u32 ctrl_val;
ctrl_val = readl_relaxed(MMU_VM_ADDR(data->sfrbase + REG_MMU_CTRL_VM, vid));
ctrl_val &= ~MMU_CTRL_ENABLE;
writel_relaxed(ctrl_val, MMU_VM_ADDR(data->sfrbase + REG_MMU_CTRL_VM, vid));
writel_relaxed(0, MMU_VM_ADDR(data->sfrbase + REG_MMU_CONTEXT0_CFG_FLPT_BASE_VM, vid));
__sysmmu_invalidate_all_vid(data, vid);
}
static inline void __sysmmu_disable(struct sysmmu_drvdata *data)
{
unsigned int vid;
for (vid = 1; vid < MAX_VIDS; vid++) {
if (data->pgtable[vid])
__sysmmu_disable_vid(data, vid);
}
__sysmmu_modify_bits_all_vm(data, MMU_CTRL_ENABLE, 0, data->sfrbase + REG_MMU_CTRL_VM);
__sysmmu_invalidate_all(data);
}
static inline void __sysmmu_set_stream(struct sysmmu_drvdata *data, int pmmu_id)
{
struct stream_props *props = &data->props[pmmu_id];
struct stream_config *cfg = props->cfg;
int id_cnt = props->id_cnt;
unsigned int i, index;
writel_relaxed(MMU_SET_PMMU_INDICATOR(pmmu_id), data->sfrbase + REG_MMU_PMMU_INDICATOR);
readl_relaxed(data->sfrbase + REG_MMU_PMMU_INDICATOR);
if (props->default_cfg != DEFAULT_STREAM_NONE)
writel_relaxed(MMU_STREAM_CFG_MASK(props->default_cfg),
data->sfrbase + REG_MMU_STREAM_CFG(0));
for (i = 0; i < id_cnt; i++) {
if (cfg[i].index == UNUSED_STREAM_INDEX)
continue;
index = cfg[i].index;
writel_relaxed(MMU_STREAM_CFG_MASK(cfg[i].cfg),
data->sfrbase + REG_MMU_STREAM_CFG(index));
writel_relaxed(MMU_STREAM_MATCH_CFG_MASK(cfg[i].match_cfg),
data->sfrbase + REG_MMU_STREAM_MATCH_CFG(index));
writel_relaxed(cfg[i].match_id_value,
data->sfrbase + REG_MMU_STREAM_MATCH_SID_VALUE(index));
writel_relaxed(cfg[i].match_id_mask,
data->sfrbase + REG_MMU_STREAM_MATCH_SID_MASK(index));
}
}
static inline void __sysmmu_init_config_attribute(struct sysmmu_drvdata *data, unsigned int vid)
{
u32 cfg;
cfg = readl_relaxed(MMU_VM_ADDR(data->sfrbase + REG_MMU_CONTEXT0_CFG_ATTRIBUTE_VM, vid));
cfg &= ~CFG_PT_CACHEABLE_MASK;
cfg |= CFG_PT_CACHEABLE_NORMAL_NC;
cfg |= CFG_USE_AP;
if (data->qos != DEFAULT_QOS_VALUE) {
cfg &= ~CFG_QOS(0xF);
cfg |= CFG_QOS_OVRRIDE | CFG_QOS(data->qos);
}
writel_relaxed(cfg, MMU_VM_ADDR(data->sfrbase + REG_MMU_CONTEXT0_CFG_ATTRIBUTE_VM, vid));
}
static inline void __sysmmu_init_config(struct sysmmu_drvdata *data)
{
int i;
for (i = 0; i < data->max_vm; i++) {
if (!(data->vmid_mask & BIT(i)))
continue;
__sysmmu_init_config_attribute(data, i);
}
for (i = 0; i < data->num_pmmu; i++)
__sysmmu_set_stream(data, i);
}
static inline void __sysmmu_enable_vid(struct sysmmu_drvdata *data, unsigned int vid)
{
u32 ctrl_val;
writel_relaxed(data->pgtable[vid] >> PT_BASE_SHIFT,
MMU_VM_ADDR(data->sfrbase + REG_MMU_CONTEXT0_CFG_FLPT_BASE_VM, vid));
__sysmmu_init_config_attribute(data, vid);
ctrl_val = readl_relaxed(MMU_VM_ADDR(data->sfrbase + REG_MMU_CTRL_VM, vid));
ctrl_val |= MMU_CTRL_ENABLE;
writel_relaxed(ctrl_val, MMU_VM_ADDR(data->sfrbase + REG_MMU_CTRL_VM, vid));
__sysmmu_invalidate_all_vid(data, vid);
}
static inline void __sysmmu_enable(struct sysmmu_drvdata *data)
{
unsigned int vid;
__sysmmu_write_all_vm(data, data->pgtable[0] >> PT_BASE_SHIFT,
data->sfrbase + REG_MMU_CONTEXT0_CFG_FLPT_BASE_VM);
__sysmmu_init_config(data);
__sysmmu_modify_bits_all_vm(data, MMU_CTRL_ENABLE, MMU_CTRL_ENABLE,
data->sfrbase + REG_MMU_CTRL_VM);
__sysmmu_invalidate_all(data);
for (vid = 1; vid < MAX_VIDS; vid++) {
if (data->pgtable[vid])
__sysmmu_enable_vid(data, vid);
}
}
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(struct device *dev, enum iommu_cap cap)
{
return cap == IOMMU_CAP_CACHE_COHERENCY;
}
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;
pgtable_flush(domain->page_table, domain->page_table + NUM_LV1ENTRIES);
spin_lock_init(&domain->pgtablelock);
return &domain->domain;
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);
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] = 0;
data->group = NULL;
}
spin_unlock_irqrestore(&data->lock, flags);
}
/* TODO: Since upstream has removed of_get_dma_window() we'll temporarily
* open code relevant portion here, until we can come up with a generic
* enough implementation that can be proposed upstream for these drivers.
*/
static int samsung_sysmmu_get_dma_window(struct device_node *dn,
dma_addr_t *addr, size_t *size)
{
const __be32 *dma_window, *prop;
u32 cells;
dma_window = of_get_property(dn, "dma-window", NULL);
if (!dma_window)
return -ENODEV;
prop = of_get_property(dn, "#dma-address-cells", NULL);
cells = prop ? be32_to_cpup(prop) : of_n_addr_cells(dn);
if (!cells)
return -EINVAL;
*addr = of_read_number(dma_window, cells);
dma_window += cells;
prop = of_get_property(dn, "#dma-size-cells", NULL);
cells = prop ? be32_to_cpup(prop) : of_n_size_cells(dn);
if (!cells)
return -EINVAL;
*size = of_read_number(dma_window, cells);
return 0;
}
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 (samsung_sysmmu_get_dma_window(dev->of_node, &start, &size))
return 0;
end = start + size;
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 struct samsung_sysmmu_domain *attach_helper(struct iommu_domain *dom, struct device *dev)
{
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
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 ERR_PTR(-ENXIO);
}
if (!dev_iommu_priv_get(dev)) {
dev_err(dev, "has no IOMMU\n");
return ERR_PTR(-ENODEV);
}
return to_sysmmu_domain(dom);
}
static int samsung_sysmmu_attach_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;
unsigned long flags;
phys_addr_t page_table;
int i, ret = -EINVAL;
domain = attach_helper(dom, dev);
if (IS_ERR(domain))
return (int)PTR_ERR(domain);
if (domain->vm_sysmmu) {
dev_err(dev, "IOMMU domain is already used as PASID domain for VID %u\n",
domain->vid);
return -EBUSY;
}
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[0] = page_table;
if (pm_runtime_active(drvdata->dev))
__sysmmu_enable(drvdata);
else
dev_info(dev, "SysMMU not powered on\n");
} else if (drvdata->pgtable[0] != 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;
domain->ap_read_implies_write = drvdata->ap_read_implies_write;
domain->ap_permissive = drvdata->ap_permissive;
dev_info(dev, "attached with pgtable %pap\n", &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 sysmmu_drvdata *drvdata;
int i;
domain = to_sysmmu_domain(dom);
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, gfp_t gfp)
{
if (lv1ent_section(sent)) {
WARN(1, "trying to map on %#011llx mapped with 1MiB page. FLPD: %#010lx", iova,
(unsigned long)*sent);
return ERR_PTR(-EADDRINUSE);
}
if (lv1ent_unmapped(sent)) {
unsigned long flags;
sysmmu_pte_t *pent;
pent = kmem_cache_zalloc(slpt_cache, gfp);
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_page_table(sysmmu_pte_t *ent, unsigned int n)
{
memset(ent, 0, sizeof(*ent) * n);
}
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;
sysmmu_pte_t *pent_to_free = NULL;
if (lv1ent_section(sent)) {
WARN(1, "Trying to map 1MB@%#011llx on valid FLPD %#010lx", iova,
(unsigned long)*sent);
return -EADDRINUSE;
}
if (lv1ent_page(sent)) {
int cnt = atomic_read(pgcnt);
if (unlikely(cnt != 0)) {
WARN(1, "Trying to map 1MB@%#011llx on valid SLPT %#010lx. pgcnt: %d", iova,
(unsigned long)*sent, cnt);
return -EADDRINUSE;
}
pent_to_free = page_entry(sent, 0);
atomic_set(pgcnt, NUM_LV2ENTRIES);
}
if (prot & IOMMU_READ || domain->ap_permissive) {
attr |= FLPD_AP_READ;
if (domain->ap_read_implies_write)
attr |= FLPD_AP_WRITE;
}
if (prot & IOMMU_WRITE || domain->ap_permissive)
attr |= FLPD_AP_WRITE;
*sent = make_sysmmu_pte(paddr, SECT_FLAG, attr);
pgtable_flush(sent, sent + 1);
if (pent_to_free) {
struct iommu_iotlb_gather gather = {
.start = iova,
.end = iova + SECT_SIZE - 1,
};
iommu_iotlb_sync(&domain->domain, &gather);
kmem_cache_free(slpt_cache, pent_to_free);
}
return 0;
}
static int lv2set_page(struct samsung_sysmmu_domain *domain, sysmmu_iova_t iova, 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;
if (prot & IOMMU_READ || domain->ap_permissive) {
attr |= SLPD_AP_READ;
if (domain->ap_read_implies_write)
attr |= SLPD_AP_WRITE;
}
if (prot & IOMMU_WRITE || domain->ap_permissive)
attr |= SLPD_AP_WRITE;
if (size == SPAGE_SIZE) {
if (unlikely(!lv2ent_unmapped(pent))) {
int cnt = atomic_read(pgcnt);
WARN(1,
"Trying to map 4KB@%#011llx on valid SLPD %#010lx. Requested pa: %pap. pgcnt: %d",
iova, (unsigned long)*pent, &paddr, cnt);
return -EADDRINUSE;
}
*pent = make_sysmmu_pte(paddr, SPAGE_FLAG, attr);
atomic_inc(pgcnt);
} else { /* size == LPAGE_SIZE */
unsigned long i;
for (i = 0; i < SPAGES_PER_LPAGE; i++, pent++) {
if (unlikely(!lv2ent_unmapped(pent))) {
int cnt = atomic_read(pgcnt);
WARN(1,
"Trying to map 64KB@%#011llx on valid SLPD %#010lx. Requested pa: %pap. pgcnt: %d. i: %ld",
iova + i * SPAGE_SIZE, (unsigned long)*pent, &paddr, cnt, i);
clear_page_table(pent - i, i);
return -EADDRINUSE;
}
*pent = make_sysmmu_pte(paddr, LPAGE_FLAG, attr);
}
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 gfp)
{
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, gfp);
if (IS_ERR(pent))
ret = PTR_ERR(pent);
else
ret = lv2set_page(domain, iova, pent, paddr, size, prot, lv2entcnt);
}
if (ret)
pr_err("failed to map %#zx @ %#llx, ret:%d\n", size, iova, ret);
return ret;
}
static inline void samsung_sysmmu_iotlb_gather_add_joint_range(struct iommu_domain *domain,
struct iommu_iotlb_gather *gather,
unsigned long iova, size_t size)
{
if (iommu_iotlb_gather_is_disjoint(gather, iova, size))
iommu_iotlb_sync(domain, gather);
iommu_iotlb_gather_add_range(gather, iova, size);
}
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_page_table(pent, SPAGES_PER_LPAGE);
pgtable_flush(pent, pent + SPAGES_PER_LPAGE);
size = LPAGE_SIZE;
atomic_sub(SPAGES_PER_LPAGE, lv2entcnt);
done:
samsung_sysmmu_iotlb_gather_add_joint_range(dom, gather, iova, size);
return size;
err:
pr_err("failed: size(%#zx) @ %#llx is smaller than page size %#zx\n",
size, iova, err_pgsize);
return 0;
}
static inline void clear_and_flush_pgtable(sysmmu_pte_t *ent, int count, atomic_t *lv2entcnt)
{
clear_page_table(ent, count);
pgtable_flush(ent, ent + count);
if (lv2entcnt)
atomic_sub(count, lv2entcnt);
}
static void unmap_slpt(struct samsung_sysmmu_domain *domain, unsigned long iova, size_t size)
{
sysmmu_pte_t *sent, *pent;
unsigned long section_end, end = iova + size;
atomic_t *lv2entcnt;
int nent;
sent = section_entry(domain->page_table, iova);
while (iova < end) {
section_end = (iova + SECT_SIZE) & SECT_MASK;
if (end < section_end)
section_end = end;
if (WARN_ON(!lv1ent_page(sent)))
goto next_section;
pent = page_entry(sent, iova);
lv2entcnt = &domain->lv2entcnt[lv1ent_offset(iova)];
nent = (section_end - iova) >> SPAGE_ORDER;
clear_and_flush_pgtable(pent, nent, lv2entcnt);
next_section:
iova = section_end;
sent++;
}
}
size_t samsung_sysmmu_unmap_pages(struct iommu_domain *dom, unsigned long iova_org,
size_t pgsize, size_t pgcount,
struct iommu_iotlb_gather *gather)
{
struct samsung_sysmmu_domain *domain = to_sysmmu_domain(dom);
unsigned long iova = iova_org;
size_t size = pgsize * pgcount;
if (pgsize == SECT_SIZE) {
sysmmu_pte_t *sent, *flush_start;
unsigned long flush_count = 0;
sent = section_entry(domain->page_table, iova);
flush_start = sent;
while (pgcount--) {
if (lv1ent_page(sent)) {
if (flush_count) {
clear_and_flush_pgtable(flush_start, flush_count, NULL);
flush_count = 0;
}
flush_start = sent + 1;
unmap_slpt(domain, iova, SECT_SIZE);
} else {
flush_count++;
}
iova += SECT_SIZE;
sent++;
}
if (flush_count)
clear_and_flush_pgtable(flush_start, flush_count, NULL);
} else {
unmap_slpt(domain, iova, size);
}
samsung_sysmmu_iotlb_gather_add_joint_range(dom, gather, iova_org, size);
return size;
}
static void samsung_sysmmu_flush_iotlb_all(struct iommu_domain *dom)
{
unsigned long flags;
struct samsung_sysmmu_domain *domain = to_sysmmu_domain(dom);
struct sysmmu_drvdata *drvdata;
if (domain->vm_sysmmu) {
/* Domain is used as PASID domain */
drvdata = domain->vm_sysmmu;
spin_lock_irqsave(&drvdata->lock, flags);
if (drvdata->attached_count && drvdata->rpm_count > 0)
__sysmmu_invalidate_all_vid(drvdata, domain->vid);
spin_unlock_irqrestore(&drvdata->lock, flags);
} else if (domain->group) {
/* Domain is used as regular domain */
/*
* domain->group might be NULL if flush_iotlb_all is called
* before attach_dev. Just ignore it.
*/
struct list_head *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_invalidate_all(drvdata);
spin_unlock_irqrestore(&drvdata->lock, flags);
}
}
}
static void samsung_sysmmu_iotlb_sync_map(struct iommu_domain *dom,
unsigned long iova, size_t size)
{
struct samsung_sysmmu_domain *domain = to_sysmmu_domain(dom);
sysmmu_pte_t *sent, *pent;
unsigned long section_end, end = iova + size;
int idx_count;
sent = section_entry(domain->page_table, iova);
while (iova < end) {
section_end = (iova + SECT_SIZE) & SECT_MASK;
if (end < section_end)
section_end = end;
if (lv1ent_section(sent))
goto next_section;
pent = page_entry(sent, iova);
idx_count = (section_end - iova) >> SPAGE_ORDER;
pgtable_flush(pent, pent + idx_count);
next_section:
iova = section_end;
sent++;
}
}
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 sysmmu_drvdata *drvdata;
if (domain->vm_sysmmu) {
/* Domain is used as PASID domain */
drvdata = domain->vm_sysmmu;
spin_lock_irqsave(&drvdata->lock, flags);
if (drvdata->attached_count && drvdata->rpm_count > 0)
__sysmmu_invalidate_vid(drvdata, domain->vid, gather->start, gather->end);
spin_unlock_irqrestore(&drvdata->lock, flags);
} else if (domain->group) {
/* Domain is used as regular domain */
/*
* domain->group might be NULL if iotlb_sync is called
* before attach_dev. Just ignore it.
*/
struct list_head *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_invalidate(drvdata, 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;
}
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_dbg(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;
if (!fwspec || fwspec->ops != &samsung_sysmmu_ops)
return;
client = (struct sysmmu_clientdata *)dev_iommu_priv_get(dev);
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)
{
static DEFINE_MUTEX(set_iommudata_mutex);
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(&set_iommudata_mutex);
if (iommu_group_get_iommudata(group)) {
mutex_unlock(&set_iommudata_mutex);
return group;
}
list = kzalloc(sizeof(*list), GFP_KERNEL);
if (!list) {
mutex_unlock(&set_iommudata_mutex);
return ERR_PTR(-ENOMEM);
}
INIT_LIST_HEAD(list);
iommu_group_set_iommudata(group, list,
samsung_sysmmu_group_data_release);
mutex_unlock(&set_iommudata_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;
if (!dev_iommu_priv_get(dev)) {
client = devres_alloc(samsung_sysmmu_clientdata_release,
sizeof(*client), GFP_KERNEL);
if (!client)
return -ENOMEM;
dev_iommu_priv_set(dev, client);
devres_add(&sysmmu->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 0;
}
static int samsung_sysmmu_set_dev_pasid(struct iommu_domain *dom, struct device *dev,
ioasid_t pasid)
{
struct sysmmu_clientdata *client;
struct samsung_sysmmu_domain *domain;
struct sysmmu_drvdata *drvdata;
unsigned long flags;
unsigned int vid = pasid;
if (vid >= MAX_VIDS) {
dev_err(dev, "Requested VID %u above or equal maximum of %u\n", vid, MAX_VIDS);
return -EINVAL;
}
domain = attach_helper(dom, dev);
if (IS_ERR(domain))
return (int)PTR_ERR(domain);
if (domain->group) {
dev_err(dev, "IOMMU domain is already in use as VID 0 domain\n");
return -EBUSY;
}
client = (struct sysmmu_clientdata *)dev_iommu_priv_get(dev);
if (client->sysmmu_count != 1) {
dev_err(dev, "IOMMU PASID domains not supported for devices served by more than one IOMMU\n");
return -ENXIO;
}
drvdata = client->sysmmus[0];
if (domain->vm_sysmmu) {
if (drvdata == domain->vm_sysmmu && vid == domain->vid)
/* We only allow a single device per IOMMU group. The challenge with
* allowing multiple devices is correctly implementing the .remove_dev_pasid
* callback. We would need to track attachment on a per-device basis,
* because only when a domain is detached from all devices can we safely
* detach it from the SysMMU instance.
*/
dev_err(dev, "For set_dev_pasid, only one device per IOMMU group allowed\n");
else
dev_err(dev, "IOMMU domain is already used as PASID domain for VID %u\n",
domain->vid);
return -EBUSY;
}
spin_lock_irqsave(&drvdata->lock, flags);
if (!drvdata->attached_count) {
dev_err(dev, "IOMMU needs to be enabled to attach PASID domain\n");
spin_unlock_irqrestore(&drvdata->lock, flags);
return -ENXIO;
}
if (drvdata->vmid_mask & BIT(vid)) {
dev_err(dev, "VID %u is used by default domain\n", vid);
spin_unlock_irqrestore(&drvdata->lock, flags);
return -EBUSY;
}
if (drvdata->pgtable[vid]) {
dev_err(dev, "VID %u is already in use\n", vid);
spin_unlock_irqrestore(&drvdata->lock, flags);
return -EBUSY;
}
drvdata->pgtable[vid] = virt_to_phys(domain->page_table);
if (pm_runtime_active(drvdata->dev))
__sysmmu_enable_vid(drvdata, vid);
spin_unlock_irqrestore(&drvdata->lock, flags);
domain->vm_sysmmu = drvdata;
domain->vid = vid;
domain->ap_read_implies_write = drvdata->ap_read_implies_write;
return 0;
}
static void samsung_sysmmu_remove_dev_pasid(struct device *dev, ioasid_t pasid)
{
struct sysmmu_clientdata *client;
struct iommu_domain *dom;
struct samsung_sysmmu_domain *domain;
struct sysmmu_drvdata *drvdata;
unsigned long flags;
unsigned int vid = pasid;
if (vid >= MAX_VIDS) {
dev_err(dev, "VID %u above or equal maximum of %u\n", vid, MAX_VIDS);
return;
}
dom = iommu_get_domain_for_dev_pasid(dev, pasid, 0);
if (WARN_ON(IS_ERR(dom)) || !dom)
return;
domain = to_sysmmu_domain(dom);
client = (struct sysmmu_clientdata *)dev_iommu_priv_get(dev);
if (client->sysmmu_count != 1) {
dev_err(dev, "IOMMU PASID domains not supported for devices served by more than one IOMMU\n");
return;
}
drvdata = client->sysmmus[0];
spin_lock_irqsave(&drvdata->lock, flags);
if (drvdata->vmid_mask & BIT(vid)) {
dev_err(dev, "VID %u is used by default domain\n", vid);
spin_unlock_irqrestore(&drvdata->lock, flags);
return;
}
domain->vm_sysmmu = NULL;
domain->vid = 0;
drvdata->pgtable[vid] = 0;
if (pm_runtime_active(drvdata->dev))
__sysmmu_disable_vid(drvdata, vid);
spin_unlock_irqrestore(&drvdata->lock, flags);
}
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;
int prot = IOMMU_READ | IOMMU_WRITE;
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, [%#llx..%#llx)\n",
propname[type], base, base + size);
return -EINVAL;
}
region = iommu_alloc_resv_region(base, size, prot,
resvtype[type],
GFP_KERNEL);
if (!region)
return -ENOMEM;
list_add_tail(&region->list, head);
dev_info(dev, "Reserved IOMMU mapping [%#llx..%#llx)\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 = {
.owner = THIS_MODULE,
.capable = samsung_sysmmu_capable,
.domain_alloc = samsung_sysmmu_domain_alloc,
.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,
.def_domain_type = samsung_sysmmu_def_domain_type,
.remove_dev_pasid = samsung_sysmmu_remove_dev_pasid,
.pgsize_bitmap = SECT_SIZE | LPAGE_SIZE | SPAGE_SIZE,
.default_domain_ops = &(const struct iommu_domain_ops) {
.attach_dev = samsung_sysmmu_attach_dev,
.detach_dev = samsung_sysmmu_detach_dev,
.set_dev_pasid = samsung_sysmmu_set_dev_pasid,
.map = samsung_sysmmu_map,
.unmap = samsung_sysmmu_unmap,
.unmap_pages = samsung_sysmmu_unmap_pages,
.flush_iotlb_all = samsung_sysmmu_flush_iotlb_all,
.iotlb_sync_map = samsung_sysmmu_iotlb_sync_map,
.iotlb_sync = samsung_sysmmu_iotlb_sync,
.iova_to_phys = samsung_sysmmu_iova_to_phys,
.free = samsung_sysmmu_domain_free,
}
};
static int sysmmu_get_hw_info(struct sysmmu_drvdata *data)
{
int ret = 0;
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->max_vm = __sysmmu_get_num_vm(data);
data->num_pmmu = __sysmmu_get_num_pmmu(data);
ret = __sysmmu_verify_num_context_per_vm(data);
pm_runtime_put(data->dev);
return ret;
}
static int sysmmu_parse_stream_property(struct device *dev, struct sysmmu_drvdata *drvdata,
int pmmu_id)
{
char default_props_name[32];
char props_name[32];
struct stream_props *props = &drvdata->props[pmmu_id];
struct stream_config *cfg;
int i, readsize, cnt, ret, num_stream;
u32 pmmu;
snprintf(default_props_name, sizeof(default_props_name), "pmmu%d,default_stream", pmmu_id);
snprintf(props_name, sizeof(props_name), "pmmu%d,stream_property", pmmu_id);
if (of_property_read_u32(dev->of_node, default_props_name, &props->default_cfg))
props->default_cfg = DEFAULT_STREAM_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 stream property %s, ret %d\n", props_name, ret);
return ret;
}
/* get num stream */
writel_relaxed(MMU_SET_PMMU_INDICATOR(pmmu_id),
drvdata->sfrbase + REG_MMU_PMMU_INDICATOR);
readl_relaxed(drvdata->sfrbase + REG_MMU_PMMU_INDICATOR);
pmmu = readl_relaxed(drvdata->sfrbase + REG_MMU_PMMU_INFO);
num_stream = MMU_PMMU_INFO_NUM_STREAM_TABLE(pmmu);
for (i = 0; i < cnt; i++) {
if (cfg[i].index >= num_stream) {
dev_err(dev, "invalid index %u in %s is ignored. (max:%d)\n",
cfg[i].index, props_name, num_stream);
cfg[i].index = UNUSED_STREAM_INDEX;
}
}
props->id_cnt = cnt;
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 address\n");
return ret;
}
return ret;
}
static int sysmmu_parse_dt(struct device *sysmmu, struct sysmmu_drvdata *data)
{
unsigned int mask;
int ret, qos = DEFAULT_QOS_VALUE, i;
struct stream_props *props;
/* 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;
}
}
data->hide_page_fault = of_property_read_bool(sysmmu->of_node, "sysmmu,hide-page-fault");
data->always_dump_full_fault_info = of_property_read_bool(sysmmu->of_node,
"sysmmu,always-dump-full-fault-info");
if (data->hide_page_fault && data->always_dump_full_fault_info)
dev_warn(sysmmu, "Device tree option sysmmu,always-dump-full-fault-info ineffective if sysmmu,hide-page-fault is set\n");
/* use async fault mode */
data->async_fault_mode = of_property_read_bool(sysmmu->of_node, "sysmmu,async-fault");
data->leave_enabled_on_suspend = of_property_read_bool(sysmmu->of_node,
"sysmmu,leave-enabled-on-suspend");
data->ap_read_implies_write = of_property_read_bool(sysmmu->of_node,
"sysmmu,ap-read-implies-write");
data->ap_permissive = of_property_read_bool(sysmmu->of_node,
"sysmmu,ap-permissive");
if (of_property_read_u32(sysmmu->of_node, "panic-action", &data->panic_action))
data->panic_action = GO_PANIC_ID;
data->vmid_mask = SYSMMU_MASK_VMID;
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;
props = devm_kcalloc(sysmmu, data->num_pmmu, sizeof(*props), GFP_KERNEL);
if (!props)
return -ENOMEM;
data->props = props;
for (i = 0; i < data->num_pmmu; i++) {
ret = sysmmu_parse_stream_property(sysmmu, data, i);
if (ret)
dev_err(sysmmu, "Failed to parse PMMU %d streams\n", i);
}
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;
}
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)
{
static DEFINE_MUTEX(initialization_mutex);
struct sysmmu_drvdata *data;
struct device *dev = &pdev->dev;
struct resource *res;
int irq, ret, err = 0;
/*Link power domain of crossponding s2mpu to this iommu instance*/
if (IS_ENABLED(CONFIG_PKVM_S2MPU_V9)) {
ret = pkvm_s2mpu_of_link_v9(dev);
if (ret == -EAGAIN)
return -EPROBE_DEFER;
else if (ret) {
dev_err(dev, "can't link with s2mpu v9, error %d\n", ret);
return ret;
}
}
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) {
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);
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;
}
err = iommu_device_register(&data->iommu, &samsung_sysmmu_ops, dev);
if (err) {
dev_err(dev, "failed to register iommu\n");
goto err_iommu_register;
}
mutex_lock(&initialization_mutex);
if (!sysmmu_global_init_done) {
err = samsung_sysmmu_init_global();
if (err) {
dev_err(dev, "failed to initialize global data\n");
mutex_unlock(&initialization_mutex);
goto err_global_init;
}
}
mutex_unlock(&initialization_mutex);
dev_info(dev, "initialized IOMMU. Ver %d.%d.%d, %sgate clock\n",
MMU_VERSION_MAJOR(data->version),
MMU_VERSION_MINOR(data->version),
MMU_VERSION_REVISION(data->version),
data->clk ? "" : "no ");
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);
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--;
/* TODO(b/261529534): Proper design of "never disabled" IOMMU behavior */
if (!drvdata->leave_enabled_on_suspend) {
if (drvdata->attached_count > 0)
__sysmmu_disable(drvdata);
}
spin_unlock_irqrestore(&drvdata->lock, flags);
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);
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-v9" },
{ }
};
MODULE_DEVICE_TABLE(of, sysmmu_of_match);
static struct platform_driver samsung_sysmmu_driver_v9 = {
.driver = {
.name = "samsung-sysmmu-v9",
.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_v9);
MODULE_SOFTDEP("pre: samsung-iommu-group");
MODULE_LICENSE("GPL v2");