// SPDX-License-Identifier: GPL-2.0
/*
* Channel path related status regions for vfio_ccw
*
* Copyright IBM Corp. 2020
*
* Author(s): Farhan Ali <[email protected]>
* Eric Farman <[email protected]>
*/
#include <linux/slab.h>
#include <linux/vfio.h>
#include "vfio_ccw_private.h"
static ssize_t vfio_ccw_schib_region_read(struct vfio_ccw_private *private,
char __user *buf, size_t count,
loff_t *ppos)
{
struct subchannel *sch = to_subchannel(private->vdev.dev->parent);
unsigned int i = VFIO_CCW_OFFSET_TO_INDEX(*ppos) - VFIO_CCW_NUM_REGIONS;
loff_t pos = *ppos & VFIO_CCW_OFFSET_MASK;
struct ccw_schib_region *region;
int ret;
if (pos + count > sizeof(*region))
return -EINVAL;
mutex_lock(&private->io_mutex);
region = private->region[i].data;
if (cio_update_schib(sch)) {
ret = -ENODEV;
goto out;
}
memcpy(region, &sch->schib, sizeof(*region));
if (copy_to_user(buf, (void *)region + pos, count)) {
ret = -EFAULT;
goto out;
}
ret = count;
out:
mutex_unlock(&private->io_mutex);
return ret;
}
static ssize_t vfio_ccw_schib_region_write(struct vfio_ccw_private *private,
const char __user *buf, size_t count,
loff_t *ppos)
{
return -EINVAL;
}
static void vfio_ccw_schib_region_release(struct vfio_ccw_private *private,
struct vfio_ccw_region *region)
{
}
static const struct vfio_ccw_regops vfio_ccw_schib_region_ops = {
.read = vfio_ccw_schib_region_read,
.write = vfio_ccw_schib_region_write,
.release = vfio_ccw_schib_region_release,
};
int vfio_ccw_register_schib_dev_regions(struct vfio_ccw_private *private)
{
return vfio_ccw_register_dev_region(private,
VFIO_REGION_SUBTYPE_CCW_SCHIB,
&vfio_ccw_schib_region_ops,
sizeof(struct ccw_schib_region),
VFIO_REGION_INFO_FLAG_READ,
private->schib_region);
}
static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
char __user *buf, size_t count,
loff_t *ppos)
{
unsigned int i = VFIO_CCW_OFFSET_TO_INDEX(*ppos) - VFIO_CCW_NUM_REGIONS;
loff_t pos = *ppos & VFIO_CCW_OFFSET_MASK;
struct ccw_crw_region *region;
struct vfio_ccw_crw *crw;
int ret;
if (pos + count > sizeof(*region))
return -EINVAL;
crw = list_first_entry_or_null(&private->crw,
struct vfio_ccw_crw, next);
if (crw)
list_del(&crw->next);
mutex_lock(&private->io_mutex);
region = private->region[i].data;
if (crw)
memcpy(®ion->crw, &crw->crw, sizeof(region->crw));
if (copy_to_user(buf, (void *)region + pos, count))
ret = -EFAULT;
else
ret = count;
region->crw = 0;
mutex_unlock(&private->io_mutex);
kfree(crw);
/* Notify the guest if more CRWs are on our queue */
if (!list_empty(&private->crw) && private->crw_trigger)
eventfd_signal(private->crw_trigger);
return ret;
}
static ssize_t vfio_ccw_crw_region_write(struct vfio_ccw_private *private,
const char __user *buf, size_t count,
loff_t *ppos)
{
return -EINVAL;
}
static void vfio_ccw_crw_region_release(struct vfio_ccw_private *private,
struct vfio_ccw_region *region)
{
}
static const struct vfio_ccw_regops vfio_ccw_crw_region_ops = {
.read = vfio_ccw_crw_region_read,
.write = vfio_ccw_crw_region_write,
.release = vfio_ccw_crw_region_release,
};
int vfio_ccw_register_crw_dev_regions(struct vfio_ccw_private *private)
{
return vfio_ccw_register_dev_region(private,
VFIO_REGION_SUBTYPE_CCW_CRW,
&vfio_ccw_crw_region_ops,
sizeof(struct ccw_crw_region),
VFIO_REGION_INFO_FLAG_READ,
private->crw_region);
}