162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Privileged ADI driver for sparc64
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Author: Tom Hromatka <tom.hromatka@oracle.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include <linux/kernel.h>
862306a36Sopenharmony_ci#include <linux/miscdevice.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/proc_fs.h>
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci#include <linux/uaccess.h>
1362306a36Sopenharmony_ci#include <asm/asi.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define MAX_BUF_SZ	PAGE_SIZE
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistatic int adi_open(struct inode *inode, struct file *file)
1862306a36Sopenharmony_ci{
1962306a36Sopenharmony_ci	file->f_mode |= FMODE_UNSIGNED_OFFSET;
2062306a36Sopenharmony_ci	return 0;
2162306a36Sopenharmony_ci}
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic int read_mcd_tag(unsigned long addr)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	long err;
2662306a36Sopenharmony_ci	int ver;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	__asm__ __volatile__(
2962306a36Sopenharmony_ci		"1:	ldxa [%[addr]] %[asi], %[ver]\n"
3062306a36Sopenharmony_ci		"	mov 0, %[err]\n"
3162306a36Sopenharmony_ci		"2:\n"
3262306a36Sopenharmony_ci		"	.section .fixup,#alloc,#execinstr\n"
3362306a36Sopenharmony_ci		"	.align 4\n"
3462306a36Sopenharmony_ci		"3:	sethi %%hi(2b), %%g1\n"
3562306a36Sopenharmony_ci		"	jmpl  %%g1 + %%lo(2b), %%g0\n"
3662306a36Sopenharmony_ci		"	mov %[invalid], %[err]\n"
3762306a36Sopenharmony_ci		"	.previous\n"
3862306a36Sopenharmony_ci		"	.section __ex_table, \"a\"\n"
3962306a36Sopenharmony_ci		"	.align 4\n"
4062306a36Sopenharmony_ci		"	.word  1b, 3b\n"
4162306a36Sopenharmony_ci		"	.previous\n"
4262306a36Sopenharmony_ci		: [ver] "=r" (ver), [err] "=r" (err)
4362306a36Sopenharmony_ci		: [addr] "r"  (addr), [invalid] "i" (EFAULT),
4462306a36Sopenharmony_ci		  [asi] "i" (ASI_MCD_REAL)
4562306a36Sopenharmony_ci		: "memory", "g1"
4662306a36Sopenharmony_ci		);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	if (err)
4962306a36Sopenharmony_ci		return -EFAULT;
5062306a36Sopenharmony_ci	else
5162306a36Sopenharmony_ci		return ver;
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic ssize_t adi_read(struct file *file, char __user *buf,
5562306a36Sopenharmony_ci			size_t count, loff_t *offp)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	size_t ver_buf_sz, bytes_read = 0;
5862306a36Sopenharmony_ci	int ver_buf_idx = 0;
5962306a36Sopenharmony_ci	loff_t offset;
6062306a36Sopenharmony_ci	u8 *ver_buf;
6162306a36Sopenharmony_ci	ssize_t ret;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	ver_buf_sz = min_t(size_t, count, MAX_BUF_SZ);
6462306a36Sopenharmony_ci	ver_buf = kmalloc(ver_buf_sz, GFP_KERNEL);
6562306a36Sopenharmony_ci	if (!ver_buf)
6662306a36Sopenharmony_ci		return -ENOMEM;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	offset = (*offp) * adi_blksize();
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	while (bytes_read < count) {
7162306a36Sopenharmony_ci		ret = read_mcd_tag(offset);
7262306a36Sopenharmony_ci		if (ret < 0)
7362306a36Sopenharmony_ci			goto out;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci		ver_buf[ver_buf_idx] = (u8)ret;
7662306a36Sopenharmony_ci		ver_buf_idx++;
7762306a36Sopenharmony_ci		offset += adi_blksize();
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci		if (ver_buf_idx >= ver_buf_sz) {
8062306a36Sopenharmony_ci			if (copy_to_user(buf + bytes_read, ver_buf,
8162306a36Sopenharmony_ci					 ver_buf_sz)) {
8262306a36Sopenharmony_ci				ret = -EFAULT;
8362306a36Sopenharmony_ci				goto out;
8462306a36Sopenharmony_ci			}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci			bytes_read += ver_buf_sz;
8762306a36Sopenharmony_ci			ver_buf_idx = 0;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci			ver_buf_sz = min(count - bytes_read,
9062306a36Sopenharmony_ci					 (size_t)MAX_BUF_SZ);
9162306a36Sopenharmony_ci		}
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	(*offp) += bytes_read;
9562306a36Sopenharmony_ci	ret = bytes_read;
9662306a36Sopenharmony_ciout:
9762306a36Sopenharmony_ci	kfree(ver_buf);
9862306a36Sopenharmony_ci	return ret;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic int set_mcd_tag(unsigned long addr, u8 ver)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	long err;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	__asm__ __volatile__(
10662306a36Sopenharmony_ci		"1:	stxa %[ver], [%[addr]] %[asi]\n"
10762306a36Sopenharmony_ci		"	mov 0, %[err]\n"
10862306a36Sopenharmony_ci		"2:\n"
10962306a36Sopenharmony_ci		"	.section .fixup,#alloc,#execinstr\n"
11062306a36Sopenharmony_ci		"	.align 4\n"
11162306a36Sopenharmony_ci		"3:	sethi %%hi(2b), %%g1\n"
11262306a36Sopenharmony_ci		"	jmpl %%g1 + %%lo(2b), %%g0\n"
11362306a36Sopenharmony_ci		"	mov %[invalid], %[err]\n"
11462306a36Sopenharmony_ci		"	.previous\n"
11562306a36Sopenharmony_ci		"	.section __ex_table, \"a\"\n"
11662306a36Sopenharmony_ci		"	.align 4\n"
11762306a36Sopenharmony_ci		"	.word 1b, 3b\n"
11862306a36Sopenharmony_ci		"	.previous\n"
11962306a36Sopenharmony_ci		: [err] "=r" (err)
12062306a36Sopenharmony_ci		: [ver] "r" (ver), [addr] "r"  (addr),
12162306a36Sopenharmony_ci		  [invalid] "i" (EFAULT), [asi] "i" (ASI_MCD_REAL)
12262306a36Sopenharmony_ci		: "memory", "g1"
12362306a36Sopenharmony_ci		);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	if (err)
12662306a36Sopenharmony_ci		return -EFAULT;
12762306a36Sopenharmony_ci	else
12862306a36Sopenharmony_ci		return ver;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic ssize_t adi_write(struct file *file, const char __user *buf,
13262306a36Sopenharmony_ci			 size_t count, loff_t *offp)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	size_t ver_buf_sz, bytes_written = 0;
13562306a36Sopenharmony_ci	loff_t offset;
13662306a36Sopenharmony_ci	u8 *ver_buf;
13762306a36Sopenharmony_ci	ssize_t ret;
13862306a36Sopenharmony_ci	int i;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (count <= 0)
14162306a36Sopenharmony_ci		return -EINVAL;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	ver_buf_sz = min_t(size_t, count, MAX_BUF_SZ);
14462306a36Sopenharmony_ci	ver_buf = kmalloc(ver_buf_sz, GFP_KERNEL);
14562306a36Sopenharmony_ci	if (!ver_buf)
14662306a36Sopenharmony_ci		return -ENOMEM;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	offset = (*offp) * adi_blksize();
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	do {
15162306a36Sopenharmony_ci		if (copy_from_user(ver_buf, &buf[bytes_written],
15262306a36Sopenharmony_ci				   ver_buf_sz)) {
15362306a36Sopenharmony_ci			ret = -EFAULT;
15462306a36Sopenharmony_ci			goto out;
15562306a36Sopenharmony_ci		}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci		for (i = 0; i < ver_buf_sz; i++) {
15862306a36Sopenharmony_ci			ret = set_mcd_tag(offset, ver_buf[i]);
15962306a36Sopenharmony_ci			if (ret < 0)
16062306a36Sopenharmony_ci				goto out;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci			offset += adi_blksize();
16362306a36Sopenharmony_ci		}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci		bytes_written += ver_buf_sz;
16662306a36Sopenharmony_ci		ver_buf_sz = min(count - bytes_written, (size_t)MAX_BUF_SZ);
16762306a36Sopenharmony_ci	} while (bytes_written < count);
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	(*offp) += bytes_written;
17062306a36Sopenharmony_ci	ret = bytes_written;
17162306a36Sopenharmony_ciout:
17262306a36Sopenharmony_ci	__asm__ __volatile__("membar #Sync");
17362306a36Sopenharmony_ci	kfree(ver_buf);
17462306a36Sopenharmony_ci	return ret;
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic loff_t adi_llseek(struct file *file, loff_t offset, int whence)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	loff_t ret = -EINVAL;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	switch (whence) {
18262306a36Sopenharmony_ci	case SEEK_END:
18362306a36Sopenharmony_ci	case SEEK_DATA:
18462306a36Sopenharmony_ci	case SEEK_HOLE:
18562306a36Sopenharmony_ci		/* unsupported */
18662306a36Sopenharmony_ci		return -EINVAL;
18762306a36Sopenharmony_ci	case SEEK_CUR:
18862306a36Sopenharmony_ci		if (offset == 0)
18962306a36Sopenharmony_ci			return file->f_pos;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci		offset += file->f_pos;
19262306a36Sopenharmony_ci		break;
19362306a36Sopenharmony_ci	case SEEK_SET:
19462306a36Sopenharmony_ci		break;
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	if (offset != file->f_pos) {
19862306a36Sopenharmony_ci		file->f_pos = offset;
19962306a36Sopenharmony_ci		file->f_version = 0;
20062306a36Sopenharmony_ci		ret = offset;
20162306a36Sopenharmony_ci	}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	return ret;
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic const struct file_operations adi_fops = {
20762306a36Sopenharmony_ci	.owner		= THIS_MODULE,
20862306a36Sopenharmony_ci	.llseek		= adi_llseek,
20962306a36Sopenharmony_ci	.open		= adi_open,
21062306a36Sopenharmony_ci	.read		= adi_read,
21162306a36Sopenharmony_ci	.write		= adi_write,
21262306a36Sopenharmony_ci};
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cistatic struct miscdevice adi_miscdev = {
21562306a36Sopenharmony_ci	.minor = MISC_DYNAMIC_MINOR,
21662306a36Sopenharmony_ci	.name = KBUILD_MODNAME,
21762306a36Sopenharmony_ci	.fops = &adi_fops,
21862306a36Sopenharmony_ci};
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cistatic int __init adi_init(void)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	if (!adi_capable())
22362306a36Sopenharmony_ci		return -EPERM;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	return misc_register(&adi_miscdev);
22662306a36Sopenharmony_ci}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic void __exit adi_exit(void)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	misc_deregister(&adi_miscdev);
23162306a36Sopenharmony_ci}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_cimodule_init(adi_init);
23462306a36Sopenharmony_cimodule_exit(adi_exit);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ciMODULE_AUTHOR("Tom Hromatka <tom.hromatka@oracle.com>");
23762306a36Sopenharmony_ciMODULE_DESCRIPTION("Privileged interface to ADI");
23862306a36Sopenharmony_ciMODULE_VERSION("1.0");
23962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
240