cache-sh4.c 9.8 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4
/*
 * arch/sh/mm/cache-sh4.c
 *
 * Copyright (C) 1999, 2000, 2002  Niibe Yutaka
5
 * Copyright (C) 2001 - 2009  Paul Mundt
L
Linus Torvalds 已提交
6
 * Copyright (C) 2003  Richard Curnow
7
 * Copyright (c) 2007 STMicroelectronics (R&D) Ltd.
L
Linus Torvalds 已提交
8 9 10 11 12 13 14
 *
 * 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/mm.h>
P
Paul Mundt 已提交
15 16
#include <linux/io.h>
#include <linux/mutex.h>
17
#include <linux/fs.h>
18 19
#include <linux/highmem.h>
#include <asm/pgtable.h>
L
Linus Torvalds 已提交
20 21 22
#include <asm/mmu_context.h>
#include <asm/cacheflush.h>

23 24 25 26 27
/*
 * The maximum number of pages we support up to when doing ranged dcache
 * flushing. Anything exceeding this will simply flush the dcache in its
 * entirety.
 */
28
#define MAX_ICACHE_PAGES	32
29

30
static void __flush_cache_one(unsigned long addr, unsigned long phys,
31
			       unsigned long exec_offset);
32

L
Linus Torvalds 已提交
33 34 35
/*
 * Write back the range of D-cache, and purge the I-cache.
 *
36 37
 * Called from kernel/module.c:sys_init_module and routine for a.out format,
 * signal handler code and kprobes code
L
Linus Torvalds 已提交
38
 */
39
static void __uses_jump_to_uncached sh4_flush_icache_range(void *args)
L
Linus Torvalds 已提交
40
{
P
Paul Mundt 已提交
41 42
	struct flusher_data *data = args;
	unsigned long start, end;
43
	unsigned long flags, v;
L
Linus Torvalds 已提交
44 45
	int i;

P
Paul Mundt 已提交
46 47 48
	start = data->addr1;
	end = data->addr2;

49 50 51 52 53 54 55 56 57 58 59 60 61
	/* If there are too many pages then just blow away the caches */
	if (((end - start) >> PAGE_SHIFT) >= MAX_ICACHE_PAGES) {
		local_flush_cache_all(NULL);
		return;
	}

	/*
	 * Selectively flush d-cache then invalidate the i-cache.
	 * This is inefficient, so only use this for small ranges.
	 */
	start &= ~(L1_CACHE_BYTES-1);
	end += L1_CACHE_BYTES-1;
	end &= ~(L1_CACHE_BYTES-1);
62

63 64
	local_irq_save(flags);
	jump_to_uncached();
65

66 67
	for (v = start; v < end; v += L1_CACHE_BYTES) {
		unsigned long icacheaddr;
68
		int j, n;
69

70
		__ocbwb(v);
71

72 73
		icacheaddr = CACHE_IC_ADDRESS_ARRAY | (v &
				cpu_data->icache.entry_mask);
74

75
		/* Clear i-cache line valid-bit */
76
		n = boot_cpu_data.icache.n_aliases;
77
		for (i = 0; i < cpu_data->icache.ways; i++) {
78 79
			for (j = 0; j < n; j++)
				__raw_writel(0, icacheaddr + (j * PAGE_SIZE));
80 81
			icacheaddr += cpu_data->icache.way_incr;
		}
82
	}
83 84 85

	back_to_cached();
	local_irq_restore(flags);
L
Linus Torvalds 已提交
86 87
}

88
static inline void flush_cache_one(unsigned long start, unsigned long phys)
L
Linus Torvalds 已提交
89
{
90
	unsigned long flags, exec_offset = 0;
91

L
Linus Torvalds 已提交
92
	/*
M
Matt Fleming 已提交
93 94
	 * All types of SH-4 require PC to be uncached to operate on the I-cache.
	 * Some types of SH-4 require PC to be uncached to operate on the D-cache.
L
Linus Torvalds 已提交
95
	 */
96
	if ((boot_cpu_data.flags & CPU_HAS_P2_FLUSH_BUG) ||
97
	    (start < CACHE_OC_ADDRESS_ARRAY))
M
Matt Fleming 已提交
98
		exec_offset = cached_to_uncached;
99

100
	local_irq_save(flags);
101
	__flush_cache_one(start, phys, exec_offset);
102
	local_irq_restore(flags);
L
Linus Torvalds 已提交
103 104 105 106 107 108
}

