]>
Commit | Line | Data |
---|---|---|
873c38a4 TH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Privileged ADI driver for sparc64 | |
4 | * | |
5 | * Author: Tom Hromatka <[email protected]> | |
6 | */ | |
7 | #include <linux/kernel.h> | |
8 | #include <linux/miscdevice.h> | |
9 | #include <linux/module.h> | |
10 | #include <linux/proc_fs.h> | |
11 | #include <linux/slab.h> | |
12 | #include <linux/uaccess.h> | |
13 | #include <asm/asi.h> | |
14 | ||
15 | #define MAX_BUF_SZ PAGE_SIZE | |
16 | ||
873c38a4 TH |
17 | static int read_mcd_tag(unsigned long addr) |
18 | { | |
19 | long err; | |
20 | int ver; | |
21 | ||
22 | __asm__ __volatile__( | |
23 | "1: ldxa [%[addr]] %[asi], %[ver]\n" | |
24 | " mov 0, %[err]\n" | |
25 | "2:\n" | |
26 | " .section .fixup,#alloc,#execinstr\n" | |
27 | " .align 4\n" | |
28 | "3: sethi %%hi(2b), %%g1\n" | |
29 | " jmpl %%g1 + %%lo(2b), %%g0\n" | |
30 | " mov %[invalid], %[err]\n" | |
31 | " .previous\n" | |
32 | " .section __ex_table, \"a\"\n" | |
33 | " .align 4\n" | |
34 | " .word 1b, 3b\n" | |
35 | " .previous\n" | |
36 | : [ver] "=r" (ver), [err] "=r" (err) | |
37 | : [addr] "r" (addr), [invalid] "i" (EFAULT), | |
38 | [asi] "i" (ASI_MCD_REAL) | |
39 | : "memory", "g1" | |
40 | ); | |
41 | ||
42 | if (err) | |
43 | return -EFAULT; | |
44 | else | |
45 | return ver; | |
46 | } | |
47 | ||
48 | static ssize_t adi_read(struct file *file, char __user *buf, | |
49 | size_t count, loff_t *offp) | |
50 | { | |
51 | size_t ver_buf_sz, bytes_read = 0; | |
52 | int ver_buf_idx = 0; | |
53 | loff_t offset; | |
54 | u8 *ver_buf; | |
55 | ssize_t ret; | |
56 | ||
57 | ver_buf_sz = min_t(size_t, count, MAX_BUF_SZ); | |
58 | ver_buf = kmalloc(ver_buf_sz, GFP_KERNEL); | |
59 | if (!ver_buf) | |
60 | return -ENOMEM; | |
61 | ||
62 | offset = (*offp) * adi_blksize(); | |
63 | ||
64 | while (bytes_read < count) { | |
65 | ret = read_mcd_tag(offset); | |
66 | if (ret < 0) | |
67 | goto out; | |
68 | ||
69 | ver_buf[ver_buf_idx] = (u8)ret; | |
70 | ver_buf_idx++; | |
71 | offset += adi_blksize(); | |
72 | ||
73 | if (ver_buf_idx >= ver_buf_sz) { | |
74 | if (copy_to_user(buf + bytes_read, ver_buf, | |
75 | ver_buf_sz)) { | |
76 | ret = -EFAULT; | |
77 | goto out; | |
78 | } | |
79 | ||
80 | bytes_read += ver_buf_sz; | |
81 | ver_buf_idx = 0; | |
82 | ||
83 | ver_buf_sz = min(count - bytes_read, | |
84 | (size_t)MAX_BUF_SZ); | |
85 | } | |
86 | } | |
87 | ||
88 | (*offp) += bytes_read; | |
89 | ret = bytes_read; | |
90 | out: | |
91 | kfree(ver_buf); | |
92 | return ret; | |
93 | } | |
94 | ||
95 | static int set_mcd_tag(unsigned long addr, u8 ver) | |
96 | { | |
97 | long err; | |
98 | ||
99 | __asm__ __volatile__( | |
100 | "1: stxa %[ver], [%[addr]] %[asi]\n" | |
101 | " mov 0, %[err]\n" | |
102 | "2:\n" | |
103 | " .section .fixup,#alloc,#execinstr\n" | |
104 | " .align 4\n" | |
105 | "3: sethi %%hi(2b), %%g1\n" | |
106 | " jmpl %%g1 + %%lo(2b), %%g0\n" | |
107 | " mov %[invalid], %[err]\n" | |
108 | " .previous\n" | |
109 | " .section __ex_table, \"a\"\n" | |
110 | " .align 4\n" | |
111 | " .word 1b, 3b\n" | |
112 | " .previous\n" | |
113 | : [err] "=r" (err) | |
114 | : [ver] "r" (ver), [addr] "r" (addr), | |
115 | [invalid] "i" (EFAULT), [asi] "i" (ASI_MCD_REAL) | |
116 | : "memory", "g1" | |
117 | ); | |
118 | ||
119 | if (err) | |
120 | return -EFAULT; | |
121 | else | |
122 | return ver; | |
123 | } | |
124 | ||
125 | static ssize_t adi_write(struct file *file, const char __user *buf, | |
126 | size_t count, loff_t *offp) | |
127 | { | |
128 | size_t ver_buf_sz, bytes_written = 0; | |
129 | loff_t offset; | |
130 | u8 *ver_buf; | |
131 | ssize_t ret; | |
132 | int i; | |
133 | ||
134 | if (count <= 0) | |
135 | return -EINVAL; | |
136 | ||
137 | ver_buf_sz = min_t(size_t, count, MAX_BUF_SZ); | |
138 | ver_buf = kmalloc(ver_buf_sz, GFP_KERNEL); | |
139 | if (!ver_buf) | |
140 | return -ENOMEM; | |
141 | ||
142 | offset = (*offp) * adi_blksize(); | |
143 | ||
144 | do { | |
145 | if (copy_from_user(ver_buf, &buf[bytes_written], | |
146 | ver_buf_sz)) { | |
147 | ret = -EFAULT; | |
148 | goto out; | |
149 | } | |
150 | ||
151 | for (i = 0; i < ver_buf_sz; i++) { | |
152 | ret = set_mcd_tag(offset, ver_buf[i]); | |
153 | if (ret < 0) | |
154 | goto out; | |
155 | ||
156 | offset += adi_blksize(); | |
157 | } | |
158 | ||
159 | bytes_written += ver_buf_sz; | |
160 | ver_buf_sz = min(count - bytes_written, (size_t)MAX_BUF_SZ); | |
161 | } while (bytes_written < count); | |
162 | ||
163 | (*offp) += bytes_written; | |
164 | ret = bytes_written; | |
165 | out: | |
166 | __asm__ __volatile__("membar #Sync"); | |
167 | kfree(ver_buf); | |
168 | return ret; | |
169 | } | |
170 | ||
171 | static loff_t adi_llseek(struct file *file, loff_t offset, int whence) | |
172 | { | |
173 | loff_t ret = -EINVAL; | |
174 | ||
175 | switch (whence) { | |
176 | case SEEK_END: | |
177 | case SEEK_DATA: | |
178 | case SEEK_HOLE: | |
179 | /* unsupported */ | |
180 | return -EINVAL; | |
181 | case SEEK_CUR: | |
182 | if (offset == 0) | |
183 | return file->f_pos; | |
184 | ||
185 | offset += file->f_pos; | |
186 | break; | |
187 | case SEEK_SET: | |
188 | break; | |
189 | } | |
190 | ||
191 | if (offset != file->f_pos) { | |
192 | file->f_pos = offset; | |
873c38a4 TH |
193 | ret = offset; |
194 | } | |
195 | ||
196 | return ret; | |
197 | } | |
198 | ||
199 | static const struct file_operations adi_fops = { | |
200 | .owner = THIS_MODULE, | |
201 | .llseek = adi_llseek, | |
873c38a4 TH |
202 | .read = adi_read, |
203 | .write = adi_write, | |
641bb439 | 204 | .fop_flags = FOP_UNSIGNED_OFFSET, |
873c38a4 TH |
205 | }; |
206 | ||
207 | static struct miscdevice adi_miscdev = { | |
208 | .minor = MISC_DYNAMIC_MINOR, | |
209 | .name = KBUILD_MODNAME, | |
210 | .fops = &adi_fops, | |
211 | }; | |
212 | ||
213 | static int __init adi_init(void) | |
214 | { | |
215 | if (!adi_capable()) | |
216 | return -EPERM; | |
217 | ||
218 | return misc_register(&adi_miscdev); | |
219 | } | |
220 | ||
221 | static void __exit adi_exit(void) | |
222 | { | |
223 | misc_deregister(&adi_miscdev); | |
224 | } | |
225 | ||
226 | module_init(adi_init); | |
227 | module_exit(adi_exit); | |
228 | ||
229 | MODULE_AUTHOR("Tom Hromatka <[email protected]>"); | |
230 | MODULE_DESCRIPTION("Privileged interface to ADI"); | |
231 | MODULE_VERSION("1.0"); | |
232 | MODULE_LICENSE("GPL v2"); |