linux-stable/drivers/misc/ocxl/file.c
Linus Torvalds 694a20dae6 powerpc fixes for 4.16 #2
A larger batch of fixes than we'd like. Roughly 1/3 fixes for new code, 1/3
 fixes for stable and 1/3 minor things.
 
 There's four commits fixing bugs when using 16GB huge pages on hash, caused by
 some of the preparatory changes for pkeys.
 
 Two fixes for bugs in the enhanced IRQ soft masking for local_t, one of which
 broke KVM in some circumstances.
 
 Four fixes for Power9. The most bizarre being a bug where futexes stopped
 working because a NULL pointer dereference didn't trap during early boot (it
 aliased the kernel mapping). A fix for memory hotplug when using the Radix MMU,
 and a fix for live migration of guests using the Radix MMU.
 
 Two fixes for hotplug on pseries machines. One where we weren't correctly
 updating NUMA info when CPUs are added and removed. And the other fixes
 crashes/hangs seen when doing memory hot remove during boot, which is apparently
 a thing people do.
 
 Finally a handful of build fixes for obscure configs and other minor fixes.
 
 Thanks to:
   Alexey Kardashevskiy, Aneesh Kumar K.V, Balbir Singh, Colin Ian King, Daniel
   Henrique Barboza, Florian Weimer, Guenter Roeck, Harish, Laurent Vivier,
   Madhavan Srinivasan, Mauricio Faria de Oliveira, Nathan Fontenot, Nicholas
   Piggin, Sam Bobroff.
 -----BEGIN PGP SIGNATURE-----
 
 iQIwBAABCAAaBQJahDTmExxtcGVAZWxsZXJtYW4uaWQuYXUACgkQUevqPMjhpYAd
 chAAtVe8hmkEJefTbU63GBeqva0JHSiTu2DENZAlN/epWtbtyl05PLETMdTcwGCv
 nK2zzR+xbSFN1DzZK8KQfDBW33McKZE+YkHwYOC8Kff/N0SKdHK4zvxYr7FTZGzG
 9uSG5vrxVEsPLT/yANabl0d0vKWMsJ1jZquvJAU0eLNUbA/skGjEPADtXqYQUXiA
 EnW4xeczsMLjuzTleoRqrBx74Gulovuq9LVAjfDvkydWlCU9MQkrodCgP0V2hQtw
 RAJ/QLY+NS/vMCBnvVOGBaKzIqrfeQTHF3P0j4pyBeBq/2kNuidM5n25uoc31wUq
 DE4Ebe2FJA6CHP5KEyf7dr9y7gsks/ak3/CKs+l6Yz3/0BqenEMhu6WKJ1tgf9cC
 qAmi1dIjtpw6JZ6baCbkloUdAGNjKVfLWB9ld9VIfg0C+C3y4L7+TKJukxrCBGI6
 hopfT/3p8xUdla3euiRXRLZzajyKDGrqk71hk5J/J0ChXfWB0B51X0F6NIfH41Mn
 YsVUQ95p3zS79Pl942ijGScFX/bNVLfEEGzlI/nwU/wbTxF5g/XNXm5PjBsGSr/W
 zFcCwCpFV2b/kypQoxQA5CbrKRCLOleDA/lLOxW/1NMYOQsNj05DM9wYAw5Bl+lX
 AVj2c5jM9heNN4scxDiufRNfqZbyjZ4fFUpXLNqs7N5vcks=
 =BmuL
 -----END PGP SIGNATURE-----

Merge tag 'powerpc-4.16-2' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux

Pull powerpc fixes from Michael Ellerman:
 "A larger batch of fixes than we'd like. Roughly 1/3 fixes for new
  code, 1/3 fixes for stable and 1/3 minor things.

  There's four commits fixing bugs when using 16GB huge pages on hash,
  caused by some of the preparatory changes for pkeys.

  Two fixes for bugs in the enhanced IRQ soft masking for local_t, one
  of which broke KVM in some circumstances.

  Four fixes for Power9. The most bizarre being a bug where futexes
  stopped working because a NULL pointer dereference didn't trap during
  early boot (it aliased the kernel mapping). A fix for memory hotplug
  when using the Radix MMU, and a fix for live migration of guests using
  the Radix MMU.

  Two fixes for hotplug on pseries machines. One where we weren't
  correctly updating NUMA info when CPUs are added and removed. And the
  other fixes crashes/hangs seen when doing memory hot remove during
  boot, which is apparently a thing people do.

  Finally a handful of build fixes for obscure configs and other minor
  fixes.

  Thanks to: Alexey Kardashevskiy, Aneesh Kumar K.V, Balbir Singh, Colin
  Ian King, Daniel Henrique Barboza, Florian Weimer, Guenter Roeck,
  Harish, Laurent Vivier, Madhavan Srinivasan, Mauricio Faria de
  Oliveira, Nathan Fontenot, Nicholas Piggin, Sam Bobroff"

