xref: /openbmc/linux/drivers/char/adi.c (revision 3eb66e91a25497065c5322b1268cbc3953642227)
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