1/*
2 * Copyright (C) 2004-2006 Atmel Corporation
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9#include <linux/highmem.h>
10#include <linux/unistd.h>
11
12#include <asm/cacheflush.h>
13#include <asm/cachectl.h>
14#include <asm/processor.h>
15#include <asm/uaccess.h>
16
17/*
18 * If you attempt to flush anything more than this, you need superuser
19 * privileges.  The value is completely arbitrary.
20 */
21#define CACHEFLUSH_MAX_LEN	1024
22
23void invalidate_dcache_region(void *start, size_t size)
24{
25	unsigned long v, begin, end, linesz, mask;
26
27	linesz = boot_cpu_data.dcache.linesz;
28	mask = linesz - 1;
29
30	/* when first and/or last cachelines are shared, flush them
31	 * instead of invalidating ... never discard valid data!
32	 */
33	begin = (unsigned long)start;
34	end = begin + size;
35
36	if (begin & mask) {
37		flush_dcache_line(start);
38		begin += linesz;
39	}
40	if (end & mask) {
41		flush_dcache_line((void *)end);
42		end &= ~mask;
43	}
44
45	/* remaining cachelines only need invalidation */
46	for (v = begin; v < end; v += linesz)
47		invalidate_dcache_line((void *)v);
48	flush_write_buffer();
49}
50
51void clean_dcache_region(void *start, size_t size)
52{
53	unsigned long v, begin, end, linesz;
54
55	linesz = boot_cpu_data.dcache.linesz;
56	begin = (unsigned long)start & ~(linesz - 1);
57	end = ((unsigned long)start + size + linesz - 1) & ~(linesz - 1);
58
59	for (v = begin; v < end; v += linesz)
60		clean_dcache_line((void *)v);
61	flush_write_buffer();
62}
63
64void flush_dcache_region(void *start, size_t size)
65{
66	unsigned long v, begin, end, linesz;
67
68	linesz = boot_cpu_data.dcache.linesz;
69	begin = (unsigned long)start & ~(linesz - 1);
70	end = ((unsigned long)start + size + linesz - 1) & ~(linesz - 1);
71
72	for (v = begin; v < end; v += linesz)
73		flush_dcache_line((void *)v);
74	flush_write_buffer();
75}
76
77void invalidate_icache_region(void *start, size_t size)
78{
79	unsigned long v, begin, end, linesz;
80
81	linesz = boot_cpu_data.icache.linesz;
82	begin = (unsigned long)start & ~(linesz - 1);
83	end = ((unsigned long)start + size + linesz - 1) & ~(linesz - 1);
84
85	for (v = begin; v < end; v += linesz)
86		invalidate_icache_line((void *)v);
87}
88
89static inline void __flush_icache_range(unsigned long start, unsigned long end)
90{
91	unsigned long v, linesz;
92
93	linesz = boot_cpu_data.dcache.linesz;
94	for (v = start; v < end; v += linesz) {
95		clean_dcache_line((void *)v);
96		invalidate_icache_line((void *)v);
97	}
98
99	flush_write_buffer();
100}
101
102/*
103 * This one is called after a module has been loaded.
104 */
105void flush_icache_range(unsigned long start, unsigned long end)
106{
107	unsigned long linesz;
108
109	linesz = boot_cpu_data.dcache.linesz;
110	__flush_icache_range(start & ~(linesz - 1),
111			     (end + linesz - 1) & ~(linesz - 1));
112}
113
114/*
115 * This one is called from do_no_page(), do_swap_page() and install_page().
116 */
117void flush_icache_page(struct vm_area_struct *vma, struct page *page)
118{
119	if (vma->vm_flags & VM_EXEC) {
120		void *v = page_address(page);
121		__flush_icache_range((unsigned long)v, (unsigned long)v + PAGE_SIZE);
122	}
123}
124
125/*
126 * This one is used by copy_to_user_page()
127 */
128void flush_icache_user_range(struct vm_area_struct *vma, struct page *page,
129			     unsigned long addr, int len)
130{
131	if (vma->vm_flags & VM_EXEC)
132		flush_icache_range(addr, addr + len);
133}
134
135asmlinkage int sys_cacheflush(int operation, void __user *addr, size_t len)
136{
137	int ret;
138
139	if (len > CACHEFLUSH_MAX_LEN) {
140		ret = -EPERM;
141		if (!capable(CAP_SYS_ADMIN))
142			goto out;
143	}
144
145	ret = -EFAULT;
146	if (!access_ok(VERIFY_WRITE, addr, len))
147		goto out;
148
149	switch (operation) {
150	case CACHE_IFLUSH:
151		flush_icache_range((unsigned long)addr,
152				   (unsigned long)addr + len);
153		ret = 0;
154		break;
155	default:
156		ret = -EINVAL;
157	}
158
159out:
160	return ret;
161}
162