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");