162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * VAS user space API for its accelerators (Only NX-GZIP is supported now)
462306a36Sopenharmony_ci * Copyright (C) 2019 Haren Myneni, IBM Corp
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#define pr_fmt(fmt)	"vas-api: " fmt
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/device.h>
1162306a36Sopenharmony_ci#include <linux/cdev.h>
1262306a36Sopenharmony_ci#include <linux/fs.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/uaccess.h>
1562306a36Sopenharmony_ci#include <linux/kthread.h>
1662306a36Sopenharmony_ci#include <linux/sched/signal.h>
1762306a36Sopenharmony_ci#include <linux/mmu_context.h>
1862306a36Sopenharmony_ci#include <linux/io.h>
1962306a36Sopenharmony_ci#include <asm/vas.h>
2062306a36Sopenharmony_ci#include <uapi/asm/vas-api.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci/*
2362306a36Sopenharmony_ci * The driver creates the device node that can be used as follows:
2462306a36Sopenharmony_ci * For NX-GZIP
2562306a36Sopenharmony_ci *
2662306a36Sopenharmony_ci *	fd = open("/dev/crypto/nx-gzip", O_RDWR);
2762306a36Sopenharmony_ci *	rc = ioctl(fd, VAS_TX_WIN_OPEN, &attr);
2862306a36Sopenharmony_ci *	paste_addr = mmap(NULL, PAGE_SIZE, prot, MAP_SHARED, fd, 0ULL).
2962306a36Sopenharmony_ci *	vas_copy(&crb, 0, 1);
3062306a36Sopenharmony_ci *	vas_paste(paste_addr, 0, 1);
3162306a36Sopenharmony_ci *	close(fd) or exit process to close window.
3262306a36Sopenharmony_ci *
3362306a36Sopenharmony_ci * where "vas_copy" and "vas_paste" are defined in copy-paste.h.
3462306a36Sopenharmony_ci * copy/paste returns to the user space directly. So refer NX hardware
3562306a36Sopenharmony_ci * documentation for exact copy/paste usage and completion / error
3662306a36Sopenharmony_ci * conditions.
3762306a36Sopenharmony_ci */
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/*
4062306a36Sopenharmony_ci * Wrapper object for the nx-gzip device - there is just one instance of
4162306a36Sopenharmony_ci * this node for the whole system.
4262306a36Sopenharmony_ci */
4362306a36Sopenharmony_cistatic struct coproc_dev {
4462306a36Sopenharmony_ci	struct cdev cdev;
4562306a36Sopenharmony_ci	struct device *device;
4662306a36Sopenharmony_ci	char *name;
4762306a36Sopenharmony_ci	dev_t devt;
4862306a36Sopenharmony_ci	struct class *class;
4962306a36Sopenharmony_ci	enum vas_cop_type cop_type;
5062306a36Sopenharmony_ci	const struct vas_user_win_ops *vops;
5162306a36Sopenharmony_ci} coproc_device;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistruct coproc_instance {
5462306a36Sopenharmony_ci	struct coproc_dev *coproc;
5562306a36Sopenharmony_ci	struct vas_window *txwin;
5662306a36Sopenharmony_ci};
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic char *coproc_devnode(const struct device *dev, umode_t *mode)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	return kasprintf(GFP_KERNEL, "crypto/%s", dev_name(dev));
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci/*
6462306a36Sopenharmony_ci * Take reference to pid and mm
6562306a36Sopenharmony_ci */
6662306a36Sopenharmony_ciint get_vas_user_win_ref(struct vas_user_win_ref *task_ref)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	/*
6962306a36Sopenharmony_ci	 * Window opened by a child thread may not be closed when
7062306a36Sopenharmony_ci	 * it exits. So take reference to its pid and release it
7162306a36Sopenharmony_ci	 * when the window is free by parent thread.
7262306a36Sopenharmony_ci	 * Acquire a reference to the task's pid to make sure
7362306a36Sopenharmony_ci	 * pid will not be re-used - needed only for multithread
7462306a36Sopenharmony_ci	 * applications.
7562306a36Sopenharmony_ci	 */
7662306a36Sopenharmony_ci	task_ref->pid = get_task_pid(current, PIDTYPE_PID);
7762306a36Sopenharmony_ci	/*
7862306a36Sopenharmony_ci	 * Acquire a reference to the task's mm.
7962306a36Sopenharmony_ci	 */
8062306a36Sopenharmony_ci	task_ref->mm = get_task_mm(current);
8162306a36Sopenharmony_ci	if (!task_ref->mm) {
8262306a36Sopenharmony_ci		put_pid(task_ref->pid);
8362306a36Sopenharmony_ci		pr_err("pid(%d): mm_struct is not found\n",
8462306a36Sopenharmony_ci				current->pid);
8562306a36Sopenharmony_ci		return -EPERM;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	mmgrab(task_ref->mm);
8962306a36Sopenharmony_ci	mmput(task_ref->mm);
9062306a36Sopenharmony_ci	/*
9162306a36Sopenharmony_ci	 * Process closes window during exit. In the case of
9262306a36Sopenharmony_ci	 * multithread application, the child thread can open
9362306a36Sopenharmony_ci	 * window and can exit without closing it. So takes tgid
9462306a36Sopenharmony_ci	 * reference until window closed to make sure tgid is not
9562306a36Sopenharmony_ci	 * reused.
9662306a36Sopenharmony_ci	 */
9762306a36Sopenharmony_ci	task_ref->tgid = find_get_pid(task_tgid_vnr(current));
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	return 0;
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci/*
10362306a36Sopenharmony_ci * Successful return must release the task reference with
10462306a36Sopenharmony_ci * put_task_struct
10562306a36Sopenharmony_ci */
10662306a36Sopenharmony_cistatic bool ref_get_pid_and_task(struct vas_user_win_ref *task_ref,
10762306a36Sopenharmony_ci			  struct task_struct **tskp, struct pid **pidp)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	struct task_struct *tsk;
11062306a36Sopenharmony_ci	struct pid *pid;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	pid = task_ref->pid;
11362306a36Sopenharmony_ci	tsk = get_pid_task(pid, PIDTYPE_PID);
11462306a36Sopenharmony_ci	if (!tsk) {
11562306a36Sopenharmony_ci		pid = task_ref->tgid;
11662306a36Sopenharmony_ci		tsk = get_pid_task(pid, PIDTYPE_PID);
11762306a36Sopenharmony_ci		/*
11862306a36Sopenharmony_ci		 * Parent thread (tgid) will be closing window when it
11962306a36Sopenharmony_ci		 * exits. So should not get here.
12062306a36Sopenharmony_ci		 */
12162306a36Sopenharmony_ci		if (WARN_ON_ONCE(!tsk))
12262306a36Sopenharmony_ci			return false;
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	/* Return if the task is exiting. */
12662306a36Sopenharmony_ci	if (tsk->flags & PF_EXITING) {
12762306a36Sopenharmony_ci		put_task_struct(tsk);
12862306a36Sopenharmony_ci		return false;
12962306a36Sopenharmony_ci	}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	*tskp = tsk;
13262306a36Sopenharmony_ci	*pidp = pid;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	return true;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci/*
13862306a36Sopenharmony_ci * Update the CSB to indicate a translation error.
13962306a36Sopenharmony_ci *
14062306a36Sopenharmony_ci * User space will be polling on CSB after the request is issued.
14162306a36Sopenharmony_ci * If NX can handle the request without any issues, it updates CSB.
14262306a36Sopenharmony_ci * Whereas if NX encounters page fault, the kernel will handle the
14362306a36Sopenharmony_ci * fault and update CSB with translation error.
14462306a36Sopenharmony_ci *
14562306a36Sopenharmony_ci * If we are unable to update the CSB means copy_to_user failed due to
14662306a36Sopenharmony_ci * invalid csb_addr, send a signal to the process.
14762306a36Sopenharmony_ci */
14862306a36Sopenharmony_civoid vas_update_csb(struct coprocessor_request_block *crb,
14962306a36Sopenharmony_ci		    struct vas_user_win_ref *task_ref)
15062306a36Sopenharmony_ci{
15162306a36Sopenharmony_ci	struct coprocessor_status_block csb;
15262306a36Sopenharmony_ci	struct kernel_siginfo info;
15362306a36Sopenharmony_ci	struct task_struct *tsk;
15462306a36Sopenharmony_ci	void __user *csb_addr;
15562306a36Sopenharmony_ci	struct pid *pid;
15662306a36Sopenharmony_ci	int rc;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	/*
15962306a36Sopenharmony_ci	 * NX user space windows can not be opened for task->mm=NULL
16062306a36Sopenharmony_ci	 * and faults will not be generated for kernel requests.
16162306a36Sopenharmony_ci	 */
16262306a36Sopenharmony_ci	if (WARN_ON_ONCE(!task_ref->mm))
16362306a36Sopenharmony_ci		return;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	csb_addr = (void __user *)be64_to_cpu(crb->csb_addr);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	memset(&csb, 0, sizeof(csb));
16862306a36Sopenharmony_ci	csb.cc = CSB_CC_FAULT_ADDRESS;
16962306a36Sopenharmony_ci	csb.ce = CSB_CE_TERMINATION;
17062306a36Sopenharmony_ci	csb.cs = 0;
17162306a36Sopenharmony_ci	csb.count = 0;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	/*
17462306a36Sopenharmony_ci	 * NX operates and returns in BE format as defined CRB struct.
17562306a36Sopenharmony_ci	 * So saves fault_storage_addr in BE as NX pastes in FIFO and
17662306a36Sopenharmony_ci	 * expects user space to convert to CPU format.
17762306a36Sopenharmony_ci	 */
17862306a36Sopenharmony_ci	csb.address = crb->stamp.nx.fault_storage_addr;
17962306a36Sopenharmony_ci	csb.flags = 0;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	/*
18262306a36Sopenharmony_ci	 * Process closes send window after all pending NX requests are
18362306a36Sopenharmony_ci	 * completed. In multi-thread applications, a child thread can
18462306a36Sopenharmony_ci	 * open a window and can exit without closing it. May be some
18562306a36Sopenharmony_ci	 * requests are pending or this window can be used by other
18662306a36Sopenharmony_ci	 * threads later. We should handle faults if NX encounters
18762306a36Sopenharmony_ci	 * pages faults on these requests. Update CSB with translation
18862306a36Sopenharmony_ci	 * error and fault address. If csb_addr passed by user space is
18962306a36Sopenharmony_ci	 * invalid, send SEGV signal to pid saved in window. If the
19062306a36Sopenharmony_ci	 * child thread is not running, send the signal to tgid.
19162306a36Sopenharmony_ci	 * Parent thread (tgid) will close this window upon its exit.
19262306a36Sopenharmony_ci	 *
19362306a36Sopenharmony_ci	 * pid and mm references are taken when window is opened by
19462306a36Sopenharmony_ci	 * process (pid). So tgid is used only when child thread opens
19562306a36Sopenharmony_ci	 * a window and exits without closing it.
19662306a36Sopenharmony_ci	 */
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if (!ref_get_pid_and_task(task_ref, &tsk, &pid))
19962306a36Sopenharmony_ci		return;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	kthread_use_mm(task_ref->mm);
20262306a36Sopenharmony_ci	rc = copy_to_user(csb_addr, &csb, sizeof(csb));
20362306a36Sopenharmony_ci	/*
20462306a36Sopenharmony_ci	 * User space polls on csb.flags (first byte). So add barrier
20562306a36Sopenharmony_ci	 * then copy first byte with csb flags update.
20662306a36Sopenharmony_ci	 */
20762306a36Sopenharmony_ci	if (!rc) {
20862306a36Sopenharmony_ci		csb.flags = CSB_V;
20962306a36Sopenharmony_ci		/* Make sure update to csb.flags is visible now */
21062306a36Sopenharmony_ci		smp_mb();
21162306a36Sopenharmony_ci		rc = copy_to_user(csb_addr, &csb, sizeof(u8));
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci	kthread_unuse_mm(task_ref->mm);
21462306a36Sopenharmony_ci	put_task_struct(tsk);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	/* Success */
21762306a36Sopenharmony_ci	if (!rc)
21862306a36Sopenharmony_ci		return;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	pr_debug("Invalid CSB address 0x%p signalling pid(%d)\n",
22262306a36Sopenharmony_ci			csb_addr, pid_vnr(pid));
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	clear_siginfo(&info);
22562306a36Sopenharmony_ci	info.si_signo = SIGSEGV;
22662306a36Sopenharmony_ci	info.si_errno = EFAULT;
22762306a36Sopenharmony_ci	info.si_code = SEGV_MAPERR;
22862306a36Sopenharmony_ci	info.si_addr = csb_addr;
22962306a36Sopenharmony_ci	/*
23062306a36Sopenharmony_ci	 * process will be polling on csb.flags after request is sent to
23162306a36Sopenharmony_ci	 * NX. So generally CSB update should not fail except when an
23262306a36Sopenharmony_ci	 * application passes invalid csb_addr. So an error message will
23362306a36Sopenharmony_ci	 * be displayed and leave it to user space whether to ignore or
23462306a36Sopenharmony_ci	 * handle this signal.
23562306a36Sopenharmony_ci	 */
23662306a36Sopenharmony_ci	rcu_read_lock();
23762306a36Sopenharmony_ci	rc = kill_pid_info(SIGSEGV, &info, pid);
23862306a36Sopenharmony_ci	rcu_read_unlock();
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	pr_devel("pid %d kill_proc_info() rc %d\n", pid_vnr(pid), rc);
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_civoid vas_dump_crb(struct coprocessor_request_block *crb)
24462306a36Sopenharmony_ci{
24562306a36Sopenharmony_ci	struct data_descriptor_entry *dde;
24662306a36Sopenharmony_ci	struct nx_fault_stamp *nx;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	dde = &crb->source;
24962306a36Sopenharmony_ci	pr_devel("SrcDDE: addr 0x%llx, len %d, count %d, idx %d, flags %d\n",
25062306a36Sopenharmony_ci		be64_to_cpu(dde->address), be32_to_cpu(dde->length),
25162306a36Sopenharmony_ci		dde->count, dde->index, dde->flags);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	dde = &crb->target;
25462306a36Sopenharmony_ci	pr_devel("TgtDDE: addr 0x%llx, len %d, count %d, idx %d, flags %d\n",
25562306a36Sopenharmony_ci		be64_to_cpu(dde->address), be32_to_cpu(dde->length),
25662306a36Sopenharmony_ci		dde->count, dde->index, dde->flags);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	nx = &crb->stamp.nx;
25962306a36Sopenharmony_ci	pr_devel("NX Stamp: PSWID 0x%x, FSA 0x%llx, flags 0x%x, FS 0x%x\n",
26062306a36Sopenharmony_ci		be32_to_cpu(nx->pswid),
26162306a36Sopenharmony_ci		be64_to_cpu(crb->stamp.nx.fault_storage_addr),
26262306a36Sopenharmony_ci		nx->flags, nx->fault_status);
26362306a36Sopenharmony_ci}
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_cistatic int coproc_open(struct inode *inode, struct file *fp)
26662306a36Sopenharmony_ci{
26762306a36Sopenharmony_ci	struct coproc_instance *cp_inst;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	cp_inst = kzalloc(sizeof(*cp_inst), GFP_KERNEL);
27062306a36Sopenharmony_ci	if (!cp_inst)
27162306a36Sopenharmony_ci		return -ENOMEM;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	cp_inst->coproc = container_of(inode->i_cdev, struct coproc_dev,
27462306a36Sopenharmony_ci					cdev);
27562306a36Sopenharmony_ci	fp->private_data = cp_inst;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	return 0;
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic int coproc_ioc_tx_win_open(struct file *fp, unsigned long arg)
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci	void __user *uptr = (void __user *)arg;
28362306a36Sopenharmony_ci	struct vas_tx_win_open_attr uattr;
28462306a36Sopenharmony_ci	struct coproc_instance *cp_inst;
28562306a36Sopenharmony_ci	struct vas_window *txwin;
28662306a36Sopenharmony_ci	int rc;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	cp_inst = fp->private_data;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	/*
29162306a36Sopenharmony_ci	 * One window for file descriptor
29262306a36Sopenharmony_ci	 */
29362306a36Sopenharmony_ci	if (cp_inst->txwin)
29462306a36Sopenharmony_ci		return -EEXIST;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	rc = copy_from_user(&uattr, uptr, sizeof(uattr));
29762306a36Sopenharmony_ci	if (rc) {
29862306a36Sopenharmony_ci		pr_err("copy_from_user() returns %d\n", rc);
29962306a36Sopenharmony_ci		return -EFAULT;
30062306a36Sopenharmony_ci	}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	if (uattr.version != 1) {
30362306a36Sopenharmony_ci		pr_err("Invalid window open API version\n");
30462306a36Sopenharmony_ci		return -EINVAL;
30562306a36Sopenharmony_ci	}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	if (!cp_inst->coproc->vops || !cp_inst->coproc->vops->open_win) {
30862306a36Sopenharmony_ci		pr_err("VAS API is not registered\n");
30962306a36Sopenharmony_ci		return -EACCES;
31062306a36Sopenharmony_ci	}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	txwin = cp_inst->coproc->vops->open_win(uattr.vas_id, uattr.flags,
31362306a36Sopenharmony_ci						cp_inst->coproc->cop_type);
31462306a36Sopenharmony_ci	if (IS_ERR(txwin)) {
31562306a36Sopenharmony_ci		pr_err_ratelimited("VAS window open failed rc=%ld\n",
31662306a36Sopenharmony_ci				PTR_ERR(txwin));
31762306a36Sopenharmony_ci		return PTR_ERR(txwin);
31862306a36Sopenharmony_ci	}
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	mutex_init(&txwin->task_ref.mmap_mutex);
32162306a36Sopenharmony_ci	cp_inst->txwin = txwin;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	return 0;
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cistatic int coproc_release(struct inode *inode, struct file *fp)
32762306a36Sopenharmony_ci{
32862306a36Sopenharmony_ci	struct coproc_instance *cp_inst = fp->private_data;
32962306a36Sopenharmony_ci	int rc;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	if (cp_inst->txwin) {
33262306a36Sopenharmony_ci		if (cp_inst->coproc->vops &&
33362306a36Sopenharmony_ci			cp_inst->coproc->vops->close_win) {
33462306a36Sopenharmony_ci			rc = cp_inst->coproc->vops->close_win(cp_inst->txwin);
33562306a36Sopenharmony_ci			if (rc)
33662306a36Sopenharmony_ci				return rc;
33762306a36Sopenharmony_ci		}
33862306a36Sopenharmony_ci		cp_inst->txwin = NULL;
33962306a36Sopenharmony_ci	}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	kfree(cp_inst);
34262306a36Sopenharmony_ci	fp->private_data = NULL;
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	/*
34562306a36Sopenharmony_ci	 * We don't know here if user has other receive windows
34662306a36Sopenharmony_ci	 * open, so we can't really call clear_thread_tidr().
34762306a36Sopenharmony_ci	 * So, once the process calls set_thread_tidr(), the
34862306a36Sopenharmony_ci	 * TIDR value sticks around until process exits, resulting
34962306a36Sopenharmony_ci	 * in an extra copy in restore_sprs().
35062306a36Sopenharmony_ci	 */
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	return 0;
35362306a36Sopenharmony_ci}
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci/*
35662306a36Sopenharmony_ci * If the executed instruction that caused the fault was a paste, then
35762306a36Sopenharmony_ci * clear regs CR0[EQ], advance NIP, and return 0. Else return error code.
35862306a36Sopenharmony_ci */
35962306a36Sopenharmony_cistatic int do_fail_paste(void)
36062306a36Sopenharmony_ci{
36162306a36Sopenharmony_ci	struct pt_regs *regs = current->thread.regs;
36262306a36Sopenharmony_ci	u32 instword;
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	if (WARN_ON_ONCE(!regs))
36562306a36Sopenharmony_ci		return -EINVAL;
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	if (WARN_ON_ONCE(!user_mode(regs)))
36862306a36Sopenharmony_ci		return -EINVAL;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	/*
37162306a36Sopenharmony_ci	 * If we couldn't translate the instruction, the driver should
37262306a36Sopenharmony_ci	 * return success without handling the fault, it will be retried
37362306a36Sopenharmony_ci	 * or the instruction fetch will fault.
37462306a36Sopenharmony_ci	 */
37562306a36Sopenharmony_ci	if (get_user(instword, (u32 __user *)(regs->nip)))
37662306a36Sopenharmony_ci		return -EAGAIN;
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	/*
37962306a36Sopenharmony_ci	 * Not a paste instruction, driver may fail the fault.
38062306a36Sopenharmony_ci	 */
38162306a36Sopenharmony_ci	if ((instword & PPC_INST_PASTE_MASK) != PPC_INST_PASTE)
38262306a36Sopenharmony_ci		return -ENOENT;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	regs->ccr &= ~0xe0000000;	/* Clear CR0[0-2] to fail paste */
38562306a36Sopenharmony_ci	regs_add_return_ip(regs, 4);	/* Emulate the paste */
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	return 0;
38862306a36Sopenharmony_ci}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci/*
39162306a36Sopenharmony_ci * This fault handler is invoked when the core generates page fault on
39262306a36Sopenharmony_ci * the paste address. Happens if the kernel closes window in hypervisor
39362306a36Sopenharmony_ci * (on pseries) due to lost credit or the paste address is not mapped.
39462306a36Sopenharmony_ci */
39562306a36Sopenharmony_cistatic vm_fault_t vas_mmap_fault(struct vm_fault *vmf)
39662306a36Sopenharmony_ci{
39762306a36Sopenharmony_ci	struct vm_area_struct *vma = vmf->vma;
39862306a36Sopenharmony_ci	struct file *fp = vma->vm_file;
39962306a36Sopenharmony_ci	struct coproc_instance *cp_inst = fp->private_data;
40062306a36Sopenharmony_ci	struct vas_window *txwin;
40162306a36Sopenharmony_ci	vm_fault_t fault;
40262306a36Sopenharmony_ci	u64 paste_addr;
40362306a36Sopenharmony_ci	int ret;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	/*
40662306a36Sopenharmony_ci	 * window is not opened. Shouldn't expect this error.
40762306a36Sopenharmony_ci	 */
40862306a36Sopenharmony_ci	if (!cp_inst || !cp_inst->txwin) {
40962306a36Sopenharmony_ci		pr_err("Unexpected fault on paste address with TX window closed\n");
41062306a36Sopenharmony_ci		return VM_FAULT_SIGBUS;
41162306a36Sopenharmony_ci	}
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	txwin = cp_inst->txwin;
41462306a36Sopenharmony_ci	/*
41562306a36Sopenharmony_ci	 * When the LPAR lost credits due to core removal or during
41662306a36Sopenharmony_ci	 * migration, invalidate the existing mapping for the current
41762306a36Sopenharmony_ci	 * paste addresses and set windows in-active (zap_vma_pages in
41862306a36Sopenharmony_ci	 * reconfig_close_windows()).
41962306a36Sopenharmony_ci	 * New mapping will be done later after migration or new credits
42062306a36Sopenharmony_ci	 * available. So continue to receive faults if the user space
42162306a36Sopenharmony_ci	 * issue NX request.
42262306a36Sopenharmony_ci	 */
42362306a36Sopenharmony_ci	if (txwin->task_ref.vma != vmf->vma) {
42462306a36Sopenharmony_ci		pr_err("No previous mapping with paste address\n");
42562306a36Sopenharmony_ci		return VM_FAULT_SIGBUS;
42662306a36Sopenharmony_ci	}
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	mutex_lock(&txwin->task_ref.mmap_mutex);
42962306a36Sopenharmony_ci	/*
43062306a36Sopenharmony_ci	 * The window may be inactive due to lost credit (Ex: core
43162306a36Sopenharmony_ci	 * removal with DLPAR). If the window is active again when
43262306a36Sopenharmony_ci	 * the credit is available, map the new paste address at the
43362306a36Sopenharmony_ci	 * window virtual address.
43462306a36Sopenharmony_ci	 */
43562306a36Sopenharmony_ci	if (txwin->status == VAS_WIN_ACTIVE) {
43662306a36Sopenharmony_ci		paste_addr = cp_inst->coproc->vops->paste_addr(txwin);
43762306a36Sopenharmony_ci		if (paste_addr) {
43862306a36Sopenharmony_ci			fault = vmf_insert_pfn(vma, vma->vm_start,
43962306a36Sopenharmony_ci					(paste_addr >> PAGE_SHIFT));
44062306a36Sopenharmony_ci			mutex_unlock(&txwin->task_ref.mmap_mutex);
44162306a36Sopenharmony_ci			return fault;
44262306a36Sopenharmony_ci		}
44362306a36Sopenharmony_ci	}
44462306a36Sopenharmony_ci	mutex_unlock(&txwin->task_ref.mmap_mutex);
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	/*
44762306a36Sopenharmony_ci	 * Received this fault due to closing the actual window.
44862306a36Sopenharmony_ci	 * It can happen during migration or lost credits.
44962306a36Sopenharmony_ci	 * Since no mapping, return the paste instruction failure
45062306a36Sopenharmony_ci	 * to the user space.
45162306a36Sopenharmony_ci	 */
45262306a36Sopenharmony_ci	ret = do_fail_paste();
45362306a36Sopenharmony_ci	/*
45462306a36Sopenharmony_ci	 * The user space can retry several times until success (needed
45562306a36Sopenharmony_ci	 * for migration) or should fallback to SW compression or
45662306a36Sopenharmony_ci	 * manage with the existing open windows if available.
45762306a36Sopenharmony_ci	 * Looking at sysfs interface, it can determine whether these
45862306a36Sopenharmony_ci	 * failures are coming during migration or core removal:
45962306a36Sopenharmony_ci	 * nr_used_credits > nr_total_credits when lost credits
46062306a36Sopenharmony_ci	 */
46162306a36Sopenharmony_ci	if (!ret || (ret == -EAGAIN))
46262306a36Sopenharmony_ci		return VM_FAULT_NOPAGE;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	return VM_FAULT_SIGBUS;
46562306a36Sopenharmony_ci}
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_cistatic const struct vm_operations_struct vas_vm_ops = {
46862306a36Sopenharmony_ci	.fault = vas_mmap_fault,
46962306a36Sopenharmony_ci};
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_cistatic int coproc_mmap(struct file *fp, struct vm_area_struct *vma)
47262306a36Sopenharmony_ci{
47362306a36Sopenharmony_ci	struct coproc_instance *cp_inst = fp->private_data;
47462306a36Sopenharmony_ci	struct vas_window *txwin;
47562306a36Sopenharmony_ci	unsigned long pfn;
47662306a36Sopenharmony_ci	u64 paste_addr;
47762306a36Sopenharmony_ci	pgprot_t prot;
47862306a36Sopenharmony_ci	int rc;
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	txwin = cp_inst->txwin;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	if ((vma->vm_end - vma->vm_start) > PAGE_SIZE) {
48362306a36Sopenharmony_ci		pr_debug("size 0x%zx, PAGE_SIZE 0x%zx\n",
48462306a36Sopenharmony_ci				(vma->vm_end - vma->vm_start), PAGE_SIZE);
48562306a36Sopenharmony_ci		return -EINVAL;
48662306a36Sopenharmony_ci	}
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	/* Ensure instance has an open send window */
48962306a36Sopenharmony_ci	if (!txwin) {
49062306a36Sopenharmony_ci		pr_err("No send window open?\n");
49162306a36Sopenharmony_ci		return -EINVAL;
49262306a36Sopenharmony_ci	}
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	if (!cp_inst->coproc->vops || !cp_inst->coproc->vops->paste_addr) {
49562306a36Sopenharmony_ci		pr_err("VAS API is not registered\n");
49662306a36Sopenharmony_ci		return -EACCES;
49762306a36Sopenharmony_ci	}
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	/*
50062306a36Sopenharmony_ci	 * The initial mmap is done after the window is opened
50162306a36Sopenharmony_ci	 * with ioctl. But before mmap(), this window can be closed in
50262306a36Sopenharmony_ci	 * the hypervisor due to lost credit (core removal on pseries).
50362306a36Sopenharmony_ci	 * So if the window is not active, return mmap() failure with
50462306a36Sopenharmony_ci	 * -EACCES and expects the user space reissue mmap() when it
50562306a36Sopenharmony_ci	 * is active again or open new window when the credit is available.
50662306a36Sopenharmony_ci	 * mmap_mutex protects the paste address mmap() with DLPAR
50762306a36Sopenharmony_ci	 * close/open event and allows mmap() only when the window is
50862306a36Sopenharmony_ci	 * active.
50962306a36Sopenharmony_ci	 */
51062306a36Sopenharmony_ci	mutex_lock(&txwin->task_ref.mmap_mutex);
51162306a36Sopenharmony_ci	if (txwin->status != VAS_WIN_ACTIVE) {
51262306a36Sopenharmony_ci		pr_err("Window is not active\n");
51362306a36Sopenharmony_ci		rc = -EACCES;
51462306a36Sopenharmony_ci		goto out;
51562306a36Sopenharmony_ci	}
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci	paste_addr = cp_inst->coproc->vops->paste_addr(txwin);
51862306a36Sopenharmony_ci	if (!paste_addr) {
51962306a36Sopenharmony_ci		pr_err("Window paste address failed\n");
52062306a36Sopenharmony_ci		rc = -EINVAL;
52162306a36Sopenharmony_ci		goto out;
52262306a36Sopenharmony_ci	}
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	pfn = paste_addr >> PAGE_SHIFT;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	/* flags, page_prot from cxl_mmap(), except we want cachable */
52762306a36Sopenharmony_ci	vm_flags_set(vma, VM_IO | VM_PFNMAP);
52862306a36Sopenharmony_ci	vma->vm_page_prot = pgprot_cached(vma->vm_page_prot);
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci	prot = __pgprot(pgprot_val(vma->vm_page_prot) | _PAGE_DIRTY);
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	rc = remap_pfn_range(vma, vma->vm_start, pfn + vma->vm_pgoff,
53362306a36Sopenharmony_ci			vma->vm_end - vma->vm_start, prot);
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	pr_devel("paste addr %llx at %lx, rc %d\n", paste_addr,
53662306a36Sopenharmony_ci			vma->vm_start, rc);
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	txwin->task_ref.vma = vma;
53962306a36Sopenharmony_ci	vma->vm_ops = &vas_vm_ops;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ciout:
54262306a36Sopenharmony_ci	mutex_unlock(&txwin->task_ref.mmap_mutex);
54362306a36Sopenharmony_ci	return rc;
54462306a36Sopenharmony_ci}
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_cistatic long coproc_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
54762306a36Sopenharmony_ci{
54862306a36Sopenharmony_ci	switch (cmd) {
54962306a36Sopenharmony_ci	case VAS_TX_WIN_OPEN:
55062306a36Sopenharmony_ci		return coproc_ioc_tx_win_open(fp, arg);
55162306a36Sopenharmony_ci	default:
55262306a36Sopenharmony_ci		return -EINVAL;
55362306a36Sopenharmony_ci	}
55462306a36Sopenharmony_ci}
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_cistatic struct file_operations coproc_fops = {
55762306a36Sopenharmony_ci	.open = coproc_open,
55862306a36Sopenharmony_ci	.release = coproc_release,
55962306a36Sopenharmony_ci	.mmap = coproc_mmap,
56062306a36Sopenharmony_ci	.unlocked_ioctl = coproc_ioctl,
56162306a36Sopenharmony_ci};
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci/*
56462306a36Sopenharmony_ci * Supporting only nx-gzip coprocessor type now, but this API code
56562306a36Sopenharmony_ci * extended to other coprocessor types later.
56662306a36Sopenharmony_ci */
56762306a36Sopenharmony_ciint vas_register_coproc_api(struct module *mod, enum vas_cop_type cop_type,
56862306a36Sopenharmony_ci			    const char *name,
56962306a36Sopenharmony_ci			    const struct vas_user_win_ops *vops)
57062306a36Sopenharmony_ci{
57162306a36Sopenharmony_ci	int rc = -EINVAL;
57262306a36Sopenharmony_ci	dev_t devno;
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	rc = alloc_chrdev_region(&coproc_device.devt, 1, 1, name);
57562306a36Sopenharmony_ci	if (rc) {
57662306a36Sopenharmony_ci		pr_err("Unable to allocate coproc major number: %i\n", rc);
57762306a36Sopenharmony_ci		return rc;
57862306a36Sopenharmony_ci	}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	pr_devel("%s device allocated, dev [%i,%i]\n", name,
58162306a36Sopenharmony_ci			MAJOR(coproc_device.devt), MINOR(coproc_device.devt));
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	coproc_device.class = class_create(name);
58462306a36Sopenharmony_ci	if (IS_ERR(coproc_device.class)) {
58562306a36Sopenharmony_ci		rc = PTR_ERR(coproc_device.class);
58662306a36Sopenharmony_ci		pr_err("Unable to create %s class %d\n", name, rc);
58762306a36Sopenharmony_ci		goto err_class;
58862306a36Sopenharmony_ci	}
58962306a36Sopenharmony_ci	coproc_device.class->devnode = coproc_devnode;
59062306a36Sopenharmony_ci	coproc_device.cop_type = cop_type;
59162306a36Sopenharmony_ci	coproc_device.vops = vops;
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	coproc_fops.owner = mod;
59462306a36Sopenharmony_ci	cdev_init(&coproc_device.cdev, &coproc_fops);
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci	devno = MKDEV(MAJOR(coproc_device.devt), 0);
59762306a36Sopenharmony_ci	rc = cdev_add(&coproc_device.cdev, devno, 1);
59862306a36Sopenharmony_ci	if (rc) {
59962306a36Sopenharmony_ci		pr_err("cdev_add() failed %d\n", rc);
60062306a36Sopenharmony_ci		goto err_cdev;
60162306a36Sopenharmony_ci	}
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci	coproc_device.device = device_create(coproc_device.class, NULL,
60462306a36Sopenharmony_ci			devno, NULL, name, MINOR(devno));
60562306a36Sopenharmony_ci	if (IS_ERR(coproc_device.device)) {
60662306a36Sopenharmony_ci		rc = PTR_ERR(coproc_device.device);
60762306a36Sopenharmony_ci		pr_err("Unable to create coproc-%d %d\n", MINOR(devno), rc);
60862306a36Sopenharmony_ci		goto err;
60962306a36Sopenharmony_ci	}
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci	pr_devel("Added dev [%d,%d]\n", MAJOR(devno), MINOR(devno));
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_ci	return 0;
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_cierr:
61662306a36Sopenharmony_ci	cdev_del(&coproc_device.cdev);
61762306a36Sopenharmony_cierr_cdev:
61862306a36Sopenharmony_ci	class_destroy(coproc_device.class);
61962306a36Sopenharmony_cierr_class:
62062306a36Sopenharmony_ci	unregister_chrdev_region(coproc_device.devt, 1);
62162306a36Sopenharmony_ci	return rc;
62262306a36Sopenharmony_ci}
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_civoid vas_unregister_coproc_api(void)
62562306a36Sopenharmony_ci{
62662306a36Sopenharmony_ci	dev_t devno;
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	cdev_del(&coproc_device.cdev);
62962306a36Sopenharmony_ci	devno = MKDEV(MAJOR(coproc_device.devt), 0);
63062306a36Sopenharmony_ci	device_destroy(coproc_device.class, devno);
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci	class_destroy(coproc_device.class);
63362306a36Sopenharmony_ci	unregister_chrdev_region(coproc_device.devt, 1);
63462306a36Sopenharmony_ci}
635