/*
 * Write back & invalidate the D-cache of the page.
 * (To avoid "alias" issues)
 */
109
static void sh4_flush_dcache_page(void *arg)
L
Linus Torvalds 已提交
110
{
111
	struct page *page = arg;
P
Paul Mundt 已提交
112
#ifndef CONFIG_SMP
113 114 115 116 117 118 119
	struct address_space *mapping = page_mapping(page);

	if (mapping && !mapping_mapped(mapping))
		set_bit(PG_dcache_dirty, &page->flags);
	else
#endif
	{
120
		unsigned long phys = page_to_phys(page);
121 122
		unsigned long addr = CACHE_OC_ADDRESS_ARRAY;
		int i, n;
L
Linus Torvalds 已提交
123 124

		/* Loop all the D-cache */
125
		n = boot_cpu_data.dcache.n_aliases;
126
		for (i = 0; i < n; i++, addr += PAGE_SIZE)
127
			flush_cache_one(addr, phys);
L
Linus Torvalds 已提交
128
	}
129 130

	wmb();
L
Linus Torvalds 已提交
131 132
}

133
/* TODO: Selective icache invalidation through IC address array.. */
P
Paul Mundt 已提交
134
static void __uses_jump_to_uncached flush_icache_all(void)
L
Linus Torvalds 已提交
135
{
136
	unsigned long flags, ccr;
L
Linus Torvalds 已提交
137

138
	local_irq_save(flags);
139
	jump_to_uncached();
L
Linus Torvalds 已提交
140 141 142 143 144 145

	/* Flush I-cache */
	ccr = ctrl_inl(CCR);
	ccr |= CCR_CACHE_ICI;
	ctrl_outl(ccr, CCR);

P
Paul Mundt 已提交
146
	/*
147
	 * back_to_cached() will take care of the barrier for us, don't add
P
Paul Mundt 已提交
148 149
	 * another one!
	 */
150

151
	back_to_cached();
152
	local_irq_restore(flags);
L
Linus Torvalds 已提交
153 154
}

155
static void flush_dcache_all(void)
L
Linus Torvalds 已提交
156
{
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
	unsigned long addr, end_addr, entry_offset;

	end_addr = CACHE_OC_ADDRESS_ARRAY +
		(current_cpu_data.dcache.sets <<
		 current_cpu_data.dcache.entry_shift) *
			current_cpu_data.dcache.ways;

	entry_offset = 1 << current_cpu_data.dcache.entry_shift;

	for (addr = CACHE_OC_ADDRESS_ARRAY; addr < end_addr; ) {
		__raw_writel(0, addr); addr += entry_offset;
		__raw_writel(0, addr); addr += entry_offset;
		__raw_writel(0, addr); addr += entry_offset;
		__raw_writel(0, addr); addr += entry_offset;
		__raw_writel(0, addr); addr += entry_offset;
		__raw_writel(0, addr); addr += entry_offset;
		__raw_writel(0, addr); addr += entry_offset;
		__raw_writel(0, addr); addr += entry_offset;
	}
176 177
}

P
Paul Mundt 已提交
178
static void sh4_flush_cache_all(void *unused)
179 180
{
	flush_dcache_all();
L
Linus Torvalds 已提交
181 182 183
	flush_icache_all();
}

184 185 186 187 188 189
/*
 * Note : (RPC) since the caches are physically tagged, the only point
 * of flush_cache_mm for SH-4 is to get rid of aliases from the
 * D-cache.  The assumption elsewhere, e.g. flush_cache_range, is that
 * lines can stay resident so long as the virtual address they were
 * accessed with (hence cache set) is in accord with the physical
190
 * address (i.e. tag).  It's no different here.
191 192 193
 *
 * Caller takes mm->mmap_sem.
 */
