diff --git a/Makefile.dep b/Makefile.dep
index b909cf125fbb960f484144cf8275b0bd6b80b469..bde52cff2826800f0dbee312c9c4cf3317c41c03 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -601,6 +601,10 @@ ifneq (,$(filter constfs,$(USEMODULE)))
   USEMODULE += vfs
 endif
 
+ifneq (,$(filter devfs,$(USEMODULE)))
+  USEMODULE += vfs
+endif
+
 # include package dependencies
 -include $(USEPKG:%=$(RIOTPKG)/%/Makefile.dep)
 
diff --git a/sys/Makefile b/sys/Makefile
index 8e4f04357749862c60ab83146959ad3924428b67..78a120f68561c18d857b2ad28f2c15e689b9823c 100644
--- a/sys/Makefile
+++ b/sys/Makefile
@@ -116,6 +116,10 @@ ifneq (,$(filter constfs,$(USEMODULE)))
     DIRS += fs/constfs
 endif
 
+ifneq (,$(filter devfs,$(USEMODULE)))
+    DIRS += fs/devfs
+endif
+
 DIRS += $(dir $(wildcard $(addsuffix /Makefile, ${USEMODULE})))
 
 include $(RIOTBASE)/Makefile.base
diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c
index c7813baf1ead038bbe7c9fa3b262975d77433fd9..5169fa97d94f245c746354ca4b488bb6ae4fcd22 100644
--- a/sys/auto_init/auto_init.c
+++ b/sys/auto_init/auto_init.c
@@ -152,6 +152,11 @@ void auto_init(void)
     DEBUG("Auto init gcoap module.\n");
     gcoap_init();
 #endif
+#ifdef MODULE_DEVFS
+    DEBUG("Mounting /dev\n");
+    extern void auto_init_devfs(void);
+    auto_init_devfs();
+#endif
 
 /* initialize network devices */
 #ifdef MODULE_AUTO_INIT_GNRC_NETIF
