]> Zhao Yanbai Git Server - minix.git/commitdiff
Test set for sys_vumap()
authorDavid van Moolenbroek <david@minix3.org>
Sun, 18 Mar 2012 01:07:18 +0000 (02:07 +0100)
committerDavid van Moolenbroek <david@minix3.org>
Sat, 24 Mar 2012 18:51:14 +0000 (19:51 +0100)
Located in test/kernel. Invoke "run" to run tests.

test/kernel/run [new file with mode: 0755]
test/kernel/sys_vumap/Makefile [new file with mode: 0644]
test/kernel/sys_vumap/Makefile.inc [new file with mode: 0644]
test/kernel/sys_vumap/com.h [new file with mode: 0644]
test/kernel/sys_vumap/run [new file with mode: 0755]
test/kernel/sys_vumap/system.conf [new file with mode: 0644]
test/kernel/sys_vumap/vumaprelay.c [new file with mode: 0644]
test/kernel/sys_vumap/vumaptest.c [new file with mode: 0644]

diff --git a/test/kernel/run b/test/kernel/run
new file mode 100755 (executable)
index 0000000..c1e8b1d
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+tests="sys_vumap"
+
+for i in $tests; do (cd $i && ./run); done
diff --git a/test/kernel/sys_vumap/Makefile b/test/kernel/sys_vumap/Makefile
new file mode 100644 (file)
index 0000000..ef02114
--- /dev/null
@@ -0,0 +1,14 @@
+# Makefile for the sys_vumap test.
+PROG=  vumaptest vumaprelay
+SRCS.vumaptest=        vumaptest.c
+SRCS.vumaprelay= vumaprelay.c
+
+DPADD+=        ${LIBSYS}
+LDADD+=        -lsys
+
+MAN=
+
+BINDIR?= /usr/sbin
+
+.include "Makefile.inc"
+.include <minix.service.mk>
diff --git a/test/kernel/sys_vumap/Makefile.inc b/test/kernel/sys_vumap/Makefile.inc
new file mode 100644 (file)
index 0000000..f166ffe
--- /dev/null
@@ -0,0 +1,5 @@
+# Copied from drivers/Makefile.inc
+CPPFLAGS+= -D_MINIX -D_NETBSD_SOURCE
+LDADD+= -lminlib -lcompat_minix
+DPADD+= ${LIBMINLIB} ${LIBCOMPAT_MINIX}
+BINDIR?=/usr/sbin
diff --git a/test/kernel/sys_vumap/com.h b/test/kernel/sys_vumap/com.h
new file mode 100644 (file)
index 0000000..bec559e
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef _VUMAP_COM_H
+#define _VUMAP_COM_H
+
+#define VTR_RELAY      0x3000          /* SYS_VUMAP relay request */
+
+#define VTR_VGRANT     m10_l1          /* grant for virtual vector */
+#define VTR_VCOUNT     m10_i1          /* nr of elements in virtual vector */
+#define VTR_OFFSET     m10_l2          /* offset into first element */
+#define VTR_ACCESS     m10_i2          /* access flags (VUA_) */
+#define VTR_PGRANT     m10_l3          /* grant for physical vector */
+#define VTR_PCOUNT     m10_i3          /* nr of physical elements (in/out) */
+
+#endif /* _VUMAP_COM_H */
diff --git a/test/kernel/sys_vumap/run b/test/kernel/sys_vumap/run
new file mode 100755 (executable)
index 0000000..18de72e
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+make >/dev/null
+
+echo -n "Kernel test (sys_vumap): "
+service up `pwd`/vumaprelay -config system.conf -label vumaprelay -script /etc/rs.single
+service up `pwd`/vumaptest -config system.conf -script /etc/rs.single 2>/dev/null
+r=$?
+service down vumaprelay
+
+if [ $r -ne 0 ]; then
+  echo "failure"
+  exit 1
+fi
+
+echo "ok"
diff --git a/test/kernel/sys_vumap/system.conf b/test/kernel/sys_vumap/system.conf
new file mode 100644 (file)
index 0000000..53a2720
--- /dev/null
@@ -0,0 +1,12 @@
+service vumaptest {
+       system
+               UMAP            # 14
+               VUMAP           # 18
+       ;
+};
+
+service vumaprelay {
+       system
+               VUMAP           # 18
+       ;
+};
diff --git a/test/kernel/sys_vumap/vumaprelay.c b/test/kernel/sys_vumap/vumaprelay.c
new file mode 100644 (file)
index 0000000..759affb
--- /dev/null
@@ -0,0 +1,77 @@
+/* Test for sys_vumap() - by D.C. van Moolenbroek */
+#include <minix/drivers.h>
+#include <assert.h>
+
+#include "com.h"
+
+PRIVATE int do_request(message *m)
+{
+       struct vumap_vir vvec[MAPVEC_NR + 3];
+       struct vumap_phys pvec[MAPVEC_NR + 3];
+       int r, r2, access, vcount, pcount;
+       size_t offset;
+
+       assert(m->m_type == VTR_RELAY);
+
+       vcount = m->VTR_VCOUNT;
+       pcount = m->VTR_PCOUNT;
+       offset = m->VTR_OFFSET;
+       access = m->VTR_ACCESS;
+
+       r2 = sys_safecopyfrom(m->m_source, m->VTR_VGRANT, 0, (vir_bytes) vvec,
+               sizeof(vvec[0]) * vcount, D);
+       assert(r2 == OK);
+
+       r = sys_vumap(m->m_source, vvec, vcount, offset, access, pvec,
+               &pcount);
+
+       if (pcount >= 1 && pcount <= MAPVEC_NR + 3) {
+               r2 = sys_safecopyto(m->m_source, m->VTR_PGRANT, 0,
+                       (vir_bytes) pvec, sizeof(pvec[0]) * pcount, D);
+               assert(r2 == OK);
+       }
+
+       m->VTR_PCOUNT = pcount;
+
+       return r;
+}
+
+PRIVATE int sef_cb_init_fresh(int UNUSED(type), sef_init_info_t *UNUSED(info))
+{
+       return OK;
+}
+
+PRIVATE void sef_cb_signal_handler(int sig)
+{
+       if (sig == SIGTERM)
+               exit(0);
+}
+
+PRIVATE void sef_local_startup(void)
+{
+       sef_setcb_init_fresh(sef_cb_init_fresh);
+       sef_setcb_signal_handler(sef_cb_signal_handler);
+
+       sef_startup();
+}
+
+PUBLIC int main(int argc, char **argv)
+{
+       message m;
+       int r;
+
+       env_setargs(argc, argv);
+
+       sef_local_startup();
+
+       for (;;) {
+               if ((r = sef_receive(ANY, &m)) != OK)
+                       panic("sef_receive failed (%d)\n", r);
+
+               m.m_type = do_request(&m);
+
+               send(m.m_source, &m);
+       }
+
+       return 0;
+}
diff --git a/test/kernel/sys_vumap/vumaptest.c b/test/kernel/sys_vumap/vumaptest.c
new file mode 100644 (file)
index 0000000..d4cc1c1
--- /dev/null
@@ -0,0 +1,1409 @@
+/* Test for sys_vumap() - by D.C. van Moolenbroek */
+#include <minix/drivers.h>
+#include <minix/ds.h>
+#include <sys/mman.h>
+#include <assert.h>
+
+#include "com.h"
+
+struct buf {
+       int pages;
+       int flags;
+       vir_bytes addr;
+       phys_bytes phys;
+};
+#define BUF_PREALLOC   0x1     /* if set, immediately allocate the page */
+#define BUF_ADJACENT   0x2     /* virtually contiguous with the last buffer */
+
+PRIVATE unsigned int count = 0, failures = 0;
+
+PRIVATE int success;
+PRIVATE char *fail_file;
+PRIVATE int fail_line;
+
+PRIVATE int relay;
+PRIVATE endpoint_t endpt;
+
+PRIVATE int verbose;
+
+PRIVATE enum {
+       GE_NONE,                /* no exception */
+       GE_REVOKED,             /* revoked grant */
+       GE_INVALID              /* invalid grant */
+} grant_exception = GE_NONE;
+
+PRIVATE int grant_access = 0;
+
+#define expect(r)      expect_f((r), __FILE__, __LINE__)
+
+PRIVATE void alloc_buf(struct buf *buf, phys_bytes next)
+{
+       void *tmp = NULL;
+       vir_bytes addr;
+       size_t len;
+       int r, prealloc, flags;
+
+       /* is_allocated() cannot handle buffers that are not physically
+        * contiguous, and we cannot guarantee physical contiguity if not
+        * not preallocating.
+        */
+       assert((buf->flags & BUF_PREALLOC) || buf->pages == 1);
+
+       len = buf->pages * PAGE_SIZE;
+       prealloc = (buf->flags & BUF_PREALLOC);
+       flags = MAP_ANON | (prealloc ? (MAP_CONTIG | MAP_PREALLOC) : 0);
+
+       if (prealloc) {
+               /* Allocate a same-sized piece of memory elsewhere, to make it
+                * very unlikely that the actual piece of memory will end up
+                * being physically contiguous with the last piece.
+                */
+               tmp = minix_mmap((void *) (buf->addr + len + PAGE_SIZE), len,
+                       PROT_READ | PROT_WRITE, MAP_ANON | MAP_PREALLOC |
+                       MAP_CONTIG, -1, 0L);
+
+               if (tmp == MAP_FAILED)
+                       panic("unable to allocate temporary buffer");
+       }
+
+       addr = (vir_bytes) minix_mmap((void *) buf->addr, len,
+               PROT_READ | PROT_WRITE, flags, -1, 0L);
+
+       if (addr != buf->addr)
+               panic("unable to allocate buffer (2)");
+
+       if (!prealloc)
+               return;
+
+       if ((r = minix_munmap(tmp, len)) != OK)
+               panic("unable to unmap buffer (%d)", errno);
+
+       if ((r = sys_umap(SELF, VM_D, addr, len, &buf->phys)) < 0)
+               panic("unable to get physical address of buffer (%d)", r);
+
+       if (buf->phys != next)
+               return;
+
+       if (verbose)
+               printf("WARNING: alloc noncontigous range, second try\n");
+
+       /* Can't remap this to elsewhere, so we run the risk of allocating the
+        * exact same physically contiguous page again. However, now that we've
+        * unmapped the temporary memory also, there's a small chance we'll end
+        * up with a different physical page this time. Who knows.
+        */
+       minix_munmap((void *) addr, len);
+
+       addr = (vir_bytes) minix_mmap((void *) buf->addr, len,
+               PROT_READ | PROT_WRITE, flags, -1, 0L);
+
+       if (addr != buf->addr)
+               panic("unable to allocate buffer, second try");
+
+       if ((r = sys_umap(SELF, VM_D, addr, len, &buf->phys)) < 0)
+               panic("unable to get physical address of buffer (%d)", r);
+
+       /* Still the same page? Screw it. */
+       if (buf->phys == next)
+               panic("unable to allocate noncontiguous range");
+}
+
+PRIVATE void alloc_bufs(struct buf *buf, int count)
+{
+       static vir_bytes base = 0x80000000L;
+       phys_bytes next;
+       int i;
+
+       /* Allocate the given memory in virtually contiguous blocks whenever
+        * each next buffer is requested to be adjacent. Insert a virtual gap
+        * after each such block. Make sure that each two adjacent buffers in a
+        * block are physically non-contiguous.
+        */
+       for (i = 0; i < count; i++) {
+               if (i > 0 && (buf[i].flags & BUF_ADJACENT)) {
+                       next = buf[i-1].phys + buf[i-1].pages * PAGE_SIZE;
+               } else {
+                       base += PAGE_SIZE * 16;
+                       next = 0L;
+               }
+
+               buf[i].addr = base;
+
+               alloc_buf(&buf[i], next);
+
+               base += buf[i].pages * PAGE_SIZE;
+       }
+
+#if DEBUG
+       for (i = 0; i < count; i++)
+               printf("Buf %d: %d pages, flags %x, vir %08x, phys %08x\n", i,
+                       buf[i].pages, buf[i].flags, buf[i].addr, buf[i].phys);
+#endif
+}
+
+PRIVATE void free_bufs(struct buf *buf, int count)
+{
+       int i, j, r;
+
+       for (i = 0; i < count; i++) {
+               for (j = 0; j < buf[i].pages; j++) {
+                       r = minix_munmap((void *) (buf[i].addr + j * PAGE_SIZE),
+                               PAGE_SIZE);
+
+                       if (r != OK)
+                               panic("unable to unmap range (%d)", errno);
+               }
+       }
+}
+
+PRIVATE int is_allocated(vir_bytes addr, size_t bytes, phys_bytes *phys)
+{
+       int r;
+
+       /* This will have to do for now. Of course, we could use sys_vumap with
+        * VUA_READ for this, but that would defeat the point of one test. It
+        * is still a decent alternative in case sys_umap's behavior ever
+        * changes, though.
+        */
+       r = sys_umap(SELF, VM_D, addr, bytes, phys);
+
+       return r == OK;
+}
+
+PRIVATE int is_buf_allocated(struct buf *buf)
+{
+       return is_allocated(buf->addr, buf->pages * PAGE_SIZE, &buf->phys);
+}
+
+PRIVATE void test_group(char *name)
+{
+       if (verbose)
+               printf("Test group: %s (%s)\n",
+                       name, relay ? "relay" : "local");
+}
+
+PRIVATE void expect_f(int res, char *file, int line)
+{
+       if (!res && success) {
+               success = FALSE;
+               fail_file = file;
+               fail_line = line;
+       }
+}
+
+PRIVATE void got_result(char *desc)
+{
+       count++;
+
+       if (!success) {
+               failures++;
+
+               printf("#%02d: %-38s\t[FAIL]\n", count, desc);
+               printf("- failure at %s:%d\n", fail_file, fail_line);
+       } else {
+               if (verbose)
+                       printf("#%02d: %-38s\t[PASS]\n", count, desc);
+       }
+}
+
+PRIVATE int relay_vumap(struct vumap_vir *vvec, int vcount, size_t offset,
+       int access, struct vumap_phys *pvec, int *pcount)
+{
+       struct vumap_vir gvvec[MAPVEC_NR + 3];
+       cp_grant_id_t vgrant, pgrant;
+       message m;
+       int i, r, gaccess;
+
+       assert(vcount > 0 && vcount <= MAPVEC_NR + 3);
+       assert(*pcount > 0 && *pcount <= MAPVEC_NR + 3);
+
+       /* Allow grant access flags to be overridden for testing purposes. */
+       if (!(gaccess = grant_access)) {
+               if (access & VUA_READ) gaccess |= CPF_READ;
+               if (access & VUA_WRITE) gaccess |= CPF_WRITE;
+       }
+
+       for (i = 0; i < vcount; i++) {
+               gvvec[i].vv_grant = cpf_grant_direct(endpt, vvec[i].vv_addr,
+                       vvec[i].vv_size, gaccess);
+               assert(gvvec[i].vv_grant != GRANT_INVALID);
+               gvvec[i].vv_size = vvec[i].vv_size;
+       }
+
+       vgrant = cpf_grant_direct(endpt, (vir_bytes) gvvec,
+               sizeof(gvvec[0]) * vcount, CPF_READ);
+       assert(vgrant != GRANT_INVALID);
+
+       pgrant = cpf_grant_direct(endpt, (vir_bytes) pvec,
+               sizeof(pvec[0]) * *pcount, CPF_WRITE);
+       assert(pgrant != GRANT_INVALID);
+
+       /* This must be done after allocating all other grants. */
+       if (grant_exception != GE_NONE) {
+               cpf_revoke(gvvec[vcount - 1].vv_grant);
+               if (grant_exception == GE_INVALID)
+                       gvvec[vcount - 1].vv_grant = GRANT_INVALID;
+       }
+
+       m.m_type = VTR_RELAY;
+       m.VTR_VGRANT = vgrant;
+       m.VTR_VCOUNT = vcount;
+       m.VTR_OFFSET = offset;
+       m.VTR_ACCESS = access;
+       m.VTR_PGRANT = pgrant;
+       m.VTR_PCOUNT = *pcount;
+
+       r = sendrec(endpt, &m);
+
+       cpf_revoke(pgrant);
+       cpf_revoke(vgrant);
+
+       for (i = 0; i < vcount - !!grant_exception; i++)
+               cpf_revoke(gvvec[i].vv_grant);
+
+       *pcount = m.VTR_PCOUNT;
+
+       return (r != OK) ? r : m.m_type;
+}
+
+PRIVATE int do_vumap(endpoint_t endpt, struct vumap_vir *vvec, int vcount,
+       size_t offset, int access, struct vumap_phys *pvec, int *pcount)
+{
+       struct vumap_phys pv_backup[MAPVEC_NR + 3];
+       int r, pc_backup, pv_test = FALSE;
+
+       /* Make a copy of pvec and pcount for later. */
+       pc_backup = *pcount;
+
+       /* We cannot compare pvec contents before and after when relaying,
+        * since the original contents are not transferred.
+        */
+       if (!relay && pvec != NULL && pc_backup >= 1 &&
+                       pc_backup <= MAPVEC_NR + 3) {
+               pv_test = TRUE;
+               memcpy(pv_backup, pvec, sizeof(*pvec) * pc_backup);
+       }
+
+       /* Reset the test result. */
+       success = TRUE;
+
+       /* Perform the vumap call, either directly or through a relay. */
+       if (relay) {
+               assert(endpt == SELF);
+               r = relay_vumap(vvec, vcount, offset, access, pvec, pcount);
+       } else {
+               r = sys_vumap(endpt, vvec, vcount, offset, access, pvec,
+                       pcount);
+       }
+
+       /* Upon failure, pvec and pcount must be unchanged. */
+       if (r != OK) {
+               expect(pc_backup == *pcount);
+
+               if (pv_test)
+                       expect(memcmp(pv_backup, pvec,
+                               sizeof(*pvec) * pc_backup) == 0);
+       }
+
+       return r;
+}
+
+PRIVATE void test_basics(void)
+{
+       struct vumap_vir vvec[2];
+       struct vumap_phys pvec[4];
+       struct buf buf[4];
+       int r, pcount;
+
+       test_group("basics");
+
+       buf[0].pages = 1;
+       buf[0].flags = BUF_PREALLOC;
+       buf[1].pages = 2;
+       buf[1].flags = BUF_PREALLOC;
+       buf[2].pages = 1;
+       buf[2].flags = BUF_PREALLOC;
+       buf[3].pages = 1;
+       buf[3].flags = BUF_PREALLOC | BUF_ADJACENT;
+
+       alloc_bufs(buf, 4);
+
+       /* Test single whole page. */
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE;
+       pcount = 1;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 1);
+       expect(pvec[0].vp_addr == buf[0].phys);
+       expect(pvec[0].vp_size == vvec[0].vv_size);
+
+       got_result("single whole page");
+
+       /* Test single partial page. */
+       vvec[0].vv_addr = buf[0].addr + 123;
+       vvec[0].vv_size = PAGE_SIZE - 456;
+       pcount = 1;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 1);
+       expect(pvec[0].vp_addr == buf[0].phys + 123);
+       expect(pvec[0].vp_size == vvec[0].vv_size);
+
+       got_result("single partial page");
+
+       /* Test multiple contiguous whole pages. */
+       vvec[0].vv_addr = buf[1].addr;
+       vvec[0].vv_size = PAGE_SIZE * 2;
+       pcount = 1;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 1);
+       expect(pvec[0].vp_addr == buf[1].phys);
+       expect(pvec[0].vp_size == vvec[0].vv_size);
+
+       got_result("multiple contiguous whole pages");
+
+       /* Test range in multiple contiguous pages. */
+       vvec[0].vv_addr = buf[1].addr + 234;
+       vvec[0].vv_size = PAGE_SIZE * 2 - 234;
+       pcount = 2;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 1);
+       expect(pvec[0].vp_addr == buf[1].phys + 234);
+       expect(pvec[0].vp_size == vvec[0].vv_size);
+
+       got_result("range in multiple contiguous pages");
+
+       /* Test multiple noncontiguous whole pages. */
+       vvec[0].vv_addr = buf[2].addr;
+       vvec[0].vv_size = PAGE_SIZE * 2;
+       pcount = 3;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 2);
+       expect(pvec[0].vp_addr == buf[2].phys);
+       expect(pvec[0].vp_size == PAGE_SIZE);
+       expect(pvec[1].vp_addr == buf[3].phys);
+       expect(pvec[1].vp_size == PAGE_SIZE);
+
+       got_result("multiple noncontiguous whole pages");
+
+       /* Test range in multiple noncontiguous pages. */
+       vvec[0].vv_addr = buf[2].addr + 1;
+       vvec[0].vv_size = PAGE_SIZE * 2 - 2;
+       pcount = 2;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_WRITE, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 2);
+       expect(pvec[0].vp_addr == buf[2].phys + 1);
+       expect(pvec[0].vp_size == PAGE_SIZE - 1);
+       expect(pvec[1].vp_addr == buf[3].phys);
+       expect(pvec[1].vp_size == PAGE_SIZE - 1);
+
+       got_result("range in multiple noncontiguous pages");
+
+       /* Test single-input result truncation. */
+       vvec[0].vv_addr = buf[2].addr + PAGE_SIZE / 2;
+       vvec[0].vv_size = PAGE_SIZE;
+       pvec[1].vp_addr = 0L;
+       pvec[1].vp_size = 0;
+       pcount = 1;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 1);
+       expect(pvec[0].vp_addr == buf[2].phys + PAGE_SIZE / 2);
+       expect(pvec[0].vp_size == PAGE_SIZE / 2);
+       expect(pvec[1].vp_addr == 0L);
+       expect(pvec[1].vp_size == 0);
+
+       got_result("single-input result truncation");
+
+       /* Test multiple inputs, contiguous first. */
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE;
+       vvec[1].vv_addr = buf[2].addr + PAGE_SIZE - 1;
+       vvec[1].vv_size = 2;
+       pcount = 3;
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 3);
+       expect(pvec[0].vp_addr == buf[0].phys);
+       expect(pvec[0].vp_size == PAGE_SIZE);
+       expect(pvec[1].vp_addr == buf[2].phys + PAGE_SIZE - 1);
+       expect(pvec[1].vp_size == 1);
+       expect(pvec[2].vp_addr == buf[3].phys);
+       expect(pvec[2].vp_size == 1);
+
+       got_result("multiple inputs, contiguous first");
+
+       /* Test multiple inputs, contiguous last. */
+       vvec[0].vv_addr = buf[2].addr + 123;
+       vvec[0].vv_size = PAGE_SIZE * 2 - 456;
+       vvec[1].vv_addr = buf[1].addr + 234;
+       vvec[1].vv_size = PAGE_SIZE * 2 - 345;
+       pcount = 4;
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_WRITE, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 3);
+       expect(pvec[0].vp_addr == buf[2].phys + 123);
+       expect(pvec[0].vp_size == PAGE_SIZE - 123);
+       expect(pvec[1].vp_addr == buf[3].phys);
+       expect(pvec[1].vp_size == PAGE_SIZE - (456 - 123));
+       expect(pvec[2].vp_addr == buf[1].phys + 234);
+       expect(pvec[2].vp_size == vvec[1].vv_size);
+
+       got_result("multiple inputs, contiguous last");
+
+       /* Test multiple-inputs result truncation. */
+       vvec[0].vv_addr = buf[2].addr + 2;
+       vvec[0].vv_size = PAGE_SIZE * 2 - 3;
+       vvec[1].vv_addr = buf[0].addr;
+       vvec[1].vv_size = 135;
+       pvec[2].vp_addr = 0xDEADBEEFL;
+       pvec[2].vp_size = 1234;
+       pcount = 2;
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 2);
+       expect(pvec[0].vp_addr == buf[2].phys + 2);
+       expect(pvec[0].vp_size == PAGE_SIZE - 2);
+       expect(pvec[1].vp_addr == buf[3].phys);
+       expect(pvec[1].vp_size == PAGE_SIZE - 1);
+       expect(pvec[2].vp_addr == 0xDEADBEEFL);
+       expect(pvec[2].vp_size == 1234);
+
+       got_result("multiple-inputs result truncation");
+
+       free_bufs(buf, 4);
+}
+
+PRIVATE void test_endpt(void)
+{
+       struct vumap_vir vvec[1];
+       struct vumap_phys pvec[1];
+       struct buf buf[1];
+       int r, pcount;
+
+       test_group("endpoint");
+
+       buf[0].pages = 1;
+       buf[0].flags = BUF_PREALLOC;
+
+       alloc_bufs(buf, 1);
+
+       /* Test NONE endpoint. */
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE;
+       pcount = 1;
+
+       r = do_vumap(NONE, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EINVAL);
+
+       got_result("NONE endpoint");
+
+       /* Test ANY endpoint. */
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE;
+       pcount = 1;
+
+       r = do_vumap(ANY, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EINVAL);
+
+       got_result("ANY endpoint");
+
+       free_bufs(buf, 1);
+}
+
+PRIVATE void test_vector1(void)
+{
+       struct vumap_vir vvec[2];
+       struct vumap_phys pvec[3];
+       struct buf buf[2];
+       int r, pcount;
+
+       test_group("vector, part 1");
+
+       buf[0].pages = 2;
+       buf[0].flags = BUF_PREALLOC;
+       buf[1].pages = 1;
+       buf[1].flags = BUF_PREALLOC;
+
+       alloc_bufs(buf, 2);
+
+       /* Test zero virtual memory size. */
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE * 2;
+       vvec[1].vv_addr = buf[1].addr;
+       vvec[1].vv_size = 0;
+       pcount = 3;
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EINVAL);
+
+       got_result("zero virtual memory size");
+
+       /* Test excessive virtual memory size. */
+       vvec[1].vv_size = (vir_bytes) -1;
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EFAULT || r == EPERM);
+
+       got_result("excessive virtual memory size");
+
+       /* Test invalid virtual memory. */
+       vvec[1].vv_addr = 0L;
+       vvec[1].vv_size = PAGE_SIZE;
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EFAULT);
+
+       got_result("invalid virtual memory");
+
+       /* Test virtual memory overrun. */
+       vvec[0].vv_size++;
+       vvec[1].vv_addr = buf[1].addr;
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EFAULT);
+
+       got_result("virtual memory overrun");
+
+       free_bufs(buf, 2);
+}
+
+PRIVATE void test_vector2(void)
+{
+       struct vumap_vir vvec[2], *vvecp;
+       struct vumap_phys pvec[3], *pvecp;
+       struct buf buf[2];
+       phys_bytes dummy;
+       int r, pcount;
+
+       test_group("vector, part 2");
+
+       buf[0].pages = 2;
+       buf[0].flags = BUF_PREALLOC;
+       buf[1].pages = 1;
+       buf[1].flags = BUF_PREALLOC;
+
+       alloc_bufs(buf, 2);
+
+       /* Test zero virtual count. */
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE * 2;
+       vvec[1].vv_addr = buf[1].addr;
+       vvec[1].vv_size = PAGE_SIZE;
+       pcount = 3;
+
+       r = do_vumap(SELF, vvec, 0, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EINVAL);
+
+       got_result("zero virtual count");
+
+       /* Test negative virtual count. */
+       r = do_vumap(SELF, vvec, -1, 0, VUA_WRITE, pvec, &pcount);
+
+       expect(r == EINVAL);
+
+       got_result("negative virtual count");
+
+       /* Test zero physical count. */
+       pcount = 0;
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_WRITE, pvec, &pcount);
+
+       expect(r == EINVAL);
+
+       got_result("zero physical count");
+
+       /* Test negative physical count. */
+       pcount = -1;
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EINVAL);
+
+       got_result("negative physical count");
+
+       /* Test invalid virtual vector pointer. */
+       pcount = 2;
+
+       r = do_vumap(SELF, NULL, 2, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EFAULT);
+
+       got_result("invalid virtual vector pointer");
+
+       /* Test unallocated virtual vector. */
+       vvecp = (struct vumap_vir *) minix_mmap(NULL, PAGE_SIZE,
+               PROT_READ | PROT_WRITE, MAP_ANON, -1, 0L);
+
+       if (vvecp == MAP_FAILED)
+               panic("unable to allocate virtual vector");
+
+       r = do_vumap(SELF, vvecp, 2, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EFAULT);
+       expect(!is_allocated((vir_bytes) vvecp, PAGE_SIZE, &dummy));
+
+       got_result("unallocated virtual vector pointer");
+
+       minix_munmap((void *) vvecp, PAGE_SIZE);
+
+       /* Test invalid physical vector pointer. */
+       r = do_vumap(SELF, vvec, 2, 0, VUA_READ, NULL, &pcount);
+
+       expect(r == EFAULT);
+
+       got_result("invalid physical vector pointer");
+
+       /* Test unallocated physical vector. */
+       pvecp = (struct vumap_phys *) minix_mmap(NULL, PAGE_SIZE,
+               PROT_READ | PROT_WRITE, MAP_ANON, -1, 0L);
+
+       if (pvecp == MAP_FAILED)
+               panic("unable to allocate physical vector");
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_READ, pvecp, &pcount);
+
+       expect(r == OK);
+       expect(is_allocated((vir_bytes) pvecp, PAGE_SIZE, &dummy));
+       expect(pcount == 2);
+       expect(pvecp[0].vp_size == PAGE_SIZE * 2);
+       expect(pvecp[0].vp_addr == buf[0].phys);
+       expect(pvecp[1].vp_size == PAGE_SIZE);
+       expect(pvecp[1].vp_addr == buf[1].phys);
+
+       got_result("unallocated physical vector pointer");
+
+       minix_munmap((void *) pvecp, PAGE_SIZE);
+
+       free_bufs(buf, 2);
+}
+
+PRIVATE void test_grant(void)
+{
+       struct vumap_vir vvec[2];
+       struct vumap_phys pvec[3];
+       struct buf buf[2];
+       int r, pcount;
+
+       test_group("grant");
+
+       buf[0].pages = 1;
+       buf[0].flags = BUF_PREALLOC;
+       buf[1].pages = 2;
+       buf[1].flags = BUF_PREALLOC;
+
+       alloc_bufs(buf, 2);
+
+       /* Test write-only access on read-only grant. */
+       grant_access = CPF_READ; /* override */
+
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE;
+       pcount = 1;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_WRITE, pvec, &pcount);
+
+       expect(r == EPERM);
+
+       got_result("write-only access on read-only grant");
+
+       /* Test read-write access on read-only grant. */
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ | VUA_WRITE, pvec, &pcount);
+
+       expect(r == EPERM);
+
+       got_result("read-write access on read-only grant");
+
+       /* Test read-only access on write-only grant. */
+       grant_access = CPF_WRITE; /* override */
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EPERM);
+
+       got_result("read-only access on write-only grant");
+
+       /* Test read-write access on write grant. */
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ | VUA_WRITE, pvec, &pcount);
+
+       expect(r == EPERM);
+
+       got_result("read-write access on write-only grant");
+
+       /* Test read-only access on read-write grant. */
+       grant_access = CPF_READ | CPF_WRITE; /* override */
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 1);
+       expect(pvec[0].vp_size == PAGE_SIZE);
+       expect(pvec[0].vp_addr == buf[0].phys);
+
+       got_result("read-only access on read-write grant");
+
+       grant_access = 0; /* reset */
+
+       /* Test invalid grant. */
+       grant_exception = GE_INVALID;
+
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE;
+       vvec[1].vv_addr = buf[1].addr;
+       vvec[1].vv_size = PAGE_SIZE * 2;
+       pcount = 3;
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EINVAL);
+
+       got_result("invalid grant");
+
+       /* Test revoked grant. */
+       grant_exception = GE_REVOKED;
+
+       r = do_vumap(SELF, vvec, 2, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EPERM);
+
+       got_result("revoked grant");
+
+       grant_exception = GE_NONE;
+
+       free_bufs(buf, 2);
+}
+
+PRIVATE void test_offset(void)
+{
+       struct vumap_vir vvec[2];
+       struct vumap_phys pvec[3];
+       struct buf buf[4];
+       size_t off, off2;
+       int r, pcount;
+
+       test_group("offsets");
+
+       buf[0].pages = 1;
+       buf[0].flags = BUF_PREALLOC;
+       buf[1].pages = 2;
+       buf[1].flags = BUF_PREALLOC;
+       buf[2].pages = 1;
+       buf[2].flags = BUF_PREALLOC;
+       buf[3].pages = 1;
+       buf[3].flags = BUF_PREALLOC | BUF_ADJACENT;
+
+       alloc_bufs(buf, 4);
+
+       /* Test offset into aligned page. */
+       off = 123;
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE;
+       pcount = 2;
+
+       r = do_vumap(SELF, vvec, 1, off, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 1);
+       expect(pvec[0].vp_addr == buf[0].phys + off);
+       expect(pvec[0].vp_size == vvec[0].vv_size - off);
+
+       got_result("offset into aligned page");
+
+       /* Test offset into unaligned page. */
+       off2 = 456;
+       assert(off + off2 < PAGE_SIZE);
+       vvec[0].vv_addr = buf[0].addr + off;
+       vvec[0].vv_size = PAGE_SIZE - off;
+       pcount = 2;
+
+       r = do_vumap(SELF, vvec, 1, off2, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 1);
+       expect(pvec[0].vp_addr == buf[0].phys + off + off2);
+       expect(pvec[0].vp_size == vvec[0].vv_size - off2);
+
+       got_result("offset into unaligned page");
+
+       /* Test offset into unaligned page set. */
+       off = 1234;
+       off2 = 567;
+       assert(off + off2 < PAGE_SIZE);
+       vvec[0].vv_addr = buf[1].addr + off;
+       vvec[0].vv_size = (PAGE_SIZE - off) * 2;
+       pcount = 3;
+
+       r = do_vumap(SELF, vvec, 1, off2, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 1);
+       expect(pvec[0].vp_addr == buf[1].phys + off + off2);
+       expect(pvec[0].vp_size == vvec[0].vv_size - off2);
+
+       got_result("offset into contiguous page set");
+
+       /* Test offset into noncontiguous page set. */
+       vvec[0].vv_addr = buf[2].addr + off;
+       vvec[0].vv_size = (PAGE_SIZE - off) * 2;
+       pcount = 3;
+
+       r = do_vumap(SELF, vvec, 1, off2, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 2);
+       expect(pvec[0].vp_addr == buf[2].phys + off + off2);
+       expect(pvec[0].vp_size == PAGE_SIZE - off - off2);
+       expect(pvec[1].vp_addr == buf[3].phys);
+       expect(pvec[1].vp_size == PAGE_SIZE - off);
+
+       got_result("offset into noncontiguous page set");
+
+       /* Test offset to last byte. */
+       off = PAGE_SIZE - off2 - 1;
+       vvec[0].vv_addr = buf[0].addr + off2;
+       vvec[0].vv_size = PAGE_SIZE - off2;
+       pcount = 2;
+
+       r = do_vumap(SELF, vvec, 1, off, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 1);
+       expect(pvec[0].vp_addr == buf[0].phys + off + off2);
+       expect(pvec[0].vp_size == 1);
+
+       got_result("offset to last byte");
+
+       /* Test offset at range end. */
+       off = 234;
+       vvec[0].vv_addr = buf[1].addr + off;
+       vvec[0].vv_size = PAGE_SIZE - off * 2;
+       vvec[1].vv_addr = vvec[0].vv_addr + vvec[0].vv_size;
+       vvec[1].vv_size = off;
+
+       r = do_vumap(SELF, vvec, 2, vvec[0].vv_size, VUA_READ, pvec, &pcount);
+
+       expect(r == EINVAL);
+
+       got_result("offset at range end");
+
+       /* Test offset beyond range end. */
+       vvec[0].vv_addr = buf[1].addr;
+       vvec[0].vv_size = PAGE_SIZE;
+       vvec[1].vv_addr = buf[1].addr + PAGE_SIZE;
+       vvec[1].vv_size = PAGE_SIZE;
+
+       r = do_vumap(SELF, vvec, 2, PAGE_SIZE + off, VUA_READ, pvec, &pcount);
+
+       expect(r == EINVAL);
+
+       got_result("offset beyond range end");
+
+       /* Test negative offset. */
+       vvec[0].vv_addr = buf[1].addr + off + off2;
+       vvec[0].vv_size = PAGE_SIZE;
+
+       r = do_vumap(SELF, vvec, 1, (size_t) -1, VUA_READ, pvec, &pcount);
+
+       expect(r == EINVAL);
+
+       got_result("negative offset");
+
+       free_bufs(buf, 4);
+}
+
+PRIVATE void test_access(void)
+{
+       struct vumap_vir vvec[3];
+       struct vumap_phys pvec[4], *pvecp;
+       struct buf buf[7];
+       int i, r, pcount, pindex;
+
+       test_group("access");
+
+       buf[0].pages = 1;
+       buf[0].flags = 0;
+       buf[1].pages = 1;
+       buf[1].flags = BUF_PREALLOC | BUF_ADJACENT;
+       buf[2].pages = 1;
+       buf[2].flags = BUF_ADJACENT;
+
+       alloc_bufs(buf, 3);
+
+       /* Test no access flags. */
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE * 3;
+       pcount = 4;
+
+       r = do_vumap(SELF, vvec, 1, 0, 0, pvec, &pcount);
+
+       expect(r == EINVAL);
+       expect(!is_buf_allocated(&buf[0]));
+       expect(is_buf_allocated(&buf[1]));
+       expect(!is_buf_allocated(&buf[2]));
+
+       got_result("no access flags");
+
+       /* Test read-only access. */
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE * 3;
+       pcount = 1;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == EFAULT);
+       expect(!is_buf_allocated(&buf[0]));
+       expect(is_buf_allocated(&buf[1]));
+       expect(!is_buf_allocated(&buf[2]));
+
+       got_result("read-only access");
+
+       /* Test read-write access. */
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE * 3;
+       pcount = 4;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ | VUA_WRITE, pvec, &pcount);
+
+       expect(r == EFAULT);
+       expect(!is_buf_allocated(&buf[0]));
+       expect(is_buf_allocated(&buf[1]));
+       expect(!is_buf_allocated(&buf[2]));
+
+       got_result("read-write access");
+
+       /* Test write-only access. */
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = PAGE_SIZE * 3;
+       pcount = 4;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_WRITE, pvec, &pcount);
+
+       expect(r == OK);
+       /* We don't control the physical addresses of the faulted-in pages, so
+        * they may or may not end up being contiguous with their neighbours.
+        */
+       expect(pcount >= 1 && pcount <= 3);
+       expect(is_buf_allocated(&buf[0]));
+       expect(is_buf_allocated(&buf[1]));
+       expect(is_buf_allocated(&buf[2]));
+       expect(pvec[0].vp_addr == buf[0].phys);
+       switch (pcount) {
+       case 1:
+               expect(pvec[0].vp_size == PAGE_SIZE * 3);
+               break;
+       case 2:
+               expect(pvec[0].vp_size + pvec[1].vp_size == PAGE_SIZE * 3);
+               if (pvec[0].vp_size > PAGE_SIZE)
+                       expect(pvec[1].vp_addr == buf[2].phys);
+               else
+                       expect(pvec[1].vp_addr == buf[1].phys);
+               break;
+       case 3:
+               expect(pvec[0].vp_size == PAGE_SIZE);
+               expect(pvec[1].vp_addr == buf[1].phys);
+               expect(pvec[1].vp_size == PAGE_SIZE);
+               expect(pvec[2].vp_addr == buf[2].phys);
+               expect(pvec[2].vp_size == PAGE_SIZE);
+               break;
+       }
+
+       got_result("write-only access");
+
+       free_bufs(buf, 3);
+
+       /* Test page faulting. */
+       buf[0].pages = 1;
+       buf[0].flags = 0;
+       buf[1].pages = 1;
+       buf[1].flags = BUF_PREALLOC | BUF_ADJACENT;
+       buf[2].pages = 1;
+       buf[2].flags = 0;
+       buf[3].pages = 2;
+       buf[3].flags = BUF_PREALLOC;
+       buf[4].pages = 1;
+       buf[4].flags = BUF_ADJACENT;
+       buf[5].pages = 1;
+       buf[5].flags = BUF_ADJACENT;
+       buf[6].pages = 1;
+       buf[6].flags = 0;
+
+       alloc_bufs(buf, 7);
+
+       vvec[0].vv_addr = buf[0].addr + PAGE_SIZE - 1;
+       vvec[0].vv_size = PAGE_SIZE - 1;
+       vvec[1].vv_addr = buf[2].addr;
+       vvec[1].vv_size = PAGE_SIZE;
+       vvec[2].vv_addr = buf[3].addr + 123;
+       vvec[2].vv_size = PAGE_SIZE * 4 - 456;
+       pvecp = (struct vumap_phys *) buf[6].addr;
+       pcount = 7;
+       assert(sizeof(struct vumap_phys) * pcount <= PAGE_SIZE);
+
+       r = do_vumap(SELF, vvec, 3, 0, VUA_WRITE, pvecp, &pcount);
+
+       expect(r == OK);
+       /* Same story but more possibilities. I hope I got this right. */
+       expect(pcount >= 3 || pcount <= 6);
+       for (i = 0; i < 7; i++)
+               expect(is_buf_allocated(&buf[i]));
+       expect(pvecp[0].vp_addr = buf[0].phys);
+       if (pvecp[0].vp_size == 1) {
+               expect(pvecp[1].vp_addr == buf[1].phys);
+               expect(pvecp[1].vp_size == PAGE_SIZE - 2);
+               pindex = 2;
+       } else {
+               expect(pvecp[0].vp_size == PAGE_SIZE - 1);
+               pindex = 1;
+       }
+       expect(pvecp[pindex].vp_addr == buf[2].phys);
+       expect(pvecp[pindex].vp_size == PAGE_SIZE);
+       pindex++;
+       expect(pvecp[pindex].vp_addr == buf[3].phys + 123);
+       switch (pcount - pindex) {
+       case 1:
+               expect(pvecp[pindex].vp_size == PAGE_SIZE * 4 - 456);
+               break;
+       case 2:
+               if (pvecp[pindex].vp_size > PAGE_SIZE * 2 - 123) {
+                       expect(pvecp[pindex].vp_size == PAGE_SIZE * 3 - 123);
+                       expect(pvecp[pindex + 1].vp_addr == buf[5].phys);
+                       expect(pvecp[pindex + 1].vp_size ==
+                               PAGE_SIZE - (456 - 123));
+               } else {
+                       expect(pvecp[pindex].vp_size == PAGE_SIZE * 2 - 123);
+                       expect(pvecp[pindex + 1].vp_addr == buf[4].phys);
+                       expect(pvecp[pindex + 1].vp_size ==
+                               PAGE_SIZE * 2 - (456 - 123));
+               }
+               break;
+       case 3:
+               expect(pvecp[pindex].vp_size == PAGE_SIZE * 2 - 123);
+               expect(pvecp[pindex + 1].vp_addr == buf[4].phys);
+               expect(pvecp[pindex + 1].vp_size == PAGE_SIZE);
+               expect(pvecp[pindex + 2].vp_addr == buf[5].phys);
+               expect(pvecp[pindex + 2].vp_size == PAGE_SIZE - (456 - 123));
+               break;
+       default:
+               expect(0);
+       }
+
+       got_result("page faulting");
+
+       free_bufs(buf, 7);
+
+       /* MISSING: tests to see whether a request with VUA_WRITE or
+        * (VUA_READ|VUA_WRITE) correctly gets an EFAULT for a read-only page.
+        * As of writing, support for such protection is missing from the
+        * system at all.
+        */
+}
+
+PRIVATE void phys_limit(struct vumap_vir *vvec, int vcount,
+       struct vumap_phys *pvec, int pcount, struct buf *buf, char *desc)
+{
+       int i, r;
+
+       r = do_vumap(SELF, vvec, vcount, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == MAPVEC_NR);
+       for (i = 0; i < MAPVEC_NR; i++) {
+               expect(pvec[i].vp_addr == buf[i].phys);
+               expect(pvec[i].vp_size == PAGE_SIZE);
+       }
+
+       got_result(desc);
+}
+
+PRIVATE void test_limits(void)
+{
+       struct vumap_vir vvec[MAPVEC_NR + 3];
+       struct vumap_phys pvec[MAPVEC_NR + 3];
+       struct buf buf[MAPVEC_NR + 9];
+       int i, r, vcount, pcount, nr_bufs;
+
+       test_group("limits");
+
+       /* Test large contiguous range. */
+       buf[0].pages = MAPVEC_NR + 2;
+       buf[0].flags = BUF_PREALLOC;
+
+       alloc_bufs(buf, 1);
+
+       vvec[0].vv_addr = buf[0].addr;
+       vvec[0].vv_size = (MAPVEC_NR + 2) * PAGE_SIZE;
+       pcount = 2;
+
+       r = do_vumap(SELF, vvec, 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == 1);
+       expect(pvec[0].vp_addr == buf[0].phys);
+       expect(pvec[0].vp_size == vvec[0].vv_size);
+
+       got_result("large contiguous range");
+
+       free_bufs(buf, 1);
+
+       /* I'd like to test MAPVEC_NR contiguous ranges of MAPVEC_NR pages
+        * each, but chances are we don't have that much contiguous memory
+        * available at all. In fact, the previous test may already fail
+        * because of this..
+        */
+
+       for (i = 0; i < MAPVEC_NR + 2; i++) {
+               buf[i].pages = 1;
+               buf[i].flags = BUF_PREALLOC;
+       }
+       buf[i].pages = 1;
+       buf[i].flags = BUF_PREALLOC | BUF_ADJACENT;
+
+       alloc_bufs(buf, MAPVEC_NR + 3);
+
+       /* Test virtual limit, one below. */
+       for (i = 0; i < MAPVEC_NR + 2; i++) {
+               vvec[i].vv_addr = buf[i].addr;
+               vvec[i].vv_size = PAGE_SIZE;
+       }
+       vvec[i - 1].vv_size += PAGE_SIZE;
+
+       pcount = MAPVEC_NR + 3;
+
+       r = do_vumap(SELF, vvec, MAPVEC_NR - 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == MAPVEC_NR - 1);
+       for (i = 0; i < MAPVEC_NR - 1; i++) {
+               expect(pvec[i].vp_addr == buf[i].phys);
+               expect(pvec[i].vp_size == PAGE_SIZE);
+       }
+
+       got_result("virtual limit, one below");
+
+       /* Test virtual limit, exact match. */
+       pcount = MAPVEC_NR + 3;
+
+       r = do_vumap(SELF, vvec, MAPVEC_NR, 0, VUA_WRITE, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == MAPVEC_NR);
+       for (i = 0; i < MAPVEC_NR; i++) {
+               expect(pvec[i].vp_addr == buf[i].phys);
+               expect(pvec[i].vp_size == PAGE_SIZE);
+       }
+
+       got_result("virtual limit, exact match");
+
+       /* Test virtual limit, one above. */
+       pcount = MAPVEC_NR + 3;
+
+       r = do_vumap(SELF, vvec, MAPVEC_NR + 1, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == MAPVEC_NR);
+       for (i = 0; i < MAPVEC_NR; i++) {
+               expect(pvec[i].vp_addr == buf[i].phys);
+               expect(pvec[i].vp_size == PAGE_SIZE);
+       }
+
+       got_result("virtual limit, one above");
+
+       /* Test virtual limit, two above. */
+       pcount = MAPVEC_NR + 3;
+
+       r = do_vumap(SELF, vvec, MAPVEC_NR + 2, 0, VUA_WRITE, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == MAPVEC_NR);
+       for (i = 0; i < MAPVEC_NR; i++) {
+               expect(pvec[i].vp_addr == buf[i].phys);
+               expect(pvec[i].vp_size == PAGE_SIZE);
+       }
+
+       got_result("virtual limit, two above");
+
+       /* Test physical limit, one below, aligned. */
+       pcount = MAPVEC_NR - 1;
+
+       r = do_vumap(SELF, vvec + 2, MAPVEC_NR, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == MAPVEC_NR - 1);
+       for (i = 0; i < MAPVEC_NR - 1; i++) {
+               expect(pvec[i].vp_addr == buf[i + 2].phys);
+               expect(pvec[i].vp_size == PAGE_SIZE);
+       }
+
+       got_result("physical limit, one below, aligned");
+
+       /* Test physical limit, one below, unaligned. */
+       pcount = MAPVEC_NR - 1;
+
+       r = do_vumap(SELF, vvec + 3, MAPVEC_NR, 0, VUA_READ, pvec, &pcount);
+
+       expect(r == OK);
+       expect(pcount == MAPVEC_NR - 1);
+       for (i = 0; i < MAPVEC_NR - 1; i++) {
+               expect(pvec[i].vp_addr == buf[i + 3].phys);
+               expect(pvec[i].vp_size == PAGE_SIZE);
+       }
+
+       got_result("physical limit, one below, unaligned");
+
+       free_bufs(buf, MAPVEC_NR + 3);
+
+       nr_bufs = sizeof(buf) / sizeof(buf[0]);
+
+       /* This ends up looking in our virtual address space as follows:
+        * [P] [P] [P] [PPP] [PPP] ...(MAPVEC_NR x [PPP])... [PPP]
+        * ..where P is a page, and the blocks are virtually contiguous.
+        */
+       for (i = 0; i < nr_bufs; i += 3) {
+               buf[i].pages = 1;
+               buf[i].flags = BUF_PREALLOC;
+               buf[i + 1].pages = 1;
+               buf[i + 1].flags =
+                       BUF_PREALLOC | ((i >= 3) ? BUF_ADJACENT : 0);
+               buf[i + 2].pages = 1;
+               buf[i + 2].flags =
+                       BUF_PREALLOC | ((i >= 3) ? BUF_ADJACENT : 0);
+       }
+
+       alloc_bufs(buf, nr_bufs);
+
+       for (i = 0; i < 3; i++) {
+               vvec[i].vv_addr = buf[i].addr;
+               vvec[i].vv_size = PAGE_SIZE;
+       }
+       for ( ; i < nr_bufs / 3 + 1; i++) {
+               vvec[i].vv_addr = buf[(i - 2) * 3].addr;
+               vvec[i].vv_size = PAGE_SIZE * 3;
+       }
+       vcount = i;
+
+       /* Out of each of the following tests, one will be aligned (that is,
+        * the last pvec entry will be for the last page in a vvec entry) and
+        * two will be unaligned.
+        */
+
+       /* Test physical limit, exact match. */
+       phys_limit(vvec, vcount, pvec, MAPVEC_NR, buf,
+               "physical limit, exact match, try 1");
+       phys_limit(vvec + 1, vcount - 1, pvec, MAPVEC_NR, buf + 1,
+               "physical limit, exact match, try 2");
+       phys_limit(vvec + 2, vcount - 2, pvec, MAPVEC_NR, buf + 2,
+               "physical limit, exact match, try 3");
+
+       /* Test physical limit, one above. */
+       phys_limit(vvec, vcount, pvec, MAPVEC_NR + 1, buf,
+               "physical limit, one above, try 1");
+       phys_limit(vvec + 1, vcount - 1, pvec, MAPVEC_NR + 1, buf + 1,
+               "physical limit, one above, try 2");
+       phys_limit(vvec + 2, vcount - 2, pvec, MAPVEC_NR + 1, buf + 2,
+               "physical limit, one above, try 3");
+
+       /* Test physical limit, two above. */
+       phys_limit(vvec, vcount, pvec, MAPVEC_NR + 2, buf,
+               "physical limit, two above, try 1");
+       phys_limit(vvec + 1, vcount - 1, pvec, MAPVEC_NR + 2, buf + 1,
+               "physical limit, two above, try 2");
+       phys_limit(vvec + 2, vcount - 2, pvec, MAPVEC_NR + 2, buf + 2,
+               "physical limit, two above, try 3");
+
+       free_bufs(buf, nr_bufs);
+}
+
+PRIVATE void do_tests(int use_relay)
+{
+       relay = use_relay;
+
+       test_basics();
+
+       if (!relay) test_endpt();       /* local only */
+
+       test_vector1();
+
+       if (!relay) test_vector2();     /* local only */
+
+       if (relay) test_grant();        /* remote only */
+
+       test_offset();
+
+       test_access();
+
+       test_limits();
+}
+
+PRIVATE int sef_cb_init_fresh(int UNUSED(type), sef_init_info_t *UNUSED(info))
+{
+       int r;
+
+       verbose = (env_argc > 1 && !strcmp(env_argv[1], "-v"));
+
+       if (verbose)
+               printf("Starting sys_vumap test set\n");
+
+       do_tests(FALSE /*use_relay*/);
+
+       if ((r = ds_retrieve_label_endpt("vumaprelay", &endpt)) != OK)
+               panic("unable to obtain endpoint for 'vumaprelay' (%d)", r);
+
+       do_tests(TRUE /*use_relay*/);
+
+       if (verbose)
+               printf("Completed sys_vumap test set, %u/%u tests failed\n",
+                       failures, count);
+
+       /* The returned code will determine the outcome of the RS call, and
+        * thus the entire test. The actual error code does not matter.
+        */
+       return (failures) ? EINVAL : OK;
+}
+
+PRIVATE void sef_local_startup(void)
+{
+       sef_setcb_init_fresh(sef_cb_init_fresh);
+
+       sef_startup();
+}
+
+PUBLIC int main(int argc, char **argv)
+{
+       env_setargs(argc, argv);
+
+       sef_local_startup();
+
+       return 0;
+}