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