blob: 1a0da6da410ac3f055abca5fba073a8c9634305e [file] [log] [blame]
/* linux/drivers/iommu/exynos_pcie_iommu.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/highmem.h>
#include <linux/io.h>
#include <linux/iommu.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/of.h>
#include <linux/of_iommu.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/smc.h>
#include <linux/swap.h>
#include <linux/swapops.h>
#include <linux/genalloc.h>
#include <linux/memblock.h>
#include <asm/cacheflush.h>
#include <asm/pgtable.h>
#include <dt-bindings/sysmmu/sysmmu.h>
#include "exynos-pcie-iommu.h"
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
#include <linux/sec_debug.h>
#endif
#if defined(CONFIG_SOC_EXYNOS9820)
#define MAX_RC_NUM 2
#endif
static u8 *gen_buff;
static struct kmem_cache *lv2table_kmem_cache;
#ifndef USE_DYNAMIC_MEM_ALLOC
static struct gen_pool *lv2table_pool;
#endif
static struct sysmmu_drvdata g_sysmmu_drvdata[MAX_RC_NUM];
static u32 alloc_counter;
static u32 lv2table_counter;
static u32 max_req_cnt;
static struct dram_region ch_dram_region[NUM_DRAM_REGION];
static u32 max_dram_region;
static u32 wrong_pf_cnt;
#ifdef CONFIG_PCIE_IOMMU_HISTORY_LOG
struct history_buff pcie_map_history, pcie_unmap_history;
#endif
int pcie_iommu_map(int ch_num, unsigned long iova, phys_addr_t paddr,
size_t size, int prot);
size_t pcie_iommu_unmap(int ch_num, unsigned long iova, size_t size);
/* For ARM64 only */
static inline void pgtable_flush(void *vastart, void *vaend)
{
__dma_flush_area(vastart, vaend - vastart);
}
static void __sysmmu_tlb_invalidate_all(void __iomem *sfrbase)
{
writel(0x1, sfrbase + REG_MMU_FLUSH);
}
static void __sysmmu_set_ptbase(void __iomem *sfrbase, phys_addr_t pfn_pgtable)
{
writel_relaxed(pfn_pgtable, sfrbase + REG_PT_BASE_PPN);
__sysmmu_tlb_invalidate_all(sfrbase);
}
#if defined(CONFIG_SOC_EXYNOS9810)
static void __sysmmu_tlb_pinning(struct sysmmu_drvdata *drvdata)
{
dma_addr_t mem_start_addr;
size_t mem_size;
u32 start_addr, end_addr;
u32 upper_addr, reg_val;
void __iomem *sfrbase = drvdata->sfrbase;
/* Set TLB1 pinning address */
get_atomic_pool_info(&mem_start_addr, &mem_size);
upper_addr = (mem_start_addr >> 32) & 0xf;
start_addr = mem_start_addr & 0xffffffff;
end_addr = start_addr + mem_size;
reg_val = readl_relaxed(sfrbase + REG_MMU_TLB_MATCH_CFG(1));
/* Clear EVA/SVA_UPPER */
reg_val &= ~(0xff);
reg_val |= (upper_addr << 4) | (upper_addr << 0);
writel_relaxed(reg_val, sfrbase + REG_MMU_TLB_MATCH_CFG(1));
/* Set Start/End TLB MATCH */
writel_relaxed(start_addr, sfrbase + REG_MMU_TLB_MATCH_SVA(1));
writel_relaxed(end_addr, sfrbase + REG_MMU_TLB_MATCH_EVA(1));
pr_info("Set TLB MATCH address for TLB1 Pinning : 0x%x_%x ~ 0x%x_%x\n",
readl_relaxed(sfrbase + REG_MMU_TLB_MATCH_CFG(1)) & 0xf,
readl_relaxed(sfrbase + REG_MMU_TLB_MATCH_SVA(1)),
upper_addr,
readl_relaxed(sfrbase + REG_MMU_TLB_MATCH_EVA(1)));
}
#endif
static void __sysmmu_init_config(struct sysmmu_drvdata *drvdata)
{
unsigned long cfg = 0;
writel_relaxed(CTRL_BLOCK, drvdata->sfrbase + REG_MMU_CTRL);
/* Set TLB0 */
writel_relaxed(0x0, drvdata->sfrbase + REG_MMU_TLB_CFG(0));
/* Enable TLB1 to be used by both PCIe channel 0 and 1*/
#if defined(CONFIG_SOC_EXYNOS9810)
writel_relaxed(0x0, drvdata->sfrbase + REG_MMU_TLB_CFG(1));
writel_relaxed(TLB_USED_ALL_PCIE_PORT | TLB_USED_RW_REQ,
drvdata->sfrbase + REG_MMU_TLB_MATCH_CFG(1));
if (drvdata->use_tlb_pinning)
__sysmmu_tlb_pinning(drvdata);
#endif
if (drvdata->qos != DEFAULT_QOS_VALUE)
cfg |= CFG_QOS_OVRRIDE | CFG_QOS(drvdata->qos);
cfg |= __raw_readl(drvdata->sfrbase + REG_MMU_CFG) & ~CFG_MASK;
writel_relaxed(cfg, drvdata->sfrbase + REG_MMU_CFG);
}
static void __sysmmu_enable_nocount(struct sysmmu_drvdata *drvdata)
{
clk_enable(drvdata->clk);
__sysmmu_init_config(drvdata);
__sysmmu_set_ptbase(drvdata->sfrbase, drvdata->pgtable / PAGE_SIZE);
writel(CTRL_ENABLE, drvdata->sfrbase + REG_MMU_CTRL);
}
static void __sysmmu_disable_nocount(struct sysmmu_drvdata *drvdata)
{
writel_relaxed(0, drvdata->sfrbase + REG_MMU_CFG);
#ifdef USE_BLOCK_DISABE
writel_relaxed(CTRL_BLOCK_DISABLE, drvdata->sfrbase + REG_MMU_CTRL);
BUG_ON(readl_relaxed(drvdata->sfrbase + REG_MMU_CTRL)
!= CTRL_BLOCK_DISABLE);
#else
/* PCIe SysMMU use full disable(by pass) as default */
writel_relaxed(CTRL_DISABLE, drvdata->sfrbase + REG_MMU_CTRL);
#endif
clk_disable(drvdata->clk);
}
void pcie_sysmmu_enable(int ch_num)
{
struct sysmmu_drvdata *drvdata = &g_sysmmu_drvdata[ch_num];
__sysmmu_enable_nocount(drvdata);
set_sysmmu_active(drvdata);
}
EXPORT_SYMBOL(pcie_sysmmu_enable);
void pcie_sysmmu_disable(int ch_num)
{
struct sysmmu_drvdata *drvdata = &g_sysmmu_drvdata[ch_num];
set_sysmmu_inactive(drvdata);
__sysmmu_disable_nocount(drvdata);
pr_info("SysMMU alloc num : %d(Max:%d), lv2_alloc : %d, fault : %d\n",
alloc_counter, max_req_cnt,
lv2table_counter, wrong_pf_cnt);
}
EXPORT_SYMBOL(pcie_sysmmu_disable);
void pcie_sysmmu_all_buff_free(int ch_num)
{
struct sysmmu_drvdata *drvdata = &g_sysmmu_drvdata[ch_num];
struct exynos_iommu_domain *domain;
void __maybe_unused *virt_addr;
int i;
domain = drvdata->domain;
for (i = 0; i < NUM_LV1ENTRIES; i++) {
if (lv1ent_page(domain->pgtable + i)) {
#ifdef USE_DYNAMIC_MEM_ALLOC
virt_addr = phys_to_virt(
lv2table_base(domain->pgtable + i));
kmem_cache_free(lv2table_kmem_cache, virt_addr);
#else
gen_pool_free(lv2table_pool,
lv1ent_page(domain->pgtable + i),
LV2TABLE_REFCNT_SZ);
#endif
}
}
}
EXPORT_SYMBOL(pcie_sysmmu_all_buff_free);
static int get_hw_version(struct sysmmu_drvdata *drvdata, void __iomem *sfrbase)
{
int ret;
__sysmmu_enable_nocount(drvdata);
ret = MMU_RAW_VER(__raw_readl(sfrbase + REG_MMU_VERSION));
__sysmmu_disable_nocount(drvdata);
return ret;
}
static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
"PTW ACCESS FAULT",
"PAGE FAULT",
"L1TLB MULTI-HIT FAULT",
"ACCESS FAULT",
"SECURITY FAULT",
"UNKNOWN FAULT"
};
static unsigned int dump_tlb_entry_port_type(void __iomem *sfrbase,
int idx_way, int idx_set)
{
if (MMU_TLB_ENTRY_VALID(__raw_readl(sfrbase + REG_CAPA1_TLB_ATTR))) {
pr_crit("[%02d][%02d] VPN: %#010x, PPN: %#010x, ATTR: %#010x\n",
idx_way, idx_set,
__raw_readl(sfrbase + REG_CAPA1_TLB_VPN),
__raw_readl(sfrbase + REG_CAPA1_TLB_PPN),
__raw_readl(sfrbase + REG_CAPA1_TLB_ATTR));
return 1;
}
return 0;
}
#define MMU_NUM_TLB_SUBLINE 4
static void dump_sysmmu_tlb_port(struct sysmmu_drvdata *drvdata)
{
int t, i, j, k;
u32 capa0, capa1, info;
unsigned int cnt;
int num_tlb, num_port, num_sbb;
void __iomem *sfrbase = drvdata->sfrbase;
capa0 = __raw_readl(sfrbase + REG_MMU_CAPA0_V7);
capa1 = __raw_readl(sfrbase + REG_MMU_CAPA1_V7);
num_tlb = MMU_CAPA1_NUM_TLB(capa1);
num_port = MMU_CAPA1_NUM_PORT(capa1);
num_sbb = 1 << MMU_CAPA_NUM_SBB_ENTRY(capa0);
pr_crit("SysMMU has %d TLBs, %d ports, %d sbb entries\n",
num_tlb, num_port, num_sbb);
for (t = 0; t < num_tlb; t++) {
int num_set, num_way;
info = __raw_readl(sfrbase + MMU_TLB_INFO(t));
num_way = MMU_CAPA1_NUM_TLB_WAY(info);
num_set = MMU_CAPA1_NUM_TLB_SET(info);
pr_crit("TLB.%d has %d way, %d set.\n", t, num_way, num_set);
pr_crit("------------- TLB[WAY][SET][ENTRY] -------------\n");
for (i = 0, cnt = 0; i < num_way; i++) {
for (j = 0; j < num_set; j++) {
for (k = 0; k < MMU_NUM_TLB_SUBLINE; k++) {
__raw_writel(MMU_CAPA1_SET_TLB_READ_ENTRY(t, j, i, k),
sfrbase + REG_CAPA1_TLB_READ);
cnt += dump_tlb_entry_port_type(sfrbase, i, j);
}
}
}
}
if (!cnt)
pr_crit(">> No Valid TLB Entries\n");
pr_crit("--- SBB(Second-Level Page Table Base Address Buffer ---\n");
for (i = 0, cnt = 0; i < num_sbb; i++) {
__raw_writel(i, sfrbase + REG_CAPA1_SBB_READ);
if (MMU_SBB_ENTRY_VALID(__raw_readl(sfrbase + REG_CAPA1_SBB_VPN))) {
pr_crit("[%02d] VPN: %#010x, PPN: %#010x, ATTR: %#010x\n",
i, __raw_readl(sfrbase + REG_CAPA1_SBB_VPN),
__raw_readl(sfrbase + REG_CAPA1_SBB_LINK),
__raw_readl(sfrbase + REG_CAPA1_SBB_ATTR));
cnt++;
}
}
if (!cnt)
pr_crit(">> No Valid SBB Entries\n");
}
void print_pcie_sysmmu_tlb(int ch_num)
{
struct sysmmu_drvdata *drvdata = &g_sysmmu_drvdata[ch_num];
phys_addr_t pgtable;
pgtable = __raw_readl(drvdata->sfrbase + REG_PT_BASE_PPN);
pgtable <<= PAGE_SHIFT;
pr_crit("Page Table Base Address : 0x%llx\n", pgtable);
dump_sysmmu_tlb_port(drvdata);
}
EXPORT_SYMBOL(print_pcie_sysmmu_tlb);
static int show_fault_information(struct sysmmu_drvdata *drvdata,
int flags, unsigned long fault_addr)
{
unsigned int info;
phys_addr_t pgtable;
int fault_id = SYSMMU_FAULT_ID(flags);
const char *port_name = NULL;
int ret = 0;
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
char temp_buf[SZ_128];
#endif
pgtable = __raw_readl(drvdata->sfrbase + REG_PT_BASE_PPN);
pgtable <<= PAGE_SHIFT;
if (MMU_MAJ_VER(drvdata->version) >= 7) {
info = __raw_readl(drvdata->sfrbase + REG_FAULT_TRANS_INFO);
} else {
/* TODO: Remove me later */
info = __raw_readl(drvdata->sfrbase +
((flags & IOMMU_FAULT_WRITE) ?
REG_FAULT_AW_TRANS_INFO : REG_FAULT_AR_TRANS_INFO));
}
of_property_read_string(drvdata->sysmmu->of_node,
"port-name", &port_name);
pr_crit("----------------------------------------------------------\n");
pr_crit("From [%s], SysMMU %s %s at %#010lx (page table @ %pa)\n",
port_name ? port_name : dev_name(drvdata->sysmmu),
(flags & IOMMU_FAULT_WRITE) ? "WRITE" : "READ",
sysmmu_fault_name[fault_id], fault_addr, &pgtable);
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
snprintf(temp_buf, SZ_128, "%s %s %s at %#010lx (%pa)",
port_name ? port_name : dev_name(drvdata->sysmmu),
(flags & IOMMU_FAULT_WRITE) ? "WRITE" : "READ",
sysmmu_fault_name[fault_id], fault_addr, &pgtable);
sec_debug_set_extra_info_sysmmu(temp_buf);
#endif
if (fault_id == SYSMMU_FAULT_UNKNOWN) {
pr_crit("The fault is not caused by this System MMU.\n");
pr_crit("Please check IRQ and SFR base address.\n");
goto finish;
}
pr_crit("AxID: %#x, AxLEN: %#x\n", info & 0xFFFF, (info >> 16) & 0xF);
if (pgtable != drvdata->pgtable)
pr_crit("Page table base of driver: %pa\n",
&drvdata->pgtable);
if (fault_id == SYSMMU_FAULT_PTW_ACCESS)
pr_crit("System MMU has failed to access page table\n");
if (!pfn_valid(pgtable >> PAGE_SHIFT)) {
pr_crit("Page table base is not in a valid memory region\n");
} else {
sysmmu_pte_t *ent;
ent = section_entry(phys_to_virt(pgtable), fault_addr);
pr_crit("Lv1 entry: %#010x\n", *ent);
if (lv1ent_page(ent)) {
u64 sft_ent_addr, sft_fault_addr;
ent = page_entry(ent, fault_addr);
pr_crit("Lv2 entry: %#010x\n", *ent);
sft_ent_addr = (*ent) >> 8;
sft_fault_addr = fault_addr >> 12;
if (sft_ent_addr == sft_fault_addr) {
pr_crit("ent(%#llx) == faddr(%#llx)...\n",
sft_ent_addr, sft_fault_addr);
pr_crit("Try to IGNORE Page fault panic...\n");
ret = SYSMMU_NO_PANIC;
wrong_pf_cnt++;
}
}
}
dump_sysmmu_tlb_port(drvdata);
finish:
pr_crit("----------------------------------------------------------\n");
return ret;
}
#ifdef CONFIG_BCMDHD_PCIE
extern void dhd_smmu_fault_handler(u32 axid, unsigned long fault_addr);
#endif /* CONFIG_BCMDHD_PCIE */
static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id)
{
struct sysmmu_drvdata *drvdata = dev_id;
unsigned int itype;
unsigned long addr = -1;
int flags = 0;
u32 info;
u32 int_status;
int ret;
dev_info(drvdata->sysmmu, "%s:%d: irq(%d) happened\n",
__func__, __LINE__, irq);
WARN(!is_sysmmu_active(drvdata),
"Fault occurred while System MMU %s is not enabled!\n",
dev_name(drvdata->sysmmu));
int_status = __raw_readl(drvdata->sfrbase + REG_INT_STATUS);
itype = __ffs(int_status);
if (WARN_ON(!(itype < SYSMMU_FAULT_UNKNOWN)))
itype = SYSMMU_FAULT_UNKNOWN;
else
addr = __raw_readl(drvdata->sfrbase + REG_FAULT_ADDR);
/* It support 36bit address */
addr |= (unsigned long)(__raw_readl(drvdata->sfrbase +
REG_FAULT_TRANS_INFO) >> 28) << 32;
info = __raw_readl(drvdata->sfrbase + REG_FAULT_TRANS_INFO);
flags = MMU_IS_READ_FAULT(info) ?
IOMMU_FAULT_READ : IOMMU_FAULT_WRITE;
flags |= SYSMMU_FAULT_FLAG(itype);
/* Clear interrupts */
writel_relaxed(int_status, drvdata->sfrbase + REG_INT_CLEAR);
ret = show_fault_information(drvdata, flags, addr);
if (ret == SYSMMU_NO_PANIC)
return IRQ_HANDLED;
#ifdef CONFIG_BCMDHD_PCIE
if (drvdata->ch_num == 0) {
/* Kernel Panic will be triggered by dump handler */
dhd_smmu_fault_handler(info & 0xFFFF, addr);
disable_irq_nosync(irq);
}
#endif /* CONFIG_BCMDHD_PCIE */
atomic_notifier_call_chain(&drvdata->fault_notifiers, addr, &flags);
#ifndef CONFIG_BCMDHD_PCIE
panic("Unrecoverable System MMU Fault!!");
#endif /* !CONFIG_BCMDHD_PCIE */
return IRQ_HANDLED;
}
static void __sysmmu_tlb_invalidate(struct sysmmu_drvdata *drvdata,
dma_addr_t iova, size_t size)
{
void * __iomem sfrbase = drvdata->sfrbase;
u32 set_val = 0;
u32 lower_addr = lower_32_bits(iova);
u32 upper_addr = upper_32_bits(iova);
set_val = lower_addr & 0xfffff000;
set_val |= (upper_addr & 0xf) << 8;
__raw_writel(set_val, sfrbase + REG_FLUSH_RANGE_START);
set_val = (u32)(size - 1 + lower_addr);
set_val &= ~(0xf << 8);
set_val |= (upper_addr & 0xf) << 8;
set_val &= ~(0xff);
__raw_writel(set_val, sfrbase + REG_FLUSH_RANGE_END);
writel(0x1, sfrbase + REG_MMU_FLUSH_RANGE);
}
static void exynos_sysmmu_tlb_invalidate(int ch_num, dma_addr_t d_start, size_t size)
{
struct sysmmu_drvdata *drvdata = &g_sysmmu_drvdata[ch_num];
sysmmu_iova_t start = (sysmmu_iova_t)d_start;
spin_lock(&drvdata->lock);
if (!is_sysmmu_active(drvdata)) {
spin_unlock(&drvdata->lock);
dev_dbg(drvdata->sysmmu,
"Skip TLB invalidation %#zx@%#llx\n", size, start);
return ;
}
dev_dbg(drvdata->sysmmu,
"TLB invalidation %#zx@%#llx\n", size, start);
__sysmmu_tlb_invalidate(drvdata, start, size);
spin_unlock(&drvdata->lock);
}
static void clear_lv2_page_table(sysmmu_pte_t *ent, int n)
{
if (n > 0)
memset(ent, 0, sizeof(*ent) * n);
}
static int lv1set_section(struct exynos_iommu_domain *domain,
sysmmu_pte_t *sent, sysmmu_iova_t iova,
phys_addr_t paddr, int prot, atomic_t *pgcnt)
{
bool shareable = !!(prot & IOMMU_CACHE);
if (lv1ent_section(sent)) {
WARN(1, "Trying mapping on 1MiB@%#09llx that is mapped",
iova);
return -EADDRINUSE;
}
if (lv1ent_page(sent)) {
if (WARN_ON(atomic_read(pgcnt) != NUM_LV2ENTRIES)) {
WARN(1, "Trying mapping on 1MiB@%#09llx that is mapped",
iova);
return -EADDRINUSE;
}
/* TODO: for v7, free lv2 page table */
}
*sent = mk_lv1ent_sect(paddr);
if (shareable)
set_lv1ent_shareable(sent);
pgtable_flush(sent, sent + 1);
return 0;
}
static int lv2set_page(sysmmu_pte_t *pent, phys_addr_t paddr, size_t size,
int prot, atomic_t *pgcnt)
{
bool shareable = !!(prot & IOMMU_CACHE);
if (size == SPAGE_SIZE) {
if (!lv2ent_fault(pent)) {
sysmmu_pte_t *refcnt_buf;
/* Duplicated IOMMU map 4KB */
refcnt_buf = pent + NUM_LV2ENTRIES;
*refcnt_buf = *refcnt_buf + 1;
atomic_dec(pgcnt);
return 0;
}
*pent = mk_lv2ent_spage(paddr);
if (shareable)
set_lv2ent_shareable(pent);
pgtable_flush(pent, pent + 1);
atomic_dec(pgcnt);
} else { /* size == LPAGE_SIZE */
int i;
pr_err("Allocate LPAGE_SIZE!!!\n");
for (i = 0; i < SPAGES_PER_LPAGE; i++, pent++) {
if (WARN_ON(!lv2ent_fault(pent))) {
clear_lv2_page_table(pent - i, i);
return -EADDRINUSE;
}
*pent = mk_lv2ent_lpage(paddr);
if (shareable)
set_lv2ent_shareable(pent);
}
pgtable_flush(pent - SPAGES_PER_LPAGE, pent);
atomic_sub(SPAGES_PER_LPAGE, pgcnt);
}
return 0;
}
#ifdef USE_DYNAMIC_MEM_ALLOC
static sysmmu_pte_t *alloc_extend_buff(struct exynos_iommu_domain *domain)
{
sysmmu_pte_t *alloc_buff = NULL;
int i;
/* Find Empty buffer */
for (i = 0; i < MAX_EXT_BUFF_NUM; i++) {
if (domain->ext_buff[i].used == 0) {
pr_err("Use extend buffer index : %d...\n", i);
break;
}
}
if (i == MAX_EXT_BUFF_NUM) {
pr_err("WARNNING - Extend buffers are full!!!\n");
return NULL;
}
domain->ext_buff[i].used = 1;
alloc_buff = domain->ext_buff[i].buff;
return alloc_buff;
}
#endif
static sysmmu_pte_t *alloc_lv2entry(struct exynos_iommu_domain *domain,
sysmmu_pte_t *sent, sysmmu_iova_t iova,
atomic_t *pgcounter, gfp_t gfpmask)
{
sysmmu_pte_t *pent = NULL;
if (lv1ent_section(sent)) {
WARN(1, "Trying mapping on %#09llx mapped with 1MiB page",
iova);
return ERR_PTR(-EADDRINUSE);
}
if (lv1ent_fault(sent)) {
lv2table_counter++;
#ifdef USE_DYNAMIC_MEM_ALLOC
pent = kmem_cache_zalloc(lv2table_kmem_cache,
gfpmask);
if (!pent) {
/* Use extended Buffer */
pent = alloc_extend_buff(domain);
if (!pent)
return ERR_PTR(-ENOMEM);
}
#else
if (gen_pool_avail(lv2table_pool) >= LV2TABLE_REFCNT_SZ) {
pent = phys_to_virt(gen_pool_alloc(lv2table_pool,
LV2TABLE_REFCNT_SZ));
} else {
pr_err("Gen_pool is full!! Try dynamic alloc\n");
pent = kmem_cache_zalloc(lv2table_kmem_cache, gfpmask);
if (!pent)
return ERR_PTR(-ENOMEM);
}
#endif
*sent = mk_lv1ent_page(virt_to_phys(pent));
pgtable_flush(sent, sent + 1);
pgtable_flush(pent, pent + NUM_LV2ENTRIES);
atomic_set(pgcounter, NUM_LV2ENTRIES);
kmemleak_ignore(pent);
}
return page_entry(sent, iova);
}
static size_t iommu_pgsize(int ch_num, unsigned long addr_merge, size_t size)
{
struct sysmmu_drvdata *drvdata = &g_sysmmu_drvdata[ch_num];
struct exynos_iommu_domain *domain = drvdata->domain;
unsigned int pgsize_idx;
size_t pgsize;
/* Max page size that still fits into 'size' */
pgsize_idx = __fls(size);
/* need to consider alignment requirements ? */
if (likely(addr_merge)) {
/* Max page size allowed by address */
unsigned int align_pgsize_idx = __ffs(addr_merge);
pgsize_idx = min(pgsize_idx, align_pgsize_idx);
}
/* build a mask of acceptable page sizes */
pgsize = (1UL << (pgsize_idx + 1)) - 1;
/* throw away page sizes not supported by the hardware */
pgsize &= domain->pgsize_bitmap;
/* make sure we're still sane */
BUG_ON(!pgsize);
/* pick the biggest page */
pgsize_idx = __fls(pgsize);
pgsize = 1UL << pgsize_idx;
return pgsize;
}
static int exynos_iommu_map(int ch_num, unsigned long l_iova, phys_addr_t paddr,
size_t size, int prot)
{
struct sysmmu_drvdata *drvdata = &g_sysmmu_drvdata[ch_num];
struct exynos_iommu_domain *domain = drvdata->domain;
sysmmu_pte_t *entry;
sysmmu_iova_t iova = (sysmmu_iova_t)l_iova;
int ret = -ENOMEM;
BUG_ON(domain->pgtable == NULL);
entry = section_entry(domain->pgtable, iova);
if (size == SECT_SIZE) {
ret = lv1set_section(domain, entry, iova, paddr, prot,
&domain->lv2entcnt[lv1ent_offset(iova)]);
} else {
sysmmu_pte_t *pent;
pent = alloc_lv2entry(domain, entry, iova,
&domain->lv2entcnt[lv1ent_offset(iova)],
GFP_ATOMIC);
if (IS_ERR(pent))
ret = PTR_ERR(pent);
else
ret = lv2set_page(pent, paddr, size, prot,
&domain->lv2entcnt[lv1ent_offset(iova)]);
}
if (ret)
pr_err("%s: Failed(%d) to map %#zx bytes @ %#llx\n",
__func__, ret, size, iova);
return ret;
}
static size_t exynos_iommu_unmap(int ch_num, unsigned long l_iova, size_t size)
{
struct sysmmu_drvdata *drvdata = &g_sysmmu_drvdata[ch_num];
struct exynos_iommu_domain *domain = drvdata->domain;
sysmmu_iova_t iova = (sysmmu_iova_t)l_iova;
sysmmu_pte_t *sent, *pent;
size_t err_pgsize;
atomic_t *lv2entcnt = &domain->lv2entcnt[lv1ent_offset(iova)];
BUG_ON(domain->pgtable == NULL);
sent = section_entry(domain->pgtable, 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_fault(sent))) {
if (size > SECT_SIZE)
size = SECT_SIZE;
goto done;
}
/* lv1ent_page(sent) == true here */
pent = page_entry(sent, iova);
if (unlikely(lv2ent_fault(pent))) {
size = SPAGE_SIZE;
goto done;
}
if (lv2ent_small(pent)) {
/* Check Duplicated IOMMU Unmp */
sysmmu_pte_t *refcnt_buf;
refcnt_buf = (pent + NUM_LV2ENTRIES);
if (*refcnt_buf != 0) {
*refcnt_buf = *refcnt_buf - 1;
atomic_inc(lv2entcnt);
goto done;
}
*pent = 0;
size = SPAGE_SIZE;
pgtable_flush(pent, pent + 1);
atomic_inc(lv2entcnt);
goto unmap_flpd;
}
/* 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_add(SPAGES_PER_LPAGE, lv2entcnt);
unmap_flpd:
/* TODO: for v7, remove all */
if (atomic_read(lv2entcnt) == NUM_LV2ENTRIES) {
sysmmu_pte_t* free_buff;
phys_addr_t __maybe_unused paddr;
free_buff = page_entry(sent, 0);
paddr = virt_to_phys(free_buff);
lv2table_counter--;
*sent = 0;
pgtable_flush(sent, sent + 1);
atomic_set(lv2entcnt, 0);
#ifdef USE_DYNAMIC_MEM_ALLOC
if (free_buff >= domain->ext_buff[0].buff && free_buff
<= domain->ext_buff[MAX_EXT_BUFF_NUM - 1].buff) {
/* Allocated from extend buffer */
u64 index =
(u64)free_buff - (u64)domain->ext_buff[0].buff;
index /= SZ_2K;
if (index < MAX_EXT_BUFF_NUM) {
pr_err("Extend buffer %llu free...\n", index);
domain->ext_buff[index].used = 0;
} else
pr_err("WARN - Lv2table free ERR!!!\n");
} else {
kmem_cache_free(lv2table_kmem_cache,
free_buff);
}
#else
if (addr_in_gen_pool(lv2table_pool, paddr, LV2TABLE_REFCNT_SZ))
gen_pool_free(lv2table_pool, paddr, LV2TABLE_REFCNT_SZ);
else
kmem_cache_free(lv2table_kmem_cache, free_buff);
#endif
}
done:
return size;
err:
pr_err("%s: Failed: size(%#zx)@%#llx is smaller than page size %#zx\n",
__func__, size, iova, err_pgsize);
return 0;
}
#ifdef CONFIG_PCIE_IOMMU_HISTORY_LOG
static inline void add_history_buff(struct history_buff *hbuff,
phys_addr_t addr, phys_addr_t orig_addr,
size_t size, size_t orig_size)
{
ktime_t current_time;
current_time = ktime_get();
hbuff->save_addr[hbuff->index] = (u32)((addr >> 0x4) & 0xffffffff);
hbuff->orig_addr[hbuff->index] = (u32)((orig_addr >> 0x4) & 0xffffffff);
hbuff->size[hbuff->index] = size;
hbuff->orig_size[hbuff->index] = orig_size;
hbuff->time_ns[hbuff->index] = ktime_to_us(current_time);
hbuff->index++;
if (hbuff->index >= MAX_HISTROY_BUFF)
hbuff->index = 0;
}
#endif
static inline int check_memory_validation(phys_addr_t paddr)
{
int i;
for (i = 0; i < max_dram_region; i++) {
if (paddr >= ch_dram_region[i].start &&
paddr <= ch_dram_region[i].end) {
return 0;
}
}
pr_err("Requested address %llx is NOT in DRAM rergion!!\n", paddr);
return -EINVAL;
}
int pcie_iommu_map(int ch_num, unsigned long iova, phys_addr_t paddr, size_t size, int prot)
{
struct sysmmu_drvdata *drvdata = &g_sysmmu_drvdata[ch_num];
struct exynos_iommu_domain *domain = drvdata->domain;
unsigned long orig_iova = iova;
unsigned int min_pagesz;
size_t orig_size = size;
phys_addr_t __maybe_unused orig_paddr = paddr;
int ret = 0;
unsigned long changed_iova, changed_size;
unsigned long flags;
if (check_memory_validation(paddr) != 0) {
pr_err("WARN - Unexpected address request : 0x%llx\n", paddr);
return -EINVAL;
}
/* Make sure start address align least 4KB */
if ((iova & SYSMMU_4KB_MASK) != 0) {
size += iova & SYSMMU_4KB_MASK;
iova &= ~(SYSMMU_4KB_MASK);
paddr &= ~(SYSMMU_4KB_MASK);
}
/* Make sure size align least 4KB */
if ((size & SYSMMU_4KB_MASK) != 0) {
size &= ~(SYSMMU_4KB_MASK);
size += SZ_4K;
}
/* Check for debugging */
changed_iova = iova;
changed_size = size;
/* find out the minimum page size supported */
min_pagesz = 1 << __ffs(domain->pgsize_bitmap);
/*
* both the virtual address and the physical one, as well as
* the size of the mapping, must be aligned (at least) to the
* size of the smallest page supported by the hardware
*/
if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) {
pr_err("unaligned: iova 0x%lx pa %p sz 0x%zx min_pagesz 0x%x\n",
iova, &paddr, size, min_pagesz);
return -EINVAL;
}
spin_lock_irqsave(&domain->pgtablelock, flags);
while (size) {
size_t pgsize = iommu_pgsize(ch_num, iova | paddr, size);
pr_debug("mapping: iova 0x%lx pa %pa pgsize 0x%zx\n",
iova, &paddr, pgsize);
alloc_counter++;
if (alloc_counter > max_req_cnt)
max_req_cnt = alloc_counter;
ret = exynos_iommu_map(ch_num, iova, paddr, pgsize, prot);
exynos_sysmmu_tlb_invalidate(ch_num, iova, pgsize);
#ifdef CONFIG_PCIE_IOMMU_HISTORY_LOG
add_history_buff(&pcie_map_history, paddr, orig_paddr,
changed_size, orig_size);
#endif
if (ret)
break;
iova += pgsize;
paddr += pgsize;
size -= pgsize;
}
spin_unlock_irqrestore(&domain->pgtablelock, flags);
/* unroll mapping in case something went wrong */
if (ret) {
pr_err("PCIe SysMMU mapping Error!\n");
pcie_iommu_unmap(ch_num, orig_iova, orig_size - size);
}
pr_debug("mapped: req 0x%lx(org : 0x%lx) size 0x%zx(0x%zx)\n",
changed_iova, orig_iova, changed_size, orig_size);
return ret;
}
EXPORT_SYMBOL_GPL(pcie_iommu_map);
size_t pcie_iommu_unmap(int ch_num, unsigned long iova, size_t size)
{
struct sysmmu_drvdata *drvdata = &g_sysmmu_drvdata[ch_num];
struct exynos_iommu_domain *domain = drvdata->domain;
size_t unmapped_page, unmapped = 0;
unsigned int min_pagesz;
unsigned long __maybe_unused orig_iova = iova;
unsigned long __maybe_unused changed_iova;
size_t __maybe_unused orig_size = size;
unsigned long flags;
/* Make sure start address align least 4KB */
if ((iova & SYSMMU_4KB_MASK) != 0) {
size += iova & SYSMMU_4KB_MASK;
iova &= ~(SYSMMU_4KB_MASK);
}
/* Make sure size align least 4KB */
if ((size & SYSMMU_4KB_MASK) != 0) {
size &= ~(SYSMMU_4KB_MASK);
size += SZ_4K;
}
changed_iova = iova;
/* find out the minimum page size supported */
min_pagesz = 1 << __ffs(domain->pgsize_bitmap);
/*
* The virtual address, as well as the size of the mapping, must be
* aligned (at least) to the size of the smallest page supported
* by the hardware
*/
if (!IS_ALIGNED(iova | size, min_pagesz)) {
pr_err("unaligned: iova 0x%lx size 0x%zx min_pagesz 0x%x\n",
iova, size, min_pagesz);
return -EINVAL;
}
pr_debug("unmap this: iova 0x%lx size 0x%zx\n", iova, size);
spin_lock_irqsave(&domain->pgtablelock, flags);
/*
* Keep iterating until we either unmap 'size' bytes (or more)
* or we hit an area that isn't mapped.
*/
while (unmapped < size) {
size_t pgsize = iommu_pgsize(ch_num, iova, size - unmapped);
alloc_counter--;
unmapped_page = exynos_iommu_unmap(ch_num, iova, pgsize);
exynos_sysmmu_tlb_invalidate(ch_num, iova, pgsize);
#ifdef CONFIG_PCIE_IOMMU_HISTORY_LOG
add_history_buff(&pcie_unmap_history, iova, orig_iova,
size, orig_size);
#endif
if (!unmapped_page)
break;
pr_debug("unmapped: iova 0x%lx size 0x%zx\n",
iova, unmapped_page);
iova += unmapped_page;
unmapped += unmapped_page;
}
spin_unlock_irqrestore(&domain->pgtablelock, flags);
pr_debug("UNMAPPED : req 0x%lx(0x%lx) size 0x%zx(0x%zx)\n",
changed_iova, orig_iova, size, orig_size);
return unmapped;
}
EXPORT_SYMBOL_GPL(pcie_iommu_unmap);
static int __init sysmmu_parse_dt(struct device *sysmmu,
struct sysmmu_drvdata *drvdata)
{
unsigned int qos = DEFAULT_QOS_VALUE;
#if defined(CONFIG_SOC_EXYNOS9810)
const char *use_tlb_pinning;
#endif
int ret = 0;
/* 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;
}
drvdata->qos = qos;
if (of_property_read_bool(sysmmu->of_node, "sysmmu,no-suspend"))
dev_pm_syscore_device(sysmmu, true);
#if defined(CONFIG_SOC_EXYNOS9810)
if (!of_property_read_string(sysmmu->of_node,
"use-tlb-pinning", &use_tlb_pinning)) {
if (!strcmp(use_tlb_pinning, "true")) {
dev_err(sysmmu, "Enable TLB Pinning.\n");
drvdata->use_tlb_pinning = true;
} else if (!strcmp(use_tlb_pinning, "false")) {
drvdata->use_tlb_pinning = false;
} else {
dev_err(sysmmu, "Invalid TLB pinning value"
"(set to default -> false)\n");
drvdata->use_tlb_pinning = false;
}
} else {
drvdata->use_tlb_pinning = false;
}
#endif
return 0;
}
static struct exynos_iommu_domain *exynos_iommu_domain_alloc(void)
{
struct exynos_iommu_domain *domain;
int __maybe_unused i;
domain = kzalloc(sizeof(*domain), GFP_KERNEL);
if (!domain)
return NULL;
/* 36bit VA FLPD must be aligned in 256KB */
domain->pgtable =
(sysmmu_pte_t *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 6);
if (!domain->pgtable)
goto err_pgtable;
domain->lv2entcnt = (atomic_t *)
__get_free_pages(GFP_KERNEL | __GFP_ZERO, 6);
if (!domain->lv2entcnt)
goto err_counter;
pgtable_flush(domain->pgtable, domain->pgtable + NUM_LV1ENTRIES);
spin_lock_init(&domain->lock);
spin_lock_init(&domain->pgtablelock);
INIT_LIST_HEAD(&domain->clients_list);
/* TODO: get geometry from device tree */
domain->domain.geometry.aperture_start = 0;
domain->domain.geometry.aperture_end = ~0UL;
domain->domain.geometry.force_aperture = true;
domain->pgsize_bitmap = SECT_SIZE | LPAGE_SIZE | SPAGE_SIZE;
#ifdef USE_DYNAMIC_MEM_ALLOC
domain->ext_buff[0].buff =
kzalloc(SZ_2K * MAX_EXT_BUFF_NUM, GFP_KERNEL);
if (!domain->ext_buff[0].buff) {
pr_err("Can't allocate extend buffer!\n");
goto err_counter;
}
/* ext_buff[0] is already initialized */
for (i = 1; i < MAX_EXT_BUFF_NUM; i ++) {
domain->ext_buff[i].index = i;
domain->ext_buff[i].used = 0;
domain->ext_buff[i].buff =
domain->ext_buff[i - 1].buff +
(SZ_2K / sizeof(sysmmu_pte_t));
}
#endif
return domain;
err_counter:
free_pages((unsigned long)domain->pgtable, 6);
err_pgtable:
kfree(domain);
return NULL;
}
static int __init exynos_sysmmu_probe(struct platform_device *pdev)
{
int irq, ret;
struct device *dev = &pdev->dev;
struct sysmmu_drvdata *data;
struct resource *res;
int __maybe_unused i;
int ch_num;
struct device_node *np = pdev->dev.of_node;
if (of_property_read_u32(np, "ch-num", &ch_num)) {
dev_err(dev, "Failed to parse the channel number\n");
return -EINVAL;
}
data = &g_sysmmu_drvdata[ch_num];
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) {
dev_err(dev, "Unable to find IRQ resource\n");
return irq;
}
ret = devm_request_irq(dev, irq, exynos_sysmmu_irq, 0,
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, "aclk");
if (IS_ERR(data->clk)) {
dev_err(dev, "Failed to get clock!\n");
return PTR_ERR(data->clk);
} else {
ret = clk_prepare(data->clk);
if (ret) {
dev_err(dev, "Failed to prepare clk\n");
return ret;
}
}
data->ch_num = ch_num;
data->sysmmu = dev;
spin_lock_init(&data->lock);
ATOMIC_INIT_NOTIFIER_HEAD(&data->fault_notifiers);
platform_set_drvdata(pdev, data);
data->qos = DEFAULT_QOS_VALUE;
data->version = get_hw_version(data, data->sfrbase);
ret = sysmmu_parse_dt(data->sysmmu, data);
if (ret) {
dev_err(dev, "Failed to parse DT\n");
return ret;
}
/* Create Temp Domain */
data->domain = exynos_iommu_domain_alloc();
data->pgtable = virt_to_phys(data->domain->pgtable);
dev_info(dev, "L1Page Table Address : 0x%llx(phys)\n", data->pgtable);
dev_info(data->sysmmu, "is probed. Version %d.%d.%d\n",
MMU_MAJ_VER(data->version),
MMU_MIN_VER(data->version),
MMU_REV_VER(data->version));
max_dram_region = memblock.memory.cnt;
if (max_dram_region >= NUM_DRAM_REGION) {
dev_err(dev, "DRAM region for validation is too big!!!\n");
return -EINVAL;
}
for (i = 0; i < max_dram_region; i++) {
ch_dram_region[i].start = memblock.memory.regions[i].base;
ch_dram_region[i].end = memblock.memory.regions[i].base +
memblock.memory.regions[i].size;
dev_info(dev, "Valid DRAM region%d : 0x%llx ~ 0x%llx\n", i,
ch_dram_region[i].start, ch_dram_region[i].end);
}
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int exynos_sysmmu_suspend(struct device *dev)
{
unsigned long flags;
struct sysmmu_drvdata *drvdata = dev_get_drvdata(dev);
spin_lock_irqsave(&drvdata->lock, flags);
drvdata->is_suspended = true;
spin_unlock_irqrestore(&drvdata->lock, flags);
return 0;
}
static int exynos_sysmmu_resume(struct device *dev)
{
unsigned long flags;
struct sysmmu_drvdata *drvdata = dev_get_drvdata(dev);
spin_lock_irqsave(&drvdata->lock, flags);
drvdata->is_suspended = false;
spin_unlock_irqrestore(&drvdata->lock, flags);
return 0;
}
#endif
static const struct dev_pm_ops sysmmu_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(exynos_sysmmu_suspend,
exynos_sysmmu_resume)
};
static const struct of_device_id sysmmu_of_match[] = {
{ .compatible = "samsung,pcie-sysmmu", },
{ },
};
static struct platform_driver exynos_sysmmu_driver __refdata = {
.probe = exynos_sysmmu_probe,
.driver = {
.name = "pcie-sysmmu",
.of_match_table = sysmmu_of_match,
.pm = &sysmmu_pm_ops,
}
};
static int __init pcie_iommu_init(void)
{
phys_addr_t buff_paddr;
int ret;
if (lv2table_kmem_cache)
return 0;
lv2table_kmem_cache = kmem_cache_create("pcie-iommu-lv2table",
LV2TABLE_REFCNT_SZ, LV2TABLE_REFCNT_SZ, 0, NULL);
if (!lv2table_kmem_cache) {
pr_err("%s: Failed to create kmem cache\n", __func__);
return -ENOMEM;
}
gen_buff = kzalloc(LV2_GENPOOL_SZIE, GFP_KERNEL);
if (gen_buff == NULL) {
pr_err("Failed to allocate pool buffer\n");
ret = -ENOMEM;
goto err3;
}
buff_paddr = virt_to_phys(gen_buff);
#ifndef USE_DYNAMIC_MEM_ALLOC
lv2table_pool = gen_pool_create(ilog2(LV2TABLE_REFCNT_SZ), -1);
if (!lv2table_pool) {
pr_err("Failed to allocate lv2table gen pool\n");
ret = -ENOMEM;
goto err2;
}
ret = gen_pool_add(lv2table_pool, (unsigned long)buff_paddr,
LV2_GENPOOL_SZIE, -1);
if (ret) {
ret = -ENOMEM;
goto err1;
}
#endif
ret = platform_driver_probe(&exynos_sysmmu_driver, exynos_sysmmu_probe);
if (ret == 0)
return ret;
err1:
#ifndef USE_DYNAMIC_MEM_ALLOC
gen_pool_destroy(lv2table_pool);
#endif
err2:
kzfree(gen_buff);
err3:
kmem_cache_destroy(lv2table_kmem_cache);
return ret;
}
subsys_initcall(pcie_iommu_init);