Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.15.
  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
 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	else
 45		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	else
122		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");