P
Paul Mundt 已提交
194
static void sh4_flush_cache_mm(void *arg)
L
Linus Torvalds 已提交
195
{
P
Paul Mundt 已提交
196 197
	struct mm_struct *mm = arg;

198 199 200
	if (cpu_context(smp_processor_id(), mm) == NO_CONTEXT)
		return;

201
	flush_dcache_all();
L
Linus Torvalds 已提交
202 203 204 205 206 207 208 209
}

/*
 * Write back and invalidate I/D-caches for the page.
 *
 * ADDR: Virtual Address (U0 address)
 * PFN: Physical page number
 */
P
Paul Mundt 已提交
210
static void sh4_flush_cache_page(void *args)
L
Linus Torvalds 已提交
211
{
P
Paul Mundt 已提交
212 213
	struct flusher_data *data = args;
	struct vm_area_struct *vma;
214
	struct page *page;
P
Paul Mundt 已提交
215
	unsigned long address, pfn, phys;
216 217 218 219 220 221
	int map_coherent = 0;
	pgd_t *pgd;
	pud_t *pud;
	pmd_t *pmd;
	pte_t *pte;
	void *vaddr;
222

P
Paul Mundt 已提交
223
	vma = data->vma;
P
Paul Mundt 已提交
224
	address = data->addr1 & PAGE_MASK;
P
Paul Mundt 已提交
225 226
	pfn = data->addr2;
	phys = pfn << PAGE_SHIFT;
227
	page = pfn_to_page(pfn);
P
Paul Mundt 已提交
228

229 230 231
	if (cpu_context(smp_processor_id(), vma->vm_mm) == NO_CONTEXT)
		return;

232 233 234 235 236 237 238 239
	pgd = pgd_offset(vma->vm_mm, address);
	pud = pud_offset(pgd, address);
	pmd = pmd_offset(pud, address);
	pte = pte_offset_kernel(pmd, address);

	/* If the page isn't present, there is nothing to do here. */
	if (!(pte_val(*pte) & _PAGE_PRESENT))
		return;
L
Linus Torvalds 已提交
240

241 242 243
	if ((vma->vm_mm == current->active_mm))
		vaddr = NULL;
	else {
244
		/*
245 246
		 * Use kmap_coherent or kmap_atomic to do flushes for
		 * another ASID than the current one.
247
		 */
248 249 250 251 252 253 254 255 256 257 258 259
		map_coherent = (current_cpu_data.dcache.n_aliases &&
			!test_bit(PG_dcache_dirty, &page->flags) &&
			page_mapped(page));
		if (map_coherent)
			vaddr = kmap_coherent(page, address);
		else
			vaddr = kmap_atomic(page, KM_USER0);

		address = (unsigned long)vaddr;
	}

	if (pages_do_alias(address, phys))
P
Paul Mundt 已提交
260
		flush_cache_one(CACHE_OC_ADDRESS_ARRAY |
261 262 263 264 265 266 267 268 269 270
			(address & shm_align_mask), phys);

	if (vma->vm_flags & VM_EXEC)
		flush_icache_all();

	if (vaddr) {
		if (map_coherent)
			kunmap_coherent(vaddr);
		else
			kunmap_atomic(vaddr, KM_USER0);
271
	}
L
Linus Torvalds 已提交
272 273 274 275 276 277 278 279 280 281 282
}

/*
 * Write back and invalidate D-caches.
 *
 * START, END: Virtual Address (U0 address)
 *
 * NOTE: We need to flush the _physical_ page entry.
 * Flushing the cache lines for U0 only isn't enough.
 * We need to flush for P1 too, which may contain aliases.
 */
