summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--etc/rc2
-rw-r--r--sys/arch/i386/conf/LOCKDOC2
-rw-r--r--sys/arch/i386/conf/files.i3861
-rw-r--r--sys/arch/i386/conf/majors.i3862
-rw-r--r--sys/arch/x86/x86/pmap.c3
-rw-r--r--sys/lockdoc/test.c545
-rwxr-xr-xupdate_system.sh8
7 files changed, 562 insertions, 1 deletions
diff --git a/etc/rc b/etc/rc
index 04895498338..926e41f87d2 100644
--- a/etc/rc
+++ b/etc/rc
@@ -44,6 +44,8 @@ fi
#
trap '' INT QUIT
+/usr/pkg/bin/bash /lockdoc/boot.init.bash.sh
+
#
# This string will be used to mark lines of meta-data sent over the pipe
# from the rc_real_work() function to the rc_postprocess() function. Lines
diff --git a/sys/arch/i386/conf/LOCKDOC b/sys/arch/i386/conf/LOCKDOC
index 14d7556a892..f2606cdf287 100644
--- a/sys/arch/i386/conf/LOCKDOC
+++ b/sys/arch/i386/conf/LOCKDOC
@@ -1012,7 +1012,7 @@ uk* at atapibus? drive ? flags 0x0000 # ATAPI unknown
#vmx* at pci? dev ? function ? # VMware VMXNET3
#vr* at pci? dev ? function ? # VIA Rhine Fast Ethernet
#wi* at pci? dev ? function ? # Intersil Prism Mini-PCI (802.11b)
-#wm* at pci? dev ? function ? # Intel 8254x gigabit
+wm* at pci? dev ? function ? # Intel 8254x gigabit
#wpi* at pci? dev ? function ? # Intel PRO/Wireless 3945ABG
#xge* at pci? dev ? function ? # Neterion (S2io) Xframe-I 10GbE
diff --git a/sys/arch/i386/conf/files.i386 b/sys/arch/i386/conf/files.i386
index 20a74104e31..a126d6badb5 100644
--- a/sys/arch/i386/conf/files.i386
+++ b/sys/arch/i386/conf/files.i386
@@ -47,6 +47,7 @@ defparam opt_beep.h BEEP_ONHALT_PERIOD=250
file lockdoc/log.c lockdoc
+file lockdoc/test.c lockdoc
# Multiboot support
defflag opt_multiboot.h MULTIBOOT
diff --git a/sys/arch/i386/conf/majors.i386 b/sys/arch/i386/conf/majors.i386
index d3313372291..c5f59760bc7 100644
--- a/sys/arch/i386/conf/majors.i386
+++ b/sys/arch/i386/conf/majors.i386
@@ -108,6 +108,8 @@ device-major ct char 106 block 23 ct
device-major mt char 107 block 24 mt
#device-major obsolete char 108 block 25 obsolete (vinum, pf)
#device-major obsolete char 108 obsolete (opencrypto, bthub)
+#device-major lockdoc_ctl char 138 lockdoc
+#device-major lockdoc_iter char 139 lockdoc
# Reserve a major for VirtualBox Guest Additions
device-major vboxguest char 109 vboxguest
diff --git a/sys/arch/x86/x86/pmap.c b/sys/arch/x86/x86/pmap.c
index ba5c983b44f..31fe582fd62 100644
--- a/sys/arch/x86/x86/pmap.c
+++ b/sys/arch/x86/x86/pmap.c
@@ -1050,8 +1050,11 @@ pmap_kenter_pa(vaddr_t va, paddr_t pa, vm_prot_t prot, u_int flags)
KASSERTMSG(!(opte & PTE_PS), "PTE_PS va=%#"PRIxVADDR, va);
if ((opte & (PTE_P | PTE_A)) == (PTE_P | PTE_A)) {
+/* Disable the warning message during LOCKDOC tests since it makes the console unusable */
+#ifndef LOCKDOC
/* This should not happen. */
printf_nolog("%s: mapping already present\n", __func__);
+#endif
kpreempt_disable();
pmap_tlb_shootdown(pmap_kernel(), va, opte, TLBSHOOT_KENTER);
kpreempt_enable();
diff --git a/sys/lockdoc/test.c b/sys/lockdoc/test.c
new file mode 100644
index 00000000000..db0274f33f9
--- /dev/null
+++ b/sys/lockdoc/test.c
@@ -0,0 +1,545 @@
+#include <sys/module.h>
+#include <sys/conf.h>
+#include <sys/malloc.h>
+#include <sys/kthread.h>
+#include <sys/lockdoc.h>
+#include <sys/mutex.h>
+#include <sys/proc.h>
+#include <sys/cpu.h>
+
+#ifndef LOCKDOC_TEST_ITERATIONS
+#define LOCKDOC_TEST_ITERATIONS 200
+#endif
+
+#define DEV_DIR_NAME "lockdoc"
+#define DEV_FILE_CONTROL_NAME "control"
+#define DEV_FILE_ITER_NAME "iterations"
+#define DEFAULT_ITERATIONS LOCKDOC_TEST_ITERATIONS
+
+#define TSLEEP_TIMEOUT 120000
+#define MS_TO_HZ(x) ((x) * hz / 1000)
+/*
+ * For some reasons, our ring buffer (aka BSB ring buffer)
+ * can only hold size - 1 elements.
+ * If we want to store DEFAULT_ITERATIONS elements, as desired,
+ * the buffer must be one element larger.
+ * Hence, RING_BUFFER_SIZE_REAL is used for allocating the actual buffer
+ * and used for the size member.
+ * In contrast, RING_BUFFER_SIZE_VIRT is used when asigning a new value
+ * for iterations in procfile_iter_write.
+ */
+#define RING_BUFFER_SIZE_REAL (DEFAULT_ITERATIONS + 1)
+#define RING_BUFFER_SIZE_VIRT (RING_BUFFER_SIZE_REAL - 1)
+#define RING_BUFFER_STORAGE_TYPE int
+#define MK_STRING(x) #x
+#define START_AND_WAIT_THREAD(x) start_and_wait_thread(MK_STRING(x), x)
+
+static const wchan_t pause_wchan[MAXCPUS];
+
+dev_type_open(lockdoc_ctl_open);
+dev_type_close(lockdoc_ctl_close);
+dev_type_write(lockdoc_ctl_read);
+dev_type_write(lockdoc_ctl_write);
+
+dev_type_open(lockdoc_iter_open);
+dev_type_close(lockdoc_iter_close);
+dev_type_read(lockdoc_iter_read);
+dev_type_write(lockdoc_iter_write);
+
+static const struct cdevsw lockdoc_ctl_cdevsw = {
+ .d_read = lockdoc_ctl_read,
+ .d_write = lockdoc_ctl_write,
+
+ .d_open = lockdoc_ctl_open,
+ .d_close = lockdoc_ctl_close,
+
+ .d_ioctl = noioctl,
+ .d_stop = nostop,
+ .d_tty = notty,
+ .d_poll = nopoll,
+ .d_mmap = nommap,
+ .d_kqfilter = nokqfilter,
+ .d_discard = nodiscard,
+ .d_flag = D_OTHER
+};
+static const struct cdevsw lockdoc_iter_cdevsw = {
+ .d_read = lockdoc_iter_read,
+ .d_write = lockdoc_iter_write,
+
+ .d_open = lockdoc_iter_open,
+ .d_close = lockdoc_iter_close,
+
+ .d_ioctl = noioctl,
+ .d_stop = nostop,
+ .d_tty = notty,
+ .d_poll = nopoll,
+ .d_mmap = nommap,
+ .d_kqfilter = nokqfilter,
+ .d_discard = nodiscard,
+ .d_flag = D_OTHER
+};
+
+static int iterations = DEFAULT_ITERATIONS;
+static lwp_t *control_thread = NULL;
+
+static kmutex_t rb_lock;
+static kmutex_t consumer_lock;
+static kmutex_t producer_lock;
+
+MALLOC_DEFINE(M_LOCKDOC, "lockdoc_ring_buffer", "ring buffer for LockDoc test");
+struct lockdoc_ring_buffer {
+ int next_in;
+ int next_out;
+ int size;
+ RING_BUFFER_STORAGE_TYPE data[RING_BUFFER_SIZE_REAL];
+};
+static struct lockdoc_ring_buffer *ring_buffer = NULL;
+
+static __noinline int is_full(volatile struct lockdoc_ring_buffer *buffer) { return (buffer->next_in + 1) % buffer->size == buffer->next_out; }
+static __noinline int is_empty(volatile struct lockdoc_ring_buffer *buffer) { return buffer->next_out == buffer->next_in; }
+
+static __noinline int produce(volatile struct lockdoc_ring_buffer *buffer, RING_BUFFER_STORAGE_TYPE data) {
+ if (is_full(buffer)) {
+ return -1;
+ }
+ buffer->data[buffer->next_in] = data;
+ buffer->next_in = (buffer->next_in + 1) % buffer->size;
+
+ return 0;
+}
+
+static __noinline RING_BUFFER_STORAGE_TYPE consume(volatile struct lockdoc_ring_buffer *buffer) {
+ RING_BUFFER_STORAGE_TYPE result;
+
+ if (is_empty(buffer)) {
+ return -1;
+ }
+ result = buffer->data[buffer->next_out];
+ buffer->next_out = (buffer->next_out + 1) % buffer->size;
+
+ return result;
+}
+
+int lockdoc_ctl_open(dev_t self __unused, int flag __unused, int mode __unused,
+ struct lwp *l __unused)
+{
+ return 0;
+}
+
+int lockdoc_ctl_close(dev_t self __unused, int flag __unused, int mode __unused,
+ struct lwp *l __unused)
+{
+ return 0;
+}
+
+int lockdoc_iter_open(dev_t self __unused, int flag __unused, int mode __unused,
+ struct lwp *l __unused)
+{
+ return 0;
+}
+
+int lockdoc_iter_close(dev_t self __unused, int flag __unused, int mode __unused,
+ struct lwp *l __unused)
+{
+ return 0;
+}
+
+static void producer_thread_work(void *data) {
+ int i, ret;
+
+ /*
+ * Produce 'iterations' elements.
+ * This fills every element in the ring buffer.
+ * The 'iterations'+1 call to produce() would fail due to a full buffer.
+ * The consumer thread will completely empty the buffer.
+ */
+ for (i = 0; i < iterations; i++) {
+ mutex_enter(&rb_lock);
+ ret = is_full(ring_buffer);
+ mutex_exit(&rb_lock);
+ if (ret) {
+ printf("%s: Ring buffer is full\n", __func__);
+ }
+
+ mutex_enter(&producer_lock);
+ mutex_enter(&rb_lock);
+ ret = produce(ring_buffer, i + 30);
+ mutex_exit(&producer_lock);
+ mutex_exit(&rb_lock);
+ printf("%s-%03d: Produced(%d): %03d\n", __func__, i, ret, i + 30);
+
+ //pause("W", MS_TO_HZ(100));
+ tsleep(&pause_wchan[cpu_number()], 0, "W", MS_TO_HZ(100));
+
+ }
+
+ kthread_exit(0);
+}
+
+static void consumer_thread_work(void *data) {
+ int i, ret;
+
+ for (i = 0; i < iterations; i++) {
+ mutex_enter(&rb_lock);
+ ret = is_empty(ring_buffer);
+ mutex_exit(&rb_lock);
+ if (ret) {
+ printf("%s: Ring buffer is empty\n", __func__);
+ }
+
+ mutex_enter(&consumer_lock);
+ mutex_enter(&rb_lock);
+ ret = consume(ring_buffer);
+ mutex_exit(&consumer_lock);
+ mutex_exit(&rb_lock);
+ printf("%s-%03d: Consumed: %03d\n", __func__, i, ret);
+
+ //pause("W", MS_TO_HZ(100));
+ tsleep(&pause_wchan[cpu_number()], 0, "W", MS_TO_HZ(100));
+ }
+
+ kthread_exit(0);
+}
+
+static void dirty_nolocks_thread_work(void *data) {
+ int i = 0, ret;
+
+ /*
+ * Wait a bit. Otherwise the call to tsleep() in control_thread_work() will wait for ever,
+ * because this thread has terminated and the caller will not be notified.
+ */
+ //pause("W", MS_TO_HZ(500));
+ tsleep(&pause_wchan[cpu_number()], 0, "W", MS_TO_HZ(500));
+
+ ret = is_full(ring_buffer);
+ if (ret) {
+ printf("%s: Ring buffer is full\n", __func__);
+ }
+
+ ret = produce(ring_buffer, i - 1);
+ printf("%s-%03d: Produced(%d): %03d\n", __func__, i, ret, i - 1);
+
+ ret = is_empty(ring_buffer);
+ if (ret) {
+ printf("%s: Ring buffer is empty\n",__func__);
+ }
+
+ ret = consume(ring_buffer);
+ printf("%s-%03d: Consumed: %03d\n", __func__, i, ret);
+
+ kthread_exit(0);
+}
+
+static void dirty_fewlocks_thread_work(void *data) {
+ int i = 0, ret;
+
+ /*
+ * Wait a bit. Otherwise the call to tsleep() in control_thread_work() will wait for ever,
+ * because this thread has terminated and the caller will not be notified.
+ */
+ //pause("W", MS_TO_HZ(500));
+ tsleep(&pause_wchan[cpu_number()], 0, "W", MS_TO_HZ(500));
+
+ mutex_enter(&rb_lock);
+ ret = is_full(ring_buffer);
+ mutex_exit(&rb_lock);
+ if (ret) {
+ printf("%s: Ring buffer is full\n", __func__);
+ }
+
+ mutex_enter(&rb_lock);
+ ret = produce(ring_buffer, i - 1);
+ mutex_exit(&rb_lock);
+ printf("%s-%03d: Produced(%d): %03d\n", __func__, i, ret, i - 1);
+
+ mutex_enter(&rb_lock);
+ ret = is_empty(ring_buffer);
+ mutex_exit(&rb_lock);
+ if (ret) {
+ printf("%s: Ring buffer is empty\n", __func__);
+ }
+
+ mutex_enter(&rb_lock);
+ ret = consume(ring_buffer);
+ mutex_exit(&rb_lock);
+ printf("%s-%03d: Consumed: %03d\n", __func__, i, ret);
+
+ kthread_exit(0);
+}
+
+static void dirty_alllocks_thread_work(void *data) {
+ int i = 0, ret;
+
+ /*
+ * Wait a bit. Otherwise the call to tsleep() in control_thread_work() will wait for ever,
+ * because this thread has terminated and the caller will not be notified.
+ */
+ //pause("W", MS_TO_HZ(500));
+ tsleep(&pause_wchan[cpu_number()], 0, "W", MS_TO_HZ(500));
+
+ mutex_enter(&producer_lock);
+ mutex_enter(&consumer_lock);
+ mutex_enter(&rb_lock);
+ ret = is_full(ring_buffer);
+ mutex_exit(&rb_lock);
+ mutex_exit(&consumer_lock);
+ mutex_exit(&producer_lock);
+ if (ret) {
+ printf("%s: Ring buffer is full\n", __func__);
+ }
+
+ mutex_enter(&producer_lock);
+ mutex_enter(&consumer_lock);
+ mutex_enter(&rb_lock);
+ ret = produce(ring_buffer, i - 1);
+ mutex_exit(&rb_lock);
+ mutex_exit(&consumer_lock);
+ mutex_exit(&producer_lock);
+ printf("%s-%03d: Produced(%d): %03d\n", __func__, i, ret, i - 1);
+
+ mutex_enter(&producer_lock);
+ mutex_enter(&consumer_lock);
+ mutex_enter(&rb_lock);
+ ret = is_empty(ring_buffer);
+ mutex_exit(&rb_lock);
+ mutex_exit(&consumer_lock);
+ mutex_exit(&producer_lock);
+ if (ret) {
+ printf("%s: Ring buffer is empty\n", __func__);
+ }
+
+ mutex_enter(&producer_lock);
+ mutex_enter(&consumer_lock);
+ mutex_enter(&rb_lock);
+ ret = consume(ring_buffer);
+ mutex_exit(&rb_lock);
+ mutex_exit(&consumer_lock);
+ mutex_exit(&producer_lock);
+ printf("%s-%03d: Consumed: %03d\n", __func__, i, ret);
+
+ kthread_exit(0);
+}
+
+static void dirty_order_thread_work(void *data) {
+ int i = 0, ret;
+
+ /*
+ * Wait a bit. Otherwise the call to tsleep() in control_thread_work() will wait for ever,
+ * because this thread has terminated and the caller will not be notified.
+ */
+ //pause("W", MS_TO_HZ(500));
+ tsleep(&pause_wchan[cpu_number()], 0, "W", MS_TO_HZ(500));
+
+ mutex_enter(&rb_lock);
+ mutex_enter(&producer_lock);
+ ret = produce(ring_buffer, i - 1);
+ mutex_exit(&producer_lock);
+ mutex_exit(&rb_lock);
+ printf("%s-%03d: Produced(%d): %03d\n", __func__, i, ret, i - 1);
+
+ mutex_enter(&rb_lock);
+ mutex_enter(&consumer_lock);
+ ret = consume(ring_buffer);
+ mutex_exit(&consumer_lock);
+ mutex_exit(&rb_lock);
+ printf("%s-%03d: Consumed: %03d\n", __func__, i, ret);
+
+ kthread_exit(0);
+}
+
+static void start_and_wait_thread(const char *fn_name, void (*work_fn)(void*)) {
+ lwp_t *temp = NULL;
+ int error;
+
+ printf("%s: Starting %s thread...\n", __func__, fn_name);
+ //error = kthread_create(work_fn, NULL, &temp,0, 0, "lockdoc-control");
+ error = kthread_create(PRI_NONE, 0, NULL, work_fn, NULL, &temp, "lockdoc-control");
+
+ if (error) {
+ return;
+ }
+ printf("%s: Waiting for %s thread to terminate...\n", __func__, fn_name);
+ error = tsleep(temp, 0, "ldctl", MS_TO_HZ(TSLEEP_TIMEOUT));
+ if (error) {
+ printf("%s: Error waiting for %s thread\n", __func__, fn_name);
+ } else {
+ printf("%s: %s thread terminated successfully\n", __func__, fn_name);
+ }
+}
+
+static void control_thread_work(void *data) {
+
+ mutex_init(&rb_lock, /*"LockDoc test rb lock",*/ MUTEX_DEFAULT, IPL_NONE);
+ mutex_init(&consumer_lock, /*"LockDoc test consumer lock",*/ MUTEX_DEFAULT, IPL_NONE);
+ mutex_init(&producer_lock, /*"LockDoc test producer lock",*/ MUTEX_DEFAULT, IPL_NONE);
+
+ ring_buffer = malloc(sizeof(*ring_buffer), M_LOCKDOC, M_WAITOK | M_ZERO);
+ if (!ring_buffer) {
+ printf("Cannot allocate %u bytes for ring buffer\n", sizeof(*ring_buffer));
+ kthread_exit(1);
+ }
+ ring_buffer->size = RING_BUFFER_SIZE_REAL;
+ log_memory(1, "lockdoc_ring_buffer", ring_buffer, sizeof(*ring_buffer));
+
+ START_AND_WAIT_THREAD(producer_thread_work);
+ START_AND_WAIT_THREAD(consumer_thread_work);
+ START_AND_WAIT_THREAD(dirty_nolocks_thread_work);
+ START_AND_WAIT_THREAD(dirty_fewlocks_thread_work);
+ START_AND_WAIT_THREAD(dirty_alllocks_thread_work);
+ START_AND_WAIT_THREAD(dirty_order_thread_work);
+
+ log_memory(0, "lockdoc_ring_buffer", ring_buffer, sizeof(*ring_buffer));
+ free(ring_buffer, M_LOCKDOC);
+
+ mutex_destroy(&rb_lock);
+ mutex_destroy(&producer_lock);
+ mutex_destroy(&consumer_lock);
+
+ kthread_exit(0);
+}
+
+// Stub out this method, otherwise (accidentaly) reading from /dev/lockdoc/control causes a kernel panic
+int lockdoc_ctl_read(dev_t self, struct uio *uio, int flags){
+ return 0;
+}
+
+#define BUFSIZE 5
+int lockdoc_ctl_write(dev_t self, struct uio *uio, int flags){
+
+ unsigned long value = 0;
+ size_t amt;
+ int error;
+ char buffer[BUFSIZE];
+
+ if (uio->uio_offset != 0 && (uio->uio_offset != BUFSIZE)) {
+ return (EINVAL);
+ }
+
+ amt = MIN(uio->uio_resid, BUFSIZE);
+ error = uiomove(buffer, amt, uio);
+ if (error) {
+ return (error);
+ }
+
+ /* parse input */
+ value = strtoul(buffer, NULL, 10);
+ if (value == 1) {
+ //error = kthread_create(control_thread_work, NULL, &control_thread,0, 0, "lockdoc-control");
+ error = kthread_create(PRI_NONE, 0, NULL, control_thread_work, NULL, &control_thread, "lockdoc-control");
+ if (error) {
+ return (error);
+ }
+
+ //pause("W", MS_TO_HZ(200));
+ tsleep(&pause_wchan[cpu_number()], 0, "W", MS_TO_HZ(500));
+
+ uprintf("%s: Waiting for control_thread to terminate...\n", __func__);
+ // This will block the caller until all threads terminated
+ error = tsleep(control_thread, 0, "ldctl", MS_TO_HZ(TSLEEP_TIMEOUT));
+ if (error) {
+ uprintf("%s: Wait for control thread timed out\n", __func__);
+ return (error);
+ }
+ uprintf("%s: Control thread terminated successfully!\n", __func__);
+ } else {
+ return (EINVAL);
+ }
+
+ return 0;
+}
+#undef BUFSIZE
+
+#define BUFSIZE 20
+int lockdoc_iter_read(dev_t self, struct uio *uio, int flags)
+{
+ size_t amt;
+ int error;
+ char buffer[BUFSIZE];
+ int ret = snprintf(buffer, BUFSIZE, "%u\n", iterations);
+
+ if (ret >= BUFSIZE) {
+ ret = BUFSIZE;
+ }
+
+ amt = MIN(uio->uio_resid, uio->uio_offset >= ret ? 0 :
+ ret - uio->uio_offset);
+
+ error = uiomove(buffer, amt, uio);
+ if (error != 0) {
+ uprintf("uiomove failed!\n");
+ }
+
+ return (error);
+}
+#undef BUFSIZE
+
+#define BUFSIZE 20
+int lockdoc_iter_write(dev_t self, struct uio *uio, int flags) {
+ unsigned long value = 0;
+ size_t amt;
+ int error;
+ char buffer[BUFSIZE];
+
+ if (uio->uio_offset != 0 && (uio->uio_offset != BUFSIZE)) {
+ return (EINVAL);
+ }
+
+ amt = MIN(uio->uio_resid, BUFSIZE);
+ error = uiomove(buffer, amt, uio);
+ if (error) {
+ return (error);
+ }
+
+ /* parse input */
+ value = strtoul(buffer, NULL, 10);
+ /*
+ * Iterations cannot be larger than the buffer size.
+ * If 'iterations' is larger than the buffer size,
+ * the consumer/producer thread will execute different code paths (iterations - RING_BUFFER_SIZE_VIRT) times.
+ * We want to the consumer and producer thread to execute the same code 'iterations' times.
+ */
+ if (value > RING_BUFFER_SIZE_VIRT) {
+ uprintf("%s: Desired iterations (%lu) is larger than buffer size (%d)\n", __func__, value, RING_BUFFER_SIZE_VIRT);
+ return (EINVAL);
+ }
+ iterations = value;
+ uprintf("Setting iterations to %d\n", iterations);
+
+ return 0;
+}
+#undef BUFSIZE
+
+MODULE(MODULE_CLASS_MISC, lockdoc_test, NULL);
+
+static int lockdoc_test_modcmd(modcmd_t what, void *arg __unused)
+{
+ int error = 0;
+
+ switch (what) {
+ case MODULE_CMD_INIT: ;
+ int ctl_cmajor = 138, ctl_bmajor = -1;
+ error = devsw_attach(DEV_FILE_CONTROL_NAME, NULL, &ctl_bmajor, &lockdoc_ctl_cdevsw, &ctl_cmajor);
+ if (error != 0) {
+ printf("Could not create lockdoc control device: ERRNO %i\n", error);
+ break;
+ }
+ int iter_cmajor = 139, iter_bmajor = -1;
+ error = devsw_attach(DEV_FILE_ITER_NAME, NULL, &iter_bmajor, &lockdoc_iter_cdevsw, &iter_cmajor);
+ if (error != 0) {
+ printf("Could not create lockdoc iterations device: ERRNO %i\n", error);
+ devsw_detach(NULL, &lockdoc_ctl_cdevsw);
+ break;
+ }
+ break;
+ case MODULE_CMD_FINI:
+ devsw_detach(NULL, &lockdoc_ctl_cdevsw);
+ devsw_detach(NULL, &lockdoc_iter_cdevsw);
+ break;
+ default:
+ error = EOPNOTSUPP;
+ break;
+ }
+ return (error);
+}
+
+// Loosely based on http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/sys/modules/examples/readhappy/readhappy.c?rev=1.1&content-type=text/plain&only_with_tag=MAIN \ No newline at end of file
diff --git a/update_system.sh b/update_system.sh
new file mode 100755
index 00000000000..0afaa6bf52c
--- /dev/null
+++ b/update_system.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+set -xeu
+
+NETBSD_IP=10.0.20.246
+
+./build.sh -U -O ./obj -j$(nproc) -m i386 -a i386 -u build kernel=LOCKDOC
+scp etc/rc root@$NETBSD_IP:/etc/rc
+scp obj/sys/arch/i386/compile/LOCKDOC/netbsd root@$NETBSD_IP:/netbsd