1 // SPDX-License-Identifier: GPL-2.0
3 * Privileged ADI driver for sparc64
5 * Author: Tom Hromatka <tom.hromatka@oracle.com>
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>
15 #define MAX_BUF_SZ PAGE_SIZE
17 static int adi_open(struct inode
*inode
, struct file
*file
)
19 file
->f_mode
|= FMODE_UNSIGNED_OFFSET
;
23 static int read_mcd_tag(unsigned long addr
)
29 "1: ldxa [%[addr]] %[asi], %[ver]\n"
32 " .section .fixup,#alloc,#execinstr\n"
34 "3: sethi %%hi(2b), %%g1\n"
35 " jmpl %%g1 + %%lo(2b), %%g0\n"
36 " mov %[invalid], %[err]\n"
38 " .section __ex_table, \"a\"\n"
42 : [ver
] "=r" (ver
), [err
] "=r" (err
)
43 : [addr
] "r" (addr
), [invalid
] "i" (EFAULT
),
44 [asi
] "i" (ASI_MCD_REAL
)
54 static ssize_t
adi_read(struct file
*file
, char __user
*buf
,
55 size_t count
, loff_t
*offp
)
57 size_t ver_buf_sz
, bytes_read
= 0;
63 ver_buf_sz
= min_t(size_t, count
, MAX_BUF_SZ
);
64 ver_buf
= kmalloc(ver_buf_sz
, GFP_KERNEL
);
68 offset
= (*offp
) * adi_blksize();
70 while (bytes_read
< count
) {
71 ret
= read_mcd_tag(offset
);
75 ver_buf
[ver_buf_idx
] = (u8
)ret
;
77 offset
+= adi_blksize();
79 if (ver_buf_idx
>= ver_buf_sz
) {
80 if (copy_to_user(buf
+ bytes_read
, ver_buf
,
86 bytes_read
+= ver_buf_sz
;
89 ver_buf_sz
= min(count
- bytes_read
,
94 (*offp
) += bytes_read
;
101 static int set_mcd_tag(unsigned long addr
, u8 ver
)
105 __asm__
__volatile__(
106 "1: stxa %[ver], [%[addr]] %[asi]\n"
109 " .section .fixup,#alloc,#execinstr\n"
111 "3: sethi %%hi(2b), %%g1\n"
112 " jmpl %%g1 + %%lo(2b), %%g0\n"
113 " mov %[invalid], %[err]\n"
115 " .section __ex_table, \"a\"\n"
120 : [ver
] "r" (ver
), [addr
] "r" (addr
),
121 [invalid
] "i" (EFAULT
), [asi
] "i" (ASI_MCD_REAL
)
131 static ssize_t
adi_write(struct file
*file
, const char __user
*buf
,
132 size_t count
, loff_t
*offp
)
134 size_t ver_buf_sz
, bytes_written
= 0;
143 ver_buf_sz
= min_t(size_t, count
, MAX_BUF_SZ
);
144 ver_buf
= kmalloc(ver_buf_sz
, GFP_KERNEL
);
148 offset
= (*offp
) * adi_blksize();
151 if (copy_from_user(ver_buf
, &buf
[bytes_written
],
157 for (i
= 0; i
< ver_buf_sz
; i
++) {
158 ret
= set_mcd_tag(offset
, ver_buf
[i
]);
162 offset
+= adi_blksize();
165 bytes_written
+= ver_buf_sz
;
166 ver_buf_sz
= min(count
- bytes_written
, (size_t)MAX_BUF_SZ
);
167 } while (bytes_written
< count
);
169 (*offp
) += bytes_written
;
172 __asm__
__volatile__("membar #Sync");
177 static loff_t
adi_llseek(struct file
*file
, loff_t offset
, int whence
)
179 loff_t ret
= -EINVAL
;
191 offset
+= file
->f_pos
;
197 if (offset
!= file
->f_pos
) {
198 file
->f_pos
= offset
;
206 static const struct file_operations adi_fops
= {
207 .owner
= THIS_MODULE
,
208 .llseek
= adi_llseek
,
214 static struct miscdevice adi_miscdev
= {
215 .minor
= MISC_DYNAMIC_MINOR
,
216 .name
= KBUILD_MODNAME
,
220 static int __init
adi_init(void)
225 return misc_register(&adi_miscdev
);
228 static void __exit
adi_exit(void)
230 misc_deregister(&adi_miscdev
);
233 module_init(adi_init
);
234 module_exit(adi_exit
);
236 MODULE_AUTHOR("Tom Hromatka <tom.hromatka@oracle.com>");
237 MODULE_DESCRIPTION("Privileged interface to ADI");
238 MODULE_VERSION("1.0");
239 MODULE_LICENSE("GPL v2");