diff --git a/Makefile.dep b/Makefile.dep
index 65411f6db73347a28da47caa42e1be5ec43a6851..0b4d6df2bd71df169b5b2ec7345ba6458da924e7 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -373,6 +373,10 @@ ifneq (,$(filter rtt_stdio,$(USEMODULE)))
 endif
 
 ifneq (,$(filter uart_stdio,$(USEMODULE)))
+  USEMODULE += isrpipe
+endif
+
+ifneq (,$(filter isrpipe,$(USEMODULE)))
   USEMODULE += tsrb
 endif
 
diff --git a/drivers/ethos/ethos.c b/drivers/ethos/ethos.c
index 698a365d70960f84d2d4da70c8f0414a01b779d9..8a2c2a680e9e7ece6c56b53a10a29f3649ff5e84 100644
--- a/drivers/ethos/ethos.c
+++ b/drivers/ethos/ethos.c
@@ -35,6 +35,8 @@
 
 #ifdef USE_ETHOS_FOR_STDIO
 #include "uart_stdio.h"
+#include "isrpipe.h"
+extern isrpipe_t uart_stdio_isrpipe;
 #endif
 
 #define ENABLE_DEBUG (0)
@@ -100,7 +102,7 @@ static void _handle_char(ethos_t *dev, char c)
 #ifdef USE_ETHOS_FOR_STDIO
         case ETHOS_FRAME_TYPE_TEXT:
             dev->framesize++;
-            uart_stdio_rx_cb(NULL, c);
+            isrpipe_write_one(&uart_stdio_isrpipe, c);
 #endif
     }
 }
diff --git a/sys/include/isrpipe.h b/sys/include/isrpipe.h
new file mode 100644
index 0000000000000000000000000000000000000000..3d4590b07f336d30ba0588a29e5bb1cad652f329
--- /dev/null
+++ b/sys/include/isrpipe.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * 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 sys
+ * @{
+ * @file
+ * @brief       ISR -> userspace pipe interface
+ *
+ * @author      Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ */
+
+#ifndef ISRPIPE_H
+#define ISRPIPE_H
+
+#include <stdint.h>
+
+#include "mutex.h"
+#include "tsrb.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Context structure for isrpipe
+ */
+typedef struct {
+    mutex_t mutex;      /**< isrpipe mutex */
+    tsrb_t tsrb;        /**< isrpipe thread safe ringbuffer */
+} isrpipe_t;
+
+/**
+ * @brief   Static initializer for irspipe
+ */
+#define ISRPIPE_INIT(tsrb_buf) { .mutex = MUTEX_INIT, .tsrb = TSRB_INIT(tsrb_buf) }
+
+/**
+ * @brief   Initialisation function for isrpipe
+ *
+ * @param[in]   isrpipe     isrpipe object to initialize
+ * @param[in]   buf         buffer to use as ringbuffer (must be power of two sized!)
+ * @param[in]   bufsize     size of @p buf
+ */
+void isrpipe_init(isrpipe_t *isrpipe, char *buf, size_t bufsize);
+
+/**
+ * @brief   Put one character into the isrpipe's buffer
+ *
+ * @param[in]   isrpipe     isrpipe object to initialize
+ * @param[in]   c           character to add to isrpipe buffer
+ *
+ * @returns     0 if character could be added
+ * @returns     -1 if buffer was full
+ */
+int isrpipe_write_one(isrpipe_t *isrpipe, char c);
+
+/**
+ * @brief   Read data from isrpipe (blocking)
+ *
+ * @param[in]   isrpipe    isrpipe object to operate on
+ * @param[in]   buf        buffer to write to
+ * @param[in]   count      number of bytes to read
+ *
+ * @returns     number of bytes read
+ */
+int isrpipe_read(isrpipe_t *isrpipe, char *buf, size_t count);
+
+/**
+ * @brief   Read data from isrpipe (with timeout, blocking)
+ *
+ * Currently, the timeout parameter is applied on every underlying read, which
+ * might be *per single byte*.
+ *
+ * @note This function might return less than @p count bytes
+ *
+ * @param[in]   isrpipe    isrpipe object to operate on
+ * @param[in]   buf        buffer to write to
+ * @param[in]   count      number of bytes to read
+ * @param[in]   timeout    timeout in ms
+ *
+ * @returns     number of bytes read
+ * @returns     -ETIMEDOUT on timeout
+ */
+int isrpipe_read_timeout(isrpipe_t *isrpipe, char *buf, size_t count, uint32_t timeout);
+
+/**
+ * @brief   Read data from isrpipe (with timeout, blocking, wait until all read)
+ *
+ * This function is like @ref isrpipe_read_timeout, but will only return on
+ * timeout or when @p count bytes have been received.
+ *
+ * @param[in]   isrpipe    isrpipe object to operate on
+ * @param[in]   buf        buffer to write to
+ * @param[in]   count      number of bytes to read
+ * @param[in]   timeout    timeout in ms
+ *
+ * @returns     number of bytes read
+ * @returns     -ETIMEDOUT on timeout
+ */
+int isrpipe_read_all_timeout(isrpipe_t *isrpipe, char *buf, size_t count, uint32_t timeout);
+
+#ifdef __cplusplus
+}
+#endif
+/** @} */
+#endif /* ISRPIPE_H */
diff --git a/sys/isrpipe/Makefile b/sys/isrpipe/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2
--- /dev/null
+++ b/sys/isrpipe/Makefile
@@ -0,0 +1 @@
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/isrpipe/isrpipe.c b/sys/isrpipe/isrpipe.c
new file mode 100644
index 0000000000000000000000000000000000000000..c9f6e1926acbc10950afc73b61af471a3c226a0a
--- /dev/null
+++ b/sys/isrpipe/isrpipe.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2016 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * 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 sys
+ * @{
+ * @file
+ * @brief       ISR -> userspace pipe implementation
+ *
+ * @author      Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * @}
+ */
+
+#include <errno.h>
+
+#include "isrpipe.h"
+#include "xtimer.h"
+
+void isrpipe_init(isrpipe_t *isrpipe, char *buf, size_t bufsize)
+{
+    mutex_init(&isrpipe->mutex);
+    tsrb_init(&isrpipe->tsrb, buf, bufsize);
+}
+
+int isrpipe_write_one(isrpipe_t *isrpipe, char c)
+{
+    int res = tsrb_add_one(&isrpipe->tsrb, c);
+
+    /* `res` is either 0 on success or -1 when the buffer is full. Either way,
+     * unlocking the mutex is fine.
+     */
+    mutex_unlock(&isrpipe->mutex);
+
+    return res;
+}
+
+int isrpipe_read(isrpipe_t *isrpipe, char *buffer, size_t count)
+{
+    int res;
+
+    while (!(res = tsrb_get(&isrpipe->tsrb, buffer, count))) {
+        mutex_lock(&isrpipe->mutex);
+    }
+    return res;
+}
+
+typedef struct {
+    mutex_t *mutex;
+    int flag;
+} _isrpipe_timeout_t;
+
+static void _cb(void *arg)
+{
+    _isrpipe_timeout_t *_timeout = (_isrpipe_timeout_t *) arg;
+
+    _timeout->flag = 1;
+    mutex_unlock(_timeout->mutex);
+}
+
+int isrpipe_read_timeout(isrpipe_t *isrpipe, char *buffer, size_t count, uint32_t timeout)
+{
+    int res;
+
+    _isrpipe_timeout_t _timeout = { .mutex = &isrpipe->mutex, .flag = 0 };
+
+    xtimer_t timer = { .callback = _cb, .arg = &_timeout };
+
+    xtimer_set(&timer, timeout);
+    while (!(res = tsrb_get(&isrpipe->tsrb, buffer, count))) {
+        mutex_lock(&isrpipe->mutex);
+        if (_timeout.flag) {
+            res = -ETIMEDOUT;
+            break;
+        }
+    }
+
+    xtimer_remove(&timer);
+    return res;
+}
+
+
+int isrpipe_read_all_timeout(isrpipe_t *isrpipe, char *buffer, size_t count, uint32_t timeout)
+{
+    char *pos = buffer;
+
+    while (count) {
+        int res = isrpipe_read_timeout(isrpipe, pos, count, timeout);
+        if (res >= 0) {
+            count -= res;
+            pos += res;
+        }
+        else {
+            return res;
+        }
+    }
+
+    return pos - buffer;
+}
diff --git a/sys/uart_stdio/uart_stdio.c b/sys/uart_stdio/uart_stdio.c
index 14dace5e5c86255ff8b4bf34f9a5ce0e87144572..55f2a395be8f55d88db5a1126784f4882185629b 100644
--- a/sys/uart_stdio/uart_stdio.c
+++ b/sys/uart_stdio/uart_stdio.c
@@ -24,15 +24,12 @@
  */
 
 #include <stdio.h>
