162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Apple DART page table allocator.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2022 The Asahi Linux Contributors
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Based on io-pgtable-arm.
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Copyright (C) 2014 ARM Limited
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * Author: Will Deacon <will.deacon@arm.com>
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define pr_fmt(fmt)	"dart io-pgtable: " fmt
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/atomic.h>
1762306a36Sopenharmony_ci#include <linux/bitfield.h>
1862306a36Sopenharmony_ci#include <linux/bitops.h>
1962306a36Sopenharmony_ci#include <linux/io-pgtable.h>
2062306a36Sopenharmony_ci#include <linux/kernel.h>
2162306a36Sopenharmony_ci#include <linux/sizes.h>
2262306a36Sopenharmony_ci#include <linux/slab.h>
2362306a36Sopenharmony_ci#include <linux/types.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#include <asm/barrier.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define DART1_MAX_ADDR_BITS	36
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define DART_MAX_TABLES		4
3062306a36Sopenharmony_ci#define DART_LEVELS		2
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci/* Struct accessors */
3362306a36Sopenharmony_ci#define io_pgtable_to_data(x)						\
3462306a36Sopenharmony_ci	container_of((x), struct dart_io_pgtable, iop)
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#define io_pgtable_ops_to_data(x)					\
3762306a36Sopenharmony_ci	io_pgtable_to_data(io_pgtable_ops_to_pgtable(x))
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#define DART_GRANULE(d)						\
4062306a36Sopenharmony_ci	(sizeof(dart_iopte) << (d)->bits_per_level)
4162306a36Sopenharmony_ci#define DART_PTES_PER_TABLE(d)					\
4262306a36Sopenharmony_ci	(DART_GRANULE(d) >> ilog2(sizeof(dart_iopte)))
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci#define APPLE_DART_PTE_SUBPAGE_START   GENMASK_ULL(63, 52)
4562306a36Sopenharmony_ci#define APPLE_DART_PTE_SUBPAGE_END     GENMASK_ULL(51, 40)
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define APPLE_DART1_PADDR_MASK	GENMASK_ULL(35, 12)
4862306a36Sopenharmony_ci#define APPLE_DART2_PADDR_MASK	GENMASK_ULL(37, 10)
4962306a36Sopenharmony_ci#define APPLE_DART2_PADDR_SHIFT	(4)
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/* Apple DART1 protection bits */
5262306a36Sopenharmony_ci#define APPLE_DART1_PTE_PROT_NO_READ	BIT(8)
5362306a36Sopenharmony_ci#define APPLE_DART1_PTE_PROT_NO_WRITE	BIT(7)
5462306a36Sopenharmony_ci#define APPLE_DART1_PTE_PROT_SP_DIS	BIT(1)
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci/* Apple DART2 protection bits */
5762306a36Sopenharmony_ci#define APPLE_DART2_PTE_PROT_NO_READ	BIT(3)
5862306a36Sopenharmony_ci#define APPLE_DART2_PTE_PROT_NO_WRITE	BIT(2)
5962306a36Sopenharmony_ci#define APPLE_DART2_PTE_PROT_NO_CACHE	BIT(1)
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci/* marks PTE as valid */
6262306a36Sopenharmony_ci#define APPLE_DART_PTE_VALID		BIT(0)
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci/* IOPTE accessors */
6562306a36Sopenharmony_ci#define iopte_deref(pte, d) __va(iopte_to_paddr(pte, d))
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistruct dart_io_pgtable {
6862306a36Sopenharmony_ci	struct io_pgtable	iop;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	int			tbl_bits;
7162306a36Sopenharmony_ci	int			bits_per_level;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	void			*pgd[DART_MAX_TABLES];
7462306a36Sopenharmony_ci};
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_citypedef u64 dart_iopte;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic dart_iopte paddr_to_iopte(phys_addr_t paddr,
8062306a36Sopenharmony_ci				     struct dart_io_pgtable *data)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	dart_iopte pte;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	if (data->iop.fmt == APPLE_DART)
8562306a36Sopenharmony_ci		return paddr & APPLE_DART1_PADDR_MASK;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	/* format is APPLE_DART2 */
8862306a36Sopenharmony_ci	pte = paddr >> APPLE_DART2_PADDR_SHIFT;
8962306a36Sopenharmony_ci	pte &= APPLE_DART2_PADDR_MASK;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	return pte;
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic phys_addr_t iopte_to_paddr(dart_iopte pte,
9562306a36Sopenharmony_ci				  struct dart_io_pgtable *data)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	u64 paddr;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	if (data->iop.fmt == APPLE_DART)
10062306a36Sopenharmony_ci		return pte & APPLE_DART1_PADDR_MASK;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	/* format is APPLE_DART2 */
10362306a36Sopenharmony_ci	paddr = pte & APPLE_DART2_PADDR_MASK;
10462306a36Sopenharmony_ci	paddr <<= APPLE_DART2_PADDR_SHIFT;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	return paddr;
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic void *__dart_alloc_pages(size_t size, gfp_t gfp,
11062306a36Sopenharmony_ci				    struct io_pgtable_cfg *cfg)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	int order = get_order(size);
11362306a36Sopenharmony_ci	struct page *p;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	VM_BUG_ON((gfp & __GFP_HIGHMEM));
11662306a36Sopenharmony_ci	p = alloc_pages(gfp | __GFP_ZERO, order);
11762306a36Sopenharmony_ci	if (!p)
11862306a36Sopenharmony_ci		return NULL;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	return page_address(p);
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic int dart_init_pte(struct dart_io_pgtable *data,
12462306a36Sopenharmony_ci			     unsigned long iova, phys_addr_t paddr,
12562306a36Sopenharmony_ci			     dart_iopte prot, int num_entries,
12662306a36Sopenharmony_ci			     dart_iopte *ptep)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	int i;
12962306a36Sopenharmony_ci	dart_iopte pte = prot;
13062306a36Sopenharmony_ci	size_t sz = data->iop.cfg.pgsize_bitmap;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	for (i = 0; i < num_entries; i++)
13362306a36Sopenharmony_ci		if (ptep[i] & APPLE_DART_PTE_VALID) {
13462306a36Sopenharmony_ci			/* We require an unmap first */
13562306a36Sopenharmony_ci			WARN_ON(ptep[i] & APPLE_DART_PTE_VALID);
13662306a36Sopenharmony_ci			return -EEXIST;
13762306a36Sopenharmony_ci		}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	/* subpage protection: always allow access to the entire page */
14062306a36Sopenharmony_ci	pte |= FIELD_PREP(APPLE_DART_PTE_SUBPAGE_START, 0);
14162306a36Sopenharmony_ci	pte |= FIELD_PREP(APPLE_DART_PTE_SUBPAGE_END, 0xfff);
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	pte |= APPLE_DART1_PTE_PROT_SP_DIS;
14462306a36Sopenharmony_ci	pte |= APPLE_DART_PTE_VALID;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	for (i = 0; i < num_entries; i++)
14762306a36Sopenharmony_ci		ptep[i] = pte | paddr_to_iopte(paddr + i * sz, data);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	return 0;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic dart_iopte dart_install_table(dart_iopte *table,
15362306a36Sopenharmony_ci					     dart_iopte *ptep,
15462306a36Sopenharmony_ci					     dart_iopte curr,
15562306a36Sopenharmony_ci					     struct dart_io_pgtable *data)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	dart_iopte old, new;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	new = paddr_to_iopte(__pa(table), data) | APPLE_DART_PTE_VALID;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	/*
16262306a36Sopenharmony_ci	 * Ensure the table itself is visible before its PTE can be.
16362306a36Sopenharmony_ci	 * Whilst we could get away with cmpxchg64_release below, this
16462306a36Sopenharmony_ci	 * doesn't have any ordering semantics when !CONFIG_SMP.
16562306a36Sopenharmony_ci	 */
16662306a36Sopenharmony_ci	dma_wmb();
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	old = cmpxchg64_relaxed(ptep, curr, new);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	return old;
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cistatic int dart_get_table(struct dart_io_pgtable *data, unsigned long iova)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	return (iova >> (3 * data->bits_per_level + ilog2(sizeof(dart_iopte)))) &
17662306a36Sopenharmony_ci		((1 << data->tbl_bits) - 1);
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic int dart_get_l1_index(struct dart_io_pgtable *data, unsigned long iova)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	return (iova >> (2 * data->bits_per_level + ilog2(sizeof(dart_iopte)))) &
18362306a36Sopenharmony_ci		 ((1 << data->bits_per_level) - 1);
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_cistatic int dart_get_l2_index(struct dart_io_pgtable *data, unsigned long iova)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return (iova >> (data->bits_per_level + ilog2(sizeof(dart_iopte)))) &
19062306a36Sopenharmony_ci		 ((1 << data->bits_per_level) - 1);
19162306a36Sopenharmony_ci}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic  dart_iopte *dart_get_l2(struct dart_io_pgtable *data, unsigned long iova)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	dart_iopte pte, *ptep;
19662306a36Sopenharmony_ci	int tbl = dart_get_table(data, iova);
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	ptep = data->pgd[tbl];
19962306a36Sopenharmony_ci	if (!ptep)
20062306a36Sopenharmony_ci		return NULL;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	ptep += dart_get_l1_index(data, iova);
20362306a36Sopenharmony_ci	pte = READ_ONCE(*ptep);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	/* Valid entry? */
20662306a36Sopenharmony_ci	if (!pte)
20762306a36Sopenharmony_ci		return NULL;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	/* Deref to get level 2 table */
21062306a36Sopenharmony_ci	return iopte_deref(pte, data);
21162306a36Sopenharmony_ci}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_cistatic dart_iopte dart_prot_to_pte(struct dart_io_pgtable *data,
21462306a36Sopenharmony_ci					   int prot)
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	dart_iopte pte = 0;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	if (data->iop.fmt == APPLE_DART) {
21962306a36Sopenharmony_ci		if (!(prot & IOMMU_WRITE))
22062306a36Sopenharmony_ci			pte |= APPLE_DART1_PTE_PROT_NO_WRITE;
22162306a36Sopenharmony_ci		if (!(prot & IOMMU_READ))
22262306a36Sopenharmony_ci			pte |= APPLE_DART1_PTE_PROT_NO_READ;
22362306a36Sopenharmony_ci	}
22462306a36Sopenharmony_ci	if (data->iop.fmt == APPLE_DART2) {
22562306a36Sopenharmony_ci		if (!(prot & IOMMU_WRITE))
22662306a36Sopenharmony_ci			pte |= APPLE_DART2_PTE_PROT_NO_WRITE;
22762306a36Sopenharmony_ci		if (!(prot & IOMMU_READ))
22862306a36Sopenharmony_ci			pte |= APPLE_DART2_PTE_PROT_NO_READ;
22962306a36Sopenharmony_ci		if (!(prot & IOMMU_CACHE))
23062306a36Sopenharmony_ci			pte |= APPLE_DART2_PTE_PROT_NO_CACHE;
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	return pte;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic int dart_map_pages(struct io_pgtable_ops *ops, unsigned long iova,
23762306a36Sopenharmony_ci			      phys_addr_t paddr, size_t pgsize, size_t pgcount,
23862306a36Sopenharmony_ci			      int iommu_prot, gfp_t gfp, size_t *mapped)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	struct dart_io_pgtable *data = io_pgtable_ops_to_data(ops);
24162306a36Sopenharmony_ci	struct io_pgtable_cfg *cfg = &data->iop.cfg;
24262306a36Sopenharmony_ci	size_t tblsz = DART_GRANULE(data);
24362306a36Sopenharmony_ci	int ret = 0, tbl, num_entries, max_entries, map_idx_start;
24462306a36Sopenharmony_ci	dart_iopte pte, *cptep, *ptep;
24562306a36Sopenharmony_ci	dart_iopte prot;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	if (WARN_ON(pgsize != cfg->pgsize_bitmap))
24862306a36Sopenharmony_ci		return -EINVAL;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	if (WARN_ON(paddr >> cfg->oas))
25162306a36Sopenharmony_ci		return -ERANGE;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	/* If no access, then nothing to do */
25462306a36Sopenharmony_ci	if (!(iommu_prot & (IOMMU_READ | IOMMU_WRITE)))
25562306a36Sopenharmony_ci		return 0;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	tbl = dart_get_table(data, iova);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	ptep = data->pgd[tbl];
26062306a36Sopenharmony_ci	ptep += dart_get_l1_index(data, iova);
26162306a36Sopenharmony_ci	pte = READ_ONCE(*ptep);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	/* no L2 table present */
26462306a36Sopenharmony_ci	if (!pte) {
26562306a36Sopenharmony_ci		cptep = __dart_alloc_pages(tblsz, gfp, cfg);
26662306a36Sopenharmony_ci		if (!cptep)
26762306a36Sopenharmony_ci			return -ENOMEM;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci		pte = dart_install_table(cptep, ptep, 0, data);
27062306a36Sopenharmony_ci		if (pte)
27162306a36Sopenharmony_ci			free_pages((unsigned long)cptep, get_order(tblsz));
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci		/* L2 table is present (now) */
27462306a36Sopenharmony_ci		pte = READ_ONCE(*ptep);
27562306a36Sopenharmony_ci	}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	ptep = iopte_deref(pte, data);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	/* install a leaf entries into L2 table */
28062306a36Sopenharmony_ci	prot = dart_prot_to_pte(data, iommu_prot);
28162306a36Sopenharmony_ci	map_idx_start = dart_get_l2_index(data, iova);
28262306a36Sopenharmony_ci	max_entries = DART_PTES_PER_TABLE(data) - map_idx_start;
28362306a36Sopenharmony_ci	num_entries = min_t(int, pgcount, max_entries);
28462306a36Sopenharmony_ci	ptep += map_idx_start;
28562306a36Sopenharmony_ci	ret = dart_init_pte(data, iova, paddr, prot, num_entries, ptep);
28662306a36Sopenharmony_ci	if (!ret && mapped)
28762306a36Sopenharmony_ci		*mapped += num_entries * pgsize;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	/*
29062306a36Sopenharmony_ci	 * Synchronise all PTE updates for the new mapping before there's
29162306a36Sopenharmony_ci	 * a chance for anything to kick off a table walk for the new iova.
29262306a36Sopenharmony_ci	 */
29362306a36Sopenharmony_ci	wmb();
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	return ret;
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cistatic size_t dart_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova,
29962306a36Sopenharmony_ci				   size_t pgsize, size_t pgcount,
30062306a36Sopenharmony_ci				   struct iommu_iotlb_gather *gather)
30162306a36Sopenharmony_ci{
30262306a36Sopenharmony_ci	struct dart_io_pgtable *data = io_pgtable_ops_to_data(ops);
30362306a36Sopenharmony_ci	struct io_pgtable_cfg *cfg = &data->iop.cfg;
30462306a36Sopenharmony_ci	int i = 0, num_entries, max_entries, unmap_idx_start;
30562306a36Sopenharmony_ci	dart_iopte pte, *ptep;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	if (WARN_ON(pgsize != cfg->pgsize_bitmap || !pgcount))
30862306a36Sopenharmony_ci		return 0;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	ptep = dart_get_l2(data, iova);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	/* Valid L2 IOPTE pointer? */
31362306a36Sopenharmony_ci	if (WARN_ON(!ptep))
31462306a36Sopenharmony_ci		return 0;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	unmap_idx_start = dart_get_l2_index(data, iova);
31762306a36Sopenharmony_ci	ptep += unmap_idx_start;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	max_entries = DART_PTES_PER_TABLE(data) - unmap_idx_start;
32062306a36Sopenharmony_ci	num_entries = min_t(int, pgcount, max_entries);
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	while (i < num_entries) {
32362306a36Sopenharmony_ci		pte = READ_ONCE(*ptep);
32462306a36Sopenharmony_ci		if (WARN_ON(!pte))
32562306a36Sopenharmony_ci			break;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci		/* clear pte */
32862306a36Sopenharmony_ci		*ptep = 0;
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci		if (!iommu_iotlb_gather_queued(gather))
33162306a36Sopenharmony_ci			io_pgtable_tlb_add_page(&data->iop, gather,
33262306a36Sopenharmony_ci						iova + i * pgsize, pgsize);
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci		ptep++;
33562306a36Sopenharmony_ci		i++;
33662306a36Sopenharmony_ci	}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	return i * pgsize;
33962306a36Sopenharmony_ci}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic phys_addr_t dart_iova_to_phys(struct io_pgtable_ops *ops,
34262306a36Sopenharmony_ci					 unsigned long iova)
34362306a36Sopenharmony_ci{
34462306a36Sopenharmony_ci	struct dart_io_pgtable *data = io_pgtable_ops_to_data(ops);
34562306a36Sopenharmony_ci	dart_iopte pte, *ptep;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	ptep = dart_get_l2(data, iova);
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	/* Valid L2 IOPTE pointer? */
35062306a36Sopenharmony_ci	if (!ptep)
35162306a36Sopenharmony_ci		return 0;
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	ptep += dart_get_l2_index(data, iova);
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	pte = READ_ONCE(*ptep);
35662306a36Sopenharmony_ci	/* Found translation */
35762306a36Sopenharmony_ci	if (pte) {
35862306a36Sopenharmony_ci		iova &= (data->iop.cfg.pgsize_bitmap - 1);
35962306a36Sopenharmony_ci		return iopte_to_paddr(pte, data) | iova;
36062306a36Sopenharmony_ci	}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	/* Ran out of page tables to walk */
36362306a36Sopenharmony_ci	return 0;
36462306a36Sopenharmony_ci}
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_cistatic struct dart_io_pgtable *
36762306a36Sopenharmony_cidart_alloc_pgtable(struct io_pgtable_cfg *cfg)
36862306a36Sopenharmony_ci{
36962306a36Sopenharmony_ci	struct dart_io_pgtable *data;
37062306a36Sopenharmony_ci	int tbl_bits, bits_per_level, va_bits, pg_shift;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	pg_shift = __ffs(cfg->pgsize_bitmap);
37362306a36Sopenharmony_ci	bits_per_level = pg_shift - ilog2(sizeof(dart_iopte));
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	va_bits = cfg->ias - pg_shift;
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	tbl_bits = max_t(int, 0, va_bits - (bits_per_level * DART_LEVELS));
37862306a36Sopenharmony_ci	if ((1 << tbl_bits) > DART_MAX_TABLES)
37962306a36Sopenharmony_ci		return NULL;
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	data = kzalloc(sizeof(*data), GFP_KERNEL);
38262306a36Sopenharmony_ci	if (!data)
38362306a36Sopenharmony_ci		return NULL;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	data->tbl_bits = tbl_bits;
38662306a36Sopenharmony_ci	data->bits_per_level = bits_per_level;
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	data->iop.ops = (struct io_pgtable_ops) {
38962306a36Sopenharmony_ci		.map_pages	= dart_map_pages,
39062306a36Sopenharmony_ci		.unmap_pages	= dart_unmap_pages,
39162306a36Sopenharmony_ci		.iova_to_phys	= dart_iova_to_phys,
39262306a36Sopenharmony_ci	};
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	return data;
39562306a36Sopenharmony_ci}
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_cistatic struct io_pgtable *
39862306a36Sopenharmony_ciapple_dart_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
39962306a36Sopenharmony_ci{
40062306a36Sopenharmony_ci	struct dart_io_pgtable *data;
40162306a36Sopenharmony_ci	int i;
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci	if (!cfg->coherent_walk)
40462306a36Sopenharmony_ci		return NULL;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	if (cfg->oas != 36 && cfg->oas != 42)
40762306a36Sopenharmony_ci		return NULL;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	if (cfg->ias > cfg->oas)
41062306a36Sopenharmony_ci		return NULL;
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	if (!(cfg->pgsize_bitmap == SZ_4K || cfg->pgsize_bitmap == SZ_16K))
41362306a36Sopenharmony_ci		return NULL;
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	data = dart_alloc_pgtable(cfg);
41662306a36Sopenharmony_ci	if (!data)
41762306a36Sopenharmony_ci		return NULL;
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	cfg->apple_dart_cfg.n_ttbrs = 1 << data->tbl_bits;
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	for (i = 0; i < cfg->apple_dart_cfg.n_ttbrs; ++i) {
42262306a36Sopenharmony_ci		data->pgd[i] = __dart_alloc_pages(DART_GRANULE(data), GFP_KERNEL,
42362306a36Sopenharmony_ci					   cfg);
42462306a36Sopenharmony_ci		if (!data->pgd[i])
42562306a36Sopenharmony_ci			goto out_free_data;
42662306a36Sopenharmony_ci		cfg->apple_dart_cfg.ttbr[i] = virt_to_phys(data->pgd[i]);
42762306a36Sopenharmony_ci	}
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	return &data->iop;
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ciout_free_data:
43262306a36Sopenharmony_ci	while (--i >= 0)
43362306a36Sopenharmony_ci		free_pages((unsigned long)data->pgd[i],
43462306a36Sopenharmony_ci			   get_order(DART_GRANULE(data)));
43562306a36Sopenharmony_ci	kfree(data);
43662306a36Sopenharmony_ci	return NULL;
43762306a36Sopenharmony_ci}
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_cistatic void apple_dart_free_pgtable(struct io_pgtable *iop)
44062306a36Sopenharmony_ci{
44162306a36Sopenharmony_ci	struct dart_io_pgtable *data = io_pgtable_to_data(iop);
44262306a36Sopenharmony_ci	dart_iopte *ptep, *end;
44362306a36Sopenharmony_ci	int i;
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	for (i = 0; i < (1 << data->tbl_bits) && data->pgd[i]; ++i) {
44662306a36Sopenharmony_ci		ptep = data->pgd[i];
44762306a36Sopenharmony_ci		end = (void *)ptep + DART_GRANULE(data);
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci		while (ptep != end) {
45062306a36Sopenharmony_ci			dart_iopte pte = *ptep++;
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci			if (pte) {
45362306a36Sopenharmony_ci				unsigned long page =
45462306a36Sopenharmony_ci					(unsigned long)iopte_deref(pte, data);
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci				free_pages(page, get_order(DART_GRANULE(data)));
45762306a36Sopenharmony_ci			}
45862306a36Sopenharmony_ci		}
45962306a36Sopenharmony_ci		free_pages((unsigned long)data->pgd[i],
46062306a36Sopenharmony_ci			   get_order(DART_GRANULE(data)));
46162306a36Sopenharmony_ci	}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	kfree(data);
46462306a36Sopenharmony_ci}
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_cistruct io_pgtable_init_fns io_pgtable_apple_dart_init_fns = {
46762306a36Sopenharmony_ci	.alloc	= apple_dart_alloc_pgtable,
46862306a36Sopenharmony_ci	.free	= apple_dart_free_pgtable,
46962306a36Sopenharmony_ci};
470