* tag 'powerpc-4.16-2' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux:
  selftests/powerpc: Fix to use ucontext_t instead of struct ucontext
  powerpc/kdump: Fix powernv build break when KEXEC_CORE=n
  powerpc/pseries: Fix build break for SPLPAR=n and CPU hotplug
  powerpc/mm/hash64: Zero PGD pages on allocation
  powerpc/mm/hash64: Store the slot information at the right offset for hugetlb
  powerpc/mm/hash64: Allocate larger PMD table if hugetlb config is enabled
  powerpc/mm: Fix crashes with 16G huge pages
  powerpc/mm: Flush radix process translations when setting MMU type
  powerpc/vas: Don't set uses_vas for kernel windows
  powerpc/pseries: Enable RAS hotplug events later
  powerpc/mm/radix: Split linear mapping on hot-unplug
  powerpc/64s/radix: Boot-time NULL pointer protection using a guard-PID
  ocxl: fix signed comparison with less than zero
  powerpc/64s: Fix may_hard_irq_enable() for PMI soft masking
  powerpc/64s: Fix MASKABLE_RELON_EXCEPTION_HV_OOL macro
  powerpc/numa: Invalidate numa_cpu_lookup_table on cpu remove
2018-02-14 10:06:41 -08:00

432 lines
9.3 KiB
C