diff --git a/sys/fs/devfs/Makefile b/sys/fs/devfs/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..67d6ef1eb3cd8dd59d3e9a448e23ad091d9e3578
--- /dev/null
+++ b/sys/fs/devfs/Makefile
@@ -0,0 +1,2 @@
+MODULE=devfs
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/fs/devfs/auto_init_devfs.c b/sys/fs/devfs/auto_init_devfs.c
new file mode 100644
index 0000000000000000000000000000000000000000..9de9b57057c49468dbff748c093765a9c752bfd3
--- /dev/null
+++ b/sys/fs/devfs/auto_init_devfs.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 Eistec AB
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ *
+ */
+
+/**
+ * @ingroup     auto_init_fs
+ * @{
+ *
+ * @file
+ * @brief       Automatic mount of DevFS on /dev
+ *
+ * @author      Joakim Nohlgård <joakim.nohlgard@eistec.se>
+ *
+ * @}
+ */
+
+#include "vfs.h"
+#include "fs/devfs.h"
+
+#define ENABLE_DEBUG (0)
+#include "debug.h"
+
+static vfs_mount_t _devfs_auto_init_mount = {
+    .fs = &devfs_file_system,
+    .mount_point = "/dev",
+};
+
+void auto_init_devfs(void)
+{
+    DEBUG("auto_init_devfs: mounting /dev\n");
+    vfs_mount(&_devfs_auto_init_mount);
+}
diff --git a/sys/fs/devfs/devfs.c b/sys/fs/devfs/devfs.c
new file mode 100644
index 0000000000000000000000000000000000000000..07dc70b82663f30eacbff34f85f8bd60de609f4c
--- /dev/null
+++ b/sys/fs/devfs/devfs.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2016 Eistec AB
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     fs_devfs
+ * @{
+ *
+ * @file
+ * @brief       DevFS implementation
+ *
+ * @author      Joakim Nohlgård <joakim.nohlgard@eistec.se>
+ *
+ * @}
+ */
+
+/* Required for strnlen in string.h, when building with -std=c99 */
+#define _DEFAULT_SOURCE 1
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "fs/devfs.h"
+#include "vfs.h"
+#include "mutex.h"
+
+#define ENABLE_DEBUG (0)
+#include "debug.h"
+
+/**
+ * @internal
+ * @brief DevFS list head
+ *
+ * DevFS operates as a singleton, the same files show up in all mounted instances.
+ */
+static clist_node_t _devfs_list;
+/**
+ * @internal
+ * @brief mutex to protect the DevFS list from corruption
+ */
+static mutex_t _devfs_mutex = MUTEX_INIT;
+
+/* No need for file system operations, no extra work to be done on
+ * mount/umount. unlink is not permitted, use devfs_unregister instead */
+
+/* File operations */
+/* open is overloaded to allow searching for the correct device */
+static int devfs_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path);
+/* A minimal fcntl is also provided to enable SETFL handling */
+static int devfs_fcntl(vfs_file_t *filp, int cmd, int arg);
+
+/* Directory operations */
+static int devfs_opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path);
+static int devfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry);
+static int devfs_closedir(vfs_DIR *dirp);
+
+static const vfs_file_ops_t devfs_file_ops = {
+    .open  = devfs_open,
+    .fcntl = devfs_fcntl,
+};
+
+static const vfs_dir_ops_t devfs_dir_ops = {
+    .opendir = devfs_opendir,
+    .readdir = devfs_readdir,
+    .closedir = devfs_closedir,
+};
+
+const vfs_file_system_t devfs_file_system = {
+    .f_op = &devfs_file_ops,
+    .d_op = &devfs_dir_ops,
+};
+
+static int devfs_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path)
+{
+    DEBUG("devfs_open: %p, \"%s\", 0x%x, 0%03lo, \"%s\"\n", (void *)filp, name, flags, (unsigned long)mode, abs_path);
+    /* linear search through the device list */
+    mutex_lock(&_devfs_mutex);
+    clist_node_t *it = _devfs_list.next;
+    if (it == NULL) {
+        /* list empty */
+        mutex_unlock(&_devfs_mutex);
+        return -ENOENT;
+    }
+    do {
+        it = it->next;
+        devfs_t *devp = container_of(it, devfs_t, list_entry);
+        if (strcmp(devp->path, name) == 0) {
+            mutex_unlock(&_devfs_mutex);
+            DEBUG("devfs_open: Found :)\n");
+            /* Add private data from DevFS node */
+            filp->private_data.ptr = devp->private_data;
+            /* Replace f_op with the operations provided by the device driver */
+            filp->f_op = devp->f_op;
+            /* Chain the open() method for the specific device */
+            if (filp->f_op->open != NULL) {
+                return filp->f_op->open(filp, name, flags, mode, abs_path);
+            }
+            return 0;
+        }
+    } while (it != _devfs_list.next);
+    mutex_unlock(&_devfs_mutex);
+    DEBUG("devfs_open: Not found :(\n");
+    return -ENOENT;
+}
+
+static int devfs_fcntl(vfs_file_t *filp, int cmd, int arg)
+{
+    DEBUG("devfs_fcntl: %p, 0x%x, 0x%x\n", (void *)filp, cmd, arg);
+    switch (cmd) {
+        /* F_GETFL is handled directly by vfs_fcntl */
+        case F_SETFL:
+            DEBUG("devfs_fcntl: SETFL: %d\n", arg);
+            filp->flags = arg;
+            return filp->flags;
+        default:
+            return -EINVAL;
+    }
+}
+
+static int devfs_opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path)
+{
+    (void) abs_path;
+    DEBUG("devfs_opendir: %p, \"%s\", \"%s\"\n", (void *)dirp, dirname, abs_path);
+    if (strncmp(dirname, "/", 2) != 0) {
+        /* We keep it simple and only support a flat file system, only a root directory */
+        return -ENOENT;
+    }
+    dirp->private_data.ptr = NULL;
+    return 0;
+}
+
+static int devfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry)
+{
+    DEBUG("devfs_readdir: %p, %p\n", (void *)dirp, (void *)entry);
+    mutex_lock(&_devfs_mutex);
+    clist_node_t *it = dirp->private_data.ptr;
+    if (it == _devfs_list.next) {
+        /* end of list was reached */
+        mutex_unlock(&_devfs_mutex);
+        return 0;
+    }
+    if (it == NULL) {
+        /* first readdir after opendir */
+        it = _devfs_list.next;
+        if (it == NULL) {
+            /* empty list */
+            mutex_unlock(&_devfs_mutex);
+            return 0;
+        }
+    }
+    it = it->next;
+    dirp->private_data.ptr = it;
+    mutex_unlock(&_devfs_mutex);
+    devfs_t *devp = container_of(it, devfs_t, list_entry);
+    if (devp->path == NULL) {
+        /* skip past the broken entry and try again */
+        return -EAGAIN;
+    }
+    size_t len = strnlen(devp->path, VFS_NAME_MAX + 1);
+    if (len > VFS_NAME_MAX) {
+        /* name does not fit in vfs_dirent_t buffer */
+        /* skip past the broken entry and try again */
+        return -EAGAIN;
+    }
+    /* clear the dirent */
+    memset(entry, 0, sizeof(*entry));
+    /* copy the string, including terminating null */
+    memcpy(&entry->d_name[0], devp->path, len + 1);
+    return 1;
+}
+
+static int devfs_closedir(vfs_DIR *dirp)
+{
+    /* Just an example, it's not necessary to define closedir if there is
+     * nothing to clean up */
+    (void) dirp;
+    DEBUG("devfs_closedir: %p\n", (void *)dirp);
+    return 0;
+}
+
+int devfs_register(devfs_t *devp)
+{
+    DEBUG("devfs_register: %p\n", (void *)devp);
+    if (devp == NULL) {
+        return -EINVAL;
+    }
+    DEBUG("devfs_register: \"%s\" -> (%p, %p)\n", devp->path, (void *)devp->f_op, devp->private_data);
+    if (devp->path == NULL) {
+        return -EINVAL;
+    }
+    if (devp->f_op == NULL) {
+        return -EINVAL;
+    }
+    mutex_lock(&_devfs_mutex);
+    clist_node_t *it = _devfs_list.next;
+    if (it != NULL) {
+        /* list not empty */
+        do {
+            it = it->next;
+            if (it == &devp->list_entry) {
+                /* Already registered */
+                mutex_unlock(&_devfs_mutex);
+                DEBUG("devfs_register: %p already registered\n", (void *)devp);
+                return -EEXIST;
+            }
+            devfs_t *devit = container_of(it, devfs_t, list_entry);
+            if (strcmp(devit->path, devp->path) == 0) {
+                /* Path already registered */
+                mutex_unlock(&_devfs_mutex);
+                DEBUG("devfs_register: \"%s\" occupied\n", devp->path);
+                return -EEXIST;
+            }
+        } while(it != _devfs_list.next);
+    }
+    /* insert last in list */
+    clist_rpush(&_devfs_list, &devp->list_entry);
+    mutex_unlock(&_devfs_mutex);
+    return 0;
+}
+
+int devfs_unregister(devfs_t *devp)
+{
+    DEBUG("devfs_unregister: %p\n", (void *)devp);
+    if (devp == NULL) {
+        return -EINVAL;
+    }
+    mutex_lock(&_devfs_mutex);
+    /* find devp in the list and remove it */
+    clist_node_t *node = clist_remove(&_devfs_list, &devp->list_entry);
+    mutex_unlock(&_devfs_mutex);
+    if (node == NULL) {
+        /* not found */
+        DEBUG("devfs_unregister: ERR not registered!\n");
+        return -ENOENT;
+    }
+    return 0;
+}
diff --git a/sys/include/fs/devfs.h b/sys/include/fs/devfs.h
new file mode 100644
index 0000000000000000000000000000000000000000..f5c4260edd67b44b0fc333f92fdbdd74ae8b4fd9
--- /dev/null
+++ b/sys/include/fs/devfs.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 Eistec AB
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @defgroup  fs_devfs DevFS device file system
+ * @ingroup   fs
+ * @brief     Dynamic device file system
+ *
+ * This file system implementation allows devices to register file names for
+ * easier access to device drivers from shell commands etc.
+ *
+ * The idea is similar to the /dev directory on Unix.
+ *
+ * @{
+ * @file
+ * @brief   DevFS public API
+ * @author  Joakim Nohlgård <joakim.nohlgard@eistec.se>
+ */
+
+#ifndef DEVFS_H_
+#define DEVFS_H_
+
+#include "clist.h"
+#include "vfs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief DevFS node typedef
+ */
+typedef struct devfs devfs_t;
+
+/**
+ * @brief A device "file" consists of a file name and an opaque pointer to device driver private data
+ *
+ * The file system is implemented as a linked list.
+ */
+struct devfs {
+    clist_node_t list_entry;    /**< List item entry */
+    const char *path;           /**< File system relative path to this node */
+    const vfs_file_ops_t *f_op; /**< Pointer to file operations table for this device */
+    void *private_data;         /**< Pointer to device driver specific data */
+};
+
+/**
+ * @brief DevFS file system driver
+ *
+ * For use with vfs_mount
+ */
+extern const vfs_file_system_t devfs_file_system;
+
+/**
+ * @brief Register a node in DevFS
+ *
+ * The node will immediately become available to @c vfs_open, if DevFS is already
+ * mounted somewhere.
+ *
+ * If DevFS is not mounted, the node will be registered and will become
+ * available to @c vfs_open when DevFS is mounted.
+ *
+ * @param[in]  node    DevFS node to register
+ *
+ * @return 0 on success
+ * @return <0 on error
+ */
+int devfs_register(devfs_t *node);
+
+/**
+ * @brief Remove a registration from DevFS
+ *
+ * The node will no longer be available to @c vfs_open, but any already opened FDs
+ * will remain open.
+ *
+ * @param[in]  node    DevFS node to unregister
+ *
+ * @return 0 on success
+ * @return <0 on error
+ */
+int devfs_unregister(devfs_t *node);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+/** @} */
diff --git a/tests/unittests/tests-devfs/Makefile b/tests/unittests/tests-devfs/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2
--- /dev/null
+++ b/tests/unittests/tests-devfs/Makefile
@@ -0,0 +1 @@
+include $(RIOTBASE)/Makefile.base
diff --git a/tests/unittests/tests-devfs/Makefile.include b/tests/unittests/tests-devfs/Makefile.include
new file mode 100644
index 0000000000000000000000000000000000000000..ded525dbfa8b4f257377f29963ca0269ed244262
--- /dev/null
+++ b/tests/unittests/tests-devfs/Makefile.include
@@ -0,0 +1,2 @@
+USEMODULE += vfs
+USEMODULE += devfs
diff --git a/tests/unittests/tests-devfs/tests-devfs.c b/tests/unittests/tests-devfs/tests-devfs.c
new file mode 100644
index 0000000000000000000000000000000000000000..cb9dd321c714a5e560ec1683fdc71b006676edbd
--- /dev/null
+++ b/tests/unittests/tests-devfs/tests-devfs.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 Eistec AB
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @{
+ *
+ * @file
+ * @brief       Unittests for DevFS
+ *
+ * @author      Joakim Nohlgård <joakim.nohlgard@eistec.se>
+ */
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "fs/devfs.h"
+
+#include "embUnit/embUnit.h"
+
+#include "tests-devfs.h"
+
+static int _mock_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path);
+static ssize_t _mock_read(vfs_file_t *filp, void *dest, size_t nbytes);
+static ssize_t _mock_write(vfs_file_t *filp, const void *src, size_t nbytes);
+
+static volatile int _mock_open_calls = 0;
+static volatile int _mock_read_calls = 0;
+static volatile int _mock_write_calls = 0;
+
+static int _mock_private_data;
+
+static const vfs_file_ops_t _mock_devfs_ops = {
+    .open = _mock_open,
+    .read = _mock_read,
+    .write = _mock_write,
+};
+
+static int _mock_private_data_tag = 4321;
+
+static devfs_t _mock_devfs_node = {
+    .path = "/mock0",
+    .f_op = &_mock_devfs_ops,
+    .private_data = &_mock_private_data_tag,
+};
+
+static vfs_mount_t _test_devfs_mount = {
+    .fs = &devfs_file_system,
+    .mount_point = "/test",
+    .private_data = &_mock_private_data,
+};
+
+static int _mock_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path)
+{
+    (void) name;
+    (void) flags;
+    (void) mode;
+    (void) abs_path;
+    if (filp->private_data.ptr != &_mock_private_data_tag) {
+        return -4321;
+    }
+    int *np = filp->mp->private_data;
+    ++(*np);
+    ++_mock_open_calls;
+    return 0;
+}
+
+static ssize_t _mock_read(vfs_file_t *filp, void *dest, size_t nbytes)
+{
+    (void) dest;
+    (void) nbytes;
+    if (filp->private_data.ptr != &_mock_private_data_tag) {
+        return -4321;
+    }
+    int *np = filp->mp->private_data;
+    ++(*np);
+    ++_mock_read_calls;
+    return 0;
+}
+
+static ssize_t _mock_write(vfs_file_t *filp, const void *src, size_t nbytes)
+{
+    (void) src;
+    (void) nbytes;
+    if (filp->private_data.ptr != &_mock_private_data_tag) {
+        return -4321;
+    }
+    int *np = filp->mp->private_data;
+    ++(*np);
+    ++_mock_write_calls;
+    return 0;
+}
+
+static void test_devfs_register(void)
+{
+    int res = devfs_register(NULL);
+    TEST_ASSERT(res < 0);
+
+    res = devfs_register(&_mock_devfs_node);
+    TEST_ASSERT(res == 0);
+
+    res = devfs_register(&_mock_devfs_node);
+    TEST_ASSERT(res < 0);
+
+    res = devfs_unregister(&_mock_devfs_node);
+    TEST_ASSERT(res == 0);
+
+    res = devfs_unregister(&_mock_devfs_node);
+    TEST_ASSERT(res < 0);
+}
+
+static void test_devfs_mount_open(void)
+{
+    _mock_private_data = 12345;
+    int res;
+    res = vfs_mount(&_test_devfs_mount);
+    TEST_ASSERT_EQUAL_INT(0, res);
+    TEST_ASSERT_EQUAL_INT(_mock_private_data, 12345);
+
+    res = devfs_register(&_mock_devfs_node);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    int count = _mock_open_calls;
+    int fd = vfs_open("/test/mock0", O_RDWR, 0);
+    TEST_ASSERT(fd >= 0);
+    TEST_ASSERT_EQUAL_INT(count + 1, _mock_open_calls);
+    TEST_ASSERT_EQUAL_INT(_mock_private_data, 12346);
+
+    res = vfs_close(fd);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = devfs_unregister(&_mock_devfs_node);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_umount(&_test_devfs_mount);
+    TEST_ASSERT_EQUAL_INT(0, res);
+}
+
+Test *tests_devfs_tests(void)
+{
+    EMB_UNIT_TESTFIXTURES(fixtures) {
+        new_TestFixture(test_devfs_register),
+        new_TestFixture(test_devfs_mount_open),
+    };
+
+    EMB_UNIT_TESTCALLER(devfs_tests, NULL, NULL, fixtures);
+
+    return (Test *)&devfs_tests;
+}
+
+void tests_devfs(void)
+{
+    TESTS_RUN(tests_devfs_tests());
+}
+/** @} */
diff --git a/tests/unittests/tests-devfs/tests-devfs.h b/tests/unittests/tests-devfs/tests-devfs.h
new file mode 100644
index 0000000000000000000000000000000000000000..1d9a45e66ae6c38dcfd4f70bd14b2f6637dc4d38
--- /dev/null
+++ b/tests/unittests/tests-devfs/tests-devfs.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 Eistec AB
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @addtogroup  unittests
+ * @{
+ *
+ * @file
+ * @brief       Unittests for DevFS
+ *
+ * @author      Joakim Nohlgård <joakim.nohlgard@eistec.se>
+ */
+#ifndef TESTS_DEVFS_H
+#define TESTS_DEVFS_H
+
+#include "embUnit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   The entry point of this test suite.
+ */
+void tests_devfs(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TESTS_DEVFS_H */
+/** @} */