diff --git a/drivers/gpu/drm/hisilicon/kirin960/kirin_drm_dss.c b/drivers/gpu/drm/hisilicon/kirin960/kirin_drm_dss.c index 64d0b1979bf5..e99a17270fc0 100644 --- a/drivers/gpu/drm/hisilicon/kirin960/kirin_drm_dss.c +++ b/drivers/gpu/drm/hisilicon/kirin960/kirin_drm_dss.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index 8ee54d71c7eb..b984fffccb6e 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -362,4 +362,22 @@ config MTK_IOMMU_V1 if unsure, say N here. +config HISI_IODOMAIN_API + bool + +config HISI_IOMMU + bool "Hisilicon IOMMU Support" + select IOMMU_API + select HISI_IODOMAIN_API + help + Hisilicon IOMMU Support. + +config HISI_IOMMU_LPAE + bool "Hisilicon IOMMU LPAE Support" + select IOMMU_API + select IODOMAIN_API + select HISI_IOMMU + help + Hisilicon IOMMU Support. + endif # IOMMU_SUPPORT diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 195f7b997d8e..b0d5377af413 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -27,3 +27,5 @@ obj-$(CONFIG_TEGRA_IOMMU_SMMU) += tegra-smmu.o obj-$(CONFIG_EXYNOS_IOMMU) += exynos-iommu.o obj-$(CONFIG_FSL_PAMU) += fsl_pamu.o fsl_pamu_domain.o obj-$(CONFIG_S390_IOMMU) += s390-iommu.o +obj-$(CONFIG_HISI_IODOMAIN_API) += ion-iommu-map.o +obj-$(CONFIG_HISI_IOMMU_LPAE) += hisi_smmu_lpae.o diff --git a/drivers/iommu/hisi_smmu.h b/drivers/iommu/hisi_smmu.h new file mode 100644 index 000000000000..4637244dba6b --- /dev/null +++ b/drivers/iommu/hisi_smmu.h @@ -0,0 +1,178 @@ +#ifndef HISI_SMMU_H +#define HISI_SMMU_H + +/*#define IOMMU_DEBUG*/ +#ifdef IOMMU_DEBUG +#define dbg(format, arg...) printk(KERN_ERR "[iommu]"format, ##arg); +#else +#define dbg(format, arg...) +#endif + +#define SMMU_PHY_PTRS_PER_PTE (256) +/*#define SMMU_PHY_PTRS_PER_PGD (4096)*/ +#define SMMU_PTRS_PER_PGD (4) +#define SMMU_PTRS_PER_PMD (512) +#define SMMU_PTRS_PER_PTE (512) +#define SMMU_PAGE_SHIFT (12) + +#define PAGE_TABLE_ADDR_MASK (UL(0xFFFFFFF) << SMMU_PAGE_SHIFT) + +#define SMMU_PAGE_SIZE BIT(SMMU_PAGE_SHIFT) +#define SMMU_PAGE_MASK (~(SMMU_PAGE_SIZE-1)) + +#define SMMU_PGDIR_SHIFT (30) +#define SMMU_PGDIR_SIZE BIT(SMMU_PGDIR_SHIFT) +#define SMMU_PGDIR_MASK (~(SMMU_PGDIR_SIZE-1)) + +#define SMMU_PMDIR_SHIFT (21) +#define SMMU_PMDIR_SIZE BIT(SMMU_PMDIR_SHIFT) +#define SMMU_PMDIR_MASK (~(SMMU_PMDIR_SIZE-1)) +#define SMMU_PGD_TYPE (BIT(0) | BIT(1)) +#define SMMU_PMD_TYPE (BIT(0) | BIT(1)) +#define SMMU_PTE_TYPE (BIT(0) | BIT(1)) + +#define SMMU_PGD_NS BIT(63) +#define SMMU_PMD_NS BIT(63) +#define SMMU_PTE_NS BIT(5) + +#define SMMU_PTE_PXN BIT(53) /* Privileged XN */ +#define SMMU_PTE_UXN BIT(54) /* User XN */ +#define SMMU_PTE_USER BIT(6) /* AP[1] */ +#define SMMU_PTE_RDONLY BIT(7) /* AP[2] */ +#define SMMU_PTE_SHARED (BIT(8) | BIT(9)) /* SH[1:0], inner shareable */ +#define SMMU_PTE_AF BIT(10) /* Access Flag */ +#define SMMU_PTE_NG BIT(11) /* nG */ +#define SMMU_PTE_ATTRINDX(t) ((t) << 2) +/* + * Memory types available. + * USED BY A7 + */ +#define HISI_MT_NORMAL 0 +#define HISI_MT_NORMAL_CACHE 4 +#define HISI_MT_NORMAL_NC 5 +#define HISI_MT_DEVICE_nGnRE 6 + + +#define SMMU_PAGE_DEFAULT (SMMU_PTE_TYPE | SMMU_PTE_AF | SMMU_PTE_SHARED) + +#define SMMU_PROT_DEVICE_nGnRE (SMMU_PAGE_DEFAULT | SMMU_PTE_PXN | \ + SMMU_PTE_UXN | SMMU_PTE_ATTRINDX(HISI_MT_DEVICE_nGnRE)) +#define SMMU_PROT_NORMAL_CACHE (SMMU_PAGE_DEFAULT | SMMU_PTE_PXN | \ + SMMU_PTE_UXN | SMMU_PTE_ATTRINDX(HISI_MT_NORMAL_CACHE)) +#define SMMU_PROT_NORMAL_NC (SMMU_PAGE_DEFAULT | SMMU_PTE_PXN | \ + SMMU_PTE_UXN | SMMU_PTE_ATTRINDX(HISI_MT_NORMAL_NC)) +#define SMMU_PROT_NORMAL (SMMU_PAGE_DEFAULT | SMMU_PTE_PXN | \ + SMMU_PTE_UXN | SMMU_PTE_ATTRINDX(HISI_MT_NORMAL)) + +#define SMMU_PAGE_READWRITE (SMMU_PAGE_DEFAULT | SMMU_PTE_USER | \ + SMMU_PTE_NG | SMMU_PTE_PXN | SMMU_PTE_UXN) +#define SMMU_PAGE_READONLY (SMMU_PAGE_DEFAULT | SMMU_PTE_USER | \ + SMMU_PTE_RDONLY | SMMU_PTE_NG | SMMU_PTE_PXN | SMMU_PTE_UXN) +#define SMMU_PAGE_READONLY_EXEC (SMMU_PAGE_DEFAULT | SMMU_PTE_USER | \ + SMMU_PTE_NG) + +#define smmu_pte_index(addr) (((addr) >> SMMU_PAGE_SHIFT) & (SMMU_PTRS_PER_PTE - 1)) +#define smmu_pmd_index(addr) (((addr) >> SMMU_PMDIR_SHIFT) & (SMMU_PTRS_PER_PMD - 1)) +#define smmu_pgd_index(addr) (((addr) >> SMMU_PGDIR_SHIFT) & (SMMU_PTRS_PER_PGD - 1)) +#define SMMU_PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE) + +typedef u64 smmu_pgd_t; +typedef u64 smmu_pmd_t; +typedef u64 smmu_pte_t; + +/*smmu device object*/ +struct hisi_smmu_device_lpae { + struct device *dev ; + struct list_head domain_list; + unsigned int ref_count; + spinlock_t lock; + unsigned long va_pgtable_addr; + phys_addr_t smmu_phy_pgtable_addr; + smmu_pgd_t *smmu_pgd; +}; + +struct hisi_map_tile_position_lpae { + struct scatterlist *sg ; + unsigned long offset; +}; + +extern struct hisi_smmu_device_lpae *hisi_smmu_dev; + +static inline unsigned int smmu_pgd_none_lpae(smmu_pgd_t pgd) { + return !(pgd ? pgd : 0); +} + +static inline unsigned int smmu_pmd_none_lpae(smmu_pmd_t pmd) { + return !(pmd ? pmd : 0); +} + +static inline unsigned int smmu_pte_none_lpae(smmu_pte_t pte) { + return !(pte ? pte : 0); +} + +static inline unsigned int pte_is_valid_lpae(smmu_pte_t *ptep) { + return (unsigned int)((*(ptep)&SMMU_PTE_TYPE) ? 1 : 0); +} + +/* Find an entry in the second-level page table.. */ +static inline void *smmu_pmd_page_vaddr_lpae(smmu_pmd_t *pgd) +{ + return phys_to_virt(*pgd & PAGE_TABLE_ADDR_MASK); +} + +/* Find an entry in the third-level page table.. */ +static inline void *smmu_pte_page_vaddr_lpae(smmu_pmd_t *pmd) +{ + return phys_to_virt(*pmd & PAGE_TABLE_ADDR_MASK); +} + + +/*fill the pgd entry, pgd value must be 64bit */ +static inline void smmu_set_pgd_lpae(smmu_pgd_t *pgdp, u64 pgd) +{ + *pgdp = pgd; + dsb(ishst); + isb(); +} + +/*fill the pmd entry, pgd value must be 64bit */ +static inline void smmu_set_pmd_lpae(smmu_pgd_t *pmdp, u64 pmd) +{ + dbg("smmu_set_pmd_lpae: pmd = 0x%lx \n", pmd); + *pmdp = pmd; + dsb(ishst); + isb(); +} + +static inline void smmu_pmd_populate_lpae(smmu_pmd_t *pmdp, pgtable_t ptep, pgdval_t prot) +{ + smmu_set_pmd_lpae(pmdp, (u64)(page_to_phys(ptep) | prot)); +} + +static inline void smmu_pgd_populate_lpae(smmu_pgd_t *pgdp, pgtable_t pmdp, pgdval_t prot) +{ + smmu_set_pgd_lpae(pgdp, (u64)(page_to_phys(pmdp) | prot)); +} + +static inline unsigned long smmu_pgd_addr_end_lpae(unsigned long addr, unsigned long end) +{ + unsigned long boundary = (addr + SMMU_PGDIR_SIZE) & SMMU_PGDIR_MASK; + + return (boundary - 1 < end - 1) ? boundary : end; +} + +static inline unsigned long smmu_pmd_addr_end_lpae(unsigned long addr, unsigned long end) +{ + unsigned long boundary = (addr + SMMU_PMDIR_SIZE) & SMMU_PMDIR_MASK; + + return (boundary - 1 < end - 1) ? boundary : end; +} + +int hisi_smmu_handle_mapping_lpae(struct iommu_domain *domain, + unsigned long iova, phys_addr_t paddr, + size_t size, int prot); + +unsigned int hisi_smmu_handle_unmapping_lpae(struct iommu_domain *domain, + unsigned long iova, size_t size); + +#endif diff --git a/drivers/iommu/hisi_smmu_lpae.c b/drivers/iommu/hisi_smmu_lpae.c new file mode 100644 index 000000000000..0ccd5c9ffeb1 --- /dev/null +++ b/drivers/iommu/hisi_smmu_lpae.c @@ -0,0 +1,849 @@ + +/* + * hisi_smmu_lpae.c -- 3 layer pagetable + * + * Copyright (c) 2014 Huawei Technologies CO., Ltd. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hisi_smmu.h" + +struct hisi_smmu_device_lpae *hisi_smmu_dev; + +/*transfer 64bit pte table pointer to struct page*/ +static pgtable_t smmu_pgd_to_pte_lpae(unsigned int ppte_table) +{ + unsigned long page_table_addr; + + if (!ppte_table) { + dbg("error: the pointer of pte_table is NULL\n"); + return NULL; + } + page_table_addr = (unsigned long)ppte_table; + return phys_to_page(page_table_addr); +} + +/*transfer 64bit pte table pointer to struct page*/ +static pgtable_t smmu_pmd_to_pte_lpae(unsigned long ppte_table) +{ + struct page *table = NULL; + + if (!ppte_table) { + dbg("error: the pointer of pte_table is NULL\n"); + return NULL; + } + table = phys_to_page(ppte_table); + return table; +} + +static int get_domain_data_lpae(struct device_node *np, + struct iommu_domain_data *data) +{ + unsigned long long align; + struct device_node *node = NULL; + int ret = 0; + + data->phy_pgd_base = hisi_smmu_dev->smmu_phy_pgtable_addr; + if (np) { + node = of_find_node_by_name(np, "iommu_info"); + if (!node) { + dbg("find iommu_info node error\n"); + return -ENODEV; + } + ret = of_property_read_u32(node, "start-addr", + &data->iova_start); + if (ret) { + dbg("read iova start address error\n"); + goto read_error; + } + ret = of_property_read_u32(node, "size", &data->iova_size); + if (ret) { + dbg("read iova size error\n"); + goto read_error; + } + ret = of_property_read_u64(node, "iova-align", &align); + if (!ret) + data->iova_align = (unsigned long)align; + else + data->iova_align = SZ_256K; + + pr_err("%s:start_addr 0x%x, size 0x%x align 0x%lx\n", + __func__, data->iova_start, + data->iova_size, data->iova_align); + } + + return 0; + +read_error: + return ret; +} + +static struct iommu_domain +*hisi_smmu_domain_alloc_lpae(unsigned iommu_domain_type) +{ + struct iommu_domain *domain; + + if (iommu_domain_type != IOMMU_DOMAIN_UNMANAGED) + return NULL; + + domain = kzalloc(sizeof(*domain), GFP_KERNEL); + if (!domain) { + pr_err("%s: fail to kzalloc %lu bytes\n", + __func__, sizeof(*domain)); + } + + return domain; +} + + +static void hisi_smmu_flush_pgtable_lpae(void *addr, size_t size) +{ + __flush_dcache_area(addr, size); +} + +static void hisi_smmu_free_ptes_lpae(smmu_pgd_t pmd) +{ + pgtable_t table = smmu_pgd_to_pte_lpae(pmd); + + if (!table) { + dbg("pte table is null\n"); + return; + } + __free_page(table); + smmu_set_pmd_lpae(&pmd, 0); +} + + +static void hisi_smmu_free_pmds_lpae(smmu_pgd_t pgd) +{ + pgtable_t table = smmu_pmd_to_pte_lpae(pgd); + + if (!table) { + dbg("pte table is null\n"); + return; + } + __free_page(table); + smmu_set_pgd_lpae(&pgd, 0); +} + +static void hisi_smmu_free_pgtables_lpae(unsigned long *page_table_addr) +{ + int i, j; + smmu_pgd_t *pgd; + smmu_pmd_t *pmd; + unsigned long flags; + + pgd = (smmu_pgd_t *)page_table_addr; + pmd = (smmu_pmd_t *)page_table_addr; + + spin_lock_irqsave(&hisi_smmu_dev->lock, flags); + for (i = 0; i < SMMU_PTRS_PER_PGD; ++i) { + if ((smmu_pgd_none_lpae(*pgd)) & (smmu_pmd_none_lpae(*pmd))) + continue; + for (j = 0; j < SMMU_PTRS_PER_PMD; ++j) { + hisi_smmu_free_pmds_lpae(*pgd); + pmd++; + } + hisi_smmu_free_ptes_lpae(*pmd); + pgd++; + } + memset((void *)page_table_addr, 0, PAGE_SIZE); + spin_unlock_irqrestore(&hisi_smmu_dev->lock, flags); +} + +static void hisi_smmu_domain_free_lpae(struct iommu_domain *domain) +{ + if (list_empty(&hisi_smmu_dev->domain_list)) + hisi_smmu_free_pgtables_lpae((unsigned long *) + hisi_smmu_dev->va_pgtable_addr); + + kfree(domain); + +} + +static int hisi_smmu_alloc_init_pte_lpae(smmu_pmd_t *ppmd, + unsigned long addr, unsigned long end, + unsigned long pfn, u64 prot, unsigned long *flags) +{ + smmu_pte_t *pte, *start; + pgtable_t table; + u64 pteval = SMMU_PTE_TYPE; + + if (!smmu_pmd_none_lpae(*ppmd)) + goto pte_ready; + + /* Allocate a new set of tables */ + spin_unlock_irqrestore(&hisi_smmu_dev->lock, *flags); + table = alloc_page(GFP_KERNEL | __GFP_ZERO | __GFP_DMA); + spin_lock_irqsave(&hisi_smmu_dev->lock, *flags); + if (!table) { + dbg("%s: alloc page fail\n", __func__); + return -ENOMEM; + } + + if (smmu_pmd_none_lpae(*ppmd)) { + hisi_smmu_flush_pgtable_lpae(page_address(table), + SMMU_PAGE_SIZE); + smmu_pmd_populate_lpae(ppmd, table, SMMU_PMD_TYPE|SMMU_PMD_NS); + hisi_smmu_flush_pgtable_lpae(ppmd, sizeof(*ppmd)); + } else + __free_page(table); + +pte_ready: + if (prot & IOMMU_SEC) + *ppmd &= (~SMMU_PMD_NS); + + start = (smmu_pte_t *)smmu_pte_page_vaddr_lpae(ppmd) + + smmu_pte_index(addr); + pte = start; + if (!prot) { + pteval |= SMMU_PROT_NORMAL; + pteval |= SMMU_PTE_NS; + } else { + if (prot & IOMMU_DEVICE) { + pteval |= SMMU_PROT_DEVICE_nGnRE; + } else { + if (prot & IOMMU_CACHE) + pteval |= SMMU_PROT_NORMAL_CACHE; + else + pteval |= SMMU_PROT_NORMAL_NC; + + if ((prot & IOMMU_READ) && (prot & IOMMU_WRITE)) + pteval |= SMMU_PAGE_READWRITE; + else if ((prot & IOMMU_READ) && !(prot & IOMMU_WRITE)) + pteval |= SMMU_PAGE_READONLY; + else + WARN_ON("you do not set read attribute!"); + + if (prot & IOMMU_EXEC) { + pteval |= SMMU_PAGE_READONLY_EXEC; + pteval &= ~(SMMU_PTE_PXN | SMMU_PTE_UXN); + } + } + if (prot & IOMMU_SEC) + pteval &= (~SMMU_PTE_NS); + else + pteval |= SMMU_PTE_NS; + } + + do { + if (!pte_is_valid_lpae(pte)) + *pte = (u64)(__pfn_to_phys(pfn)|pteval); + else + WARN_ONCE(1, "map to same VA more times!\n"); + pte++; + pfn++; + addr += SMMU_PAGE_SIZE; + } while (addr < end); + + hisi_smmu_flush_pgtable_lpae(start, sizeof(*pte) * (pte - start)); + return 0; +} + +static int hisi_smmu_alloc_init_pmd_lpae(smmu_pgd_t *ppgd, + unsigned long addr, unsigned long end, + unsigned long paddr, int prot, unsigned long *flags) +{ + int ret = 0; + smmu_pmd_t *ppmd, *start; + u64 next; + pgtable_t table; + + if (!smmu_pgd_none_lpae(*ppgd)) + goto pmd_ready; + + /* Allocate a new set of tables */ + spin_unlock_irqrestore(&hisi_smmu_dev->lock, *flags); + table = alloc_page(GFP_KERNEL | __GFP_ZERO | __GFP_DMA); + spin_lock_irqsave(&hisi_smmu_dev->lock, *flags); + if (!table) { + dbg("%s: alloc page fail\n", __func__); + return -ENOMEM; + } + + if (smmu_pgd_none_lpae(*ppgd)) { + hisi_smmu_flush_pgtable_lpae(page_address(table), + SMMU_PAGE_SIZE); + smmu_pgd_populate_lpae(ppgd, table, SMMU_PGD_TYPE|SMMU_PGD_NS); + hisi_smmu_flush_pgtable_lpae(ppgd, sizeof(*ppgd)); + } else + __free_page(table); + +pmd_ready: + if (prot & IOMMU_SEC) + *ppgd &= (~SMMU_PGD_NS); + start = (smmu_pmd_t *)smmu_pmd_page_vaddr_lpae(ppgd) + + smmu_pmd_index(addr); + ppmd = start; + + do { + next = smmu_pmd_addr_end_lpae(addr, end); + ret = hisi_smmu_alloc_init_pte_lpae(ppmd, + addr, next, __phys_to_pfn(paddr), prot, flags); + if (ret) + goto error; + paddr += (next - addr); + addr = next; + } while (ppmd++, addr < end); +error: + return ret; +} + +int hisi_smmu_handle_mapping_lpae(struct iommu_domain *domain, + unsigned long iova, phys_addr_t paddr, + size_t size, int prot) +{ + int ret; + unsigned long end; + unsigned long next; + unsigned long flags; + smmu_pgd_t *pgd = (smmu_pgd_t *)hisi_smmu_dev->va_pgtable_addr; + + if (!pgd) { + dbg("pgd is null\n"); + return -EINVAL; + } + iova = ALIGN(iova, SMMU_PAGE_SIZE); + size = ALIGN(size, SMMU_PAGE_SIZE); + spin_lock_irqsave(&hisi_smmu_dev->lock, flags); + pgd += smmu_pgd_index(iova); + end = iova + size; + do { + next = smmu_pgd_addr_end_lpae(iova, end); + ret = hisi_smmu_alloc_init_pmd_lpae(pgd, + iova, next, paddr, prot, &flags); + if (ret) + goto out_unlock; + paddr += next - iova; + iova = next; + } while (pgd++, iova < end); +out_unlock: + spin_unlock_irqrestore(&hisi_smmu_dev->lock, flags); + return ret; +} + +static int hisi_smmu_map_lpae(struct iommu_domain *domain, + unsigned long iova, + phys_addr_t paddr, size_t size, + int prot) +{ + unsigned long max_iova; + struct iommu_domain_data *data; + + if (!domain) { + dbg("domain is null\n"); + return -ENODEV; + } + data = domain->priv; + max_iova = data->iova_start + data->iova_size; + if (iova < data->iova_start) { + dbg("iova failed: iova = 0x%lx, start = 0x%8x\n", + iova, data->iova_start); + goto error; + } + if ((iova+size) > max_iova) { + dbg("iova out of domain range, iova+size=0x%lx, end=0x%lx\n", + iova+size, max_iova); + goto error; + } + return hisi_smmu_handle_mapping_lpae(domain, iova, paddr, size, prot); +error: + dbg("iova is not in this range\n"); + return -EINVAL; +} + +static unsigned int hisi_smmu_clear_pte_lpae(smmu_pgd_t *pmdp, + unsigned int iova, unsigned int end) +{ + smmu_pte_t *ptep = NULL; + smmu_pte_t *ppte = NULL; + unsigned int size = end - iova; + + ptep = smmu_pte_page_vaddr_lpae(pmdp); + ppte = ptep + smmu_pte_index(iova); + + if (!!size) + memset(ppte, 0x0, (size / SMMU_PAGE_SIZE) * sizeof(*ppte)); + + return size; +} + +static unsigned int hisi_smmu_clear_pmd_lpae(smmu_pgd_t *pgdp, + unsigned int iova, unsigned int end) +{ + smmu_pmd_t *pmdp = NULL; + smmu_pmd_t *ppmd = NULL; + unsigned int next = 0; + unsigned int size = end - iova; + + pmdp = smmu_pmd_page_vaddr_lpae(pgdp); + ppmd = pmdp + smmu_pmd_index(iova); + do { + next = smmu_pmd_addr_end_lpae(iova, end); + hisi_smmu_clear_pte_lpae(ppmd, iova, next); + iova = next; + dbg("%s: iova=0x%lx, end=0x%lx\n", __func__, iova, end); + } while (ppmd++, iova < end); + + return size; +} + +unsigned int hisi_smmu_handle_unmapping_lpae(struct iommu_domain *domain, + unsigned long iova, size_t size) +{ + smmu_pgd_t *pgdp = NULL; + unsigned int end = 0; + unsigned int next = 0; + unsigned int unmap_size = 0; + unsigned long flags; + + iova = SMMU_PAGE_ALIGN(iova); + size = SMMU_PAGE_ALIGN(size); + pgdp = (smmu_pgd_t *)hisi_smmu_dev->va_pgtable_addr; + end = iova + size; + dbg("%s:end=0x%x\n", __func__, end); + pgdp += smmu_pgd_index(iova); + spin_lock_irqsave(&hisi_smmu_dev->lock, flags); + do { + next = smmu_pgd_addr_end_lpae(iova, end); + unmap_size += hisi_smmu_clear_pmd_lpae(pgdp, iova, next); + iova = next; + dbg("%s: pgdp=%p, iova=0x%lx\n", __func__, pgdp, iova); + } while (pgdp++, iova < end); + + spin_unlock_irqrestore(&hisi_smmu_dev->lock, flags); + return unmap_size; +} + +static size_t hisi_smmu_unmap_lpae(struct iommu_domain *domain, + unsigned long iova, size_t size) +{ + unsigned long max_iova; + unsigned int ret; + struct iommu_domain_data *data; + + if (!domain) { + dbg("domain is null\n"); + return -ENODEV; + } + data = domain->priv; + /*caculate the max io virtual address */ + max_iova = data->iova_start + data->iova_size; + /*check the iova */ + if (iova < data->iova_start) + goto error; + if ((iova+size) > max_iova) { + dbg("iova out of domain range, iova+size=0x%lx, end=0x%lx\n", + iova+size, max_iova); + goto error; + } + /*unmapping the range of iova*/ + ret = hisi_smmu_handle_unmapping_lpae(domain, iova, size); + if (ret == size) { + dbg("%s:unmap size:0x%x\n", __func__, (unsigned int)size); + return size; + } else { + return 0; + } +error: + dbg("%s:the range of io address is wrong\n", __func__); + return -EINVAL; +} + +static phys_addr_t hisi_smmu_iova_to_phys_lpae( + struct iommu_domain *domain, dma_addr_t iova) +{ + smmu_pgd_t *pgdp, pgd; + smmu_pmd_t pmd; + smmu_pte_t pte; + + pgdp = (smmu_pgd_t *)hisi_smmu_dev->va_pgtable_addr; + if (!pgdp) + return 0; + + pgd = *(pgdp + smmu_pgd_index(iova)); + if (smmu_pgd_none_lpae(pgd)) + return 0; + + pmd = *((smmu_pmd_t *)smmu_pmd_page_vaddr_lpae(&pgd) + + smmu_pmd_index(iova)); + if (smmu_pmd_none_lpae(pmd)) + return 0; + + pte = *((u64 *)smmu_pte_page_vaddr_lpae(&pmd) + smmu_pte_index(iova)); + if (smmu_pte_none_lpae(pte)) + return 0; + + return __pfn_to_phys(pte_pfn(__pte(pte))) | (iova & ~SMMU_PAGE_MASK); +} + +static int hisi_attach_dev_lpae(struct iommu_domain *domain, struct device *dev) +{ + struct device_node *np = dev->of_node; + int ret = 0; + struct iommu_domain_data *iommu_info = NULL; + + iommu_info = kzalloc(sizeof(struct iommu_domain_data), GFP_KERNEL); + if (!iommu_info) { + dbg("alloc iommu_domain_data fail\n"); + return -EINVAL; + } + list_add(&iommu_info->list, &hisi_smmu_dev->domain_list); + domain->priv = iommu_info; + ret = get_domain_data_lpae(np, domain->priv); + return ret; +} + +static void hisi_detach_dev_lpae(struct iommu_domain *domain, + struct device *dev) +{ + struct iommu_domain_data *data; + + data = (struct iommu_domain_data *)domain->priv; + if (data) { + list_del(&data->list); + domain->priv = NULL; + kfree(data); + } else { + dbg("%s:error! data entry has been delected\n", __func__); + } +} + +static dma_addr_t get_phys_addr_lpae(struct scatterlist *sg) +{ + dma_addr_t dma_addr = sg_dma_address(sg); + + if (!dma_addr) + dma_addr = sg_phys(sg); + return dma_addr; +} + +int iommu_map_tile(struct iommu_domain *domain, unsigned long iova, + struct scatterlist *sg, size_t size, int prot, + struct tile_format *format) +{ + if (unlikely(!(domain->ops->map_tile))) + return -ENODEV; + + BUG_ON(iova & (~PAGE_MASK)); + + return domain->ops->map_tile(domain, iova, sg, size, prot, format); +} + +int iommu_unmap_tile(struct iommu_domain *domain, unsigned long iova, + size_t size) +{ + if (unlikely(!(domain->ops->unmap_tile))) + return -ENODEV; + + BUG_ON(iova & (~PAGE_MASK)); + + return domain->ops->unmap_tile(domain, iova, size); +} + +/* + *iova: the start address for tile mapping + *size: the physical memory size + *sg: the node of scatter list where are the start node of physical memory + *sg_offset:the physical memory offset in the sg node ,where is the start + position of physical memory + *port: the pape property of virtual memory + * this function complete one row mapping. + */ +static size_t hisi_map_tile_row_lpae(struct iommu_domain *domain, unsigned long + iova, size_t size, struct scatterlist *sg, size_t sg_offset, + struct hisi_map_tile_position_lpae *map_position, + unsigned int prot){ + + unsigned long map_size; /*the memory size that will be mapped*/ + unsigned long phys_addr; + unsigned long mapped_size = 0; /*memory size that has been mapped*/ + int ret; + + while (1) { + /* + *get the remain memory,if current sg node is not enough memory, + *we map the remain memory firstly. + */ + map_size = size - mapped_size; + if (map_size > (sg->length - sg_offset)) + map_size = (sg->length - sg_offset); + + /*get the start physical address*/ + phys_addr = (unsigned long)get_phys_addr_lpae(sg) + sg_offset; + ret = hisi_smmu_map_lpae(domain, + iova + mapped_size, phys_addr, map_size, prot); + if (ret) { + dbg("[%s] hisi_smmu_map failed!\n", __func__); + break; + } + /*update mapped memory size*/ + mapped_size += map_size; + /* + * if finished mapping, + * we update the memory offset of current node and + * save the memory position. otherwise we clean the sg_offset + * to zero and get next sg node. + */ + if (mapped_size < size) { + sg_offset = 0; + sg = sg_next(sg); + if (!sg) { + dbg("[%s] phy memory not enough\n", __func__); + break; + } + } else { + sg_offset += map_size; + /*if physcial memory of this node is exhausted, + * we choose next node + */ + if (sg_offset == sg->length) { + sg_offset = 0; + sg = sg_next(sg); + } + break; + } + } + /*save current position*/ + map_position->sg = sg; + map_position->offset = sg_offset; + + return mapped_size; +} + +/* + *domain:the iommu domain for mapping + *iova:the start virtual address + *sg: the scatter list of physical memory + *size:the total size of all virtual memory + *port:the property of page table of virtual memory + *format:the parameter of tile mapping + *this function map physical memory in tile mode + */ +static int hisi_smmu_map_tile_lpae(struct iommu_domain *domain, + unsigned long iova, + struct scatterlist *sg, size_t size, int prot, + struct tile_format *format){ + + unsigned int phys_length; + struct scatterlist *sg_node; + unsigned int row_number, row; + unsigned int size_virt, size_phys; + unsigned int sg_offset; + int ret = size; + unsigned int mapped_size, header_size; + struct hisi_map_tile_position_lpae map_position; + + /* calculate the whole length of phys mem */ + for (phys_length = 0, sg_node = sg; sg_node; sg_node = sg_next(sg_node)) + phys_length += ALIGN(sg_node->length, PAGE_SIZE); + + header_size = format->header_size; + + /* calculate the number of raws*/ + row_number = ((phys_length - header_size) >> PAGE_SHIFT) + / format->phys_page_line; + dbg("phys_length: 0x%x, rows: 0x%x, header_size: 0x%x\n", + phys_length, row_number, header_size); + + /*caculate the need physical memory and virtual memory for one row*/ + size_phys = (format->phys_page_line * PAGE_SIZE); + size_virt = (format->virt_page_line * PAGE_SIZE); + + sg_offset = 0; + sg_node = sg; + + /*set start position*/ + map_position.sg = sg; + map_position.offset = 0; + + /*map header*/ + if (header_size) { + mapped_size = hisi_map_tile_row_lpae(domain, iova, + header_size, sg_node, + sg_offset, &map_position, + prot); + if (mapped_size != header_size) { + WARN(1, "map head fail\n"); + ret = -EINVAL; + goto error; + } + iova += ALIGN(header_size, size_virt); + } + /* map row by row */ + for (row = 0; row < row_number; row++) { + /* get physical memory position */ + if (map_position.sg) { + sg_node = map_position.sg; + sg_offset = map_position.offset; + } else { + dbg("[%s]:physical memory is not enough\n", __func__); + break; + } + /* map one row*/ + mapped_size = hisi_map_tile_row_lpae(domain, + iova + (size_virt * row), + size_phys, sg_node, sg_offset, + &map_position, prot); + if (mapped_size != size_phys) { + WARN(1, "hisi_map_tile_row failed!\n"); + ret = -EINVAL; + break; + } + }; +error: + return ret; +} + +static size_t hisi_smmu_unmap_tile_lpae(struct iommu_domain *domain, + unsigned long iova, size_t size) +{ + return hisi_smmu_unmap_lpae(domain, iova, size); +} + +size_t hisi_iommu_map_sg_lpae(struct iommu_domain *domain, unsigned long iova, + struct scatterlist *sg, unsigned int nents, int prot) +{ + struct scatterlist *s; + size_t mapped = 0; + unsigned int i, min_pagesz; + int ret; + + if (domain->ops->pgsize_bitmap == 0UL) + return 0; + + min_pagesz = (unsigned int)1 << __ffs(domain->ops->pgsize_bitmap); + + for_each_sg(sg, s, nents, i) { + phys_addr_t phys = page_to_phys(sg_page(s)) + s->offset; + + /* + * We are mapping on IOMMU page boundaries, so offset within + * the page must be 0. However, the IOMMU may support pages + * smaller than PAGE_SIZE, so s->offset may still represent + * an offset of that boundary within the CPU page. + */ + if (!IS_ALIGNED(s->offset, min_pagesz)) + goto out_err; + + ret = hisi_smmu_map_lpae(domain, iova + mapped, phys, + (size_t)s->length, prot); + if (ret) + goto out_err; + + mapped += s->length; + } + + return mapped; + +out_err: + /* undo mappings already done */ + hisi_smmu_unmap_lpae(domain, iova, mapped); + + return 0; +} + +static struct iommu_ops hisi_smmu_ops = { + .domain_alloc = hisi_smmu_domain_alloc_lpae, + .domain_free = hisi_smmu_domain_free_lpae, + .map = hisi_smmu_map_lpae, + .unmap = hisi_smmu_unmap_lpae, + .map_sg = hisi_iommu_map_sg_lpae, + .attach_dev = hisi_attach_dev_lpae, + .detach_dev = hisi_detach_dev_lpae, + .iova_to_phys = hisi_smmu_iova_to_phys_lpae, + .pgsize_bitmap = SMMU_PAGE_SIZE, + .map_tile = hisi_smmu_map_tile_lpae, + .unmap_tile = hisi_smmu_unmap_tile_lpae, +}; + +static int hisi_smmu_probe_lpae(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + dbg("enter %s\n", __func__); + hisi_smmu_dev = devm_kzalloc(dev, + sizeof(struct hisi_smmu_device_lpae), GFP_KERNEL); + + hisi_smmu_dev->smmu_pgd = devm_kzalloc(dev, SZ_64, GFP_KERNEL | __GFP_DMA); + if (!hisi_smmu_dev) + goto smmu_device_error; + hisi_smmu_dev->dev = dev; + INIT_LIST_HEAD(&hisi_smmu_dev->domain_list); + spin_lock_init(&hisi_smmu_dev->lock); + + hisi_smmu_dev->smmu_pgd = (smmu_pgd_t *)(ALIGN((unsigned long)(hisi_smmu_dev->smmu_pgd), SZ_32)); + + hisi_smmu_dev->smmu_phy_pgtable_addr = + virt_to_phys(hisi_smmu_dev->smmu_pgd); + printk(KERN_ERR "%s, smmu_phy_pgtable_addr is = %llx\n", __func__, hisi_smmu_dev->smmu_phy_pgtable_addr); + + hisi_smmu_dev->va_pgtable_addr = (unsigned long)(hisi_smmu_dev->smmu_pgd); + bus_set_iommu(&platform_bus_type, &hisi_smmu_ops); + return 0; + +smmu_device_error: + return -ENOMEM; +} + +static int hisi_smmu_remove_lpae(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id hisi_smmu_of_match_lpae[] = { + { .compatible = "hisi,hisi-smmu-lpae"}, + { }, +}; +MODULE_DEVICE_TABLE(of, hisi_smmu_of_match_lpae); + +static struct platform_driver hisi_smmu_driver_lpae = { + .driver = { + .owner = THIS_MODULE, + .name = "hisi-smmu-lpae", + .of_match_table = of_match_ptr(hisi_smmu_of_match_lpae), + }, + .probe = hisi_smmu_probe_lpae, + .remove = hisi_smmu_remove_lpae, +}; + +static int __init hisi_smmu_init_lpae(void) +{ + int ret = 0; + + ret = platform_driver_register(&hisi_smmu_driver_lpae); + return ret; +} + +static void __exit hisi_smmu_exit_lpae(void) +{ + return platform_driver_unregister(&hisi_smmu_driver_lpae); +} + +subsys_initcall(hisi_smmu_init_lpae); +module_exit(hisi_smmu_exit_lpae); + +MODULE_DESCRIPTION("IOMMU API for HI3660 architected SMMU implementations"); +MODULE_AUTHOR("huawei hisilicon company"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iommu/hisilicon/Kconfig b/drivers/iommu/hisilicon/Kconfig new file mode 100644 index 000000000000..e760c7e31d6e --- /dev/null +++ b/drivers/iommu/hisilicon/Kconfig @@ -0,0 +1,40 @@ +# Hisilicon IOMMU support + +config HISI_IODOMAIN_API + bool + +config HISI_IOMMU + bool "Hisilicon IOMMU Support" + select IOMMU_API + select HISI_IODOMAIN_API + help + Hisilicon IOMMU Support. + +config HISI_IOMMU_LPAE + bool "Hisilicon IOMMU LPAE Support" + select IOMMU_API + depends on HISI_IOMMU + help + Hisilicon IOMMU Support. + +config HISI_IOMMU_LEGACY + bool "Hisilicon IOMMU SECOND LEVEL PAGE TABLE Support" + select IOMMU_API + depends on HISI_IOMMU && !HISI_IOMMU_LPAE + help + Hisilicon IOMMU Support. + +config HISI_IOMMU_COMPACT + bool "Hisilicon IOMMU compact linux4.1" + help + On linux4.1, IOMMU framework has some change point, + you need compact it.If you build on linux4.1 and need + hisilicon iommu,you should select this 'y'. + Then, you should select this 'n'. + +config HISI_IOMMU_TEST + bool "Hisilicon IOMMU TEST Support" + depends on HISI_IOMMU_LPAE + select IOMMU_API + help + Hisilicon IOMMU Support. diff --git a/drivers/iommu/hisilicon/Makefile b/drivers/iommu/hisilicon/Makefile new file mode 100644 index 000000000000..3971520fdc91 --- /dev/null +++ b/drivers/iommu/hisilicon/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_HISI_IOMMU_LEGACY) += hisi_smmu.o +obj-$(CONFIG_HISI_IODOMAIN_API) += ion-iommu-map.o +obj-$(CONFIG_HISI_IOMMU_LPAE) += hisi_smmu_lpae.o +obj-$(CONFIG_HISI_IOMMU_TEST) += hisi_smmu_test.o +obj-$(CONFIG_HISI_IOMMU_TEST) += hisi_smmu_unittest.o diff --git a/drivers/iommu/hisilicon/hisi_smmu_test.c b/drivers/iommu/hisilicon/hisi_smmu_test.c new file mode 100644 index 000000000000..8ed884c64c52 --- /dev/null +++ b/drivers/iommu/hisilicon/hisi_smmu_test.c @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2013-2013 ... + * ... + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*#define IOMMU_DEBUG*/ +#ifdef IOMMU_DEBUG +#define D(format, arg...) \ + do {\ + printk(KERN_ERR "[iommutest] " format, ##arg);\ + } while (0) +#else +#define D(format, arg...) +#endif +#undef IOMMU_DEBUG + + +static struct sg_table *table; + +enum { + IOMMU_TEST, + ION_TEST, + TILE_TEST +}; + +struct smmu_tester{ + struct ion_handle *ion_handle; + struct ion_client *ion_client; + struct iommu_domain *domain; +}; + +struct iommu_page_info { + struct page *page; + unsigned int order; + struct list_head list; +}; + +static struct iommu_domain_data *info; +static struct sg_table *table; +struct smmu_tester *smmu_tester; + +/* + *iova:the start io virtual address. + *size:the size of io virtual memory. + *this function unmap a section io virtual memory + */ +static int iommu_unmap_test(unsigned int iova, unsigned int size) +{ + int ret; + if (!smmu_tester->domain) { + D("domain is null"); + return -EINVAL; + } + /* iommu unmap */ + ret = iommu_unmap(smmu_tester->domain, iova, size); + if (!ret) { + D("hisi iommu unmap domain failed!\n"); + } + return ret; +} + +/*this function do iommu map*/ +ssize_t iommu_map_test(unsigned int start , unsigned int size) +{ + unsigned int iova_start = 0; + unsigned int map_size = 0; + unsigned int i; + struct scatterlist *sg = NULL ; + ktime_t time_start, time_end; + unsigned long diff; + int ret = 0; + + /*get parameter from buffer*/ + iova_start = start; + map_size = size; + + D("%s: iova_start: 0x%x, size: 0x%x ", __func__, iova_start, map_size); + time_start = ktime_get(); + + if (smmu_tester->domain && table) { + for_each_sg(table->sgl, sg, table->nents, i){ + ret = iommu_map(smmu_tester->domain, iova_start, page_to_phys(sg_page(sg)), sg->length, IOMMU_READ|IOMMU_WRITE|IOMMU_CACHE); + if (ret) { + D("failed to map devmem: %d\n",ret); + goto out; + } + iova_start += sg->length; + } + } + + time_end = ktime_get(); + diff = ktime_to_us(ktime_sub(time_end,time_start)); + D("%s: mapping time is 0x%lx \n", __func__, diff); + return size; + +out: + return -ENOSPC; +} + +/*get physical address according to io vritual address*/ +ssize_t iova_to_phy(void) +{ + unsigned int iova_addr = 0; + unsigned int phys_addr; + + phys_addr = iommu_iova_to_phys(smmu_tester->domain, iova_addr); + D("iova_addr=0x%x and phys_addr=0x%x\n", iova_addr, phys_addr); + return 0; +} + +/*free physical memory*/ +static int free_memory(void) +{ + int i; + struct scatterlist *sg = NULL; + unsigned int mem_size = 0; + if (table) { + for_each_sg(table->sgl, sg, table->nents, i) { + __free_pages(sg_page(sg), get_order(sg->length)); + mem_size += sg->length; + } + D("%s:free total memory 0x%x \n", __func__, mem_size); + sg_free_table(table); + kfree(table); + } + table = NULL; + return 0; +} + +/* + *this function allocate physical memory, + *and make them to scatter lista. + *table is global . + */ +static struct iommu_page_info *create_node(void) +{ + struct iommu_page_info *info = NULL; + struct page *page = NULL ; + info = kmalloc(sizeof(struct iommu_page_info), GFP_KERNEL); + if (!info) { + D("%s: kmalloc info failed!\n", __func__); + return NULL; + } + page = alloc_pages(GFP_KERNEL, 1); + /*alloc 8kb each + * time*/ + if (!page) { + D("alloc page error \n"); + kfree(info); + return NULL; + } + info->page = page; + info->order = 0; + INIT_LIST_HEAD(&info->list); + return info; +} + +static int alloc_memory (unsigned int size) +{ + int map_size = 0; + unsigned int sum = 0; + struct list_head pages; + struct iommu_page_info *info, *tmp_info; + unsigned int i = 0, ret = 0; + struct scatterlist *sg = NULL; + + INIT_LIST_HEAD(&pages); + map_size = size; + + if (map_size < 0) + return -EINVAL; + D("%s: map_size=0x%x \n", __func__, map_size); + do { + info = create_node(); + if (!info) + goto error; + list_add_tail(&info->list, &pages); + sum += (1 << info->order) *PAGE_SIZE; + i++; + } while (sum < map_size); + + table = kzalloc(sizeof(struct sg_table), GFP_KERNEL); + if (!table) { + goto error; + } + + ret = sg_alloc_table(table,i, GFP_KERNEL); + if (ret) { + kfree(table); + goto error; + } + sg = table->sgl; + list_for_each_entry_safe(info, tmp_info, &pages, list) + { + struct page *page = info->page; + sg_set_page(sg, page, (1 << info->order)*PAGE_SIZE, 0); + sg = sg_next(sg); + list_del(&info->list); + kfree(info); + } + D("sglist is ok \n"); + return map_size; +error: + list_for_each_entry_safe(info, tmp_info, &pages, list) + { + list_del(&info->list); + kfree(info); + } + return 0; +} +/* + *test mapping address from ion device + */ +int test_smmu_ion_tile_map(unsigned int global_map_start, unsigned int global_map_size) +{ + int ret; + struct iommu_map_format format = {0}; + format.iova_size = global_map_size; + format.phys_page_line = 60; + format.virt_page_line = 64; + format.is_tile = 0x01; + format.prot = 0xff; + + smmu_tester->ion_handle = ion_alloc(smmu_tester->ion_client, global_map_size, SZ_4K, ION_HEAP(ION_SYSTEM_HEAP_ID),0x0); + if (IS_ERR(smmu_tester->ion_handle)) { + D("alloc is fail\n"); + return 0; + } + + ret = ion_map_iommu(smmu_tester->ion_client, smmu_tester->ion_handle, &format); + if (ret) + { + D("ion map iommu is failed\n"); + return 0; + } + D("%s end\n", __func__); + return format.iova_start; +} + +/* + * test unmapping address from ion device + */ +static int test_smmu_ion_tile_unmap(void) +{ + printk(KERN_ERR"%s start\n", __func__); + ion_unmap_iommu(smmu_tester->ion_client, smmu_tester->ion_handle); + return 0; +} + +int test_smmu_ion_map(unsigned int global_map_start, unsigned int global_map_size) +{ + int ret; + unsigned int size = global_map_size; + struct iommu_map_format format = {0}; + unsigned int align = SZ_4K; + format.iova_size = size; + format.prot = 0xff; + smmu_tester->ion_handle = ion_alloc(smmu_tester->ion_client, size,align, ION_HEAP(ION_SYSTEM_HEAP_ID), 0x0); + if (IS_ERR(smmu_tester->ion_handle)) { + pr_err("alloc is fail\n"); + return 0; + } + ret = ion_map_iommu(smmu_tester->ion_client, smmu_tester->ion_handle, &format); + if (ret) { + pr_err("ion_map_iommu is failed\n"); + return 0; + } + return format.iova_start; +} + +int test_smmu_ion_unmap(void) +{ + D("%s start\n", __func__); + ion_unmap_iommu(smmu_tester->ion_client, smmu_tester->ion_handle); + return 1; +} + +extern void smmu_print_pgtable(void); +extern void set_smmu_param(unsigned int start, unsigned int size); +ssize_t smmu_test_main(int type, unsigned int global_map_size, unsigned int global_map_start) +{ + unsigned int ret = 0; + + ret = alloc_memory(global_map_size); + if (!ret) { + D("ret = %d\n", ret); + return -ENOMEM; + } + switch (type) { + case IOMMU_TEST: + iommu_map_test(global_map_start, global_map_size); + set_smmu_param(global_map_start, global_map_size); + smmu_print_pgtable(); + iommu_unmap_test(global_map_start, global_map_size); + smmu_print_pgtable(); + break; + case ION_TEST: + ret = test_smmu_ion_map(global_map_start, global_map_size); + set_smmu_param(ret, global_map_size); + smmu_print_pgtable(); + test_smmu_ion_unmap(); + smmu_print_pgtable(); + break; + case TILE_TEST: + ret = test_smmu_ion_tile_map(global_map_start, global_map_size); + set_smmu_param(ret, global_map_size); + smmu_print_pgtable(); + test_smmu_ion_tile_unmap(); + smmu_print_pgtable(); + break; + default: + return -EINVAL; + break; + }; + free_memory(); + return 0; +} + +static int hisi_iommutest_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + smmu_tester = devm_kzalloc(&pdev->dev, sizeof(struct smmu_tester), GFP_KERNEL); + if (!smmu_tester) { + D("in %s devm_kzalloc is failed\n", __func__); + return -ENOMEM; + } + + smmu_tester->ion_client = hisi_ion_client_create("smmu_test"); + if (IS_ERR(smmu_tester->ion_client)) { + D("hisi ion client create failed \n"); + return -ENODEV; + } + smmu_tester->domain = iommu_domain_alloc(dev->bus); + if (!smmu_tester->domain) { + D("create domain fail \n"); + return -ENOMEM; + } else { + iommu_attach_device(smmu_tester->domain, dev); + info = (struct iommu_domain_data *)smmu_tester->domain->priv; + D("%s,iova_start=0x%lx,iova_size=0x%lx \n", __func__, + info->iova_start, + info->iova_size); + } + return 0; +} + + +static struct of_device_id hisi_smmu_of_table[] = { + { .compatible = "hisi,hisi-smmu-tester"}, + { }, +}; +MODULE_DEVICE_TABLE(of, hisi_smmu_of_table); + +static struct platform_driver hisi_iommutest_drv = { + .driver = { + .owner = THIS_MODULE, + .name = "hisi-smmu-tester", + .of_match_table = of_match_ptr(hisi_smmu_of_table), + }, + .probe = hisi_iommutest_probe, +}; + +/*the inital function for iommu test module*/ +static int __init init_iommu_test(void) +{ + int err = -EBUSY; + err = platform_driver_register(&hisi_iommutest_drv); + if (err) + D("register device error \n"); + + return 0; + +} +module_init(init_iommu_test); + diff --git a/drivers/iommu/ion-iommu-map.c b/drivers/iommu/ion-iommu-map.c new file mode 100644 index 000000000000..52aa9a7bf699 --- /dev/null +++ b/drivers/iommu/ion-iommu-map.c @@ -0,0 +1,365 @@ +/* + * Copyright (C) 20013-2013 hisilicon. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef IOMMU_DEBUG +#define dbg(format, arg...) \ + pr_info("[hisi_iommu_domain]"format, ##arg) +#else +#define dbg(format, arg...) +#endif + +struct map_result { + unsigned long iova_start; + unsigned long iova_size; + unsigned long iommu_ptb_base; + unsigned long iommu_iova_base; + unsigned long is_tile; +}; + +struct iommu_domain_data *domain_info; +static struct hisi_iommu_domain *m_hisi_domain; +DEFINE_MUTEX(iova_pool_mutex); + +static unsigned long hisi_alloc_iova(struct gen_pool *pool, + unsigned long size, unsigned long align) +{ + unsigned long iova = 0; + + mutex_lock(&iova_pool_mutex); + + iova = gen_pool_alloc(pool, size); + if (!iova) { + mutex_unlock(&iova_pool_mutex); + pr_err("hisi iommu gen_pool_alloc failed! size = %lu\n", size); + return 0; + } + + if (align > (1 << pool->min_alloc_order)) + WARN(1, "hisi iommu domain cant align to 0x%lx\n", align); + + mutex_unlock(&iova_pool_mutex); + return iova; +} + +static void hisi_free_iova(struct gen_pool *pool, + unsigned long iova, size_t size) +{ + mutex_lock(&iova_pool_mutex); + gen_pool_free(pool, iova, size); + + mutex_unlock(&iova_pool_mutex); +} + +unsigned long hisi_iommu_alloc_iova(size_t size, unsigned long align) +{ + struct hisi_iommu_domain *hisi_domain = m_hisi_domain; + + return hisi_alloc_iova(hisi_domain->iova_pool, size, align); +} +EXPORT_SYMBOL_GPL(hisi_iommu_alloc_iova); + +void hisi_iommu_free_iova(unsigned long iova, size_t size) +{ + int ret; + struct hisi_iommu_domain *hisi_domain = m_hisi_domain; + + ret = addr_in_gen_pool(hisi_domain->iova_pool, iova, size); + if(!ret) { + pr_err("%s:illegal para!!iova = %lx, size = %lx\n", + __func__, iova, size); + } + hisi_free_iova(hisi_domain->iova_pool, iova, size); +} +EXPORT_SYMBOL_GPL(hisi_iommu_free_iova); + +static struct gen_pool *iova_pool_setup(unsigned long start, + unsigned long size, unsigned long align) +{ + struct gen_pool *pool = NULL; + int ret = 0; + + pool = gen_pool_create(order_base_2(align), -1);/*lint !e666 */ + if (!pool) { + pr_err("Create gen pool failed!\n"); + return NULL; + } + /* iova start should not be 0, because return + 0 when alloc iova is considered as error */ + if (!start) + WARN(1, "iova start should not be 0!\n"); + + ret = gen_pool_add(pool, start, size, -1); + if (ret) { + pr_err("Gen pool add failed!\n"); + gen_pool_destroy(pool); + return NULL; + } + + return pool; +} + + +static void iova_pool_destroy(struct gen_pool *pool) +{ + gen_pool_destroy(pool); +} + +static int do_iommu_domain_map(struct hisi_iommu_domain *hisi_domain, + struct scatterlist *sgl, struct iommu_map_format *format, + struct map_result *result) +{ + int ret; + unsigned long phys_len, iova_size; + unsigned long iova_start; + + struct gen_pool *pool; + struct iommu_domain *domain; + struct scatterlist *sg; + struct tile_format fmt; + /* calculate whole phys mem length */ + for (phys_len = 0, sg = sgl; sg; sg = sg_next(sg)) + phys_len += (unsigned long)ALIGN(sg->length, PAGE_SIZE); + + /* get io virtual address size */ + if (format->is_tile) { + unsigned long lines; + unsigned long body_size; + + body_size = phys_len - format->header_size; + lines = body_size / (format->phys_page_line * PAGE_SIZE); + + /*header need more lines virtual space*/ + if (format->header_size) { + unsigned long header_size; + + header_size = ALIGN(format->header_size, + format->virt_page_line * PAGE_SIZE); + lines += header_size + / (format->virt_page_line * PAGE_SIZE); + } + + iova_size = lines * format->virt_page_line * PAGE_SIZE; + } else { + iova_size = phys_len; + } + + /* alloc iova */ + pool = hisi_domain->iova_pool; + domain = hisi_domain->domain; + iova_start = hisi_alloc_iova(pool, iova_size, hisi_domain->range.align); + if (!iova_start) { + pr_err("[%s]hisi_alloc_iova alloc size 0x%lx failed!" + "hisi ion pool avail 0x%lx\n", + __func__, iova_size, gen_pool_avail(pool)); + return -EINVAL; + } + + if (0x100000000 < (iova_start + iova_size)) { + pr_err("hisi iommu can not deal with iova 0x%lx size 0x%lx\n", + iova_start, iova_size); + } + + /* do map */ + if (format->is_tile) { + fmt.is_tile = format->is_tile; + fmt.phys_page_line = format->phys_page_line; + fmt.virt_page_line = format->virt_page_line; + fmt.header_size = format->header_size; + ret = iommu_map_tile(domain, iova_start, + sgl, iova_size, 0, &fmt); + } else { + ret = iommu_map_sg(domain, iova_start, sgl, + sg_nents(sgl), format->prot); + } + + if (ret != iova_size) { + pr_err("[%s]map failed!iova_start = %lx, iova_size = %lx\n", + __func__, iova_start, iova_size); + hisi_free_iova(pool, iova_start, iova_size); + return ret; + } + + /* out put result */ + result->iova_start = iova_start; + result->iova_size = iova_size; + + return 0; +} + +int hisi_iommu_map_domain(struct scatterlist *sgl, + struct iommu_map_format *format) +{ + int ret = 0; + struct map_result result; + struct hisi_iommu_domain *hisi_domain; + + hisi_domain = m_hisi_domain; + + memset(&result, 0, sizeof(result)); + + ret = do_iommu_domain_map(hisi_domain, sgl, format, &result); + if (ret) { + dbg("alloc iova fail\n"); + return ret; + } + format->iova_start = result.iova_start; + format->iova_size = result.iova_size; + + /* get value which write into iommu register */ + return ret; +} +EXPORT_SYMBOL_GPL(hisi_iommu_map_domain); + +static int do_iommu_domain_unmap(struct map_result *result) +{ + int ret; + unsigned long unmaped_size; + struct hisi_iommu_domain *hisi_domain = m_hisi_domain; + struct gen_pool *pool = hisi_domain->iova_pool; + + /* never unmap a zero length address space */ + if (!result->iova_size) { + pr_err("[%s]unmap failed! iova_start=%lx, iova_size=%lu\n", + __func__, result->iova_start, result->iova_size); + return -EINVAL; + } + + /* unmap tile equals to unmpa range */ + if (result->is_tile) { + unmaped_size = iommu_unmap_tile(hisi_domain->domain, + result->iova_start, result->iova_size); + } else { + unmaped_size = iommu_unmap(hisi_domain->domain, + result->iova_start, result->iova_size); + } + + if (unmaped_size != result->iova_size) { + dbg("[%s]unmap failed!\n", __func__); + return -EINVAL; + } + /* free iova */ + if (pool) { + ret = addr_in_gen_pool(pool, result->iova_start, + result->iova_size); + if(!ret) { + pr_err("[%s]illegal para!!iova = %lx, size = %lx\n", + __func__, result->iova_start, result->iova_size); + } + hisi_free_iova(pool, result->iova_start, result->iova_size); + } + return 0; +} + +#ifdef CONFIG_ARM64_64K_PAGES +#error hisi iommu can not deal with 64k pages! +#endif + +/** + * Called by ION + */ +int hisi_iommu_unmap_domain(struct iommu_map_format *format) +{ + struct map_result result; + + result.iova_start = format->iova_start; + result.iova_size = format->iova_size; + result.is_tile = format->is_tile; + + return do_iommu_domain_unmap(&result); +} +EXPORT_SYMBOL_GPL(hisi_iommu_unmap_domain); + +/*only used to test*/ +phys_addr_t hisi_iommu_domain_iova_to_phys(unsigned long iova) +{ + struct iommu_domain *domain; + domain = m_hisi_domain->domain; + return iommu_iova_to_phys(domain, iova); +} +EXPORT_SYMBOL_GPL(hisi_iommu_domain_iova_to_phys); + +int hisi_ion_enable_iommu(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct hisi_iommu_domain *hisi_domain; + + pr_info("in %s start\n", __func__); + hisi_domain = kzalloc(sizeof(*hisi_domain), GFP_KERNEL); + if (!hisi_domain) { + dbg("alloc hisi_domain object fail\n"); + return -ENOMEM; + } + + if (!iommu_present(dev->bus)) { + dbg("iommu not found\n"); + kfree(hisi_domain); + return 0; + } + + /* create iommu domain */ + hisi_domain->domain = iommu_domain_alloc(dev->bus); + if (!hisi_domain->domain) { + ret = -EINVAL; + goto error; + } + iommu_attach_device(hisi_domain->domain, dev); + domain_info = (struct iommu_domain_data *)hisi_domain->domain->priv; + + /** + * Current align is 256K + */ + hisi_domain->iova_pool = iova_pool_setup(domain_info->iova_start, + domain_info->iova_size, domain_info->iova_align); + if (!hisi_domain->iova_pool) { + ret = -EINVAL; + goto error; + } + /* this is a global pointer */ + m_hisi_domain = hisi_domain; + + dbg("in %s end\n", __func__); + return 0; + +error: + WARN(1, "hisi_iommu_domain_init failed!\n"); + if (hisi_domain->iova_pool) + iova_pool_destroy(hisi_domain->iova_pool); + if (hisi_domain->domain) + iommu_domain_free(hisi_domain->domain); + kfree(hisi_domain); + + return ret; +} +EXPORT_SYMBOL(hisi_ion_enable_iommu); diff --git a/include/linux/hisi/hisi-iommu.h b/include/linux/hisi/hisi-iommu.h new file mode 100644 index 000000000000..00dd5e97db59 --- /dev/null +++ b/include/linux/hisi/hisi-iommu.h @@ -0,0 +1,13 @@ +#ifndef _HI36XX_SMMU_H +#define _HI36XX_SMMU_H + +#include +struct iommu_domain_data { + unsigned int iova_start; + unsigned int iova_size; + phys_addr_t phy_pgd_base; + unsigned long iova_align; + struct list_head list; +}; + +#endif diff --git a/include/linux/hisi/hisi_ion.h b/include/linux/hisi/hisi_ion.h new file mode 100644 index 000000000000..0d7be75f795f --- /dev/null +++ b/include/linux/hisi/hisi_ion.h @@ -0,0 +1,178 @@ +/* + * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _LINUX_HISI_ION_H +#define _LINUX_HISI_ION_H + +#include +#include + +/** + * These are the only ids that should be used for Ion heap ids. + * The ids listed are the order in which allocation will be attempted + * if specified. Don't swap the order of heap ids unless you know what + * you are doing! + * Id's are spaced by purpose to allow new Id's to be inserted in-between (for + * possible fallbacks) + */ + +enum ion_heap_ids { + INVALID_HEAP_ID = -1, + ION_SYSTEM_HEAP_ID = 0, + ION_SYSTEM_CONTIG_HEAP_ID = 1, + ION_GRALLOC_HEAP_ID = 2, + ION_DMA_HEAP_ID = 3, + ION_DMA_POOL_HEAP_ID = 4, + ION_CPU_DRAW_HEAP_ID = 5, + ION_CAMERA_HEAP_ID = 6, + ION_OVERLAY_HEAP_ID = 7, + ION_VCODEC_HEAP_ID = 8, + ION_ISP_HEAP_ID = 9, + ION_FB_HEAP_ID = 10, + ION_VPU_HEAP_ID = 11, + ION_JPU_HEAP_ID = 12, + HISI_ION_HEAP_IOMMU_ID = 13, + ION_MISC_HEAP_ID = 14, + ION_DRM_GRALLOC_HEAP_ID=15, + ION_DRM_VCODEC_HEAP_ID =16, + ION_TUI_HEAP_ID=17, + ION_IRIS_HEAP_ID=18, + ION_RESERV2_ID=19, + ION_DRM_HEAP_ID=20, + ION_HEAP_ID_RESERVED = 31, /* Bit reserved */ +}; + + +/** + * Macro should be used with ion_heap_ids defined above. + */ +#define ION_HEAP(bit) (1 << (bit)) +#define ION_8K_ALIGN(len) ALIGN(len, SZ_8K) +#define IOMMU_PAGE_SIZE SZ_8K + +#define ION_VMALLOC_HEAP_NAME "vmalloc" +#define ION_KMALLOC_HEAP_NAME "kmalloc" +#define ION_GRALLOC_HEAP_NAME "gralloc" + + +#define ION_SET_CACHED(__cache) (__cache | ION_FLAG_CACHED) +#define ION_SET_UNCACHED(__cache) (__cache & ~ION_FLAG_CACHED) + +#define ION_IS_CACHED(__flags) ((__flags) & ION_FLAG_CACHED) + +//struct used for get phys addr of contig heap +struct ion_phys_data { + int fd_buffer; + unsigned int size; + union { + unsigned int phys; + unsigned int phys_l; + }; + unsigned int phys_h; +}; + +struct ion_flag_data { + int shared_fd; + int flags; +}; + +struct ion_smart_pool_info_data { + int water_mark; +}; + +#define HISI_ION_NAME_LEN 16 + +struct ion_heap_info_data{ + char name[HISI_ION_NAME_LEN]; + phys_addr_t heap_phy; + unsigned int heap_size; +}; +struct ion_kern_va_data { + int handle_id; + unsigned int kern_va_h; + unsigned int kern_va_l; +}; +struct ion_issupport_iommu_data{ + int is_support_iommu; +}; + +struct ion_flush_data { + int fd; + void *vaddr; + unsigned int offset; + unsigned int length; +}; + + +//user command add for additional use +enum ION_HISI_CUSTOM_CMD { + ION_HISI_CUSTOM_PHYS, + ION_HISI_CLEAN_CACHES, + ION_HISI_INV_CACHES, + ION_HISI_CLEAN_INV_CACHES, + ION_HISI_CUSTOM_GET_KERN_VA, + ION_HISI_CUSTOM_FREE_KERN_VA, + ION_HISI_CUSTOM_ISSUPPORT_IOMMU, + ION_HISI_CUSTOM_GET_MEDIA_HEAP_MODE, + ION_HISI_CUSTOM_SET_FLAG, + ION_HISI_CUSTOM_SET_SMART_POOL_INFO, +}; + +enum ION_HISI_HEAP_MODE { + ION_CARVEROUT_MODE=0, + ION_IOMMU_MODE=1, +}; + +#define TINY_SYSTEM 0x0 /* tiny version system for chip test*/ +#define FULL_SYSTEM 0x1 /* full version system */ +/** + * hisi_ion_client_create() - create iommu mapping for the given handle + * @heap_mask: ion heap type mask + * @name: the client name + * @return: the client handle + * + * This function should called by high-level user in kernel. Before users + * can access a buffer, they should get a client via calling this function. + */ +struct ion_client * +hisi_ion_client_create(const char *name); +int hisi_ion_get_heap_info(unsigned int id,struct ion_heap_info_data* data); +int hisi_ion_get_media_mode(void); +unsigned long long get_system_type(void); +struct ion_device * get_ion_device(void); +#define ION_IOC_HISI_MAGIC 'H' +/** + *DOC: ION_IOC_FLUSH_ALL_CACHES - flush all the caches pf L1 and L2 + * + *flush all the caches pf L1 and L2 + */ +#define ION_IOC_FLUSH_ALL_CACHES _IOWR(ION_IOC_HISI_MAGIC, 3, \ + struct ion_flush_data) + +#ifdef CONFIG_ION +extern unsigned long hisi_ion_total(void); +#else +static inline unsigned long hisi_ion_total(void) +{ + return 0; +} +#endif + +/*k3 add to calc free memory*/ +void hisi_ionsysinfo(struct sysinfo *si); +int ion_handle_get_flags(struct ion_client *client, struct ion_handle *handle, + unsigned long *flags); +int hisi_ion_memory_info(bool verbose); +#endif diff --git a/include/linux/hisi/ion-iommu.h b/include/linux/hisi/ion-iommu.h new file mode 100644 index 000000000000..da92e5347315 --- /dev/null +++ b/include/linux/hisi/ion-iommu.h @@ -0,0 +1,79 @@ +#ifndef _HISI_IOMMU_H_ +#define _HISI_IOMMU_H_ + +#include +#include +#include +#include +#ifdef CONFIG_HISI_IODOMAIN_API + +struct section_info { + unsigned int iova_start; + unsigned int iova_size; + unsigned int page_size; + unsigned int align; +}; +struct hisi_iommu_domain { + struct iommu_domain *domain; + struct gen_pool *iova_pool; + struct section_info range; +}; + +/** + * hisi iommu domain interface + */ +struct hisi_iommu_domain * hisi_get_domain(void); +size_t hisi_iommu_iova_size(void); +size_t hisi_iommu_iova_available(void); +void hisi_iommu_free_iova(unsigned long iova, size_t size); +unsigned long hisi_iommu_alloc_iova(size_t size, unsigned long align); +int hisi_iommu_map_range(struct iommu_domain *domain,unsigned long iova_start, struct scatterlist *sgl, + unsigned long iova_size,unsigned int prot); +int hisi_iommu_unmap_range(struct iommu_domain *domain,unsigned long iova_start,unsigned long iova_size); +int hisi_iommu_map_domain(struct scatterlist *sg,struct iommu_map_format *format); +int hisi_iommu_unmap_domain(struct iommu_map_format *format); + +phys_addr_t hisi_iommu_domain_iova_to_phys(unsigned long iova); + +unsigned int hisi_iommu_page_size (void); +bool hisi_iommu_off_on(void); +int hisi_iommu_get_info(unsigned int *iova_start, unsigned int *pgtbl_base); + +#else + +/** + * hisi iommu domain interface + */ +static inline int hisi_iommu_map_domain(struct scatterlist *sg, + struct iommu_map_format *format) +{ + return 0; +} + +static inline int hisi_iommu_unmap_domain(struct iommu_map_format *format) +{ + return 0; +} + +static inline phys_addr_t hisi_iommu_domain_iova_to_phys(unsigned long iova) +{ + return 0; +} +unsigned int hisi_iommu_page_size (void) +{ + return SZ_4K; +} + +bool hisi_iommu_off_on(void) +{ + return false; +} + +static inline int hisi_iommu_get_info(unsigned int *iova_start, unsigned int *pgtbl_base) +{ + return 0; +} + +#endif /* CONFIG_HISI_IODOMAIN_API */ + +#endif /* _HISI_IOMMU_H_ */ diff --git a/include/linux/hisi_ion.h b/include/linux/hisi_ion.h new file mode 100644 index 000000000000..0d7be75f795f --- /dev/null +++ b/include/linux/hisi_ion.h @@ -0,0 +1,178 @@ +/* + * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _LINUX_HISI_ION_H +#define _LINUX_HISI_ION_H + +#include +#include + +/** + * These are the only ids that should be used for Ion heap ids. + * The ids listed are the order in which allocation will be attempted + * if specified. Don't swap the order of heap ids unless you know what + * you are doing! + * Id's are spaced by purpose to allow new Id's to be inserted in-between (for + * possible fallbacks) + */ + +enum ion_heap_ids { + INVALID_HEAP_ID = -1, + ION_SYSTEM_HEAP_ID = 0, + ION_SYSTEM_CONTIG_HEAP_ID = 1, + ION_GRALLOC_HEAP_ID = 2, + ION_DMA_HEAP_ID = 3, + ION_DMA_POOL_HEAP_ID = 4, + ION_CPU_DRAW_HEAP_ID = 5, + ION_CAMERA_HEAP_ID = 6, + ION_OVERLAY_HEAP_ID = 7, + ION_VCODEC_HEAP_ID = 8, + ION_ISP_HEAP_ID = 9, + ION_FB_HEAP_ID = 10, + ION_VPU_HEAP_ID = 11, + ION_JPU_HEAP_ID = 12, + HISI_ION_HEAP_IOMMU_ID = 13, + ION_MISC_HEAP_ID = 14, + ION_DRM_GRALLOC_HEAP_ID=15, + ION_DRM_VCODEC_HEAP_ID =16, + ION_TUI_HEAP_ID=17, + ION_IRIS_HEAP_ID=18, + ION_RESERV2_ID=19, + ION_DRM_HEAP_ID=20, + ION_HEAP_ID_RESERVED = 31, /* Bit reserved */ +}; + + +/** + * Macro should be used with ion_heap_ids defined above. + */ +#define ION_HEAP(bit) (1 << (bit)) +#define ION_8K_ALIGN(len) ALIGN(len, SZ_8K) +#define IOMMU_PAGE_SIZE SZ_8K + +#define ION_VMALLOC_HEAP_NAME "vmalloc" +#define ION_KMALLOC_HEAP_NAME "kmalloc" +#define ION_GRALLOC_HEAP_NAME "gralloc" + + +#define ION_SET_CACHED(__cache) (__cache | ION_FLAG_CACHED) +#define ION_SET_UNCACHED(__cache) (__cache & ~ION_FLAG_CACHED) + +#define ION_IS_CACHED(__flags) ((__flags) & ION_FLAG_CACHED) + +//struct used for get phys addr of contig heap +struct ion_phys_data { + int fd_buffer; + unsigned int size; + union { + unsigned int phys; + unsigned int phys_l; + }; + unsigned int phys_h; +}; + +struct ion_flag_data { + int shared_fd; + int flags; +}; + +struct ion_smart_pool_info_data { + int water_mark; +}; + +#define HISI_ION_NAME_LEN 16 + +struct ion_heap_info_data{ + char name[HISI_ION_NAME_LEN]; + phys_addr_t heap_phy; + unsigned int heap_size; +}; +struct ion_kern_va_data { + int handle_id; + unsigned int kern_va_h; + unsigned int kern_va_l; +}; +struct ion_issupport_iommu_data{ + int is_support_iommu; +}; + +struct ion_flush_data { + int fd; + void *vaddr; + unsigned int offset; + unsigned int length; +}; + + +//user command add for additional use +enum ION_HISI_CUSTOM_CMD { + ION_HISI_CUSTOM_PHYS, + ION_HISI_CLEAN_CACHES, + ION_HISI_INV_CACHES, + ION_HISI_CLEAN_INV_CACHES, + ION_HISI_CUSTOM_GET_KERN_VA, + ION_HISI_CUSTOM_FREE_KERN_VA, + ION_HISI_CUSTOM_ISSUPPORT_IOMMU, + ION_HISI_CUSTOM_GET_MEDIA_HEAP_MODE, + ION_HISI_CUSTOM_SET_FLAG, + ION_HISI_CUSTOM_SET_SMART_POOL_INFO, +}; + +enum ION_HISI_HEAP_MODE { + ION_CARVEROUT_MODE=0, + ION_IOMMU_MODE=1, +}; + +#define TINY_SYSTEM 0x0 /* tiny version system for chip test*/ +#define FULL_SYSTEM 0x1 /* full version system */ +/** + * hisi_ion_client_create() - create iommu mapping for the given handle + * @heap_mask: ion heap type mask + * @name: the client name + * @return: the client handle + * + * This function should called by high-level user in kernel. Before users + * can access a buffer, they should get a client via calling this function. + */ +struct ion_client * +hisi_ion_client_create(const char *name); +int hisi_ion_get_heap_info(unsigned int id,struct ion_heap_info_data* data); +int hisi_ion_get_media_mode(void); +unsigned long long get_system_type(void); +struct ion_device * get_ion_device(void); +#define ION_IOC_HISI_MAGIC 'H' +/** + *DOC: ION_IOC_FLUSH_ALL_CACHES - flush all the caches pf L1 and L2 + * + *flush all the caches pf L1 and L2 + */ +#define ION_IOC_FLUSH_ALL_CACHES _IOWR(ION_IOC_HISI_MAGIC, 3, \ + struct ion_flush_data) + +#ifdef CONFIG_ION +extern unsigned long hisi_ion_total(void); +#else +static inline unsigned long hisi_ion_total(void) +{ + return 0; +} +#endif + +/*k3 add to calc free memory*/ +void hisi_ionsysinfo(struct sysinfo *si); +int ion_handle_get_flags(struct ion_client *client, struct ion_handle *handle, + unsigned long *flags); +int hisi_ion_memory_info(bool verbose); +#endif diff --git a/include/linux/iommu.h b/include/linux/iommu.h index 436dc21318af..c4cd5a6565d1 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -31,6 +31,11 @@ #define IOMMU_CACHE (1 << 2) /* DMA cache coherency */ #define IOMMU_NOEXEC (1 << 3) #define IOMMU_MMIO (1 << 4) /* e.g. things like MSI doorbells */ +#if CONFIG_HISI_IOMMU +#define IOMMU_DEVICE (1 << 4) +#define IOMMU_SEC (1 << 5) +#define IOMMU_EXEC (1 << 6) +#endif struct iommu_ops; struct iommu_group; @@ -84,6 +89,7 @@ struct iommu_domain { void *handler_token; struct iommu_domain_geometry geometry; void *iova_cookie; + void *priv; }; enum iommu_cap { @@ -117,6 +123,28 @@ enum iommu_attr { DOMAIN_ATTR_MAX, }; +#if CONFIG_HISI_IOMMU +/* metadata for iommu mapping */ +struct iommu_map_format { + unsigned long iova_start; + unsigned long iova_size; + unsigned long iommu_ptb_base; + unsigned long iommu_iova_base; + unsigned long header_size; + unsigned long phys_page_line; + unsigned long virt_page_line; + unsigned long is_tile; + unsigned long prot; +}; + +struct tile_format { + unsigned long header_size; + unsigned long is_tile; + unsigned long phys_page_line; + unsigned long virt_page_line; +}; +#endif + /** * struct iommu_dm_region - descriptor for a direct mapped memory region * @list: Linked list pointers @@ -201,6 +229,14 @@ struct iommu_ops { int (*of_xlate)(struct device *dev, struct of_phandle_args *args); +#if CONFIG_HISI_IOMMU + int (*map_tile)(struct iommu_domain *domain, unsigned long iova, + struct scatterlist *sg, size_t size, int prot, + struct tile_format *format); + size_t (*unmap_tile)(struct iommu_domain *domain, unsigned long iova, + size_t size); +#endif + unsigned long pgsize_bitmap; }; @@ -272,7 +308,14 @@ struct device *iommu_device_create(struct device *parent, void *drvdata, void iommu_device_destroy(struct device *dev); int iommu_device_link(struct device *dev, struct device *link); void iommu_device_unlink(struct device *dev, struct device *link); +#if CONFIG_HISI_IOMMU +int iommu_map_tile(struct iommu_domain *domain, unsigned long iova, + struct scatterlist *sg, size_t size, int prot, + struct tile_format *format); +int iommu_unmap_tile(struct iommu_domain *domain, unsigned long iova, + size_t size); +#endif /* Window handling function prototypes */ extern int iommu_domain_window_enable(struct iommu_domain *domain, u32 wnd_nr, phys_addr_t offset, u64 size, diff --git a/include/linux/ion.h b/include/linux/ion.h new file mode 100644 index 000000000000..2598e7a7e6f0 --- /dev/null +++ b/include/linux/ion.h @@ -0,0 +1,5 @@ +#ifndef _INCLUDE_LINUX_ION_H_ +#define _INCLUDE_LINUX_ION_H_ +#include "../../drivers/staging/android/uapi/ion.h" +#include "../../drivers/staging/android/ion/ion.h" +#endif /* _INCLUDE_LINUX_ION_H_ */