162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* Copyright(c) 2019 Intel Corporation. All rights rsvd. */
362306a36Sopenharmony_ci#include <linux/init.h>
462306a36Sopenharmony_ci#include <linux/kernel.h>
562306a36Sopenharmony_ci#include <linux/module.h>
662306a36Sopenharmony_ci#include <linux/pci.h>
762306a36Sopenharmony_ci#include <linux/device.h>
862306a36Sopenharmony_ci#include <linux/sched/task.h>
962306a36Sopenharmony_ci#include <linux/io-64-nonatomic-lo-hi.h>
1062306a36Sopenharmony_ci#include <linux/cdev.h>
1162306a36Sopenharmony_ci#include <linux/fs.h>
1262306a36Sopenharmony_ci#include <linux/poll.h>
1362306a36Sopenharmony_ci#include <linux/iommu.h>
1462306a36Sopenharmony_ci#include <linux/highmem.h>
1562306a36Sopenharmony_ci#include <uapi/linux/idxd.h>
1662306a36Sopenharmony_ci#include <linux/xarray.h>
1762306a36Sopenharmony_ci#include "registers.h"
1862306a36Sopenharmony_ci#include "idxd.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistruct idxd_cdev_context {
2162306a36Sopenharmony_ci	const char *name;
2262306a36Sopenharmony_ci	dev_t devt;
2362306a36Sopenharmony_ci	struct ida minor_ida;
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/*
2762306a36Sopenharmony_ci * Since user file names are global in DSA devices, define their ida's as
2862306a36Sopenharmony_ci * global to avoid conflict file names.
2962306a36Sopenharmony_ci */
3062306a36Sopenharmony_cistatic DEFINE_IDA(file_ida);
3162306a36Sopenharmony_cistatic DEFINE_MUTEX(ida_lock);
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci/*
3462306a36Sopenharmony_ci * ictx is an array based off of accelerator types. enum idxd_type
3562306a36Sopenharmony_ci * is used as index
3662306a36Sopenharmony_ci */
3762306a36Sopenharmony_cistatic struct idxd_cdev_context ictx[IDXD_TYPE_MAX] = {
3862306a36Sopenharmony_ci	{ .name = "dsa" },
3962306a36Sopenharmony_ci	{ .name = "iax" }
4062306a36Sopenharmony_ci};
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistruct idxd_user_context {
4362306a36Sopenharmony_ci	struct idxd_wq *wq;
4462306a36Sopenharmony_ci	struct task_struct *task;
4562306a36Sopenharmony_ci	unsigned int pasid;
4662306a36Sopenharmony_ci	struct mm_struct *mm;
4762306a36Sopenharmony_ci	unsigned int flags;
4862306a36Sopenharmony_ci	struct iommu_sva *sva;
4962306a36Sopenharmony_ci	struct idxd_dev idxd_dev;
5062306a36Sopenharmony_ci	u64 counters[COUNTER_MAX];
5162306a36Sopenharmony_ci	int id;
5262306a36Sopenharmony_ci	pid_t pid;
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic void idxd_cdev_evl_drain_pasid(struct idxd_wq *wq, u32 pasid);
5662306a36Sopenharmony_cistatic void idxd_xa_pasid_remove(struct idxd_user_context *ctx);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic inline struct idxd_user_context *dev_to_uctx(struct device *dev)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	struct idxd_dev *idxd_dev = confdev_to_idxd_dev(dev);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	return container_of(idxd_dev, struct idxd_user_context, idxd_dev);
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic ssize_t cr_faults_show(struct device *dev, struct device_attribute *attr, char *buf)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	struct idxd_user_context *ctx = dev_to_uctx(dev);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	return sysfs_emit(buf, "%llu\n", ctx->counters[COUNTER_FAULTS]);
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_cistatic DEVICE_ATTR_RO(cr_faults);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic ssize_t cr_fault_failures_show(struct device *dev,
7462306a36Sopenharmony_ci				      struct device_attribute *attr, char *buf)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	struct idxd_user_context *ctx = dev_to_uctx(dev);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	return sysfs_emit(buf, "%llu\n", ctx->counters[COUNTER_FAULT_FAILS]);
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_cistatic DEVICE_ATTR_RO(cr_fault_failures);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic ssize_t pid_show(struct device *dev, struct device_attribute *attr, char *buf)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	struct idxd_user_context *ctx = dev_to_uctx(dev);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	return sysfs_emit(buf, "%u\n", ctx->pid);
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_cistatic DEVICE_ATTR_RO(pid);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic struct attribute *cdev_file_attributes[] = {
9162306a36Sopenharmony_ci	&dev_attr_cr_faults.attr,
9262306a36Sopenharmony_ci	&dev_attr_cr_fault_failures.attr,
9362306a36Sopenharmony_ci	&dev_attr_pid.attr,
9462306a36Sopenharmony_ci	NULL
9562306a36Sopenharmony_ci};
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic umode_t cdev_file_attr_visible(struct kobject *kobj, struct attribute *a, int n)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	struct device *dev = container_of(kobj, typeof(*dev), kobj);
10062306a36Sopenharmony_ci	struct idxd_user_context *ctx = dev_to_uctx(dev);
10162306a36Sopenharmony_ci	struct idxd_wq *wq = ctx->wq;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (!wq_pasid_enabled(wq))
10462306a36Sopenharmony_ci		return 0;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	return a->mode;
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic const struct attribute_group cdev_file_attribute_group = {
11062306a36Sopenharmony_ci	.attrs = cdev_file_attributes,
11162306a36Sopenharmony_ci	.is_visible = cdev_file_attr_visible,
11262306a36Sopenharmony_ci};
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic const struct attribute_group *cdev_file_attribute_groups[] = {
11562306a36Sopenharmony_ci	&cdev_file_attribute_group,
11662306a36Sopenharmony_ci	NULL
11762306a36Sopenharmony_ci};
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic void idxd_file_dev_release(struct device *dev)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	struct idxd_user_context *ctx = dev_to_uctx(dev);
12262306a36Sopenharmony_ci	struct idxd_wq *wq = ctx->wq;
12362306a36Sopenharmony_ci	struct idxd_device *idxd = wq->idxd;
12462306a36Sopenharmony_ci	int rc;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	mutex_lock(&ida_lock);
12762306a36Sopenharmony_ci	ida_free(&file_ida, ctx->id);
12862306a36Sopenharmony_ci	mutex_unlock(&ida_lock);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/* Wait for in-flight operations to complete. */
13162306a36Sopenharmony_ci	if (wq_shared(wq)) {
13262306a36Sopenharmony_ci		idxd_device_drain_pasid(idxd, ctx->pasid);
13362306a36Sopenharmony_ci	} else {
13462306a36Sopenharmony_ci		if (device_user_pasid_enabled(idxd)) {
13562306a36Sopenharmony_ci			/* The wq disable in the disable pasid function will drain the wq */
13662306a36Sopenharmony_ci			rc = idxd_wq_disable_pasid(wq);
13762306a36Sopenharmony_ci			if (rc < 0)
13862306a36Sopenharmony_ci				dev_err(dev, "wq disable pasid failed.\n");
13962306a36Sopenharmony_ci		} else {
14062306a36Sopenharmony_ci			idxd_wq_drain(wq);
14162306a36Sopenharmony_ci		}
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	if (ctx->sva) {
14562306a36Sopenharmony_ci		idxd_cdev_evl_drain_pasid(wq, ctx->pasid);
14662306a36Sopenharmony_ci		iommu_sva_unbind_device(ctx->sva);
14762306a36Sopenharmony_ci		idxd_xa_pasid_remove(ctx);
14862306a36Sopenharmony_ci	}
14962306a36Sopenharmony_ci	kfree(ctx);
15062306a36Sopenharmony_ci	mutex_lock(&wq->wq_lock);
15162306a36Sopenharmony_ci	idxd_wq_put(wq);
15262306a36Sopenharmony_ci	mutex_unlock(&wq->wq_lock);
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic struct device_type idxd_cdev_file_type = {
15662306a36Sopenharmony_ci	.name = "idxd_file",
15762306a36Sopenharmony_ci	.release = idxd_file_dev_release,
15862306a36Sopenharmony_ci	.groups = cdev_file_attribute_groups,
15962306a36Sopenharmony_ci};
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic void idxd_cdev_dev_release(struct device *dev)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	struct idxd_cdev *idxd_cdev = dev_to_cdev(dev);
16462306a36Sopenharmony_ci	struct idxd_cdev_context *cdev_ctx;
16562306a36Sopenharmony_ci	struct idxd_wq *wq = idxd_cdev->wq;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	cdev_ctx = &ictx[wq->idxd->data->type];
16862306a36Sopenharmony_ci	ida_simple_remove(&cdev_ctx->minor_ida, idxd_cdev->minor);
16962306a36Sopenharmony_ci	kfree(idxd_cdev);
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_cistatic struct device_type idxd_cdev_device_type = {
17362306a36Sopenharmony_ci	.name = "idxd_cdev",
17462306a36Sopenharmony_ci	.release = idxd_cdev_dev_release,
17562306a36Sopenharmony_ci};
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic inline struct idxd_cdev *inode_idxd_cdev(struct inode *inode)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	struct cdev *cdev = inode->i_cdev;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	return container_of(cdev, struct idxd_cdev, cdev);
18262306a36Sopenharmony_ci}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cistatic inline struct idxd_wq *inode_wq(struct inode *inode)
18562306a36Sopenharmony_ci{
18662306a36Sopenharmony_ci	struct idxd_cdev *idxd_cdev = inode_idxd_cdev(inode);
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	return idxd_cdev->wq;
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cistatic void idxd_xa_pasid_remove(struct idxd_user_context *ctx)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	struct idxd_wq *wq = ctx->wq;
19462306a36Sopenharmony_ci	void *ptr;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	mutex_lock(&wq->uc_lock);
19762306a36Sopenharmony_ci	ptr = xa_cmpxchg(&wq->upasid_xa, ctx->pasid, ctx, NULL, GFP_KERNEL);
19862306a36Sopenharmony_ci	if (ptr != (void *)ctx)
19962306a36Sopenharmony_ci		dev_warn(&wq->idxd->pdev->dev, "xarray cmpxchg failed for pasid %u\n",
20062306a36Sopenharmony_ci			 ctx->pasid);
20162306a36Sopenharmony_ci	mutex_unlock(&wq->uc_lock);
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_civoid idxd_user_counter_increment(struct idxd_wq *wq, u32 pasid, int index)
20562306a36Sopenharmony_ci{
20662306a36Sopenharmony_ci	struct idxd_user_context *ctx;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (index >= COUNTER_MAX)
20962306a36Sopenharmony_ci		return;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	mutex_lock(&wq->uc_lock);
21262306a36Sopenharmony_ci	ctx = xa_load(&wq->upasid_xa, pasid);
21362306a36Sopenharmony_ci	if (!ctx) {
21462306a36Sopenharmony_ci		mutex_unlock(&wq->uc_lock);
21562306a36Sopenharmony_ci		return;
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci	ctx->counters[index]++;
21862306a36Sopenharmony_ci	mutex_unlock(&wq->uc_lock);
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic int idxd_cdev_open(struct inode *inode, struct file *filp)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	struct idxd_user_context *ctx;
22462306a36Sopenharmony_ci	struct idxd_device *idxd;
22562306a36Sopenharmony_ci	struct idxd_wq *wq;
22662306a36Sopenharmony_ci	struct device *dev, *fdev;
22762306a36Sopenharmony_ci	int rc = 0;
22862306a36Sopenharmony_ci	struct iommu_sva *sva;
22962306a36Sopenharmony_ci	unsigned int pasid;
23062306a36Sopenharmony_ci	struct idxd_cdev *idxd_cdev;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	wq = inode_wq(inode);
23362306a36Sopenharmony_ci	idxd = wq->idxd;
23462306a36Sopenharmony_ci	dev = &idxd->pdev->dev;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	dev_dbg(dev, "%s called: %d\n", __func__, idxd_wq_refcount(wq));
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
23962306a36Sopenharmony_ci	if (!ctx)
24062306a36Sopenharmony_ci		return -ENOMEM;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	mutex_lock(&wq->wq_lock);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	if (idxd_wq_refcount(wq) > 0 && wq_dedicated(wq)) {
24562306a36Sopenharmony_ci		rc = -EBUSY;
24662306a36Sopenharmony_ci		goto failed;
24762306a36Sopenharmony_ci	}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	ctx->wq = wq;
25062306a36Sopenharmony_ci	filp->private_data = ctx;
25162306a36Sopenharmony_ci	ctx->pid = current->pid;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	if (device_user_pasid_enabled(idxd)) {
25462306a36Sopenharmony_ci		sva = iommu_sva_bind_device(dev, current->mm);
25562306a36Sopenharmony_ci		if (IS_ERR(sva)) {
25662306a36Sopenharmony_ci			rc = PTR_ERR(sva);
25762306a36Sopenharmony_ci			dev_err(dev, "pasid allocation failed: %d\n", rc);
25862306a36Sopenharmony_ci			goto failed;
25962306a36Sopenharmony_ci		}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci		pasid = iommu_sva_get_pasid(sva);
26262306a36Sopenharmony_ci		if (pasid == IOMMU_PASID_INVALID) {
26362306a36Sopenharmony_ci			rc = -EINVAL;
26462306a36Sopenharmony_ci			goto failed_get_pasid;
26562306a36Sopenharmony_ci		}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci		ctx->sva = sva;
26862306a36Sopenharmony_ci		ctx->pasid = pasid;
26962306a36Sopenharmony_ci		ctx->mm = current->mm;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci		mutex_lock(&wq->uc_lock);
27262306a36Sopenharmony_ci		rc = xa_insert(&wq->upasid_xa, pasid, ctx, GFP_KERNEL);
27362306a36Sopenharmony_ci		mutex_unlock(&wq->uc_lock);
27462306a36Sopenharmony_ci		if (rc < 0)
27562306a36Sopenharmony_ci			dev_warn(dev, "PASID entry already exist in xarray.\n");
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci		if (wq_dedicated(wq)) {
27862306a36Sopenharmony_ci			rc = idxd_wq_set_pasid(wq, pasid);
27962306a36Sopenharmony_ci			if (rc < 0) {
28062306a36Sopenharmony_ci				dev_err(dev, "wq set pasid failed: %d\n", rc);
28162306a36Sopenharmony_ci				goto failed_set_pasid;
28262306a36Sopenharmony_ci			}
28362306a36Sopenharmony_ci		}
28462306a36Sopenharmony_ci	}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	idxd_cdev = wq->idxd_cdev;
28762306a36Sopenharmony_ci	mutex_lock(&ida_lock);
28862306a36Sopenharmony_ci	ctx->id = ida_alloc(&file_ida, GFP_KERNEL);
28962306a36Sopenharmony_ci	mutex_unlock(&ida_lock);
29062306a36Sopenharmony_ci	if (ctx->id < 0) {
29162306a36Sopenharmony_ci		dev_warn(dev, "ida alloc failure\n");
29262306a36Sopenharmony_ci		goto failed_ida;
29362306a36Sopenharmony_ci	}
29462306a36Sopenharmony_ci	ctx->idxd_dev.type  = IDXD_DEV_CDEV_FILE;
29562306a36Sopenharmony_ci	fdev = user_ctx_dev(ctx);
29662306a36Sopenharmony_ci	device_initialize(fdev);
29762306a36Sopenharmony_ci	fdev->parent = cdev_dev(idxd_cdev);
29862306a36Sopenharmony_ci	fdev->bus = &dsa_bus_type;
29962306a36Sopenharmony_ci	fdev->type = &idxd_cdev_file_type;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	rc = dev_set_name(fdev, "file%d", ctx->id);
30262306a36Sopenharmony_ci	if (rc < 0) {
30362306a36Sopenharmony_ci		dev_warn(dev, "set name failure\n");
30462306a36Sopenharmony_ci		goto failed_dev_name;
30562306a36Sopenharmony_ci	}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	rc = device_add(fdev);
30862306a36Sopenharmony_ci	if (rc < 0) {
30962306a36Sopenharmony_ci		dev_warn(dev, "file device add failure\n");
31062306a36Sopenharmony_ci		goto failed_dev_add;
31162306a36Sopenharmony_ci	}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	idxd_wq_get(wq);
31462306a36Sopenharmony_ci	mutex_unlock(&wq->wq_lock);
31562306a36Sopenharmony_ci	return 0;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_cifailed_dev_add:
31862306a36Sopenharmony_cifailed_dev_name:
31962306a36Sopenharmony_ci	put_device(fdev);
32062306a36Sopenharmony_cifailed_ida:
32162306a36Sopenharmony_cifailed_set_pasid:
32262306a36Sopenharmony_ci	if (device_user_pasid_enabled(idxd))
32362306a36Sopenharmony_ci		idxd_xa_pasid_remove(ctx);
32462306a36Sopenharmony_cifailed_get_pasid:
32562306a36Sopenharmony_ci	if (device_user_pasid_enabled(idxd))
32662306a36Sopenharmony_ci		iommu_sva_unbind_device(sva);
32762306a36Sopenharmony_cifailed:
32862306a36Sopenharmony_ci	mutex_unlock(&wq->wq_lock);
32962306a36Sopenharmony_ci	kfree(ctx);
33062306a36Sopenharmony_ci	return rc;
33162306a36Sopenharmony_ci}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_cistatic void idxd_cdev_evl_drain_pasid(struct idxd_wq *wq, u32 pasid)
33462306a36Sopenharmony_ci{
33562306a36Sopenharmony_ci	struct idxd_device *idxd = wq->idxd;
33662306a36Sopenharmony_ci	struct idxd_evl *evl = idxd->evl;
33762306a36Sopenharmony_ci	union evl_status_reg status;
33862306a36Sopenharmony_ci	u16 h, t, size;
33962306a36Sopenharmony_ci	int ent_size = evl_ent_size(idxd);
34062306a36Sopenharmony_ci	struct __evl_entry *entry_head;
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	if (!evl)
34362306a36Sopenharmony_ci		return;
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	spin_lock(&evl->lock);
34662306a36Sopenharmony_ci	status.bits = ioread64(idxd->reg_base + IDXD_EVLSTATUS_OFFSET);
34762306a36Sopenharmony_ci	t = status.tail;
34862306a36Sopenharmony_ci	h = status.head;
34962306a36Sopenharmony_ci	size = evl->size;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	while (h != t) {
35262306a36Sopenharmony_ci		entry_head = (struct __evl_entry *)(evl->log + (h * ent_size));
35362306a36Sopenharmony_ci		if (entry_head->pasid == pasid && entry_head->wq_idx == wq->id)
35462306a36Sopenharmony_ci			set_bit(h, evl->bmap);
35562306a36Sopenharmony_ci		h = (h + 1) % size;
35662306a36Sopenharmony_ci	}
35762306a36Sopenharmony_ci	spin_unlock(&evl->lock);
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	drain_workqueue(wq->wq);
36062306a36Sopenharmony_ci}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_cistatic int idxd_cdev_release(struct inode *node, struct file *filep)
36362306a36Sopenharmony_ci{
36462306a36Sopenharmony_ci	struct idxd_user_context *ctx = filep->private_data;
36562306a36Sopenharmony_ci	struct idxd_wq *wq = ctx->wq;
36662306a36Sopenharmony_ci	struct idxd_device *idxd = wq->idxd;
36762306a36Sopenharmony_ci	struct device *dev = &idxd->pdev->dev;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	dev_dbg(dev, "%s called\n", __func__);
37062306a36Sopenharmony_ci	filep->private_data = NULL;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	device_unregister(user_ctx_dev(ctx));
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	return 0;
37562306a36Sopenharmony_ci}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_cistatic int check_vma(struct idxd_wq *wq, struct vm_area_struct *vma,
37862306a36Sopenharmony_ci		     const char *func)
37962306a36Sopenharmony_ci{
38062306a36Sopenharmony_ci	struct device *dev = &wq->idxd->pdev->dev;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	if ((vma->vm_end - vma->vm_start) > PAGE_SIZE) {
38362306a36Sopenharmony_ci		dev_info_ratelimited(dev,
38462306a36Sopenharmony_ci				     "%s: %s: mapping too large: %lu\n",
38562306a36Sopenharmony_ci				     current->comm, func,
38662306a36Sopenharmony_ci				     vma->vm_end - vma->vm_start);
38762306a36Sopenharmony_ci		return -EINVAL;
38862306a36Sopenharmony_ci	}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	return 0;
39162306a36Sopenharmony_ci}
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_cistatic int idxd_cdev_mmap(struct file *filp, struct vm_area_struct *vma)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	struct idxd_user_context *ctx = filp->private_data;
39662306a36Sopenharmony_ci	struct idxd_wq *wq = ctx->wq;
39762306a36Sopenharmony_ci	struct idxd_device *idxd = wq->idxd;
39862306a36Sopenharmony_ci	struct pci_dev *pdev = idxd->pdev;
39962306a36Sopenharmony_ci	phys_addr_t base = pci_resource_start(pdev, IDXD_WQ_BAR);
40062306a36Sopenharmony_ci	unsigned long pfn;
40162306a36Sopenharmony_ci	int rc;
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci	dev_dbg(&pdev->dev, "%s called\n", __func__);
40462306a36Sopenharmony_ci	rc = check_vma(wq, vma, __func__);
40562306a36Sopenharmony_ci	if (rc < 0)
40662306a36Sopenharmony_ci		return rc;
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	vm_flags_set(vma, VM_DONTCOPY);
40962306a36Sopenharmony_ci	pfn = (base + idxd_get_wq_portal_full_offset(wq->id,
41062306a36Sopenharmony_ci				IDXD_PORTAL_LIMITED)) >> PAGE_SHIFT;
41162306a36Sopenharmony_ci	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
41262306a36Sopenharmony_ci	vma->vm_private_data = ctx;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	return io_remap_pfn_range(vma, vma->vm_start, pfn, PAGE_SIZE,
41562306a36Sopenharmony_ci			vma->vm_page_prot);
41662306a36Sopenharmony_ci}
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_cistatic __poll_t idxd_cdev_poll(struct file *filp,
41962306a36Sopenharmony_ci			       struct poll_table_struct *wait)
42062306a36Sopenharmony_ci{
42162306a36Sopenharmony_ci	struct idxd_user_context *ctx = filp->private_data;
42262306a36Sopenharmony_ci	struct idxd_wq *wq = ctx->wq;
42362306a36Sopenharmony_ci	struct idxd_device *idxd = wq->idxd;
42462306a36Sopenharmony_ci	__poll_t out = 0;
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	poll_wait(filp, &wq->err_queue, wait);
42762306a36Sopenharmony_ci	spin_lock(&idxd->dev_lock);
42862306a36Sopenharmony_ci	if (idxd->sw_err.valid)
42962306a36Sopenharmony_ci		out = EPOLLIN | EPOLLRDNORM;
43062306a36Sopenharmony_ci	spin_unlock(&idxd->dev_lock);
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	return out;
43362306a36Sopenharmony_ci}
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_cistatic const struct file_operations idxd_cdev_fops = {
43662306a36Sopenharmony_ci	.owner = THIS_MODULE,
43762306a36Sopenharmony_ci	.open = idxd_cdev_open,
43862306a36Sopenharmony_ci	.release = idxd_cdev_release,
43962306a36Sopenharmony_ci	.mmap = idxd_cdev_mmap,
44062306a36Sopenharmony_ci	.poll = idxd_cdev_poll,
44162306a36Sopenharmony_ci};
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ciint idxd_cdev_get_major(struct idxd_device *idxd)
44462306a36Sopenharmony_ci{
44562306a36Sopenharmony_ci	return MAJOR(ictx[idxd->data->type].devt);
44662306a36Sopenharmony_ci}
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ciint idxd_wq_add_cdev(struct idxd_wq *wq)
44962306a36Sopenharmony_ci{
45062306a36Sopenharmony_ci	struct idxd_device *idxd = wq->idxd;
45162306a36Sopenharmony_ci	struct idxd_cdev *idxd_cdev;
45262306a36Sopenharmony_ci	struct cdev *cdev;
45362306a36Sopenharmony_ci	struct device *dev;
45462306a36Sopenharmony_ci	struct idxd_cdev_context *cdev_ctx;
45562306a36Sopenharmony_ci	int rc, minor;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	idxd_cdev = kzalloc(sizeof(*idxd_cdev), GFP_KERNEL);
45862306a36Sopenharmony_ci	if (!idxd_cdev)
45962306a36Sopenharmony_ci		return -ENOMEM;
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	idxd_cdev->idxd_dev.type = IDXD_DEV_CDEV;
46262306a36Sopenharmony_ci	idxd_cdev->wq = wq;
46362306a36Sopenharmony_ci	cdev = &idxd_cdev->cdev;
46462306a36Sopenharmony_ci	dev = cdev_dev(idxd_cdev);
46562306a36Sopenharmony_ci	cdev_ctx = &ictx[wq->idxd->data->type];
46662306a36Sopenharmony_ci	minor = ida_simple_get(&cdev_ctx->minor_ida, 0, MINORMASK, GFP_KERNEL);
46762306a36Sopenharmony_ci	if (minor < 0) {
46862306a36Sopenharmony_ci		kfree(idxd_cdev);
46962306a36Sopenharmony_ci		return minor;
47062306a36Sopenharmony_ci	}
47162306a36Sopenharmony_ci	idxd_cdev->minor = minor;
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	device_initialize(dev);
47462306a36Sopenharmony_ci	dev->parent = wq_confdev(wq);
47562306a36Sopenharmony_ci	dev->bus = &dsa_bus_type;
47662306a36Sopenharmony_ci	dev->type = &idxd_cdev_device_type;
47762306a36Sopenharmony_ci	dev->devt = MKDEV(MAJOR(cdev_ctx->devt), minor);
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	rc = dev_set_name(dev, "%s/wq%u.%u", idxd->data->name_prefix, idxd->id, wq->id);
48062306a36Sopenharmony_ci	if (rc < 0)
48162306a36Sopenharmony_ci		goto err;
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	wq->idxd_cdev = idxd_cdev;
48462306a36Sopenharmony_ci	cdev_init(cdev, &idxd_cdev_fops);
48562306a36Sopenharmony_ci	rc = cdev_device_add(cdev, dev);
48662306a36Sopenharmony_ci	if (rc) {
48762306a36Sopenharmony_ci		dev_dbg(&wq->idxd->pdev->dev, "cdev_add failed: %d\n", rc);
48862306a36Sopenharmony_ci		goto err;
48962306a36Sopenharmony_ci	}
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	return 0;
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci err:
49462306a36Sopenharmony_ci	put_device(dev);
49562306a36Sopenharmony_ci	wq->idxd_cdev = NULL;
49662306a36Sopenharmony_ci	return rc;
49762306a36Sopenharmony_ci}
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_civoid idxd_wq_del_cdev(struct idxd_wq *wq)
50062306a36Sopenharmony_ci{
50162306a36Sopenharmony_ci	struct idxd_cdev *idxd_cdev;
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	idxd_cdev = wq->idxd_cdev;
50462306a36Sopenharmony_ci	ida_destroy(&file_ida);
50562306a36Sopenharmony_ci	wq->idxd_cdev = NULL;
50662306a36Sopenharmony_ci	cdev_device_del(&idxd_cdev->cdev, cdev_dev(idxd_cdev));
50762306a36Sopenharmony_ci	put_device(cdev_dev(idxd_cdev));
50862306a36Sopenharmony_ci}
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_cistatic int idxd_user_drv_probe(struct idxd_dev *idxd_dev)
51162306a36Sopenharmony_ci{
51262306a36Sopenharmony_ci	struct idxd_wq *wq = idxd_dev_to_wq(idxd_dev);
51362306a36Sopenharmony_ci	struct idxd_device *idxd = wq->idxd;
51462306a36Sopenharmony_ci	int rc;
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci	if (idxd->state != IDXD_DEV_ENABLED)
51762306a36Sopenharmony_ci		return -ENXIO;
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	/*
52062306a36Sopenharmony_ci	 * User type WQ is enabled only when SVA is enabled for two reasons:
52162306a36Sopenharmony_ci	 *   - If no IOMMU or IOMMU Passthrough without SVA, userspace
52262306a36Sopenharmony_ci	 *     can directly access physical address through the WQ.
52362306a36Sopenharmony_ci	 *   - The IDXD cdev driver does not provide any ways to pin
52462306a36Sopenharmony_ci	 *     user pages and translate the address from user VA to IOVA or
52562306a36Sopenharmony_ci	 *     PA without IOMMU SVA. Therefore the application has no way
52662306a36Sopenharmony_ci	 *     to instruct the device to perform DMA function. This makes
52762306a36Sopenharmony_ci	 *     the cdev not usable for normal application usage.
52862306a36Sopenharmony_ci	 */
52962306a36Sopenharmony_ci	if (!device_user_pasid_enabled(idxd)) {
53062306a36Sopenharmony_ci		idxd->cmd_status = IDXD_SCMD_WQ_USER_NO_IOMMU;
53162306a36Sopenharmony_ci		dev_dbg(&idxd->pdev->dev,
53262306a36Sopenharmony_ci			"User type WQ cannot be enabled without SVA.\n");
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci		return -EOPNOTSUPP;
53562306a36Sopenharmony_ci	}
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci	mutex_lock(&wq->wq_lock);
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	wq->wq = create_workqueue(dev_name(wq_confdev(wq)));
54062306a36Sopenharmony_ci	if (!wq->wq) {
54162306a36Sopenharmony_ci		rc = -ENOMEM;
54262306a36Sopenharmony_ci		goto wq_err;
54362306a36Sopenharmony_ci	}
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	wq->type = IDXD_WQT_USER;
54662306a36Sopenharmony_ci	rc = drv_enable_wq(wq);
54762306a36Sopenharmony_ci	if (rc < 0)
54862306a36Sopenharmony_ci		goto err;
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	rc = idxd_wq_add_cdev(wq);
55162306a36Sopenharmony_ci	if (rc < 0) {
55262306a36Sopenharmony_ci		idxd->cmd_status = IDXD_SCMD_CDEV_ERR;
55362306a36Sopenharmony_ci		goto err_cdev;
55462306a36Sopenharmony_ci	}
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	idxd->cmd_status = 0;
55762306a36Sopenharmony_ci	mutex_unlock(&wq->wq_lock);
55862306a36Sopenharmony_ci	return 0;
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_cierr_cdev:
56162306a36Sopenharmony_ci	drv_disable_wq(wq);
56262306a36Sopenharmony_cierr:
56362306a36Sopenharmony_ci	destroy_workqueue(wq->wq);
56462306a36Sopenharmony_ci	wq->type = IDXD_WQT_NONE;
56562306a36Sopenharmony_ciwq_err:
56662306a36Sopenharmony_ci	mutex_unlock(&wq->wq_lock);
56762306a36Sopenharmony_ci	return rc;
56862306a36Sopenharmony_ci}
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_cistatic void idxd_user_drv_remove(struct idxd_dev *idxd_dev)
57162306a36Sopenharmony_ci{
57262306a36Sopenharmony_ci	struct idxd_wq *wq = idxd_dev_to_wq(idxd_dev);
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	mutex_lock(&wq->wq_lock);
57562306a36Sopenharmony_ci	idxd_wq_del_cdev(wq);
57662306a36Sopenharmony_ci	drv_disable_wq(wq);
57762306a36Sopenharmony_ci	wq->type = IDXD_WQT_NONE;
57862306a36Sopenharmony_ci	destroy_workqueue(wq->wq);
57962306a36Sopenharmony_ci	wq->wq = NULL;
58062306a36Sopenharmony_ci	mutex_unlock(&wq->wq_lock);
58162306a36Sopenharmony_ci}
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_cistatic enum idxd_dev_type dev_types[] = {
58462306a36Sopenharmony_ci	IDXD_DEV_WQ,
58562306a36Sopenharmony_ci	IDXD_DEV_NONE,
58662306a36Sopenharmony_ci};
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_cistruct idxd_device_driver idxd_user_drv = {
58962306a36Sopenharmony_ci	.probe = idxd_user_drv_probe,
59062306a36Sopenharmony_ci	.remove = idxd_user_drv_remove,
59162306a36Sopenharmony_ci	.name = "user",
59262306a36Sopenharmony_ci	.type = dev_types,
59362306a36Sopenharmony_ci};
59462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(idxd_user_drv);
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ciint idxd_cdev_register(void)
59762306a36Sopenharmony_ci{
59862306a36Sopenharmony_ci	int rc, i;
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	for (i = 0; i < IDXD_TYPE_MAX; i++) {
60162306a36Sopenharmony_ci		ida_init(&ictx[i].minor_ida);
60262306a36Sopenharmony_ci		rc = alloc_chrdev_region(&ictx[i].devt, 0, MINORMASK,
60362306a36Sopenharmony_ci					 ictx[i].name);
60462306a36Sopenharmony_ci		if (rc)
60562306a36Sopenharmony_ci			goto err_free_chrdev_region;
60662306a36Sopenharmony_ci	}
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	return 0;
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_cierr_free_chrdev_region:
61162306a36Sopenharmony_ci	for (i--; i >= 0; i--)
61262306a36Sopenharmony_ci		unregister_chrdev_region(ictx[i].devt, MINORMASK);
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	return rc;
61562306a36Sopenharmony_ci}
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_civoid idxd_cdev_remove(void)
61862306a36Sopenharmony_ci{
61962306a36Sopenharmony_ci	int i;
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	for (i = 0; i < IDXD_TYPE_MAX; i++) {
62262306a36Sopenharmony_ci		unregister_chrdev_region(ictx[i].devt, MINORMASK);
62362306a36Sopenharmony_ci		ida_destroy(&ictx[i].minor_ida);
62462306a36Sopenharmony_ci	}
62562306a36Sopenharmony_ci}
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ci/**
62862306a36Sopenharmony_ci * idxd_copy_cr - copy completion record to user address space found by wq and
62962306a36Sopenharmony_ci *		  PASID
63062306a36Sopenharmony_ci * @wq:		work queue
63162306a36Sopenharmony_ci * @pasid:	PASID
63262306a36Sopenharmony_ci * @addr:	user fault address to write
63362306a36Sopenharmony_ci * @cr:		completion record
63462306a36Sopenharmony_ci * @len:	number of bytes to copy
63562306a36Sopenharmony_ci *
63662306a36Sopenharmony_ci * This is called by a work that handles completion record fault.
63762306a36Sopenharmony_ci *
63862306a36Sopenharmony_ci * Return: number of bytes copied.
63962306a36Sopenharmony_ci */
64062306a36Sopenharmony_ciint idxd_copy_cr(struct idxd_wq *wq, ioasid_t pasid, unsigned long addr,
64162306a36Sopenharmony_ci		 void *cr, int len)
64262306a36Sopenharmony_ci{
64362306a36Sopenharmony_ci	struct device *dev = &wq->idxd->pdev->dev;
64462306a36Sopenharmony_ci	int left = len, status_size = 1;
64562306a36Sopenharmony_ci	struct idxd_user_context *ctx;
64662306a36Sopenharmony_ci	struct mm_struct *mm;
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	mutex_lock(&wq->uc_lock);
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ci	ctx = xa_load(&wq->upasid_xa, pasid);
65162306a36Sopenharmony_ci	if (!ctx) {
65262306a36Sopenharmony_ci		dev_warn(dev, "No user context\n");
65362306a36Sopenharmony_ci		goto out;
65462306a36Sopenharmony_ci	}
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	mm = ctx->mm;
65762306a36Sopenharmony_ci	/*
65862306a36Sopenharmony_ci	 * The completion record fault handling work is running in kernel
65962306a36Sopenharmony_ci	 * thread context. It temporarily switches to the mm to copy cr
66062306a36Sopenharmony_ci	 * to addr in the mm.
66162306a36Sopenharmony_ci	 */
66262306a36Sopenharmony_ci	kthread_use_mm(mm);
66362306a36Sopenharmony_ci	left = copy_to_user((void __user *)addr + status_size, cr + status_size,
66462306a36Sopenharmony_ci			    len - status_size);
66562306a36Sopenharmony_ci	/*
66662306a36Sopenharmony_ci	 * Copy status only after the rest of completion record is copied
66762306a36Sopenharmony_ci	 * successfully so that the user gets the complete completion record
66862306a36Sopenharmony_ci	 * when a non-zero status is polled.
66962306a36Sopenharmony_ci	 */
67062306a36Sopenharmony_ci	if (!left) {
67162306a36Sopenharmony_ci		u8 status;
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci		/*
67462306a36Sopenharmony_ci		 * Ensure that the completion record's status field is written
67562306a36Sopenharmony_ci		 * after the rest of the completion record has been written.
67662306a36Sopenharmony_ci		 * This ensures that the user receives the correct completion
67762306a36Sopenharmony_ci		 * record information once polling for a non-zero status.
67862306a36Sopenharmony_ci		 */
67962306a36Sopenharmony_ci		wmb();
68062306a36Sopenharmony_ci		status = *(u8 *)cr;
68162306a36Sopenharmony_ci		if (put_user(status, (u8 __user *)addr))
68262306a36Sopenharmony_ci			left += status_size;
68362306a36Sopenharmony_ci	} else {
68462306a36Sopenharmony_ci		left += status_size;
68562306a36Sopenharmony_ci	}
68662306a36Sopenharmony_ci	kthread_unuse_mm(mm);
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ciout:
68962306a36Sopenharmony_ci	mutex_unlock(&wq->uc_lock);
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	return len - left;
69262306a36Sopenharmony_ci}
693