1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Privileged ADI driver for sparc64 4 * 5 * Author: Tom Hromatka <tom.hromatka@oracle.com> 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 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; 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, 202 .read = adi_read, 203 .write = adi_write, 204 .fop_flags = FOP_UNSIGNED_OFFSET, 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 <tom.hromatka@oracle.com>"); 230 MODULE_DESCRIPTION("Privileged interface to ADI"); 231 MODULE_VERSION("1.0"); 232 MODULE_LICENSE("GPL v2"); 233