diff --git a/extmod/modmachine.c b/extmod/modmachine.c
index 72197879656d46835e7405e03f9b4c2aa74e1f9a..b9ebee120ec443e7438684195c74e08fa07abacd 100644
--- a/extmod/modmachine.c
+++ b/extmod/modmachine.c
@@ -29,22 +29,44 @@
 
 #include "py/nlr.h"
 #include "py/obj.h"
+#if MICROPY_PLAT_DEV_MEM
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#endif
 
 #if MICROPY_PY_MACHINE
 
-STATIC mp_uint_t get_read_addr(mp_obj_t addr_o, uint align) {
+#define PAGE_SIZE 4096
+#define PAGE_MASK (PAGE_SIZE - 1)
+
+STATIC mp_uint_t get_addr(mp_obj_t addr_o, uint align) {
     mp_uint_t addr = mp_obj_int_get_truncated(addr_o);
     if ((addr & (align - 1)) != 0) {
         nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "address %08x is not aligned to %d bytes", addr, align));
     }
-    return addr;
-}
+    #if MICROPY_PLAT_DEV_MEM
+    {
+        // Not thread-safe
+        static int fd;
+        static mp_uint_t last_base = (mp_uint_t)-1;
+        static mp_uint_t map_page;
+        if (!fd) {
+            fd = open("/dev/mem", O_RDWR | O_SYNC);
+            if (fd == -1) {
+                nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errno)));
+            }
+        }
 
-STATIC mp_uint_t get_write_addr(mp_obj_t addr_o, uint align) {
-    mp_uint_t addr = mp_obj_int_get_truncated(addr_o);
-    if ((addr & (align - 1)) != 0) {
-        nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "address %08x is not aligned to %d bytes", addr, align));
+        mp_uint_t cur_base = addr & ~PAGE_MASK;
+        if (cur_base != last_base) {
+            map_page = (mp_uint_t)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, cur_base);
+            last_base = cur_base;
+        }
+        addr = map_page + (addr & PAGE_MASK);
     }
+    #endif
+
     return addr;
 }
 
@@ -67,7 +89,7 @@ STATIC mp_obj_t machine_mem_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t va
         return MP_OBJ_NULL; // op not supported
     } else if (value == MP_OBJ_SENTINEL) {
         // load
-        mp_uint_t addr = get_read_addr(index, self->elem_size);
+        mp_uint_t addr = get_addr(index, self->elem_size);
         uint32_t val;
         switch (self->elem_size) {
             case 1: val = (*(uint8_t*)addr); break;
@@ -77,7 +99,7 @@ STATIC mp_obj_t machine_mem_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t va
         return mp_obj_new_int(val);
     } else {
         // store
-        mp_uint_t addr = get_write_addr(index, self->elem_size);
+        mp_uint_t addr = get_addr(index, self->elem_size);
         uint32_t val = mp_obj_get_int(value);
         switch (self->elem_size) {
             case 1: (*(uint8_t*)addr) = val; break;
diff --git a/tests/extmod/machine1.py b/tests/extmod/machine1.py
index e7fd40d4dbd2f969741d5284a3858484eac38bb7..af4dacd03a01f362153288e2034d82566fdee6c0 100644
--- a/tests/extmod/machine1.py
+++ b/tests/extmod/machine1.py
@@ -7,22 +7,8 @@ except ImportError:
     import sys
     sys.exit()
 
-import uctypes
-
 print(machine.mem8)
 
-buf = bytearray(8)
-addr = uctypes.addressof(buf)
-
-machine.mem8[addr] = 123
-print(machine.mem8[addr])
-
-machine.mem16[addr] = 12345
-print(machine.mem16[addr])
-
-machine.mem32[addr] = 123456789
-print(machine.mem32[addr])
-
 try:
     machine.mem16[1]
 except ValueError:
diff --git a/tests/extmod/machine1.py.exp b/tests/extmod/machine1.py.exp
index e46afc7528d5cedef8ee6df75f76f83e62532f09..bb421ea5cfa42c0a90ac73fd0681491874d4101a 100644
--- a/tests/extmod/machine1.py.exp
+++ b/tests/extmod/machine1.py.exp
@@ -1,7 +1,4 @@
 <8-bit memory>
-123
-12345
-123456789
 ValueError
 ValueError
 TypeError
diff --git a/tests/extmod/machine_mem.py b/tests/extmod/machine_mem.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d8a9ac01e4538a3985a84122670458be6c56614
--- /dev/null
+++ b/tests/extmod/machine_mem.py
@@ -0,0 +1,16 @@
+# This test requires root privilege, so is usually skipped
+# It also assumes x86 legacy hardware (with Video BIOS present).
+
+try:
+    import machine
+except ImportError:
+    print("SKIP")
+    import sys
+    sys.exit()
+
+try:
+    print(hex(machine.mem16[0xc0000]))
+except OSError:
+    print("SKIP")
+    import sys
+    sys.exit()
diff --git a/tests/extmod/machine_mem.py.exp b/tests/extmod/machine_mem.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..371f8fa44dc67bef9eb3c613612596fc0f187d6c
--- /dev/null
+++ b/tests/extmod/machine_mem.py.exp
@@ -0,0 +1 @@
+0xaa55
diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h
index a0e7bf4674a6a2cbddc00680756c807e4187c6b7..9f65bc9295a11a161f848173761e38a782badc9b 100644
--- a/unix/mpconfigport.h
+++ b/unix/mpconfigport.h
@@ -179,6 +179,11 @@ void mp_unix_mark_exec(void);
 
 #define MP_PLAT_PRINT_STRN(str, len) fwrite(str, 1, len, stdout)
 
+#ifdef __linux__
+// Can access physical memory using /dev/mem
+#define MICROPY_PLAT_DEV_MEM  (1)
+#endif
+
 extern const struct _mp_obj_fun_builtin_t mp_builtin_input_obj;
 extern const struct _mp_obj_fun_builtin_t mp_builtin_open_obj;
 #define MICROPY_PORT_BUILTINS \