P
Paul Mundt 已提交
283
static void sh4_flush_cache_range(void *args)
L
Linus Torvalds 已提交
284
{
P
Paul Mundt 已提交
285 286 287 288 289 290 291 292
	struct flusher_data *data = args;
	struct vm_area_struct *vma;
	unsigned long start, end;

	vma = data->vma;
	start = data->addr1;
	end = data->addr2;

293 294 295
	if (cpu_context(smp_processor_id(), vma->vm_mm) == NO_CONTEXT)
		return;

296 297 298 299
	/*
	 * If cache is only 4k-per-way, there are never any 'aliases'.  Since
	 * the cache is physically tagged, the data can just be left in there.
	 */
300
	if (boot_cpu_data.dcache.n_aliases == 0)
301 302
		return;

303
	flush_dcache_all();
304

305
	if (vma->vm_flags & VM_EXEC)
L
Linus Torvalds 已提交
306 307 308
		flush_icache_all();
}

309
/**
310
 * __flush_cache_one
311 312 313 314 315 316 317 318 319 320 321 322
 *
 * @addr:  address in memory mapped cache array
 * @phys:  P1 address to flush (has to match tags if addr has 'A' bit
 *         set i.e. associative write)
 * @exec_offset: set to 0x20000000 if flush has to be executed from P2
 *               region else 0x0
 *
 * The offset into the cache array implied by 'addr' selects the
 * 'colour' of the virtual address range that will be flushed.  The
 * operation (purge/write-back) is selected by the lower 2 bits of
 * 'phys'.
 */
323
static void __flush_cache_one(unsigned long addr, unsigned long phys,
324 325 326 327 328 329 330 331 332
			       unsigned long exec_offset)
{
	int way_count;
	unsigned long base_addr = addr;
	struct cache_info *dcache;
	unsigned long way_incr;
	unsigned long a, ea, p;
	unsigned long temp_pc;

333
	dcache = &boot_cpu_data.dcache;
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
	/* Write this way for better assembly. */
	way_count = dcache->ways;
	way_incr = dcache->way_incr;

	/*
	 * Apply exec_offset (i.e. branch to P2 if required.).
	 *
	 * FIXME:
	 *
	 *	If I write "=r" for the (temp_pc), it puts this in r6 hence
	 *	trashing exec_offset before it's been added on - why?  Hence
	 *	"=&r" as a 'workaround'
	 */
	asm volatile("mov.l 1f, %0\n\t"
		     "add   %1, %0\n\t"
		     "jmp   @%0\n\t"
		     "nop\n\t"
		     ".balign 4\n\t"
		     "1:  .long 2f\n\t"
		     "2:\n" : "=&r" (temp_pc) : "r" (exec_offset));

	/*
	 * We know there will be >=1 iteration, so write as do-while to avoid
	 * pointless nead-of-loop check for 0 iterations.
	 */
	do {
		ea = base_addr + PAGE_SIZE;
		a = base_addr;
		p = phys;

		do {
			*(volatile unsigned long *)a = p;
			/*
			 * Next line: intentionally not p+32, saves an add, p
			 * will do since only the cache tag bits need to
			 * match.
			 */
			*(volatile unsigned long *)(a+32) = p;
			a += 64;
			p += 64;
		} while (a < ea);

		base_addr += way_incr;
	} while (--way_count != 0);
}

380 381 382 383 384 385 386 387 388 389 390 391
extern void __weak sh4__flush_region_init(void);

/*
 * SH-4 has virtually indexed and physically tagged cache.
 */
void __init sh4_cache_init(void)
{
	printk("PVR=%08x CVR=%08x PRR=%08x\n",
		ctrl_inl(CCN_PVR),
		ctrl_inl(CCN_CVR),
		ctrl_inl(CCN_PRR));

P
Paul Mundt 已提交
392 393 394 395 396 397 398
	local_flush_icache_range	= sh4_flush_icache_range;
	local_flush_dcache_page		= sh4_flush_dcache_page;
	local_flush_cache_all		= sh4_flush_cache_all;
	local_flush_cache_mm		= sh4_flush_cache_mm;
	local_flush_cache_dup_mm	= sh4_flush_cache_mm;
	local_flush_cache_page		= sh4_flush_cache_page;
	local_flush_cache_range		= sh4_flush_cache_range;
399 400 401

	sh4__flush_region_init();
}