/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (c) 2016 Citrix Systems Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include static bool _raw_copy_from_guest_buf_offset(void *dst, const struct dmop_args *args, unsigned int buf_idx, size_t offset_bytes, size_t dst_bytes) { size_t buf_bytes; if ( buf_idx >= args->nr_bufs ) return false; buf_bytes = args->buf[buf_idx].size; if ( (offset_bytes + dst_bytes) < offset_bytes || (offset_bytes + dst_bytes) > buf_bytes ) return false; return !copy_from_guest_offset(dst, args->buf[buf_idx].h, offset_bytes, dst_bytes); } #define COPY_FROM_GUEST_BUF_OFFSET(dst, bufs, buf_idx, offset_bytes) \ _raw_copy_from_guest_buf_offset(&(dst), bufs, buf_idx, offset_bytes, \ sizeof(dst)) static int track_dirty_vram(struct domain *d, xen_pfn_t first_pfn, unsigned int nr_frames, const struct xen_dm_op_buf *buf) { if ( nr_frames > (GB(1) >> PAGE_SHIFT) ) return -EINVAL; if ( d->is_dying ) return -ESRCH; if ( !d->max_vcpus || !d->vcpu[0] ) return -EINVAL; if ( DIV_ROUND_UP(nr_frames, BITS_PER_BYTE) > buf->size ) return -EINVAL; return shadow_mode_enabled(d) ? shadow_track_dirty_vram(d, first_pfn, nr_frames, buf->h) : hap_track_dirty_vram(d, first_pfn, nr_frames, buf->h); } static int set_pci_intx_level(struct domain *d, uint16_t domain, uint8_t bus, uint8_t device, uint8_t intx, uint8_t level) { if ( domain != 0 || bus != 0 || device > 0x1f || intx > 3 ) return -EINVAL; switch ( level ) { case 0: hvm_pci_intx_deassert(d, device, intx); break; case 1: hvm_pci_intx_assert(d, device, intx); break; default: return -EINVAL; } return 0; } static int set_isa_irq_level(struct domain *d, uint8_t isa_irq, uint8_t level) { if ( isa_irq > 15 ) return -EINVAL; switch ( level ) { case 0: hvm_isa_irq_deassert(d, isa_irq); break; case 1: hvm_isa_irq_assert(d, isa_irq, NULL); break; default: return -EINVAL; } return 0; } static int modified_memory(struct domain *d, const struct dmop_args *bufs, struct xen_dm_op_modified_memory *header) { #define EXTENTS_BUFFER 1 /* Process maximum of 256 pfns before checking for continuation. */ const unsigned int cont_check_interval = 0x100; unsigned int *rem_extents = &header->nr_extents; unsigned int batch_rem_pfns = cont_check_interval; /* Used for continuation. */ unsigned int *pfns_done = &header->opaque; if ( !paging_mode_log_dirty(d) ) return 0; if ( (bufs->buf[EXTENTS_BUFFER].size / sizeof(struct xen_dm_op_modified_memory_extent)) < *rem_extents ) return -EINVAL; while ( *rem_extents > 0 ) { struct xen_dm_op_modified_memory_extent extent; unsigned int batch_nr; xen_pfn_t pfn, end_pfn; if ( !COPY_FROM_GUEST_BUF_OFFSET(extent, bufs, EXTENTS_BUFFER, (*rem_extents - 1) * sizeof(extent)) ) return -EFAULT; if ( extent.pad ) return -EINVAL; end_pfn = extent.first_pfn + extent.nr; if ( end_pfn <= extent.first_pfn || end_pfn > domain_get_maximum_gpfn(d) ) return -EINVAL; if ( *pfns_done >= extent.nr ) return -EINVAL; pfn = extent.first_pfn + *pfns_done; batch_nr = extent.nr - *pfns_done; if ( batch_nr > batch_rem_pfns ) { batch_nr = batch_rem_pfns; *pfns_done += batch_nr; end_pfn = pfn + batch_nr; } else { (*rem_extents)--; *pfns_done = 0; } batch_rem_pfns -= batch_nr; for ( ; pfn < end_pfn; pfn++ ) { struct page_info *page; page = get_page_from_gfn(d, pfn, NULL, P2M_UNSHARE); if ( page ) { paging_mark_pfn_dirty(d, _pfn(pfn)); /* * These are most probably not page tables any more * don't take a long time and don't die either. */ sh_remove_shadows(d, page_to_mfn(page), 1, 0); put_page(page); } } /* * After a full batch of cont_check_interval pfns * have been processed, and there are still extents * remaining to process, check for continuation. */ if ( (batch_rem_pfns == 0) && (*rem_extents > 0) ) { if ( hypercall_preempt_check() ) return -ERESTART; batch_rem_pfns = cont_check_interval; } } return 0; #undef EXTENTS_BUFFER } static bool allow_p2m_type_change(p2m_type_t old, p2m_type_t new) { if ( new == p2m_ioreq_server ) return old == p2m_ram_rw; if ( old == p2m_ioreq_server ) return new == p2m_ram_rw; return p2m_is_ram(old) || (p2m_is_hole(old) && new == p2m_mmio_dm); } static int set_mem_type(struct domain *d, struct xen_dm_op_set_mem_type *data) { xen_pfn_t last_pfn = data->first_pfn + data->nr - 1; unsigned int iter = 0, mem_type; int rc = 0; /* Interface types to internal p2m types */ static const p2m_type_t memtype[] = { [HVMMEM_ram_rw] = p2m_ram_rw, [HVMMEM_ram_ro] = p2m_ram_ro, [HVMMEM_mmio_dm] = p2m_mmio_dm, [HVMMEM_unused] = p2m_invalid, [HVMMEM_ioreq_server] = p2m_ioreq_server, }; if ( (data->first_pfn > last_pfn) || (last_pfn > domain_get_maximum_gpfn(d)) ) return -EINVAL; if ( data->mem_type >= ARRAY_SIZE(memtype) || unlikely(data->mem_type == HVMMEM_unused) ) return -EINVAL; mem_type = array_index_nospec(data->mem_type, ARRAY_SIZE(memtype)); if ( mem_type == HVMMEM_ioreq_server ) { unsigned int flags; if ( !hap_enabled(d) ) return -EOPNOTSUPP; /* Do not change to HVMMEM_ioreq_server if no ioreq server mapped. */ if ( !p2m_get_ioreq_server(d, &flags) ) return -EINVAL; } while ( iter < data->nr ) { unsigned long pfn = data->first_pfn + iter; p2m_type_t t; get_gfn_unshare(d, pfn, &t); if ( p2m_is_paging(t) ) { put_gfn(d, pfn); p2m_mem_paging_populate(d, _gfn(pfn)); return -EAGAIN; } if ( p2m_is_shared(t) ) rc = -EAGAIN; else if ( !allow_p2m_type_change(t, memtype[mem_type]) ) rc = -EINVAL; else rc = p2m_change_type_one(d, pfn, t, memtype[mem_type]); put_gfn(d, pfn); if ( rc ) break; iter++; /* * Check for continuation every 256th iteration and if the * iteration is not the last. */ if ( (iter < data->nr) && ((iter & 0xff) == 0) && hypercall_preempt_check() ) { data->first_pfn += iter; data->nr -= iter; rc = -ERESTART; break; } } return rc; } static int inject_event(struct domain *d, const struct xen_dm_op_inject_event *data) { struct vcpu *v; if ( data->vcpuid >= d->max_vcpus || !(v = d->vcpu[data->vcpuid]) ) return -EINVAL; if ( cmpxchg(&v->arch.hvm.inject_event.vector, HVM_EVENT_VECTOR_UNSET, HVM_EVENT_VECTOR_UPDATING) != HVM_EVENT_VECTOR_UNSET ) return -EBUSY; v->arch.hvm.inject_event.type = data->type; v->arch.hvm.inject_event.insn_len = data->insn_len; v->arch.hvm.inject_event.error_code = data->error_code; v->arch.hvm.inject_event.cr2 = data->cr2; smp_wmb(); v->arch.hvm.inject_event.vector = data->vector; return 0; } int dm_op(const struct dmop_args *op_args) { struct domain *d; struct xen_dm_op op; bool const_op = true; long rc; size_t offset; 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), [XEN_DMOP_nr_vcpus] = sizeof(struct xen_dm_op_nr_vcpus), }; rc = rcu_lock_remote_domain_by_id(op_args->domid, &d); if ( rc ) return rc; rc = -EINVAL; if ( !is_hvm_domain(d) ) goto out; rc = xsm_dm_op(XSM_DM_PRIV, d); if ( rc ) goto out; offset = offsetof(struct xen_dm_op, u); 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_map_mem_type_to_ioreq_server: { struct xen_dm_op_map_mem_type_to_ioreq_server *data = &op.u.map_mem_type_to_ioreq_server; unsigned long first_gfn = data->opaque; const_op = false; rc = -EOPNOTSUPP; if ( !hap_enabled(d) ) break; if ( first_gfn == 0 ) rc = ioreq_server_map_mem_type(d, data->id, data->type, data->flags); else rc = 0; /* * Iterate p2m table when an ioreq server unmaps from p2m_ioreq_server, * and reset the remaining p2m_ioreq_server entries back to p2m_ram_rw. */ if ( rc == 0 && data->flags == 0 ) { struct p2m_domain *p2m = p2m_get_hostp2m(d); while ( read_atomic(&p2m->ioreq.entry_count) && first_gfn <= p2m->max_mapped_pfn ) { /* Iterate p2m table for 256 gfns each time. */ rc = p2m_finish_type_change(d, _gfn(first_gfn), 256); if ( rc < 0 ) break; first_gfn += 256; /* Check for continuation if it's not the last iteration. */ if ( first_gfn <= p2m->max_mapped_pfn && hypercall_preempt_check() ) { rc = -ERESTART; data->opaque = first_gfn; break; } } } break; } case XEN_DMOP_track_dirty_vram: { const struct xen_dm_op_track_dirty_vram *data = &op.u.track_dirty_vram; rc = -EINVAL; if ( data->pad ) break; if ( op_args->nr_bufs < 2 ) break; rc = track_dirty_vram(d, data->first_pfn, data->nr, &op_args->buf[1]); break; } case XEN_DMOP_set_pci_intx_level: { const struct xen_dm_op_set_pci_intx_level *data = &op.u.set_pci_intx_level; rc = set_pci_intx_level(d, data->domain, data->bus, data->device, data->intx, data->level); break; } case XEN_DMOP_set_isa_irq_level: { const struct xen_dm_op_set_isa_irq_level *data = &op.u.set_isa_irq_level; rc = set_isa_irq_level(d, data->isa_irq, data->level); break; } case XEN_DMOP_set_pci_link_route: { const struct xen_dm_op_set_pci_link_route *data = &op.u.set_pci_link_route; rc = hvm_set_pci_link_route(d, data->link, data->isa_irq); break; } case XEN_DMOP_modified_memory: { struct xen_dm_op_modified_memory *data = &op.u.modified_memory; rc = modified_memory(d, op_args, data); const_op = !rc; break; } case XEN_DMOP_set_mem_type: { struct xen_dm_op_set_mem_type *data = &op.u.set_mem_type; const_op = false; rc = -EINVAL; if ( data->pad ) break; rc = set_mem_type(d, data); break; } case XEN_DMOP_inject_event: { const struct xen_dm_op_inject_event *data = &op.u.inject_event; rc = -EINVAL; if ( data->pad0 || data->pad1 ) break; rc = inject_event(d, data); break; } case XEN_DMOP_inject_msi: { const struct xen_dm_op_inject_msi *data = &op.u.inject_msi; rc = -EINVAL; if ( data->pad ) break; rc = hvm_inject_msi(d, data->addr, data->data); break; } case XEN_DMOP_remote_shutdown: { const struct xen_dm_op_remote_shutdown *data = &op.u.remote_shutdown; domain_shutdown(d, data->reason); rc = 0; break; } case XEN_DMOP_relocate_memory: { struct xen_dm_op_relocate_memory *data = &op.u.relocate_memory; struct xen_add_to_physmap xatp = { .domid = op_args->domid, .size = data->size, .space = XENMAPSPACE_gmfn_range, .idx = data->src_gfn, .gpfn = data->dst_gfn, }; if ( data->pad ) { rc = -EINVAL; break; } rc = xenmem_add_to_physmap(d, &xatp, 0); if ( rc == 0 && data->size != xatp.size ) rc = xatp.size; if ( rc > 0 ) { data->size -= rc; data->src_gfn += rc; data->dst_gfn += rc; const_op = false; rc = -ERESTART; } break; } case XEN_DMOP_pin_memory_cacheattr: { const struct xen_dm_op_pin_memory_cacheattr *data = &op.u.pin_memory_cacheattr; if ( data->pad ) { rc = -EINVAL; break; } rc = hvm_set_mem_pinned_cacheattr(d, data->start, data->end, data->type); break; } case XEN_DMOP_nr_vcpus: { struct xen_dm_op_nr_vcpus *data = &op.u.nr_vcpus; data->vcpus = d->max_vcpus; const_op = false; rc = 0; break; } default: rc = ioreq_server_dm_op(&op, d, &const_op); break; } 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; } #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; CHECK_dm_op_nr_vcpus; 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; } /* * Local variables: * mode: C * c-file-style: "BSD" * c-basic-offset: 4 * tab-width: 4 * indent-tabs-mode: nil * End: */