diff options
| -rw-r--r-- | etc/rc | 2 | ||||
| -rw-r--r-- | sys/arch/i386/conf/LOCKDOC | 2 | ||||
| -rw-r--r-- | sys/arch/i386/conf/files.i386 | 1 | ||||
| -rw-r--r-- | sys/arch/i386/conf/majors.i386 | 2 | ||||
| -rw-r--r-- | sys/arch/x86/x86/pmap.c | 3 | ||||
| -rw-r--r-- | sys/lockdoc/test.c | 545 | ||||
| -rwxr-xr-x | update_system.sh | 8 |
7 files changed, 562 insertions, 1 deletions
@@ -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 8614c7696ab..17c115a8c7c 100644 --- a/sys/arch/i386/conf/files.i386 +++ b/sys/arch/i386/conf/files.i386 @@ -49,6 +49,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 126284ffa14..4974b1429a2 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 # # Device majors for Xen. These are assigned here so that: diff --git a/sys/arch/x86/x86/pmap.c b/sys/arch/x86/x86/pmap.c index d952a925fc9..e20b947940a 100644 --- a/sys/arch/x86/x86/pmap.c +++ b/sys/arch/x86/x86/pmap.c @@ -902,8 +902,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 |