-#include "uart_stdio.h"
 
-#include "tsrb.h"
-#include "thread.h"
-#include "mutex.h"
-#include "irq.h"
+#include "uart_stdio.h"
 
 #include "board.h"
 #include "periph/uart.h"
+#include "isrpipe.h"
 
 #ifdef USE_ETHOS_FOR_STDIO
 #include "ethos.h"
@@ -42,39 +39,21 @@ extern ethos_t ethos;
 #define ENABLE_DEBUG 0
 #include "debug.h"
 
-/**
- * @brief use mutex for waiting on incoming UART chars
- */
-static mutex_t _rx_mutex = MUTEX_INIT;
 static char _rx_buf_mem[UART_STDIO_RX_BUFSIZE];
-static tsrb_t _rx_buf = TSRB_INIT(_rx_buf_mem);
-
-/**
- * @brief Receive a new character from the UART and put it into the receive buffer
- */
-void uart_stdio_rx_cb(void *arg, uint8_t data)
-{
-    (void)arg;
-    tsrb_add_one(&_rx_buf, (uint8_t)data);
-    mutex_unlock(&_rx_mutex);
-}
+isrpipe_t uart_stdio_isrpipe = ISRPIPE_INIT(_rx_buf_mem);
 
 void uart_stdio_init(void)
 {
 #ifndef USE_ETHOS_FOR_STDIO
-    uart_init(UART_STDIO_DEV, UART_STDIO_BAUDRATE, uart_stdio_rx_cb, NULL);
+    uart_init(UART_STDIO_DEV, UART_STDIO_BAUDRATE, (uart_rx_cb_t) isrpipe_write_one, &uart_stdio_isrpipe);
 #else
-    uart_init(ETHOS_UART, ETHOS_BAUDRATE, uart_stdio_rx_cb, NULL);
+    uart_init(ETHOS_UART, ETHOS_BAUDRATE, (uart_rx_cb_t) isrpipe_write_one, &uart_stdio_isrpipe);
 #endif
 }
 
 int uart_stdio_read(char* buffer, int count)
 {
-    int res;
-    while (!(res = tsrb_get(&_rx_buf, buffer, count))) {
-        mutex_lock(&_rx_mutex);
-    }
-    return res;
+    return isrpipe_read(&uart_stdio_isrpipe, buffer, count);
 }
 
 int uart_stdio_write(const char* buffer, int len)