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