Commit a6bd8e13 authored by Rusty Russell's avatar Rusty Russell

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: default avatarRusty Russell <rusty@rustcorp.com.au>
parent e18b094f
/*P:100 This is the Launcher code, a simple program which lays out the /*P:100 This is the Launcher code, a simple program which lays out the
* "physical" memory for the new Guest by mapping the kernel image and the * "physical" memory for the new Guest by mapping the kernel image and
* virtual devices, then reads repeatedly from /dev/lguest to run the Guest. * the virtual devices, then opens /dev/lguest to tell the kernel
:*/ * about the Guest and control it. :*/
#define _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE
#define _GNU_SOURCE #define _GNU_SOURCE
#include <stdio.h> #include <stdio.h>
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
#include "linux/virtio_console.h" #include "linux/virtio_console.h"
#include "linux/virtio_ring.h" #include "linux/virtio_ring.h"
#include "asm-x86/bootparam.h" #include "asm-x86/bootparam.h"
/*L:110 We can ignore the 38 include files we need for this program, but I do /*L:110 We can ignore the 39 include files we need for this program, but I do
* want to draw attention to the use of kernel-style types. * want to draw attention to the use of kernel-style types.
* *
* As Linus said, "C is a Spartan language, and so should your naming be." I * As Linus said, "C is a Spartan language, and so should your naming be." I
...@@ -320,7 +320,7 @@ static unsigned long map_elf(int elf_fd, const Elf32_Ehdr *ehdr) ...@@ -320,7 +320,7 @@ static unsigned long map_elf(int elf_fd, const Elf32_Ehdr *ehdr)
err(1, "Reading program headers"); err(1, "Reading program headers");
/* Try all the headers: there are usually only three. A read-only one, /* Try all the headers: there are usually only three. A read-only one,
* a read-write one, and a "note" section which isn't loadable. */ * a read-write one, and a "note" section which we don't load. */
for (i = 0; i < ehdr->e_phnum; i++) { for (i = 0; i < ehdr->e_phnum; i++) {
/* If this isn't a loadable segment, we ignore it */ /* If this isn't a loadable segment, we ignore it */
if (phdr[i].p_type != PT_LOAD) if (phdr[i].p_type != PT_LOAD)
...@@ -387,7 +387,7 @@ static unsigned long load_kernel(int fd) ...@@ -387,7 +387,7 @@ static unsigned long load_kernel(int fd)
if (memcmp(hdr.e_ident, ELFMAG, SELFMAG) == 0) if (memcmp(hdr.e_ident, ELFMAG, SELFMAG) == 0)
return map_elf(fd, &hdr); return map_elf(fd, &hdr);
/* Otherwise we assume it's a bzImage, and try to unpack it */ /* Otherwise we assume it's a bzImage, and try to load it. */
return load_bzimage(fd); return load_bzimage(fd);
} }
...@@ -433,12 +433,12 @@ static unsigned long load_initrd(const char *name, unsigned long mem) ...@@ -433,12 +433,12 @@ static unsigned long load_initrd(const char *name, unsigned long mem)
return len; return len;
} }
/* Once we know how much memory we have, we can construct simple linear page /* Once we know how much memory we have we can construct simple linear page
* tables which set virtual == physical which will get the Guest far enough * tables which set virtual == physical which will get the Guest far enough
* into the boot to create its own. * into the boot to create its own.
* *
* We lay them out of the way, just below the initrd (which is why we need to * We lay them out of the way, just below the initrd (which is why we need to
* know its size). */ * know its size here). */
static unsigned long setup_pagetables(unsigned long mem, static unsigned long setup_pagetables(unsigned long mem,
unsigned long initrd_size) unsigned long initrd_size)
{ {
...@@ -850,7 +850,8 @@ static void handle_console_output(int fd, struct virtqueue *vq) ...@@ -850,7 +850,8 @@ static void handle_console_output(int fd, struct virtqueue *vq)
* *
* Handling output for network is also simple: we get all the output buffers * Handling output for network is also simple: we get all the output buffers
* and write them (ignoring the first element) to this device's file descriptor * and write them (ignoring the first element) to this device's file descriptor
* (stdout). */ * (/dev/net/tun).
*/
static void handle_net_output(int fd, struct virtqueue *vq) static void handle_net_output(int fd, struct virtqueue *vq)
{ {
unsigned int head, out, in; unsigned int head, out, in;
...@@ -924,7 +925,7 @@ static void enable_fd(int fd, struct virtqueue *vq) ...@@ -924,7 +925,7 @@ static void enable_fd(int fd, struct virtqueue *vq)
write(waker_fd, &vq->dev->fd, sizeof(vq->dev->fd)); write(waker_fd, &vq->dev->fd, sizeof(vq->dev->fd));
} }
/* Resetting a device is fairly easy. */ /* When the Guest asks us to reset a device, it's is fairly easy. */
static void reset_device(struct device *dev) static void reset_device(struct device *dev)
{ {
struct virtqueue *vq; struct virtqueue *vq;
...@@ -1003,8 +1004,8 @@ static void handle_input(int fd) ...@@ -1003,8 +1004,8 @@ static void handle_input(int fd)
if (select(devices.max_infd+1, &fds, NULL, NULL, &poll) == 0) if (select(devices.max_infd+1, &fds, NULL, NULL, &poll) == 0)
break; break;
/* Otherwise, call the device(s) which have readable /* Otherwise, call the device(s) which have readable file
* file descriptors and a method of handling them. */ * descriptors and a method of handling them. */
for (i = devices.dev; i; i = i->next) { for (i = devices.dev; i; i = i->next) {
if (i->handle_input && FD_ISSET(i->fd, &fds)) { if (i->handle_input && FD_ISSET(i->fd, &fds)) {
int dev_fd; int dev_fd;
...@@ -1015,8 +1016,7 @@ static void handle_input(int fd) ...@@ -1015,8 +1016,7 @@ static void handle_input(int fd)
* should no longer service it. Networking and * should no longer service it. Networking and
* console do this when there's no input * console do this when there's no input
* buffers to deliver into. Console also uses * buffers to deliver into. Console also uses
* it when it discovers that stdin is * it when it discovers that stdin is closed. */
* closed. */
FD_CLR(i->fd, &devices.infds); FD_CLR(i->fd, &devices.infds);
/* Tell waker to ignore it too, by sending a /* Tell waker to ignore it too, by sending a
* negative fd number (-1, since 0 is a valid * negative fd number (-1, since 0 is a valid
...@@ -1033,7 +1033,8 @@ static void handle_input(int fd) ...@@ -1033,7 +1033,8 @@ static void handle_input(int fd)
* *
* All devices need a descriptor so the Guest knows it exists, and a "struct * All devices need a descriptor so the Guest knows it exists, and a "struct
* device" so the Launcher can keep track of it. We have common helper * device" so the Launcher can keep track of it. We have common helper
* routines to allocate and manage them. */ * routines to allocate and manage them.
*/
/* The layout of the device page is a "struct lguest_device_desc" followed by a /* The layout of the device page is a "struct lguest_device_desc" followed by a
* number of virtqueue descriptors, then two sets of feature bits, then an * number of virtqueue descriptors, then two sets of feature bits, then an
...@@ -1078,7 +1079,7 @@ static void add_virtqueue(struct device *dev, unsigned int num_descs, ...@@ -1078,7 +1079,7 @@ static void add_virtqueue(struct device *dev, unsigned int num_descs,
struct virtqueue **i, *vq = malloc(sizeof(*vq)); struct virtqueue **i, *vq = malloc(sizeof(*vq));
void *p; void *p;
/* First we need some pages for this virtqueue. */ /* First we need some memory for this virtqueue. */
pages = (vring_size(num_descs, getpagesize()) + getpagesize() - 1) pages = (vring_size(num_descs, getpagesize()) + getpagesize() - 1)
/ getpagesize(); / getpagesize();
p = get_pages(pages); p = get_pages(pages);
...@@ -1122,7 +1123,7 @@ static void add_virtqueue(struct device *dev, unsigned int num_descs, ...@@ -1122,7 +1123,7 @@ static void add_virtqueue(struct device *dev, unsigned int num_descs,
} }
/* The first half of the feature bitmask is for us to advertise features. The /* The first half of the feature bitmask is for us to advertise features. The
* second half if for the Guest to accept features. */ * second half is for the Guest to accept features. */
static void add_feature(struct device *dev, unsigned bit) static void add_feature(struct device *dev, unsigned bit)
{ {
u8 *features = get_feature_bits(dev); u8 *features = get_feature_bits(dev);
...@@ -1151,7 +1152,9 @@ static void set_config(struct device *dev, unsigned len, const void *conf) ...@@ -1151,7 +1152,9 @@ static void set_config(struct device *dev, unsigned len, const void *conf)
} }
/* This routine does all the creation and setup of a new device, including /* This routine does all the creation and setup of a new device, including
* calling new_dev_desc() to allocate the descriptor and device memory. */ * calling new_dev_desc() to allocate the descriptor and device memory.
*
* See what I mean about userspace being boring? */
static struct device *new_device(const char *name, u16 type, int fd, static struct device *new_device(const char *name, u16 type, int fd,
bool (*handle_input)(int, struct device *)) bool (*handle_input)(int, struct device *))
{ {
...@@ -1492,7 +1495,10 @@ static int io_thread(void *_dev) ...@@ -1492,7 +1495,10 @@ static int io_thread(void *_dev)
while (read(vblk->workpipe[0], &c, 1) == 1) { while (read(vblk->workpipe[0], &c, 1) == 1) {
/* We acknowledge each request immediately to reduce latency, /* We acknowledge each request immediately to reduce latency,
* rather than waiting until we've done them all. I haven't * rather than waiting until we've done them all. I haven't
* measured to see if it makes any difference. */ * measured to see if it makes any difference.
*
* That would be an interesting test, wouldn't it? You could
* also try having more than one I/O thread. */
while (service_io(dev)) while (service_io(dev))
write(vblk->done_fd, &c, 1); write(vblk->done_fd, &c, 1);
} }
...@@ -1500,7 +1506,7 @@ static int io_thread(void *_dev) ...@@ -1500,7 +1506,7 @@ static int io_thread(void *_dev)
} }
/* Now we've seen the I/O thread, we return to the Launcher to see what happens /* Now we've seen the I/O thread, we return to the Launcher to see what happens
* when the thread tells us it's completed some I/O. */ * when that thread tells us it's completed some I/O. */
static bool handle_io_finish(int fd, struct device *dev) static bool handle_io_finish(int fd, struct device *dev)
{ {
char c; char c;
...@@ -1572,11 +1578,12 @@ static void setup_block_file(const char *filename) ...@@ -1572,11 +1578,12 @@ static void setup_block_file(const char *filename)
* more work. */ * more work. */
pipe(vblk->workpipe); pipe(vblk->workpipe);
/* Create stack for thread and run it */ /* Create stack for thread and run it. Since stack grows upwards, we
* point the stack pointer to the end of this region. */
stack = malloc(32768); stack = malloc(32768);
/* SIGCHLD - We dont "wait" for our cloned thread, so prevent it from /* SIGCHLD - We dont "wait" for our cloned thread, so prevent it from
* becoming a zombie. */ * becoming a zombie. */
if (clone(io_thread, stack + 32768, CLONE_VM | SIGCHLD, dev) == -1) if (clone(io_thread, stack + 32768, CLONE_VM | SIGCHLD, dev) == -1)
err(1, "Creating clone"); err(1, "Creating clone");
/* We don't need to keep the I/O thread's end of the pipes open. */ /* We don't need to keep the I/O thread's end of the pipes open. */
...@@ -1586,14 +1593,14 @@ static void setup_block_file(const char *filename) ...@@ -1586,14 +1593,14 @@ static void setup_block_file(const char *filename)
verbose("device %u: virtblock %llu sectors\n", verbose("device %u: virtblock %llu sectors\n",
devices.device_num, le64_to_cpu(conf.capacity)); devices.device_num, le64_to_cpu(conf.capacity));
} }
/* That's the end of device setup. :*/ /* That's the end of device setup. */
/* Reboot */ /*L:230 Reboot is pretty easy: clean up and exec() the Launcher afresh. */
static void __attribute__((noreturn)) restart_guest(void) static void __attribute__((noreturn)) restart_guest(void)
{ {
unsigned int i; unsigned int i;
/* Closing pipes causes the waker thread and io_threads to die, and /* Closing pipes causes the Waker thread and io_threads to die, and
* closing /dev/lguest cleans up the Guest. Since we don't track all * closing /dev/lguest cleans up the Guest. Since we don't track all
* open fds, we simply close everything beyond stderr. */ * open fds, we simply close everything beyond stderr. */
for (i = 3; i < FD_SETSIZE; i++) for (i = 3; i < FD_SETSIZE; i++)
...@@ -1602,7 +1609,7 @@ static void __attribute__((noreturn)) restart_guest(void) ...@@ -1602,7 +1609,7 @@ static void __attribute__((noreturn)) restart_guest(void)
err(1, "Could not exec %s", main_args[0]); err(1, "Could not exec %s", main_args[0]);
} }
/*L:220 Finally we reach the core of the Launcher, which runs the Guest, serves /*L:220 Finally we reach the core of the Launcher which runs the Guest, serves
* its input and output, and finally, lays it to rest. */ * its input and output, and finally, lays it to rest. */
static void __attribute__((noreturn)) run_guest(int lguest_fd) static void __attribute__((noreturn)) run_guest(int lguest_fd)
{ {
...@@ -1643,7 +1650,7 @@ static void __attribute__((noreturn)) run_guest(int lguest_fd) ...@@ -1643,7 +1650,7 @@ static void __attribute__((noreturn)) run_guest(int lguest_fd)
err(1, "Resetting break"); err(1, "Resetting break");
} }
} }
/* /*L:240
* This is the end of the Launcher. The good news: we are over halfway * This is the end of the Launcher. The good news: we are over halfway
* through! The bad news: the most fiendish part of the code still lies ahead * through! The bad news: the most fiendish part of the code still lies ahead
* of us. * of us.
...@@ -1690,8 +1697,8 @@ int main(int argc, char *argv[]) ...@@ -1690,8 +1697,8 @@ int main(int argc, char *argv[])
* device receive input from a file descriptor, we keep an fdset * device receive input from a file descriptor, we keep an fdset
* (infds) and the maximum fd number (max_infd) with the head of the * (infds) and the maximum fd number (max_infd) with the head of the
* list. We also keep a pointer to the last device. Finally, we keep * list. We also keep a pointer to the last device. Finally, we keep
* the next interrupt number to hand out (1: remember that 0 is used by * the next interrupt number to use for devices (1: remember that 0 is
* the timer). */ * used by the timer). */
FD_ZERO(&devices.infds); FD_ZERO(&devices.infds);
devices.max_infd = -1; devices.max_infd = -1;
devices.lastdev = NULL; devices.lastdev = NULL;
...@@ -1792,8 +1799,8 @@ int main(int argc, char *argv[]) ...@@ -1792,8 +1799,8 @@ int main(int argc, char *argv[])
lguest_fd = tell_kernel(pgdir, start); lguest_fd = tell_kernel(pgdir, start);
/* We fork off a child process, which wakes the Launcher whenever one /* We fork off a child process, which wakes the Launcher whenever one
* of the input file descriptors needs attention. Otherwise we would * of the input file descriptors needs attention. We call this the
* run the Guest until it tries to output something. */ * Waker, and we'll cover it in a moment. */
waker_fd = setup_waker(lguest_fd); waker_fd = setup_waker(lguest_fd);
/* Finally, run the Guest. This doesn't return. */ /* Finally, run the Guest. This doesn't return. */
......
This diff is collapsed.
...@@ -5,13 +5,20 @@ ...@@ -5,13 +5,20 @@
#include <asm/thread_info.h> #include <asm/thread_info.h>
#include <asm/processor-flags.h> #include <asm/processor-flags.h>
/*G:020 This is where we begin: head.S notes that the boot header's platform /*G:020 Our story starts with the kernel booting into startup_32 in
* type field is "1" (lguest), so calls us here. * arch/x86/kernel/head_32.S. It expects a boot header, which is created by
* the bootloader (the Launcher in our case).
*
* The startup_32 function does very little: it clears the uninitialized global
* C variables which we expect to be zero (ie. BSS) and then copies the boot
* header and kernel command line somewhere safe. Finally it checks the
* 'hardware_subarch' field. This was introduced in 2.6.24 for lguest and Xen:
* if it's set to '1' (lguest's assigned number), then it calls us here.
* *
* WARNING: be very careful here! We're running at addresses equal to physical * WARNING: be very careful here! We're running at addresses equal to physical
* addesses (around 0), not above PAGE_OFFSET as most code expectes * addesses (around 0), not above PAGE_OFFSET as most code expectes
* (eg. 0xC0000000). Jumps are relative, so they're OK, but we can't touch any * (eg. 0xC0000000). Jumps are relative, so they're OK, but we can't touch any
* data. * data without remembering to subtract __PAGE_OFFSET!
* *
* The .section line puts this code in .init.text so it will be discarded after * The .section line puts this code in .init.text so it will be discarded after
* boot. */ * boot. */
...@@ -24,7 +31,7 @@ ENTRY(lguest_entry) ...@@ -24,7 +31,7 @@ ENTRY(lguest_entry)
int $LGUEST_TRAP_ENTRY int $LGUEST_TRAP_ENTRY
/* The Host put the toplevel pagetable in lguest_data.pgdir. The movsl /* The Host put the toplevel pagetable in lguest_data.pgdir. The movsl
* instruction uses %esi implicitly as the source for the copy we' * instruction uses %esi implicitly as the source for the copy we're
* about to do. */ * about to do. */
movl lguest_data - __PAGE_OFFSET + LGUEST_DATA_pgdir, %esi movl lguest_data - __PAGE_OFFSET + LGUEST_DATA_pgdir, %esi
......
/*P:400 This contains run_guest() which actually calls into the Host<->Guest /*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 * 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 * Host to do something. This file also contains useful helper routines. :*/
* couple of non-obvious setup and teardown pieces which were implemented after
* days of debugging pain. :*/
#include <linux/module.h> #include <linux/module.h>
#include <linux/stringify.h> #include <linux/stringify.h>
#include <linux/stddef.h> #include <linux/stddef.h>
...@@ -49,8 +47,8 @@ static __init int map_switcher(void) ...@@ -49,8 +47,8 @@ static __init int map_switcher(void)
* easy. * easy.
*/ */
/* We allocate an array of "struct page"s. map_vm_area() wants the /* We allocate an array of struct page pointers. map_vm_area() wants
* pages in this form, rather than just an array of pointers. */ * this, rather than just an array of pages. */
switcher_page = kmalloc(sizeof(switcher_page[0])*TOTAL_SWITCHER_PAGES, switcher_page = kmalloc(sizeof(switcher_page[0])*TOTAL_SWITCHER_PAGES,
GFP_KERNEL); GFP_KERNEL);
if (!switcher_page) { if (!switcher_page) {
...@@ -172,7 +170,7 @@ void __lgread(struct lg_cpu *cpu, void *b, unsigned long addr, unsigned bytes) ...@@ -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, void __lgwrite(struct lg_cpu *cpu, unsigned long addr, const void *b,
unsigned bytes) unsigned bytes)
{ {
...@@ -209,9 +207,9 @@ int run_guest(struct lg_cpu *cpu, unsigned long __user *user) ...@@ -209,9 +207,9 @@ int run_guest(struct lg_cpu *cpu, unsigned long __user *user)
if (cpu->break_out) if (cpu->break_out)
return -EAGAIN; return -EAGAIN;
/* Check if there are any interrupts which can be delivered /* Check if there are any interrupts which can be delivered now:
* now: if so, this sets up the hander to be executed when we * if so, this sets up the hander to be executed when we next
* next run the Guest. */ * run the Guest. */
maybe_do_interrupt(cpu); maybe_do_interrupt(cpu);
/* All long-lived kernel loops need to check with this horrible /* 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) ...@@ -246,8 +244,10 @@ int run_guest(struct lg_cpu *cpu, unsigned long __user *user)
lguest_arch_handle_trap(cpu); lguest_arch_handle_trap(cpu);
} }
/* Special case: Guest is 'dead' but wants a reboot. */
if (cpu->lg->dead == ERR_PTR(-ERESTART)) if (cpu->lg->dead == ERR_PTR(-ERESTART))
return -ERESTART; return -ERESTART;
/* The Guest is dead => "No such file or directory" */ /* The Guest is dead => "No such file or directory" */
return -ENOENT; return -ENOENT;
} }
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
#include "lg.h" #include "lg.h"
/*H:120 This is the core hypercall routine: where the Guest gets what it wants. /*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) static void do_hcall(struct lg_cpu *cpu, struct hcall_args *args)
{ {
switch (args->arg0) { switch (args->arg0) {
...@@ -190,6 +190,13 @@ static void initialize(struct lg_cpu *cpu) ...@@ -190,6 +190,13 @@ static void initialize(struct lg_cpu *cpu)
* pagetable. */ * pagetable. */
guest_pagetable_clear_all(cpu); 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 /*H:100
* Hypercalls * Hypercalls
...@@ -227,7 +234,7 @@ void do_hypercalls(struct lg_cpu *cpu) ...@@ -227,7 +234,7 @@ void do_hypercalls(struct lg_cpu *cpu)
* However, if we are signalled or the Guest sends I/O to the * However, if we are signalled or the Guest sends I/O to the
* Launcher, the run_guest() loop will exit without running the * Launcher, the run_guest() loop will exit without running the
* Guest. When it comes back it would try to re-run the * Guest. When it comes back it would try to re-run the
* hypercall. */ * hypercall. Finding that bug sucked. */
cpu->hcall = NULL; cpu->hcall = NULL;
} }
} }
......
...@@ -144,7 +144,6 @@ void maybe_do_interrupt(struct lg_cpu *cpu) ...@@ -144,7 +144,6 @@ void maybe_do_interrupt(struct lg_cpu *cpu)
if (copy_from_user(&blk, cpu->lg->lguest_data->blocked_interrupts, if (copy_from_user(&blk, cpu->lg->lguest_data->blocked_interrupts,
sizeof(blk))) sizeof(blk)))
return; return;
bitmap_andnot(blk, cpu->irqs_pending, blk, LGUEST_IRQS); bitmap_andnot(blk, cpu->irqs_pending, blk, LGUEST_IRQS);
/* Find the first interrupt. */ /* Find the first interrupt. */
...@@ -237,9 +236,9 @@ void free_interrupts(void) ...@@ -237,9 +236,9 @@ void free_interrupts(void)
clear_bit(syscall_vector, used_vectors); clear_bit(syscall_vector, used_vectors);
} }
/*H:220 Now we've got the routines to deliver interrupts, delivering traps /*H:220 Now we've got the routines to deliver interrupts, delivering traps like
* like page fault is easy. The only trick is that Intel decided that some * page fault is easy. The only trick is that Intel decided that some traps
* traps should have error codes: */ * should have error codes: */
static int has_err(unsigned int trap) static int has_err(unsigned int trap)
{ {
return (trap == 8 || (trap >= 10 && trap <= 14) || trap == 17); return (trap == 8 || (trap >= 10 && trap <= 14) || trap == 17);
......
/*P:050 Lguest guests use a very simple method to describe devices. It's a /*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. * memory.
* *
* We use the standard "virtio" device infrastructure, which provides us with a * We use the standard "virtio" device infrastructure, which provides us with a
* console, a network and a block driver. Each one expects some configuration * 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/init.h>
#include <linux/bootmem.h> #include <linux/bootmem.h>
#include <linux/lguest_launcher.h> #include <linux/lguest_launcher.h>
...@@ -53,7 +53,7 @@ struct lguest_device { ...@@ -53,7 +53,7 @@ struct lguest_device {
* Device configurations * Device configurations
* *
* The configuration information for a device consists of one or more * 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 * configuration bytes don't really matter to us: the Launcher sets them up, and
* the driver will look at them during setup. * the driver will look at them during setup.
* *
...@@ -179,7 +179,7 @@ struct lguest_vq_info ...@@ -179,7 +179,7 @@ struct lguest_vq_info
}; };
/* When the virtio_ring code wants to prod the Host, it calls us here and we /* 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. */ * knows which virtqueue we're talking about. */
static void lg_notify(struct virtqueue *vq) static void lg_notify(struct virtqueue *vq)
{ {
...@@ -199,7 +199,8 @@ 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 * 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. * 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, static struct virtqueue *lg_find_vq(struct virtio_device *vdev,
unsigned index, unsigned index,
void (*callback)(struct virtqueue *vq)) 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) ...@@ -73,7 +73,7 @@ static ssize_t read(struct file *file, char __user *user, size_t size,loff_t*o)
if (current != cpu->tsk) if (current != cpu->tsk)
return -EPERM; return -EPERM;
/* If the guest is already dead, we indicate why */ /* If the Guest is already dead, we indicate why */
if (lg->dead) { if (lg->dead) {
size_t len; size_t len;
...@@ -88,7 +88,7 @@ static ssize_t read(struct file *file, char __user *user, size_t size,loff_t*o) ...@@ -88,7 +88,7 @@ static ssize_t read(struct file *file, char __user *user, size_t size,loff_t*o)
return len; 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. */ * clear the flag. */
if (cpu->pending_notify) if (cpu->pending_notify)
cpu->pending_notify = 0; cpu->pending_notify = 0;
...@@ -97,14 +97,20 @@ static ssize_t read(struct file *file, char __user *user, size_t size,loff_t*o) ...@@ -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); 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) 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) if (id >= NR_CPUS)
return -EINVAL; return -EINVAL;
/* Set up this CPU's id, and pointer back to the lguest struct. */
cpu->id = id; cpu->id = id;
cpu->lg = container_of((cpu - id), struct lguest, cpus[0]); cpu->lg = container_of((cpu - id), struct lguest, cpus[0]);
cpu->lg->nr_cpus++; cpu->lg->nr_cpus++;
/* Each CPU has a timer it can set. */
init_clockdev(cpu); init_clockdev(cpu);
/* We need a complete page for the Guest registers: they are accessible /* 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) ...@@ -120,11 +126,11 @@ static int lg_cpu_start(struct lg_cpu *cpu, unsigned id, unsigned long start_ip)
* address. */ * address. */
lguest_arch_setup_regs(cpu, start_ip); 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); init_waitqueue_head(&cpu->break_wq);
/* We keep a pointer to the Launcher task (ie. current task) for when /* 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; cpu->tsk = current;
/* We need to keep a pointer to the Launcher's memory map, because if /* 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) ...@@ -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. */ * when the same Guest runs on the same CPU twice. */
cpu->last_pages = NULL; cpu->last_pages = NULL;
/* No error == success. */
return 0; return 0;
} }
...@@ -185,14 +192,13 @@ static int initialize(struct file *file, const unsigned long __user *input) ...@@ -185,14 +192,13 @@ static int initialize(struct file *file, const unsigned long __user *input)
lg->mem_base = (void __user *)(long)args[0]; lg->mem_base = (void __user *)(long)args[0];
lg->pfn_limit = args[1]; 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]); err = lg_cpu_start(&lg->cpus[0], 0, args[3]);
if (err) if (err)
goto release_guest; goto release_guest;
/* Initialize the Guest's shadow page tables, using the toplevel /* Initialize the Guest's shadow page tables, using the toplevel
* address the Launcher gave us. This allocates memory, so can * address the Launcher gave us. This allocates memory, so can fail. */
* fail. */
err = init_guest_pagetable(lg, args[2]); err = init_guest_pagetable(lg, args[2]);
if (err) if (err)
goto free_regs; goto free_regs;
...@@ -218,11 +224,16 @@ unlock: ...@@ -218,11 +224,16 @@ unlock:
/*L:010 The first operation the Launcher does must be a write. All writes /*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 * 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 * 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, static ssize_t write(struct file *file, const char __user *in,
size_t size, loff_t *off) 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. */ * file private data. */
struct lguest *lg = file->private_data; struct lguest *lg = file->private_data;
const unsigned long __user *input = (const unsigned long __user *)in; 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, ...@@ -230,6 +241,7 @@ static ssize_t write(struct file *file, const char __user *in,
struct lg_cpu *uninitialized_var(cpu); struct lg_cpu *uninitialized_var(cpu);
unsigned int cpu_id = *off; unsigned int cpu_id = *off;
/* The first value tells us what this request is. */
if (get_user(req, input) != 0) if (get_user(req, input) != 0)
return -EFAULT; return -EFAULT;
input++; input++;
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
* previous encounters. It's functional, and as neat as it can be in the * 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. * 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 * 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 * it nor use it: we verify and convert it here then point the CPU to the
* actual Guest pages when running the Guest. :*/ * converted Guest pages when running the Guest. :*/
/* Copyright (C) Rusty Russell IBM Corporation 2006. /* Copyright (C) Rusty Russell IBM Corporation 2006.
* GPL v2 and any later version */ * GPL v2 and any later version */
...@@ -106,6 +106,11 @@ static unsigned long gpte_addr(pgd_t gpgd, unsigned long vaddr) ...@@ -106,6 +106,11 @@ static unsigned long gpte_addr(pgd_t gpgd, unsigned long vaddr)
BUG_ON(!(pgd_flags(gpgd) & _PAGE_PRESENT)); BUG_ON(!(pgd_flags(gpgd) & _PAGE_PRESENT));
return gpage + ((vaddr>>PAGE_SHIFT) % PTRS_PER_PTE) * sizeof(pte_t); 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 /*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 * 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) ...@@ -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 * 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. * 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 * This holds a reference to the page, so release_pte() is careful to put that
* put that back. */ * back. */
static unsigned long get_pfn(unsigned long virtpfn, int write) static unsigned long get_pfn(unsigned long virtpfn, int write)
{ {
struct page *page; struct page *page;
...@@ -532,13 +537,13 @@ static void do_set_pte(struct lg_cpu *cpu, int idx, ...@@ -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 processes. So when the page table above that address changes, we update
* all the page tables, not just the current one. This is rare. * 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 * The benefit is that when we have to track a new page table, we can keep all
* all the kernel mappings. This speeds up context switch immensely. */ * the kernel mappings. This speeds up context switch immensely. */
void guest_set_pte(struct lg_cpu *cpu, void guest_set_pte(struct lg_cpu *cpu,
unsigned long gpgdir, unsigned long vaddr, pte_t gpte) unsigned long gpgdir, unsigned long vaddr, pte_t gpte)
{ {
/* Kernel mappings must be changed on all top levels. Slow, but /* Kernel mappings must be changed on all top levels. Slow, but doesn't
* doesn't happen often. */ * happen often. */
if (vaddr >= cpu->lg->kernel_address) { if (vaddr >= cpu->lg->kernel_address) {
unsigned int i; unsigned int i;
for (i = 0; i < ARRAY_SIZE(cpu->lg->pgdirs); 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, ...@@ -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 /* 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. * 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 * If nothing else, note that all this complexity in juggling shadow page tables
* tables in sync with the Guest's page tables is for one reason: for most * in sync with the Guest's page tables is for one reason: for most Guests this
* Guests this page table dance determines how bad performance will be. This * page table dance determines how bad performance will be. This is why Xen
* is why Xen uses exotic direct Guest pagetable manipulation, and why both * uses exotic direct Guest pagetable manipulation, and why both Intel and AMD
* Intel and AMD have implemented shadow page table support directly into * have implemented shadow page table support directly into hardware.
* hardware.
* *
* There is just one file remaining in the Host. */ * There is just one file remaining in the Host. */
......
...@@ -17,6 +17,13 @@ ...@@ -17,6 +17,13 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * 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/kernel.h>
#include <linux/start_kernel.h> #include <linux/start_kernel.h>
#include <linux/string.h> #include <linux/string.h>
...@@ -157,6 +164,8 @@ static void run_guest_once(struct lg_cpu *cpu, struct lguest_pages *pages) ...@@ -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 * 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. * 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. :*/ * 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 /*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) ...@@ -182,7 +191,7 @@ void lguest_arch_run_guest(struct lg_cpu *cpu)
* was doing. */ * was doing. */
run_guest_once(cpu, lguest_pages(raw_smp_processor_id())); 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 * not really registers: a trap number which says what interrupt or
* trap made the switcher code come back, and an error code which some * trap made the switcher code come back, and an error code which some
* traps set. */ * traps set. */
...@@ -293,11 +302,10 @@ void lguest_arch_handle_trap(struct lg_cpu *cpu) ...@@ -293,11 +302,10 @@ void lguest_arch_handle_trap(struct lg_cpu *cpu)
break; break;
case 14: /* We've intercepted a Page Fault. */ case 14: /* We've intercepted a Page Fault. */
/* The Guest accessed a virtual address that wasn't mapped. /* The Guest accessed a virtual address that wasn't mapped.
* This happens a lot: we don't actually set up most of the * This happens a lot: we don't actually set up most of the page
* page tables for the Guest at all when we start: as it runs * tables for the Guest at all when we start: as it runs it asks
* it asks for more and more, and we set them up as * for more and more, and we set them up as required. In this
* required. In this case, we don't even tell the Guest that * case, we don't even tell the Guest that the fault happened.
* the fault happened.
* *
* The errcode tells whether this was a read or a write, and * The errcode tells whether this was a read or a write, and
* whether kernel or userspace code. */ * whether kernel or userspace code. */
...@@ -342,7 +350,7 @@ void lguest_arch_handle_trap(struct lg_cpu *cpu) ...@@ -342,7 +350,7 @@ void lguest_arch_handle_trap(struct lg_cpu *cpu)
if (!deliver_trap(cpu, cpu->regs->trapnum)) if (!deliver_trap(cpu, cpu->regs->trapnum))
/* If the Guest doesn't have a handler (either it hasn't /* 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 * 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)", kill_guest(cpu, "unhandled trap %li at %#lx (%#lx)",
cpu->regs->trapnum, cpu->regs->eip, cpu->regs->trapnum, cpu->regs->eip,
cpu->regs->trapnum == 14 ? cpu->arch.last_pagefault cpu->regs->trapnum == 14 ? cpu->arch.last_pagefault
...@@ -375,8 +383,8 @@ void __init lguest_arch_host_init(void) ...@@ -375,8 +383,8 @@ void __init lguest_arch_host_init(void)
* The only exception is the interrupt handlers in switcher.S: their * The only exception is the interrupt handlers in switcher.S: their
* addresses are placed in a table (default_idt_entries), so we need to * addresses are placed in a table (default_idt_entries), so we need to
* update the table with the new addresses. switcher_offset() is a * update the table with the new addresses. switcher_offset() is a
* convenience function which returns the distance between the builtin * convenience function which returns the distance between the
* switcher code and the high-mapped copy we just made. */ * compiled-in switcher code and the high-mapped copy we just made. */
for (i = 0; i < IDT_ENTRIES; i++) for (i = 0; i < IDT_ENTRIES; i++)
default_idt_entries[i] += switcher_offset(); default_idt_entries[i] += switcher_offset();
...@@ -416,7 +424,7 @@ void __init lguest_arch_host_init(void) ...@@ -416,7 +424,7 @@ void __init lguest_arch_host_init(void)
state->guest_gdt_desc.address = (long)&state->guest_gdt; state->guest_gdt_desc.address = (long)&state->guest_gdt;
/* We know where we want the stack to be when the Guest enters /* 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. */ * we start it at the end of that structure. */
state->guest_tss.sp0 = (long)(&pages->regs + 1); state->guest_tss.sp0 = (long)(&pages->regs + 1);
/* And this is the GDT entry to use for the stack: we keep a /* 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) ...@@ -513,8 +521,8 @@ int lguest_arch_init_hypercalls(struct lg_cpu *cpu)
{ {
u32 tsc_speed; u32 tsc_speed;
/* The pointer to the Guest's "struct lguest_data" is the only /* The pointer to the Guest's "struct lguest_data" is the only argument.
* argument. We check that address now. */ * We check that address now. */
if (!lguest_address_ok(cpu->lg, cpu->hcall->arg1, if (!lguest_address_ok(cpu->lg, cpu->hcall->arg1,
sizeof(*cpu->lg->lguest_data))) sizeof(*cpu->lg->lguest_data)))
return -EFAULT; return -EFAULT;
...@@ -546,6 +554,7 @@ int lguest_arch_init_hypercalls(struct lg_cpu *cpu) ...@@ -546,6 +554,7 @@ int lguest_arch_init_hypercalls(struct lg_cpu *cpu)
return 0; return 0;
} }
/*:*/
/*L:030 lguest_arch_setup_regs() /*L:030 lguest_arch_setup_regs()
* *
......
/*P:900 This is the Switcher: code which sits at 0xFFC00000 to do the low-level /*P:900 This is the Switcher: code which sits at 0xFFC00000 astride both the
* Guest<->Host switch. It is as simple as it can be made, but it's naturally * Host and Guest to do the low-level Guest<->Host switch. It is as simple as
* very specific to x86. * 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 * You have now completed Preparation. If this has whet your appetite; if you
* are feeling invigorated and refreshed then the next, more challenging stage * are feeling invigorated and refreshed then the next, more challenging stage
...@@ -189,7 +189,7 @@ ENTRY(switch_to_guest) ...@@ -189,7 +189,7 @@ ENTRY(switch_to_guest)
// Interrupts are turned back on: we are Guest. // Interrupts are turned back on: we are Guest.
iret 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 // Yet both must save Guest state and restore Host
// So we put the routine in a macro. // So we put the routine in a macro.
#define SWITCH_TO_HOST \ #define SWITCH_TO_HOST \
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
#ifndef __ASSEMBLY__ #ifndef __ASSEMBLY__
#include <asm/hw_irq.h> #include <asm/hw_irq.h>
/*G:031 First, how does our Guest contact the Host to ask for privileged /*G:031 But first, how does our Guest contact the Host to ask for privileged
* operations? There are two ways: the direct way is to make a "hypercall", * operations? There are two ways: the direct way is to make a "hypercall",
* to make requests of the Host Itself. * to make requests of the Host Itself.
* *
......
...@@ -16,6 +16,10 @@ ...@@ -16,6 +16,10 @@
* a new device, we simply need to write a new virtio driver and create support * a new device, we simply need to write a new virtio driver and create support
* for it in the Launcher: this code won't need to change. * for it in the Launcher: this code won't need to change.
* *
* Virtio devices are also used by kvm, so we can simply reuse their optimized
* device drivers. And one day when everyone uses virtio, my plan will be
* complete. Bwahahahah!
*
* Devices are described by a simplified ID, a status byte, and some "config" * Devices are described by a simplified ID, a status byte, and some "config"
* bytes which describe this device's configuration. This is placed by the * bytes which describe this device's configuration. This is placed by the
* Launcher just above the top of physical memory: * Launcher just above the top of physical memory:
...@@ -26,7 +30,7 @@ struct lguest_device_desc { ...@@ -26,7 +30,7 @@ struct lguest_device_desc {
/* The number of virtqueues (first in config array) */ /* The number of virtqueues (first in config array) */
__u8 num_vq; __u8 num_vq;
/* The number of bytes of feature bits. Multiply by 2: one for host /* The number of bytes of feature bits. Multiply by 2: one for host
* features and one for guest acknowledgements. */ * features and one for Guest acknowledgements. */
__u8 feature_len; __u8 feature_len;
/* The number of bytes of the config array after virtqueues. */ /* The number of bytes of the config array after virtqueues. */
__u8 config_len; __u8 config_len;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment