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_4096(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

69
		__ocbwb(v);
70

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

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

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

static inline void flush_cache_4096(unsigned long start,
				    unsigned long phys)
{
88
	unsigned long flags, exec_offset = 0;
89

L
Linus Torvalds 已提交
90
	/*
91 92
	 * All types of SH-4 require PC to be in P2 to operate on the I-cache.
	 * Some types of SH-4 require PC to be in P2 to operate on the D-cache.
L
Linus Torvalds 已提交
93
	 */
94
	if ((boot_cpu_data.flags & CPU_HAS_P2_FLUSH_BUG) ||
95
	    (start < CACHE_OC_ADDRESS_ARRAY))
96
		exec_offset = 0x20000000;
97

98
	local_irq_save(flags);
99
	__flush_cache_4096(start | SH_CACHE_ASSOC,
M
Matt Fleming 已提交
100
			   virt_to_phys(phys), exec_offset);
101
	local_irq_restore(flags);
L
Linus Torvalds 已提交
102 103 104 105 106 107
}

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

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

		/* Loop all the D-cache */
124
		n = boot_cpu_data.dcache.way_incr >> 12;
125
		for (i = 0; i < n; i++, addr += 4096)
126
			flush_cache_4096(addr, phys);
L
Linus Torvalds 已提交
127
	}
128 129

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

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

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

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

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

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

154
static void flush_dcache_all(void)
L
Linus Torvalds 已提交
155
{
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
	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;
	}
175 176
}

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

183 184 185 186 187 188
/*
 * 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
189
 * address (i.e. tag).  It's no different here.
190 191 192
 *
 * Caller takes mm->mmap_sem.
 */
P
Paul Mundt 已提交
193
static void sh4_flush_cache_mm(void *arg)
L
Linus Torvalds 已提交
194
{
P
Paul Mundt 已提交
195 196
	struct mm_struct *mm = arg;

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

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

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

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

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

231 232 233 234 235 236 237 238 239
	address &= PAGE_MASK;
	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 260 261 262 263 264 265 266 267 268 269 270
		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))
		flush_cache_4096(CACHE_OC_ADDRESS_ARRAY |
			(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 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
/**
 * __flush_cache_4096
 *
 * @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'.
 */
static void __flush_cache_4096(unsigned long addr, unsigned long phys,
			       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
	/* 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 {
360
		ea = base_addr + 4096;
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
		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();
}