From a454bb36cabdac163fcd80ddb37662893ea603f1 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:09 +1030 Subject: lguest: have --rng read from /dev/urandom not /dev/random. Theoretical debates aside, now it boots. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 32cf2ce15d69..3f7f2326cd9a 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -1733,9 +1733,9 @@ static void setup_block_file(const char *filename) } /*L:211 - * Our random number generator device reads from /dev/random into the Guest's + * Our random number generator device reads from /dev/urandom into the Guest's * input buffers. The usual case is that the Guest doesn't want random numbers - * and so has no buffers although /dev/random is still readable, whereas + * and so has no buffers although /dev/urandom is still readable, whereas * console is the reverse. * * The same logic applies, however. @@ -1763,7 +1763,7 @@ static void rng_input(struct virtqueue *vq) while (!iov_empty(iov, in_num)) { len = readv(rng_info->rfd, iov, in_num); if (len <= 0) - err(1, "Read from /dev/random gave %i", len); + err(1, "Read from /dev/urandom gave %i", len); iov_consume(iov, in_num, NULL, len); totlen += len; } @@ -1780,8 +1780,8 @@ static void setup_rng(void) struct device *dev; struct rng_info *rng_info = malloc(sizeof(*rng_info)); - /* Our device's privat info simply contains the /dev/random fd. */ - rng_info->rfd = open_or_die("/dev/random", O_RDONLY); + /* Our device's private info simply contains the /dev/urandom fd. */ + rng_info->rfd = open_or_die("/dev/urandom", O_RDONLY); /* Create the new device. */ dev = new_device("rng", VIRTIO_ID_RNG); -- cgit v1.2.3 From 69a09dc1742ffbb3b02f3a1e03da4801e96452e9 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:09 +1030 Subject: lguest: write more information to userspace about pending traps. This is preparation for userspace handling MMIO and ioport accesses. Signed-off-by: Rusty Russell --- drivers/lguest/core.c | 7 ++++--- drivers/lguest/hypercalls.c | 7 ++++--- drivers/lguest/lg.h | 3 ++- drivers/lguest/lguest_user.c | 14 +++++++++----- include/linux/lguest_launcher.h | 13 +++++++++++++ tools/lguest/lguest.c | 16 ++++++++++------ 6 files changed, 42 insertions(+), 18 deletions(-) (limited to 'tools') diff --git a/drivers/lguest/core.c b/drivers/lguest/core.c index cdb2f9aa5860..9159dbc583f6 100644 --- a/drivers/lguest/core.c +++ b/drivers/lguest/core.c @@ -229,16 +229,17 @@ int run_guest(struct lg_cpu *cpu, unsigned long __user *user) * It's possible the Guest did a NOTIFY hypercall to the * Launcher. */ - if (cpu->pending_notify) { + if (cpu->pending.trap) { /* * Does it just needs to write to a registered * eventfd (ie. the appropriate virtqueue thread)? */ if (!send_notify_to_eventfd(cpu)) { /* OK, we tell the main Launcher. */ - if (put_user(cpu->pending_notify, user)) + if (copy_to_user(user, &cpu->pending, + sizeof(cpu->pending))) return -EFAULT; - return sizeof(cpu->pending_notify); + return sizeof(cpu->pending); } } diff --git a/drivers/lguest/hypercalls.c b/drivers/lguest/hypercalls.c index 83511eb0923d..5dd1fb8a6610 100644 --- a/drivers/lguest/hypercalls.c +++ b/drivers/lguest/hypercalls.c @@ -118,7 +118,8 @@ static void do_hcall(struct lg_cpu *cpu, struct hcall_args *args) cpu->halted = 1; break; case LHCALL_NOTIFY: - cpu->pending_notify = args->arg1; + cpu->pending.trap = LGUEST_TRAP_ENTRY; + cpu->pending.addr = args->arg1; break; default: /* It should be an architecture-specific hypercall. */ @@ -189,7 +190,7 @@ static void do_async_hcalls(struct lg_cpu *cpu) * Stop doing hypercalls if they want to notify the Launcher: * it needs to service this first. */ - if (cpu->pending_notify) + if (cpu->pending.trap) break; } } @@ -280,7 +281,7 @@ void do_hypercalls(struct lg_cpu *cpu) * NOTIFY to the Launcher, we want to return now. Otherwise we do * the hypercall. */ - if (!cpu->pending_notify) { + if (!cpu->pending.trap) { do_hcall(cpu, cpu->hcall); /* * Tricky point: we reset the hcall pointer to mark the diff --git a/drivers/lguest/lg.h b/drivers/lguest/lg.h index 1c98bf74fd68..020fec5bb072 100644 --- a/drivers/lguest/lg.h +++ b/drivers/lguest/lg.h @@ -50,7 +50,8 @@ struct lg_cpu { /* Bitmap of what has changed: see CHANGED_* above. */ int changed; - unsigned long pending_notify; /* pfn from LHCALL_NOTIFY */ + /* Pending operation. */ + struct lguest_pending pending; unsigned long *reg_read; /* register from LHREQ_GETREG */ diff --git a/drivers/lguest/lguest_user.c b/drivers/lguest/lguest_user.c index 7f14c152dd23..dcf9efd94cf4 100644 --- a/drivers/lguest/lguest_user.c +++ b/drivers/lguest/lguest_user.c @@ -29,6 +29,10 @@ bool send_notify_to_eventfd(struct lg_cpu *cpu) unsigned int i; struct lg_eventfd_map *map; + /* We only connect LHCALL_NOTIFY to event fds, not other traps. */ + if (cpu->pending.trap != LGUEST_TRAP_ENTRY) + return false; + /* * This "rcu_read_lock()" helps track when someone is still looking at * the (RCU-using) eventfds array. It's not actually a lock at all; @@ -52,9 +56,9 @@ bool send_notify_to_eventfd(struct lg_cpu *cpu) * we'll continue to use the old array and just won't see the new one. */ for (i = 0; i < map->num; i++) { - if (map->map[i].addr == cpu->pending_notify) { + if (map->map[i].addr == cpu->pending.addr) { eventfd_signal(map->map[i].event, 1); - cpu->pending_notify = 0; + cpu->pending.trap = 0; break; } } @@ -62,7 +66,7 @@ bool send_notify_to_eventfd(struct lg_cpu *cpu) rcu_read_unlock(); /* If we cleared the notification, it's because we found a match. */ - return cpu->pending_notify == 0; + return cpu->pending.trap == 0; } /*L:055 @@ -282,8 +286,8 @@ static ssize_t read(struct file *file, char __user *user, size_t size,loff_t*o) * If we returned from read() last time because the Guest sent I/O, * clear the flag. */ - if (cpu->pending_notify) - cpu->pending_notify = 0; + if (cpu->pending.trap) + cpu->pending.trap = 0; /* Run the Guest until something interesting happens. */ return run_guest(cpu, (unsigned long __user *)user); diff --git a/include/linux/lguest_launcher.h b/include/linux/lguest_launcher.h index f27cae27b0c1..c4451ebece47 100644 --- a/include/linux/lguest_launcher.h +++ b/include/linux/lguest_launcher.h @@ -67,6 +67,19 @@ enum lguest_req LHREQ_SETREG, /* + offset within struct pt_regs, value. */ }; +/* + * This is what read() of the lguest fd populates. trap == + * LGUEST_TRAP_ENTRY for an LHCALL_NOTIFY (addr is the + * argument), 14 for a page fault in the MMIO region (addr is + * the trap address, insn is the instruction), or 13 for a GPF + * (insn is the instruction). + */ +struct lguest_pending { + __u8 trap; + __u8 insn[7]; + __u32 addr; +}; + /* * The alignment to use between consumer and producer parts of vring. * x86 pagesize for historical reasons. diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 3f7f2326cd9a..0e754d04876d 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -1820,17 +1820,21 @@ static void __attribute__((noreturn)) restart_guest(void) static void __attribute__((noreturn)) run_guest(void) { for (;;) { - unsigned long notify_addr; + struct lguest_pending notify; int readval; /* We read from the /dev/lguest device to run the Guest. */ - readval = pread(lguest_fd, ¬ify_addr, - sizeof(notify_addr), cpu_id); + readval = pread(lguest_fd, ¬ify, sizeof(notify), cpu_id); /* One unsigned long means the Guest did HCALL_NOTIFY */ - if (readval == sizeof(notify_addr)) { - verbose("Notify on address %#lx\n", notify_addr); - handle_output(notify_addr); + if (readval == sizeof(notify)) { + if (notify.trap == 0x1F) { + verbose("Notify on address %#08x\n", + notify.addr); + handle_output(notify.addr); + } else + errx(1, "Unknown trap %i addr %#08x\n", + notify.trap, notify.addr); /* ENOENT means the Guest died. Reading tells us why. */ } else if (errno == ENOENT) { char reason[1024] = { 0 }; -- cgit v1.2.3 From c565650b1028bc551e5d16dd0ec8f7078da7cace Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:10 +1030 Subject: lguest: send trap 13 through to userspace. We copy 7 bytes at eip for userspace's instruction decode; we have to carefully handle the case where eip is at the end of a page. We can't leave this to userspace since kernel has all the page table decode logic. The decode logic moves to userspace, basically unchanged. Signed-off-by: Rusty Russell --- drivers/lguest/x86/core.c | 133 +++++++++++++---------------------------- tools/lguest/lguest.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 90 deletions(-) (limited to 'tools') diff --git a/drivers/lguest/x86/core.c b/drivers/lguest/x86/core.c index f7a16b4ea456..42e87bf14113 100644 --- a/drivers/lguest/x86/core.c +++ b/drivers/lguest/x86/core.c @@ -314,95 +314,52 @@ void lguest_arch_run_guest(struct lg_cpu *cpu) * usually attached to a PC. * * When the Guest uses one of these instructions, we get a trap (General - * Protection Fault) and come here. We see if it's one of those troublesome - * instructions and skip over it. We return true if we did. + * Protection Fault) and come here. We queue this to be sent out to the + * Launcher to handle. */ -static int emulate_insn(struct lg_cpu *cpu) -{ - u8 insn; - unsigned int insnlen = 0, in = 0, small_operand = 0; - /* - * The eip contains the *virtual* address of the Guest's instruction: - * walk the Guest's page tables to find the "physical" address. - */ - unsigned long physaddr = guest_pa(cpu, cpu->regs->eip); - - /* - * This must be the Guest kernel trying to do something, not userspace! - * The bottom two bits of the CS segment register are the privilege - * level. - */ - if ((cpu->regs->cs & 3) != GUEST_PL) - return 0; - /* Decoding x86 instructions is icky. */ - insn = lgread(cpu, physaddr, u8); - - /* - * Around 2.6.33, the kernel started using an emulation for the - * cmpxchg8b instruction in early boot on many configurations. This - * code isn't paravirtualized, and it tries to disable interrupts. - * Ignore it, which will Mostly Work. - */ - if (insn == 0xfa) { - /* "cli", or Clear Interrupt Enable instruction. Skip it. */ - cpu->regs->eip++; - return 1; +/* + * The eip contains the *virtual* address of the Guest's instruction: + * we copy the instruction here so the Launcher doesn't have to walk + * the page tables to decode it. We handle the case (eg. in a kernel + * module) where the instruction is over two pages, and the pages are + * virtually but not physically contiguous. + * + * The longest possible x86 instruction is 15 bytes, but we don't handle + * anything that strange. + */ +static void copy_from_guest(struct lg_cpu *cpu, + void *dst, unsigned long vaddr, size_t len) +{ + size_t to_page_end = PAGE_SIZE - (vaddr % PAGE_SIZE); + unsigned long paddr; + + BUG_ON(len > PAGE_SIZE); + + /* If it goes over a page, copy in two parts. */ + if (len > to_page_end) { + /* But make sure the next page is mapped! */ + if (__guest_pa(cpu, vaddr + to_page_end, &paddr)) + copy_from_guest(cpu, dst + to_page_end, + vaddr + to_page_end, + len - to_page_end); + else + /* Otherwise fill with zeroes. */ + memset(dst + to_page_end, 0, len - to_page_end); + len = to_page_end; } - /* - * 0x66 is an "operand prefix". It means a 16, not 32 bit in/out. - */ - if (insn == 0x66) { - small_operand = 1; - /* The instruction is 1 byte so far, read the next byte. */ - insnlen = 1; - insn = lgread(cpu, physaddr + insnlen, u8); - } + /* This will kill the guest if it isn't mapped, but that + * shouldn't happen. */ + __lgread(cpu, dst, guest_pa(cpu, vaddr), len); +} - /* - * We can ignore the lower bit for the moment and decode the 4 opcodes - * we need to emulate. - */ - switch (insn & 0xFE) { - case 0xE4: /* in ,%al */ - insnlen += 2; - in = 1; - break; - case 0xEC: /* in (%dx),%al */ - insnlen += 1; - in = 1; - break; - case 0xE6: /* out %al, */ - insnlen += 2; - break; - case 0xEE: /* out %al,(%dx) */ - insnlen += 1; - break; - default: - /* OK, we don't know what this is, can't emulate. */ - return 0; - } - /* - * If it was an "IN" instruction, they expect the result to be read - * into %eax, so we change %eax. We always return all-ones, which - * traditionally means "there's nothing there". - */ - if (in) { - /* Lower bit tells means it's a 32/16 bit access */ - if (insn & 0x1) { - if (small_operand) - cpu->regs->eax |= 0xFFFF; - else - cpu->regs->eax = 0xFFFFFFFF; - } else - cpu->regs->eax |= 0xFF; - } - /* Finally, we've "done" the instruction, so move past it. */ - cpu->regs->eip += insnlen; - /* Success! */ - return 1; +static void setup_emulate_insn(struct lg_cpu *cpu) +{ + cpu->pending.trap = 13; + copy_from_guest(cpu, cpu->pending.insn, cpu->regs->eip, + sizeof(cpu->pending.insn)); } /*H:050 Once we've re-enabled interrupts, we look at why the Guest exited. */ @@ -410,14 +367,10 @@ void lguest_arch_handle_trap(struct lg_cpu *cpu) { switch (cpu->regs->trapnum) { case 13: /* We've intercepted a General Protection Fault. */ - /* - * Check if this was one of those annoying IN or OUT - * instructions which we need to emulate. If so, we just go - * back into the Guest after we've done it. - */ + /* Hand to Launcher to emulate those pesky IN and OUT insns */ if (cpu->regs->errcode == 0) { - if (emulate_insn(cpu)) - return; + setup_emulate_insn(cpu); + return; } break; case 14: /* We've intercepted a Page Fault. */ diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 0e754d04876d..b2217657f62c 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -41,6 +41,7 @@ #include #include #include +#include #ifndef VIRTIO_F_ANY_LAYOUT #define VIRTIO_F_ANY_LAYOUT 27 @@ -1143,6 +1144,150 @@ static void handle_output(unsigned long addr) strnlen(from_guest_phys(addr), guest_limit - addr)); } +/*L:216 + * This is where we emulate a handful of Guest instructions. It's ugly + * and we used to do it in the kernel but it grew over time. + */ + +/* + * We use the ptrace syscall's pt_regs struct to talk about registers + * to lguest: these macros convert the names to the offsets. + */ +#define getreg(name) getreg_off(offsetof(struct user_regs_struct, name)) +#define setreg(name, val) \ + setreg_off(offsetof(struct user_regs_struct, name), (val)) + +static u32 getreg_off(size_t offset) +{ + u32 r; + unsigned long args[] = { LHREQ_GETREG, offset }; + + if (pwrite(lguest_fd, args, sizeof(args), cpu_id) < 0) + err(1, "Getting register %u", offset); + if (pread(lguest_fd, &r, sizeof(r), cpu_id) != sizeof(r)) + err(1, "Reading register %u", offset); + + return r; +} + +static void setreg_off(size_t offset, u32 val) +{ + unsigned long args[] = { LHREQ_SETREG, offset, val }; + + if (pwrite(lguest_fd, args, sizeof(args), cpu_id) < 0) + err(1, "Setting register %u", offset); +} + +static void emulate_insn(const u8 insn[]) +{ + unsigned long args[] = { LHREQ_TRAP, 13 }; + unsigned int insnlen = 0, in = 0, small_operand = 0, byte_access; + unsigned int eax, port, mask; + /* + * We always return all-ones on IO port reads, which traditionally + * means "there's nothing there". + */ + u32 val = 0xFFFFFFFF; + + /* + * This must be the Guest kernel trying to do something, not userspace! + * The bottom two bits of the CS segment register are the privilege + * level. + */ + if ((getreg(xcs) & 3) != 0x1) + goto no_emulate; + + /* Decoding x86 instructions is icky. */ + + /* + * Around 2.6.33, the kernel started using an emulation for the + * cmpxchg8b instruction in early boot on many configurations. This + * code isn't paravirtualized, and it tries to disable interrupts. + * Ignore it, which will Mostly Work. + */ + if (insn[insnlen] == 0xfa) { + /* "cli", or Clear Interrupt Enable instruction. Skip it. */ + insnlen = 1; + goto skip_insn; + } + + /* + * 0x66 is an "operand prefix". It means a 16, not 32 bit in/out. + */ + if (insn[insnlen] == 0x66) { + small_operand = 1; + /* The instruction is 1 byte so far, read the next byte. */ + insnlen = 1; + } + + /* If the lower bit isn't set, it's a single byte access */ + byte_access = !(insn[insnlen] & 1); + + /* + * Now we can ignore the lower bit and decode the 4 opcodes + * we need to emulate. + */ + switch (insn[insnlen] & 0xFE) { + case 0xE4: /* in ,%al */ + port = insn[insnlen+1]; + insnlen += 2; + in = 1; + break; + case 0xEC: /* in (%dx),%al */ + port = getreg(edx) & 0xFFFF; + insnlen += 1; + in = 1; + break; + case 0xE6: /* out %al, */ + port = insn[insnlen+1]; + insnlen += 2; + break; + case 0xEE: /* out %al,(%dx) */ + port = getreg(edx) & 0xFFFF; + insnlen += 1; + break; + default: + /* OK, we don't know what this is, can't emulate. */ + goto no_emulate; + } + + /* Set a mask of the 1, 2 or 4 bytes, depending on size of IO */ + if (byte_access) + mask = 0xFF; + else if (small_operand) + mask = 0xFFFF; + else + mask = 0xFFFFFFFF; + + /* + * If it was an "IN" instruction, they expect the result to be read + * into %eax, so we change %eax. + */ + eax = getreg(eax); + + if (in) { + /* Clear the bits we're about to read */ + eax &= ~mask; + /* Copy bits in from val. */ + eax |= val & mask; + /* Now update the register. */ + setreg(eax, eax); + } + + verbose("IO %s of %x to %u: %#08x\n", + in ? "IN" : "OUT", mask, port, eax); +skip_insn: + /* Finally, we've "done" the instruction, so move past it. */ + setreg(eip, getreg(eip) + insnlen); + return; + +no_emulate: + /* Inject trap into Guest. */ + if (write(lguest_fd, args, sizeof(args)) < 0) + err(1, "Reinjecting trap 13 for fault at %#x", getreg(eip)); +} + + /*L:190 * Device Setup * @@ -1832,6 +1977,10 @@ static void __attribute__((noreturn)) run_guest(void) verbose("Notify on address %#08x\n", notify.addr); handle_output(notify.addr); + } else if (notify.trap == 13) { + verbose("Emulating instruction at %#x\n", + getreg(eip)); + emulate_insn(notify.insn); } else errx(1, "Unknown trap %i addr %#08x\n", notify.trap, notify.addr); -- cgit v1.2.3 From 48fd6b71d60ef66ef2d791045d750168c0d09201 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:10 +1030 Subject: lguest: suppress PS/2 keyboard polling. While hacking on getting I/O out to the lguest launcher, I noticed that returning 0xFF for the PS/2 keyboard status made it spin for a while thinking there was a key pending. Fix this by returning 1 instead of 0xFF. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index b2217657f62c..485fe13db12e 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -1259,6 +1259,10 @@ static void emulate_insn(const u8 insn[]) else mask = 0xFFFFFFFF; + /* This is the PS/2 keyboard status; 1 means ready for output */ + if (port == 0x64) + val = 1; + /* * If it was an "IN" instruction, they expect the result to be read * into %eax, so we change %eax. -- cgit v1.2.3 From 7313d5217e6b9817897172d6a6ff477bdc415ed6 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:10 +1030 Subject: lguest: add iomem region, where guest page faults get sent to userspace. This lets us implement PCI. Signed-off-by: Rusty Russell --- drivers/lguest/lg.h | 7 ++++++- drivers/lguest/lguest_user.c | 3 ++- drivers/lguest/page_tables.c | 33 ++++++++++++++++++++++++++++++--- drivers/lguest/x86/core.c | 19 ++++++++++++++++++- tools/lguest/lguest.c | 3 ++- 5 files changed, 58 insertions(+), 7 deletions(-) (limited to 'tools') diff --git a/drivers/lguest/lg.h b/drivers/lguest/lg.h index 9da4f351e077..eb81abc05995 100644 --- a/drivers/lguest/lg.h +++ b/drivers/lguest/lg.h @@ -97,8 +97,12 @@ struct lguest { struct lg_cpu cpus[NR_CPUS]; unsigned int nr_cpus; + /* Valid guest memory pages must be < this. */ u32 pfn_limit; + /* Device memory is >= pfn_limit and < device_limit. */ + u32 device_limit; + /* * This provides the offset to the base of guest-physical memory in the * Launcher. @@ -200,7 +204,8 @@ void guest_pagetable_flush_user(struct lg_cpu *cpu); void guest_set_pte(struct lg_cpu *cpu, unsigned long gpgdir, unsigned long vaddr, pte_t val); void map_switcher_in_guest(struct lg_cpu *cpu, struct lguest_pages *pages); -bool demand_page(struct lg_cpu *cpu, unsigned long cr2, int errcode); +bool demand_page(struct lg_cpu *cpu, unsigned long cr2, int errcode, + unsigned long *iomem); void pin_page(struct lg_cpu *cpu, unsigned long vaddr); bool __guest_pa(struct lg_cpu *cpu, unsigned long vaddr, unsigned long *paddr); unsigned long guest_pa(struct lg_cpu *cpu, unsigned long vaddr); diff --git a/drivers/lguest/lguest_user.c b/drivers/lguest/lguest_user.c index be996d173615..c8b0e8575b44 100644 --- a/drivers/lguest/lguest_user.c +++ b/drivers/lguest/lguest_user.c @@ -385,7 +385,7 @@ static int initialize(struct file *file, const unsigned long __user *input) /* "struct lguest" contains all we (the Host) know about a Guest. */ struct lguest *lg; int err; - unsigned long args[3]; + unsigned long args[4]; /* * We grab the Big Lguest lock, which protects against multiple @@ -419,6 +419,7 @@ static int initialize(struct file *file, const unsigned long __user *input) /* Populate the easy fields of our "struct lguest" */ lg->mem_base = (void __user *)args[0]; lg->pfn_limit = args[1]; + lg->device_limit = args[3]; /* This is the first cpu (cpu 0) and it will start booting at args[2] */ err = lg_cpu_start(&lg->cpus[0], 0, args[2]); diff --git a/drivers/lguest/page_tables.c b/drivers/lguest/page_tables.c index 69c35caa955a..e3abebc912c0 100644 --- a/drivers/lguest/page_tables.c +++ b/drivers/lguest/page_tables.c @@ -250,6 +250,16 @@ static void release_pte(pte_t pte) } /*:*/ +static bool gpte_in_iomem(struct lg_cpu *cpu, pte_t gpte) +{ + /* We don't handle large pages. */ + if (pte_flags(gpte) & _PAGE_PSE) + return false; + + return (pte_pfn(gpte) >= cpu->lg->pfn_limit + && pte_pfn(gpte) < cpu->lg->device_limit); +} + static bool check_gpte(struct lg_cpu *cpu, pte_t gpte) { if ((pte_flags(gpte) & _PAGE_PSE) || @@ -374,8 +384,14 @@ static pte_t *find_spte(struct lg_cpu *cpu, unsigned long vaddr, bool allocate, * * If we fixed up the fault (ie. we mapped the address), this routine returns * true. Otherwise, it was a real fault and we need to tell the Guest. + * + * There's a corner case: they're trying to access memory between + * pfn_limit and device_limit, which is I/O memory. In this case, we + * return false and set @iomem to the physical address, so the the + * Launcher can handle the instruction manually. */ -bool demand_page(struct lg_cpu *cpu, unsigned long vaddr, int errcode) +bool demand_page(struct lg_cpu *cpu, unsigned long vaddr, int errcode, + unsigned long *iomem) { unsigned long gpte_ptr; pte_t gpte; @@ -383,6 +399,8 @@ bool demand_page(struct lg_cpu *cpu, unsigned long vaddr, int errcode) pmd_t gpmd; pgd_t gpgd; + *iomem = 0; + /* We never demand page the Switcher, so trying is a mistake. */ if (vaddr >= switcher_addr) return false; @@ -459,6 +477,12 @@ bool demand_page(struct lg_cpu *cpu, unsigned long vaddr, int errcode) if ((errcode & 4) && !(pte_flags(gpte) & _PAGE_USER)) return false; + /* If they're accessing io memory, we expect a fault. */ + if (gpte_in_iomem(cpu, gpte)) { + *iomem = (pte_pfn(gpte) << PAGE_SHIFT) | (vaddr & ~PAGE_MASK); + return false; + } + /* * Check that the Guest PTE flags are OK, and the page number is below * the pfn_limit (ie. not mapping the Launcher binary). @@ -553,7 +577,9 @@ static bool page_writable(struct lg_cpu *cpu, unsigned long vaddr) */ void pin_page(struct lg_cpu *cpu, unsigned long vaddr) { - if (!page_writable(cpu, vaddr) && !demand_page(cpu, vaddr, 2)) + unsigned long iomem; + + if (!page_writable(cpu, vaddr) && !demand_page(cpu, vaddr, 2, &iomem)) kill_guest(cpu, "bad stack page %#lx", vaddr); } /*:*/ @@ -928,7 +954,8 @@ static void __guest_set_pte(struct lg_cpu *cpu, int idx, * now. This shaves 10% off a copy-on-write * micro-benchmark. */ - if (pte_flags(gpte) & (_PAGE_DIRTY | _PAGE_ACCESSED)) { + if ((pte_flags(gpte) & (_PAGE_DIRTY | _PAGE_ACCESSED)) + && !gpte_in_iomem(cpu, gpte)) { if (!check_gpte(cpu, gpte)) return; set_pte(spte, diff --git a/drivers/lguest/x86/core.c b/drivers/lguest/x86/core.c index 42e87bf14113..18d841e738bc 100644 --- a/drivers/lguest/x86/core.c +++ b/drivers/lguest/x86/core.c @@ -362,9 +362,19 @@ static void setup_emulate_insn(struct lg_cpu *cpu) sizeof(cpu->pending.insn)); } +static void setup_iomem_insn(struct lg_cpu *cpu, unsigned long iomem_addr) +{ + cpu->pending.trap = 14; + cpu->pending.addr = iomem_addr; + copy_from_guest(cpu, cpu->pending.insn, cpu->regs->eip, + sizeof(cpu->pending.insn)); +} + /*H:050 Once we've re-enabled interrupts, we look at why the Guest exited. */ void lguest_arch_handle_trap(struct lg_cpu *cpu) { + unsigned long iomem_addr; + switch (cpu->regs->trapnum) { case 13: /* We've intercepted a General Protection Fault. */ /* Hand to Launcher to emulate those pesky IN and OUT insns */ @@ -385,8 +395,15 @@ void lguest_arch_handle_trap(struct lg_cpu *cpu) * whether kernel or userspace code. */ if (demand_page(cpu, cpu->arch.last_pagefault, - cpu->regs->errcode)) + cpu->regs->errcode, &iomem_addr)) + return; + + /* Was this an access to memory mapped IO? */ + if (iomem_addr) { + /* Tell Launcher, let it handle it. */ + setup_iomem_insn(cpu, iomem_addr); return; + } /* * OK, it's really not there (or not OK): the Guest needs to diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 485fe13db12e..02f353989e6c 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -548,7 +548,8 @@ static void tell_kernel(unsigned long start) { unsigned long args[] = { LHREQ_INITIALIZE, (unsigned long)guest_base, - guest_limit / getpagesize(), start }; + guest_limit / getpagesize(), start, + guest_limit / getpagesize() }; verbose("Guest: %p - %p (%#lx)\n", guest_base, guest_base + guest_limit, guest_limit); lguest_fd = open_or_die("/dev/lguest", O_RDWR); -- cgit v1.2.3 From 0a6bcc183f5377eca07cbf0cf6f4b6cb00e4c1ec Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:11 +1030 Subject: lguest: add MMIO region allocator in example launcher. This is where we point our PCI BARs, so that we can intercept MMIO accesses. We tell the kernel about it so any faults in this area are directed to us. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 02f353989e6c..35d7aa90aa24 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -92,7 +92,7 @@ static bool verbose; /* The pointer to the start of guest memory. */ static void *guest_base; /* The maximum guest physical address allowed, and maximum possible. */ -static unsigned long guest_limit, guest_max; +static unsigned long guest_limit, guest_max, guest_mmio; /* The /dev/lguest file descriptor. */ static int lguest_fd; @@ -321,6 +321,23 @@ static void *get_pages(unsigned int num) return addr; } +/* Get some bytes which won't be mapped into the guest. */ +static unsigned long get_mmio_region(size_t size) +{ + unsigned long addr = guest_mmio; + size_t i; + + if (!size) + return addr; + + /* Size has to be a power of 2 (and multiple of 16) */ + for (i = 1; i < size; i <<= 1); + + guest_mmio += i; + + return addr; +} + /* * This routine is used to load the kernel or initrd. It tries mmap, but if * that fails (Plan 9's kernel file isn't nicely aligned on page boundaries), @@ -549,9 +566,10 @@ static void tell_kernel(unsigned long start) unsigned long args[] = { LHREQ_INITIALIZE, (unsigned long)guest_base, guest_limit / getpagesize(), start, - guest_limit / getpagesize() }; - verbose("Guest: %p - %p (%#lx)\n", - guest_base, guest_base + guest_limit, guest_limit); + (guest_mmio+getpagesize()-1) / getpagesize() }; + verbose("Guest: %p - %p (%#lx, MMIO %#lx)\n", + guest_base, guest_base + guest_limit, + guest_limit, guest_mmio); lguest_fd = open_or_die("/dev/lguest", O_RDWR); if (write(lguest_fd, args, sizeof(args)) < 0) err(1, "Writing to /dev/lguest"); @@ -2079,7 +2097,7 @@ int main(int argc, char *argv[]) guest_base = map_zeroed_pages(mem / getpagesize() + DEVICE_PAGES); guest_limit = mem; - guest_max = mem + DEVICE_PAGES*getpagesize(); + guest_max = guest_mmio = mem + DEVICE_PAGES*getpagesize(); devices.descpage = get_pages(1); break; } -- cgit v1.2.3 From 6a54f9ab0d65a2095de50160b8ca7ce6469aaac0 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:11 +1030 Subject: lguest: decode mmio accesses for PCI in example launcher. We don't do anything with them yet (emulate_mmio_write and emulate_mmio_read are stubs), but we decode the instructions and search for the device they're hitting. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 35d7aa90aa24..e52a3571076a 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -99,6 +99,9 @@ static int lguest_fd; /* a per-cpu variable indicating whose vcpu is currently running */ static unsigned int __thread cpu_id; +/* 5 bit device number in the PCI_CONFIG_ADDR => 32 only */ +#define MAX_PCI_DEVICES 32 + /* This is our list of devices. */ struct device_list { /* Counter to assign interrupt numbers. */ @@ -114,6 +117,9 @@ struct device_list { struct device *dev; /* And a pointer to the last device for easy append. */ struct device *lastdev; + + /* PCI devices. */ + struct device *pci[MAX_PCI_DEVICES]; }; /* The list of Guest devices, based on command line arguments. */ @@ -140,6 +146,10 @@ struct device { /* Is it operational */ bool running; + /* PCI MMIO resources (all in BAR0) */ + size_t mmio_size; + u32 mmio_addr; + /* Device-specific data. */ void *priv; }; @@ -1197,6 +1207,77 @@ static void setreg_off(size_t offset, u32 val) err(1, "Setting register %u", offset); } +/* Get register by instruction encoding */ +static u32 getreg_num(unsigned regnum, u32 mask) +{ + /* 8 bit ops use regnums 4-7 for high parts of word */ + if (mask == 0xFF && (regnum & 0x4)) + return getreg_num(regnum & 0x3, 0xFFFF) >> 8; + + switch (regnum) { + case 0: return getreg(eax) & mask; + case 1: return getreg(ecx) & mask; + case 2: return getreg(edx) & mask; + case 3: return getreg(ebx) & mask; + case 4: return getreg(esp) & mask; + case 5: return getreg(ebp) & mask; + case 6: return getreg(esi) & mask; + case 7: return getreg(edi) & mask; + } + abort(); +} + +/* Set register by instruction encoding */ +static void setreg_num(unsigned regnum, u32 val, u32 mask) +{ + /* Don't try to set bits out of range */ + assert(~(val & ~mask)); + + /* 8 bit ops use regnums 4-7 for high parts of word */ + if (mask == 0xFF && (regnum & 0x4)) { + /* Construct the 16 bits we want. */ + val = (val << 8) | getreg_num(regnum & 0x3, 0xFF); + setreg_num(regnum & 0x3, val, 0xFFFF); + return; + } + + switch (regnum) { + case 0: setreg(eax, val | (getreg(eax) & ~mask)); return; + case 1: setreg(ecx, val | (getreg(ecx) & ~mask)); return; + case 2: setreg(edx, val | (getreg(edx) & ~mask)); return; + case 3: setreg(ebx, val | (getreg(ebx) & ~mask)); return; + case 4: setreg(esp, val | (getreg(esp) & ~mask)); return; + case 5: setreg(ebp, val | (getreg(ebp) & ~mask)); return; + case 6: setreg(esi, val | (getreg(esi) & ~mask)); return; + case 7: setreg(edi, val | (getreg(edi) & ~mask)); return; + } + abort(); +} + +/* Get bytes of displacement appended to instruction, from r/m encoding */ +static u32 insn_displacement_len(u8 mod_reg_rm) +{ + /* Switch on the mod bits */ + switch (mod_reg_rm >> 6) { + case 0: + /* If mod == 0, and r/m == 101, 16-bit displacement follows */ + if ((mod_reg_rm & 0x7) == 0x5) + return 2; + /* Normally, mod == 0 means no literal displacement */ + return 0; + case 1: + /* One byte displacement */ + return 1; + case 2: + /* Four byte displacement */ + return 4; + case 3: + /* Register mode */ + return 0; + } + abort(); +} + static void emulate_insn(const u8 insn[]) { unsigned long args[] = { LHREQ_TRAP, 13 }; @@ -1310,6 +1391,88 @@ no_emulate: err(1, "Reinjecting trap 13 for fault at %#x", getreg(eip)); } +static struct device *find_mmio_region(unsigned long paddr, u32 *off) +{ + unsigned int i; + + for (i = 1; i < MAX_PCI_DEVICES; i++) { + struct device *d = devices.pci[i]; + + if (!d) + continue; + if (paddr < d->mmio_addr) + continue; + if (paddr >= d->mmio_addr + d->mmio_size) + continue; + *off = paddr - d->mmio_addr; + return d; + } + return NULL; +} + +static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) +{ +} + +static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) +{ + return 0xFFFFFFFF; +} + +static void emulate_mmio(unsigned long paddr, const u8 *insn) +{ + u32 val, off, mask = 0xFFFFFFFF, insnlen = 0; + struct device *d = find_mmio_region(paddr, &off); + unsigned long args[] = { LHREQ_TRAP, 14 }; + + if (!d) { + warnx("MMIO touching %#08lx (not a device)", paddr); + goto reinject; + } + + /* Prefix makes it a 16 bit op */ + if (insn[0] == 0x66) { + mask = 0xFFFF; + insnlen++; + } + + /* iowrite */ + if (insn[insnlen] == 0x89) { + /* Next byte is r/m byte: bits 3-5 are register. */ + val = getreg_num((insn[insnlen+1] >> 3) & 0x7, mask); + emulate_mmio_write(d, off, val, mask); + insnlen += 2 + insn_displacement_len(insn[insnlen+1]); + } else if (insn[insnlen] == 0x8b) { /* ioread */ + /* Next byte is r/m byte: bits 3-5 are register. */ + val = emulate_mmio_read(d, off, mask); + setreg_num((insn[insnlen+1] >> 3) & 0x7, val, mask); + insnlen += 2 + insn_displacement_len(insn[insnlen+1]); + } else if (insn[0] == 0x88) { /* 8-bit iowrite */ + mask = 0xff; + /* Next byte is r/m byte: bits 3-5 are register. */ + val = getreg_num((insn[1] >> 3) & 0x7, mask); + emulate_mmio_write(d, off, val, mask); + insnlen = 2 + insn_displacement_len(insn[1]); + } else if (insn[0] == 0x8a) { /* 8-bit ioread */ + mask = 0xff; + val = emulate_mmio_read(d, off, mask); + setreg_num((insn[1] >> 3) & 0x7, val, mask); + insnlen = 2 + insn_displacement_len(insn[1]); + } else { + warnx("Unknown MMIO instruction touching %#08lx:" + " %02x %02x %02x %02x at %u", + paddr, insn[0], insn[1], insn[2], insn[3], getreg(eip)); + reinject: + /* Inject trap into Guest. */ + if (write(lguest_fd, args, sizeof(args)) < 0) + err(1, "Reinjecting trap 14 for fault at %#x", + getreg(eip)); + return; + } + + /* Finally, we've "done" the instruction, so move past it. */ + setreg(eip, getreg(eip) + insnlen); +} /*L:190 * Device Setup @@ -2004,6 +2167,10 @@ static void __attribute__((noreturn)) run_guest(void) verbose("Emulating instruction at %#x\n", getreg(eip)); emulate_insn(notify.insn); + } else if (notify.trap == 14) { + verbose("Emulating MMIO at %#x\n", + getreg(eip)); + emulate_mmio(notify.addr, notify.insn); } else errx(1, "Unknown trap %i addr %#08x\n", notify.trap, notify.addr); -- cgit v1.2.3 From d7fbf6e95e2c5e7ef97c463a97499d7a2341fb09 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:11 +1030 Subject: lguest: add PCI config space emulation to example launcher. This handles ioport 0xCF8 and 0xCFC accesses, which are used to read/write PCI device config space. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 216 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 211 insertions(+), 5 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index e52a3571076a..0f29657fc065 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -42,6 +42,7 @@ #include #include #include +#include #ifndef VIRTIO_F_ANY_LAYOUT #define VIRTIO_F_ANY_LAYOUT 27 @@ -125,6 +126,21 @@ struct device_list { /* The list of Guest devices, based on command line arguments. */ static struct device_list devices; +/* This is the layout (little-endian) of the PCI config space. */ +struct pci_config { + u16 vendor_id, device_id; + u16 command, status; + u8 revid, prog_if, subclass, class; + u8 cacheline_size, lat_timer, header_type, bist; + u32 bar[6]; + u32 cardbus_cis_ptr; + u16 subsystem_vendor_id, subsystem_device_id; + u32 expansion_rom_addr; + u8 capabilities, reserved1[3]; + u32 reserved2; + u8 irq_line, irq_pin, min_grant, max_latency; +}; + /* The device structure describes a single device. */ struct device { /* The linked-list pointer. */ @@ -146,6 +162,15 @@ struct device { /* Is it operational */ bool running; + /* PCI configuration */ + union { + struct pci_config config; + u32 config_words[sizeof(struct pci_config) / sizeof(u32)]; + }; + + /* Device-specific config hangs off the end of this. */ + struct virtio_pci_mmio *mmio; + /* PCI MMIO resources (all in BAR0) */ size_t mmio_size; u32 mmio_addr; @@ -1173,6 +1198,169 @@ static void handle_output(unsigned long addr) strnlen(from_guest_phys(addr), guest_limit - addr)); } +/*L:217 + * We do PCI. This is mainly done to let us test the kernel virtio PCI + * code. + */ + +/* The IO ports used to read the PCI config space. */ +#define PCI_CONFIG_ADDR 0xCF8 +#define PCI_CONFIG_DATA 0xCFC + +/* + * Not really portable, but does help readability: this is what the Guest + * writes to the PCI_CONFIG_ADDR IO port. + */ +union pci_config_addr { + struct { + unsigned mbz: 2; + unsigned offset: 6; + unsigned funcnum: 3; + unsigned devnum: 5; + unsigned busnum: 8; + unsigned reserved: 7; + unsigned enabled : 1; + } bits; + u32 val; +}; + +/* + * We cache what they wrote to the address port, so we know what they're + * talking about when they access the data port. + */ +static union pci_config_addr pci_config_addr; + +static struct device *find_pci_device(unsigned int index) +{ + return devices.pci[index]; +} + +/* PCI can do 1, 2 and 4 byte reads; we handle that here. */ +static void ioread(u16 off, u32 v, u32 mask, u32 *val) +{ + assert(off < 4); + assert(mask == 0xFF || mask == 0xFFFF || mask == 0xFFFFFFFF); + *val = (v >> (off * 8)) & mask; +} + +/* PCI can do 1, 2 and 4 byte writes; we handle that here. */ +static void iowrite(u16 off, u32 v, u32 mask, u32 *dst) +{ + assert(off < 4); + assert(mask == 0xFF || mask == 0xFFFF || mask == 0xFFFFFFFF); + *dst &= ~(mask << (off * 8)); + *dst |= (v & mask) << (off * 8); +} + +/* + * Where PCI_CONFIG_DATA accesses depends on the previous write to + * PCI_CONFIG_ADDR. + */ +static struct device *dev_and_reg(u32 *reg) +{ + if (!pci_config_addr.bits.enabled) + return NULL; + + if (pci_config_addr.bits.funcnum != 0) + return NULL; + + if (pci_config_addr.bits.busnum != 0) + return NULL; + + if (pci_config_addr.bits.offset * 4 >= sizeof(struct pci_config)) + return NULL; + + *reg = pci_config_addr.bits.offset; + return find_pci_device(pci_config_addr.bits.devnum); +} + +/* Is this accessing the PCI config address port?. */ +static bool is_pci_addr_port(u16 port) +{ + return port >= PCI_CONFIG_ADDR && port < PCI_CONFIG_ADDR + 4; +} + +static bool pci_addr_iowrite(u16 port, u32 mask, u32 val) +{ + iowrite(port - PCI_CONFIG_ADDR, val, mask, + &pci_config_addr.val); + verbose("PCI%s: %#x/%x: bus %u dev %u func %u reg %u\n", + pci_config_addr.bits.enabled ? "" : " DISABLED", + val, mask, + pci_config_addr.bits.busnum, + pci_config_addr.bits.devnum, + pci_config_addr.bits.funcnum, + pci_config_addr.bits.offset); + return true; +} + +static void pci_addr_ioread(u16 port, u32 mask, u32 *val) +{ + ioread(port - PCI_CONFIG_ADDR, pci_config_addr.val, mask, val); +} + +/* Is this accessing the PCI config data port?. */ +static bool is_pci_data_port(u16 port) +{ + return port >= PCI_CONFIG_DATA && port < PCI_CONFIG_DATA + 4; +} + +static bool pci_data_iowrite(u16 port, u32 mask, u32 val) +{ + u32 reg, portoff; + struct device *d = dev_and_reg(®); + + /* Complain if they don't belong to a device. */ + if (!d) + return false; + + /* They can do 1 byte writes, etc. */ + portoff = port - PCI_CONFIG_DATA; + + /* + * PCI uses a weird way to determine the BAR size: the OS + * writes all 1's, and sees which ones stick. + */ + if (&d->config_words[reg] == &d->config.bar[0]) { + int i; + + iowrite(portoff, val, mask, &d->config.bar[0]); + for (i = 0; (1 << i) < d->mmio_size; i++) + d->config.bar[0] &= ~(1 << i); + return true; + } else if ((&d->config_words[reg] > &d->config.bar[0] + && &d->config_words[reg] <= &d->config.bar[6]) + || &d->config_words[reg] == &d->config.expansion_rom_addr) { + /* Allow writing to any other BAR, or expansion ROM */ + iowrite(portoff, val, mask, &d->config_words[reg]); + return true; + /* We let them overide latency timer and cacheline size */ + } else if (&d->config_words[reg] == (void *)&d->config.cacheline_size) { + /* Only let them change the first two fields. */ + if (mask == 0xFFFFFFFF) + mask = 0xFFFF; + iowrite(portoff, val, mask, &d->config_words[reg]); + return true; + } else if (&d->config_words[reg] == (void *)&d->config.command + && mask == 0xFFFF) { + /* Ignore command writes. */ + return true; + } + + /* Complain about other writes. */ + return false; +} + +static void pci_data_ioread(u16 port, u32 mask, u32 *val) +{ + u32 reg; + struct device *d = dev_and_reg(®); + + if (!d) + return; + ioread(port - PCI_CONFIG_DATA, d->config_words[reg], mask, val); +} + /*L:216 * This is where we emulate a handful of Guest instructions. It's ugly * and we used to do it in the kernel but it grew over time. @@ -1284,7 +1472,7 @@ static void emulate_insn(const u8 insn[]) unsigned int insnlen = 0, in = 0, small_operand = 0, byte_access; unsigned int eax, port, mask; /* - * We always return all-ones on IO port reads, which traditionally + * Default is to return all-ones on IO port reads, which traditionally * means "there's nothing there". */ u32 val = 0xFFFFFFFF; @@ -1359,10 +1547,6 @@ static void emulate_insn(const u8 insn[]) else mask = 0xFFFFFFFF; - /* This is the PS/2 keyboard status; 1 means ready for output */ - if (port == 0x64) - val = 1; - /* * If it was an "IN" instruction, they expect the result to be read * into %eax, so we change %eax. @@ -1370,12 +1554,30 @@ static void emulate_insn(const u8 insn[]) eax = getreg(eax); if (in) { + /* This is the PS/2 keyboard status; 1 means ready for output */ + if (port == 0x64) + val = 1; + else if (is_pci_addr_port(port)) + pci_addr_ioread(port, mask, &val); + else if (is_pci_data_port(port)) + pci_data_ioread(port, mask, &val); + /* Clear the bits we're about to read */ eax &= ~mask; /* Copy bits in from val. */ eax |= val & mask; /* Now update the register. */ setreg(eax, eax); + } else { + if (is_pci_addr_port(port)) { + if (!pci_addr_iowrite(port, mask, eax)) + goto bad_io; + } else if (is_pci_data_port(port)) { + if (!pci_data_iowrite(port, mask, eax)) + goto bad_io; + } + /* There are many other ports, eg. CMOS clock, serial + * and parallel ports, so we ignore them all. */ } verbose("IO %s of %x to %u: %#08x\n", @@ -1385,6 +1587,10 @@ skip_insn: setreg(eip, getreg(eip) + insnlen); return; +bad_io: + warnx("Attempt to %s port %u (%#x mask)", + in ? "read from" : "write to", port, mask); + no_emulate: /* Inject trap into Guest. */ if (write(lguest_fd, args, sizeof(args)) < 0) -- cgit v1.2.3 From 93153077107ecfbf35a3412f6220521e8d8c14ba Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:11 +1030 Subject: lguest: implement virtio-PCI MMIO accesses. For each device, We need to include the vendor capabilities to demark where virtio common, notification and ISR regions are (we put them all in BAR0). We need to handle the switching of the virtqueues using the accessors. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 492 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 490 insertions(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 0f29657fc065..eafdaf2a14c4 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -63,12 +63,16 @@ typedef uint16_t u16; typedef uint8_t u8; /*:*/ -#include +#define VIRTIO_PCI_NO_LEGACY + +/* Use in-kernel ones, which defines VIRTIO_F_VERSION_1 */ +#include "../../include/uapi/linux/virtio_config.h" #include #include #include #include #include +#include "../../include/uapi/linux/virtio_pci.h" #include #include "../../include/linux/lguest_launcher.h" @@ -126,6 +130,19 @@ struct device_list { /* The list of Guest devices, based on command line arguments. */ static struct device_list devices; +struct virtio_pci_cfg_cap { + struct virtio_pci_cap cap; + u32 window; /* Data for BAR access. */ +}; + +struct virtio_pci_mmio { + struct virtio_pci_common_cfg cfg; + u16 notify; + u8 isr; + u8 padding; + /* Device-specific configuration follows this. */ +}; + /* This is the layout (little-endian) of the PCI config space. */ struct pci_config { u16 vendor_id, device_id; @@ -139,6 +156,14 @@ struct pci_config { u8 capabilities, reserved1[3]; u32 reserved2; u8 irq_line, irq_pin, min_grant, max_latency; + + /* Now, this is the linked capability list. */ + struct virtio_pci_cap common; + struct virtio_pci_notify_cap notify; + struct virtio_pci_cap isr; + struct virtio_pci_cap device; + /* FIXME: Implement this! */ + struct virtio_pci_cfg_cap cfg_access; }; /* The device structure describes a single device. */ @@ -168,6 +193,9 @@ struct device { u32 config_words[sizeof(struct pci_config) / sizeof(u32)]; }; + /* Features we offer, and those accepted. */ + u64 features, features_accepted; + /* Device-specific config hangs off the end of this. */ struct virtio_pci_mmio *mmio; @@ -192,6 +220,9 @@ struct virtqueue { /* The actual ring of buffers. */ struct vring vring; + /* The information about this virtqueue (we only use queue_size on) */ + struct virtio_pci_common_cfg pci_config; + /* Last available index we saw. */ u16 last_avail_idx; @@ -680,6 +711,10 @@ static void trigger_irq(struct virtqueue *vq) return; } + /* For a PCI device, set isr to 1 (queue interrupt pending) */ + if (vq->dev->mmio) + vq->dev->mmio->isr = 0x1; + /* Send the Guest an interrupt tell them we used something up. */ if (write(lguest_fd, buf, sizeof(buf)) != 0) err(1, "Triggering irq %i", vq->config.irq); @@ -1616,13 +1651,264 @@ static struct device *find_mmio_region(unsigned long paddr, u32 *off) return NULL; } +/* FIXME: Use vq array. */ +static struct virtqueue *vq_by_num(struct device *d, u32 num) +{ + struct virtqueue *vq = d->vq; + + while (num-- && vq) + vq = vq->next; + + return vq; +} + +static void save_vq_config(const struct virtio_pci_common_cfg *cfg, + struct virtqueue *vq) +{ + vq->pci_config = *cfg; +} + +static void restore_vq_config(struct virtio_pci_common_cfg *cfg, + struct virtqueue *vq) +{ + /* Only restore the per-vq part */ + size_t off = offsetof(struct virtio_pci_common_cfg, queue_size); + + memcpy((void *)cfg + off, (void *)&vq->pci_config + off, + sizeof(*cfg) - off); +} + +/* + * When they enable the virtqueue, we check that their setup is valid. + */ +static void enable_virtqueue(struct device *d, struct virtqueue *vq) +{ + /* + * Create stack for thread. Since the stack grows upwards, we point + * the stack pointer to the end of this region. + */ + char *stack = malloc(32768); + + /* Because lguest is 32 bit, all the descriptor high bits must be 0 */ + if (vq->pci_config.queue_desc_hi + || vq->pci_config.queue_avail_hi + || vq->pci_config.queue_used_hi) + errx(1, "%s: invalid 64-bit queue address", d->name); + + /* Initialize the virtqueue and check they're all in range. */ + vq->vring.num = vq->pci_config.queue_size; + vq->vring.desc = check_pointer(vq->pci_config.queue_desc_lo, + sizeof(*vq->vring.desc) * vq->vring.num); + vq->vring.avail = check_pointer(vq->pci_config.queue_avail_lo, + sizeof(*vq->vring.avail) + + (sizeof(vq->vring.avail->ring[0]) + * vq->vring.num)); + vq->vring.used = check_pointer(vq->pci_config.queue_used_lo, + sizeof(*vq->vring.used) + + (sizeof(vq->vring.used->ring[0]) + * vq->vring.num)); + + + /* Create a zero-initialized eventfd. */ + vq->eventfd = eventfd(0, 0); + if (vq->eventfd < 0) + err(1, "Creating eventfd"); + + /* + * CLONE_VM: because it has to access the Guest memory, and SIGCHLD so + * we get a signal if it dies. + */ + vq->thread = clone(do_thread, stack + 32768, CLONE_VM | SIGCHLD, vq); + if (vq->thread == (pid_t)-1) + err(1, "Creating clone"); +} + +static void reset_pci_device(struct device *dev) +{ + /* FIXME */ +} + static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) { + struct virtqueue *vq; + + switch (off) { + case offsetof(struct virtio_pci_mmio, cfg.device_feature_select): + if (val == 0) + d->mmio->cfg.device_feature = d->features; + else if (val == 1) + d->mmio->cfg.device_feature = (d->features >> 32); + else + d->mmio->cfg.device_feature = 0; + goto write_through32; + case offsetof(struct virtio_pci_mmio, cfg.guest_feature_select): + if (val > 1) + errx(1, "%s: Unexpected driver select %u", + d->name, val); + goto write_through32; + case offsetof(struct virtio_pci_mmio, cfg.guest_feature): + if (d->mmio->cfg.guest_feature_select == 0) { + d->features_accepted &= ~((u64)0xFFFFFFFF); + d->features_accepted |= val; + } else { + assert(d->mmio->cfg.guest_feature_select == 1); + d->features_accepted &= ((u64)0xFFFFFFFF << 32); + d->features_accepted |= ((u64)val) << 32; + } + if (d->features_accepted & ~d->features) + errx(1, "%s: over-accepted features %#llx of %#llx", + d->name, d->features_accepted, d->features); + goto write_through32; + case offsetof(struct virtio_pci_mmio, cfg.device_status): + verbose("%s: device status -> %#x\n", d->name, val); + if (val == 0) + reset_pci_device(d); + goto write_through8; + case offsetof(struct virtio_pci_mmio, cfg.queue_select): + vq = vq_by_num(d, val); + /* Out of range? Return size 0 */ + if (!vq) { + d->mmio->cfg.queue_size = 0; + goto write_through16; + } + /* Save registers for old vq, if it was a valid vq */ + if (d->mmio->cfg.queue_size) + save_vq_config(&d->mmio->cfg, + vq_by_num(d, d->mmio->cfg.queue_select)); + /* Restore the registers for the queue they asked for */ + restore_vq_config(&d->mmio->cfg, vq); + goto write_through16; + case offsetof(struct virtio_pci_mmio, cfg.queue_size): + if (val & (val-1)) + errx(1, "%s: invalid queue size %u\n", d->name, val); + if (d->mmio->cfg.queue_enable) + errx(1, "%s: changing queue size on live device", + d->name); + goto write_through16; + case offsetof(struct virtio_pci_mmio, cfg.queue_msix_vector): + errx(1, "%s: attempt to set MSIX vector to %u", + d->name, val); + case offsetof(struct virtio_pci_mmio, cfg.queue_enable): + if (val != 1) + errx(1, "%s: setting queue_enable to %u", d->name, val); + d->mmio->cfg.queue_enable = val; + save_vq_config(&d->mmio->cfg, + vq_by_num(d, d->mmio->cfg.queue_select)); + enable_virtqueue(d, vq_by_num(d, d->mmio->cfg.queue_select)); + goto write_through16; + case offsetof(struct virtio_pci_mmio, cfg.queue_notify_off): + errx(1, "%s: attempt to write to queue_notify_off", d->name); + case offsetof(struct virtio_pci_mmio, cfg.queue_desc_lo): + case offsetof(struct virtio_pci_mmio, cfg.queue_desc_hi): + case offsetof(struct virtio_pci_mmio, cfg.queue_avail_lo): + case offsetof(struct virtio_pci_mmio, cfg.queue_avail_hi): + case offsetof(struct virtio_pci_mmio, cfg.queue_used_lo): + case offsetof(struct virtio_pci_mmio, cfg.queue_used_hi): + if (d->mmio->cfg.queue_enable) + errx(1, "%s: changing queue on live device", + d->name); + goto write_through32; + case offsetof(struct virtio_pci_mmio, notify): + vq = vq_by_num(d, val); + if (!vq) + errx(1, "Invalid vq notification on %u", val); + /* Notify the process handling this vq by adding 1 to eventfd */ + write(vq->eventfd, "\1\0\0\0\0\0\0\0", 8); + goto write_through16; + case offsetof(struct virtio_pci_mmio, isr): + errx(1, "%s: Unexpected write to isr", d->name); + default: + errx(1, "%s: Unexpected write to offset %u", d->name, off); + } + +write_through32: + if (mask != 0xFFFFFFFF) { + errx(1, "%s: non-32-bit write to offset %u (%#x)", + d->name, off, getreg(eip)); + return; + } + memcpy((char *)d->mmio + off, &val, 4); + return; + +write_through16: + if (mask != 0xFFFF) + errx(1, "%s: non-16-bit (%#x) write to offset %u (%#x)", + d->name, mask, off, getreg(eip)); + memcpy((char *)d->mmio + off, &val, 2); + return; + +write_through8: + if (mask != 0xFF) + errx(1, "%s: non-8-bit write to offset %u (%#x)", + d->name, off, getreg(eip)); + memcpy((char *)d->mmio + off, &val, 1); + return; } static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) { - return 0xFFFFFFFF; + u8 isr; + u32 val = 0; + + switch (off) { + case offsetof(struct virtio_pci_mmio, cfg.device_feature_select): + case offsetof(struct virtio_pci_mmio, cfg.device_feature): + case offsetof(struct virtio_pci_mmio, cfg.guest_feature_select): + case offsetof(struct virtio_pci_mmio, cfg.guest_feature): + goto read_through32; + case offsetof(struct virtio_pci_mmio, cfg.msix_config): + errx(1, "%s: read of msix_config", d->name); + case offsetof(struct virtio_pci_mmio, cfg.num_queues): + goto read_through16; + case offsetof(struct virtio_pci_mmio, cfg.device_status): + case offsetof(struct virtio_pci_mmio, cfg.config_generation): + goto read_through8; + case offsetof(struct virtio_pci_mmio, notify): + goto read_through16; + case offsetof(struct virtio_pci_mmio, isr): + if (mask != 0xFF) + errx(1, "%s: non-8-bit read from offset %u (%#x)", + d->name, off, getreg(eip)); + /* Read resets the isr */ + isr = d->mmio->isr; + d->mmio->isr = 0; + return isr; + case offsetof(struct virtio_pci_mmio, padding): + errx(1, "%s: read from padding (%#x)", + d->name, getreg(eip)); + default: + /* Read from device config space, beware unaligned overflow */ + if (off > d->mmio_size - 4) + errx(1, "%s: read past end (%#x)", + d->name, getreg(eip)); + if (mask == 0xFFFFFFFF) + goto read_through32; + else if (mask == 0xFFFF) + goto read_through16; + else + goto read_through8; + } + +read_through32: + if (mask != 0xFFFFFFFF) + errx(1, "%s: non-32-bit read to offset %u (%#x)", + d->name, off, getreg(eip)); + memcpy(&val, (char *)d->mmio + off, 4); + return val; + +read_through16: + if (mask != 0xFFFF) + errx(1, "%s: non-16-bit read to offset %u (%#x)", + d->name, off, getreg(eip)); + memcpy(&val, (char *)d->mmio + off, 2); + return val; + +read_through8: + if (mask != 0xFF) + errx(1, "%s: non-8-bit read to offset %u (%#x)", + d->name, off, getreg(eip)); + memcpy(&val, (char *)d->mmio + off, 1); + return val; } static void emulate_mmio(unsigned long paddr, const u8 *insn) @@ -1783,6 +2069,42 @@ static void add_virtqueue(struct device *dev, unsigned int num_descs, *i = vq; } +static void add_pci_virtqueue(struct device *dev, + void (*service)(struct virtqueue *)) +{ + struct virtqueue **i, *vq = malloc(sizeof(*vq)); + + /* Initialize the virtqueue */ + vq->next = NULL; + vq->last_avail_idx = 0; + vq->dev = dev; + + /* + * This is the routine the service thread will run, and its Process ID + * once it's running. + */ + vq->service = service; + vq->thread = (pid_t)-1; + + /* Initialize the configuration. */ + vq->pci_config.queue_size = VIRTQUEUE_NUM; + vq->pci_config.queue_enable = 0; + vq->pci_config.queue_notify_off = 0; + + /* Add one to the number of queues */ + vq->dev->mmio->cfg.num_queues++; + + /* FIXME: Do irq per virtqueue, not per device. */ + vq->config.irq = vq->dev->config.irq_line; + + /* + * Add to tail of list, so dev->vq is first vq, dev->vq->next is + * second. + */ + for (i = &dev->vq; *i; i = &(*i)->next); + *i = vq; +} + /* * The first half of the feature bitmask is for us to advertise features. The * second half is for the Guest to accept features. @@ -1800,6 +2122,11 @@ static void add_feature(struct device *dev, unsigned bit) features[bit / CHAR_BIT] |= (1 << (bit % CHAR_BIT)); } +static void add_pci_feature(struct device *dev, unsigned bit) +{ + dev->features |= (1ULL << bit); +} + /* * This routine sets the configuration fields for an existing device's * descriptor. It only works for the last device, but that's OK because that's @@ -1819,6 +2146,139 @@ static void set_config(struct device *dev, unsigned len, const void *conf) assert(dev->desc->config_len == len); } +/* For devices with no config. */ +static void no_device_config(struct device *dev) +{ + dev->mmio_addr = get_mmio_region(dev->mmio_size); + + dev->config.bar[0] = dev->mmio_addr; + /* Bottom 4 bits must be zero */ + assert(~(dev->config.bar[0] & 0xF)); +} + +/* This puts the device config into BAR0 */ +static void set_device_config(struct device *dev, const void *conf, size_t len) +{ + /* Set up BAR 0 */ + dev->mmio_size += len; + dev->mmio = realloc(dev->mmio, dev->mmio_size); + memcpy(dev->mmio + 1, conf, len); + + /* Hook up device cfg */ + dev->config.cfg_access.cap.cap_next + = offsetof(struct pci_config, device); + + /* Fix up device cfg field length. */ + dev->config.device.length = len; + + /* The rest is the same as the no-config case */ + no_device_config(dev); +} + +static void init_cap(struct virtio_pci_cap *cap, size_t caplen, int type, + size_t bar_offset, size_t bar_bytes, u8 next) +{ + cap->cap_vndr = PCI_CAP_ID_VNDR; + cap->cap_next = next; + cap->cap_len = caplen; + cap->cfg_type = type; + cap->bar = 0; + memset(cap->padding, 0, sizeof(cap->padding)); + cap->offset = bar_offset; + cap->length = bar_bytes; +} + +/* + * This sets up the pci_config structure, as defined in the virtio 1.0 + * standard (and PCI standard). + */ +static void init_pci_config(struct pci_config *pci, u16 type, + u8 class, u8 subclass) +{ + size_t bar_offset, bar_len; + + /* Save typing: most thing are happy being zero. */ + memset(pci, 0, sizeof(*pci)); + + /* 4.1.2.1: Devices MUST have the PCI Vendor ID 0x1AF4 */ + pci->vendor_id = 0x1AF4; + /* 4.1.2.1: ... PCI Device ID calculated by adding 0x1040 ... */ + pci->device_id = 0x1040 + type; + + /* + * PCI have specific codes for different types of devices. + * Linux doesn't care, but it's a good clue for people looking + * at the device. + * + * eg : + * VIRTIO_ID_CONSOLE: class = 0x07, subclass = 0x00 + * VIRTIO_ID_NET: class = 0x02, subclass = 0x00 + * VIRTIO_ID_BLOCK: class = 0x01, subclass = 0x80 + * VIRTIO_ID_RNG: class = 0xff, subclass = 0 + */ + pci->class = class; + pci->subclass = subclass; + + /* + * 4.1.2.1 Non-transitional devices SHOULD have a PCI Revision + * ID of 1 or higher + */ + pci->revid = 1; + + /* + * 4.1.2.1 Non-transitional devices SHOULD have a PCI + * Subsystem Device ID of 0x40 or higher. + */ + pci->subsystem_device_id = 0x40; + + /* We use our dummy interrupt controller, and irq_line is the irq */ + pci->irq_line = devices.next_irq++; + pci->irq_pin = 0; + + /* Support for extended capabilities. */ + pci->status = (1 << 4); + + /* Link them in. */ + pci->capabilities = offsetof(struct pci_config, common); + + bar_offset = offsetof(struct virtio_pci_mmio, cfg); + bar_len = sizeof(((struct virtio_pci_mmio *)0)->cfg); + init_cap(&pci->common, sizeof(pci->common), VIRTIO_PCI_CAP_COMMON_CFG, + bar_offset, bar_len, + offsetof(struct pci_config, notify)); + + bar_offset += bar_len; + bar_len = sizeof(((struct virtio_pci_mmio *)0)->notify); + /* FIXME: Use a non-zero notify_off, for per-queue notification? */ + init_cap(&pci->notify.cap, sizeof(pci->notify), + VIRTIO_PCI_CAP_NOTIFY_CFG, + bar_offset, bar_len, + offsetof(struct pci_config, isr)); + + bar_offset += bar_len; + bar_len = sizeof(((struct virtio_pci_mmio *)0)->isr); + init_cap(&pci->isr, sizeof(pci->isr), + VIRTIO_PCI_CAP_ISR_CFG, + bar_offset, bar_len, + offsetof(struct pci_config, cfg_access)); + + /* This doesn't have any presence in the BAR */ + init_cap(&pci->cfg_access.cap, sizeof(pci->cfg_access), + VIRTIO_PCI_CAP_PCI_CFG, + 0, 0, 0); + + bar_offset += bar_len + sizeof(((struct virtio_pci_mmio *)0)->padding); + assert(bar_offset == sizeof(struct virtio_pci_mmio)); + + /* + * This gets sewn in and length set in set_device_config(). + * Some devices don't have a device configuration interface, so + * we never expose this if we don't call set_device_config(). + */ + init_cap(&pci->device, sizeof(pci->device), VIRTIO_PCI_CAP_DEVICE_CFG, + bar_offset, 0, 0); +} + /* * This routine does all the creation and setup of a new device, including * calling new_dev_desc() to allocate the descriptor and device memory. We @@ -1854,6 +2314,34 @@ static struct device *new_device(const char *name, u16 type) return dev; } +static struct device *new_pci_device(const char *name, u16 type, + u8 class, u8 subclass) +{ + struct device *dev = malloc(sizeof(*dev)); + + /* Now we populate the fields one at a time. */ + dev->desc = NULL; + dev->name = name; + dev->vq = NULL; + dev->feature_len = 0; + dev->num_vq = 0; + dev->running = false; + dev->next = NULL; + dev->mmio_size = sizeof(struct virtio_pci_mmio); + dev->mmio = calloc(1, dev->mmio_size); + dev->features = (u64)1 << VIRTIO_F_VERSION_1; + dev->features_accepted = 0; + + if (devices.device_num + 1 >= 32) + errx(1, "Can only handle 31 PCI devices"); + + init_pci_config(&dev->config, type, class, subclass); + assert(!devices.pci[devices.device_num+1]); + devices.pci[++devices.device_num] = dev; + + return dev; +} + /* * Our first setup routine is the console. It's a fairly simple device, but * UNIX tty handling makes it uglier than it could be. -- cgit v1.2.3 From 3e0e5f2640d3b8f8f958e72f1577f1e323e11da6 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:11 +1030 Subject: lguest: fix failure to find linux/virtio_types.h We want to use the local kernel headers, but -I../../include/uapi leads us into a world of hurt. Instead we create a dummy include/ dir with symlinks. If we just use #include "../../include/uapi/linux/virtio_blk.h" we get: ../../include/uapi/linux/virtio_blk.h:31:32: fatal error: linux/virtio_types.h: No such file or directory #include Signed-off-by: Rusty Russell --- tools/lguest/Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/lguest/Makefile b/tools/lguest/Makefile index 97bca4871ea3..a107b5e4da13 100644 --- a/tools/lguest/Makefile +++ b/tools/lguest/Makefile @@ -1,7 +1,13 @@ # This creates the demonstration utility "lguest" which runs a Linux guest. -CFLAGS:=-m32 -Wall -Wmissing-declarations -Wmissing-prototypes -O3 -U_FORTIFY_SOURCE +CFLAGS:=-m32 -Wall -Wmissing-declarations -Wmissing-prototypes -O3 -U_FORTIFY_SOURCE -Iinclude all: lguest +include/linux/virtio_types.h: ../../include/uapi/linux/virtio_types.h + mkdir -p include/linux 2>&1 || true + ln -sf ../../../../include/uapi/linux/virtio_types.h $@ + +lguest: include/linux/virtio_types.h + clean: rm -f lguest -- cgit v1.2.3 From 8e70946943961cf5bb9be3a0cf12bd0da7a7cb0d Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:12 +1030 Subject: lguest: add a dummy PCI host bridge. Otherwise Linux fails to find the bus. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index eafdaf2a14c4..c8930bc5ce99 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -1238,6 +1238,17 @@ static void handle_output(unsigned long addr) * code. */ +/* Linux expects a PCI host bridge: ours is a dummy, and first on the bus. */ +static struct device pci_host_bridge; + +static void init_pci_host_bridge(void) +{ + pci_host_bridge.name = "PCI Host Bridge"; + pci_host_bridge.config.class = 0x06; /* bridge */ + pci_host_bridge.config.subclass = 0; /* host bridge */ + devices.pci[0] = &pci_host_bridge; +} + /* The IO ports used to read the PCI config space. */ #define PCI_CONFIG_ADDR 0xCF8 #define PCI_CONFIG_DATA 0xCFC @@ -3007,6 +3018,9 @@ int main(int argc, char *argv[]) /* We always have a console device */ setup_console(); + /* Initialize the (fake) PCI host bridge device. */ + init_pci_host_bridge(); + /* Now we load the kernel */ start = load_kernel(open_or_die(argv[optind+1], O_RDONLY)); -- cgit v1.2.3 From 5051654764d55a101747b5b2a695bcecae75fa4c Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:15:12 +1030 Subject: lguest: Convert block device to virtio 1.0 PCI. We remove SCSI support (which was removed for 1.0) and VIRTIO_BLK_F_FLUSH feature flag (removed too, since it's compulsory for 1.0). The rest is mainly mechanical. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index c8930bc5ce99..d4a79f6ddfbd 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -64,11 +64,12 @@ typedef uint8_t u8; /*:*/ #define VIRTIO_PCI_NO_LEGACY +#define VIRTIO_BLK_NO_LEGACY /* Use in-kernel ones, which defines VIRTIO_F_VERSION_1 */ #include "../../include/uapi/linux/virtio_config.h" #include -#include +#include "../../include/uapi/linux/virtio_blk.h" #include #include #include @@ -2224,7 +2225,6 @@ static void init_pci_config(struct pci_config *pci, u16 type, * eg : * VIRTIO_ID_CONSOLE: class = 0x07, subclass = 0x00 * VIRTIO_ID_NET: class = 0x02, subclass = 0x00 - * VIRTIO_ID_BLOCK: class = 0x01, subclass = 0x80 * VIRTIO_ID_RNG: class = 0xff, subclass = 0 */ pci->class = class; @@ -2663,15 +2663,7 @@ static void blk_request(struct virtqueue *vq) */ off = out.sector * 512; - /* - * In general the virtio block driver is allowed to try SCSI commands. - * It'd be nice if we supported eject, for example, but we don't. - */ - if (out.type & VIRTIO_BLK_T_SCSI_CMD) { - fprintf(stderr, "Scsi commands unsupported\n"); - *in = VIRTIO_BLK_S_UNSUPP; - wlen = sizeof(*in); - } else if (out.type & VIRTIO_BLK_T_OUT) { + if (out.type & VIRTIO_BLK_T_OUT) { /* * Write * @@ -2735,11 +2727,11 @@ static void setup_block_file(const char *filename) struct vblk_info *vblk; struct virtio_blk_config conf; - /* Creat the device. */ - dev = new_device("block", VIRTIO_ID_BLOCK); + /* Create the device. */ + dev = new_pci_device("block", VIRTIO_ID_BLOCK, 0x01, 0x80); /* The device has one virtqueue, where the Guest places requests. */ - add_virtqueue(dev, VIRTQUEUE_NUM, blk_request); + add_pci_virtqueue(dev, blk_request); /* Allocate the room for our own bookkeeping */ vblk = dev->priv = malloc(sizeof(*vblk)); @@ -2748,9 +2740,6 @@ static void setup_block_file(const char *filename) vblk->fd = open_or_die(filename, O_RDWR|O_LARGEFILE); vblk->len = lseek64(vblk->fd, 0, SEEK_END); - /* We support FLUSH. */ - add_feature(dev, VIRTIO_BLK_F_FLUSH); - /* Tell Guest how many sectors this device has. */ conf.capacity = cpu_to_le64(vblk->len / 512); @@ -2758,14 +2747,13 @@ static void setup_block_file(const char *filename) * Tell Guest not to put in too many descriptors at once: two are used * for the in and out elements. */ - add_feature(dev, VIRTIO_BLK_F_SEG_MAX); + add_pci_feature(dev, VIRTIO_BLK_F_SEG_MAX); conf.seg_max = cpu_to_le32(VIRTQUEUE_NUM - 2); - /* Don't try to put whole struct: we have 8 bit limit. */ - set_config(dev, offsetof(struct virtio_blk_config, geometry), &conf); + set_device_config(dev, &conf, sizeof(struct virtio_blk_config)); verbose("device %u: virtblock %llu sectors\n", - ++devices.device_num, le64_to_cpu(conf.capacity)); + devices.device_num, le64_to_cpu(conf.capacity)); } /*L:211 -- cgit v1.2.3 From bf6d40344d7006f29da1a2782f45188cdbbb0904 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:16:01 +1030 Subject: lguest: Convert net device to virtio 1.0 PCI. The only real change here (other than using the PCI bus) is that we didn't negotiate VIRTIO_NET_F_MRG_RXBUF before, so the format of the packet header changed with virtio 1.0; we need TUNSETVNETHDRSZ on the tun fd to tell it about the extra two bytes. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index d4a79f6ddfbd..b6c88a10a4c9 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -68,7 +68,7 @@ typedef uint8_t u8; /* Use in-kernel ones, which defines VIRTIO_F_VERSION_1 */ #include "../../include/uapi/linux/virtio_config.h" -#include +#include "../../include/uapi/linux/virtio_net.h" #include "../../include/uapi/linux/virtio_blk.h" #include #include @@ -2224,7 +2224,6 @@ static void init_pci_config(struct pci_config *pci, u16 type, * * eg : * VIRTIO_ID_CONSOLE: class = 0x07, subclass = 0x00 - * VIRTIO_ID_NET: class = 0x02, subclass = 0x00 * VIRTIO_ID_RNG: class = 0xff, subclass = 0 */ pci->class = class; @@ -2485,6 +2484,7 @@ static void configure_device(int fd, const char *tapif, u32 ipaddr) static int get_tun_device(char tapif[IFNAMSIZ]) { struct ifreq ifr; + int vnet_hdr_sz; int netfd; /* Start with this zeroed. Messy but sure. */ @@ -2512,6 +2512,18 @@ static int get_tun_device(char tapif[IFNAMSIZ]) */ ioctl(netfd, TUNSETNOCSUM, 1); + /* + * In virtio before 1.0 (aka legacy virtio), we added a 16-bit + * field at the end of the network header iff + * VIRTIO_NET_F_MRG_RXBUF was negotiated. For virtio 1.0, + * that became the norm, but we need to tell the tun device + * about our expanded header (which is called + * virtio_net_hdr_mrg_rxbuf in the legacy system). + */ + vnet_hdr_sz = sizeof(struct virtio_net_hdr_mrg_rxbuf); + if (ioctl(netfd, TUNSETVNETHDRSZ, &vnet_hdr_sz) != 0) + err(1, "Setting tun header size to %u", vnet_hdr_sz); + memcpy(tapif, ifr.ifr_name, IFNAMSIZ); return netfd; } @@ -2535,12 +2547,12 @@ static void setup_tun_net(char *arg) net_info->tunfd = get_tun_device(tapif); /* First we create a new network device. */ - dev = new_device("net", VIRTIO_ID_NET); + dev = new_pci_device("net", VIRTIO_ID_NET, 0x02, 0x00); dev->priv = net_info; /* Network devices need a recv and a send queue, just like console. */ - add_virtqueue(dev, VIRTQUEUE_NUM, net_input); - add_virtqueue(dev, VIRTQUEUE_NUM, net_output); + add_pci_virtqueue(dev, net_input); + add_pci_virtqueue(dev, net_output); /* * We need a socket to perform the magic network ioctls to bring up the @@ -2560,7 +2572,7 @@ static void setup_tun_net(char *arg) p = strchr(arg, ':'); if (p) { str2mac(p+1, conf.mac); - add_feature(dev, VIRTIO_NET_F_MAC); + add_pci_feature(dev, VIRTIO_NET_F_MAC); *p = '\0'; } @@ -2574,25 +2586,21 @@ static void setup_tun_net(char *arg) configure_device(ipfd, tapif, ip); /* Expect Guest to handle everything except UFO */ - add_feature(dev, VIRTIO_NET_F_CSUM); - add_feature(dev, VIRTIO_NET_F_GUEST_CSUM); - add_feature(dev, VIRTIO_NET_F_GUEST_TSO4); - add_feature(dev, VIRTIO_NET_F_GUEST_TSO6); - add_feature(dev, VIRTIO_NET_F_GUEST_ECN); - add_feature(dev, VIRTIO_NET_F_HOST_TSO4); - add_feature(dev, VIRTIO_NET_F_HOST_TSO6); - add_feature(dev, VIRTIO_NET_F_HOST_ECN); + add_pci_feature(dev, VIRTIO_NET_F_CSUM); + add_pci_feature(dev, VIRTIO_NET_F_GUEST_CSUM); + add_pci_feature(dev, VIRTIO_NET_F_GUEST_TSO4); + add_pci_feature(dev, VIRTIO_NET_F_GUEST_TSO6); + add_pci_feature(dev, VIRTIO_NET_F_GUEST_ECN); + add_pci_feature(dev, VIRTIO_NET_F_HOST_TSO4); + add_pci_feature(dev, VIRTIO_NET_F_HOST_TSO6); + add_pci_feature(dev, VIRTIO_NET_F_HOST_ECN); /* We handle indirect ring entries */ - add_feature(dev, VIRTIO_RING_F_INDIRECT_DESC); - /* We're compliant with the damn spec. */ - add_feature(dev, VIRTIO_F_ANY_LAYOUT); - set_config(dev, sizeof(conf), &conf); + add_pci_feature(dev, VIRTIO_RING_F_INDIRECT_DESC); + set_device_config(dev, &conf, sizeof(conf)); /* We don't need the socket any more; setup is done. */ close(ipfd); - devices.device_num++; - if (bridging) verbose("device %u: tun %s attached to bridge: %s\n", devices.device_num, tapif, arg); -- cgit v1.2.3 From 0d5b5d399f8cecfeebefdd010048b2d608eab463 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:17:01 +1030 Subject: lguest: Convert entropy device to virtio 1.0 PCI. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index b6c88a10a4c9..842c82bd76ff 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -71,7 +71,7 @@ typedef uint8_t u8; #include "../../include/uapi/linux/virtio_net.h" #include "../../include/uapi/linux/virtio_blk.h" #include -#include +#include "../../include/uapi/linux/virtio_rng.h" #include #include "../../include/uapi/linux/virtio_pci.h" #include @@ -2224,7 +2224,6 @@ static void init_pci_config(struct pci_config *pci, u16 type, * * eg : * VIRTIO_ID_CONSOLE: class = 0x07, subclass = 0x00 - * VIRTIO_ID_RNG: class = 0xff, subclass = 0 */ pci->class = class; pci->subclass = subclass; @@ -2816,13 +2815,16 @@ static void setup_rng(void) rng_info->rfd = open_or_die("/dev/urandom", O_RDONLY); /* Create the new device. */ - dev = new_device("rng", VIRTIO_ID_RNG); + dev = new_pci_device("rng", VIRTIO_ID_RNG, 0xff, 0); dev->priv = rng_info; /* The device has one virtqueue, where the Guest places inbufs. */ - add_virtqueue(dev, VIRTQUEUE_NUM, rng_input); + add_pci_virtqueue(dev, rng_input); - verbose("device %u: rng\n", devices.device_num++); + /* We don't have any configuration space */ + no_device_config(dev); + + verbose("device %u: rng\n", devices.device_num); } /* That's the end of device setup. */ -- cgit v1.2.3 From ebff01137acd21534fffaffcf35cd4a3681b95ae Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:18:01 +1030 Subject: lguest: Convert console device to virtio 1.0 PCI. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 842c82bd76ff..fadd5723ba57 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -2221,9 +2221,6 @@ static void init_pci_config(struct pci_config *pci, u16 type, * PCI have specific codes for different types of devices. * Linux doesn't care, but it's a good clue for people looking * at the device. - * - * eg : - * VIRTIO_ID_CONSOLE: class = 0x07, subclass = 0x00 */ pci->class = class; pci->subclass = subclass; @@ -2370,7 +2367,7 @@ static void setup_console(void) tcsetattr(STDIN_FILENO, TCSANOW, &term); } - dev = new_device("console", VIRTIO_ID_CONSOLE); + dev = new_pci_device("console", VIRTIO_ID_CONSOLE, 0x07, 0x00); /* We store the console state in dev->priv, and initialize it. */ dev->priv = malloc(sizeof(struct console_abort)); @@ -2382,10 +2379,13 @@ static void setup_console(void) * stdin. When they put something in the output queue, we write it to * stdout. */ - add_virtqueue(dev, VIRTQUEUE_NUM, console_input); - add_virtqueue(dev, VIRTQUEUE_NUM, console_output); + add_pci_virtqueue(dev, console_input); + add_pci_virtqueue(dev, console_output); + + /* There's no configuration area for this device. */ + no_device_config(dev); - verbose("device %u: console\n", ++devices.device_num); + verbose("device %u: console\n", devices.device_num); } /*:*/ -- cgit v1.2.3 From eb39f83372b45bebc8af59b34af5d35bb0defe53 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:19:01 +1030 Subject: lguest: define VIRTIO_CONFIG_NO_LEGACY in example launcher. We only support virtio 1.0 now Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 1 + 1 file changed, 1 insertion(+) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index fadd5723ba57..663166aff1f5 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -63,6 +63,7 @@ typedef uint16_t u16; typedef uint8_t u8; /*:*/ +#define VIRTIO_CONFIG_NO_LEGACY #define VIRTIO_PCI_NO_LEGACY #define VIRTIO_BLK_NO_LEGACY -- cgit v1.2.3 From d9028eda7b381e57246a53bf9bffc04a4a2920b5 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:21:01 +1030 Subject: lguest: remove support for lguest bus in demonstration launcher. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 355 ++++---------------------------------------------- 1 file changed, 22 insertions(+), 333 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 663166aff1f5..b5ac73525f6d 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -117,14 +117,6 @@ struct device_list { /* Counter to print out convenient device numbers. */ unsigned int device_num; - /* The descriptor page for the devices. */ - u8 *descpage; - - /* A single linked list of devices. */ - struct device *dev; - /* And a pointer to the last device for easy append. */ - struct device *lastdev; - /* PCI devices. */ struct device *pci[MAX_PCI_DEVICES]; }; @@ -170,16 +162,6 @@ struct pci_config { /* The device structure describes a single device. */ struct device { - /* The linked-list pointer. */ - struct device *next; - - /* The device's descriptor, as mapped into the Guest. */ - struct lguest_device_desc *desc; - - /* We can't trust desc values once Guest has booted: we use these. */ - unsigned int feature_len; - unsigned int num_vq; - /* The name of this device, for --verbose. */ const char *name; @@ -216,9 +198,6 @@ struct virtqueue { /* Which device owns me. */ struct device *dev; - /* The configuration for this queue. */ - struct lguest_vqconfig config; - /* The actual ring of buffers. */ struct vring vring; @@ -301,13 +280,6 @@ static void iov_consume(struct iovec iov[], unsigned num_iov, errx(1, "iovec too short!"); } -/* The device virtqueue descriptors are followed by feature bitmasks. */ -static u8 *get_feature_bits(struct device *dev) -{ - return (u8 *)(dev->desc + 1) - + dev->num_vq * sizeof(struct lguest_vqconfig); -} - /*L:100 * The Launcher code itself takes us out into userspace, that scary place where * pointers run wild and free! Unfortunately, like most userspace programs, @@ -378,17 +350,6 @@ static void *map_zeroed_pages(unsigned int num) return addr + getpagesize(); } -/* Get some more pages for a device. */ -static void *get_pages(unsigned int num) -{ - void *addr = from_guest_phys(guest_limit); - - guest_limit += num * getpagesize(); - if (guest_limit > guest_max) - errx(1, "Not enough memory for devices"); - return addr; -} - /* Get some bytes which won't be mapped into the guest. */ static unsigned long get_mmio_region(size_t size) { @@ -701,7 +662,7 @@ static unsigned next_desc(struct vring_desc *desc, */ static void trigger_irq(struct virtqueue *vq) { - unsigned long buf[] = { LHREQ_IRQ, vq->config.irq }; + unsigned long buf[] = { LHREQ_IRQ, vq->dev->config.irq_line }; /* Don't inform them if nothing used. */ if (!vq->pending_used) @@ -713,13 +674,12 @@ static void trigger_irq(struct virtqueue *vq) return; } - /* For a PCI device, set isr to 1 (queue interrupt pending) */ - if (vq->dev->mmio) - vq->dev->mmio->isr = 0x1; + /* Set isr to 1 (queue interrupt pending) */ + vq->dev->mmio->isr = 0x1; /* Send the Guest an interrupt tell them we used something up. */ if (write(lguest_fd, buf, sizeof(buf)) != 0) - err(1, "Triggering irq %i", vq->config.irq); + err(1, "Triggering irq %i", vq->dev->config.irq_line); } /* @@ -1085,21 +1045,18 @@ static void reset_device(struct device *dev) verbose("Resetting device %s\n", dev->name); /* Clear any features they've acked. */ - memset(get_feature_bits(dev) + dev->feature_len, 0, dev->feature_len); + dev->features_accepted = 0; /* We're going to be explicitly killing threads, so ignore them. */ signal(SIGCHLD, SIG_IGN); - /* Zero out the virtqueues, get rid of their threads */ + /* Get rid of the virtqueue threads */ for (vq = dev->vq; vq; vq = vq->next) { if (vq->thread != (pid_t)-1) { kill(vq->thread, SIGTERM); waitpid(vq->thread, NULL, 0); vq->thread = (pid_t)-1; } - memset(vq->vring.desc, 0, - vring_size(vq->config.num, LGUEST_VRING_ALIGN)); - lg_last_avail(vq) = 0; } dev->running = false; @@ -1107,122 +1064,27 @@ static void reset_device(struct device *dev) signal(SIGCHLD, (void *)kill_launcher); } -/*L:216 - * This actually creates the thread which services the virtqueue for a device. - */ -static void create_thread(struct virtqueue *vq) -{ - /* - * Create stack for thread. Since the stack grows upwards, we point - * the stack pointer to the end of this region. - */ - char *stack = malloc(32768); - unsigned long args[] = { LHREQ_EVENTFD, - vq->config.pfn*getpagesize(), 0 }; - - /* Create a zero-initialized eventfd. */ - vq->eventfd = eventfd(0, 0); - if (vq->eventfd < 0) - err(1, "Creating eventfd"); - args[2] = vq->eventfd; - - /* - * Attach an eventfd to this virtqueue: it will go off when the Guest - * does an LHCALL_NOTIFY for this vq. - */ - if (write(lguest_fd, &args, sizeof(args)) != 0) - err(1, "Attaching eventfd"); - - /* - * CLONE_VM: because it has to access the Guest memory, and SIGCHLD so - * we get a signal if it dies. - */ - vq->thread = clone(do_thread, stack + 32768, CLONE_VM | SIGCHLD, vq); - if (vq->thread == (pid_t)-1) - err(1, "Creating clone"); - - /* We close our local copy now the child has it. */ - close(vq->eventfd); -} - -static void start_device(struct device *dev) +static void cleanup_devices(void) { unsigned int i; - struct virtqueue *vq; - - verbose("Device %s OK: offered", dev->name); - for (i = 0; i < dev->feature_len; i++) - verbose(" %02x", get_feature_bits(dev)[i]); - verbose(", accepted"); - for (i = 0; i < dev->feature_len; i++) - verbose(" %02x", get_feature_bits(dev) - [dev->feature_len+i]); - for (vq = dev->vq; vq; vq = vq->next) { - if (vq->service) - create_thread(vq); + for (i = 1; i < MAX_PCI_DEVICES; i++) { + struct device *d = devices.pci[i]; + if (!d) + continue; + reset_device(d); } - dev->running = true; -} - -static void cleanup_devices(void) -{ - struct device *dev; - - for (dev = devices.dev; dev; dev = dev->next) - reset_device(dev); /* If we saved off the original terminal settings, restore them now. */ if (orig_term.c_lflag & (ISIG|ICANON|ECHO)) tcsetattr(STDIN_FILENO, TCSANOW, &orig_term); } -/* When the Guest tells us they updated the status field, we handle it. */ -static void update_device_status(struct device *dev) -{ - /* A zero status is a reset, otherwise it's a set of flags. */ - if (dev->desc->status == 0) - reset_device(dev); - else if (dev->desc->status & VIRTIO_CONFIG_S_FAILED) { - warnx("Device %s configuration FAILED", dev->name); - if (dev->running) - reset_device(dev); - } else { - if (dev->running) - err(1, "Device %s features finalized twice", dev->name); - start_device(dev); - } -} - /*L:215 - * This is the generic routine we call when the Guest uses LHCALL_NOTIFY. In - * particular, it's used to notify us of device status changes during boot. + * This is the generic routine we call when the Guest uses LHCALL_NOTIFY. */ static void handle_output(unsigned long addr) { - struct device *i; - - /* Check each device. */ - for (i = devices.dev; i; i = i->next) { - struct virtqueue *vq; - - /* - * Notifications to device descriptors mean they updated the - * device status. - */ - if (from_guest_phys(addr) == i->desc) { - update_device_status(i); - return; - } - - /* Devices should not be used before features are finalized. */ - for (vq = i->vq; vq; vq = vq->next) { - if (addr != vq->config.pfn*getpagesize()) - continue; - errx(1, "Notification on %s before setup!", i->name); - } - } - /* * Early console write is done using notify on a nul-terminated string * in Guest memory. It's also great for hacking debugging messages @@ -1736,11 +1598,6 @@ static void enable_virtqueue(struct device *d, struct virtqueue *vq) err(1, "Creating clone"); } -static void reset_pci_device(struct device *dev) -{ - /* FIXME */ -} - static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) { struct virtqueue *vq; @@ -1775,7 +1632,7 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) case offsetof(struct virtio_pci_mmio, cfg.device_status): verbose("%s: device status -> %#x\n", d->name, val); if (val == 0) - reset_pci_device(d); + reset_device(d); goto write_through8; case offsetof(struct virtio_pci_mmio, cfg.queue_select): vq = vq_by_num(d, val); @@ -1986,102 +1843,6 @@ static void emulate_mmio(unsigned long paddr, const u8 *insn) * device" so the Launcher can keep track of it. We have common helper * routines to allocate and manage them. */ - -/* - * 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 - * array of configuration bytes. This routine returns the configuration - * pointer. - */ -static u8 *device_config(const struct device *dev) -{ - return (void *)(dev->desc + 1) - + dev->num_vq * sizeof(struct lguest_vqconfig) - + dev->feature_len * 2; -} - -/* - * This routine allocates a new "struct lguest_device_desc" from descriptor - * table page just above the Guest's normal memory. It returns a pointer to - * that descriptor. - */ -static struct lguest_device_desc *new_dev_desc(u16 type) -{ - struct lguest_device_desc d = { .type = type }; - void *p; - - /* Figure out where the next device config is, based on the last one. */ - if (devices.lastdev) - p = device_config(devices.lastdev) - + devices.lastdev->desc->config_len; - else - p = devices.descpage; - - /* We only have one page for all the descriptors. */ - if (p + sizeof(d) > (void *)devices.descpage + getpagesize()) - errx(1, "Too many devices"); - - /* p might not be aligned, so we memcpy in. */ - return memcpy(p, &d, sizeof(d)); -} - -/* - * Each device descriptor is followed by the description of its virtqueues. We - * specify how many descriptors the virtqueue is to have. - */ -static void add_virtqueue(struct device *dev, unsigned int num_descs, - void (*service)(struct virtqueue *)) -{ - unsigned int pages; - struct virtqueue **i, *vq = malloc(sizeof(*vq)); - void *p; - - /* First we need some memory for this virtqueue. */ - pages = (vring_size(num_descs, LGUEST_VRING_ALIGN) + getpagesize() - 1) - / getpagesize(); - p = get_pages(pages); - - /* Initialize the virtqueue */ - vq->next = NULL; - vq->last_avail_idx = 0; - vq->dev = dev; - - /* - * This is the routine the service thread will run, and its Process ID - * once it's running. - */ - vq->service = service; - vq->thread = (pid_t)-1; - - /* Initialize the configuration. */ - vq->config.num = num_descs; - vq->config.irq = devices.next_irq++; - vq->config.pfn = to_guest_phys(p) / getpagesize(); - - /* Initialize the vring. */ - vring_init(&vq->vring, num_descs, p, LGUEST_VRING_ALIGN); - - /* - * Append virtqueue to this device's descriptor. We use - * device_config() to get the end of the device's current virtqueues; - * we check that we haven't added any config or feature information - * yet, otherwise we'd be overwriting them. - */ - assert(dev->desc->config_len == 0 && dev->desc->feature_len == 0); - memcpy(device_config(dev), &vq->config, sizeof(vq->config)); - dev->num_vq++; - dev->desc->num_vq++; - - verbose("Virtqueue page %#lx\n", to_guest_phys(p)); - - /* - * Add to tail of list, so dev->vq is first vq, dev->vq->next is - * second. - */ - for (i = &dev->vq; *i; i = &(*i)->next); - *i = vq; -} - static void add_pci_virtqueue(struct device *dev, void (*service)(struct virtqueue *)) { @@ -2107,9 +1868,6 @@ static void add_pci_virtqueue(struct device *dev, /* Add one to the number of queues */ vq->dev->mmio->cfg.num_queues++; - /* FIXME: Do irq per virtqueue, not per device. */ - vq->config.irq = vq->dev->config.irq_line; - /* * Add to tail of list, so dev->vq is first vq, dev->vq->next is * second. @@ -2118,47 +1876,12 @@ static void add_pci_virtqueue(struct device *dev, *i = vq; } -/* - * The first half of the feature bitmask is for us to advertise features. The - * second half is for the Guest to accept features. - */ -static void add_feature(struct device *dev, unsigned bit) -{ - u8 *features = get_feature_bits(dev); - - /* We can't extend the feature bits once we've added config bytes */ - if (dev->desc->feature_len <= bit / CHAR_BIT) { - assert(dev->desc->config_len == 0); - dev->feature_len = dev->desc->feature_len = (bit/CHAR_BIT) + 1; - } - - features[bit / CHAR_BIT] |= (1 << (bit % CHAR_BIT)); -} - +/* The Guest accesses the feature bits via the PCI common config MMIO region */ static void add_pci_feature(struct device *dev, unsigned bit) { dev->features |= (1ULL << bit); } -/* - * This routine sets the configuration fields for an existing device's - * descriptor. It only works for the last device, but that's OK because that's - * how we use it. - */ -static void set_config(struct device *dev, unsigned len, const void *conf) -{ - /* Check we haven't overflowed our single page. */ - if (device_config(dev) + len > devices.descpage + getpagesize()) - errx(1, "Too many devices"); - - /* Copy in the config information, and store the length. */ - memcpy(device_config(dev), conf, len); - dev->desc->config_len = len; - - /* Size must fit in config_len field (8 bits)! */ - assert(dev->desc->config_len == len); -} - /* For devices with no config. */ static void no_device_config(struct device *dev) { @@ -2287,59 +2010,28 @@ static void init_pci_config(struct pci_config *pci, u16 type, } /* - * This routine does all the creation and setup of a new device, including - * calling new_dev_desc() to allocate the descriptor and device memory. We - * don't actually start the service threads until later. + * This routine does all the creation and setup of a new device, but we don't + * actually place the MMIO region until we know the size (if any) of the + * device-specific config. And we don't actually start the service threads + * until later. * * See what I mean about userspace being boring? */ -static struct device *new_device(const char *name, u16 type) -{ - struct device *dev = malloc(sizeof(*dev)); - - /* Now we populate the fields one at a time. */ - dev->desc = new_dev_desc(type); - dev->name = name; - dev->vq = NULL; - dev->feature_len = 0; - dev->num_vq = 0; - dev->running = false; - dev->next = NULL; - - /* - * Append to device list. Prepending to a single-linked list is - * easier, but the user expects the devices to be arranged on the bus - * in command-line order. The first network device on the command line - * is eth0, the first block device /dev/vda, etc. - */ - if (devices.lastdev) - devices.lastdev->next = dev; - else - devices.dev = dev; - devices.lastdev = dev; - - return dev; -} - static struct device *new_pci_device(const char *name, u16 type, u8 class, u8 subclass) { struct device *dev = malloc(sizeof(*dev)); /* Now we populate the fields one at a time. */ - dev->desc = NULL; dev->name = name; dev->vq = NULL; - dev->feature_len = 0; - dev->num_vq = 0; dev->running = false; - dev->next = NULL; dev->mmio_size = sizeof(struct virtio_pci_mmio); dev->mmio = calloc(1, dev->mmio_size); dev->features = (u64)1 << VIRTIO_F_VERSION_1; dev->features_accepted = 0; - if (devices.device_num + 1 >= 32) + if (devices.device_num + 1 >= MAX_PCI_DEVICES) errx(1, "Can only handle 31 PCI devices"); init_pci_config(&dev->config, type, class, subclass); @@ -2940,11 +2632,9 @@ int main(int argc, char *argv[]) main_args = argv; /* - * First we initialize the device list. We keep a pointer to the last - * device, and the next interrupt number to use for devices (1: - * remember that 0 is used by the timer). + * First we initialize the device list. We remember next interrupt + * number to use for devices (1: remember that 0 is used by the timer). */ - devices.lastdev = NULL; devices.next_irq = 1; /* We're CPU 0. In fact, that's the only CPU possible right now. */ @@ -2969,7 +2659,6 @@ int main(int argc, char *argv[]) + DEVICE_PAGES); guest_limit = mem; guest_max = guest_mmio = mem + DEVICE_PAGES*getpagesize(); - devices.descpage = get_pages(1); break; } } -- cgit v1.2.3 From e8330d9bc1f7af7737500aebd3fc1f488e3dbb71 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:23:01 +1030 Subject: lguest: support emerg_wr in console device in example launcher. This is a magic register which causes a character to be outputted: it can be used even before the device is configured. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index b5ac73525f6d..8959ac246668 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -71,7 +71,7 @@ typedef uint8_t u8; #include "../../include/uapi/linux/virtio_config.h" #include "../../include/uapi/linux/virtio_net.h" #include "../../include/uapi/linux/virtio_blk.h" -#include +#include "../../include/uapi/linux/virtio_console.h" #include "../../include/uapi/linux/virtio_rng.h" #include #include "../../include/uapi/linux/virtio_pci.h" @@ -1687,6 +1687,15 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) goto write_through16; case offsetof(struct virtio_pci_mmio, isr): errx(1, "%s: Unexpected write to isr", d->name); + /* Weird corner case: write to emerg_wr of console */ + case sizeof(struct virtio_pci_mmio) + + offsetof(struct virtio_console_config, emerg_wr): + if (strcmp(d->name, "console") == 0) { + char c = val; + write(STDOUT_FILENO, &c, 1); + goto write_through32; + } + /* Fall through... */ default: errx(1, "%s: Unexpected write to offset %u", d->name, off); } @@ -2048,6 +2057,7 @@ static struct device *new_pci_device(const char *name, u16 type, static void setup_console(void) { struct device *dev; + struct virtio_console_config conf; /* If we can save the initial standard input settings... */ if (tcgetattr(STDIN_FILENO, &orig_term) == 0) { @@ -2075,8 +2085,9 @@ static void setup_console(void) add_pci_virtqueue(dev, console_input); add_pci_virtqueue(dev, console_output); - /* There's no configuration area for this device. */ - no_device_config(dev); + /* We need a configuration area for the emerg_wr early writes. */ + add_pci_feature(dev, VIRTIO_CONSOLE_F_EMERG_WRITE); + set_device_config(dev, &conf, sizeof(conf)); verbose("device %u: console\n", devices.device_num); } -- cgit v1.2.3 From 59eba788db298c3597728774dc3d0f16bdc8a1a4 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:24:01 +1030 Subject: lguest: support backdoor window. The VIRTIO_PCI_CAP_PCI_CFG in the PCI virtio 1.0 spec allows access to the BAR registers without mapping them. This is a compulsory feature, and we implement it here. There are some subtleties involving access widths which we should note: 4.1.4.7.1 Device Requirements: PCI configuration access capability ... Upon detecting driver write access to pci_cfg_data, the device MUST execute a write access at offset cap.offset at BAR selected by cap.bar using the first cap.length bytes from pci_cfg_data. Upon detecting driver read access to pci_cfg_data, the device MUST execute a read access of length cap.length at offset cap.offset at BAR selected by cap.bar and store the first cap.length bytes in pci_cfg_data. So, for a write, we copy into the pci_cfg_data window, then write from there out to the BAR. This works correctly if cap.length != width of write. Similarly, for a read, we read into window from the BAR then read the value from there. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 8959ac246668..e3c4d3d7dc2a 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -156,7 +156,6 @@ struct pci_config { struct virtio_pci_notify_cap notify; struct virtio_pci_cap isr; struct virtio_pci_cap device; - /* FIXME: Implement this! */ struct virtio_pci_cfg_cap cfg_access; }; @@ -1184,6 +1183,36 @@ static struct device *dev_and_reg(u32 *reg) return find_pci_device(pci_config_addr.bits.devnum); } +/* + * We can get invalid combinations of values while they're writing, so we + * only fault if they try to write with some invalid bar/offset/length. + */ +static bool valid_bar_access(struct device *d, + struct virtio_pci_cfg_cap *cfg_access) +{ + /* We only have 1 bar (BAR0) */ + if (cfg_access->cap.bar != 0) + return false; + + /* Check it's within BAR0. */ + if (cfg_access->cap.offset >= d->mmio_size + || cfg_access->cap.offset + cfg_access->cap.length > d->mmio_size) + return false; + + /* Check length is 1, 2 or 4. */ + if (cfg_access->cap.length != 1 + && cfg_access->cap.length != 2 + && cfg_access->cap.length != 4) + return false; + + /* Offset must be multiple of length */ + if (cfg_access->cap.offset % cfg_access->cap.length != 0) + return false; + + /* Return pointer into word in BAR0. */ + return true; +} + /* Is this accessing the PCI config address port?. */ static bool is_pci_addr_port(u16 port) { @@ -1215,6 +1244,8 @@ static bool is_pci_data_port(u16 port) return port >= PCI_CONFIG_DATA && port < PCI_CONFIG_DATA + 4; } +static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask); + static bool pci_data_iowrite(u16 port, u32 mask, u32 val) { u32 reg, portoff; @@ -1255,12 +1286,53 @@ static bool pci_data_iowrite(u16 port, u32 mask, u32 val) && mask == 0xFFFF) { /* Ignore command writes. */ return true; + } else if (&d->config_words[reg] + == (void *)&d->config.cfg_access.cap.bar + || &d->config_words[reg] + == &d->config.cfg_access.cap.length + || &d->config_words[reg] + == &d->config.cfg_access.cap.offset) { + + /* + * The VIRTIO_PCI_CAP_PCI_CFG capability + * provides a backdoor to access the MMIO + * regions without mapping them. Weird, but + * useful. + */ + iowrite(portoff, val, mask, &d->config_words[reg]); + return true; + } else if (&d->config_words[reg] == &d->config.cfg_access.window) { + u32 write_mask; + + /* Must be bar 0 */ + if (!valid_bar_access(d, &d->config.cfg_access)) + return false; + + /* First copy what they wrote into the window */ + iowrite(portoff, val, mask, &d->config.cfg_access.window); + + /* + * Now emulate a write. The mask we use is set by + * len, *not* this write! + */ + write_mask = (1ULL<<(8*d->config.cfg_access.cap.length)) - 1; + verbose("Window writing %#x/%#x to bar %u, offset %u len %u\n", + d->config.cfg_access.window, write_mask, + d->config.cfg_access.cap.bar, + d->config.cfg_access.cap.offset, + d->config.cfg_access.cap.length); + + emulate_mmio_write(d, d->config.cfg_access.cap.offset, + d->config.cfg_access.window, write_mask); + return true; } /* Complain about other writes. */ return false; } +static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask); + static void pci_data_ioread(u16 port, u32 mask, u32 *val) { u32 reg; @@ -1268,6 +1340,33 @@ static void pci_data_ioread(u16 port, u32 mask, u32 *val) if (!d) return; + + /* Read through the PCI MMIO access window is special */ + if (&d->config_words[reg] == &d->config.cfg_access.window) { + u32 read_mask; + + /* Must be bar 0 */ + if (!valid_bar_access(d, &d->config.cfg_access)) + errx(1, "Invalid cfg_access to bar%u, offset %u len %u", + d->config.cfg_access.cap.bar, + d->config.cfg_access.cap.offset, + d->config.cfg_access.cap.length); + + /* + * Read into the window. The mask we use is set by + * len, *not* this read! + */ + read_mask = (1ULL<<(8*d->config.cfg_access.cap.length))-1; + d->config.cfg_access.window + = emulate_mmio_read(d, + d->config.cfg_access.cap.offset, + read_mask); + verbose("Window read %#x/%#x from bar %u, offset %u len %u\n", + d->config.cfg_access.window, read_mask, + d->config.cfg_access.cap.bar, + d->config.cfg_access.cap.offset, + d->config.cfg_access.cap.length); + } ioread(port - PCI_CONFIG_DATA, d->config_words[reg], mask, val); } -- cgit v1.2.3 From 713e3f72244cb67fe1ad5c82a061c0b1be2f2fc5 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:25:01 +1030 Subject: lguest: always put console in PCI slot #1. This simplifies the early probe. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index e3c4d3d7dc2a..7cc1fed1094d 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -2773,6 +2773,9 @@ int main(int argc, char *argv[]) } } + /* We always have a console device, and it's always device 1. */ + setup_console(); + /* The options are fairly straight-forward */ while ((c = getopt_long(argc, argv, "v", opts, NULL)) != EOF) { switch (c) { @@ -2813,9 +2816,6 @@ int main(int argc, char *argv[]) verbose("Guest base is at %p\n", guest_base); - /* We always have a console device */ - setup_console(); - /* Initialize the (fake) PCI host bridge device. */ init_pci_host_bridge(); -- cgit v1.2.3 From 00f8d546512a7661d43600625f87a42a98cae26a Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 11 Feb 2015 15:27:01 +1030 Subject: lguest: remove NOTIFY facility from demonstration launcher. This was only used for early console, now we can get rid of it altogether. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 7cc1fed1094d..5d104321f70f 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -1079,23 +1079,6 @@ static void cleanup_devices(void) tcsetattr(STDIN_FILENO, TCSANOW, &orig_term); } -/*L:215 - * This is the generic routine we call when the Guest uses LHCALL_NOTIFY. - */ -static void handle_output(unsigned long addr) -{ - /* - * Early console write is done using notify on a nul-terminated string - * in Guest memory. It's also great for hacking debugging messages - * into a Guest. - */ - if (addr >= guest_limit) - errx(1, "Bad NOTIFY %#lx", addr); - - write(STDOUT_FILENO, from_guest_phys(addr), - strnlen(from_guest_phys(addr), guest_limit - addr)); -} - /*L:217 * We do PCI. This is mainly done to let us test the kernel virtio PCI * code. @@ -2662,14 +2645,8 @@ static void __attribute__((noreturn)) run_guest(void) /* We read from the /dev/lguest device to run the Guest. */ readval = pread(lguest_fd, ¬ify, sizeof(notify), cpu_id); - - /* One unsigned long means the Guest did HCALL_NOTIFY */ if (readval == sizeof(notify)) { - if (notify.trap == 0x1F) { - verbose("Notify on address %#08x\n", - notify.addr); - handle_output(notify.addr); - } else if (notify.trap == 13) { + if (notify.trap == 13) { verbose("Emulating instruction at %#x\n", getreg(eip)); emulate_insn(notify.insn); -- cgit v1.2.3 From d2dbdac336e8ea1296fd08c4eb8a28daacec1817 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 13 Feb 2015 17:13:40 +1030 Subject: tools/lguest: handle device reset correctly in example launcher. The example launcher doesn't reset the queue_enable like the spec says we have to. Plus, we should reset the size in case they negotiated a different (smaller) one. This is easy to test by unloading and reloading a virtio module. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 5d104321f70f..60cabafdf615 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -1037,6 +1037,12 @@ static void kill_launcher(int signal) kill(0, SIGTERM); } +static void reset_vq_pci_config(struct virtqueue *vq) +{ + vq->pci_config.queue_size = VIRTQUEUE_NUM; + vq->pci_config.queue_enable = 0; +} + static void reset_device(struct device *dev) { struct virtqueue *vq; @@ -1049,8 +1055,19 @@ static void reset_device(struct device *dev) /* We're going to be explicitly killing threads, so ignore them. */ signal(SIGCHLD, SIG_IGN); + /* + * 4.1.4.3.1: + * + * The device MUST present a 0 in queue_enable on reset. + * + * This means we set it here, and reset the saved ones in every vq. + */ + dev->mmio->cfg.queue_enable = 0; + /* Get rid of the virtqueue threads */ for (vq = dev->vq; vq; vq = vq->next) { + vq->last_avail_idx = 0; + reset_vq_pci_config(vq); if (vq->thread != (pid_t)-1) { kill(vq->thread, SIGTERM); waitpid(vq->thread, NULL, 0); @@ -1952,8 +1969,7 @@ static void add_pci_virtqueue(struct device *dev, vq->thread = (pid_t)-1; /* Initialize the configuration. */ - vq->pci_config.queue_size = VIRTQUEUE_NUM; - vq->pci_config.queue_enable = 0; + reset_vq_pci_config(vq); vq->pci_config.queue_notify_off = 0; /* Add one to the number of queues */ -- cgit v1.2.3 From 53aceb49f9b7e1d42064ffff4f4df7e9882b182d Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 13 Feb 2015 17:13:41 +1030 Subject: tools/lguest: fix features_accepted logic in example launcher. We were clearing the lower bits when setting the upper bits. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 60cabafdf615..b3e73f258910 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -1721,7 +1721,7 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) d->features_accepted |= val; } else { assert(d->mmio->cfg.guest_feature_select == 1); - d->features_accepted &= ((u64)0xFFFFFFFF << 32); + d->features_accepted &= 0xFFFFFFFF; d->features_accepted |= ((u64)val) << 32; } if (d->features_accepted & ~d->features) -- cgit v1.2.3 From b2ce1ea4427f0c752f8718a411435cc9527faa3d Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 13 Feb 2015 17:13:41 +1030 Subject: tools/lguest: rename virtio_pci_cfg_cap field to match spec. The next patch will insert many quotes from the virtio 1.0 spec; they make most sense if we copy the spec. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index b3e73f258910..b00263f5febb 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -126,7 +126,7 @@ static struct device_list devices; struct virtio_pci_cfg_cap { struct virtio_pci_cap cap; - u32 window; /* Data for BAR access. */ + u32 pci_cfg_data; /* Data for BAR access. */ }; struct virtio_pci_mmio { @@ -1301,7 +1301,7 @@ static bool pci_data_iowrite(u16 port, u32 mask, u32 val) */ iowrite(portoff, val, mask, &d->config_words[reg]); return true; - } else if (&d->config_words[reg] == &d->config.cfg_access.window) { + } else if (&d->config_words[reg] == &d->config.cfg_access.pci_cfg_data) { u32 write_mask; /* Must be bar 0 */ @@ -1309,7 +1309,7 @@ static bool pci_data_iowrite(u16 port, u32 mask, u32 val) return false; /* First copy what they wrote into the window */ - iowrite(portoff, val, mask, &d->config.cfg_access.window); + iowrite(portoff, val, mask, &d->config.cfg_access.pci_cfg_data); /* * Now emulate a write. The mask we use is set by @@ -1317,13 +1317,14 @@ static bool pci_data_iowrite(u16 port, u32 mask, u32 val) */ write_mask = (1ULL<<(8*d->config.cfg_access.cap.length)) - 1; verbose("Window writing %#x/%#x to bar %u, offset %u len %u\n", - d->config.cfg_access.window, write_mask, + d->config.cfg_access.pci_cfg_data, write_mask, d->config.cfg_access.cap.bar, d->config.cfg_access.cap.offset, d->config.cfg_access.cap.length); emulate_mmio_write(d, d->config.cfg_access.cap.offset, - d->config.cfg_access.window, write_mask); + d->config.cfg_access.pci_cfg_data, + write_mask); return true; } @@ -1342,7 +1343,7 @@ static void pci_data_ioread(u16 port, u32 mask, u32 *val) return; /* Read through the PCI MMIO access window is special */ - if (&d->config_words[reg] == &d->config.cfg_access.window) { + if (&d->config_words[reg] == &d->config.cfg_access.pci_cfg_data) { u32 read_mask; /* Must be bar 0 */ @@ -1357,12 +1358,12 @@ static void pci_data_ioread(u16 port, u32 mask, u32 *val) * len, *not* this read! */ read_mask = (1ULL<<(8*d->config.cfg_access.cap.length))-1; - d->config.cfg_access.window + d->config.cfg_access.pci_cfg_data = emulate_mmio_read(d, d->config.cfg_access.cap.offset, read_mask); verbose("Window read %#x/%#x from bar %u, offset %u len %u\n", - d->config.cfg_access.window, read_mask, + d->config.cfg_access.pci_cfg_data, read_mask, d->config.cfg_access.cap.bar, d->config.cfg_access.cap.offset, d->config.cfg_access.cap.length); -- cgit v1.2.3 From 8dc425ffdd20b3462cfb43eb4f94a7ed8296dd63 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 13 Feb 2015 17:13:41 +1030 Subject: tools/lguest: insert device references from the 1.0 spec (4.1 Virtio Over PCI) There are some (optional) parts we don't implement, but this quotes all the device requirements from the spec (csd 03, but it should be the same across all released versions). Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 131 insertions(+), 9 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index b00263f5febb..10a72b810127 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -673,7 +673,13 @@ static void trigger_irq(struct virtqueue *vq) return; } - /* Set isr to 1 (queue interrupt pending) */ + /* + * 4.1.4.5.1: + * + * If MSI-X capability is disabled, the device MUST set the Queue + * Interrupt bit in ISR status before sending a virtqueue notification + * to the driver. + */ vq->dev->mmio->isr = 0x1; /* Send the Guest an interrupt tell them we used something up. */ @@ -1304,11 +1310,19 @@ static bool pci_data_iowrite(u16 port, u32 mask, u32 val) } else if (&d->config_words[reg] == &d->config.cfg_access.pci_cfg_data) { u32 write_mask; + /* + * 4.1.4.7.1: + * + * Upon detecting driver write access to pci_cfg_data, the + * device MUST execute a write access at offset cap.offset at + * BAR selected by cap.bar using the first cap.length bytes + * from pci_cfg_data. + */ + /* Must be bar 0 */ if (!valid_bar_access(d, &d->config.cfg_access)) return false; - /* First copy what they wrote into the window */ iowrite(portoff, val, mask, &d->config.cfg_access.pci_cfg_data); /* @@ -1346,6 +1360,14 @@ static void pci_data_ioread(u16 port, u32 mask, u32 *val) if (&d->config_words[reg] == &d->config.cfg_access.pci_cfg_data) { u32 read_mask; + /* + * 4.1.4.7.1: + * + * Upon detecting driver read access to pci_cfg_data, the + * device MUST execute a read access of length cap.length at + * offset cap.offset at BAR selected by cap.bar and store the + * first cap.length bytes in pci_cfg_data. + */ /* Must be bar 0 */ if (!valid_bar_access(d, &d->config.cfg_access)) errx(1, "Invalid cfg_access to bar%u, offset %u len %u", @@ -1704,6 +1726,13 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) switch (off) { case offsetof(struct virtio_pci_mmio, cfg.device_feature_select): + /* + * 4.1.4.3.1: + * + * The device MUST present the feature bits it is offering in + * device_feature, starting at bit device_feature_select ∗ 32 + * for any device_feature_select written by the driver + */ if (val == 0) d->mmio->cfg.device_feature = d->features; else if (val == 1) @@ -1731,12 +1760,23 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) goto write_through32; case offsetof(struct virtio_pci_mmio, cfg.device_status): verbose("%s: device status -> %#x\n", d->name, val); + /* + * 4.1.4.3.1: + * + * The device MUST reset when 0 is written to device_status, + * and present a 0 in device_status once that is done. + */ if (val == 0) reset_device(d); goto write_through8; case offsetof(struct virtio_pci_mmio, cfg.queue_select): vq = vq_by_num(d, val); - /* Out of range? Return size 0 */ + /* + * 4.1.4.3.1: + * + * The device MUST present a 0 in queue_size if the virtqueue + * corresponding to the current queue_select is unavailable. + */ if (!vq) { d->mmio->cfg.queue_size = 0; goto write_through16; @@ -1841,6 +1881,17 @@ static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) goto read_through16; case offsetof(struct virtio_pci_mmio, cfg.device_status): case offsetof(struct virtio_pci_mmio, cfg.config_generation): + /* + * 4.1.4.3.1: + * + * The device MUST present a changed config_generation after + * the driver has read a device-specific configuration value + * which has changed since any part of the device-specific + * configuration was last read. + * + * This is simple: none of our devices change config, so this + * is always 0. + */ goto read_through8; case offsetof(struct virtio_pci_mmio, notify): goto read_through16; @@ -1848,8 +1899,12 @@ static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) if (mask != 0xFF) errx(1, "%s: non-8-bit read from offset %u (%#x)", d->name, off, getreg(eip)); - /* Read resets the isr */ isr = d->mmio->isr; + /* + * 4.1.4.5.1: + * + * The device MUST reset ISR status to 0 on driver read. + */ d->mmio->isr = 0; return isr; case offsetof(struct virtio_pci_mmio, padding): @@ -2008,10 +2063,25 @@ static void set_device_config(struct device *dev, const void *conf, size_t len) dev->mmio = realloc(dev->mmio, dev->mmio_size); memcpy(dev->mmio + 1, conf, len); + /* + * 4.1.4.6: + * + * The device MUST present at least one VIRTIO_PCI_CAP_DEVICE_CFG + * capability for any device type which has a device-specific + * configuration. + */ /* Hook up device cfg */ dev->config.cfg_access.cap.cap_next = offsetof(struct pci_config, device); + /* + * 4.1.4.6.1: + * + * The offset for the device-specific configuration MUST be 4-byte + * aligned. + */ + assert(dev->config.cfg_access.cap.cap_next % 4 == 0); + /* Fix up device cfg field length. */ dev->config.device.length = len; @@ -2041,7 +2111,12 @@ static void init_pci_config(struct pci_config *pci, u16 type, { size_t bar_offset, bar_len; - /* Save typing: most thing are happy being zero. */ + /* + * 4.1.4.4.1: + * + * The device MUST either present notify_off_multiplier as an even + * power of 2, or present notify_off_multiplier as 0. + */ memset(pci, 0, sizeof(*pci)); /* 4.1.2.1: Devices MUST have the PCI Vendor ID 0x1AF4 */ @@ -2058,14 +2133,18 @@ static void init_pci_config(struct pci_config *pci, u16 type, pci->subclass = subclass; /* - * 4.1.2.1 Non-transitional devices SHOULD have a PCI Revision - * ID of 1 or higher + * 4.1.2.1: + * + * Non-transitional devices SHOULD have a PCI Revision ID of 1 or + * higher */ pci->revid = 1; /* - * 4.1.2.1 Non-transitional devices SHOULD have a PCI - * Subsystem Device ID of 0x40 or higher. + * 4.1.2.1: + * + * Non-transitional devices SHOULD have a PCI Subsystem Device ID of + * 0x40 or higher. */ pci->subsystem_device_id = 0x40; @@ -2077,17 +2156,48 @@ static void init_pci_config(struct pci_config *pci, u16 type, pci->status = (1 << 4); /* Link them in. */ + /* + * 4.1.4.3.1: + * + * The device MUST present at least one common configuration + * capability. + */ pci->capabilities = offsetof(struct pci_config, common); + /* 4.1.4.3.1 ... offset MUST be 4-byte aligned. */ + assert(pci->capabilities % 4 == 0); + bar_offset = offsetof(struct virtio_pci_mmio, cfg); bar_len = sizeof(((struct virtio_pci_mmio *)0)->cfg); init_cap(&pci->common, sizeof(pci->common), VIRTIO_PCI_CAP_COMMON_CFG, bar_offset, bar_len, offsetof(struct pci_config, notify)); + /* + * 4.1.4.4.1: + * + * The device MUST present at least one notification capability. + */ bar_offset += bar_len; bar_len = sizeof(((struct virtio_pci_mmio *)0)->notify); + + /* + * 4.1.4.4.1: + * + * The cap.offset MUST be 2-byte aligned. + */ + assert(pci->common.cap_next % 2 == 0); + /* FIXME: Use a non-zero notify_off, for per-queue notification? */ + /* + * 4.1.4.4.1: + * + * The value cap.length presented by the device MUST be at least 2 and + * MUST be large enough to support queue notification offsets for all + * supported queues in all possible configurations. + */ + assert(bar_len >= 2); + init_cap(&pci->notify.cap, sizeof(pci->notify), VIRTIO_PCI_CAP_NOTIFY_CFG, bar_offset, bar_len, @@ -2095,11 +2205,23 @@ static void init_pci_config(struct pci_config *pci, u16 type, bar_offset += bar_len; bar_len = sizeof(((struct virtio_pci_mmio *)0)->isr); + /* + * 4.1.4.5.1: + * + * The device MUST present at least one VIRTIO_PCI_CAP_ISR_CFG + * capability. + */ init_cap(&pci->isr, sizeof(pci->isr), VIRTIO_PCI_CAP_ISR_CFG, bar_offset, bar_len, offsetof(struct pci_config, cfg_access)); + /* + * 4.1.4.7.1: + * + * The device MUST present at least one VIRTIO_PCI_CAP_PCI_CFG + * capability. + */ /* This doesn't have any presence in the BAR */ init_cap(&pci->cfg_access.cap, sizeof(pci->cfg_access), VIRTIO_PCI_CAP_PCI_CFG, -- cgit v1.2.3 From c97eb679ef70dbb4482e66b9d192fc9e5bc6e0d6 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 13 Feb 2015 17:13:42 +1030 Subject: tools/lguest: insert driver references from the 1.0 spec (4.1 Virtio Over PCI) As a demonstration, the lguest launcher is pretty strict, trying to catch badly behaved drivers. Document this precisely. A good implementation would *NOT* crash the guest when these happened! Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 10a72b810127..80dc6346030e 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -1211,7 +1211,12 @@ static bool valid_bar_access(struct device *d, && cfg_access->cap.length != 4) return false; - /* Offset must be multiple of length */ + /* + * 4.1.4.7.2: + * + * The driver MUST NOT write a cap.offset which is not a multiple of + * cap.length (ie. all accesses MUST be aligned). + */ if (cfg_access->cap.offset % cfg_access->cap.length != 0) return false; @@ -1342,7 +1347,13 @@ static bool pci_data_iowrite(u16 port, u32 mask, u32 val) return true; } - /* Complain about other writes. */ + /* + * 4.1.4.1: + * + * The driver MUST NOT write into any field of the capability + * structure, with the exception of those with cap_type + * VIRTIO_PCI_CAP_PCI_CFG... + */ return false; } @@ -1789,6 +1800,12 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) restore_vq_config(&d->mmio->cfg, vq); goto write_through16; case offsetof(struct virtio_pci_mmio, cfg.queue_size): + /* + * 4.1.4.3.2: + * + * The driver MUST NOT write a value which is not a power of 2 + * to queue_size. + */ if (val & (val-1)) errx(1, "%s: invalid queue size %u\n", d->name, val); if (d->mmio->cfg.queue_enable) @@ -1799,11 +1816,22 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) errx(1, "%s: attempt to set MSIX vector to %u", d->name, val); case offsetof(struct virtio_pci_mmio, cfg.queue_enable): + /* + * 4.1.4.3.2: + * + * The driver MUST NOT write a 0 to queue_enable. + */ if (val != 1) errx(1, "%s: setting queue_enable to %u", d->name, val); d->mmio->cfg.queue_enable = val; save_vq_config(&d->mmio->cfg, vq_by_num(d, d->mmio->cfg.queue_select)); + /* + * 4.1.4.3.2: + * + * The driver MUST configure the other virtqueue fields before + * enabling the virtqueue with queue_enable. + */ enable_virtqueue(d, vq_by_num(d, d->mmio->cfg.queue_select)); goto write_through16; case offsetof(struct virtio_pci_mmio, cfg.queue_notify_off): @@ -1814,6 +1842,12 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) case offsetof(struct virtio_pci_mmio, cfg.queue_avail_hi): case offsetof(struct virtio_pci_mmio, cfg.queue_used_lo): case offsetof(struct virtio_pci_mmio, cfg.queue_used_hi): + /* + * 4.1.4.3.2: + * + * The driver MUST configure the other virtqueue fields before + * enabling the virtqueue with queue_enable. + */ if (d->mmio->cfg.queue_enable) errx(1, "%s: changing queue on live device", d->name); @@ -1837,9 +1871,23 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) } /* Fall through... */ default: + /* + * 4.1.4.3.2: + * + * The driver MUST NOT write to device_feature, num_queues, + * config_generation or queue_notify_off. + */ errx(1, "%s: Unexpected write to offset %u", d->name, off); } + + /* + * 4.1.3.1: + * + * The driver MUST access each field using the “natural” access + * method, i.e. 32-bit accesses for 32-bit fields, 16-bit accesses for + * 16-bit fields and 8-bit accesses for 8-bit fields. + */ write_through32: if (mask != 0xFFFFFFFF) { errx(1, "%s: non-32-bit write to offset %u (%#x)", @@ -1923,6 +1971,13 @@ static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) goto read_through8; } + /* + * 4.1.3.1: + * + * The driver MUST access each field using the “natural” access + * method, i.e. 32-bit accesses for 32-bit fields, 16-bit accesses for + * 16-bit fields and 8-bit accesses for 8-bit fields. + */ read_through32: if (mask != 0xFFFFFFFF) errx(1, "%s: non-32-bit read to offset %u (%#x)", -- cgit v1.2.3 From 3afe3e0f8db10a41a5923e1d7498318877473f33 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 13 Feb 2015 17:13:42 +1030 Subject: tools/lguest: handle indirect partway through chain. Linux doesn't generate these, but it's perfectly valid according to a close reading of the spec. I opened virtio spec bug VIRTIO-134 to make this clearer there, too. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 80dc6346030e..990671e61f87 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -769,20 +769,21 @@ static unsigned wait_for_vq_desc(struct virtqueue *vq, * that: no rmb() required. */ - /* - * If this is an indirect entry, then this buffer contains a descriptor - * table which we handle as if it's any normal descriptor chain. - */ - if (desc[i].flags & VRING_DESC_F_INDIRECT) { - if (desc[i].len % sizeof(struct vring_desc)) - errx(1, "Invalid size for indirect buffer table"); + do { + /* + * If this is an indirect entry, then this buffer contains a + * descriptor table which we handle as if it's any normal + * descriptor chain. + */ + if (desc[i].flags & VRING_DESC_F_INDIRECT) { + if (desc[i].len % sizeof(struct vring_desc)) + errx(1, "Invalid size for indirect buffer table"); - max = desc[i].len / sizeof(struct vring_desc); - desc = check_pointer(desc[i].addr, desc[i].len); - i = 0; - } + max = desc[i].len / sizeof(struct vring_desc); + desc = check_pointer(desc[i].addr, desc[i].len); + i = 0; + } - do { /* Grab the first descriptor, and check it's OK. */ iov[*out_num + *in_num].iov_len = desc[i].len; iov[*out_num + *in_num].iov_base -- cgit v1.2.3 From d761b0329108c73020a7c95b6fa0d7e82e35fe8b Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 13 Feb 2015 17:13:42 +1030 Subject: tools/lguest: don't start devices until DRIVER_OK status set. We were activating them with the virtqueues, and that's not allowed. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 990671e61f87..4c7c2aa66c89 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -1688,16 +1688,15 @@ static void restore_vq_config(struct virtio_pci_common_cfg *cfg, } /* + * 4.1.4.3.2: + * + * The driver MUST configure the other virtqueue fields before + * enabling the virtqueue with queue_enable. + * * When they enable the virtqueue, we check that their setup is valid. */ -static void enable_virtqueue(struct device *d, struct virtqueue *vq) +static void check_virtqueue(struct device *d, struct virtqueue *vq) { - /* - * Create stack for thread. Since the stack grows upwards, we point - * the stack pointer to the end of this region. - */ - char *stack = malloc(32768); - /* Because lguest is 32 bit, all the descriptor high bits must be 0 */ if (vq->pci_config.queue_desc_hi || vq->pci_config.queue_avail_hi @@ -1716,7 +1715,15 @@ static void enable_virtqueue(struct device *d, struct virtqueue *vq) sizeof(*vq->vring.used) + (sizeof(vq->vring.used->ring[0]) * vq->vring.num)); +} +static void start_virtqueue(struct virtqueue *vq) +{ + /* + * Create stack for thread. Since the stack grows upwards, we point + * the stack pointer to the end of this region. + */ + char *stack = malloc(32768); /* Create a zero-initialized eventfd. */ vq->eventfd = eventfd(0, 0); @@ -1732,6 +1739,16 @@ static void enable_virtqueue(struct device *d, struct virtqueue *vq) err(1, "Creating clone"); } +static void start_virtqueues(struct device *d) +{ + struct virtqueue *vq; + + for (vq = d->vq; vq; vq = vq->next) { + if (vq->pci_config.queue_enable) + start_virtqueue(vq); + } +} + static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) { struct virtqueue *vq; @@ -1780,6 +1797,17 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) */ if (val == 0) reset_device(d); + + /* + * 2.1.2: + * + * The device MUST NOT consume buffers or notify the driver + * before DRIVER_OK. + */ + if (val & VIRTIO_CONFIG_S_DRIVER_OK + && !(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER_OK)) + start_virtqueues(d); + goto write_through8; case offsetof(struct virtio_pci_mmio, cfg.queue_select): vq = vq_by_num(d, val); @@ -1833,7 +1861,7 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) * The driver MUST configure the other virtqueue fields before * enabling the virtqueue with queue_enable. */ - enable_virtqueue(d, vq_by_num(d, d->mmio->cfg.queue_select)); + check_virtqueue(d, vq_by_num(d, d->mmio->cfg.queue_select)); goto write_through16; case offsetof(struct virtio_pci_mmio, cfg.queue_notify_off): errx(1, "%s: attempt to write to queue_notify_off", d->name); -- cgit v1.2.3 From d39a6785f40af658224bc3ff3d4c4a5a2f7c9eda Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 13 Feb 2015 17:13:43 +1030 Subject: tools/lguest: more documentation and checking of virtio 1.0 compliance. This is from all the non-PCI parts of the spec. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 307 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 293 insertions(+), 14 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 4c7c2aa66c89..bc444aff2333 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -170,6 +170,9 @@ struct device { /* Is it operational */ bool running; + /* Has it written FEATURES_OK but not re-checked it? */ + bool wrote_features_ok; + /* PCI configuration */ union { struct pci_config config; @@ -668,7 +671,26 @@ static void trigger_irq(struct virtqueue *vq) return; vq->pending_used = 0; - /* If they don't want an interrupt, don't send one... */ + /* + * 2.4.7.1: + * + * If the VIRTIO_F_EVENT_IDX feature bit is not negotiated: + * The driver MUST set flags to 0 or 1. + */ + if (vq->vring.avail->flags > 1) + errx(1, "%s: avail->flags = %u\n", + vq->dev->name, vq->vring.avail->flags); + + /* + * 2.4.7.2: + * + * If the VIRTIO_F_EVENT_IDX feature bit is not negotiated: + * + * - The device MUST ignore the used_event value. + * - After the device writes a descriptor index into the used ring: + * - If flags is 1, the device SHOULD NOT send an interrupt. + * - If flags is 0, the device MUST send an interrupt. + */ if (vq->vring.avail->flags & VRING_AVAIL_F_NO_INTERRUPT) { return; } @@ -703,6 +725,14 @@ static unsigned wait_for_vq_desc(struct virtqueue *vq, struct vring_desc *desc; u16 last_avail = lg_last_avail(vq); + /* + * 2.4.7.1: + * + * The driver MUST handle spurious interrupts from the device. + * + * That's why this is a while loop. + */ + /* There's nothing available? */ while (last_avail == vq->vring.avail->idx) { u64 event; @@ -776,12 +806,62 @@ static unsigned wait_for_vq_desc(struct virtqueue *vq, * descriptor chain. */ if (desc[i].flags & VRING_DESC_F_INDIRECT) { + /* 2.4.5.3.1: + * + * The driver MUST NOT set the VIRTQ_DESC_F_INDIRECT + * flag unless the VIRTIO_F_INDIRECT_DESC feature was + * negotiated. + */ + if (!(vq->dev->features_accepted & + (1<dev->name); + + /* + * 2.4.5.3.1: + * + * The driver MUST NOT set the VIRTQ_DESC_F_INDIRECT + * flag within an indirect descriptor (ie. only one + * table per descriptor). + */ + if (desc != vq->vring.desc) + errx(1, "%s: Indirect within indirect", + vq->dev->name); + + /* + * Proposed update VIRTIO-134 spells this out: + * + * A driver MUST NOT set both VIRTQ_DESC_F_INDIRECT + * and VIRTQ_DESC_F_NEXT in flags. + */ + if (desc[i].flags & VRING_DESC_F_NEXT) + errx(1, "%s: indirect and next together", + vq->dev->name); + if (desc[i].len % sizeof(struct vring_desc)) errx(1, "Invalid size for indirect buffer table"); + /* + * 2.4.5.3.2: + * + * The device MUST ignore the write-only flag + * (flags&VIRTQ_DESC_F_WRITE) in the descriptor that + * refers to an indirect table. + * + * We ignore it here: :) + */ max = desc[i].len / sizeof(struct vring_desc); desc = check_pointer(desc[i].addr, desc[i].len); i = 0; + + /* 2.4.5.3.1: + * + * A driver MUST NOT create a descriptor chain longer + * than the Queue Size of the device. + */ + if (max > vq->pci_config.queue_size) + errx(1, "%s: indirect has too many entries", + vq->dev->name); } /* Grab the first descriptor, and check it's OK. */ @@ -1082,6 +1162,7 @@ static void reset_device(struct device *dev) } } dev->running = false; + dev->wrote_features_ok = false; /* Now we care if threads die. */ signal(SIGCHLD, (void *)kill_launcher); @@ -1703,6 +1784,18 @@ static void check_virtqueue(struct device *d, struct virtqueue *vq) || vq->pci_config.queue_used_hi) errx(1, "%s: invalid 64-bit queue address", d->name); + /* + * 2.4.1: + * + * The driver MUST ensure that the physical address of the first byte + * of each virtqueue part is a multiple of the specified alignment + * value in the above table. + */ + if (vq->pci_config.queue_desc_lo % 16 + || vq->pci_config.queue_avail_lo % 2 + || vq->pci_config.queue_used_lo % 4) + errx(1, "%s: invalid alignment in queue addresses", d->name); + /* Initialize the virtqueue and check they're all in range. */ vq->vring.num = vq->pci_config.queue_size; vq->vring.desc = check_pointer(vq->pci_config.queue_desc_lo, @@ -1715,6 +1808,16 @@ static void check_virtqueue(struct device *d, struct virtqueue *vq) sizeof(*vq->vring.used) + (sizeof(vq->vring.used->ring[0]) * vq->vring.num)); + + /* + * 2.4.9.1: + * + * The driver MUST initialize flags in the used ring to 0 + * when allocating the used ring. + */ + if (vq->vring.used->flags != 0) + errx(1, "%s: invalid initial used.flags %#x", + d->name, vq->vring.used->flags); } static void start_virtqueue(struct virtqueue *vq) @@ -1768,12 +1871,12 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) d->mmio->cfg.device_feature = (d->features >> 32); else d->mmio->cfg.device_feature = 0; - goto write_through32; + goto feature_write_through32; case offsetof(struct virtio_pci_mmio, cfg.guest_feature_select): if (val > 1) errx(1, "%s: Unexpected driver select %u", d->name, val); - goto write_through32; + goto feature_write_through32; case offsetof(struct virtio_pci_mmio, cfg.guest_feature): if (d->mmio->cfg.guest_feature_select == 0) { d->features_accepted &= ~((u64)0xFFFFFFFF); @@ -1783,11 +1886,19 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) d->features_accepted &= 0xFFFFFFFF; d->features_accepted |= ((u64)val) << 32; } + /* + * 2.2.1: + * + * The driver MUST NOT accept a feature which the device did + * not offer + */ if (d->features_accepted & ~d->features) errx(1, "%s: over-accepted features %#llx of %#llx", d->name, d->features_accepted, d->features); - goto write_through32; - case offsetof(struct virtio_pci_mmio, cfg.device_status): + goto feature_write_through32; + case offsetof(struct virtio_pci_mmio, cfg.device_status): { + u8 prev; + verbose("%s: device status -> %#x\n", d->name, val); /* * 4.1.4.3.1: @@ -1795,8 +1906,15 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) * The device MUST reset when 0 is written to device_status, * and present a 0 in device_status once that is done. */ - if (val == 0) + if (val == 0) { reset_device(d); + goto write_through8; + } + + /* 2.1.1: The driver MUST NOT clear a device status bit. */ + if (d->mmio->cfg.device_status & ~val) + errx(1, "%s: unset of device status bit %#x -> %#x", + d->name, d->mmio->cfg.device_status, val); /* * 2.1.2: @@ -1808,7 +1926,67 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) && !(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER_OK)) start_virtqueues(d); + /* + * 3.1.1: + * + * The driver MUST follow this sequence to initialize a device: + * - Reset the device. + * - Set the ACKNOWLEDGE status bit: the guest OS has + * notice the device. + * - Set the DRIVER status bit: the guest OS knows how + * to drive the device. + * - Read device feature bits, and write the subset + * of feature bits understood by the OS and driver + * to the device. During this step the driver MAY + * read (but MUST NOT write) the device-specific + * configuration fields to check that it can + * support the device before accepting it. + * - Set the FEATURES_OK status bit. The driver + * MUST not accept new feature bits after this + * step. + * - Re-read device status to ensure the FEATURES_OK + * bit is still set: otherwise, the device does + * not support our subset of features and the + * device is unusable. + * - Perform device-specific setup, including + * discovery of virtqueues for the device, + * optional per-bus setup, reading and possibly + * writing the device’s virtio configuration + * space, and population of virtqueues. + * - Set the DRIVER_OK status bit. At this point the + * device is “live”. + */ + prev = 0; + switch (val & ~d->mmio->cfg.device_status) { + case VIRTIO_CONFIG_S_DRIVER_OK: + prev |= VIRTIO_CONFIG_S_FEATURES_OK; /* fall thru */ + case VIRTIO_CONFIG_S_FEATURES_OK: + prev |= VIRTIO_CONFIG_S_DRIVER; /* fall thru */ + case VIRTIO_CONFIG_S_DRIVER: + prev |= VIRTIO_CONFIG_S_ACKNOWLEDGE; /* fall thru */ + case VIRTIO_CONFIG_S_ACKNOWLEDGE: + break; + default: + errx(1, "%s: unknown device status bit %#x -> %#x", + d->name, d->mmio->cfg.device_status, val); + } + if (d->mmio->cfg.device_status != prev) + errx(1, "%s: unexpected status transition %#x -> %#x", + d->name, d->mmio->cfg.device_status, val); + + /* If they just wrote FEATURES_OK, we make sure they read */ + switch (val & ~d->mmio->cfg.device_status) { + case VIRTIO_CONFIG_S_FEATURES_OK: + d->wrote_features_ok = true; + break; + case VIRTIO_CONFIG_S_DRIVER_OK: + if (d->wrote_features_ok) + errx(1, "%s: did not re-read FEATURES_OK", + d->name); + break; + } goto write_through8; + } case offsetof(struct virtio_pci_mmio, cfg.queue_select): vq = vq_by_num(d, val); /* @@ -1844,7 +2022,9 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) case offsetof(struct virtio_pci_mmio, cfg.queue_msix_vector): errx(1, "%s: attempt to set MSIX vector to %u", d->name, val); - case offsetof(struct virtio_pci_mmio, cfg.queue_enable): + case offsetof(struct virtio_pci_mmio, cfg.queue_enable): { + struct virtqueue *vq = vq_by_num(d, d->mmio->cfg.queue_select); + /* * 4.1.4.3.2: * @@ -1852,17 +2032,27 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) */ if (val != 1) errx(1, "%s: setting queue_enable to %u", d->name, val); - d->mmio->cfg.queue_enable = val; - save_vq_config(&d->mmio->cfg, - vq_by_num(d, d->mmio->cfg.queue_select)); + /* - * 4.1.4.3.2: + * 3.1.1: * - * The driver MUST configure the other virtqueue fields before - * enabling the virtqueue with queue_enable. + * 7. Perform device-specific setup, including discovery of + * virtqueues for the device, optional per-bus setup, + * reading and possibly writing the device’s virtio + * configuration space, and population of virtqueues. + * 8. Set the DRIVER_OK status bit. + * + * All our devices require all virtqueues to be enabled, so + * they should have done that before setting DRIVER_OK. */ - check_virtqueue(d, vq_by_num(d, d->mmio->cfg.queue_select)); + if (d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER_OK) + errx(1, "%s: enabling vs after DRIVER_OK", d->name); + + d->mmio->cfg.queue_enable = val; + save_vq_config(&d->mmio->cfg, vq); + check_virtqueue(d, vq); goto write_through16; + } case offsetof(struct virtio_pci_mmio, cfg.queue_notify_off): errx(1, "%s: attempt to write to queue_notify_off", d->name); case offsetof(struct virtio_pci_mmio, cfg.queue_desc_lo): @@ -1880,6 +2070,26 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) if (d->mmio->cfg.queue_enable) errx(1, "%s: changing queue on live device", d->name); + + /* + * 3.1.1: + * + * The driver MUST follow this sequence to initialize a device: + *... + * 5. Set the FEATURES_OK status bit. The driver MUST not + * accept new feature bits after this step. + */ + if (!(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_FEATURES_OK)) + errx(1, "%s: enabling vs before FEATURES_OK", d->name); + + /* + * 6. Re-read device status to ensure the FEATURES_OK bit is + * still set... + */ + if (d->wrote_features_ok) + errx(1, "%s: didn't re-read FEATURES_OK before setup", + d->name); + goto write_through32; case offsetof(struct virtio_pci_mmio, notify): vq = vq_by_num(d, val); @@ -1909,6 +2119,27 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) errx(1, "%s: Unexpected write to offset %u", d->name, off); } +feature_write_through32: + /* + * 3.1.1: + * + * The driver MUST follow this sequence to initialize a device: + *... + * - Set the DRIVER status bit: the guest OS knows how + * to drive the device. + * - Read device feature bits, and write the subset + * of feature bits understood by the OS and driver + * to the device. + *... + * - Set the FEATURES_OK status bit. The driver MUST not + * accept new feature bits after this step. + */ + if (!(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER)) + errx(1, "%s: feature write before VIRTIO_CONFIG_S_DRIVER", + d->name); + if (d->mmio->cfg.device_status & VIRTIO_CONFIG_S_FEATURES_OK) + errx(1, "%s: feature write after VIRTIO_CONFIG_S_FEATURES_OK", + d->name); /* * 4.1.3.1: @@ -1951,12 +2182,29 @@ static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) case offsetof(struct virtio_pci_mmio, cfg.device_feature): case offsetof(struct virtio_pci_mmio, cfg.guest_feature_select): case offsetof(struct virtio_pci_mmio, cfg.guest_feature): + /* + * 3.1.1: + * + * The driver MUST follow this sequence to initialize a device: + *... + * - Set the DRIVER status bit: the guest OS knows how + * to drive the device. + * - Read device feature bits, and write the subset + * of feature bits understood by the OS and driver + * to the device. + */ + if (!(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER)) + errx(1, "%s: feature read before VIRTIO_CONFIG_S_DRIVER", + d->name); goto read_through32; case offsetof(struct virtio_pci_mmio, cfg.msix_config): errx(1, "%s: read of msix_config", d->name); case offsetof(struct virtio_pci_mmio, cfg.num_queues): goto read_through16; case offsetof(struct virtio_pci_mmio, cfg.device_status): + /* As they did read, any write of FEATURES_OK is now fine. */ + d->wrote_features_ok = false; + goto read_through8; case offsetof(struct virtio_pci_mmio, cfg.config_generation): /* * 4.1.4.3.1: @@ -1971,6 +2219,15 @@ static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) */ goto read_through8; case offsetof(struct virtio_pci_mmio, notify): + /* + * 3.1.1: + * + * The driver MUST NOT notify the device before setting + * DRIVER_OK. + */ + if (!(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER_OK)) + errx(1, "%s: notify before VIRTIO_CONFIG_S_DRIVER_OK", + d->name); goto read_through16; case offsetof(struct virtio_pci_mmio, isr): if (mask != 0xFF) @@ -1992,6 +2249,23 @@ static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) if (off > d->mmio_size - 4) errx(1, "%s: read past end (%#x)", d->name, getreg(eip)); + + /* + * 3.1.1: + * The driver MUST follow this sequence to initialize a device: + *... + * 3. Set the DRIVER status bit: the guest OS knows how to + * drive the device. + * 4. Read device feature bits, and write the subset of + * feature bits understood by the OS and driver to the + * device. During this step the driver MAY read (but MUST NOT + * write) the device-specific configuration fields to check + * that it can support the device before accepting it. + */ + if (!(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER)) + errx(1, "%s: config read before VIRTIO_CONFIG_S_DRIVER", + d->name); + if (mask == 0xFFFFFFFF) goto read_through32; else if (mask == 0xFFFF) @@ -2200,6 +2474,10 @@ static void init_pci_config(struct pci_config *pci, u16 type, * * The device MUST either present notify_off_multiplier as an even * power of 2, or present notify_off_multiplier as 0. + * + * 2.1.2: + * + * The device MUST initialize device status to 0 upon reset. */ memset(pci, 0, sizeof(*pci)); @@ -2340,6 +2618,7 @@ static struct device *new_pci_device(const char *name, u16 type, dev->name = name; dev->vq = NULL; dev->running = false; + dev->wrote_features_ok = false; dev->mmio_size = sizeof(struct virtio_pci_mmio); dev->mmio = calloc(1, dev->mmio_size); dev->features = (u64)1 << VIRTIO_F_VERSION_1; -- cgit v1.2.3 From 17c56d6de8e809ac57bf4c93d504f5336eb03dd1 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 13 Feb 2015 17:13:43 +1030 Subject: tools/lguest: give virtqueues names for better error messages Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index bc444aff2333..70ee62a0eb9a 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -200,6 +200,9 @@ struct virtqueue { /* Which device owns me. */ struct device *dev; + /* Name for printing errors. */ + const char *name; + /* The actual ring of buffers. */ struct vring vring; @@ -2366,7 +2369,8 @@ static void emulate_mmio(unsigned long paddr, const u8 *insn) * routines to allocate and manage them. */ static void add_pci_virtqueue(struct device *dev, - void (*service)(struct virtqueue *)) + void (*service)(struct virtqueue *), + const char *name) { struct virtqueue **i, *vq = malloc(sizeof(*vq)); @@ -2374,6 +2378,7 @@ static void add_pci_virtqueue(struct device *dev, vq->next = NULL; vq->last_avail_idx = 0; vq->dev = dev; + vq->name = name; /* * This is the routine the service thread will run, and its Process ID @@ -2666,8 +2671,8 @@ static void setup_console(void) * stdin. When they put something in the output queue, we write it to * stdout. */ - add_pci_virtqueue(dev, console_input); - add_pci_virtqueue(dev, console_output); + add_pci_virtqueue(dev, console_input, "input"); + add_pci_virtqueue(dev, console_output, "output"); /* We need a configuration area for the emerg_wr early writes. */ add_pci_feature(dev, VIRTIO_CONSOLE_F_EMERG_WRITE); @@ -2838,8 +2843,8 @@ static void setup_tun_net(char *arg) dev->priv = net_info; /* Network devices need a recv and a send queue, just like console. */ - add_pci_virtqueue(dev, net_input); - add_pci_virtqueue(dev, net_output); + add_pci_virtqueue(dev, net_input, "rx"); + add_pci_virtqueue(dev, net_output, "tx"); /* * We need a socket to perform the magic network ioctls to bring up the @@ -3026,7 +3031,7 @@ static void setup_block_file(const char *filename) dev = new_pci_device("block", VIRTIO_ID_BLOCK, 0x01, 0x80); /* The device has one virtqueue, where the Guest places requests. */ - add_pci_virtqueue(dev, blk_request); + add_pci_virtqueue(dev, blk_request, "request"); /* Allocate the room for our own bookkeeping */ vblk = dev->priv = malloc(sizeof(*vblk)); @@ -3107,7 +3112,7 @@ static void setup_rng(void) dev->priv = rng_info; /* The device has one virtqueue, where the Guest places inbufs. */ - add_pci_virtqueue(dev, rng_input); + add_pci_virtqueue(dev, rng_input, "input"); /* We don't have any configuration space */ no_device_config(dev); -- cgit v1.2.3 From 1e1c17a7a2e5c585926eefffbea8a61d7a03a247 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 13 Feb 2015 17:13:44 +1030 Subject: tools/lguest: use common error macros in the example launcher. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 206 +++++++++++++++++++++++++------------------------- 1 file changed, 105 insertions(+), 101 deletions(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 70ee62a0eb9a..eebe94b84e8c 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -252,6 +252,16 @@ static struct termios orig_term; #define le32_to_cpu(v32) (v32) #define le64_to_cpu(v64) (v64) +/* + * A real device would ignore weird/non-compliant driver behaviour. We + * stop and flag it, to help debugging Linux problems. + */ +#define bad_driver(d, fmt, ...) \ + errx(1, "%s: bad driver: " fmt, (d)->name, ## __VA_ARGS__) +#define bad_driver_vq(vq, fmt, ...) \ + errx(1, "%s vq %s: bad driver: " fmt, (vq)->dev->name, \ + vq->name, ## __VA_ARGS__) + /* Is this iovec empty? */ static bool iov_empty(const struct iovec iov[], unsigned int num_iov) { @@ -264,7 +274,8 @@ static bool iov_empty(const struct iovec iov[], unsigned int num_iov) } /* Take len bytes from the front of this iovec. */ -static void iov_consume(struct iovec iov[], unsigned num_iov, +static void iov_consume(struct device *d, + struct iovec iov[], unsigned num_iov, void *dest, unsigned len) { unsigned int i; @@ -282,7 +293,7 @@ static void iov_consume(struct iovec iov[], unsigned num_iov, len -= used; } if (len != 0) - errx(1, "iovec too short!"); + bad_driver(d, "iovec too short!"); } /*L:100 @@ -618,7 +629,8 @@ static void tell_kernel(unsigned long start) * we have a convenient routine which checks it and exits with an error message * if something funny is going on: */ -static void *_check_pointer(unsigned long addr, unsigned int size, +static void *_check_pointer(struct device *d, + unsigned long addr, unsigned int size, unsigned int line) { /* @@ -626,7 +638,8 @@ static void *_check_pointer(unsigned long addr, unsigned int size, * or addr + size wraps around. */ if ((addr + size) > guest_limit || (addr + size) < addr) - errx(1, "%s:%i: Invalid address %#lx", __FILE__, line, addr); + bad_driver(d, "%s:%i: Invalid address %#lx", + __FILE__, line, addr); /* * We return a pointer for the caller's convenience, now we know it's * safe to use. @@ -634,14 +647,14 @@ static void *_check_pointer(unsigned long addr, unsigned int size, return from_guest_phys(addr); } /* A macro which transparently hands the line number to the real function. */ -#define check_pointer(addr,size) _check_pointer(addr, size, __LINE__) +#define check_pointer(d,addr,size) _check_pointer(d, addr, size, __LINE__) /* * Each buffer in the virtqueues is actually a chain of descriptors. This * function returns the next descriptor in the chain, or vq->vring.num if we're * at the end. */ -static unsigned next_desc(struct vring_desc *desc, +static unsigned next_desc(struct device *d, struct vring_desc *desc, unsigned int i, unsigned int max) { unsigned int next; @@ -656,7 +669,7 @@ static unsigned next_desc(struct vring_desc *desc, wmb(); if (next >= max) - errx(1, "Desc next is %u", next); + bad_driver(d, "Desc next is %u", next); return next; } @@ -681,8 +694,7 @@ static void trigger_irq(struct virtqueue *vq) * The driver MUST set flags to 0 or 1. */ if (vq->vring.avail->flags > 1) - errx(1, "%s: avail->flags = %u\n", - vq->dev->name, vq->vring.avail->flags); + bad_driver_vq(vq, "avail->flags = %u\n", vq->vring.avail->flags); /* * 2.4.7.2: @@ -769,8 +781,8 @@ static unsigned wait_for_vq_desc(struct virtqueue *vq, /* Check it isn't doing very strange things with descriptor numbers. */ if ((u16)(vq->vring.avail->idx - last_avail) > vq->vring.num) - errx(1, "Guest moved used index from %u to %u", - last_avail, vq->vring.avail->idx); + bad_driver_vq(vq, "Guest moved used index from %u to %u", + last_avail, vq->vring.avail->idx); /* * Make sure we read the descriptor number *after* we read the ring @@ -787,7 +799,7 @@ static unsigned wait_for_vq_desc(struct virtqueue *vq, /* If their number is silly, that's a fatal mistake. */ if (head >= vq->vring.num) - errx(1, "Guest says index %u is available", head); + bad_driver_vq(vq, "Guest says index %u is available", head); /* When we start there are none of either input nor output. */ *out_num = *in_num = 0; @@ -817,8 +829,7 @@ static unsigned wait_for_vq_desc(struct virtqueue *vq, */ if (!(vq->dev->features_accepted & (1<dev->name); + bad_driver_vq(vq, "vq indirect not negotiated"); /* * 2.4.5.3.1: @@ -828,8 +839,7 @@ static unsigned wait_for_vq_desc(struct virtqueue *vq, * table per descriptor). */ if (desc != vq->vring.desc) - errx(1, "%s: Indirect within indirect", - vq->dev->name); + bad_driver_vq(vq, "Indirect within indirect"); /* * Proposed update VIRTIO-134 spells this out: @@ -838,11 +848,11 @@ static unsigned wait_for_vq_desc(struct virtqueue *vq, * and VIRTQ_DESC_F_NEXT in flags. */ if (desc[i].flags & VRING_DESC_F_NEXT) - errx(1, "%s: indirect and next together", - vq->dev->name); + bad_driver_vq(vq, "indirect and next together"); if (desc[i].len % sizeof(struct vring_desc)) - errx(1, "Invalid size for indirect buffer table"); + bad_driver_vq(vq, + "Invalid size for indirect table"); /* * 2.4.5.3.2: * @@ -854,7 +864,7 @@ static unsigned wait_for_vq_desc(struct virtqueue *vq, */ max = desc[i].len / sizeof(struct vring_desc); - desc = check_pointer(desc[i].addr, desc[i].len); + desc = check_pointer(vq->dev, desc[i].addr, desc[i].len); i = 0; /* 2.4.5.3.1: @@ -863,14 +873,14 @@ static unsigned wait_for_vq_desc(struct virtqueue *vq, * than the Queue Size of the device. */ if (max > vq->pci_config.queue_size) - errx(1, "%s: indirect has too many entries", - vq->dev->name); + bad_driver_vq(vq, + "indirect has too many entries"); } /* Grab the first descriptor, and check it's OK. */ iov[*out_num + *in_num].iov_len = desc[i].len; iov[*out_num + *in_num].iov_base - = check_pointer(desc[i].addr, desc[i].len); + = check_pointer(vq->dev, desc[i].addr, desc[i].len); /* If this is an input descriptor, increment that count. */ if (desc[i].flags & VRING_DESC_F_WRITE) (*in_num)++; @@ -880,14 +890,15 @@ static unsigned wait_for_vq_desc(struct virtqueue *vq, * to come before any input descriptors. */ if (*in_num) - errx(1, "Descriptor has out after in"); + bad_driver_vq(vq, + "Descriptor has out after in"); (*out_num)++; } /* If we've got too many, that implies a descriptor loop. */ if (*out_num + *in_num > max) - errx(1, "Looped descriptor"); - } while ((i = next_desc(desc, i, max)) != max); + bad_driver_vq(vq, "Looped descriptor"); + } while ((i = next_desc(vq->dev, desc, i, max)) != max); return head; } @@ -944,7 +955,7 @@ static void console_input(struct virtqueue *vq) /* Make sure there's a descriptor available. */ head = wait_for_vq_desc(vq, iov, &out_num, &in_num); if (out_num) - errx(1, "Output buffers in console in queue?"); + bad_driver_vq(vq, "Output buffers in console in queue?"); /* Read into it. This is where we usually wait. */ len = readv(STDIN_FILENO, iov, in_num); @@ -997,7 +1008,7 @@ static void console_output(struct virtqueue *vq) /* We usually wait in here, for the Guest to give us something. */ head = wait_for_vq_desc(vq, iov, &out, &in); if (in) - errx(1, "Input buffers in console output queue?"); + bad_driver_vq(vq, "Input buffers in console output queue?"); /* writev can return a partial write, so we loop here. */ while (!iov_empty(iov, out)) { @@ -1006,7 +1017,7 @@ static void console_output(struct virtqueue *vq) warn("Write to stdout gave %i (%d)", len, errno); break; } - iov_consume(iov, out, NULL, len); + iov_consume(vq->dev, iov, out, NULL, len); } /* @@ -1035,7 +1046,7 @@ static void net_output(struct virtqueue *vq) /* We usually wait in here for the Guest to give us a packet. */ head = wait_for_vq_desc(vq, iov, &out, &in); if (in) - errx(1, "Input buffers in net output queue?"); + bad_driver_vq(vq, "Input buffers in net output queue?"); /* * Send the whole thing through to /dev/net/tun. It expects the exact * same format: what a coincidence! @@ -1083,7 +1094,7 @@ static void net_input(struct virtqueue *vq) */ head = wait_for_vq_desc(vq, iov, &out, &in); if (out) - errx(1, "Output buffers in net input queue?"); + bad_driver_vq(vq, "Output buffers in net input queue?"); /* * If it looks like we'll block reading from the tun device, send them @@ -1466,7 +1477,8 @@ static void pci_data_ioread(u16 port, u32 mask, u32 *val) */ /* Must be bar 0 */ if (!valid_bar_access(d, &d->config.cfg_access)) - errx(1, "Invalid cfg_access to bar%u, offset %u len %u", + bad_driver(d, + "Invalid cfg_access to bar%u, offset %u len %u", d->config.cfg_access.cap.bar, d->config.cfg_access.cap.offset, d->config.cfg_access.cap.length); @@ -1785,7 +1797,7 @@ static void check_virtqueue(struct device *d, struct virtqueue *vq) if (vq->pci_config.queue_desc_hi || vq->pci_config.queue_avail_hi || vq->pci_config.queue_used_hi) - errx(1, "%s: invalid 64-bit queue address", d->name); + bad_driver_vq(vq, "invalid 64-bit queue address"); /* * 2.4.1: @@ -1797,17 +1809,20 @@ static void check_virtqueue(struct device *d, struct virtqueue *vq) if (vq->pci_config.queue_desc_lo % 16 || vq->pci_config.queue_avail_lo % 2 || vq->pci_config.queue_used_lo % 4) - errx(1, "%s: invalid alignment in queue addresses", d->name); + bad_driver_vq(vq, "invalid alignment in queue addresses"); /* Initialize the virtqueue and check they're all in range. */ vq->vring.num = vq->pci_config.queue_size; - vq->vring.desc = check_pointer(vq->pci_config.queue_desc_lo, + vq->vring.desc = check_pointer(vq->dev, + vq->pci_config.queue_desc_lo, sizeof(*vq->vring.desc) * vq->vring.num); - vq->vring.avail = check_pointer(vq->pci_config.queue_avail_lo, + vq->vring.avail = check_pointer(vq->dev, + vq->pci_config.queue_avail_lo, sizeof(*vq->vring.avail) + (sizeof(vq->vring.avail->ring[0]) * vq->vring.num)); - vq->vring.used = check_pointer(vq->pci_config.queue_used_lo, + vq->vring.used = check_pointer(vq->dev, + vq->pci_config.queue_used_lo, sizeof(*vq->vring.used) + (sizeof(vq->vring.used->ring[0]) * vq->vring.num)); @@ -1819,8 +1834,8 @@ static void check_virtqueue(struct device *d, struct virtqueue *vq) * when allocating the used ring. */ if (vq->vring.used->flags != 0) - errx(1, "%s: invalid initial used.flags %#x", - d->name, vq->vring.used->flags); + bad_driver_vq(vq, "invalid initial used.flags %#x", + vq->vring.used->flags); } static void start_virtqueue(struct virtqueue *vq) @@ -1877,8 +1892,7 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) goto feature_write_through32; case offsetof(struct virtio_pci_mmio, cfg.guest_feature_select): if (val > 1) - errx(1, "%s: Unexpected driver select %u", - d->name, val); + bad_driver(d, "Unexpected driver select %u", val); goto feature_write_through32; case offsetof(struct virtio_pci_mmio, cfg.guest_feature): if (d->mmio->cfg.guest_feature_select == 0) { @@ -1896,8 +1910,8 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) * not offer */ if (d->features_accepted & ~d->features) - errx(1, "%s: over-accepted features %#llx of %#llx", - d->name, d->features_accepted, d->features); + bad_driver(d, "over-accepted features %#llx of %#llx", + d->features_accepted, d->features); goto feature_write_through32; case offsetof(struct virtio_pci_mmio, cfg.device_status): { u8 prev; @@ -1916,8 +1930,8 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) /* 2.1.1: The driver MUST NOT clear a device status bit. */ if (d->mmio->cfg.device_status & ~val) - errx(1, "%s: unset of device status bit %#x -> %#x", - d->name, d->mmio->cfg.device_status, val); + bad_driver(d, "unset of device status bit %#x -> %#x", + d->mmio->cfg.device_status, val); /* * 2.1.2: @@ -1970,12 +1984,12 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) case VIRTIO_CONFIG_S_ACKNOWLEDGE: break; default: - errx(1, "%s: unknown device status bit %#x -> %#x", - d->name, d->mmio->cfg.device_status, val); + bad_driver(d, "unknown device status bit %#x -> %#x", + d->mmio->cfg.device_status, val); } if (d->mmio->cfg.device_status != prev) - errx(1, "%s: unexpected status transition %#x -> %#x", - d->name, d->mmio->cfg.device_status, val); + bad_driver(d, "unexpected status transition %#x -> %#x", + d->mmio->cfg.device_status, val); /* If they just wrote FEATURES_OK, we make sure they read */ switch (val & ~d->mmio->cfg.device_status) { @@ -1984,8 +1998,7 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) break; case VIRTIO_CONFIG_S_DRIVER_OK: if (d->wrote_features_ok) - errx(1, "%s: did not re-read FEATURES_OK", - d->name); + bad_driver(d, "did not re-read FEATURES_OK"); break; } goto write_through8; @@ -2017,14 +2030,12 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) * to queue_size. */ if (val & (val-1)) - errx(1, "%s: invalid queue size %u\n", d->name, val); + bad_driver(d, "invalid queue size %u", val); if (d->mmio->cfg.queue_enable) - errx(1, "%s: changing queue size on live device", - d->name); + bad_driver(d, "changing queue size on live device"); goto write_through16; case offsetof(struct virtio_pci_mmio, cfg.queue_msix_vector): - errx(1, "%s: attempt to set MSIX vector to %u", - d->name, val); + bad_driver(d, "attempt to set MSIX vector to %u", val); case offsetof(struct virtio_pci_mmio, cfg.queue_enable): { struct virtqueue *vq = vq_by_num(d, d->mmio->cfg.queue_select); @@ -2034,7 +2045,7 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) * The driver MUST NOT write a 0 to queue_enable. */ if (val != 1) - errx(1, "%s: setting queue_enable to %u", d->name, val); + bad_driver(d, "setting queue_enable to %u", val); /* * 3.1.1: @@ -2049,7 +2060,7 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) * they should have done that before setting DRIVER_OK. */ if (d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER_OK) - errx(1, "%s: enabling vs after DRIVER_OK", d->name); + bad_driver(d, "enabling vq after DRIVER_OK"); d->mmio->cfg.queue_enable = val; save_vq_config(&d->mmio->cfg, vq); @@ -2057,7 +2068,7 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) goto write_through16; } case offsetof(struct virtio_pci_mmio, cfg.queue_notify_off): - errx(1, "%s: attempt to write to queue_notify_off", d->name); + bad_driver(d, "attempt to write to queue_notify_off"); case offsetof(struct virtio_pci_mmio, cfg.queue_desc_lo): case offsetof(struct virtio_pci_mmio, cfg.queue_desc_hi): case offsetof(struct virtio_pci_mmio, cfg.queue_avail_lo): @@ -2071,8 +2082,7 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) * enabling the virtqueue with queue_enable. */ if (d->mmio->cfg.queue_enable) - errx(1, "%s: changing queue on live device", - d->name); + bad_driver(d, "changing queue on live device"); /* * 3.1.1: @@ -2083,26 +2093,25 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) * accept new feature bits after this step. */ if (!(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_FEATURES_OK)) - errx(1, "%s: enabling vs before FEATURES_OK", d->name); + bad_driver(d, "setting up vq before FEATURES_OK"); /* * 6. Re-read device status to ensure the FEATURES_OK bit is * still set... */ if (d->wrote_features_ok) - errx(1, "%s: didn't re-read FEATURES_OK before setup", - d->name); + bad_driver(d, "didn't re-read FEATURES_OK before setup"); goto write_through32; case offsetof(struct virtio_pci_mmio, notify): vq = vq_by_num(d, val); if (!vq) - errx(1, "Invalid vq notification on %u", val); + bad_driver(d, "Invalid vq notification on %u", val); /* Notify the process handling this vq by adding 1 to eventfd */ write(vq->eventfd, "\1\0\0\0\0\0\0\0", 8); goto write_through16; case offsetof(struct virtio_pci_mmio, isr): - errx(1, "%s: Unexpected write to isr", d->name); + bad_driver(d, "Unexpected write to isr"); /* Weird corner case: write to emerg_wr of console */ case sizeof(struct virtio_pci_mmio) + offsetof(struct virtio_console_config, emerg_wr): @@ -2119,7 +2128,7 @@ static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask) * The driver MUST NOT write to device_feature, num_queues, * config_generation or queue_notify_off. */ - errx(1, "%s: Unexpected write to offset %u", d->name, off); + bad_driver(d, "Unexpected write to offset %u", off); } feature_write_through32: @@ -2138,11 +2147,9 @@ feature_write_through32: * accept new feature bits after this step. */ if (!(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER)) - errx(1, "%s: feature write before VIRTIO_CONFIG_S_DRIVER", - d->name); + bad_driver(d, "feature write before VIRTIO_CONFIG_S_DRIVER"); if (d->mmio->cfg.device_status & VIRTIO_CONFIG_S_FEATURES_OK) - errx(1, "%s: feature write after VIRTIO_CONFIG_S_FEATURES_OK", - d->name); + bad_driver(d, "feature write after VIRTIO_CONFIG_S_FEATURES_OK"); /* * 4.1.3.1: @@ -2153,8 +2160,8 @@ feature_write_through32: */ write_through32: if (mask != 0xFFFFFFFF) { - errx(1, "%s: non-32-bit write to offset %u (%#x)", - d->name, off, getreg(eip)); + bad_driver(d, "non-32-bit write to offset %u (%#x)", + off, getreg(eip)); return; } memcpy((char *)d->mmio + off, &val, 4); @@ -2162,15 +2169,15 @@ write_through32: write_through16: if (mask != 0xFFFF) - errx(1, "%s: non-16-bit (%#x) write to offset %u (%#x)", - d->name, mask, off, getreg(eip)); + bad_driver(d, "non-16-bit write to offset %u (%#x)", + off, getreg(eip)); memcpy((char *)d->mmio + off, &val, 2); return; write_through8: if (mask != 0xFF) - errx(1, "%s: non-8-bit write to offset %u (%#x)", - d->name, off, getreg(eip)); + bad_driver(d, "non-8-bit write to offset %u (%#x)", + off, getreg(eip)); memcpy((char *)d->mmio + off, &val, 1); return; } @@ -2197,11 +2204,11 @@ static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) * to the device. */ if (!(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER)) - errx(1, "%s: feature read before VIRTIO_CONFIG_S_DRIVER", - d->name); + bad_driver(d, + "feature read before VIRTIO_CONFIG_S_DRIVER"); goto read_through32; case offsetof(struct virtio_pci_mmio, cfg.msix_config): - errx(1, "%s: read of msix_config", d->name); + bad_driver(d, "read of msix_config"); case offsetof(struct virtio_pci_mmio, cfg.num_queues): goto read_through16; case offsetof(struct virtio_pci_mmio, cfg.device_status): @@ -2229,13 +2236,12 @@ static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) * DRIVER_OK. */ if (!(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER_OK)) - errx(1, "%s: notify before VIRTIO_CONFIG_S_DRIVER_OK", - d->name); + bad_driver(d, "notify before VIRTIO_CONFIG_S_DRIVER_OK"); goto read_through16; case offsetof(struct virtio_pci_mmio, isr): if (mask != 0xFF) - errx(1, "%s: non-8-bit read from offset %u (%#x)", - d->name, off, getreg(eip)); + bad_driver(d, "non-8-bit read from offset %u (%#x)", + off, getreg(eip)); isr = d->mmio->isr; /* * 4.1.4.5.1: @@ -2245,13 +2251,11 @@ static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) d->mmio->isr = 0; return isr; case offsetof(struct virtio_pci_mmio, padding): - errx(1, "%s: read from padding (%#x)", - d->name, getreg(eip)); + bad_driver(d, "read from padding (%#x)", getreg(eip)); default: /* Read from device config space, beware unaligned overflow */ if (off > d->mmio_size - 4) - errx(1, "%s: read past end (%#x)", - d->name, getreg(eip)); + bad_driver(d, "read past end (%#x)", getreg(eip)); /* * 3.1.1: @@ -2266,8 +2270,8 @@ static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) * that it can support the device before accepting it. */ if (!(d->mmio->cfg.device_status & VIRTIO_CONFIG_S_DRIVER)) - errx(1, "%s: config read before VIRTIO_CONFIG_S_DRIVER", - d->name); + bad_driver(d, + "config read before VIRTIO_CONFIG_S_DRIVER"); if (mask == 0xFFFFFFFF) goto read_through32; @@ -2286,22 +2290,22 @@ static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask) */ read_through32: if (mask != 0xFFFFFFFF) - errx(1, "%s: non-32-bit read to offset %u (%#x)", - d->name, off, getreg(eip)); + bad_driver(d, "non-32-bit read to offset %u (%#x)", + off, getreg(eip)); memcpy(&val, (char *)d->mmio + off, 4); return val; read_through16: if (mask != 0xFFFF) - errx(1, "%s: non-16-bit read to offset %u (%#x)", - d->name, off, getreg(eip)); + bad_driver(d, "non-16-bit read to offset %u (%#x)", + off, getreg(eip)); memcpy(&val, (char *)d->mmio + off, 2); return val; read_through8: if (mask != 0xFF) - errx(1, "%s: non-8-bit read to offset %u (%#x)", - d->name, off, getreg(eip)); + bad_driver(d, "non-8-bit read to offset %u (%#x)", + off, getreg(eip)); memcpy(&val, (char *)d->mmio + off, 1); return val; } @@ -2943,7 +2947,7 @@ static void blk_request(struct virtqueue *vq) head = wait_for_vq_desc(vq, iov, &out_num, &in_num); /* Copy the output header from the front of the iov (adjusts iov) */ - iov_consume(iov, out_num, &out, sizeof(out)); + iov_consume(vq->dev, iov, out_num, &out, sizeof(out)); /* Find and trim end of iov input array, for our status byte. */ in = NULL; @@ -2955,7 +2959,7 @@ static void blk_request(struct virtqueue *vq) } } if (!in) - errx(1, "Bad virtblk cmd with no room for status"); + bad_driver_vq(vq, "Bad virtblk cmd with no room for status"); /* * For historical reasons, block operations are expressed in 512 byte @@ -2985,7 +2989,7 @@ static void blk_request(struct virtqueue *vq) /* Trim it back to the correct length */ ftruncate64(vblk->fd, vblk->len); /* Die, bad Guest, die. */ - errx(1, "Write past end %llu+%u", off, ret); + bad_driver_vq(vq, "Write past end %llu+%u", off, ret); } wlen = sizeof(*in); @@ -3078,7 +3082,7 @@ static void rng_input(struct virtqueue *vq) /* First we need a buffer from the Guests's virtqueue. */ head = wait_for_vq_desc(vq, iov, &out_num, &in_num); if (out_num) - errx(1, "Output buffers in rng?"); + bad_driver_vq(vq, "Output buffers in rng?"); /* * Just like the console write, we loop to cover the whole iovec. @@ -3088,7 +3092,7 @@ static void rng_input(struct virtqueue *vq) len = readv(rng_info->rfd, iov, in_num); if (len <= 0) err(1, "Read from /dev/urandom gave %i", len); - iov_consume(iov, in_num, NULL, len); + iov_consume(vq->dev, iov, in_num, NULL, len); totlen += len; } -- cgit v1.2.3 From 206ad06b2e88a3d826c99da8c8b3ed98e287ad87 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 13 Feb 2015 17:13:44 +1030 Subject: tools/lguest: don't use legacy definitions for net device in example launcher. Signed-off-by: Rusty Russell --- tools/lguest/lguest.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index eebe94b84e8c..e44052483ed9 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -66,6 +66,7 @@ typedef uint8_t u8; #define VIRTIO_CONFIG_NO_LEGACY #define VIRTIO_PCI_NO_LEGACY #define VIRTIO_BLK_NO_LEGACY +#define VIRTIO_NET_NO_LEGACY /* Use in-kernel ones, which defines VIRTIO_F_VERSION_1 */ #include "../../include/uapi/linux/virtio_config.h" @@ -2816,7 +2817,7 @@ static int get_tun_device(char tapif[IFNAMSIZ]) * about our expanded header (which is called * virtio_net_hdr_mrg_rxbuf in the legacy system). */ - vnet_hdr_sz = sizeof(struct virtio_net_hdr_mrg_rxbuf); + vnet_hdr_sz = sizeof(struct virtio_net_hdr_v1); if (ioctl(netfd, TUNSETVNETHDRSZ, &vnet_hdr_sz) != 0) err(1, "Setting tun header size to %u", vnet_hdr_sz); -- cgit v1.2.3