862 lines
21 KiB
C
862 lines
21 KiB
C
|
/*
|
||
|
* arch/sh/mm/pmb.c
|
||
|
*
|
||
|
* Privileged Space Mapping Buffer (PMB) Support.
|
||
|
*
|
||
|
* Copyright (C) 2005, 2006, 2007 Paul Mundt
|
||
|
*
|
||
|
* P1/P2 Section mapping definitions from map32.h, which was:
|
||
|
*
|
||
|
* Copyright 2003 (c) Lineo Solutions,Inc.
|
||
|
*
|
||
|
* Large changes to support dynamic mappings using PMB
|
||
|
* Copyright (c) 2007 STMicroelectronics Limited
|
||
|
* Author: Stuart Menefy <stuart.menefy@st.com>
|
||
|
*
|
||
|
* 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/sysdev.h>
|
||
|
#include <linux/cpu.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/bitops.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/seq_file.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/pm.h>
|
||
|
#include <asm/system.h>
|
||
|
#include <asm/uaccess.h>
|
||
|
#include <asm/pgtable.h>
|
||
|
#include <asm/mmu.h>
|
||
|
#include <asm/io.h>
|
||
|
#include <asm/mmu_context.h>
|
||
|
#include <asm/sections.h>
|
||
|
#include <asm/cacheflush.h>
|
||
|
|
||
|
#if 0
|
||
|
#define DPRINTK(fmt, args...) printk(KERN_ERR "%s: " fmt, __FUNCTION__, ## args)
|
||
|
#else
|
||
|
#define DPRINTK(fmt, args...) do { ; } while (0)
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#define NR_PMB_ENTRIES 16
|
||
|
#define MIN_PMB_MAPPING_SIZE (8*1024*1024)
|
||
|
|
||
|
#ifdef CONFIG_PMB_64M_TILES
|
||
|
#define PMB_FIXED_SHIFT 26
|
||
|
#define PMB_VIRT2POS(virt) (((virt) >> PMB_FIXED_SHIFT) & (NR_PMB_ENTRIES - 1))
|
||
|
#define PMB_POS2VIRT(pos) (((pos) << PMB_FIXED_SHIFT) + P1SEG)
|
||
|
#endif
|
||
|
|
||
|
struct pmb_entry {
|
||
|
unsigned long vpn;
|
||
|
unsigned long ppn;
|
||
|
unsigned long flags; /* Only size */
|
||
|
struct pmb_entry *next;
|
||
|
unsigned long size;
|
||
|
int pos;
|
||
|
};
|
||
|
|
||
|
struct pmb_mapping {
|
||
|
unsigned long phys;
|
||
|
unsigned long virt;
|
||
|
unsigned long size;
|
||
|
unsigned long flags; /* Only cache etc */
|
||
|
struct pmb_entry *entries;
|
||
|
struct pmb_mapping *next;
|
||
|
int usage;
|
||
|
};
|
||
|
|
||
|
static DEFINE_RWLOCK(pmb_lock);
|
||
|
static unsigned long pmb_map;
|
||
|
static struct pmb_entry pmbe[NR_PMB_ENTRIES] __attribute__ ((__section__ (".uncached.data")));
|
||
|
static struct pmb_mapping pmbm[NR_PMB_ENTRIES];
|
||
|
static struct pmb_mapping *pmb_mappings, *pmb_mappings_free;
|
||
|
|
||
|
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 void __set_pmb_entry(unsigned long vpn,
|
||
|
unsigned long ppn, unsigned long flags, int pos)
|
||
|
{
|
||
|
#ifdef CONFIG_CACHE_WRITETHROUGH
|
||
|
/*
|
||
|
* When we are in 32-bit address extended mode, CCR.CB becomes
|
||
|
* invalid, so care must be taken to manually adjust cacheable
|
||
|
* translations.
|
||
|
*/
|
||
|
if (likely(flags & PMB_C))
|
||
|
flags |= PMB_WT;
|
||
|
#endif
|
||
|
#ifdef CONFIG_PMB_64M_TILES
|
||
|
BUG_ON(pos != PMB_VIRT2POS(vpn));
|
||
|
#endif
|
||
|
ctrl_outl(0, mk_pmb_addr(pos));
|
||
|
ctrl_outl(vpn, mk_pmb_addr(pos));
|
||
|
ctrl_outl(ppn | flags | PMB_V, mk_pmb_data(pos));
|
||
|
|
||
|
/*
|
||
|
* Read back the value just written. This shouldn't be necessary,
|
||
|
* but when resuming from hibernation it appears to fix a problem.
|
||
|
*/
|
||
|
ctrl_inl(mk_pmb_addr(pos));
|
||
|
}
|
||
|
|
||
|
static void __uses_jump_to_uncached set_pmb_entry(unsigned long vpn,
|
||
|
unsigned long ppn, unsigned long flags, int pos)
|
||
|
{
|
||
|
jump_to_uncached();
|
||
|
__set_pmb_entry(vpn, ppn, flags, pos);
|
||
|
back_to_cached();
|
||
|
}
|
||
|
|
||
|
static __always_inline void __clear_pmb_entry(int pos)
|
||
|
{
|
||
|
#ifdef CONFIG_PMB_64M_TILES
|
||
|
ctrl_outl(0, mk_pmb_addr(pos));
|
||
|
ctrl_outl(PMB_POS2VIRT(pos), mk_pmb_addr(pos));
|
||
|
ctrl_outl((CONFIG_PMB_64M_TILES_PHYS & ~((1 << PMB_FIXED_SHIFT)-1)) |
|
||
|
PMB_SZ_64M | PMB_WT | PMB_UB | PMB_V, mk_pmb_data(pos));
|
||
|
#else
|
||
|
ctrl_outl(0, mk_pmb_addr(pos));
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static void __uses_jump_to_uncached clear_pmb_entry(int pos)
|
||
|
{
|
||
|
jump_to_uncached();
|
||
|
__clear_pmb_entry(pos);
|
||
|
back_to_cached();
|
||
|
}
|
||
|
|
||
|
static int pmb_alloc(int pos)
|
||
|
{
|
||
|
if (likely(pos == PMB_NO_ENTRY))
|
||
|
pos = find_first_zero_bit(&pmb_map, NR_PMB_ENTRIES);
|
||
|
|
||
|
repeat:
|
||
|
if (unlikely(pos >= NR_PMB_ENTRIES))
|
||
|
return PMB_NO_ENTRY;
|
||
|
|
||
|
if (test_and_set_bit(pos, &pmb_map)) {
|
||
|
pos = find_first_zero_bit(&pmb_map, NR_PMB_ENTRIES);
|
||
|
goto repeat;
|
||
|
}
|
||
|
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
static void pmb_free(int entry)
|
||
|
{
|
||
|
clear_bit(entry, &pmb_map);
|
||
|
}
|
||
|
|
||
|
static struct pmb_mapping* pmb_mapping_alloc(void)
|
||
|
{
|
||
|
struct pmb_mapping *mapping;
|
||
|
|
||
|
if (pmb_mappings_free == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
mapping = pmb_mappings_free;
|
||
|
pmb_mappings_free = mapping->next;
|
||
|
|
||
|
memset(mapping, 0, sizeof(*mapping));
|
||
|
|
||
|
return mapping;
|
||
|
}
|
||
|
|
||
|
static void pmb_mapping_free(struct pmb_mapping* mapping)
|
||
|
{
|
||
|
mapping->next = pmb_mappings_free;
|
||
|
pmb_mappings_free = mapping;
|
||
|
}
|
||
|
|
||
|
static __always_inline void __pmb_mapping_set(struct pmb_mapping *mapping)
|
||
|
{
|
||
|
struct pmb_entry *entry = mapping->entries;
|
||
|
|
||
|
do {
|
||
|
__set_pmb_entry(entry->vpn, entry->ppn,
|
||
|
entry->flags | mapping->flags, entry->pos);
|
||
|
entry = entry->next;
|
||
|
} while (entry);
|
||
|
}
|
||
|
|
||
|
static void pmb_mapping_set(struct pmb_mapping *mapping)
|
||
|
{
|
||
|
struct pmb_entry *entry = mapping->entries;
|
||
|
|
||
|
do {
|
||
|
set_pmb_entry(entry->vpn, entry->ppn,
|
||
|
entry->flags | mapping->flags, entry->pos);
|
||
|
entry = entry->next;
|
||
|
} while (entry);
|
||
|
}
|
||
|
|
||
|
static void pmb_mapping_clear_and_free(struct pmb_mapping *mapping)
|
||
|
{
|
||
|
struct pmb_entry *entry = mapping->entries;
|
||
|
|
||
|
do {
|
||
|
clear_pmb_entry(entry->pos);
|
||
|
pmb_free(entry->pos);
|
||
|
entry = entry->next;
|
||
|
} while (entry);
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_PMB_64M_TILES
|
||
|
|
||
|
static struct {
|
||
|
unsigned long size;
|
||
|
int flag;
|
||
|
} pmb_sizes[] = {
|
||
|
{ .size = 1 << PMB_FIXED_SHIFT, .flag = PMB_SZ_64M, },
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Different algorithm when we tile the entire P1/P2 region with
|
||
|
* 64M PMB entries. This means the PMB entry is tied to the virtual
|
||
|
* address it covers, so we only need to search for the virtual
|
||
|
* address which accomodates the mapping we're interested in.
|
||
|
*/
|
||
|
static struct pmb_mapping* pmb_calc(unsigned long phys, unsigned long size,
|
||
|
unsigned long req_virt, int *req_pos,
|
||
|
unsigned long pmb_flags)
|
||
|
{
|
||
|
struct pmb_mapping *new_mapping;
|
||
|
struct pmb_mapping **prev_ptr;
|
||
|
unsigned long prev_end, next_start;
|
||
|
struct pmb_mapping *next_mapping;
|
||
|
unsigned long new_start, new_end;
|
||
|
const unsigned long pmb_size = pmb_sizes[0].size;
|
||
|
struct pmb_entry *entry;
|
||
|
struct pmb_entry **prev_entry_ptr;
|
||
|
|
||
|
if (size == 0)
|
||
|
return NULL;
|
||
|
|
||
|
new_mapping = pmb_mapping_alloc();
|
||
|
if (!new_mapping)
|
||
|
return NULL;
|
||
|
|
||
|
DPRINTK("request: phys %08lx, size %08lx\n", phys, size);
|
||
|
|
||
|
prev_end = P1SEG;
|
||
|
next_mapping = pmb_mappings;
|
||
|
prev_ptr = &pmb_mappings;
|
||
|
for (;;) {
|
||
|
if (next_mapping == NULL)
|
||
|
next_start = P3SEG;
|
||
|
else
|
||
|
next_start = next_mapping->virt;
|
||
|
|
||
|
DPRINTK("checking space between %08lx and %08lx\n",
|
||
|
prev_end, next_start);
|
||
|
|
||
|
if (req_virt) {
|
||
|
if ((req_virt < prev_end) || (req_virt > next_start))
|
||
|
goto next;
|
||
|
new_start = req_virt;
|
||
|
} else {
|
||
|
new_start = prev_end + (phys & (pmb_size-1));
|
||
|
}
|
||
|
|
||
|
new_end = new_start + size;
|
||
|
|
||
|
if (new_end <= next_start)
|
||
|
break;
|
||
|
|
||
|
next:
|
||
|
if (next_mapping == NULL) {
|
||
|
DPRINTK("failed, give up\n");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
prev_ptr = &next_mapping->next;
|
||
|
prev_end = next_mapping->virt + next_mapping->size;
|
||
|
next_mapping = next_mapping->next;
|
||
|
}
|
||
|
|
||
|
DPRINTK("found space at %08lx to %08lx\n", new_start, new_end);
|
||
|
|
||
|
BUG_ON(req_pos && (*req_pos != PMB_VIRT2POS(new_start)));
|
||
|
|
||
|
phys &= ~(pmb_size - 1);
|
||
|
new_start &= ~(pmb_size - 1);
|
||
|
|
||
|
new_mapping->phys = phys;
|
||
|
new_mapping->virt = new_start;
|
||
|
new_mapping->size = 0;
|
||
|
new_mapping->flags = pmb_flags;
|
||
|
new_mapping->entries = NULL;
|
||
|
new_mapping->usage = 1;
|
||
|
new_mapping->next = *prev_ptr;
|
||
|
*prev_ptr = new_mapping;
|
||
|
|
||
|
prev_entry_ptr = &new_mapping->entries;
|
||
|
while (new_start < new_end) {
|
||
|
int pos = PMB_VIRT2POS(new_start);
|
||
|
|
||
|
pos = pmb_alloc(pos);
|
||
|
BUG_ON(pos == PMB_NO_ENTRY);
|
||
|
DPRINTK("using PMB entry %d\n", pos);
|
||
|
|
||
|
entry = &pmbe[pos];
|
||
|
entry->vpn = new_start;
|
||
|
entry->ppn = phys;
|
||
|
entry->flags = pmb_sizes[0].flag;
|
||
|
entry->next = NULL;
|
||
|
entry->size = pmb_size;
|
||
|
*prev_entry_ptr = entry;
|
||
|
prev_entry_ptr = &entry->next;
|
||
|
|
||
|
new_start += pmb_size;
|
||
|
phys += pmb_size;
|
||
|
new_mapping->size += pmb_size;
|
||
|
}
|
||
|
|
||
|
return new_mapping;
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
|
||
|
static struct {
|
||
|
unsigned long size;
|
||
|
int flag;
|
||
|
} pmb_sizes[] = {
|
||
|
{ .size = 0x01000000, .flag = PMB_SZ_16M, },
|
||
|
{ .size = 0x04000000, .flag = PMB_SZ_64M, },
|
||
|
{ .size = 0x08000000, .flag = PMB_SZ_128M, },
|
||
|
{ .size = 0x20000000, .flag = PMB_SZ_512M, },
|
||
|
};
|
||
|
|
||
|
static struct pmb_mapping* pmb_calc(unsigned long phys, unsigned long size,
|
||
|
unsigned long req_virt, int *req_pos,
|
||
|
unsigned long pmb_flags)
|
||
|
{
|
||
|
unsigned long orig_phys = phys;
|
||
|
unsigned long orig_size = size;
|
||
|
int max_i = ARRAY_SIZE(pmb_sizes)-1;
|
||
|
struct pmb_mapping *new_mapping;
|
||
|
unsigned long alignment;
|
||
|
unsigned long virt_offset;
|
||
|
struct pmb_entry **prev_entry_ptr;
|
||
|
unsigned long prev_end, next_start;
|
||
|
struct pmb_mapping *next_mapping;
|
||
|
struct pmb_mapping **prev_ptr;
|
||
|
struct pmb_entry *entry;
|
||
|
unsigned long start;
|
||
|
|
||
|
if (size == 0)
|
||
|
return NULL;
|
||
|
|
||
|
new_mapping = pmb_mapping_alloc();
|
||
|
if (!new_mapping)
|
||
|
return NULL;
|
||
|
|
||
|
DPRINTK("request: phys %08lx, size %08lx\n", phys, size);
|
||
|
|
||
|
/*
|
||
|
* First work out the PMB entries to tile the physical region.
|
||
|
*
|
||
|
* Fill in new_mapping and its list of entries, all fields
|
||
|
* except those related to virtual addresses.
|
||
|
*
|
||
|
* alignment is the maximum alignment of all of the entries which
|
||
|
* make up the mapping.
|
||
|
* virt_offset will be non-zero in case some of the entries leading
|
||
|
* upto those which force the maximal alignment are smaller than
|
||
|
* those largest ones, and in this case virt_offset must be added
|
||
|
* to the eventual virtual address (which is aligned to alignment),
|
||
|
* to get the virtual address of the first entry.
|
||
|
*/
|
||
|
retry:
|
||
|
phys = orig_phys;
|
||
|
size = orig_size;
|
||
|
alignment = 0;
|
||
|
virt_offset = 0;
|
||
|
prev_entry_ptr = &new_mapping->entries;
|
||
|
new_mapping->size = 0;
|
||
|
while (size > 0) {
|
||
|
unsigned long best_size; /* bytes of size covered by tile */
|
||
|
int best_i;
|
||
|
unsigned long entry_phys;
|
||
|
unsigned long entry_size; /* total size of tile */
|
||
|
int i;
|
||
|
|
||
|
entry = *prev_entry_ptr;
|
||
|
if (entry == NULL) {
|
||
|
int pos;
|
||
|
|
||
|
pos = pmb_alloc(req_pos ? *req_pos++ : PMB_NO_ENTRY);
|
||
|
if (pos == PMB_NO_ENTRY)
|
||
|
goto failed_give_up;
|
||
|
entry = &pmbe[pos];
|
||
|
entry->next = NULL;
|
||
|
*prev_entry_ptr = entry;
|
||
|
}
|
||
|
prev_entry_ptr = &entry->next;
|
||
|
|
||
|
/*
|
||
|
* Calculate the 'best' PMB entry size. This is the
|
||
|
* one which covers the largest amount of the physical
|
||
|
* address range we are trying to map, but if
|
||
|
* increasing the size wouldn't increase the amount we
|
||
|
* would be able to map, don't bother. Similarly, if
|
||
|
* increasing the size would result in a mapping where
|
||
|
* half or more of the coverage is wasted, don't bother.
|
||
|
*/
|
||
|
best_size = best_i = 0;
|
||
|
for (i = 0; i <= max_i; i++) {
|
||
|
unsigned long pmb_size = pmb_sizes[i].size;
|
||
|
unsigned long tmp_start, tmp_end, tmp_size;
|
||
|
tmp_start = phys & ~(pmb_size-1);
|
||
|
tmp_end = tmp_start + pmb_size;
|
||
|
tmp_size = min(phys+size, tmp_end)-max(phys, tmp_start);
|
||
|
if (tmp_size <= best_size)
|
||
|
continue;
|
||
|
|
||
|
if (best_size) {
|
||
|
unsigned long wasted_size;
|
||
|
wasted_size = pmb_size - tmp_size;
|
||
|
if (wasted_size >= (pmb_size / 2))
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
best_i = i;
|
||
|
best_size = tmp_size;
|
||
|
}
|
||
|
|
||
|
BUG_ON(best_size == 0);
|
||
|
|
||
|
entry_size = pmb_sizes[best_i].size;
|
||
|
entry_phys = phys & ~(entry_size-1);
|
||
|
DPRINTK("using PMB %d: phys %08lx, size %08lx\n",
|
||
|
entry->pos, entry_phys, entry_size);
|
||
|
|
||
|
entry->ppn = entry_phys;
|
||
|
entry->size = entry_size;
|
||
|
entry->flags = pmb_sizes[best_i].flag;
|
||
|
|
||
|
if (pmb_sizes[best_i].size > alignment) {
|
||
|
alignment = entry_size;
|
||
|
if (new_mapping->size)
|
||
|
virt_offset = alignment - new_mapping->size;
|
||
|
}
|
||
|
|
||
|
new_mapping->size += entry_size;
|
||
|
size -= best_size;
|
||
|
phys += best_size;
|
||
|
}
|
||
|
|
||
|
new_mapping->phys = new_mapping->entries->ppn;
|
||
|
|
||
|
DPRINTK("mapping: phys %08lx, size %08lx\n", new_mapping->phys, new_mapping->size);
|
||
|
DPRINTK("virtual alignment %08lx, offset %08lx\n", alignment, virt_offset);
|
||
|
|
||
|
/* Each iteration should use at least as many entries previous ones */
|
||
|
BUG_ON(entry->next);
|
||
|
|
||
|
/* Do we have a conflict with the requested maping? */
|
||
|
BUG_ON(req_virt && ((req_virt & (alignment-1)) != virt_offset));
|
||
|
|
||
|
/* Next try and find a virtual address to map this */
|
||
|
prev_end = P1SEG;
|
||
|
next_mapping = pmb_mappings;
|
||
|
prev_ptr = &pmb_mappings;
|
||
|
do {
|
||
|
if (next_mapping == NULL)
|
||
|
next_start = P3SEG;
|
||
|
else
|
||
|
next_start = next_mapping->virt;
|
||
|
|
||
|
if (req_virt)
|
||
|
start = req_virt;
|
||
|
else
|
||
|
start = ALIGN(prev_end, alignment) + virt_offset;
|
||
|
|
||
|
DPRINTK("checking for virt %08lx between %08lx and %08lx\n",
|
||
|
start, prev_end, next_start);
|
||
|
|
||
|
if ((start >= prev_end) &&
|
||
|
(start + new_mapping->size <= next_start))
|
||
|
break;
|
||
|
|
||
|
if (next_mapping == NULL)
|
||
|
goto failed;
|
||
|
|
||
|
prev_ptr = &next_mapping->next;
|
||
|
prev_end = next_mapping->virt + next_mapping->size;
|
||
|
next_mapping = next_mapping->next;
|
||
|
} while (1);
|
||
|
|
||
|
DPRINTK("success, using %08lx\n", start);
|
||
|
new_mapping->virt = start;
|
||
|
new_mapping->flags = pmb_flags;
|
||
|
new_mapping->usage = 1;
|
||
|
new_mapping->next = *prev_ptr;
|
||
|
*prev_ptr = new_mapping;
|
||
|
|
||
|
/* Finally fill in the vpn's */
|
||
|
for (entry = new_mapping->entries; entry; entry=entry->next) {
|
||
|
entry->vpn = start;
|
||
|
start += entry->size;
|
||
|
}
|
||
|
|
||
|
return new_mapping;
|
||
|
|
||
|
failed:
|
||
|
if (--max_i >= 0) {
|
||
|
DPRINTK("failed, try again with max_i %d\n", max_i);
|
||
|
goto retry;
|
||
|
}
|
||
|
|
||
|
failed_give_up:
|
||
|
DPRINTK("failed, give up\n");
|
||
|
for (entry = new_mapping->entries; entry; entry = entry->next)
|
||
|
pmb_free(entry->pos);
|
||
|
pmb_mapping_free(new_mapping);
|
||
|
return NULL;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
long pmb_remap(unsigned long phys,
|
||
|
unsigned long size, unsigned long flags)
|
||
|
{
|
||
|
struct pmb_mapping *mapping;
|
||
|
int pmb_flags;
|
||
|
unsigned long offset;
|
||
|
|
||
|
/* Convert typical pgprot value to the PMB equivalent */
|
||
|
if (flags & _PAGE_CACHABLE) {
|
||
|
if (flags & _PAGE_WT)
|
||
|
pmb_flags = PMB_WT;
|
||
|
else
|
||
|
pmb_flags = PMB_C;
|
||
|
} else
|
||
|
pmb_flags = PMB_WT | PMB_UB;
|
||
|
|
||
|
DPRINTK("phys: %08lx, size %08lx, flags %08lx->%08x\n",
|
||
|
phys, size, flags, pmb_flags);
|
||
|
|
||
|
write_lock(&pmb_lock);
|
||
|
|
||
|
for (mapping = pmb_mappings; mapping; mapping=mapping->next) {
|
||
|
DPRINTK("check against phys %08lx size %08lx flags %08lx\n",
|
||
|
mapping->phys, mapping->size, mapping->flags);
|
||
|
if ((phys >= mapping->phys) &&
|
||
|
(phys+size <= mapping->phys+mapping->size) &&
|
||
|
(pmb_flags == mapping->flags))
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (mapping) {
|
||
|
/* If we hit an existing mapping, use it */
|
||
|
mapping->usage++;
|
||
|
DPRINTK("found, usage now %d\n", mapping->usage);
|
||
|
} else if (size < MIN_PMB_MAPPING_SIZE) {
|
||
|
/* We spit upon small mappings */
|
||
|
write_unlock(&pmb_lock);
|
||
|
return 0;
|
||
|
} else {
|
||
|
mapping = pmb_calc(phys, size, 0, NULL, pmb_flags);
|
||
|
if (!mapping) {
|
||
|
write_unlock(&pmb_lock);
|
||
|
return 0;
|
||
|
}
|
||
|
pmb_mapping_set(mapping);
|
||
|
}
|
||
|
|
||
|
write_unlock(&pmb_lock);
|
||
|
|
||
|
offset = phys - mapping->phys;
|
||
|
return mapping->virt + offset;
|
||
|
}
|
||
|
|
||
|
static struct pmb_mapping *pmb_mapping_find(unsigned long addr,
|
||
|
struct pmb_mapping ***prev)
|
||
|
{
|
||
|
struct pmb_mapping *mapping;
|
||
|
struct pmb_mapping **prev_mapping = &pmb_mappings;
|
||
|
|
||
|
for (mapping = pmb_mappings; mapping; mapping=mapping->next) {
|
||
|
if ((addr >= mapping->virt) &&
|
||
|
(addr < mapping->virt + mapping->size))
|
||
|
break;
|
||
|
prev_mapping = &mapping->next;
|
||
|
}
|
||
|
|
||
|
if (prev != NULL)
|
||
|
*prev = prev_mapping;
|
||
|
|
||
|
return mapping;
|
||
|
}
|
||
|
|
||
|
int pmb_unmap(unsigned long addr)
|
||
|
{
|
||
|
struct pmb_mapping *mapping;
|
||
|
struct pmb_mapping **prev_mapping;
|
||
|
|
||
|
write_lock(&pmb_lock);
|
||
|
|
||
|
mapping = pmb_mapping_find(addr, &prev_mapping);
|
||
|
|
||
|
if (unlikely(!mapping)) {
|
||
|
write_unlock(&pmb_lock);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DPRINTK("mapping: phys %08lx, size %08lx, count %d\n",
|
||
|
mapping->phys, mapping->size, mapping->usage);
|
||
|
|
||
|
if (--mapping->usage == 0) {
|
||
|
pmb_mapping_clear_and_free(mapping);
|
||
|
*prev_mapping = mapping->next;
|
||
|
pmb_mapping_free(mapping);
|
||
|
}
|
||
|
|
||
|
write_unlock(&pmb_lock);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static void noinline __uses_jump_to_uncached
|
||
|
apply_boot_mappings(struct pmb_mapping *uc_mapping, struct pmb_mapping *ram_mapping)
|
||
|
{
|
||
|
register int i __asm__("r1");
|
||
|
register unsigned long c2uc __asm__("r2");
|
||
|
register struct pmb_entry *entry __asm__("r3");
|
||
|
register unsigned long flags __asm__("r4");
|
||
|
|
||
|
/* We can execute this directly, as the current PMB is uncached */
|
||
|
__pmb_mapping_set(uc_mapping);
|
||
|
|
||
|
cached_to_uncached = uc_mapping->virt -
|
||
|
(((unsigned long)&__uncached_start) & ~(uc_mapping->entries->size-1));
|
||
|
|
||
|
jump_to_uncached();
|
||
|
|
||
|
/*
|
||
|
* We have to be cautious here, as we will temporarily lose access to
|
||
|
* the PMB entry which is mapping main RAM, and so loose access to
|
||
|
* data. So make sure all data is going to be in registers or the
|
||
|
* uncached region.
|
||
|
*/
|
||
|
|
||
|
c2uc = cached_to_uncached;
|
||
|
entry = ram_mapping->entries;
|
||
|
flags = ram_mapping->flags;
|
||
|
|
||
|
for (i=0; i<NR_PMB_ENTRIES-1; i++)
|
||
|
__clear_pmb_entry(i);
|
||
|
|
||
|
do {
|
||
|
entry = (struct pmb_entry*)(((unsigned long)entry) + c2uc);
|
||
|
__set_pmb_entry(entry->vpn, entry->ppn,
|
||
|
entry->flags | flags, entry->pos);
|
||
|
entry = entry->next;
|
||
|
} while (entry);
|
||
|
|
||
|
/* Flush out the TLB */
|
||
|
i = ctrl_inl(MMUCR);
|
||
|
i |= MMUCR_TI;
|
||
|
ctrl_outl(i, MMUCR);
|
||
|
|
||
|
back_to_cached();
|
||
|
}
|
||
|
|
||
|
struct pmb_mapping *uc_mapping, *ram_mapping
|
||
|
__attribute__ ((__section__ (".uncached.data")));
|
||
|
|
||
|
void __init pmb_init(void)
|
||
|
{
|
||
|
int i;
|
||
|
int entry;
|
||
|
|
||
|
/* Create the free list of mappings */
|
||
|
pmb_mappings_free = &pmbm[0];
|
||
|
for (i=0; i<NR_PMB_ENTRIES-1; i++)
|
||
|
pmbm[i].next = &pmbm[i+1];
|
||
|
pmbm[NR_PMB_ENTRIES-1].next = NULL;
|
||
|
|
||
|
/* Initialise the PMB entrie's pos */
|
||
|
for (i=0; i<NR_PMB_ENTRIES; i++)
|
||
|
pmbe[i].pos = i;
|
||
|
|
||
|
/* Create the initial mappings */
|
||
|
entry = NR_PMB_ENTRIES-1;
|
||
|
uc_mapping = pmb_calc(__pa(&__uncached_start), &__uncached_end - &__uncached_start,
|
||
|
P3SEG-pmb_sizes[0].size, &entry, PMB_WT | PMB_UB);
|
||
|
ram_mapping = pmb_calc(__MEMORY_START, __MEMORY_SIZE, P1SEG, 0, PMB_C);
|
||
|
apply_boot_mappings(uc_mapping, ram_mapping);
|
||
|
}
|
||
|
|
||
|
int pmb_virt_to_phys(void *addr, unsigned long *phys, unsigned long *flags)
|
||
|
{
|
||
|
struct pmb_mapping *mapping;
|
||
|
unsigned long vaddr = (unsigned long __force)addr;
|
||
|
|
||
|
read_lock(&pmb_lock);
|
||
|
|
||
|
mapping = pmb_mapping_find(vaddr, NULL);
|
||
|
if (!mapping) {
|
||
|
read_unlock(&pmb_lock);
|
||
|
return EFAULT;
|
||
|
}
|
||
|
|
||
|
if (phys)
|
||
|
*phys = mapping->phys + (vaddr - mapping->virt);
|
||
|
if (flags)
|
||
|
*flags = mapping->flags;
|
||
|
|
||
|
read_unlock(&pmb_lock);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(pmb_virt_to_phys);
|
||
|
|
||
|
bool __in_29bit_mode(void)
|
||
|
{
|
||
|
#ifdef CONFIG_CPU_SUBTYPE_STX7100
|
||
|
/* ST40-200 used a different mechanism to control SE mode */
|
||
|
return (__raw_readl(MMUCR) & MMUCR_SE) == 0;
|
||
|
#else
|
||
|
return (__raw_readl(PMB_PASCR) & PASCR_SE) == 0;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static int pmb_seq_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 = ctrl_inl(mk_pmb_addr(i));
|
||
|
data = ctrl_inl(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;
|
||
|
}
|
||
|
|
||
|
static int pmb_debugfs_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
return single_open(file, pmb_seq_show, NULL);
|
||
|
}
|
||
|
|
||
|
static const struct file_operations pmb_debugfs_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = pmb_debugfs_open,
|
||
|
.read = seq_read,
|
||
|
.llseek = seq_lseek,
|
||
|
.release = single_release,
|
||
|
};
|
||
|
|
||
|
static int __init pmb_debugfs_init(void)
|
||
|
{
|
||
|
struct dentry *dentry;
|
||
|
|
||
|
dentry = debugfs_create_file("pmb", S_IFREG | S_IRUGO,
|
||
|
sh_debugfs_root, NULL, &pmb_debugfs_fops);
|
||
|
if (!dentry)
|
||
|
return -ENOMEM;
|
||
|
if (IS_ERR(dentry))
|
||
|
return PTR_ERR(dentry);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
subsys_initcall(pmb_debugfs_init);
|
||
|
|
||
|
#ifdef CONFIG_PM
|
||
|
static __uses_jump_to_uncached
|
||
|
int pmb_sysdev_suspend(struct sys_device *dev, pm_message_t state)
|
||
|
{
|
||
|
static pm_message_t prev_state;
|
||
|
int idx;
|
||
|
switch (state.event) {
|
||
|
case PM_EVENT_ON:
|
||
|
/* Resumeing from hibernation */
|
||
|
if (prev_state.event == PM_EVENT_FREEZE) {
|
||
|
for (idx = 1; idx < NR_PMB_ENTRIES; ++idx)
|
||
|
if (pmbm[idx].usage && pmbm[idx].virt != 0xbf)
|
||
|
pmb_mapping_set(&pmbm[idx]);
|
||
|
flush_cache_all();
|
||
|
}
|
||
|
break;
|
||
|
case PM_EVENT_SUSPEND:
|
||
|
break;
|
||
|
case PM_EVENT_FREEZE:
|
||
|
break;
|
||
|
}
|
||
|
prev_state = state;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int pmb_sysdev_resume(struct sys_device *dev)
|
||
|
{
|
||
|
return pmb_sysdev_suspend(dev, PMSG_ON);
|
||
|
}
|
||
|
|
||
|
static struct sysdev_driver pmb_sysdev_driver = {
|
||
|
.suspend = pmb_sysdev_suspend,
|
||
|
.resume = pmb_sysdev_resume,
|
||
|
};
|
||
|
|
||
|
static int __init pmb_sysdev_init(void)
|
||
|
{
|
||
|
return sysdev_driver_register(&cpu_sysdev_class, &pmb_sysdev_driver);
|
||
|
}
|
||
|
|
||
|
subsys_initcall(pmb_sysdev_init);
|
||
|
|
||
|
#ifdef CONFIG_HIBERNATION_ON_MEMORY
|
||
|
|
||
|
void __uses_jump_to_uncached stm_hom_pmb_init(void)
|
||
|
{
|
||
|
apply_boot_mappings(uc_mapping, ram_mapping);
|
||
|
|
||
|
/* Now I can call the pmb_sysdev_resume */
|
||
|
pmb_sysdev_suspend(NULL, PMSG_ON);
|
||
|
}
|
||
|
#endif
|
||
|
#endif
|