/*
* Copyright (c) 2016 Citrix Systems Inc.
* Copyright (c) 2019 Arm ltd.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; If not, see .
*/
#include
#include
#include
#include
#include
static int dm_op(const struct dmop_args *op_args)
{
struct domain *d;
struct xen_dm_op op;
long rc;
bool const_op = true;
const size_t offset = offsetof(struct xen_dm_op, u);
static const uint8_t op_size[] = {
[XEN_DMOP_create_ioreq_server] = sizeof(struct xen_dm_op_create_ioreq_server),
[XEN_DMOP_get_ioreq_server_info] = sizeof(struct xen_dm_op_get_ioreq_server_info),
[XEN_DMOP_map_io_range_to_ioreq_server] = sizeof(struct xen_dm_op_ioreq_server_range),
[XEN_DMOP_unmap_io_range_from_ioreq_server] = sizeof(struct xen_dm_op_ioreq_server_range),
[XEN_DMOP_set_ioreq_server_state] = sizeof(struct xen_dm_op_set_ioreq_server_state),
[XEN_DMOP_destroy_ioreq_server] = sizeof(struct xen_dm_op_destroy_ioreq_server),
[XEN_DMOP_track_dirty_vram] = sizeof(struct xen_dm_op_track_dirty_vram),
[XEN_DMOP_set_pci_intx_level] = sizeof(struct xen_dm_op_set_pci_intx_level),
[XEN_DMOP_set_isa_irq_level] = sizeof(struct xen_dm_op_set_isa_irq_level),
[XEN_DMOP_set_pci_link_route] = sizeof(struct xen_dm_op_set_pci_link_route),
[XEN_DMOP_modified_memory] = sizeof(struct xen_dm_op_modified_memory),
[XEN_DMOP_set_mem_type] = sizeof(struct xen_dm_op_set_mem_type),
[XEN_DMOP_inject_event] = sizeof(struct xen_dm_op_inject_event),
[XEN_DMOP_inject_msi] = sizeof(struct xen_dm_op_inject_msi),
[XEN_DMOP_map_mem_type_to_ioreq_server] = sizeof(struct xen_dm_op_map_mem_type_to_ioreq_server),
[XEN_DMOP_remote_shutdown] = sizeof(struct xen_dm_op_remote_shutdown),
[XEN_DMOP_relocate_memory] = sizeof(struct xen_dm_op_relocate_memory),
[XEN_DMOP_pin_memory_cacheattr] = sizeof(struct xen_dm_op_pin_memory_cacheattr),
};
rc = rcu_lock_remote_domain_by_id(op_args->domid, &d);
if ( rc )
return rc;
if ( !is_hvm_domain(d) )
goto out;
rc = xsm_dm_op(XSM_DM_PRIV, d);
if ( rc )
goto out;
rc = -EFAULT;
if ( op_args->buf[0].size < offset )
goto out;
if ( copy_from_guest_offset((void *)&op, op_args->buf[0].h, 0, offset) )
goto out;
if ( op.op >= ARRAY_SIZE(op_size) )
{
rc = -EOPNOTSUPP;
goto out;
}
op.op = array_index_nospec(op.op, ARRAY_SIZE(op_size));
if ( op_args->buf[0].size < offset + op_size[op.op] )
goto out;
if ( copy_from_guest_offset((void *)&op.u, op_args->buf[0].h, offset,
op_size[op.op]) )
goto out;
rc = -EINVAL;
if ( op.pad )
goto out;
switch ( op.op )
{
case XEN_DMOP_create_ioreq_server:
{
struct xen_dm_op_create_ioreq_server *data =
&op.u.create_ioreq_server;
const_op = false;
rc = -EINVAL;
if ( data->pad[0] || data->pad[1] || data->pad[2] )
break;
rc = create_ioreq_server(d, data->handle_bufioreq,
&data->id);
break;
}
case XEN_DMOP_get_ioreq_server_info:
{
struct xen_dm_op_get_ioreq_server_info *data =
&op.u.get_ioreq_server_info;
const uint16_t valid_flags = XEN_DMOP_no_gfns;
const_op = false;
rc = -EINVAL;
if ( data->flags & ~valid_flags )
break;
rc = get_ioreq_server_info(d, data->id,
(data->flags & XEN_DMOP_no_gfns) ?
NULL : (unsigned long *)&data->ioreq_gfn,
(data->flags & XEN_DMOP_no_gfns) ?
NULL : (unsigned long *)&data->bufioreq_gfn,
&data->bufioreq_port);
break;
}
case XEN_DMOP_map_io_range_to_ioreq_server:
{
const struct xen_dm_op_ioreq_server_range *data =
&op.u.map_io_range_to_ioreq_server;
rc = -EINVAL;
if ( data->pad )
break;
rc = map_io_range_to_ioreq_server(d, data->id, data->type,
data->start, data->end);
break;
}
case XEN_DMOP_unmap_io_range_from_ioreq_server:
{
const struct xen_dm_op_ioreq_server_range *data =
&op.u.unmap_io_range_from_ioreq_server;
rc = -EINVAL;
if ( data->pad )
break;
rc = unmap_io_range_from_ioreq_server(d, data->id, data->type,
data->start, data->end);
break;
}
case XEN_DMOP_set_ioreq_server_state:
{
const struct xen_dm_op_set_ioreq_server_state *data =
&op.u.set_ioreq_server_state;
rc = -EINVAL;
if ( data->pad )
break;
rc = set_ioreq_server_state(d, data->id, !!data->enabled);
break;
}
case XEN_DMOP_destroy_ioreq_server:
{
const struct xen_dm_op_destroy_ioreq_server *data =
&op.u.destroy_ioreq_server;
rc = -EINVAL;
if ( data->pad )
break;
rc = destroy_ioreq_server(d, data->id);
break;
}
default:
rc = arch_dm_op(&op, d, op_args, &const_op);
}
if ( (!rc || rc == -ERESTART) &&
!const_op && copy_to_guest_offset(op_args->buf[0].h, offset,
(void *)&op.u, op_size[op.op]) )
rc = -EFAULT;
out:
rcu_unlock_domain(d);
return rc;
}
#ifdef CONFIG_COMPAT
#include
CHECK_dm_op_create_ioreq_server;
CHECK_dm_op_get_ioreq_server_info;
CHECK_dm_op_ioreq_server_range;
CHECK_dm_op_set_ioreq_server_state;
CHECK_dm_op_destroy_ioreq_server;
CHECK_dm_op_track_dirty_vram;
CHECK_dm_op_set_pci_intx_level;
CHECK_dm_op_set_isa_irq_level;
CHECK_dm_op_set_pci_link_route;
CHECK_dm_op_modified_memory;
CHECK_dm_op_set_mem_type;
CHECK_dm_op_inject_event;
CHECK_dm_op_inject_msi;
CHECK_dm_op_map_mem_type_to_ioreq_server;
CHECK_dm_op_remote_shutdown;
CHECK_dm_op_relocate_memory;
CHECK_dm_op_pin_memory_cacheattr;
int compat_dm_op(domid_t domid,
unsigned int nr_bufs,
XEN_GUEST_HANDLE_PARAM(void) bufs)
{
struct dmop_args args;
unsigned int i;
int rc;
if ( nr_bufs > ARRAY_SIZE(args.buf) )
return -E2BIG;
args.domid = domid;
args.nr_bufs = array_index_nospec(nr_bufs, ARRAY_SIZE(args.buf) + 1);
for ( i = 0; i < args.nr_bufs; i++ )
{
struct compat_dm_op_buf cmp;
if ( copy_from_guest_offset(&cmp, bufs, i, 1) )
return -EFAULT;
#define XLAT_dm_op_buf_HNDL_h(_d_, _s_) \
guest_from_compat_handle((_d_)->h, (_s_)->h)
XLAT_dm_op_buf(&args.buf[i], &cmp);
#undef XLAT_dm_op_buf_HNDL_h
}
rc = dm_op(&args);
if ( rc == -ERESTART )
rc = hypercall_create_continuation(__HYPERVISOR_dm_op, "iih",
domid, nr_bufs, bufs);
return rc;
}
#endif
long do_dm_op(domid_t domid,
unsigned int nr_bufs,
XEN_GUEST_HANDLE_PARAM(xen_dm_op_buf_t) bufs)
{
struct dmop_args args;
int rc;
if ( nr_bufs > ARRAY_SIZE(args.buf) )
return -E2BIG;
args.domid = domid;
args.nr_bufs = array_index_nospec(nr_bufs, ARRAY_SIZE(args.buf) + 1);
if ( copy_from_guest_offset(&args.buf[0], bufs, 0, args.nr_bufs) )
return -EFAULT;
rc = dm_op(&args);
if ( rc == -ERESTART )
rc = hypercall_create_continuation(__HYPERVISOR_dm_op, "iih",
domid, nr_bufs, bufs);
return rc;
}
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/