diff --git a/stmhal/modselect.c b/stmhal/modselect.c
index 6b58c04bb8bfe0f9c432f4dcbc1f4c48819ec82c..6ae3585352190db5dae3cdb954eea78d73901867 100644
--- a/stmhal/modselect.c
+++ b/stmhal/modselect.c
@@ -35,16 +35,10 @@
 #include "qstr.h"
 #include "obj.h"
 #include "objlist.h"
+#include "pybioctl.h"
 
 /// \moduleref select
 
-#define MP_IOCTL_POLL (0x100 | 1)
-
-#define MP_IOCTL_POLL_RD  (0x0001)
-#define MP_IOCTL_POLL_WR  (0x0002)
-#define MP_IOCTL_POLL_HUP (0x0004)
-#define MP_IOCTL_POLL_ERR (0x0008)
-
 typedef struct _poll_obj_t {
     mp_uint_t (*ioctl)(mp_obj_t obj, mp_uint_t request, int *errcode, ...);
     mp_uint_t flags;
@@ -85,13 +79,13 @@ STATIC mp_uint_t poll_map_poll(mp_map_t *poll_map, mp_uint_t *rwx_num) {
         }
 
         poll_obj_t *poll_obj = (poll_obj_t*)poll_map->table[i].value;
-        int errno;
-        mp_int_t ret = poll_obj->ioctl(poll_map->table[i].key, MP_IOCTL_POLL, &errno, poll_obj->flags);
+        int errcode;
+        mp_int_t ret = poll_obj->ioctl(poll_map->table[i].key, MP_IOCTL_POLL, &errcode, poll_obj->flags);
         poll_obj->flags_ret = ret;
 
         if (ret == -1) {
             // error doing ioctl
-            nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errno)));
+            nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errcode)));
         }
 
         if (ret != 0) {
diff --git a/stmhal/pybioctl.h b/stmhal/pybioctl.h
new file mode 100644
index 0000000000000000000000000000000000000000..79f994b8b6b3d645b8ad5b00af799a4e8097a467
--- /dev/null
+++ b/stmhal/pybioctl.h
@@ -0,0 +1,6 @@
+#define MP_IOCTL_POLL (0x100 | 1)
+
+#define MP_IOCTL_POLL_RD  (0x0001)
+#define MP_IOCTL_POLL_WR  (0x0002)
+#define MP_IOCTL_POLL_HUP (0x0004)
+#define MP_IOCTL_POLL_ERR (0x0008)
diff --git a/stmhal/uart.c b/stmhal/uart.c
index 1cf718c5c039df7584289a6cd4cf46175cfc3b89..6cb8a8e40df807f92a35d5c9e441ede2364d4549 100644
--- a/stmhal/uart.c
+++ b/stmhal/uart.c
@@ -26,6 +26,7 @@
 
 #include <stdio.h>
 #include <string.h>
+#include <stdarg.h>
 
 #include "stm32f4xx_hal.h"
 
@@ -37,6 +38,7 @@
 #include "runtime.h"
 #include "bufhelper.h"
 #include "uart.h"
+#include "pybioctl.h"
 
 /// \moduleref pyb
 /// \class UART - duplex serial communication bus
@@ -475,10 +477,41 @@ STATIC const mp_map_elem_t pyb_uart_locals_dict_table[] = {
 
 STATIC MP_DEFINE_CONST_DICT(pyb_uart_locals_dict, pyb_uart_locals_dict_table);
 
+mp_uint_t uart_ioctl(mp_obj_t self_in, mp_uint_t request, int *errcode, ...) {
+    pyb_uart_obj_t *self = self_in;
+    va_list vargs;
+    va_start(vargs, errcode);
+    mp_uint_t ret;
+    if (request == MP_IOCTL_POLL) {
+        mp_uint_t flags = va_arg(vargs, mp_uint_t);
+        ret = 0;
+        if (flags & MP_IOCTL_POLL_RD && uart_rx_any(self)) {
+            ret |= MP_IOCTL_POLL_RD;
+        }
+        if (flags & MP_IOCTL_POLL_WR) {
+            // TODO can we always write?
+            ret |= MP_IOCTL_POLL_WR;
+        }
+    } else {
+        *errcode = 1; // EPERM, operation not permitted
+        ret = -1;
+    }
+    va_end(vargs);
+    return ret;
+}
+
+STATIC const mp_stream_p_t uart_stream_p = {
+    //.read = uart_read, // TODO
+    //.write = uart_write, // TODO
+    .ioctl = uart_ioctl,
+    .is_text = false,
+};
+
 const mp_obj_type_t pyb_uart_type = {
     { &mp_type_type },
     .name = MP_QSTR_UART,
     .print = pyb_uart_print,
     .make_new = pyb_uart_make_new,
+    .stream_p = &uart_stream_p,
     .locals_dict = (mp_obj_t)&pyb_uart_locals_dict,
 };