mirror of
https://github.com/xen-project/xen.git
synced 2026-10-29 18:50:05 +01:00
Merge.
This commit is contained in:
commit
c71a3e6d83
32 files changed
+788
-292
No files matched your search
+5
-4
@@ -230,10 +230,11 @@ tools/remus/kmod/*(.cmd|.mod|.ko|.mod.c|.symvers|.xen)
|
||||
tools/security/secpol_tool
|
||||
tools/security/xen/*
|
||||
tools/security/xensec_tool
|
||||
tools/tests/blowfish.bin
|
||||
tools/tests/blowfish.h
|
||||
tools/tests/test_x86_emulator
|
||||
tools/tests/x86_emulate
|
||||
tools/tests/x86_emulator/blowfish.bin
|
||||
tools/tests/x86_emulator/blowfish.h
|
||||
tools/tests/x86_emulator/test_x86_emulator
|
||||
tools/tests/x86_emulator/x86_emulate
|
||||
tools/tests/xen-access/xen-access
|
||||
tools/tests/regression/installed/*
|
||||
tools/tests/regression/build/*
|
||||
tools/tests/regression/downloads/*
|
||||
|
||||
@@ -1130,6 +1130,20 @@ Specify the ticket password which is used by a client for connection.
|
||||
Whether SPICE agent is used for client mouse mode. The default is true (1)
|
||||
(turn on)
|
||||
|
||||
=item B<spicevdagent=BOOLEAN>
|
||||
|
||||
Enables spice vdagent. The Spice vdagent is an optional component for
|
||||
enhancing user experience and performing guest-oriented management
|
||||
tasks. Its features includes: client mouse mode (no need to grab mouse
|
||||
by client, no mouse lag), automatic adjustment of screen resolution,
|
||||
copy and paste (text and image) between client and domU. It also
|
||||
requires vdagent service installed on domU o.s. to work. The default is 0.
|
||||
|
||||
=item B<spice_clipboard_sharing=BOOLEAN>
|
||||
|
||||
Enables Spice clipboard sharing (copy/paste). It requires spicevdagent
|
||||
enabled. The default is false (0).
|
||||
|
||||
=back
|
||||
|
||||
=head3 Miscellaneous Emulated Hardware
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
Booting Xen
|
||||
===========
|
||||
|
||||
Xen follows the zImage protocol defined for 32-bit ARM Linux[1] and the
|
||||
Image protocol defined for ARM64 Linux[2].
|
||||
|
||||
In both cases the recommendation to boot in HYP/EL2 mode is a strict
|
||||
requirement for Xen.
|
||||
|
||||
The exceptions to this on 32-bit ARM are as follows:
|
||||
|
||||
Xen does not require the machine type to be passed in r1. This
|
||||
register is ignored (so may be invalid or the actual machine type).
|
||||
|
||||
Xen does not support the ATAG list and requires Device
|
||||
Tree. Therefore r2 must point to the physical address of device tree
|
||||
block (dtb) in system RAM.
|
||||
|
||||
NOTE: although Xen uses the zImage protocol there is no compression
|
||||
actually used. This should be transparent to the bootloader. The
|
||||
zImage protocol should still be used and not the stricter "raw
|
||||
(non-zImage)" protocol described in arm/Booting.
|
||||
|
||||
There are no exception on 64-bit ARM.
|
||||
|
||||
[1] linux/Documentation/arm/Booting
|
||||
Latest version: http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/arm/Booting
|
||||
|
||||
[2] linux/Documentation/arm64/booting.txt
|
||||
Latest version: http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/arm64/booting.txt
|
||||
@@ -70,8 +70,10 @@ struct consfront_dev *init_consfront(char *_nodename)
|
||||
|
||||
if (!_nodename)
|
||||
snprintf(nodename, sizeof(nodename), "device/console/%d", consfrontends);
|
||||
else
|
||||
strncpy(nodename, _nodename, sizeof(nodename));
|
||||
else {
|
||||
strncpy(nodename, _nodename, sizeof(nodename) - 1);
|
||||
nodename[sizeof(nodename) - 1] = 0;
|
||||
}
|
||||
|
||||
printk("******************* CONSFRONT for %s **********\n\n\n", nodename);
|
||||
|
||||
|
||||
@@ -29,9 +29,12 @@ struct xs_handle *xs_daemon_open()
|
||||
void xs_daemon_close(struct xs_handle *h)
|
||||
{
|
||||
int fd = _xs_fileno(h);
|
||||
struct xenbus_event *event;
|
||||
for (event = files[fd].xenbus.events; event; event = event->next)
|
||||
struct xenbus_event *event, *next;
|
||||
for (event = files[fd].xenbus.events; event; event = next)
|
||||
{
|
||||
next = event->next;
|
||||
free(event);
|
||||
}
|
||||
files[fd].type = FTYPE_NONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -619,7 +619,6 @@ void kdd_guest_teardown(kdd_guest *g)
|
||||
{
|
||||
flush_maps(g);
|
||||
xc_interface_close(g->xc_handle);
|
||||
free(g->id);
|
||||
free(g->hvm_buf);
|
||||
free(g);
|
||||
}
|
||||
+1
-19
@@ -417,24 +417,6 @@ elfnote_dump_format_version(xc_interface *xch,
|
||||
return dump_rtn(xch, args, (char*)&format_version, sizeof(format_version));
|
||||
}
|
||||
|
||||
static int
|
||||
get_guest_width(xc_interface *xch,
|
||||
uint32_t domid,
|
||||
unsigned int *guest_width)
|
||||
{
|
||||
DECLARE_DOMCTL;
|
||||
|
||||
memset(&domctl, 0, sizeof(domctl));
|
||||
domctl.domain = domid;
|
||||
domctl.cmd = XEN_DOMCTL_get_address_size;
|
||||
|
||||
if ( do_domctl(xch, &domctl) != 0 )
|
||||
return 1;
|
||||
|
||||
*guest_width = domctl.u.address_size.size / 8;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
xc_domain_dumpcore_via_callback(xc_interface *xch,
|
||||
uint32_t domid,
|
||||
@@ -478,7 +460,7 @@ xc_domain_dumpcore_via_callback(xc_interface *xch,
|
||||
struct xc_core_section_headers *sheaders = NULL;
|
||||
Elf64_Shdr *shdr;
|
||||
|
||||
if ( get_guest_width(xch, domid, &dinfo->guest_width) != 0 )
|
||||
if ( xc_domain_get_guest_width(xch, domid, &dinfo->guest_width) != 0 )
|
||||
{
|
||||
PERROR("Could not get address size for domain");
|
||||
return sts;
|
||||
|
||||
@@ -436,17 +436,15 @@ static void xc_cpuid_pv_policy(
|
||||
const unsigned int *input, unsigned int *regs)
|
||||
{
|
||||
DECLARE_DOMCTL;
|
||||
unsigned int guest_width;
|
||||
int guest_64bit, xen_64bit = hypervisor_is_64bit(xch);
|
||||
char brand[13];
|
||||
uint64_t xfeature_mask;
|
||||
|
||||
xc_cpuid_brand_get(brand);
|
||||
|
||||
memset(&domctl, 0, sizeof(domctl));
|
||||
domctl.domain = domid;
|
||||
domctl.cmd = XEN_DOMCTL_get_address_size;
|
||||
do_domctl(xch, &domctl);
|
||||
guest_64bit = (domctl.u.address_size.size == 64);
|
||||
xc_domain_get_guest_width(xch, domid, &guest_width);
|
||||
guest_64bit = (guest_width == 8);
|
||||
|
||||
/* Detecting Xen's atitude towards XSAVE */
|
||||
memset(&domctl, 0, sizeof(domctl));
|
||||
|
||||
+12
-19
@@ -62,19 +62,13 @@ static int setup_hypercall_page(struct xc_dom_image *dom)
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int launch_vm(xc_interface *xch, domid_t domid, xc_hypercall_buffer_t *ctxt)
|
||||
static int launch_vm(xc_interface *xch, domid_t domid,
|
||||
vcpu_guest_context_any_t *ctxt)
|
||||
{
|
||||
DECLARE_DOMCTL;
|
||||
DECLARE_HYPERCALL_BUFFER_ARGUMENT(ctxt);
|
||||
int rc;
|
||||
|
||||
xc_dom_printf(xch, "%s: called, ctxt=%p", __FUNCTION__, ctxt);
|
||||
memset(&domctl, 0, sizeof(domctl));
|
||||
domctl.cmd = XEN_DOMCTL_setvcpucontext;
|
||||
domctl.domain = domid;
|
||||
domctl.u.vcpucontext.vcpu = 0;
|
||||
set_xen_guest_handle(domctl.u.vcpucontext.ctxt, ctxt);
|
||||
rc = do_domctl(xch, &domctl);
|
||||
rc = xc_vcpu_setcontext(xch, domid, 0, ctxt);
|
||||
if ( rc != 0 )
|
||||
xc_dom_panic(xch, XC_INTERNAL_ERROR,
|
||||
"%s: SETVCPUCONTEXT failed (rc=%d)", __FUNCTION__, rc);
|
||||
@@ -203,8 +197,8 @@ void *xc_dom_boot_domU_map(struct xc_dom_image *dom, xen_pfn_t pfn,
|
||||
|
||||
int xc_dom_boot_image(struct xc_dom_image *dom)
|
||||
{
|
||||
DECLARE_DOMCTL;
|
||||
DECLARE_HYPERCALL_BUFFER(vcpu_guest_context_any_t, ctxt);
|
||||
xc_dominfo_t info;
|
||||
int rc;
|
||||
|
||||
ctxt = xc_hypercall_buffer_alloc(dom->xch, ctxt, sizeof(*ctxt));
|
||||
@@ -218,23 +212,22 @@ int xc_dom_boot_image(struct xc_dom_image *dom)
|
||||
return rc;
|
||||
|
||||
/* collect some info */
|
||||
domctl.cmd = XEN_DOMCTL_getdomaininfo;
|
||||
domctl.domain = dom->guest_domid;
|
||||
rc = do_domctl(dom->xch, &domctl);
|
||||
if ( rc != 0 )
|
||||
rc = xc_domain_getinfo(dom->xch, dom->guest_domid, 1, &info);
|
||||
if ( rc < 0 )
|
||||
{
|
||||
xc_dom_panic(dom->xch, XC_INTERNAL_ERROR,
|
||||
"%s: getdomaininfo failed (rc=%d)", __FUNCTION__, rc);
|
||||
return rc;
|
||||
}
|
||||
if ( domctl.domain != dom->guest_domid )
|
||||
if ( rc == 0 || info.domid != dom->guest_domid )
|
||||
{
|
||||
xc_dom_panic(dom->xch, XC_INTERNAL_ERROR,
|
||||
"%s: Huh? domid mismatch (%d != %d)", __FUNCTION__,
|
||||
domctl.domain, dom->guest_domid);
|
||||
"%s: Huh? No domains found (nr_domains=%d) "
|
||||
"or domid mismatch (%d != %d)", __FUNCTION__,
|
||||
rc, info.domid, dom->guest_domid);
|
||||
return -1;
|
||||
}
|
||||
dom->shared_info_mfn = domctl.u.getdomaininfo.shared_info_frame;
|
||||
dom->shared_info_mfn = info.shared_info_frame;
|
||||
|
||||
/* sanity checks */
|
||||
if ( !xc_dom_compat_check(dom) )
|
||||
@@ -270,7 +263,7 @@ int xc_dom_boot_image(struct xc_dom_image *dom)
|
||||
if ( (rc = dom->arch_hooks->vcpu(dom, ctxt)) != 0 )
|
||||
return rc;
|
||||
xc_dom_unmap_all(dom);
|
||||
rc = launch_vm(dom->xch, dom->guest_domid, HYPERCALL_BUFFER(ctxt));
|
||||
rc = launch_vm(dom->xch, dom->guest_domid, ctxt);
|
||||
|
||||
xc_hypercall_buffer_free(dom->xch, ctxt);
|
||||
return rc;
|
||||
|
||||
@@ -54,24 +54,17 @@ const char *xc_domain_get_native_protocol(xc_interface *xch,
|
||||
int ret;
|
||||
uint32_t guest_width;
|
||||
const char *protocol;
|
||||
DECLARE_DOMCTL;
|
||||
|
||||
memset(&domctl, 0, sizeof(domctl));
|
||||
domctl.domain = domid;
|
||||
domctl.cmd = XEN_DOMCTL_get_address_size;
|
||||
|
||||
ret = do_domctl(xch, &domctl);
|
||||
ret = xc_domain_get_guest_width(xch, domid, &guest_width);
|
||||
|
||||
if ( ret )
|
||||
return NULL;
|
||||
|
||||
guest_width = domctl.u.address_size.size;
|
||||
|
||||
switch (guest_width) {
|
||||
case 32: /* 32 bit guest */
|
||||
case 4: /* 32 bit guest */
|
||||
protocol = XEN_IO_PROTO_ABI_X86_32;
|
||||
break;
|
||||
case 64: /* 64 bit guest */
|
||||
case 8: /* 64 bit guest */
|
||||
protocol = XEN_IO_PROTO_ABI_X86_64;
|
||||
break;
|
||||
default:
|
||||
|
||||
+143
-6
@@ -21,6 +21,8 @@
|
||||
*/
|
||||
|
||||
#include "xc_private.h"
|
||||
#include "xc_core.h"
|
||||
#include "xg_private.h"
|
||||
#include "xg_save_restore.h"
|
||||
#include <xen/memory.h>
|
||||
#include <xen/hvm/hvm_op.h>
|
||||
@@ -270,6 +272,22 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xc_domain_get_guest_width(xc_interface *xch, uint32_t domid,
|
||||
unsigned int *guest_width)
|
||||
{
|
||||
DECLARE_DOMCTL;
|
||||
|
||||
memset(&domctl, 0, sizeof(domctl));
|
||||
domctl.domain = domid;
|
||||
domctl.cmd = XEN_DOMCTL_get_address_size;
|
||||
|
||||
if ( do_domctl(xch, &domctl) != 0 )
|
||||
return 1;
|
||||
|
||||
/* We want the result in bytes */
|
||||
*guest_width = domctl.u.address_size.size / 8;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xc_domain_getinfo(xc_interface *xch,
|
||||
uint32_t first_domid,
|
||||
@@ -1125,12 +1143,6 @@ int xc_vcpu_setcontext(xc_interface *xch,
|
||||
DECLARE_HYPERCALL_BOUNCE(ctxt, sizeof(vcpu_guest_context_any_t), XC_HYPERCALL_BUFFER_BOUNCE_IN);
|
||||
int rc;
|
||||
|
||||
if (ctxt == NULL)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( xc_hypercall_bounce_pre(xch, ctxt) )
|
||||
return -1;
|
||||
|
||||
@@ -1467,6 +1479,131 @@ int xc_domain_bind_pt_isa_irq(
|
||||
PT_IRQ_TYPE_ISA, 0, 0, 0, machine_irq));
|
||||
}
|
||||
|
||||
int xc_unmap_domain_meminfo(xc_interface *xch, struct xc_domain_meminfo *minfo)
|
||||
{
|
||||
struct domain_info_context _di = { .guest_width = minfo->guest_width };
|
||||
struct domain_info_context *dinfo = &_di;
|
||||
|
||||
free(minfo->pfn_type);
|
||||
if ( minfo->p2m_table )
|
||||
munmap(minfo->p2m_table, P2M_FLL_ENTRIES * PAGE_SIZE);
|
||||
minfo->p2m_table = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xc_map_domain_meminfo(xc_interface *xch, int domid,
|
||||
struct xc_domain_meminfo *minfo)
|
||||
{
|
||||
struct domain_info_context _di;
|
||||
struct domain_info_context *dinfo = &_di;
|
||||
|
||||
xc_dominfo_t info;
|
||||
shared_info_any_t *live_shinfo;
|
||||
xen_capabilities_info_t xen_caps = "";
|
||||
int i;
|
||||
|
||||
/* Only be initialized once */
|
||||
if ( minfo->pfn_type || minfo->p2m_table )
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( xc_domain_getinfo(xch, domid, 1, &info) != 1 )
|
||||
{
|
||||
PERROR("Could not get domain info");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( xc_domain_get_guest_width(xch, domid, &minfo->guest_width) )
|
||||
{
|
||||
PERROR("Could not get domain address size");
|
||||
return -1;
|
||||
}
|
||||
_di.guest_width = minfo->guest_width;
|
||||
|
||||
/* Get page table levels (see get_platform_info() in xg_save_restore.h */
|
||||
if ( xc_version(xch, XENVER_capabilities, &xen_caps) )
|
||||
{
|
||||
PERROR("Could not get Xen capabilities (for page table levels)");
|
||||
return -1;
|
||||
}
|
||||
if ( strstr(xen_caps, "xen-3.0-x86_64") )
|
||||
/* Depends on whether it's a compat 32-on-64 guest */
|
||||
minfo->pt_levels = ( (minfo->guest_width == 8) ? 4 : 3 );
|
||||
else if ( strstr(xen_caps, "xen-3.0-x86_32p") )
|
||||
minfo->pt_levels = 3;
|
||||
else if ( strstr(xen_caps, "xen-3.0-x86_32") )
|
||||
minfo->pt_levels = 2;
|
||||
else
|
||||
{
|
||||
errno = EFAULT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* We need the shared info page for mapping the P2M */
|
||||
live_shinfo = xc_map_foreign_range(xch, domid, PAGE_SIZE, PROT_READ,
|
||||
info.shared_info_frame);
|
||||
if ( !live_shinfo )
|
||||
{
|
||||
PERROR("Could not map the shared info frame (MFN 0x%lx)",
|
||||
info.shared_info_frame);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( xc_core_arch_map_p2m_writable(xch, minfo->guest_width, &info,
|
||||
live_shinfo, &minfo->p2m_table,
|
||||
&minfo->p2m_size) )
|
||||
{
|
||||
PERROR("Could not map the P2M table");
|
||||
munmap(live_shinfo, PAGE_SIZE);
|
||||
return -1;
|
||||
}
|
||||
munmap(live_shinfo, PAGE_SIZE);
|
||||
_di.p2m_size = minfo->p2m_size;
|
||||
|
||||
/* Make space and prepare for getting the PFN types */
|
||||
minfo->pfn_type = calloc(sizeof(*minfo->pfn_type), minfo->p2m_size);
|
||||
if ( !minfo->pfn_type )
|
||||
{
|
||||
PERROR("Could not allocate memory for the PFN types");
|
||||
goto failed;
|
||||
}
|
||||
for ( i = 0; i < minfo->p2m_size; i++ )
|
||||
minfo->pfn_type[i] = pfn_to_mfn(i, minfo->p2m_table,
|
||||
minfo->guest_width);
|
||||
|
||||
/* Retrieve PFN types in batches */
|
||||
for ( i = 0; i < minfo->p2m_size ; i+=1024 )
|
||||
{
|
||||
int count = ((minfo->p2m_size - i ) > 1024 ) ?
|
||||
1024: (minfo->p2m_size - i);
|
||||
|
||||
if ( xc_get_pfn_type_batch(xch, domid, count, minfo->pfn_type + i) )
|
||||
{
|
||||
PERROR("Could not get %d-eth batch of PFN types", (i+1)/1024);
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
failed:
|
||||
if ( minfo->pfn_type )
|
||||
{
|
||||
free(minfo->pfn_type);
|
||||
minfo->pfn_type = NULL;
|
||||
}
|
||||
if ( minfo->p2m_table )
|
||||
{
|
||||
munmap(minfo->p2m_table, P2M_FLL_ENTRIES * PAGE_SIZE);
|
||||
minfo->p2m_table = NULL;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int xc_domain_memory_mapping(
|
||||
xc_interface *xch,
|
||||
uint32_t domid,
|
||||
|
||||
@@ -1406,6 +1406,7 @@ int xc_domain_restore(xc_interface *xch, int io_fd, uint32_t dom,
|
||||
struct restore_callbacks *callbacks)
|
||||
{
|
||||
DECLARE_DOMCTL;
|
||||
xc_dominfo_t info;
|
||||
int rc = 1, frc, i, j, n, m, pae_extended_cr3 = 0, ext_vcpucontext = 0;
|
||||
int vcpuextstate = 0;
|
||||
uint32_t vcpuextstate_size = 0;
|
||||
@@ -1562,14 +1563,12 @@ int xc_domain_restore(xc_interface *xch, int io_fd, uint32_t dom,
|
||||
ROUNDUP(MAX_BATCH_SIZE * sizeof(xen_pfn_t), PAGE_SHIFT));
|
||||
|
||||
/* Get the domain's shared-info frame. */
|
||||
domctl.cmd = XEN_DOMCTL_getdomaininfo;
|
||||
domctl.domain = (domid_t)dom;
|
||||
if ( xc_domctl(xch, &domctl) < 0 )
|
||||
if ( xc_domain_getinfo(xch, (domid_t)dom, 1, &info) != 1 )
|
||||
{
|
||||
PERROR("Could not get information on new domain");
|
||||
goto out;
|
||||
}
|
||||
shared_info_frame = domctl.u.getdomaininfo.shared_info_frame;
|
||||
shared_info_frame = info.shared_info_frame;
|
||||
|
||||
/* Mark all PFNs as invalid; we allocate on demand */
|
||||
for ( pfn = 0; pfn < dinfo->p2m_size; pfn++ )
|
||||
@@ -2113,11 +2112,7 @@ int xc_domain_restore(xc_interface *xch, int io_fd, uint32_t dom,
|
||||
}
|
||||
ctxt->x64.ctrlreg[1] = FOLD_CR3(ctx->p2m[pfn]);
|
||||
}
|
||||
domctl.cmd = XEN_DOMCTL_setvcpucontext;
|
||||
domctl.domain = (domid_t)dom;
|
||||
domctl.u.vcpucontext.vcpu = i;
|
||||
set_xen_guest_handle(domctl.u.vcpucontext.ctxt, ctxt);
|
||||
frc = xc_domctl(xch, &domctl);
|
||||
frc = xc_vcpu_setcontext(xch, dom, i, ctxt);
|
||||
if ( frc != 0 )
|
||||
{
|
||||
PERROR("Couldn't build vcpu%d", i);
|
||||
|
||||
+30
-162
@@ -33,17 +33,6 @@
|
||||
#include "xg_private.h"
|
||||
#include "xg_save_restore.h"
|
||||
|
||||
struct domain_mem_info{
|
||||
int domid;
|
||||
unsigned int pt_level;
|
||||
unsigned int guest_width;
|
||||
xen_pfn_t *pfn_type;
|
||||
xen_pfn_t *p2m_table;
|
||||
unsigned long p2m_size;
|
||||
xen_pfn_t *m2p_table;
|
||||
int max_mfn;
|
||||
};
|
||||
|
||||
struct pte_backup_entry
|
||||
{
|
||||
xen_pfn_t table_mfn;
|
||||
@@ -180,146 +169,6 @@ static int xc_is_page_granted_v2(xc_interface *xch, xen_pfn_t gpfn,
|
||||
return (i != gnt_num);
|
||||
}
|
||||
|
||||
static xen_pfn_t pfn_to_mfn(xen_pfn_t pfn, xen_pfn_t *p2m, int gwidth)
|
||||
{
|
||||
return ((xen_pfn_t) ((gwidth==8)?
|
||||
(((uint64_t *)p2m)[(pfn)]):
|
||||
((((uint32_t *)p2m)[(pfn)]) == 0xffffffffU ?
|
||||
(-1UL) :
|
||||
(((uint32_t *)p2m)[(pfn)]))));
|
||||
}
|
||||
|
||||
static int get_pt_level(xc_interface *xch, uint32_t domid,
|
||||
unsigned int *pt_level,
|
||||
unsigned int *gwidth)
|
||||
{
|
||||
DECLARE_DOMCTL;
|
||||
xen_capabilities_info_t xen_caps = "";
|
||||
|
||||
if (xc_version(xch, XENVER_capabilities, &xen_caps) != 0)
|
||||
return -1;
|
||||
|
||||
memset(&domctl, 0, sizeof(domctl));
|
||||
domctl.domain = domid;
|
||||
domctl.cmd = XEN_DOMCTL_get_address_size;
|
||||
|
||||
if ( do_domctl(xch, &domctl) != 0 )
|
||||
return -1;
|
||||
|
||||
*gwidth = domctl.u.address_size.size / 8;
|
||||
|
||||
if (strstr(xen_caps, "xen-3.0-x86_64"))
|
||||
/* Depends on whether it's a compat 32-on-64 guest */
|
||||
*pt_level = ( (*gwidth == 8) ? 4 : 3 );
|
||||
else if (strstr(xen_caps, "xen-3.0-x86_32p"))
|
||||
*pt_level = 3;
|
||||
else if (strstr(xen_caps, "xen-3.0-x86_32"))
|
||||
*pt_level = 2;
|
||||
else
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int close_mem_info(xc_interface *xch, struct domain_mem_info *minfo)
|
||||
{
|
||||
if (minfo->pfn_type)
|
||||
free(minfo->pfn_type);
|
||||
munmap(minfo->m2p_table, M2P_SIZE(minfo->max_mfn));
|
||||
munmap(minfo->p2m_table, P2M_FLL_ENTRIES * PAGE_SIZE);
|
||||
minfo->p2m_table = minfo->m2p_table = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init_mem_info(xc_interface *xch, int domid,
|
||||
struct domain_mem_info *minfo,
|
||||
xc_dominfo_t *info)
|
||||
{
|
||||
uint64_aligned_t shared_info_frame;
|
||||
shared_info_any_t *live_shinfo = NULL;
|
||||
int i, rc;
|
||||
|
||||
/* Only be initialized once */
|
||||
if (minfo->pfn_type || minfo->m2p_table || minfo->p2m_table)
|
||||
return -EINVAL;
|
||||
|
||||
if ( get_pt_level(xch, domid, &minfo->pt_level,
|
||||
&minfo->guest_width) )
|
||||
{
|
||||
ERROR("Unable to get PT level info.");
|
||||
return -EFAULT;
|
||||
}
|
||||
dinfo->guest_width = minfo->guest_width;
|
||||
|
||||
shared_info_frame = info->shared_info_frame;
|
||||
|
||||
live_shinfo = xc_map_foreign_range(xch, domid,
|
||||
PAGE_SIZE, PROT_READ, shared_info_frame);
|
||||
if ( !live_shinfo )
|
||||
{
|
||||
ERROR("Couldn't map live_shinfo");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if ( (rc = xc_core_arch_map_p2m_writable(xch, minfo->guest_width,
|
||||
info, live_shinfo, &minfo->p2m_table, &minfo->p2m_size)) )
|
||||
{
|
||||
ERROR("Couldn't map p2m table %x\n", rc);
|
||||
goto failed;
|
||||
}
|
||||
munmap(live_shinfo, PAGE_SIZE);
|
||||
live_shinfo = NULL;
|
||||
|
||||
dinfo->p2m_size = minfo->p2m_size;
|
||||
|
||||
minfo->max_mfn = xc_maximum_ram_page(xch);
|
||||
if ( !(minfo->m2p_table =
|
||||
xc_map_m2p(xch, minfo->max_mfn, PROT_READ, NULL)) )
|
||||
{
|
||||
ERROR("Failed to map live M2P table");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* Get pfn type */
|
||||
minfo->pfn_type = calloc(sizeof(*minfo->pfn_type), minfo->p2m_size);
|
||||
if (!minfo->pfn_type)
|
||||
{
|
||||
ERROR("Failed to malloc pfn_type\n");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
for (i = 0; i < minfo->p2m_size; i++)
|
||||
minfo->pfn_type[i] = pfn_to_mfn(i, minfo->p2m_table,
|
||||
minfo->guest_width);
|
||||
|
||||
for (i = 0; i < minfo->p2m_size ; i+=1024)
|
||||
{
|
||||
int count = ((dinfo->p2m_size - i ) > 1024 ) ? 1024: (dinfo->p2m_size - i);
|
||||
if ( ( rc = xc_get_pfn_type_batch(xch, domid, count,
|
||||
minfo->pfn_type + i)) )
|
||||
{
|
||||
ERROR("Failed to get pfn_type %x\n", rc);
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
failed:
|
||||
if (minfo->pfn_type)
|
||||
{
|
||||
free(minfo->pfn_type);
|
||||
minfo->pfn_type = NULL;
|
||||
}
|
||||
if (live_shinfo)
|
||||
munmap(live_shinfo, PAGE_SIZE);
|
||||
munmap(minfo->m2p_table, M2P_SIZE(minfo->max_mfn));
|
||||
munmap(minfo->p2m_table, P2M_FLL_ENTRIES * PAGE_SIZE);
|
||||
minfo->p2m_table = minfo->m2p_table = NULL;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int backup_ptes(xen_pfn_t table_mfn, int offset,
|
||||
struct pte_backup *backup)
|
||||
{
|
||||
@@ -409,7 +258,7 @@ static int __update_pte(xc_interface *xch,
|
||||
}
|
||||
|
||||
static int change_pte(xc_interface *xch, int domid,
|
||||
struct domain_mem_info *minfo,
|
||||
struct xc_domain_meminfo *minfo,
|
||||
struct pte_backup *backup,
|
||||
struct xc_mmu *mmu,
|
||||
pte_func func,
|
||||
@@ -419,7 +268,7 @@ static int change_pte(xc_interface *xch, int domid,
|
||||
uint64_t i;
|
||||
void *content = NULL;
|
||||
|
||||
pte_num = PAGE_SIZE / ((minfo->pt_level == 2) ? 4 : 8);
|
||||
pte_num = PAGE_SIZE / ((minfo->pt_levels == 2) ? 4 : 8);
|
||||
|
||||
for (i = 0; i < minfo->p2m_size; i++)
|
||||
{
|
||||
@@ -442,7 +291,7 @@ static int change_pte(xc_interface *xch, int domid,
|
||||
|
||||
for (j = 0; j < pte_num; j++)
|
||||
{
|
||||
if ( minfo->pt_level == 2 )
|
||||
if ( minfo->pt_levels == 2 )
|
||||
pte = ((const uint32_t*)content)[j];
|
||||
else
|
||||
pte = ((const uint64_t*)content)[j];
|
||||
@@ -454,7 +303,7 @@ static int change_pte(xc_interface *xch, int domid,
|
||||
case 1:
|
||||
if ( xc_add_mmu_update(xch, mmu,
|
||||
table_mfn << PAGE_SHIFT |
|
||||
j * ( (minfo->pt_level == 2) ?
|
||||
j * ( (minfo->pt_levels == 2) ?
|
||||
sizeof(uint32_t): sizeof(uint64_t)) |
|
||||
MMU_PT_UPDATE_PRESERVE_AD,
|
||||
new_pte) )
|
||||
@@ -487,7 +336,7 @@ failed:
|
||||
}
|
||||
|
||||
static int update_pte(xc_interface *xch, int domid,
|
||||
struct domain_mem_info *minfo,
|
||||
struct xc_domain_meminfo *minfo,
|
||||
struct pte_backup *backup,
|
||||
struct xc_mmu *mmu,
|
||||
unsigned long new_mfn)
|
||||
@@ -497,7 +346,7 @@ static int update_pte(xc_interface *xch, int domid,
|
||||
}
|
||||
|
||||
static int clear_pte(xc_interface *xch, int domid,
|
||||
struct domain_mem_info *minfo,
|
||||
struct xc_domain_meminfo *minfo,
|
||||
struct pte_backup *backup,
|
||||
struct xc_mmu *mmu,
|
||||
xen_pfn_t mfn)
|
||||
@@ -545,7 +394,7 @@ static int is_page_exchangable(xc_interface *xch, int domid, xen_pfn_t mfn,
|
||||
int xc_exchange_page(xc_interface *xch, int domid, xen_pfn_t mfn)
|
||||
{
|
||||
xc_dominfo_t info;
|
||||
struct domain_mem_info minfo;
|
||||
struct xc_domain_meminfo minfo;
|
||||
struct xc_mmu *mmu = NULL;
|
||||
struct pte_backup old_ptes = {NULL, 0, 0};
|
||||
grant_entry_v1_t *gnttab_v1 = NULL;
|
||||
@@ -556,6 +405,8 @@ int xc_exchange_page(xc_interface *xch, int domid, xen_pfn_t mfn)
|
||||
int rc, result = -1;
|
||||
uint32_t status;
|
||||
xen_pfn_t new_mfn, gpfn;
|
||||
xen_pfn_t *m2p_table;
|
||||
int max_mfn;
|
||||
|
||||
if ( xc_domain_getinfo(xch, domid, 1, &info) != 1 )
|
||||
{
|
||||
@@ -575,10 +426,26 @@ int xc_exchange_page(xc_interface *xch, int domid, xen_pfn_t mfn)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Get domain's memory information */
|
||||
/* Map M2P and obtain gpfn */
|
||||
max_mfn = xc_maximum_ram_page(xch);
|
||||
if ( !(m2p_table = xc_map_m2p(xch, max_mfn, PROT_READ, NULL)) )
|
||||
{
|
||||
PERROR("Failed to map live M2P table");
|
||||
return -EFAULT;
|
||||
}
|
||||
gpfn = m2p_table[mfn];
|
||||
|
||||
/* Map domain's memory information */
|
||||
memset(&minfo, 0, sizeof(minfo));
|
||||
init_mem_info(xch, domid, &minfo, &info);
|
||||
gpfn = minfo.m2p_table[mfn];
|
||||
if ( xc_map_domain_meminfo(xch, domid, &minfo) )
|
||||
{
|
||||
PERROR("Could not map domain's memory information\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* For translation macros */
|
||||
dinfo->guest_width = minfo.guest_width;
|
||||
dinfo->p2m_size = minfo.p2m_size;
|
||||
|
||||
/* Don't exchange CR3 for PAE guest in PAE host environment */
|
||||
if (minfo.guest_width > sizeof(long))
|
||||
@@ -773,7 +640,8 @@ failed:
|
||||
if (gnttab_v2)
|
||||
munmap(gnttab_v2, gnt_num / (PAGE_SIZE/sizeof(grant_entry_v2_t)));
|
||||
|
||||
close_mem_info(xch, &minfo);
|
||||
xc_unmap_domain_meminfo(xch, &minfo);
|
||||
munmap(m2p_table, M2P_SIZE(max_mfn));
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -51,15 +51,13 @@ unsigned long xc_translate_foreign_address(xc_interface *xch, uint32_t dom,
|
||||
pt_levels = (ctx.msr_efer&EFER_LMA) ? 4 : (ctx.cr4&CR4_PAE) ? 3 : 2;
|
||||
paddr = ctx.cr3 & ((pt_levels == 3) ? ~0x1full : ~0xfffull);
|
||||
} else {
|
||||
DECLARE_DOMCTL;
|
||||
unsigned int gwidth;
|
||||
vcpu_guest_context_any_t ctx;
|
||||
if (xc_vcpu_getcontext(xch, dom, vcpu, &ctx) != 0)
|
||||
return 0;
|
||||
domctl.domain = dom;
|
||||
domctl.cmd = XEN_DOMCTL_get_address_size;
|
||||
if ( do_domctl(xch, &domctl) != 0 )
|
||||
if (xc_domain_get_guest_width(xch, dom, &gwidth) != 0)
|
||||
return 0;
|
||||
if (domctl.u.address_size.size == 64) {
|
||||
if (gwidth == 8) {
|
||||
pt_levels = 4;
|
||||
paddr = (uint64_t)xen_cr3_to_pfn_x86_64(ctx.x64.ctrlreg[3])
|
||||
<< PAGE_SHIFT;
|
||||
|
||||
@@ -609,11 +609,9 @@ int xc_get_pfn_list(xc_interface *xch,
|
||||
|
||||
long xc_get_tot_pages(xc_interface *xch, uint32_t domid)
|
||||
{
|
||||
DECLARE_DOMCTL;
|
||||
domctl.cmd = XEN_DOMCTL_getdomaininfo;
|
||||
domctl.domain = (domid_t)domid;
|
||||
return (do_domctl(xch, &domctl) < 0) ?
|
||||
-1 : domctl.u.getdomaininfo.tot_pages;
|
||||
xc_dominfo_t info;
|
||||
return (xc_domain_getinfo(xch, domid, 1, &info) != 1) ?
|
||||
-1 : info.nr_pages;
|
||||
}
|
||||
|
||||
int xc_copy_to_domain_page(xc_interface *xch,
|
||||
|
||||
+4
-17
@@ -24,19 +24,6 @@
|
||||
#include <xen/foreign/x86_64.h>
|
||||
#include <xen/hvm/params.h>
|
||||
|
||||
static int pv_guest_width(xc_interface *xch, uint32_t domid)
|
||||
{
|
||||
DECLARE_DOMCTL;
|
||||
domctl.domain = domid;
|
||||
domctl.cmd = XEN_DOMCTL_get_address_size;
|
||||
if ( xc_domctl(xch, &domctl) != 0 )
|
||||
{
|
||||
PERROR("Could not get guest address size");
|
||||
return -1;
|
||||
}
|
||||
return domctl.u.address_size.size / 8;
|
||||
}
|
||||
|
||||
static int modify_returncode(xc_interface *xch, uint32_t domid)
|
||||
{
|
||||
vcpu_guest_context_any_t ctxt;
|
||||
@@ -71,8 +58,7 @@ static int modify_returncode(xc_interface *xch, uint32_t domid)
|
||||
else
|
||||
{
|
||||
/* Probe PV guest address width. */
|
||||
dinfo->guest_width = pv_guest_width(xch, domid);
|
||||
if ( dinfo->guest_width < 0 )
|
||||
if ( xc_domain_get_guest_width(xch, domid, &dinfo->guest_width) )
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -120,7 +106,8 @@ static int xc_domain_resume_any(xc_interface *xch, uint32_t domid)
|
||||
xc_dominfo_t info;
|
||||
int i, rc = -1;
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
struct domain_info_context _dinfo = { .p2m_size = 0 };
|
||||
struct domain_info_context _dinfo = { .guest_width = 0,
|
||||
.p2m_size = 0 };
|
||||
struct domain_info_context *dinfo = &_dinfo;
|
||||
unsigned long mfn;
|
||||
vcpu_guest_context_any_t ctxt;
|
||||
@@ -147,7 +134,7 @@ static int xc_domain_resume_any(xc_interface *xch, uint32_t domid)
|
||||
return rc;
|
||||
}
|
||||
|
||||
dinfo->guest_width = pv_guest_width(xch, domid);
|
||||
xc_domain_get_guest_width(xch, domid, &dinfo->guest_width);
|
||||
if ( dinfo->guest_width != sizeof(long) )
|
||||
{
|
||||
ERROR("Cannot resume uncooperative cross-address-size guests");
|
||||
|
||||
@@ -560,6 +560,19 @@ int xc_vcpu_getaffinity(xc_interface *xch,
|
||||
int vcpu,
|
||||
xc_cpumap_t cpumap);
|
||||
|
||||
|
||||
/**
|
||||
* This function will return the guest_width (in bytes) for the
|
||||
* specified domain.
|
||||
*
|
||||
* @param xch a handle to an open hypervisor interface.
|
||||
* @param domid the domain id one wants the address size width of.
|
||||
* @param addr_size the address size.
|
||||
*/
|
||||
int xc_domain_get_guest_width(xc_interface *xch, uint32_t domid,
|
||||
unsigned int *guest_width);
|
||||
|
||||
|
||||
/**
|
||||
* This function will return information about one or more domains. It is
|
||||
* designed to iterate over the list of domains. If a single domain is
|
||||
|
||||
@@ -275,6 +275,23 @@ int xc_query_page_offline_status(xc_interface *xch, unsigned long start,
|
||||
int xc_exchange_page(xc_interface *xch, int domid, xen_pfn_t mfn);
|
||||
|
||||
|
||||
/**
|
||||
* Memory related information, such as PFN types, the P2M table,
|
||||
* the guest word width and the guest page table levels.
|
||||
*/
|
||||
struct xc_domain_meminfo {
|
||||
unsigned int pt_levels;
|
||||
unsigned int guest_width;
|
||||
xen_pfn_t *pfn_type;
|
||||
xen_pfn_t *p2m_table;
|
||||
unsigned long p2m_size;
|
||||
};
|
||||
|
||||
int xc_map_domain_meminfo(xc_interface *xch, int domid,
|
||||
struct xc_domain_meminfo *minfo);
|
||||
|
||||
int xc_unmap_domain_meminfo(xc_interface *xch, struct xc_domain_meminfo *mem);
|
||||
|
||||
/**
|
||||
* This function map m2p table
|
||||
* @parm xch a handle to an open hypervisor interface
|
||||
|
||||
@@ -136,6 +136,15 @@ struct domain_info_context {
|
||||
unsigned long p2m_size;
|
||||
};
|
||||
|
||||
static inline xen_pfn_t pfn_to_mfn(xen_pfn_t pfn, xen_pfn_t *p2m, int gwidth)
|
||||
{
|
||||
return ((xen_pfn_t) ((gwidth==8)?
|
||||
(((uint64_t *)p2m)[(pfn)]):
|
||||
((((uint32_t *)p2m)[(pfn)]) == 0xffffffffU ?
|
||||
(-1UL) :
|
||||
(((uint32_t *)p2m)[(pfn)]))));
|
||||
}
|
||||
|
||||
/* Number of xen_pfn_t in a page */
|
||||
#define FPP (PAGE_SIZE/(dinfo->guest_width))
|
||||
|
||||
|
||||
@@ -301,7 +301,6 @@ static inline int get_platform_info(xc_interface *xch, uint32_t dom,
|
||||
{
|
||||
xen_capabilities_info_t xen_caps = "";
|
||||
xen_platform_parameters_t xen_params;
|
||||
DECLARE_DOMCTL;
|
||||
|
||||
if (xc_version(xch, XENVER_platform_parameters, &xen_params) != 0)
|
||||
return 0;
|
||||
@@ -313,15 +312,9 @@ static inline int get_platform_info(xc_interface *xch, uint32_t dom,
|
||||
|
||||
*hvirt_start = xen_params.virt_start;
|
||||
|
||||
memset(&domctl, 0, sizeof(domctl));
|
||||
domctl.domain = dom;
|
||||
domctl.cmd = XEN_DOMCTL_get_address_size;
|
||||
|
||||
if ( do_domctl(xch, &domctl) != 0 )
|
||||
if ( xc_domain_get_guest_width(xch, dom, guest_width) != 0)
|
||||
return 0;
|
||||
|
||||
*guest_width = domctl.u.address_size.size / 8;
|
||||
|
||||
/* 64-bit tools will see the 64-bit hvirt_start, but 32-bit guests
|
||||
* will be using the compat one. */
|
||||
if ( *guest_width < sizeof (unsigned long) )
|
||||
|
||||
+9
-2
@@ -122,8 +122,8 @@ static void free_disable_deaths(libxl__gc *gc,
|
||||
|
||||
static void discard_events(struct libxl__event_list *l) {
|
||||
/* doesn't bother unlinking from the list, so l is corrupt on return */
|
||||
libxl_event *ev;
|
||||
LIBXL_TAILQ_FOREACH(ev, l, link)
|
||||
libxl_event *ev, *next;
|
||||
LIBXL_TAILQ_FOREACH_SAFE(ev, l, link, next)
|
||||
libxl_event_free(0, ev);
|
||||
}
|
||||
|
||||
@@ -2234,9 +2234,16 @@ static int libxl__device_disk_from_xs_be(libxl__gc *gc,
|
||||
libxl_ctx *ctx = libxl__gc_owner(gc);
|
||||
unsigned int len;
|
||||
char *tmp;
|
||||
int rc;
|
||||
|
||||
libxl_device_disk_init(disk);
|
||||
|
||||
rc = sscanf(be_path, "/local/domain/%d/", &disk->backend_domid);
|
||||
if (rc != 1) {
|
||||
LOG(ERROR, "Unable to fetch device backend domid from %s", be_path);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* "params" may not be present; but everything else must be. */
|
||||
tmp = xs_read(ctx->xsh, XBT_NULL,
|
||||
libxl__sprintf(gc, "%s/params", be_path), &len);
|
||||
|
||||
@@ -344,6 +344,17 @@
|
||||
*/
|
||||
#define LIBXL_HAVE_DOMINFO_OUTSTANDING_MEMKB 1
|
||||
|
||||
/*
|
||||
* LIBXL_HAVE_SPICE_VDAGENT
|
||||
*
|
||||
* If defined, then the libxl_spice_info structure will contain a boolean type:
|
||||
* vdagent and clipboard_sharing. These values define if Spice vdagent and
|
||||
* clipboard sharing are enabled.
|
||||
*
|
||||
* If this is not defined, the Spice vdagent support is ignored.
|
||||
*/
|
||||
#define LIBXL_HAVE_SPICE_VDAGENT 1
|
||||
|
||||
/* Functions annotated with LIBXL_EXTERNAL_CALLERS_ONLY may not be
|
||||
* called from within libxl itself. Callers outside libxl, who
|
||||
* do not #include libxl_internal.h, are fine. */
|
||||
|
||||
@@ -272,6 +272,9 @@ int libxl__domain_build_info_setdefault(libxl__gc *gc,
|
||||
libxl_defbool_setdefault(&b_info->u.hvm.spice.disable_ticketing,
|
||||
false);
|
||||
libxl_defbool_setdefault(&b_info->u.hvm.spice.agent_mouse, true);
|
||||
libxl_defbool_setdefault(&b_info->u.hvm.spice.vdagent, false);
|
||||
libxl_defbool_setdefault(&b_info->u.hvm.spice.clipboard_sharing,
|
||||
false);
|
||||
}
|
||||
|
||||
libxl_defbool_setdefault(&b_info->u.hvm.nographic, false);
|
||||
|
||||
@@ -351,6 +351,10 @@ static char *dm_spice_options(libxl__gc *gc,
|
||||
opt = libxl__sprintf(gc, "%s,password=%s", opt, spice->passwd);
|
||||
opt = libxl__sprintf(gc, "%s,agent-mouse=%s", opt,
|
||||
libxl_defbool_val(spice->agent_mouse) ? "on" : "off");
|
||||
|
||||
if (!libxl_defbool_val(spice->clipboard_sharing))
|
||||
opt = libxl__sprintf(gc, "%s,disable-copy-paste", opt);
|
||||
|
||||
return opt;
|
||||
}
|
||||
|
||||
@@ -472,6 +476,12 @@ static char ** libxl__build_device_model_args_new(libxl__gc *gc,
|
||||
|
||||
flexarray_append(dm_args, "-spice");
|
||||
flexarray_append(dm_args, spiceoptions);
|
||||
if (libxl_defbool_val(b_info->u.hvm.spice.vdagent)) {
|
||||
flexarray_vappend(dm_args, "-device", "virtio-serial",
|
||||
"-chardev", "spicevmc,id=vdagent,name=vdagent", "-device",
|
||||
"virtserialport,chardev=vdagent,name=com.redhat.spice.0",
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
switch (b_info->u.hvm.vga.kind) {
|
||||
|
||||
@@ -33,7 +33,7 @@ static void check_open_fds(const char *what)
|
||||
|
||||
for (i = 4; i < 256; i++) {
|
||||
#ifdef __linux__
|
||||
size_t len;
|
||||
ssize_t len;
|
||||
char path[PATH_MAX];
|
||||
char linkpath[PATH_MAX+1];
|
||||
#endif
|
||||
|
||||
@@ -181,6 +181,8 @@ libxl_spice_info = Struct("spice_info", [
|
||||
("disable_ticketing", libxl_defbool),
|
||||
("passwd", string),
|
||||
("agent_mouse", libxl_defbool),
|
||||
("vdagent", libxl_defbool),
|
||||
("clipboard_sharing", libxl_defbool),
|
||||
])
|
||||
|
||||
libxl_sdl_info = Struct("sdl_info", [
|
||||
|
||||
@@ -1495,6 +1495,10 @@ skip_vfb:
|
||||
&b_info->u.hvm.spice.passwd, 0);
|
||||
xlu_cfg_get_defbool(config, "spiceagent_mouse",
|
||||
&b_info->u.hvm.spice.agent_mouse, 0);
|
||||
xlu_cfg_get_defbool(config, "spicevdagent",
|
||||
&b_info->u.hvm.spice.vdagent, 0);
|
||||
xlu_cfg_get_defbool(config, "spice_clipboard_sharing",
|
||||
&b_info->u.hvm.spice.clipboard_sharing, 0);
|
||||
xlu_cfg_get_defbool(config, "nographic", &b_info->u.hvm.nographic, 0);
|
||||
xlu_cfg_get_defbool(config, "gfx_passthru",
|
||||
&b_info->u.hvm.gfx_passthru, 0);
|
||||
@@ -6570,7 +6574,7 @@ int main_cpupoollist(int argc, char **argv)
|
||||
char *name;
|
||||
int ret = 0;
|
||||
|
||||
SWITCH_FOREACH_OPT(opt, "hc", opts, "cpupool-list", 1) {
|
||||
SWITCH_FOREACH_OPT(opt, "hc", opts, "cpupool-list", 0) {
|
||||
case 'c':
|
||||
opt_cpus = 1;
|
||||
break;
|
||||
|
||||
+5
-2
@@ -10,7 +10,7 @@ CFLAGS += $(CFLAGS_libxenstore)
|
||||
HDRS = $(wildcard *.h)
|
||||
|
||||
TARGETS-y := xenperf xenpm xen-tmem-list-parse gtraceview gtracestat xenlockprof xenwatchdogd xencov
|
||||
TARGETS-$(CONFIG_X86) += xen-detect xen-hvmctx xen-hvmcrash xen-lowmemd
|
||||
TARGETS-$(CONFIG_X86) += xen-detect xen-hvmctx xen-hvmcrash xen-lowmemd xen-mfndump
|
||||
TARGETS-$(CONFIG_MIGRATE) += xen-hptool
|
||||
TARGETS := $(TARGETS-y)
|
||||
|
||||
@@ -22,7 +22,7 @@ INSTALL_BIN := $(INSTALL_BIN-y)
|
||||
|
||||
INSTALL_SBIN-y := xen-bugtool xen-python-path xenperf xenpm xen-tmem-list-parse gtraceview \
|
||||
gtracestat xenlockprof xenwatchdogd xen-ringwatch xencov
|
||||
INSTALL_SBIN-$(CONFIG_X86) += xen-hvmctx xen-hvmcrash xen-lowmemd
|
||||
INSTALL_SBIN-$(CONFIG_X86) += xen-hvmctx xen-hvmcrash xen-lowmemd xen-mfndump
|
||||
INSTALL_SBIN-$(CONFIG_MIGRATE) += xen-hptool
|
||||
INSTALL_SBIN := $(INSTALL_SBIN-y)
|
||||
|
||||
@@ -75,6 +75,9 @@ xenlockprof: xenlockprof.o
|
||||
xen-hptool: xen-hptool.o
|
||||
$(CC) $(LDFLAGS) -o $@ $< $(LDLIBS_libxenctrl) $(LDLIBS_libxenguest) $(LDLIBS_libxenstore) $(APPEND_LDFLAGS)
|
||||
|
||||
xen-mfndump: xen-mfndump.o
|
||||
$(CC) $(LDFLAGS) -o $@ $< $(LDLIBS_libxenctrl) $(LDLIBS_libxenguest) $(LDLIBS_libxenstore) $(APPEND_LDFLAGS)
|
||||
|
||||
xenwatchdogd: xenwatchdogd.o
|
||||
$(CC) $(LDFLAGS) -o $@ $< $(LDLIBS_libxenctrl) $(APPEND_LDFLAGS)
|
||||
|
||||
|
||||
@@ -0,0 +1,425 @@
|
||||
#include <xenctrl.h>
|
||||
#include <xc_private.h>
|
||||
#include <xc_core.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "xg_save_restore.h"
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))
|
||||
|
||||
static xc_interface *xch;
|
||||
|
||||
int help_func(int argc, char *argv[])
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage: xen-mfndump <command> [args]\n"
|
||||
"Commands:\n"
|
||||
" help show this help\n"
|
||||
" dump-m2p show M2P\n"
|
||||
" dump-p2m <domid> show P2M of <domid>\n"
|
||||
" dump-ptes <domid> <mfn> show the PTEs in <mfn>\n"
|
||||
" lookup-pte <domid> <mfn> find the PTE mapping <mfn>\n"
|
||||
" memcmp-mfns <domid1> <mfn1> <domid2> <mfn2>\n"
|
||||
" compare content of <mfn1> & <mfn2>\n"
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dump_m2p_func(int argc, char *argv[])
|
||||
{
|
||||
unsigned long i, max_mfn;
|
||||
xen_pfn_t *m2p_table;
|
||||
|
||||
if ( argc > 0 )
|
||||
{
|
||||
help_func(0, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Map M2P and obtain gpfn */
|
||||
max_mfn = xc_maximum_ram_page(xch);
|
||||
if ( !(m2p_table = xc_map_m2p(xch, max_mfn, PROT_READ, NULL)) )
|
||||
{
|
||||
ERROR("Failed to map live M2P table");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(" --- Dumping M2P ---\n");
|
||||
printf(" Max MFN: %lu\n", max_mfn);
|
||||
for ( i = 0; i < max_mfn; i++ )
|
||||
{
|
||||
printf(" mfn=0x%lx ==> pfn=0x%lx\n", i, m2p_table[i]);
|
||||
}
|
||||
printf(" --- End of M2P ---\n");
|
||||
|
||||
munmap(m2p_table, M2P_SIZE(max_mfn));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dump_p2m_func(int argc, char *argv[])
|
||||
{
|
||||
struct xc_domain_meminfo minfo;
|
||||
xc_dominfo_t info;
|
||||
unsigned long i;
|
||||
int domid;
|
||||
|
||||
if ( argc < 1 )
|
||||
{
|
||||
help_func(0, NULL);
|
||||
return 1;
|
||||
}
|
||||
domid = atoi(argv[0]);
|
||||
|
||||
if ( xc_domain_getinfo(xch, domid, 1, &info) != 1 ||
|
||||
info.domid != domid )
|
||||
{
|
||||
ERROR("Failed to obtain info for domain %d\n", domid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Retrieve all the info about the domain's memory */
|
||||
memset(&minfo, 0, sizeof(minfo));
|
||||
if ( xc_map_domain_meminfo(xch, domid, &minfo) )
|
||||
{
|
||||
ERROR("Could not map domain %d memory information\n", domid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(" --- Dumping P2M for domain %d ---\n", domid);
|
||||
printf(" Guest Width: %u, PT Levels: %u P2M size: = %lu\n",
|
||||
minfo.guest_width, minfo.pt_levels, minfo.p2m_size);
|
||||
for ( i = 0; i < minfo.p2m_size; i++ )
|
||||
{
|
||||
unsigned long pagetype = minfo.pfn_type[i] &
|
||||
XEN_DOMCTL_PFINFO_LTAB_MASK;
|
||||
|
||||
printf(" pfn=0x%lx ==> mfn=0x%lx (type 0x%lx)", i, minfo.p2m_table[i],
|
||||
pagetype >> XEN_DOMCTL_PFINFO_LTAB_SHIFT);
|
||||
|
||||
if ( is_mapped(minfo.p2m_table[i]) )
|
||||
printf(" [mapped]");
|
||||
|
||||
if ( pagetype & XEN_DOMCTL_PFINFO_LPINTAB )
|
||||
printf (" [pinned]");
|
||||
|
||||
if ( pagetype == XEN_DOMCTL_PFINFO_XTAB )
|
||||
printf(" [xtab]");
|
||||
if ( pagetype == XEN_DOMCTL_PFINFO_BROKEN )
|
||||
printf(" [broken]");
|
||||
if ( pagetype == XEN_DOMCTL_PFINFO_XALLOC )
|
||||
printf( " [xalloc]");
|
||||
|
||||
switch ( pagetype & XEN_DOMCTL_PFINFO_LTABTYPE_MASK )
|
||||
{
|
||||
case XEN_DOMCTL_PFINFO_L1TAB:
|
||||
printf(" L1 table");
|
||||
break;
|
||||
|
||||
case XEN_DOMCTL_PFINFO_L2TAB:
|
||||
printf(" L2 table");
|
||||
break;
|
||||
|
||||
case XEN_DOMCTL_PFINFO_L3TAB:
|
||||
printf(" L3 table");
|
||||
break;
|
||||
|
||||
case XEN_DOMCTL_PFINFO_L4TAB:
|
||||
printf(" L4 table");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
printf(" --- End of P2M for domain %d ---\n", domid);
|
||||
|
||||
xc_unmap_domain_meminfo(xch, &minfo);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dump_ptes_func(int argc, char *argv[])
|
||||
{
|
||||
struct xc_domain_meminfo minfo;
|
||||
xc_dominfo_t info;
|
||||
void *page = NULL;
|
||||
unsigned long i, max_mfn;
|
||||
int domid, pte_num, rc = 0;
|
||||
xen_pfn_t pfn, mfn, *m2p_table;
|
||||
|
||||
if ( argc < 2 )
|
||||
{
|
||||
help_func(0, NULL);
|
||||
return 1;
|
||||
}
|
||||
domid = atoi(argv[0]);
|
||||
mfn = strtoul(argv[1], NULL, 16);
|
||||
|
||||
if ( xc_domain_getinfo(xch, domid, 1, &info) != 1 ||
|
||||
info.domid != domid )
|
||||
{
|
||||
ERROR("Failed to obtain info for domain %d\n", domid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Retrieve all the info about the domain's memory */
|
||||
memset(&minfo, 0, sizeof(minfo));
|
||||
if ( xc_map_domain_meminfo(xch, domid, &minfo) )
|
||||
{
|
||||
ERROR("Could not map domain %d memory information\n", domid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Map M2P and obtain gpfn */
|
||||
max_mfn = xc_maximum_ram_page(xch);
|
||||
if ( (mfn > max_mfn) ||
|
||||
!(m2p_table = xc_map_m2p(xch, max_mfn, PROT_READ, NULL)) )
|
||||
{
|
||||
xc_unmap_domain_meminfo(xch, &minfo);
|
||||
ERROR("Failed to map live M2P table");
|
||||
return -1;
|
||||
}
|
||||
|
||||
pfn = m2p_table[mfn];
|
||||
if ( pfn >= minfo.p2m_size )
|
||||
{
|
||||
ERROR("pfn 0x%lx out of range for domain %d\n", pfn, domid);
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ( !(minfo.pfn_type[pfn] & XEN_DOMCTL_PFINFO_LTABTYPE_MASK) )
|
||||
{
|
||||
ERROR("pfn 0x%lx for domain %d is not a PT\n", pfn, domid);
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
page = xc_map_foreign_range(xch, domid, PAGE_SIZE, PROT_READ,
|
||||
minfo.p2m_table[pfn]);
|
||||
if ( !page )
|
||||
{
|
||||
ERROR("Failed to map 0x%lx\n", minfo.p2m_table[pfn]);
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
pte_num = PAGE_SIZE / 8;
|
||||
|
||||
printf(" --- Dumping %d PTEs for domain %d ---\n", pte_num, domid);
|
||||
printf(" Guest Width: %u, PT Levels: %u P2M size: = %lu\n",
|
||||
minfo.guest_width, minfo.pt_levels, minfo.p2m_size);
|
||||
printf(" pfn: 0x%lx, mfn: 0x%lx",
|
||||
pfn, minfo.p2m_table[pfn]);
|
||||
switch ( minfo.pfn_type[pfn] & XEN_DOMCTL_PFINFO_LTABTYPE_MASK )
|
||||
{
|
||||
case XEN_DOMCTL_PFINFO_L1TAB:
|
||||
printf(", L1 table");
|
||||
break;
|
||||
case XEN_DOMCTL_PFINFO_L2TAB:
|
||||
printf(", L2 table");
|
||||
break;
|
||||
case XEN_DOMCTL_PFINFO_L3TAB:
|
||||
printf(", L3 table");
|
||||
break;
|
||||
case XEN_DOMCTL_PFINFO_L4TAB:
|
||||
printf(", L4 table");
|
||||
break;
|
||||
}
|
||||
if ( minfo.pfn_type[pfn] & XEN_DOMCTL_PFINFO_LPINTAB )
|
||||
printf (" [pinned]");
|
||||
if ( is_mapped(minfo.p2m_table[pfn]) )
|
||||
printf(" [mapped]");
|
||||
printf("\n");
|
||||
|
||||
for ( i = 0; i < pte_num; i++ )
|
||||
printf(" pte[%lu]: 0x%lx\n", i, ((const uint64_t*)page)[i]);
|
||||
|
||||
printf(" --- End of PTEs for domain %d, pfn=0x%lx (mfn=0x%lx) ---\n",
|
||||
domid, pfn, minfo.p2m_table[pfn]);
|
||||
|
||||
out:
|
||||
munmap(page, PAGE_SIZE);
|
||||
xc_unmap_domain_meminfo(xch, &minfo);
|
||||
munmap(m2p_table, M2P_SIZE(max_mfn));
|
||||
return rc;
|
||||
}
|
||||
|
||||
int lookup_pte_func(int argc, char *argv[])
|
||||
{
|
||||
struct xc_domain_meminfo minfo;
|
||||
xc_dominfo_t info;
|
||||
void *page = NULL;
|
||||
unsigned long i, j;
|
||||
int domid, pte_num;
|
||||
xen_pfn_t mfn;
|
||||
|
||||
if ( argc < 2 )
|
||||
{
|
||||
help_func(0, NULL);
|
||||
return 1;
|
||||
}
|
||||
domid = atoi(argv[0]);
|
||||
mfn = strtoul(argv[1], NULL, 16);
|
||||
|
||||
if ( xc_domain_getinfo(xch, domid, 1, &info) != 1 ||
|
||||
info.domid != domid )
|
||||
{
|
||||
ERROR("Failed to obtain info for domain %d\n", domid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Retrieve all the info about the domain's memory */
|
||||
memset(&minfo, 0, sizeof(minfo));
|
||||
if ( xc_map_domain_meminfo(xch, domid, &minfo) )
|
||||
{
|
||||
ERROR("Could not map domain %d memory information\n", domid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pte_num = PAGE_SIZE / 8;
|
||||
|
||||
printf(" --- Lookig for PTEs mapping mfn 0x%lx for domain %d ---\n",
|
||||
mfn, domid);
|
||||
printf(" Guest Width: %u, PT Levels: %u P2M size: = %lu\n",
|
||||
minfo.guest_width, minfo.pt_levels, minfo.p2m_size);
|
||||
|
||||
for ( i = 0; i < minfo.p2m_size; i++ )
|
||||
{
|
||||
if ( !(minfo.pfn_type[i] & XEN_DOMCTL_PFINFO_LTABTYPE_MASK) )
|
||||
continue;
|
||||
|
||||
page = xc_map_foreign_range(xch, domid, PAGE_SIZE, PROT_READ,
|
||||
minfo.p2m_table[i]);
|
||||
if ( !page )
|
||||
continue;
|
||||
|
||||
for ( j = 0; j < pte_num; j++ )
|
||||
{
|
||||
uint64_t pte = ((const uint64_t*)page)[j];
|
||||
|
||||
#define __MADDR_BITS_X86 ((minfo.guest_width == 8) ? 52 : 44)
|
||||
#define __MFN_MASK_X86 ((1ULL << (__MADDR_BITS_X86 - PAGE_SHIFT_X86)) - 1)
|
||||
if ( ((pte >> PAGE_SHIFT_X86) & __MFN_MASK_X86) == mfn)
|
||||
printf(" 0x%lx <-- [0x%lx][%lu]: 0x%lx\n",
|
||||
mfn, minfo.p2m_table[i], j, pte);
|
||||
#undef __MADDR_BITS_X86
|
||||
#undef __MFN_MASK_X8
|
||||
}
|
||||
|
||||
munmap(page, PAGE_SIZE);
|
||||
page = NULL;
|
||||
}
|
||||
|
||||
xc_unmap_domain_meminfo(xch, &minfo);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int memcmp_mfns_func(int argc, char *argv[])
|
||||
{
|
||||
xc_dominfo_t info1, info2;
|
||||
void *page1 = NULL, *page2 = NULL;
|
||||
int domid1, domid2;
|
||||
xen_pfn_t mfn1, mfn2;
|
||||
int rc = 0;
|
||||
|
||||
if ( argc < 4 )
|
||||
{
|
||||
help_func(0, NULL);
|
||||
return 1;
|
||||
}
|
||||
domid1 = atoi(argv[0]);
|
||||
domid2 = atoi(argv[2]);
|
||||
mfn1 = strtoul(argv[1], NULL, 16);
|
||||
mfn2 = strtoul(argv[3], NULL, 16);
|
||||
|
||||
if ( xc_domain_getinfo(xch, domid1, 1, &info1) != 1 ||
|
||||
xc_domain_getinfo(xch, domid2, 1, &info2) != 1 ||
|
||||
info1.domid != domid1 || info2.domid != domid2)
|
||||
{
|
||||
ERROR("Failed to obtain info for domains\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
page1 = xc_map_foreign_range(xch, domid1, PAGE_SIZE, PROT_READ, mfn1);
|
||||
page2 = xc_map_foreign_range(xch, domid2, PAGE_SIZE, PROT_READ, mfn2);
|
||||
if ( !page1 || !page2 )
|
||||
{
|
||||
ERROR("Failed to map either 0x%lx[dom %d] or 0x%lx[dom %d]\n",
|
||||
mfn1, domid1, mfn2, domid2);
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
printf(" --- Comparing the content of 2 MFNs ---\n");
|
||||
printf(" 1: 0x%lx[dom %d], 2: 0x%lx[dom %d]\n",
|
||||
mfn1, domid1, mfn2, domid2);
|
||||
printf(" memcpy(1, 2) = %d\n", memcmp(page1, page2, PAGE_SIZE));
|
||||
|
||||
out:
|
||||
munmap(page1, PAGE_SIZE);
|
||||
munmap(page2, PAGE_SIZE);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
int (*func)(int argc, char *argv[]);
|
||||
} opts[] = {
|
||||
{ "help", help_func },
|
||||
{ "dump-m2p", dump_m2p_func },
|
||||
{ "dump-p2m", dump_p2m_func },
|
||||
{ "dump-ptes", dump_ptes_func },
|
||||
{ "lookup-pte", lookup_pte_func },
|
||||
{ "memcmp-mfns", memcmp_mfns_func},
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
help_func(0, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
xch = xc_interface_open(0, 0, 0);
|
||||
if ( !xch )
|
||||
{
|
||||
ERROR("Failed to open an xc handler");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for ( i = 0; i < ARRAY_SIZE(opts); i++ )
|
||||
{
|
||||
if ( !strncmp(opts[i].name, argv[1], strlen(argv[1])) )
|
||||
break;
|
||||
}
|
||||
|
||||
if ( i == ARRAY_SIZE(opts) )
|
||||
{
|
||||
fprintf(stderr, "Unknown option '%s'", argv[1]);
|
||||
help_func(0, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ret = opts[i].func(argc - 2, argv + 2);
|
||||
|
||||
xc_interface_close(xch);
|
||||
|
||||
return !!ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* mode: C
|
||||
* c-set-style: "BSD"
|
||||
* c-basic-offset: 4
|
||||
* tab-width: 4
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*/
|
||||
@@ -208,15 +208,15 @@ xenstat_node *xenstat_get_node(xenstat_handle * handle, unsigned int flags)
|
||||
node->num_domains,
|
||||
DOMAIN_CHUNK_SIZE,
|
||||
domaininfo);
|
||||
if (new_domains < 0)
|
||||
goto err;
|
||||
|
||||
tmp = realloc(node->domains,
|
||||
(node->num_domains + new_domains)
|
||||
* sizeof(xenstat_domain));
|
||||
if (tmp == NULL) {
|
||||
free(node->domains);
|
||||
free(node);
|
||||
return NULL;
|
||||
}
|
||||
if (tmp == NULL)
|
||||
goto err;
|
||||
|
||||
node->domains = tmp;
|
||||
|
||||
domain = node->domains + node->num_domains;
|
||||
@@ -280,6 +280,10 @@ xenstat_node *xenstat_get_node(xenstat_handle * handle, unsigned int flags)
|
||||
}
|
||||
|
||||
return node;
|
||||
err:
|
||||
free(node->domains);
|
||||
free(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void xenstat_free_node(xenstat_node * node)
|
||||
|
||||
@@ -32,7 +32,7 @@ evtchn_port_t xenbus_evtchn(void)
|
||||
if (fd == -1)
|
||||
return -1;
|
||||
|
||||
rc = read(fd, str, sizeof(str));
|
||||
rc = read(fd, str, sizeof(str) - 1);
|
||||
if (rc == -1)
|
||||
{
|
||||
int err = errno;
|
||||
|
||||
@@ -771,12 +771,9 @@ static void dump_ctx(int vcpu)
|
||||
}
|
||||
ctxt_word_size = (strstr(xen_caps, "xen-3.0-x86_64")) ? 8 : 4;
|
||||
} else {
|
||||
struct xen_domctl domctl;
|
||||
memset(&domctl, 0, sizeof domctl);
|
||||
domctl.domain = xenctx.domid;
|
||||
domctl.cmd = XEN_DOMCTL_get_address_size;
|
||||
if (xc_domctl(xenctx.xc_handle, &domctl) == 0)
|
||||
ctxt_word_size = guest_word_size = domctl.u.address_size.size / 8;
|
||||
unsigned int gw;
|
||||
if ( !xc_domain_get_guest_width(xenctx.xc_handle, xenctx.domid, &gw) )
|
||||
ctxt_word_size = guest_word_size = gw;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||