// SPDX-License-Identifier: GPL-2.0+
// Copyright 2017 IBM Corp.
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/sched/signal.h>
#include <linux/uaccess.h>
#include <uapi/misc/ocxl.h>
#include "ocxl_internal.h"
#define OCXL_NUM_MINORS 256 /* Total to reserve */
static dev_t ocxl_dev;
static struct class *ocxl_class;
static struct mutex minors_idr_lock;
static struct idr minors_idr;
static struct ocxl_afu *find_and_get_afu(dev_t devno)
{
struct ocxl_afu *afu;
int afu_minor;
afu_minor = MINOR(devno);
/*
* We don't declare an RCU critical section here, as our AFU
* is protected by a reference counter on the device. By the time the
* minor number of a device is removed from the idr, the ref count of
* the device is already at 0, so no user API will access that AFU and
* this function can't return it.
*/
afu = idr_find(&minors_idr, afu_minor);
if (afu)
ocxl_afu_get(afu);
return afu;
}
static int allocate_afu_minor(struct ocxl_afu *afu)
{
int minor;
mutex_lock(&minors_idr_lock);
minor = idr_alloc(&minors_idr, afu, 0, OCXL_NUM_MINORS, GFP_KERNEL);
mutex_unlock(&minors_idr_lock);
return minor;
}
static void free_afu_minor(struct ocxl_afu *afu)
{
mutex_lock(&minors_idr_lock);
idr_remove(&minors_idr, MINOR(afu->dev.devt));
mutex_unlock(&minors_idr_lock);
}
static int afu_open(struct inode *inode, struct file *file)
{
struct ocxl_afu *afu;
struct ocxl_context *ctx;
int rc;
pr_debug("%s for device %x\n", __func__, inode->i_rdev);
afu = find_and_get_afu(inode->i_rdev);
if (!afu)
return -ENODEV;
ctx = ocxl_context_alloc();
if (!ctx) {
rc = -ENOMEM;
goto put_afu;
}
rc = ocxl_context_init(ctx, afu, inode->i_mapping);
if (rc)
goto put_afu;
file->private_data = ctx;
ocxl_afu_put(afu);
return 0;
put_afu:
ocxl_afu_put(afu);
return rc;
}
static long afu_ioctl_attach(struct ocxl_context *ctx,
struct ocxl_ioctl_attach __user *uarg)
{
struct ocxl_ioctl_attach arg;
u64 amr = 0;
int rc;
pr_debug("%s for context %d\n", __func__, ctx->pasid);
if (copy_from_user(&arg, uarg, sizeof(arg)))
return -EFAULT;
/* Make sure reserved fields are not set for forward compatibility */
if (arg.reserved1 || arg.reserved2 || arg.reserved3)
return -EINVAL;
amr = arg.amr & mfspr(SPRN_UAMOR);
rc = ocxl_context_attach(ctx, amr);
return rc;
}
#define CMD_STR(x) (x == OCXL_IOCTL_ATTACH ? "ATTACH" : \
x == OCXL_IOCTL_IRQ_ALLOC ? "IRQ_ALLOC" : \
x == OCXL_IOCTL_IRQ_FREE ? "IRQ_FREE" : \
x == OCXL_IOCTL_IRQ_SET_FD ? "IRQ_SET_FD" : \
"UNKNOWN")
static long afu_ioctl(struct file *file, unsigned int cmd,
unsigned long args)
{
struct ocxl_context *ctx = file->private_data;
struct ocxl_ioctl_irq_fd irq_fd;
u64 irq_offset;
long rc;
pr_debug("%s for context %d, command %s\n", __func__, ctx->pasid,
CMD_STR(cmd));
if (ctx->status == CLOSED)
return -EIO;
switch (cmd) {
case OCXL_IOCTL_ATTACH:
rc = afu_ioctl_attach(ctx,
(struct ocxl_ioctl_attach __user *) args);
break;
case OCXL_IOCTL_IRQ_ALLOC:
rc = ocxl_afu_irq_alloc(ctx, &irq_offset);
if (!rc) {
rc = copy_to_user((u64 __user *) args, &irq_offset,
sizeof(irq_offset));
if (rc)
ocxl_afu_irq_free(ctx, irq_offset);
}
break;
case OCXL_IOCTL_IRQ_FREE:
rc = copy_from_user(&irq_offset, (u64 __user *) args,
sizeof(irq_offset));
if (rc)
return -EFAULT;
rc = ocxl_afu_irq_free(ctx, irq_offset);
break;
case OCXL_IOCTL_IRQ_SET_FD:
rc = copy_from_user(&irq_fd, (u64 __user *) args,
sizeof(irq_fd));
if (rc)
return -EFAULT;
if (irq_fd.reserved)
return -EINVAL;
rc = ocxl_afu_irq_set_fd(ctx, irq_fd.irq_offset,
irq_fd.eventfd);
break;
default:
rc = -EINVAL;
}
return rc;
}
static long afu_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long args)
{
return afu_ioctl(file, cmd, args);
}
static int afu_mmap(struct file *file, struct vm_area_struct *vma)
{
struct ocxl_context *ctx = file->private_data;
pr_debug("%s for context %d\n", __func__, ctx->pasid);
return ocxl_context_mmap(ctx, vma);
}
static bool has_xsl_error(struct ocxl_context *ctx)
{
bool ret;
mutex_lock(&ctx->xsl_error_lock);
ret = !!ctx->xsl_error.addr;
mutex_unlock(&ctx->xsl_error_lock);
return ret;
}
/*
* Are there any events pending on the AFU
* ctx: The AFU context
* Returns: true if there are events pending
*/
static bool afu_events_pending(struct ocxl_context *ctx)
{
if (has_xsl_error(ctx))
return true;
return false;
}
static unsigned int afu_poll(struct file *file, struct poll_table_struct *wait)
{
struct ocxl_context *ctx = file->private_data;
unsigned int mask = 0;
bool closed;
pr_debug("%s for context %d\n", __func__, ctx->pasid);
poll_wait(file, &ctx->events_wq, wait);
mutex_lock(&ctx->status_mutex);
closed = (ctx->status == CLOSED);
mutex_unlock(&ctx->status_mutex);
if (afu_events_pending(ctx))
mask = EPOLLIN | EPOLLRDNORM;
else if (closed)
mask = EPOLLERR;
return mask;
}
/*
* Populate the supplied buffer with a single XSL error
* ctx: The AFU context to report the error from
* header: the event header to populate
* buf: The buffer to write the body into (should be at least
* AFU_EVENT_BODY_XSL_ERROR_SIZE)
* Return: the amount of buffer that was populated
*/
static ssize_t append_xsl_error(struct ocxl_context *ctx,
struct ocxl_kernel_event_header *header,
char __user *buf)
{
struct ocxl_kernel_event_xsl_fault_error body;
memset(&body, 0, sizeof(body));
mutex_lock(&ctx->xsl_error_lock);
if (!ctx->xsl_error.addr) {
mutex_unlock(&ctx->xsl_error_lock);
return 0;
}
body.addr = ctx->xsl_error.addr;
body.dsisr = ctx->xsl_error.dsisr;
body.count = ctx->xsl_error.count;
ctx->xsl_error.addr = 0;
ctx->xsl_error.dsisr = 0;
ctx->xsl_error.count = 0;
mutex_unlock(&ctx->xsl_error_lock);
header->type = OCXL_AFU_EVENT_XSL_FAULT_ERROR;
if (copy_to_user(buf, &body, sizeof(body)))
return -EFAULT;
return sizeof(body);
}
#define AFU_EVENT_BODY_MAX_SIZE sizeof(struct ocxl_kernel_event_xsl_fault_error)
/*
* Reports events on the AFU
* Format:
* Header (struct ocxl_kernel_event_header)
* Body (struct ocxl_kernel_event_*)
* Header...
*/
static ssize_t afu_read(struct file *file, char __user *buf, size_t count,
loff_t *off)
{
struct ocxl_context *ctx = file->private_data;
struct ocxl_kernel_event_header header;
ssize_t rc;
ssize_t used = 0;
DEFINE_WAIT(event_wait);
memset(&header, 0, sizeof(header));
/* Require offset to be 0 */
if (*off != 0)
return -EINVAL;
if (count < (sizeof(struct ocxl_kernel_event_header) +
AFU_EVENT_BODY_MAX_SIZE))
return -EINVAL;
for (;;) {
prepare_to_wait(&ctx->events_wq, &event_wait,
TASK_INTERRUPTIBLE);
if (afu_events_pending(ctx))
break;
if (ctx->status == CLOSED)
break;
if (file->f_flags & O_NONBLOCK) {
finish_wait(&ctx->events_wq, &event_wait);
return -EAGAIN;
}
if (signal_pending(current)) {
finish_wait(&ctx->events_wq, &event_wait);
return -ERESTARTSYS;
}
schedule();
}
finish_wait(&ctx->events_wq, &event_wait);
if (has_xsl_error(ctx)) {
used = append_xsl_error(ctx, &header, buf + sizeof(header));
if (used < 0)
return used;
}
if (!afu_events_pending(ctx))
header.flags |= OCXL_KERNEL_EVENT_FLAG_LAST;
if (copy_to_user(buf, &header, sizeof(header)))
return -EFAULT;
used += sizeof(header);
rc = (ssize_t) used;
return rc;
}
static int afu_release(struct inode *inode, struct file *file)
{
struct ocxl_context *ctx = file->private_data;
int rc;
pr_debug("%s for device %x\n", __func__, inode->i_rdev);
rc = ocxl_context_detach(ctx);
mutex_lock(&ctx->mapping_lock);
ctx->mapping = NULL;
mutex_unlock(&ctx->mapping_lock);
wake_up_all(&ctx->events_wq);
if (rc != -EBUSY)
ocxl_context_free(ctx);
return 0;
}
static const struct file_operations ocxl_afu_fops = {
.owner = THIS_MODULE,
.open = afu_open,
.unlocked_ioctl = afu_ioctl,
.compat_ioctl = afu_compat_ioctl,
.mmap = afu_mmap,
.poll = afu_poll,
.read = afu_read,
.release = afu_release,
};
int ocxl_create_cdev(struct ocxl_afu *afu)
{
int rc;
cdev_init(&afu->cdev, &ocxl_afu_fops);
rc = cdev_add(&afu->cdev, afu->dev.devt, 1);
if (rc) {
dev_err(&afu->dev, "Unable to add afu char device: %d\n", rc);
return rc;
}
return 0;
}
void ocxl_destroy_cdev(struct ocxl_afu *afu)
{
cdev_del(&afu->cdev);
}
int ocxl_register_afu(struct ocxl_afu *afu)
{
int minor;
minor = allocate_afu_minor(afu);
if (minor < 0)
return minor;
afu->dev.devt = MKDEV(MAJOR(ocxl_dev), minor);
afu->dev.class = ocxl_class;
return device_register(&afu->dev);
}
void ocxl_unregister_afu(struct ocxl_afu *afu)
{
free_afu_minor(afu);
}
static char *ocxl_devnode(struct device *dev, umode_t *mode)
{
return kasprintf(GFP_KERNEL, "ocxl/%s", dev_name(dev));
}
int ocxl_file_init(void)
{
int rc;
mutex_init(&minors_idr_lock);
idr_init(&minors_idr);
rc = alloc_chrdev_region(&ocxl_dev, 0, OCXL_NUM_MINORS, "ocxl");
if (rc) {
pr_err("Unable to allocate ocxl major number: %d\n", rc);
return rc;
}
ocxl_class = class_create(THIS_MODULE, "ocxl");
if (IS_ERR(ocxl_class)) {
pr_err("Unable to create ocxl class\n");
unregister_chrdev_region(ocxl_dev, OCXL_NUM_MINORS);
return PTR_ERR(ocxl_class);
}
ocxl_class->devnode = ocxl_devnode;
return 0;
}
void ocxl_file_exit(void)
{
class_destroy(ocxl_class);
unregister_chrdev_region(ocxl_dev, OCXL_NUM_MINORS);
idr_destroy(&minors_idr);
}