/******************************************************************************
* arch/x86/pv/shim.c
*
* Functionaltiy for PV Shim mode
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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 .
*
* Copyright (c) 2017 Citrix Systems Ltd.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef CONFIG_PV_SHIM_EXCLUSIVE
bool pv_shim;
boolean_param("pv-shim", pv_shim);
#endif
static struct domain *guest;
static unsigned int nr_grant_list;
static unsigned long *grant_frames;
static DEFINE_SPINLOCK(grant_lock);
static PAGE_LIST_HEAD(balloon);
static DEFINE_SPINLOCK(balloon_lock);
static long pv_shim_event_channel_op(int cmd, XEN_GUEST_HANDLE_PARAM(void) arg);
static long pv_shim_grant_table_op(unsigned int cmd,
XEN_GUEST_HANDLE_PARAM(void) uop,
unsigned int count);
/*
* By default give the shim 1MB of free memory slack. Some users may wish to
* tune this constants for better memory utilization. This can be achieved
* using the following xen-shim's command line option:
*
* shim_mem=[min:,][max:,][]
*
* : The minimum amount of memory that should be allocated for xen-shim
* (ignored if greater than max)
* : The maximum amount of memory that should be allocated for xen-shim
* : The precise amount of memory to allocate for xen-shim
* (overrides both min and max)
*/
static uint64_t __initdata shim_nrpages;
static uint64_t __initdata shim_min_nrpages;
static uint64_t __initdata shim_max_nrpages;
static int __init parse_shim_mem(const char *s)
{
do {
if ( !strncmp(s, "min:", 4) )
shim_min_nrpages = parse_size_and_unit(s+4, &s) >> PAGE_SHIFT;
else if ( !strncmp(s, "max:", 4) )
shim_max_nrpages = parse_size_and_unit(s+4, &s) >> PAGE_SHIFT;
else
shim_nrpages = parse_size_and_unit(s, &s) >> PAGE_SHIFT;
} while ( *s++ == ',' );
return s[-1] ? -EINVAL : 0;
}
custom_param("shim_mem", parse_shim_mem);
uint64_t pv_shim_mem(uint64_t avail)
{
if ( !shim_nrpages )
{
shim_nrpages = max(shim_min_nrpages,
total_pages - avail + (1UL << (20 - PAGE_SHIFT)));
if ( shim_max_nrpages )
shim_max_nrpages = min(shim_nrpages, shim_max_nrpages);
}
if ( total_pages - avail > shim_nrpages )
panic("pages used by shim > shim_nrpages (%#lx > %#lx)",
total_pages - avail, shim_nrpages);
shim_nrpages -= total_pages - avail;
printk("shim used pages %#lx reserving %#lx free pages\n",
total_pages - avail, shim_nrpages);
return shim_nrpages;
}
#define L1_PROT (_PAGE_PRESENT|_PAGE_RW|_PAGE_ACCESSED|_PAGE_USER| \
_PAGE_GUEST_KERNEL)
#define COMPAT_L1_PROT (_PAGE_PRESENT|_PAGE_RW|_PAGE_ACCESSED)
static void __init replace_va_mapping(struct domain *d, l4_pgentry_t *l4start,
unsigned long va, unsigned long mfn)
{
struct page_info *page;
l4_pgentry_t *pl4e;
l3_pgentry_t *pl3e;
l2_pgentry_t *pl2e;
l1_pgentry_t *pl1e;
pl4e = l4start + l4_table_offset(va);
pl3e = l4e_to_l3e(*pl4e);
pl3e += l3_table_offset(va);
pl2e = l3e_to_l2e(*pl3e);
pl2e += l2_table_offset(va);
pl1e = l2e_to_l1e(*pl2e);
pl1e += l1_table_offset(va);
page = mfn_to_page(l1e_get_pfn(*pl1e));
put_page_and_type(page);
*pl1e = l1e_from_pfn(mfn, (!is_pv_32bit_domain(d) ? L1_PROT
: COMPAT_L1_PROT));
}
static void evtchn_reserve(struct domain *d, unsigned int port)
{
ASSERT(port_is_valid(d, port));
evtchn_from_port(d, port)->state = ECS_RESERVED;
BUG_ON(xen_hypercall_evtchn_unmask(port));
}
static bool evtchn_handled(struct domain *d, unsigned int port)
{
ASSERT(port_is_valid(d, port));
/* The shim manages VIRQs, the rest is forwarded to L0. */
return evtchn_from_port(d, port)->state == ECS_VIRQ;
}
static void evtchn_assign_vcpu(struct domain *d, unsigned int port,
unsigned int vcpu)
{
ASSERT(port_is_valid(d, port));
evtchn_from_port(d, port)->notify_vcpu_id = vcpu;
}
void __init pv_shim_setup_dom(struct domain *d, l4_pgentry_t *l4start,
unsigned long va_start, unsigned long store_va,
unsigned long console_va, unsigned long vphysmap,
start_info_t *si)
{
uint64_t param = 0;
long rc;
#define SET_AND_MAP_PARAM(p, si, va) ({ \
rc = xen_hypercall_hvm_get_param(p, ¶m); \
if ( rc ) \
panic("Unable to get " #p "\n"); \
(si) = param; \
if ( va ) \
{ \
share_xen_page_with_guest(mfn_to_page(param), d, XENSHARE_writable); \
replace_va_mapping(d, l4start, va, param); \
dom0_update_physmap(d, PFN_DOWN((va) - va_start), param, vphysmap); \
} \
else \
{ \
BUG_ON(evtchn_allocate_port(d, param)); \
evtchn_reserve(d, param); \
} \
})
SET_AND_MAP_PARAM(HVM_PARAM_STORE_PFN, si->store_mfn, store_va);
SET_AND_MAP_PARAM(HVM_PARAM_STORE_EVTCHN, si->store_evtchn, 0);
SET_AND_MAP_PARAM(HVM_PARAM_CONSOLE_EVTCHN, si->console.domU.evtchn, 0);
if ( !pv_console )
SET_AND_MAP_PARAM(HVM_PARAM_CONSOLE_PFN, si->console.domU.mfn,
console_va);
#undef SET_AND_MAP_PARAM
else
{
/* Allocate a new page for DomU's PV console */
void *page = alloc_xenheap_pages(0, MEMF_bits(32));
uint64_t console_mfn;
ASSERT(page);
clear_page(page);
console_mfn = virt_to_mfn(page);
si->console.domU.mfn = console_mfn;
share_xen_page_with_guest(mfn_to_page(console_mfn), d,
XENSHARE_writable);
replace_va_mapping(d, l4start, console_va, console_mfn);
dom0_update_physmap(d, (console_va - va_start) >> PAGE_SHIFT,
console_mfn, vphysmap);
consoled_set_ring_addr(page);
}
pv_hypercall_table_replace(__HYPERVISOR_event_channel_op,
(hypercall_fn_t *)pv_shim_event_channel_op,
(hypercall_fn_t *)pv_shim_event_channel_op);
pv_hypercall_table_replace(__HYPERVISOR_grant_table_op,
(hypercall_fn_t *)pv_shim_grant_table_op,
(hypercall_fn_t *)pv_shim_grant_table_op);
guest = d;
/*
* Set the max pages to the current number of pages to prevent the
* guest from depleting the shim memory pool.
*/
d->max_pages = d->tot_pages;
}
static void write_start_info(struct domain *d)
{
struct cpu_user_regs *regs = guest_cpu_user_regs();
start_info_t *si = map_domain_page(_mfn(is_pv_32bit_domain(d) ? regs->edx
: regs->rdx));
uint64_t param;
snprintf(si->magic, sizeof(si->magic), "xen-3.0-x86_%s",
is_pv_32bit_domain(d) ? "32p" : "64");
si->nr_pages = d->tot_pages;
si->shared_info = virt_to_maddr(d->shared_info);
si->flags = 0;
BUG_ON(xen_hypercall_hvm_get_param(HVM_PARAM_STORE_PFN, &si->store_mfn));
BUG_ON(xen_hypercall_hvm_get_param(HVM_PARAM_STORE_EVTCHN, ¶m));
si->store_evtchn = param;
BUG_ON(xen_hypercall_hvm_get_param(HVM_PARAM_CONSOLE_EVTCHN, ¶m));
si->console.domU.evtchn = param;
if ( pv_console )
si->console.domU.mfn = virt_to_mfn(consoled_get_ring_addr());
else if ( xen_hypercall_hvm_get_param(HVM_PARAM_CONSOLE_PFN,
&si->console.domU.mfn) )
BUG();
if ( is_pv_32bit_domain(d) )
xlat_start_info(si, XLAT_start_info_console_domU);
unmap_domain_page(si);
}
int pv_shim_shutdown(uint8_t reason)
{
struct domain *d = current->domain;
struct vcpu *v;
unsigned int i;
uint64_t old_store_pfn, old_console_pfn = 0, store_pfn, console_pfn;
uint64_t store_evtchn, console_evtchn;
long rc;
if ( reason != SHUTDOWN_suspend )
/* Forward to L0. */
return xen_hypercall_shutdown(reason);
BUG_ON(current->vcpu_id != 0);
BUG_ON(xen_hypercall_hvm_get_param(HVM_PARAM_STORE_PFN, &old_store_pfn));
if ( !pv_console )
BUG_ON(xen_hypercall_hvm_get_param(HVM_PARAM_CONSOLE_PFN,
&old_console_pfn));
/* Pause the other vcpus before starting the migration. */
for_each_vcpu(d, v)
if ( v != current )
vcpu_pause_by_systemcontroller(v);
rc = xen_hypercall_shutdown(SHUTDOWN_suspend);
if ( rc )
{
for_each_vcpu(d, v)
if ( v != current )
vcpu_unpause_by_systemcontroller(v);
return rc;
}
/* Resume the shim itself first. */
hypervisor_resume();
/*
* ATM there's nothing Xen can do if the console/store pfn changes,
* because Xen won't have a page_info struct for it.
*/
BUG_ON(xen_hypercall_hvm_get_param(HVM_PARAM_STORE_PFN, &store_pfn));
BUG_ON(old_store_pfn != store_pfn);
if ( !pv_console )
{
BUG_ON(xen_hypercall_hvm_get_param(HVM_PARAM_CONSOLE_PFN,
&console_pfn));
BUG_ON(old_console_pfn != console_pfn);
}
/* Update domain id. */
d->domain_id = get_initial_domain_id();
/* Clean the iomem range. */
BUG_ON(iomem_deny_access(d, 0, ~0UL));
/* Clean grant frames. */
xfree(grant_frames);
grant_frames = NULL;
nr_grant_list = 0;
/* Clean event channels. */
for ( i = 0; i < EVTCHN_2L_NR_CHANNELS; i++ )
{
if ( !port_is_valid(d, i) )
continue;
if ( evtchn_handled(d, i) )
evtchn_close(d, i, false);
else
evtchn_free(d, evtchn_from_port(d, i));
}
/* Reserve store/console event channel. */
BUG_ON(xen_hypercall_hvm_get_param(HVM_PARAM_STORE_EVTCHN, &store_evtchn));
BUG_ON(evtchn_allocate_port(d, store_evtchn));
evtchn_reserve(d, store_evtchn);
BUG_ON(xen_hypercall_hvm_get_param(HVM_PARAM_CONSOLE_EVTCHN,
&console_evtchn));
BUG_ON(evtchn_allocate_port(d, console_evtchn));
evtchn_reserve(d, console_evtchn);
/* Clean watchdogs. */
watchdog_domain_destroy(d);
watchdog_domain_init(d);
/* Clean the PIRQ EOI page. */
if ( d->arch.pirq_eoi_map != NULL )
{
unmap_domain_page_global(d->arch.pirq_eoi_map);
put_page_and_type(mfn_to_page(d->arch.pirq_eoi_map_mfn));
d->arch.pirq_eoi_map = NULL;
d->arch.pirq_eoi_map_mfn = 0;
d->arch.auto_unmask = 0;
}
/*
* NB: there's no need to fixup the p2m, since the mfns assigned
* to the PV guest have not changed at all. Just re-write the
* start_info fields with the appropriate value.
*/
write_start_info(d);
for_each_vcpu(d, v)
{
/* Unmap guest vcpu_info pages. */
unmap_vcpu_info(v);
/* Reset the periodic timer to the default value. */
v->periodic_period = MILLISECS(10);
/* Stop the singleshot timer. */
stop_timer(&v->singleshot_timer);
if ( test_bit(_VPF_down, &v->pause_flags) )
BUG_ON(vcpu_reset(v));
if ( v != current )
vcpu_unpause_by_systemcontroller(v);
else
vcpu_force_reschedule(v);
}
return 0;
}
static long pv_shim_event_channel_op(int cmd, XEN_GUEST_HANDLE_PARAM(void) arg)
{
struct domain *d = current->domain;
struct evtchn_close close;
long rc;
switch ( cmd )
{
#define EVTCHN_FORWARD(cmd, port_field) \
case EVTCHNOP_##cmd: { \
struct evtchn_##cmd op; \
\
if ( copy_from_guest(&op, arg, 1) != 0 ) \
return -EFAULT; \
\
rc = xen_hypercall_event_channel_op(EVTCHNOP_##cmd, &op); \
if ( rc ) \
break; \
\
spin_lock(&d->event_lock); \
rc = evtchn_allocate_port(d, op.port_field); \
if ( rc ) \
{ \
close.port = op.port_field; \
BUG_ON(xen_hypercall_event_channel_op(EVTCHNOP_close, &close)); \
} \
else \
evtchn_reserve(d, op.port_field); \
spin_unlock(&d->event_lock); \
\
if ( !rc && __copy_to_guest(arg, &op, 1) ) \
rc = -EFAULT; \
\
break; \
}
EVTCHN_FORWARD(alloc_unbound, port)
EVTCHN_FORWARD(bind_interdomain, local_port)
#undef EVTCHN_FORWARD
case EVTCHNOP_bind_virq: {
struct evtchn_bind_virq virq;
struct evtchn_alloc_unbound alloc = {
.dom = DOMID_SELF,
.remote_dom = DOMID_SELF,
};
if ( copy_from_guest(&virq, arg, 1) != 0 )
return -EFAULT;
/*
* The event channel space is actually controlled by L0 Xen, so
* allocate a port from L0 and then force the VIRQ to be bound to that
* specific port.
*
* This is only required for VIRQ because the rest of the event channel
* operations are handled directly by L0.
*/
rc = xen_hypercall_event_channel_op(EVTCHNOP_alloc_unbound, &alloc);
if ( rc )
break;
/* Force L1 to use the event channel port allocated on L0. */
rc = evtchn_bind_virq(&virq, alloc.port);
if ( rc )
{
close.port = alloc.port;
BUG_ON(xen_hypercall_event_channel_op(EVTCHNOP_close, &close));
}
if ( !rc && __copy_to_guest(arg, &virq, 1) )
rc = -EFAULT;
break;
}
case EVTCHNOP_status: {
struct evtchn_status status;
if ( copy_from_guest(&status, arg, 1) != 0 )
return -EFAULT;
/*
* NB: if the event channel is not handled by the shim, just forward
* the status request to L0, even if the port is not valid.
*/
if ( port_is_valid(d, status.port) && evtchn_handled(d, status.port) )
rc = evtchn_status(&status);
else
rc = xen_hypercall_event_channel_op(EVTCHNOP_status, &status);
break;
}
case EVTCHNOP_bind_vcpu: {
struct evtchn_bind_vcpu vcpu;
if ( copy_from_guest(&vcpu, arg, 1) != 0 )
return -EFAULT;
if ( !port_is_valid(d, vcpu.port) )
return -EINVAL;
if ( evtchn_handled(d, vcpu.port) )
rc = evtchn_bind_vcpu(vcpu.port, vcpu.vcpu);
else
{
rc = xen_hypercall_event_channel_op(EVTCHNOP_bind_vcpu, &vcpu);
if ( !rc )
evtchn_assign_vcpu(d, vcpu.port, vcpu.vcpu);
}
break;
}
case EVTCHNOP_close: {
if ( copy_from_guest(&close, arg, 1) != 0 )
return -EFAULT;
if ( !port_is_valid(d, close.port) )
return -EINVAL;
set_bit(close.port, XEN_shared_info->evtchn_mask);
if ( evtchn_handled(d, close.port) )
{
rc = evtchn_close(d, close.port, true);
if ( rc )
break;
}
else
evtchn_free(d, evtchn_from_port(d, close.port));
rc = xen_hypercall_event_channel_op(EVTCHNOP_close, &close);
if ( rc )
/*
* If the port cannot be closed on the L0 mark it as reserved
* in the shim to avoid re-using it.
*/
evtchn_reserve(d, close.port);
break;
}
case EVTCHNOP_bind_ipi: {
struct evtchn_bind_ipi ipi;
if ( copy_from_guest(&ipi, arg, 1) != 0 )
return -EFAULT;
rc = xen_hypercall_event_channel_op(EVTCHNOP_bind_ipi, &ipi);
if ( rc )
break;
spin_lock(&d->event_lock);
rc = evtchn_allocate_port(d, ipi.port);
if ( rc )
{
spin_unlock(&d->event_lock);
close.port = ipi.port;
BUG_ON(xen_hypercall_event_channel_op(EVTCHNOP_close, &close));
break;
}
evtchn_assign_vcpu(d, ipi.port, ipi.vcpu);
evtchn_reserve(d, ipi.port);
spin_unlock(&d->event_lock);
if ( __copy_to_guest(arg, &ipi, 1) )
rc = -EFAULT;
break;
}
case EVTCHNOP_unmask: {
struct evtchn_unmask unmask;
if ( copy_from_guest(&unmask, arg, 1) != 0 )
return -EFAULT;
/* Unmask is handled in L1 */
rc = evtchn_unmask(unmask.port);
break;
}
case EVTCHNOP_send: {
struct evtchn_send send;
if ( copy_from_guest(&send, arg, 1) != 0 )
return -EFAULT;
if ( pv_console && send.port == pv_console_evtchn() )
{
consoled_guest_rx();
rc = 0;
}
else
rc = xen_hypercall_event_channel_op(EVTCHNOP_send, &send);
break;
}
case EVTCHNOP_reset: {
struct evtchn_reset reset;
if ( copy_from_guest(&reset, arg, 1) != 0 )
return -EFAULT;
rc = xen_hypercall_event_channel_op(EVTCHNOP_reset, &reset);
break;
}
default:
/* No FIFO or PIRQ support for now */
rc = -EOPNOTSUPP;
break;
}
return rc;
}
void pv_shim_inject_evtchn(unsigned int port)
{
if ( port_is_valid(guest, port) )
{
struct evtchn *chn = evtchn_from_port(guest, port);
evtchn_port_set_pending(guest, chn->notify_vcpu_id, chn);
}
}
static long pv_shim_grant_table_op(unsigned int cmd,
XEN_GUEST_HANDLE_PARAM(void) uop,
unsigned int count)
{
struct domain *d = current->domain;
long rc = 0;
if ( count != 1 )
return -EINVAL;
switch ( cmd )
{
case GNTTABOP_setup_table:
{
bool compat = is_pv_32bit_domain(d);
struct gnttab_setup_table nat;
struct compat_gnttab_setup_table cmp;
unsigned int i;
if ( unlikely(compat ? copy_from_guest(&cmp, uop, 1)
: copy_from_guest(&nat, uop, 1)) ||
unlikely(compat ? !compat_handle_okay(cmp.frame_list,
cmp.nr_frames)
: !guest_handle_okay(nat.frame_list,
nat.nr_frames)) )
{
rc = -EFAULT;
break;
}
if ( compat )
#define XLAT_gnttab_setup_table_HNDL_frame_list(d, s)
XLAT_gnttab_setup_table(&nat, &cmp);
#undef XLAT_gnttab_setup_table_HNDL_frame_list
nat.status = GNTST_okay;
spin_lock(&grant_lock);
if ( !nr_grant_list )
{
struct gnttab_query_size query_size = {
.dom = DOMID_SELF,
};
rc = xen_hypercall_grant_table_op(GNTTABOP_query_size,
&query_size, 1);
if ( rc )
{
spin_unlock(&grant_lock);
break;
}
ASSERT(!grant_frames);
grant_frames = xzalloc_array(unsigned long,
query_size.max_nr_frames);
if ( !grant_frames )
{
spin_unlock(&grant_lock);
rc = -ENOMEM;
break;
}
nr_grant_list = query_size.max_nr_frames;
}
if ( nat.nr_frames > nr_grant_list )
{
spin_unlock(&grant_lock);
rc = -EINVAL;
break;
}
for ( i = 0; i < nat.nr_frames; i++ )
{
if ( !grant_frames[i] )
{
struct xen_add_to_physmap xatp = {
.domid = DOMID_SELF,
.idx = i,
.space = XENMAPSPACE_grant_table,
};
mfn_t mfn;
rc = hypervisor_alloc_unused_page(&mfn);
if ( rc )
{
gprintk(XENLOG_ERR,
"unable to get memory for grant table\n");
break;
}
xatp.gpfn = mfn_x(mfn);
rc = xen_hypercall_memory_op(XENMEM_add_to_physmap, &xatp);
if ( rc )
{
hypervisor_free_unused_page(mfn);
break;
}
BUG_ON(iomem_permit_access(d, mfn_x(mfn), mfn_x(mfn)));
grant_frames[i] = mfn_x(mfn);
}
ASSERT(grant_frames[i]);
if ( compat )
{
compat_pfn_t pfn = grant_frames[i];
if ( __copy_to_compat_offset(cmp.frame_list, i, &pfn, 1) )
{
nat.status = GNTST_bad_virt_addr;
rc = -EFAULT;
break;
}
}
else if ( __copy_to_guest_offset(nat.frame_list, i,
&grant_frames[i], 1) )
{
nat.status = GNTST_bad_virt_addr;
rc = -EFAULT;
break;
}
}
spin_unlock(&grant_lock);
if ( compat )
#define XLAT_gnttab_setup_table_HNDL_frame_list(d, s)
XLAT_gnttab_setup_table(&cmp, &nat);
#undef XLAT_gnttab_setup_table_HNDL_frame_list
if ( unlikely(compat ? __copy_to_guest(uop, &cmp, 1)
: __copy_to_guest(uop, &nat, 1)) )
{
rc = -EFAULT;
break;
}
break;
}
case GNTTABOP_query_size:
rc = xen_hypercall_grant_table_op(GNTTABOP_query_size, uop.p, count);
break;
default:
rc = -EOPNOTSUPP;
break;
}
return rc;
}
long pv_shim_cpu_up(void *data)
{
struct vcpu *v = data;
struct domain *d = v->domain;
bool wake;
BUG_ON(smp_processor_id() != 0);
domain_lock(d);
if ( !v->is_initialised )
{
domain_unlock(d);
return -EINVAL;
}
if ( !cpu_online(v->vcpu_id) )
{
long rc = cpu_up_helper((void *)(unsigned long)v->vcpu_id);
if ( rc )
{
domain_unlock(d);
gprintk(XENLOG_ERR, "Failed to bring up CPU#%u: %ld\n",
v->vcpu_id, rc);
return rc;
}
}
wake = test_and_clear_bit(_VPF_down, &v->pause_flags);
domain_unlock(d);
if ( wake )
vcpu_wake(v);
return 0;
}
long pv_shim_cpu_down(void *data)
{
struct vcpu *v = data;
long rc;
BUG_ON(smp_processor_id() != 0);
if ( !test_and_set_bit(_VPF_down, &v->pause_flags) )
vcpu_sleep_sync(v);
if ( cpu_online(v->vcpu_id) )
{
rc = cpu_down_helper((void *)(unsigned long)v->vcpu_id);
if ( rc )
gprintk(XENLOG_ERR, "Failed to bring down CPU#%u: %ld\n",
v->vcpu_id, rc);
/*
* NB: do not propagate errors from cpu_down_helper failing. The shim
* is going to run with extra CPUs, but that's not going to prevent
* normal operation. OTOH most guests are not prepared to handle an
* error on VCPUOP_down failing, and will likely panic.
*/
}
return 0;
}
static unsigned long batch_memory_op(unsigned int cmd, unsigned int order,
const struct page_list_head *list)
{
struct xen_memory_reservation xmr = {
.domid = DOMID_SELF,
.extent_order = order,
};
unsigned long pfns[64];
const struct page_info *pg;
unsigned long done = 0;
set_xen_guest_handle(xmr.extent_start, pfns);
page_list_for_each ( pg, list )
{
pfns[xmr.nr_extents++] = page_to_mfn(pg);
if ( xmr.nr_extents == ARRAY_SIZE(pfns) || !page_list_next(pg, list) )
{
long nr = xen_hypercall_memory_op(cmd, &xmr);
done += nr > 0 ? nr : 0;
if ( nr != xmr.nr_extents )
break;
xmr.nr_extents = 0;
}
}
return done;
}
void pv_shim_online_memory(unsigned int nr, unsigned int order)
{
struct page_info *page, *tmp;
PAGE_LIST_HEAD(list);
spin_lock(&balloon_lock);
page_list_for_each_safe ( page, tmp, &balloon )
{
/* TODO: add support for splitting high order memory chunks. */
if ( page->v.free.order != order )
continue;
page_list_del(page, &balloon);
page_list_add_tail(page, &list);
if ( !--nr )
break;
}
spin_unlock(&balloon_lock);
if ( nr )
gprintk(XENLOG_WARNING,
"failed to allocate %u extents of order %u for onlining\n",
nr, order);
nr = batch_memory_op(XENMEM_populate_physmap, order, &list);
while ( nr-- )
{
BUG_ON((page = page_list_remove_head(&list)) == NULL);
free_domheap_pages(page, order);
}
if ( !page_list_empty(&list) )
{
gprintk(XENLOG_WARNING,
"failed to online some of the memory regions\n");
spin_lock(&balloon_lock);
page_list_splice(&list, &balloon);
spin_unlock(&balloon_lock);
}
}
void pv_shim_offline_memory(unsigned int nr, unsigned int order)
{
struct page_info *page;
PAGE_LIST_HEAD(list);
while ( nr-- )
{
page = alloc_domheap_pages(NULL, order, 0);
if ( !page )
break;
page_list_add_tail(page, &list);
page->v.free.order = order;
}
if ( nr + 1 )
gprintk(XENLOG_WARNING,
"failed to reserve %u extents of order %u for offlining\n",
nr + 1, order);
nr = batch_memory_op(XENMEM_decrease_reservation, order, &list);
spin_lock(&balloon_lock);
while ( nr-- )
{
BUG_ON((page = page_list_remove_head(&list)) == NULL);
page_list_add_tail(page, &balloon);
}
spin_unlock(&balloon_lock);
if ( !page_list_empty(&list) )
{
gprintk(XENLOG_WARNING,
"failed to offline some of the memory regions\n");
while ( (page = page_list_remove_head(&list)) != NULL )
free_domheap_pages(page, order);
}
}
domid_t get_initial_domain_id(void)
{
uint32_t eax, ebx, ecx, edx;
if ( !pv_shim )
return 0;
cpuid(hypervisor_cpuid_base() + 4, &eax, &ebx, &ecx, &edx);
return (eax & XEN_HVM_CPUID_DOMID_PRESENT) ? ecx : 1;
}
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/