mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
887 lines
18 KiB
887 lines
18 KiB
/* |
|
* arch/sh/mm/pmb.c |
|
* |
|
* Privileged Space Mapping Buffer (PMB) Support. |
|
* |
|
* Copyright (C) 2005 - 2011 Paul Mundt |
|
* Copyright (C) 2010 Matt Fleming |
|
* |
|
* This file is subject to the terms and conditions of the GNU General Public |
|
* License. See the file "COPYING" in the main directory of this archive |
|
* for more details. |
|
*/ |
|
#include <linux/init.h> |
|
#include <linux/kernel.h> |
|
#include <linux/syscore_ops.h> |
|
#include <linux/cpu.h> |
|
#include <linux/module.h> |
|
#include <linux/bitops.h> |
|
#include <linux/debugfs.h> |
|
#include <linux/fs.h> |
|
#include <linux/seq_file.h> |
|
#include <linux/err.h> |
|
#include <linux/io.h> |
|
#include <linux/spinlock.h> |
|
#include <linux/vmalloc.h> |
|
#include <linux/pgtable.h> |
|
#include <asm/cacheflush.h> |
|
#include <linux/sizes.h> |
|
#include <linux/uaccess.h> |
|
#include <asm/page.h> |
|
#include <asm/mmu.h> |
|
#include <asm/mmu_context.h> |
|
|
|
struct pmb_entry; |
|
|
|
struct pmb_entry { |
|
unsigned long vpn; |
|
unsigned long ppn; |
|
unsigned long flags; |
|
unsigned long size; |
|
|
|
raw_spinlock_t lock; |
|
|
|
/* |
|
* 0 .. NR_PMB_ENTRIES for specific entry selection, or |
|
* PMB_NO_ENTRY to search for a free one |
|
*/ |
|
int entry; |
|
|
|
/* Adjacent entry link for contiguous multi-entry mappings */ |
|
struct pmb_entry *link; |
|
}; |
|
|
|
static struct { |
|
unsigned long size; |
|
int flag; |
|
} pmb_sizes[] = { |
|
{ .size = SZ_512M, .flag = PMB_SZ_512M, }, |
|
{ .size = SZ_128M, .flag = PMB_SZ_128M, }, |
|
{ .size = SZ_64M, .flag = PMB_SZ_64M, }, |
|
{ .size = SZ_16M, .flag = PMB_SZ_16M, }, |
|
}; |
|
|
|
static void pmb_unmap_entry(struct pmb_entry *, int depth); |
|
|
|
static DEFINE_RWLOCK(pmb_rwlock); |
|
static struct pmb_entry pmb_entry_list[NR_PMB_ENTRIES]; |
|
static DECLARE_BITMAP(pmb_map, NR_PMB_ENTRIES); |
|
|
|
static unsigned int pmb_iomapping_enabled; |
|
|
|
static __always_inline unsigned long mk_pmb_entry(unsigned int entry) |
|
{ |
|
return (entry & PMB_E_MASK) << PMB_E_SHIFT; |
|
} |
|
|
|
static __always_inline unsigned long mk_pmb_addr(unsigned int entry) |
|
{ |
|
return mk_pmb_entry(entry) | PMB_ADDR; |
|
} |
|
|
|
static __always_inline unsigned long mk_pmb_data(unsigned int entry) |
|
{ |
|
return mk_pmb_entry(entry) | PMB_DATA; |
|
} |
|
|
|
static __always_inline unsigned int pmb_ppn_in_range(unsigned long ppn) |
|
{ |
|
return ppn >= __pa(memory_start) && ppn < __pa(memory_end); |
|
} |
|
|
|
/* |
|
* Ensure that the PMB entries match our cache configuration. |
|
* |
|
* When we are in 32-bit address extended mode, CCR.CB becomes |
|
* invalid, so care must be taken to manually adjust cacheable |
|
* translations. |
|
*/ |
|
static __always_inline unsigned long pmb_cache_flags(void) |
|
{ |
|
unsigned long flags = 0; |
|
|
|
#if defined(CONFIG_CACHE_OFF) |
|
flags |= PMB_WT | PMB_UB; |
|
#elif defined(CONFIG_CACHE_WRITETHROUGH) |
|
flags |= PMB_C | PMB_WT | PMB_UB; |
|
#elif defined(CONFIG_CACHE_WRITEBACK) |
|
flags |= PMB_C; |
|
#endif |
|
|
|
return flags; |
|
} |
|
|
|
/* |
|
* Convert typical pgprot value to the PMB equivalent |
|
*/ |
|
static inline unsigned long pgprot_to_pmb_flags(pgprot_t prot) |
|
{ |
|
unsigned long pmb_flags = 0; |
|
u64 flags = pgprot_val(prot); |
|
|
|
if (flags & _PAGE_CACHABLE) |
|
pmb_flags |= PMB_C; |
|
if (flags & _PAGE_WT) |
|
pmb_flags |= PMB_WT | PMB_UB; |
|
|
|
return pmb_flags; |
|
} |
|
|
|
static inline bool pmb_can_merge(struct pmb_entry *a, struct pmb_entry *b) |
|
{ |
|
return (b->vpn == (a->vpn + a->size)) && |
|
(b->ppn == (a->ppn + a->size)) && |
|
(b->flags == a->flags); |
|
} |
|
|
|
static bool pmb_mapping_exists(unsigned long vaddr, phys_addr_t phys, |
|
unsigned long size) |
|
{ |
|
int i; |
|
|
|
read_lock(&pmb_rwlock); |
|
|
|
for (i = 0; i < ARRAY_SIZE(pmb_entry_list); i++) { |
|
struct pmb_entry *pmbe, *iter; |
|
unsigned long span; |
|
|
|
if (!test_bit(i, pmb_map)) |
|
continue; |
|
|
|
pmbe = &pmb_entry_list[i]; |
|
|
|
/* |
|
* See if VPN and PPN are bounded by an existing mapping. |
|
*/ |
|
if ((vaddr < pmbe->vpn) || (vaddr >= (pmbe->vpn + pmbe->size))) |
|
continue; |
|
if ((phys < pmbe->ppn) || (phys >= (pmbe->ppn + pmbe->size))) |
|
continue; |
|
|
|
/* |
|
* Now see if we're in range of a simple mapping. |
|
*/ |
|
if (size <= pmbe->size) { |
|
read_unlock(&pmb_rwlock); |
|
return true; |
|
} |
|
|
|
span = pmbe->size; |
|
|
|
/* |
|
* Finally for sizes that involve compound mappings, walk |
|
* the chain. |
|
*/ |
|
for (iter = pmbe->link; iter; iter = iter->link) |
|
span += iter->size; |
|
|
|
/* |
|
* Nothing else to do if the range requirements are met. |
|
*/ |
|
if (size <= span) { |
|
read_unlock(&pmb_rwlock); |
|
return true; |
|
} |
|
} |
|
|
|
read_unlock(&pmb_rwlock); |
|
return false; |
|
} |
|
|
|
static bool pmb_size_valid(unsigned long size) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < ARRAY_SIZE(pmb_sizes); i++) |
|
if (pmb_sizes[i].size == size) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
static inline bool pmb_addr_valid(unsigned long addr, unsigned long size) |
|
{ |
|
return (addr >= P1SEG && (addr + size - 1) < P3SEG); |
|
} |
|
|
|
static inline bool pmb_prot_valid(pgprot_t prot) |
|
{ |
|
return (pgprot_val(prot) & _PAGE_USER) == 0; |
|
} |
|
|
|
static int pmb_size_to_flags(unsigned long size) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < ARRAY_SIZE(pmb_sizes); i++) |
|
if (pmb_sizes[i].size == size) |
|
return pmb_sizes[i].flag; |
|
|
|
return 0; |
|
} |
|
|
|
static int pmb_alloc_entry(void) |
|
{ |
|
int pos; |
|
|
|
pos = find_first_zero_bit(pmb_map, NR_PMB_ENTRIES); |
|
if (pos >= 0 && pos < NR_PMB_ENTRIES) |
|
__set_bit(pos, pmb_map); |
|
else |
|
pos = -ENOSPC; |
|
|
|
return pos; |
|
} |
|
|
|
static struct pmb_entry *pmb_alloc(unsigned long vpn, unsigned long ppn, |
|
unsigned long flags, int entry) |
|
{ |
|
struct pmb_entry *pmbe; |
|
unsigned long irqflags; |
|
void *ret = NULL; |
|
int pos; |
|
|
|
write_lock_irqsave(&pmb_rwlock, irqflags); |
|
|
|
if (entry == PMB_NO_ENTRY) { |
|
pos = pmb_alloc_entry(); |
|
if (unlikely(pos < 0)) { |
|
ret = ERR_PTR(pos); |
|
goto out; |
|
} |
|
} else { |
|
if (__test_and_set_bit(entry, pmb_map)) { |
|
ret = ERR_PTR(-ENOSPC); |
|
goto out; |
|
} |
|
|
|
pos = entry; |
|
} |
|
|
|
write_unlock_irqrestore(&pmb_rwlock, irqflags); |
|
|
|
pmbe = &pmb_entry_list[pos]; |
|
|
|
memset(pmbe, 0, sizeof(struct pmb_entry)); |
|
|
|
raw_spin_lock_init(&pmbe->lock); |
|
|
|
pmbe->vpn = vpn; |
|
pmbe->ppn = ppn; |
|
pmbe->flags = flags; |
|
pmbe->entry = pos; |
|
|
|
return pmbe; |
|
|
|
out: |
|
write_unlock_irqrestore(&pmb_rwlock, irqflags); |
|
return ret; |
|
} |
|
|
|
static void pmb_free(struct pmb_entry *pmbe) |
|
{ |
|
__clear_bit(pmbe->entry, pmb_map); |
|
|
|
pmbe->entry = PMB_NO_ENTRY; |
|
pmbe->link = NULL; |
|
} |
|
|
|
/* |
|
* Must be run uncached. |
|
*/ |
|
static void __set_pmb_entry(struct pmb_entry *pmbe) |
|
{ |
|
unsigned long addr, data; |
|
|
|
addr = mk_pmb_addr(pmbe->entry); |
|
data = mk_pmb_data(pmbe->entry); |
|
|
|
jump_to_uncached(); |
|
|
|
/* Set V-bit */ |
|
__raw_writel(pmbe->vpn | PMB_V, addr); |
|
__raw_writel(pmbe->ppn | pmbe->flags | PMB_V, data); |
|
|
|
back_to_cached(); |
|
} |
|
|
|
static void __clear_pmb_entry(struct pmb_entry *pmbe) |
|
{ |
|
unsigned long addr, data; |
|
unsigned long addr_val, data_val; |
|
|
|
addr = mk_pmb_addr(pmbe->entry); |
|
data = mk_pmb_data(pmbe->entry); |
|
|
|
addr_val = __raw_readl(addr); |
|
data_val = __raw_readl(data); |
|
|
|
/* Clear V-bit */ |
|
writel_uncached(addr_val & ~PMB_V, addr); |
|
writel_uncached(data_val & ~PMB_V, data); |
|
} |
|
|
|
#ifdef CONFIG_PM |
|
static void set_pmb_entry(struct pmb_entry *pmbe) |
|
{ |
|
unsigned long flags; |
|
|
|
raw_spin_lock_irqsave(&pmbe->lock, flags); |
|
__set_pmb_entry(pmbe); |
|
raw_spin_unlock_irqrestore(&pmbe->lock, flags); |
|
} |
|
#endif /* CONFIG_PM */ |
|
|
|
int pmb_bolt_mapping(unsigned long vaddr, phys_addr_t phys, |
|
unsigned long size, pgprot_t prot) |
|
{ |
|
struct pmb_entry *pmbp, *pmbe; |
|
unsigned long orig_addr, orig_size; |
|
unsigned long flags, pmb_flags; |
|
int i, mapped; |
|
|
|
if (size < SZ_16M) |
|
return -EINVAL; |
|
if (!pmb_addr_valid(vaddr, size)) |
|
return -EFAULT; |
|
if (pmb_mapping_exists(vaddr, phys, size)) |
|
return 0; |
|
|
|
orig_addr = vaddr; |
|
orig_size = size; |
|
|
|
flush_tlb_kernel_range(vaddr, vaddr + size); |
|
|
|
pmb_flags = pgprot_to_pmb_flags(prot); |
|
pmbp = NULL; |
|
|
|
do { |
|
for (i = mapped = 0; i < ARRAY_SIZE(pmb_sizes); i++) { |
|
if (size < pmb_sizes[i].size) |
|
continue; |
|
|
|
pmbe = pmb_alloc(vaddr, phys, pmb_flags | |
|
pmb_sizes[i].flag, PMB_NO_ENTRY); |
|
if (IS_ERR(pmbe)) { |
|
pmb_unmap_entry(pmbp, mapped); |
|
return PTR_ERR(pmbe); |
|
} |
|
|
|
raw_spin_lock_irqsave(&pmbe->lock, flags); |
|
|
|
pmbe->size = pmb_sizes[i].size; |
|
|
|
__set_pmb_entry(pmbe); |
|
|
|
phys += pmbe->size; |
|
vaddr += pmbe->size; |
|
size -= pmbe->size; |
|
|
|
/* |
|
* Link adjacent entries that span multiple PMB |
|
* entries for easier tear-down. |
|
*/ |
|
if (likely(pmbp)) { |
|
raw_spin_lock_nested(&pmbp->lock, |
|
SINGLE_DEPTH_NESTING); |
|
pmbp->link = pmbe; |
|
raw_spin_unlock(&pmbp->lock); |
|
} |
|
|
|
pmbp = pmbe; |
|
|
|
/* |
|
* Instead of trying smaller sizes on every |
|
* iteration (even if we succeed in allocating |
|
* space), try using pmb_sizes[i].size again. |
|
*/ |
|
i--; |
|
mapped++; |
|
|
|
raw_spin_unlock_irqrestore(&pmbe->lock, flags); |
|
} |
|
} while (size >= SZ_16M); |
|
|
|
flush_cache_vmap(orig_addr, orig_addr + orig_size); |
|
|
|
return 0; |
|
} |
|
|
|
void __iomem *pmb_remap_caller(phys_addr_t phys, unsigned long size, |
|
pgprot_t prot, void *caller) |
|
{ |
|
unsigned long vaddr; |
|
phys_addr_t offset, last_addr; |
|
phys_addr_t align_mask; |
|
unsigned long aligned; |
|
struct vm_struct *area; |
|
int i, ret; |
|
|
|
if (!pmb_iomapping_enabled) |
|
return NULL; |
|
|
|
/* |
|
* Small mappings need to go through the TLB. |
|
*/ |
|
if (size < SZ_16M) |
|
return ERR_PTR(-EINVAL); |
|
if (!pmb_prot_valid(prot)) |
|
return ERR_PTR(-EINVAL); |
|
|
|
for (i = 0; i < ARRAY_SIZE(pmb_sizes); i++) |
|
if (size >= pmb_sizes[i].size) |
|
break; |
|
|
|
last_addr = phys + size; |
|
align_mask = ~(pmb_sizes[i].size - 1); |
|
offset = phys & ~align_mask; |
|
phys &= align_mask; |
|
aligned = ALIGN(last_addr, pmb_sizes[i].size) - phys; |
|
|
|
/* |
|
* XXX: This should really start from uncached_end, but this |
|
* causes the MMU to reset, so for now we restrict it to the |
|
* 0xb000...0xc000 range. |
|
*/ |
|
area = __get_vm_area_caller(aligned, VM_IOREMAP, 0xb0000000, |
|
P3SEG, caller); |
|
if (!area) |
|
return NULL; |
|
|
|
area->phys_addr = phys; |
|
vaddr = (unsigned long)area->addr; |
|
|
|
ret = pmb_bolt_mapping(vaddr, phys, size, prot); |
|
if (unlikely(ret != 0)) |
|
return ERR_PTR(ret); |
|
|
|
return (void __iomem *)(offset + (char *)vaddr); |
|
} |
|
|
|
int pmb_unmap(void __iomem *addr) |
|
{ |
|
struct pmb_entry *pmbe = NULL; |
|
unsigned long vaddr = (unsigned long __force)addr; |
|
int i, found = 0; |
|
|
|
read_lock(&pmb_rwlock); |
|
|
|
for (i = 0; i < ARRAY_SIZE(pmb_entry_list); i++) { |
|
if (test_bit(i, pmb_map)) { |
|
pmbe = &pmb_entry_list[i]; |
|
if (pmbe->vpn == vaddr) { |
|
found = 1; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
read_unlock(&pmb_rwlock); |
|
|
|
if (found) { |
|
pmb_unmap_entry(pmbe, NR_PMB_ENTRIES); |
|
return 0; |
|
} |
|
|
|
return -EINVAL; |
|
} |
|
|
|
static void __pmb_unmap_entry(struct pmb_entry *pmbe, int depth) |
|
{ |
|
do { |
|
struct pmb_entry *pmblink = pmbe; |
|
|
|
/* |
|
* We may be called before this pmb_entry has been |
|
* entered into the PMB table via set_pmb_entry(), but |
|
* that's OK because we've allocated a unique slot for |
|
* this entry in pmb_alloc() (even if we haven't filled |
|
* it yet). |
|
* |
|
* Therefore, calling __clear_pmb_entry() is safe as no |
|
* other mapping can be using that slot. |
|
*/ |
|
__clear_pmb_entry(pmbe); |
|
|
|
flush_cache_vunmap(pmbe->vpn, pmbe->vpn + pmbe->size); |
|
|
|
pmbe = pmblink->link; |
|
|
|
pmb_free(pmblink); |
|
} while (pmbe && --depth); |
|
} |
|
|
|
static void pmb_unmap_entry(struct pmb_entry *pmbe, int depth) |
|
{ |
|
unsigned long flags; |
|
|
|
if (unlikely(!pmbe)) |
|
return; |
|
|
|
write_lock_irqsave(&pmb_rwlock, flags); |
|
__pmb_unmap_entry(pmbe, depth); |
|
write_unlock_irqrestore(&pmb_rwlock, flags); |
|
} |
|
|
|
static void __init pmb_notify(void) |
|
{ |
|
int i; |
|
|
|
pr_info("PMB: boot mappings:\n"); |
|
|
|
read_lock(&pmb_rwlock); |
|
|
|
for (i = 0; i < ARRAY_SIZE(pmb_entry_list); i++) { |
|
struct pmb_entry *pmbe; |
|
|
|
if (!test_bit(i, pmb_map)) |
|
continue; |
|
|
|
pmbe = &pmb_entry_list[i]; |
|
|
|
pr_info(" 0x%08lx -> 0x%08lx [ %4ldMB %2scached ]\n", |
|
pmbe->vpn >> PAGE_SHIFT, pmbe->ppn >> PAGE_SHIFT, |
|
pmbe->size >> 20, (pmbe->flags & PMB_C) ? "" : "un"); |
|
} |
|
|
|
read_unlock(&pmb_rwlock); |
|
} |
|
|
|
/* |
|
* Sync our software copy of the PMB mappings with those in hardware. The |
|
* mappings in the hardware PMB were either set up by the bootloader or |
|
* very early on by the kernel. |
|
*/ |
|
static void __init pmb_synchronize(void) |
|
{ |
|
struct pmb_entry *pmbp = NULL; |
|
int i, j; |
|
|
|
/* |
|
* Run through the initial boot mappings, log the established |
|
* ones, and blow away anything that falls outside of the valid |
|
* PPN range. Specifically, we only care about existing mappings |
|
* that impact the cached/uncached sections. |
|
* |
|
* Note that touching these can be a bit of a minefield; the boot |
|
* loader can establish multi-page mappings with the same caching |
|
* attributes, so we need to ensure that we aren't modifying a |
|
* mapping that we're presently executing from, or may execute |
|
* from in the case of straddling page boundaries. |
|
* |
|
* In the future we will have to tidy up after the boot loader by |
|
* jumping between the cached and uncached mappings and tearing |
|
* down alternating mappings while executing from the other. |
|
*/ |
|
for (i = 0; i < NR_PMB_ENTRIES; i++) { |
|
unsigned long addr, data; |
|
unsigned long addr_val, data_val; |
|
unsigned long ppn, vpn, flags; |
|
unsigned long irqflags; |
|
unsigned int size; |
|
struct pmb_entry *pmbe; |
|
|
|
addr = mk_pmb_addr(i); |
|
data = mk_pmb_data(i); |
|
|
|
addr_val = __raw_readl(addr); |
|
data_val = __raw_readl(data); |
|
|
|
/* |
|
* Skip over any bogus entries |
|
*/ |
|
if (!(data_val & PMB_V) || !(addr_val & PMB_V)) |
|
continue; |
|
|
|
ppn = data_val & PMB_PFN_MASK; |
|
vpn = addr_val & PMB_PFN_MASK; |
|
|
|
/* |
|
* Only preserve in-range mappings. |
|
*/ |
|
if (!pmb_ppn_in_range(ppn)) { |
|
/* |
|
* Invalidate anything out of bounds. |
|
*/ |
|
writel_uncached(addr_val & ~PMB_V, addr); |
|
writel_uncached(data_val & ~PMB_V, data); |
|
continue; |
|
} |
|
|
|
/* |
|
* Update the caching attributes if necessary |
|
*/ |
|
if (data_val & PMB_C) { |
|
data_val &= ~PMB_CACHE_MASK; |
|
data_val |= pmb_cache_flags(); |
|
|
|
writel_uncached(data_val, data); |
|
} |
|
|
|
size = data_val & PMB_SZ_MASK; |
|
flags = size | (data_val & PMB_CACHE_MASK); |
|
|
|
pmbe = pmb_alloc(vpn, ppn, flags, i); |
|
if (IS_ERR(pmbe)) { |
|
WARN_ON_ONCE(1); |
|
continue; |
|
} |
|
|
|
raw_spin_lock_irqsave(&pmbe->lock, irqflags); |
|
|
|
for (j = 0; j < ARRAY_SIZE(pmb_sizes); j++) |
|
if (pmb_sizes[j].flag == size) |
|
pmbe->size = pmb_sizes[j].size; |
|
|
|
if (pmbp) { |
|
raw_spin_lock_nested(&pmbp->lock, SINGLE_DEPTH_NESTING); |
|
/* |
|
* Compare the previous entry against the current one to |
|
* see if the entries span a contiguous mapping. If so, |
|
* setup the entry links accordingly. Compound mappings |
|
* are later coalesced. |
|
*/ |
|
if (pmb_can_merge(pmbp, pmbe)) |
|
pmbp->link = pmbe; |
|
raw_spin_unlock(&pmbp->lock); |
|
} |
|
|
|
pmbp = pmbe; |
|
|
|
raw_spin_unlock_irqrestore(&pmbe->lock, irqflags); |
|
} |
|
} |
|
|
|
static void __init pmb_merge(struct pmb_entry *head) |
|
{ |
|
unsigned long span, newsize; |
|
struct pmb_entry *tail; |
|
int i = 1, depth = 0; |
|
|
|
span = newsize = head->size; |
|
|
|
tail = head->link; |
|
while (tail) { |
|
span += tail->size; |
|
|
|
if (pmb_size_valid(span)) { |
|
newsize = span; |
|
depth = i; |
|
} |
|
|
|
/* This is the end of the line.. */ |
|
if (!tail->link) |
|
break; |
|
|
|
tail = tail->link; |
|
i++; |
|
} |
|
|
|
/* |
|
* The merged page size must be valid. |
|
*/ |
|
if (!depth || !pmb_size_valid(newsize)) |
|
return; |
|
|
|
head->flags &= ~PMB_SZ_MASK; |
|
head->flags |= pmb_size_to_flags(newsize); |
|
|
|
head->size = newsize; |
|
|
|
__pmb_unmap_entry(head->link, depth); |
|
__set_pmb_entry(head); |
|
} |
|
|
|
static void __init pmb_coalesce(void) |
|
{ |
|
unsigned long flags; |
|
int i; |
|
|
|
write_lock_irqsave(&pmb_rwlock, flags); |
|
|
|
for (i = 0; i < ARRAY_SIZE(pmb_entry_list); i++) { |
|
struct pmb_entry *pmbe; |
|
|
|
if (!test_bit(i, pmb_map)) |
|
continue; |
|
|
|
pmbe = &pmb_entry_list[i]; |
|
|
|
/* |
|
* We're only interested in compound mappings |
|
*/ |
|
if (!pmbe->link) |
|
continue; |
|
|
|
/* |
|
* Nothing to do if it already uses the largest possible |
|
* page size. |
|
*/ |
|
if (pmbe->size == SZ_512M) |
|
continue; |
|
|
|
pmb_merge(pmbe); |
|
} |
|
|
|
write_unlock_irqrestore(&pmb_rwlock, flags); |
|
} |
|
|
|
#ifdef CONFIG_UNCACHED_MAPPING |
|
static void __init pmb_resize(void) |
|
{ |
|
int i; |
|
|
|
/* |
|
* If the uncached mapping was constructed by the kernel, it will |
|
* already be a reasonable size. |
|
*/ |
|
if (uncached_size == SZ_16M) |
|
return; |
|
|
|
read_lock(&pmb_rwlock); |
|
|
|
for (i = 0; i < ARRAY_SIZE(pmb_entry_list); i++) { |
|
struct pmb_entry *pmbe; |
|
unsigned long flags; |
|
|
|
if (!test_bit(i, pmb_map)) |
|
continue; |
|
|
|
pmbe = &pmb_entry_list[i]; |
|
|
|
if (pmbe->vpn != uncached_start) |
|
continue; |
|
|
|
/* |
|
* Found it, now resize it. |
|
*/ |
|
raw_spin_lock_irqsave(&pmbe->lock, flags); |
|
|
|
pmbe->size = SZ_16M; |
|
pmbe->flags &= ~PMB_SZ_MASK; |
|
pmbe->flags |= pmb_size_to_flags(pmbe->size); |
|
|
|
uncached_resize(pmbe->size); |
|
|
|
__set_pmb_entry(pmbe); |
|
|
|
raw_spin_unlock_irqrestore(&pmbe->lock, flags); |
|
} |
|
|
|
read_unlock(&pmb_rwlock); |
|
} |
|
#endif |
|
|
|
static int __init early_pmb(char *p) |
|
{ |
|
if (!p) |
|
return 0; |
|
|
|
if (strstr(p, "iomap")) |
|
pmb_iomapping_enabled = 1; |
|
|
|
return 0; |
|
} |
|
early_param("pmb", early_pmb); |
|
|
|
void __init pmb_init(void) |
|
{ |
|
/* Synchronize software state */ |
|
pmb_synchronize(); |
|
|
|
/* Attempt to combine compound mappings */ |
|
pmb_coalesce(); |
|
|
|
#ifdef CONFIG_UNCACHED_MAPPING |
|
/* Resize initial mappings, if necessary */ |
|
pmb_resize(); |
|
#endif |
|
|
|
/* Log them */ |
|
pmb_notify(); |
|
|
|
writel_uncached(0, PMB_IRMCR); |
|
|
|
/* Flush out the TLB */ |
|
local_flush_tlb_all(); |
|
ctrl_barrier(); |
|
} |
|
|
|
bool __in_29bit_mode(void) |
|
{ |
|
return (__raw_readl(PMB_PASCR) & PASCR_SE) == 0; |
|
} |
|
|
|
static int pmb_debugfs_show(struct seq_file *file, void *iter) |
|
{ |
|
int i; |
|
|
|
seq_printf(file, "V: Valid, C: Cacheable, WT: Write-Through\n" |
|
"CB: Copy-Back, B: Buffered, UB: Unbuffered\n"); |
|
seq_printf(file, "ety vpn ppn size flags\n"); |
|
|
|
for (i = 0; i < NR_PMB_ENTRIES; i++) { |
|
unsigned long addr, data; |
|
unsigned int size; |
|
char *sz_str = NULL; |
|
|
|
addr = __raw_readl(mk_pmb_addr(i)); |
|
data = __raw_readl(mk_pmb_data(i)); |
|
|
|
size = data & PMB_SZ_MASK; |
|
sz_str = (size == PMB_SZ_16M) ? " 16MB": |
|
(size == PMB_SZ_64M) ? " 64MB": |
|
(size == PMB_SZ_128M) ? "128MB": |
|
"512MB"; |
|
|
|
/* 02: V 0x88 0x08 128MB C CB B */ |
|
seq_printf(file, "%02d: %c 0x%02lx 0x%02lx %s %c %s %s\n", |
|
i, ((addr & PMB_V) && (data & PMB_V)) ? 'V' : ' ', |
|
(addr >> 24) & 0xff, (data >> 24) & 0xff, |
|
sz_str, (data & PMB_C) ? 'C' : ' ', |
|
(data & PMB_WT) ? "WT" : "CB", |
|
(data & PMB_UB) ? "UB" : " B"); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
DEFINE_SHOW_ATTRIBUTE(pmb_debugfs); |
|
|
|
static int __init pmb_debugfs_init(void) |
|
{ |
|
debugfs_create_file("pmb", S_IFREG | S_IRUGO, arch_debugfs_dir, NULL, |
|
&pmb_debugfs_fops); |
|
return 0; |
|
} |
|
subsys_initcall(pmb_debugfs_init); |
|
|
|
#ifdef CONFIG_PM |
|
static void pmb_syscore_resume(void) |
|
{ |
|
struct pmb_entry *pmbe; |
|
int i; |
|
|
|
read_lock(&pmb_rwlock); |
|
|
|
for (i = 0; i < ARRAY_SIZE(pmb_entry_list); i++) { |
|
if (test_bit(i, pmb_map)) { |
|
pmbe = &pmb_entry_list[i]; |
|
set_pmb_entry(pmbe); |
|
} |
|
} |
|
|
|
read_unlock(&pmb_rwlock); |
|
} |
|
|
|
static struct syscore_ops pmb_syscore_ops = { |
|
.resume = pmb_syscore_resume, |
|
}; |
|
|
|
static int __init pmb_sysdev_init(void) |
|
{ |
|
register_syscore_ops(&pmb_syscore_ops); |
|
return 0; |
|
} |
|
subsys_initcall(pmb_sysdev_init); |
|
#endif
|
|
|