233 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Privileged ADI driver for sparc644 *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_SIZE16 17static 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 else45 return ver;46}47 48static 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;90out:91 kfree(ver_buf);92 return ret;93}94 95static 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 else122 return ver;123}124 125static 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;165out:166 __asm__ __volatile__("membar #Sync");167 kfree(ver_buf);168 return ret;169}170 171static 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 199static 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 207static struct miscdevice adi_miscdev = {208 .minor = MISC_DYNAMIC_MINOR,209 .name = KBUILD_MODNAME,210 .fops = &adi_fops,211};212 213static int __init adi_init(void)214{215 if (!adi_capable())216 return -EPERM;217 218 return misc_register(&adi_miscdev);219}220 221static void __exit adi_exit(void)222{223 misc_deregister(&adi_miscdev);224}225 226module_init(adi_init);227module_exit(adi_exit);228 229MODULE_AUTHOR("Tom Hromatka <tom.hromatka@oracle.com>");230MODULE_DESCRIPTION("Privileged interface to ADI");231MODULE_VERSION("1.0");232MODULE_LICENSE("GPL v2");233