mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-29 19:21:31 -04:00
lguest: comment documentation update.
Took some cycles to re-read the Lguest Journey end-to-end, fix some rot and tighten some phrases. Only comments change. No new jokes, but a couple of recycled old jokes. Signed-off-by: Rusty Russell <[email protected]>
This commit is contained in:
parent
e18b094f0f
commit
a6bd8e1303
13 changed files with 208 additions and 142 deletions
|
|
@ -1,8 +1,6 @@
|
|||
/*P:400 This contains run_guest() which actually calls into the Host<->Guest
|
||||
* Switcher and analyzes the return, such as determining if the Guest wants the
|
||||
* Host to do something. This file also contains useful helper routines, and a
|
||||
* couple of non-obvious setup and teardown pieces which were implemented after
|
||||
* days of debugging pain. :*/
|
||||
* Host to do something. This file also contains useful helper routines. :*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/stringify.h>
|
||||
#include <linux/stddef.h>
|
||||
|
|
@ -49,8 +47,8 @@ static __init int map_switcher(void)
|
|||
* easy.
|
||||
*/
|
||||
|
||||
/* We allocate an array of "struct page"s. map_vm_area() wants the
|
||||
* pages in this form, rather than just an array of pointers. */
|
||||
/* We allocate an array of struct page pointers. map_vm_area() wants
|
||||
* this, rather than just an array of pages. */
|
||||
switcher_page = kmalloc(sizeof(switcher_page[0])*TOTAL_SWITCHER_PAGES,
|
||||
GFP_KERNEL);
|
||||
if (!switcher_page) {
|
||||
|
|
@ -172,7 +170,7 @@ void __lgread(struct lg_cpu *cpu, void *b, unsigned long addr, unsigned bytes)
|
|||
}
|
||||
}
|
||||
|
||||
/* This is the write (copy into guest) version. */
|
||||
/* This is the write (copy into Guest) version. */
|
||||
void __lgwrite(struct lg_cpu *cpu, unsigned long addr, const void *b,
|
||||
unsigned bytes)
|
||||
{
|
||||
|
|
@ -209,9 +207,9 @@ int run_guest(struct lg_cpu *cpu, unsigned long __user *user)
|
|||
if (cpu->break_out)
|
||||
return -EAGAIN;
|
||||
|
||||
/* Check if there are any interrupts which can be delivered
|
||||
* now: if so, this sets up the hander to be executed when we
|
||||
* next run the Guest. */
|
||||
/* Check if there are any interrupts which can be delivered now:
|
||||
* if so, this sets up the hander to be executed when we next
|
||||
* run the Guest. */
|
||||
maybe_do_interrupt(cpu);
|
||||
|
||||
/* All long-lived kernel loops need to check with this horrible
|
||||
|
|
@ -246,8 +244,10 @@ int run_guest(struct lg_cpu *cpu, unsigned long __user *user)
|
|||
lguest_arch_handle_trap(cpu);
|
||||
}
|
||||
|
||||
/* Special case: Guest is 'dead' but wants a reboot. */
|
||||
if (cpu->lg->dead == ERR_PTR(-ERESTART))
|
||||
return -ERESTART;
|
||||
|
||||
/* The Guest is dead => "No such file or directory" */
|
||||
return -ENOENT;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
#include "lg.h"
|
||||
|
||||
/*H:120 This is the core hypercall routine: where the Guest gets what it wants.
|
||||
* Or gets killed. Or, in the case of LHCALL_CRASH, both. */
|
||||
* Or gets killed. Or, in the case of LHCALL_SHUTDOWN, both. */
|
||||
static void do_hcall(struct lg_cpu *cpu, struct hcall_args *args)
|
||||
{
|
||||
switch (args->arg0) {
|
||||
|
|
@ -190,6 +190,13 @@ static void initialize(struct lg_cpu *cpu)
|
|||
* pagetable. */
|
||||
guest_pagetable_clear_all(cpu);
|
||||
}
|
||||
/*:*/
|
||||
|
||||
/*M:013 If a Guest reads from a page (so creates a mapping) that it has never
|
||||
* written to, and then the Launcher writes to it (ie. the output of a virtual
|
||||
* device), the Guest will still see the old page. In practice, this never
|
||||
* happens: why would the Guest read a page which it has never written to? But
|
||||
* a similar scenario might one day bite us, so it's worth mentioning. :*/
|
||||
|
||||
/*H:100
|
||||
* Hypercalls
|
||||
|
|
@ -227,7 +234,7 @@ void do_hypercalls(struct lg_cpu *cpu)
|
|||
* However, if we are signalled or the Guest sends I/O to the
|
||||
* Launcher, the run_guest() loop will exit without running the
|
||||
* Guest. When it comes back it would try to re-run the
|
||||
* hypercall. */
|
||||
* hypercall. Finding that bug sucked. */
|
||||
cpu->hcall = NULL;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,7 +144,6 @@ void maybe_do_interrupt(struct lg_cpu *cpu)
|
|||
if (copy_from_user(&blk, cpu->lg->lguest_data->blocked_interrupts,
|
||||
sizeof(blk)))
|
||||
return;
|
||||
|
||||
bitmap_andnot(blk, cpu->irqs_pending, blk, LGUEST_IRQS);
|
||||
|
||||
/* Find the first interrupt. */
|
||||
|
|
@ -237,9 +236,9 @@ void free_interrupts(void)
|
|||
clear_bit(syscall_vector, used_vectors);
|
||||
}
|
||||
|
||||
/*H:220 Now we've got the routines to deliver interrupts, delivering traps
|
||||
* like page fault is easy. The only trick is that Intel decided that some
|
||||
* traps should have error codes: */
|
||||
/*H:220 Now we've got the routines to deliver interrupts, delivering traps like
|
||||
* page fault is easy. The only trick is that Intel decided that some traps
|
||||
* should have error codes: */
|
||||
static int has_err(unsigned int trap)
|
||||
{
|
||||
return (trap == 8 || (trap >= 10 && trap <= 14) || trap == 17);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
/*P:050 Lguest guests use a very simple method to describe devices. It's a
|
||||
* series of device descriptors contained just above the top of normal
|
||||
* series of device descriptors contained just above the top of normal Guest
|
||||
* memory.
|
||||
*
|
||||
* We use the standard "virtio" device infrastructure, which provides us with a
|
||||
* console, a network and a block driver. Each one expects some configuration
|
||||
* information and a "virtqueue" mechanism to send and receive data. :*/
|
||||
* information and a "virtqueue" or two to send and receive data. :*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/lguest_launcher.h>
|
||||
|
|
@ -53,7 +53,7 @@ struct lguest_device {
|
|||
* Device configurations
|
||||
*
|
||||
* The configuration information for a device consists of one or more
|
||||
* virtqueues, a feature bitmaks, and some configuration bytes. The
|
||||
* virtqueues, a feature bitmap, and some configuration bytes. The
|
||||
* configuration bytes don't really matter to us: the Launcher sets them up, and
|
||||
* the driver will look at them during setup.
|
||||
*
|
||||
|
|
@ -179,7 +179,7 @@ struct lguest_vq_info
|
|||
};
|
||||
|
||||
/* When the virtio_ring code wants to prod the Host, it calls us here and we
|
||||
* make a hypercall. We hand the page number of the virtqueue so the Host
|
||||
* make a hypercall. We hand the physical address of the virtqueue so the Host
|
||||
* knows which virtqueue we're talking about. */
|
||||
static void lg_notify(struct virtqueue *vq)
|
||||
{
|
||||
|
|
@ -199,7 +199,8 @@ static void lg_notify(struct virtqueue *vq)
|
|||
* allocate its own pages and tell the Host where they are, but for lguest it's
|
||||
* simpler for the Host to simply tell us where the pages are.
|
||||
*
|
||||
* So we provide devices with a "find virtqueue and set it up" function. */
|
||||
* So we provide drivers with a "find the Nth virtqueue and set it up"
|
||||
* function. */
|
||||
static struct virtqueue *lg_find_vq(struct virtio_device *vdev,
|
||||
unsigned index,
|
||||
void (*callback)(struct virtqueue *vq))
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ static ssize_t read(struct file *file, char __user *user, size_t size,loff_t*o)
|
|||
if (current != cpu->tsk)
|
||||
return -EPERM;
|
||||
|
||||
/* If the guest is already dead, we indicate why */
|
||||
/* If the Guest is already dead, we indicate why */
|
||||
if (lg->dead) {
|
||||
size_t len;
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ static ssize_t read(struct file *file, char __user *user, size_t size,loff_t*o)
|
|||
return len;
|
||||
}
|
||||
|
||||
/* If we returned from read() last time because the Guest notified,
|
||||
/* If we returned from read() last time because the Guest sent I/O,
|
||||
* clear the flag. */
|
||||
if (cpu->pending_notify)
|
||||
cpu->pending_notify = 0;
|
||||
|
|
@ -97,14 +97,20 @@ static ssize_t read(struct file *file, char __user *user, size_t size,loff_t*o)
|
|||
return run_guest(cpu, (unsigned long __user *)user);
|
||||
}
|
||||
|
||||
/*L:025 This actually initializes a CPU. For the moment, a Guest is only
|
||||
* uniprocessor, so "id" is always 0. */
|
||||
static int lg_cpu_start(struct lg_cpu *cpu, unsigned id, unsigned long start_ip)
|
||||
{
|
||||
/* We have a limited number the number of CPUs in the lguest struct. */
|
||||
if (id >= NR_CPUS)
|
||||
return -EINVAL;
|
||||
|
||||
/* Set up this CPU's id, and pointer back to the lguest struct. */
|
||||
cpu->id = id;
|
||||
cpu->lg = container_of((cpu - id), struct lguest, cpus[0]);
|
||||
cpu->lg->nr_cpus++;
|
||||
|
||||
/* Each CPU has a timer it can set. */
|
||||
init_clockdev(cpu);
|
||||
|
||||
/* We need a complete page for the Guest registers: they are accessible
|
||||
|
|
@ -120,11 +126,11 @@ static int lg_cpu_start(struct lg_cpu *cpu, unsigned id, unsigned long start_ip)
|
|||
* address. */
|
||||
lguest_arch_setup_regs(cpu, start_ip);
|
||||
|
||||
/* Initialize the queue for the waker to wait on */
|
||||
/* Initialize the queue for the Waker to wait on */
|
||||
init_waitqueue_head(&cpu->break_wq);
|
||||
|
||||
/* We keep a pointer to the Launcher task (ie. current task) for when
|
||||
* other Guests want to wake this one (inter-Guest I/O). */
|
||||
* other Guests want to wake this one (eg. console input). */
|
||||
cpu->tsk = current;
|
||||
|
||||
/* We need to keep a pointer to the Launcher's memory map, because if
|
||||
|
|
@ -136,6 +142,7 @@ static int lg_cpu_start(struct lg_cpu *cpu, unsigned id, unsigned long start_ip)
|
|||
* when the same Guest runs on the same CPU twice. */
|
||||
cpu->last_pages = NULL;
|
||||
|
||||
/* No error == success. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -185,14 +192,13 @@ static int initialize(struct file *file, const unsigned long __user *input)
|
|||
lg->mem_base = (void __user *)(long)args[0];
|
||||
lg->pfn_limit = args[1];
|
||||
|
||||
/* This is the first cpu */
|
||||
/* This is the first cpu (cpu 0) and it will start booting at args[3] */
|
||||
err = lg_cpu_start(&lg->cpus[0], 0, args[3]);
|
||||
if (err)
|
||||
goto release_guest;
|
||||
|
||||
/* Initialize the Guest's shadow page tables, using the toplevel
|
||||
* address the Launcher gave us. This allocates memory, so can
|
||||
* fail. */
|
||||
* address the Launcher gave us. This allocates memory, so can fail. */
|
||||
err = init_guest_pagetable(lg, args[2]);
|
||||
if (err)
|
||||
goto free_regs;
|
||||
|
|
@ -218,11 +224,16 @@ unlock:
|
|||
/*L:010 The first operation the Launcher does must be a write. All writes
|
||||
* start with an unsigned long number: for the first write this must be
|
||||
* LHREQ_INITIALIZE to set up the Guest. After that the Launcher can use
|
||||
* writes of other values to send interrupts. */
|
||||
* writes of other values to send interrupts.
|
||||
*
|
||||
* Note that we overload the "offset" in the /dev/lguest file to indicate what
|
||||
* CPU number we're dealing with. Currently this is always 0, since we only
|
||||
* support uniprocessor Guests, but you can see the beginnings of SMP support
|
||||
* here. */
|
||||
static ssize_t write(struct file *file, const char __user *in,
|
||||
size_t size, loff_t *off)
|
||||
{
|
||||
/* Once the guest is initialized, we hold the "struct lguest" in the
|
||||
/* Once the Guest is initialized, we hold the "struct lguest" in the
|
||||
* file private data. */
|
||||
struct lguest *lg = file->private_data;
|
||||
const unsigned long __user *input = (const unsigned long __user *)in;
|
||||
|
|
@ -230,6 +241,7 @@ static ssize_t write(struct file *file, const char __user *in,
|
|||
struct lg_cpu *uninitialized_var(cpu);
|
||||
unsigned int cpu_id = *off;
|
||||
|
||||
/* The first value tells us what this request is. */
|
||||
if (get_user(req, input) != 0)
|
||||
return -EFAULT;
|
||||
input++;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* previous encounters. It's functional, and as neat as it can be in the
|
||||
* circumstances, but be wary, for these things are subtle and break easily.
|
||||
* The Guest provides a virtual to physical mapping, but we can neither trust
|
||||
* it nor use it: we verify and convert it here to point the hardware to the
|
||||
* actual Guest pages when running the Guest. :*/
|
||||
* it nor use it: we verify and convert it here then point the CPU to the
|
||||
* converted Guest pages when running the Guest. :*/
|
||||
|
||||
/* Copyright (C) Rusty Russell IBM Corporation 2006.
|
||||
* GPL v2 and any later version */
|
||||
|
|
@ -106,6 +106,11 @@ static unsigned long gpte_addr(pgd_t gpgd, unsigned long vaddr)
|
|||
BUG_ON(!(pgd_flags(gpgd) & _PAGE_PRESENT));
|
||||
return gpage + ((vaddr>>PAGE_SHIFT) % PTRS_PER_PTE) * sizeof(pte_t);
|
||||
}
|
||||
/*:*/
|
||||
|
||||
/*M:014 get_pfn is slow; it takes the mmap sem and calls get_user_pages. We
|
||||
* could probably try to grab batches of pages here as an optimization
|
||||
* (ie. pre-faulting). :*/
|
||||
|
||||
/*H:350 This routine takes a page number given by the Guest and converts it to
|
||||
* an actual, physical page number. It can fail for several reasons: the
|
||||
|
|
@ -113,8 +118,8 @@ static unsigned long gpte_addr(pgd_t gpgd, unsigned long vaddr)
|
|||
* and the page is read-only, or the write flag was set and the page was
|
||||
* shared so had to be copied, but we ran out of memory.
|
||||
*
|
||||
* This holds a reference to the page, so release_pte() is careful to
|
||||
* put that back. */
|
||||
* This holds a reference to the page, so release_pte() is careful to put that
|
||||
* back. */
|
||||
static unsigned long get_pfn(unsigned long virtpfn, int write)
|
||||
{
|
||||
struct page *page;
|
||||
|
|
@ -532,13 +537,13 @@ static void do_set_pte(struct lg_cpu *cpu, int idx,
|
|||
* all processes. So when the page table above that address changes, we update
|
||||
* all the page tables, not just the current one. This is rare.
|
||||
*
|
||||
* The benefit is that when we have to track a new page table, we can copy keep
|
||||
* all the kernel mappings. This speeds up context switch immensely. */
|
||||
* The benefit is that when we have to track a new page table, we can keep all
|
||||
* the kernel mappings. This speeds up context switch immensely. */
|
||||
void guest_set_pte(struct lg_cpu *cpu,
|
||||
unsigned long gpgdir, unsigned long vaddr, pte_t gpte)
|
||||
{
|
||||
/* Kernel mappings must be changed on all top levels. Slow, but
|
||||
* doesn't happen often. */
|
||||
/* Kernel mappings must be changed on all top levels. Slow, but doesn't
|
||||
* happen often. */
|
||||
if (vaddr >= cpu->lg->kernel_address) {
|
||||
unsigned int i;
|
||||
for (i = 0; i < ARRAY_SIZE(cpu->lg->pgdirs); i++)
|
||||
|
|
@ -704,12 +709,11 @@ static __init void populate_switcher_pte_page(unsigned int cpu,
|
|||
/* We've made it through the page table code. Perhaps our tired brains are
|
||||
* still processing the details, or perhaps we're simply glad it's over.
|
||||
*
|
||||
* If nothing else, note that all this complexity in juggling shadow page
|
||||
* tables in sync with the Guest's page tables is for one reason: for most
|
||||
* Guests this page table dance determines how bad performance will be. This
|
||||
* is why Xen uses exotic direct Guest pagetable manipulation, and why both
|
||||
* Intel and AMD have implemented shadow page table support directly into
|
||||
* hardware.
|
||||
* If nothing else, note that all this complexity in juggling shadow page tables
|
||||
* in sync with the Guest's page tables is for one reason: for most Guests this
|
||||
* page table dance determines how bad performance will be. This is why Xen
|
||||
* uses exotic direct Guest pagetable manipulation, and why both Intel and AMD
|
||||
* have implemented shadow page table support directly into hardware.
|
||||
*
|
||||
* There is just one file remaining in the Host. */
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@
|
|||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
/*P:450 This file contains the x86-specific lguest code. It used to be all
|
||||
* mixed in with drivers/lguest/core.c but several foolhardy code slashers
|
||||
* wrestled most of the dependencies out to here in preparation for porting
|
||||
* lguest to other architectures (see what I mean by foolhardy?).
|
||||
*
|
||||
* This also contains a couple of non-obvious setup and teardown pieces which
|
||||
* were implemented after days of debugging pain. :*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/start_kernel.h>
|
||||
#include <linux/string.h>
|
||||
|
|
@ -157,6 +164,8 @@ static void run_guest_once(struct lg_cpu *cpu, struct lguest_pages *pages)
|
|||
* also simplify copy_in_guest_info(). Note that we'd still need to restore
|
||||
* things when we exit to Launcher userspace, but that's fairly easy.
|
||||
*
|
||||
* We could also try using this hooks for PGE, but that might be too expensive.
|
||||
*
|
||||
* The hooks were designed for KVM, but we can also put them to good use. :*/
|
||||
|
||||
/*H:040 This is the i386-specific code to setup and run the Guest. Interrupts
|
||||
|
|
@ -182,7 +191,7 @@ void lguest_arch_run_guest(struct lg_cpu *cpu)
|
|||
* was doing. */
|
||||
run_guest_once(cpu, lguest_pages(raw_smp_processor_id()));
|
||||
|
||||
/* Note that the "regs" pointer contains two extra entries which are
|
||||
/* Note that the "regs" structure contains two extra entries which are
|
||||
* not really registers: a trap number which says what interrupt or
|
||||
* trap made the switcher code come back, and an error code which some
|
||||
* traps set. */
|
||||
|
|
@ -293,11 +302,10 @@ void lguest_arch_handle_trap(struct lg_cpu *cpu)
|
|||
break;
|
||||
case 14: /* We've intercepted a Page Fault. */
|
||||
/* The Guest accessed a virtual address that wasn't mapped.
|
||||
* This happens a lot: we don't actually set up most of the
|
||||
* page tables for the Guest at all when we start: as it runs
|
||||
* it asks for more and more, and we set them up as
|
||||
* required. In this case, we don't even tell the Guest that
|
||||
* the fault happened.
|
||||
* This happens a lot: we don't actually set up most of the page
|
||||
* tables for the Guest at all when we start: as it runs it asks
|
||||
* for more and more, and we set them up as required. In this
|
||||
* case, we don't even tell the Guest that the fault happened.
|
||||
*
|
||||
* The errcode tells whether this was a read or a write, and
|
||||
* whether kernel or userspace code. */
|
||||
|
|
@ -342,7 +350,7 @@ void lguest_arch_handle_trap(struct lg_cpu *cpu)
|
|||
if (!deliver_trap(cpu, cpu->regs->trapnum))
|
||||
/* If the Guest doesn't have a handler (either it hasn't
|
||||
* registered any yet, or it's one of the faults we don't let
|
||||
* it handle), it dies with a cryptic error message. */
|
||||
* it handle), it dies with this cryptic error message. */
|
||||
kill_guest(cpu, "unhandled trap %li at %#lx (%#lx)",
|
||||
cpu->regs->trapnum, cpu->regs->eip,
|
||||
cpu->regs->trapnum == 14 ? cpu->arch.last_pagefault
|
||||
|
|
@ -375,8 +383,8 @@ void __init lguest_arch_host_init(void)
|
|||
* The only exception is the interrupt handlers in switcher.S: their
|
||||
* addresses are placed in a table (default_idt_entries), so we need to
|
||||
* update the table with the new addresses. switcher_offset() is a
|
||||
* convenience function which returns the distance between the builtin
|
||||
* switcher code and the high-mapped copy we just made. */
|
||||
* convenience function which returns the distance between the
|
||||
* compiled-in switcher code and the high-mapped copy we just made. */
|
||||
for (i = 0; i < IDT_ENTRIES; i++)
|
||||
default_idt_entries[i] += switcher_offset();
|
||||
|
||||
|
|
@ -416,7 +424,7 @@ void __init lguest_arch_host_init(void)
|
|||
state->guest_gdt_desc.address = (long)&state->guest_gdt;
|
||||
|
||||
/* We know where we want the stack to be when the Guest enters
|
||||
* the switcher: in pages->regs. The stack grows upwards, so
|
||||
* the Switcher: in pages->regs. The stack grows upwards, so
|
||||
* we start it at the end of that structure. */
|
||||
state->guest_tss.sp0 = (long)(&pages->regs + 1);
|
||||
/* And this is the GDT entry to use for the stack: we keep a
|
||||
|
|
@ -513,8 +521,8 @@ int lguest_arch_init_hypercalls(struct lg_cpu *cpu)
|
|||
{
|
||||
u32 tsc_speed;
|
||||
|
||||
/* The pointer to the Guest's "struct lguest_data" is the only
|
||||
* argument. We check that address now. */
|
||||
/* The pointer to the Guest's "struct lguest_data" is the only argument.
|
||||
* We check that address now. */
|
||||
if (!lguest_address_ok(cpu->lg, cpu->hcall->arg1,
|
||||
sizeof(*cpu->lg->lguest_data)))
|
||||
return -EFAULT;
|
||||
|
|
@ -546,6 +554,7 @@ int lguest_arch_init_hypercalls(struct lg_cpu *cpu)
|
|||
|
||||
return 0;
|
||||
}
|
||||
/*:*/
|
||||
|
||||
/*L:030 lguest_arch_setup_regs()
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*P:900 This is the Switcher: code which sits at 0xFFC00000 to do the low-level
|
||||
* Guest<->Host switch. It is as simple as it can be made, but it's naturally
|
||||
* very specific to x86.
|
||||
/*P:900 This is the Switcher: code which sits at 0xFFC00000 astride both the
|
||||
* Host and Guest to do the low-level Guest<->Host switch. It is as simple as
|
||||
* it can be made, but it's naturally very specific to x86.
|
||||
*
|
||||
* You have now completed Preparation. If this has whet your appetite; if you
|
||||
* are feeling invigorated and refreshed then the next, more challenging stage
|
||||
|
|
@ -189,7 +189,7 @@ ENTRY(switch_to_guest)
|
|||
// Interrupts are turned back on: we are Guest.
|
||||
iret
|
||||
|
||||
// We treat two paths to switch back to the Host
|
||||
// We tread two paths to switch back to the Host
|
||||
// Yet both must save Guest state and restore Host
|
||||
// So we put the routine in a macro.
|
||||
#define SWITCH_TO_HOST \
|
||||
|
|
|
|||
Loading…
Reference in a new issue