diff --git a/.vscode/launch.json b/.vscode/launch.json
index 785c352a3d3569853f66bdc9cd77bc5b3a4cbc9b..9cdce3d3aaa3a7dcf2b76673d29430147c5fcd65 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -4,84 +4,84 @@
     // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
     "version": "0.2.0",
     "configurations": [
-        {
-            "type": "gdb",
-            "request": "attach",
-            "name": "hello_debug",
-            "gdbpath": "/usr/bin/arm-none-eabi-gdb",
-            "executable": "./target/thumbv7em-none-eabihf/debug/examples/hello_debug",
-            "target": ":3333",
-            "remote": true,
-            "autorun": [
-                "load",
-            ],
-            "cwd": "${workspaceRoot}"
-        },
-        {
-            "type": "gdb",
-            "request": "attach",
-            "name": "serial-echo",
-            "gdbpath": "/usr/bin/arm-none-eabi-gdb",
-            "executable": "./target/thumbv7em-none-eabihf/debug/examples/serial-echo",
-            "target": ":3333",
-            "remote": true,
-            "autorun": [
-                "load"
-            ],
-            "cwd": "${workspaceRoot}"
-        },
-        {
-            "type": "gdb",
-            "request": "attach",
-            "name": "rtfm-blinky-systic",
-            "gdbpath": "/usr/bin/arm-none-eabi-gdb",
-            "executable": "./target/thumbv7em-none-eabihf/debug/examples/rtfm-blinky-systic",
-            "target": ":3333",
-            "remote": true,
-            "autorun": [
-                "load"
-            ],
-            "cwd": "${workspaceRoot}"
-        },
-        {
-            "type": "gdb",
-            "request": "attach",
-            "name": "rtfm-blinky-tim2",
-            "gdbpath": "/usr/bin/arm-none-eabi-gdb",
-            "executable": "./target/thumbv7em-none-eabihf/debug/examples/rtfm-blinky-tim2",
-            "target": ":3333",
-            "remote": true,
-            "autorun": [
-                "load"
-            ],
-            "cwd": "${workspaceRoot}"
-        },
-        {
-            "type": "gdb",
-            "request": "attach",
-            "name": "rtfm-serial-echo",
-            "gdbpath": "/usr/bin/arm-none-eabi-gdb",
-            "executable": "./target/thumbv7em-none-eabihf/debug/examples/rtfm-serial-echo",
-            "target": ":3333",
-            "remote": true,
-            "autorun": [
-                "load"
-            ],
-            "cwd": "${workspaceRoot}"
-        },
-        {
-            "type": "gdb",
-            "request": "attach",
-            "name": "serial-dma-tx",
-            "gdbpath": "/usr/bin/arm-none-eabi-gdb",
-            "executable": "./target/thumbv7em-none-eabihf/debug/examples/serial-dma-tx",
-            "target": ":3333",
-            "remote": true,
-            "autorun": [
-                "load"
-            ],
-            "cwd": "${workspaceRoot}"
-        },
+        // {
+        //     "type": "gdb",
+        //     "request": "attach",
+        //     "name": "hello_debug",
+        //     "gdbpath": "/usr/bin/arm-none-eabi-gdb",
+        //     "executable": "./target/thumbv7em-none-eabihf/debug/examples/hello_debug",
+        //     "target": ":3333",
+        //     "remote": true,
+        //     "autorun": [
+        //         "load",
+        //     ],
+        //     "cwd": "${workspaceRoot}"
+        // },
+        // {
+        //     "type": "gdb",
+        //     "request": "attach",
+        //     "name": "serial-echo",
+        //     "gdbpath": "/usr/bin/arm-none-eabi-gdb",
+        //     "executable": "./target/thumbv7em-none-eabihf/debug/examples/serial-echo",
+        //     "target": ":3333",
+        //     "remote": true,
+        //     "autorun": [
+        //         "load"
+        //     ],
+        //     "cwd": "${workspaceRoot}"
+        // },
+        // {
+        //     "type": "gdb",
+        //     "request": "attach",
+        //     "name": "rtfm-blinky-systic",
+        //     "gdbpath": "/usr/bin/arm-none-eabi-gdb",
+        //     "executable": "./target/thumbv7em-none-eabihf/debug/examples/rtfm-blinky-systic",
+        //     "target": ":3333",
+        //     "remote": true,
+        //     "autorun": [
+        //         "load"
+        //     ],
+        //     "cwd": "${workspaceRoot}"
+        // },
+        // {
+        //     "type": "gdb",
+        //     "request": "attach",
+        //     "name": "rtfm-blinky-tim2",
+        //     "gdbpath": "/usr/bin/arm-none-eabi-gdb",
+        //     "executable": "./target/thumbv7em-none-eabihf/debug/examples/rtfm-blinky-tim2",
+        //     "target": ":3333",
+        //     "remote": true,
+        //     "autorun": [
+        //         "load"
+        //     ],
+        //     "cwd": "${workspaceRoot}"
+        // },
+        // {
+        //     "type": "gdb",
+        //     "request": "attach",
+        //     "name": "rtfm-serial-echo",
+        //     "gdbpath": "/usr/bin/arm-none-eabi-gdb",
+        //     "executable": "./target/thumbv7em-none-eabihf/debug/examples/rtfm-serial-echo",
+        //     "target": ":3333",
+        //     "remote": true,
+        //     "autorun": [
+        //         "load"
+        //     ],
+        //     "cwd": "${workspaceRoot}"
+        // },
+        // {
+        //     "type": "gdb",
+        //     "request": "attach",
+        //     "name": "serial-dma-tx",
+        //     "gdbpath": "/usr/bin/arm-none-eabi-gdb",
+        //     "executable": "./target/thumbv7em-none-eabihf/debug/examples/serial-dma-tx",
+        //     "target": ":3333",
+        //     "remote": true,
+        //     "autorun": [
+        //         "load"
+        //     ],
+        //     "cwd": "${workspaceRoot}"
+        // },
         {
             "type": "cortex-debug",
             "request": "launch",
@@ -117,6 +117,18 @@
                 "target/stm32f4x.cfg"
             ],
             "cwd": "${workspaceRoot}"
+        },
+        {
+            "type": "cortex-debug",
+            "request": "launch",
+            "servertype": "openocd",
+            "name": "c serial-dma-rx",
+            "executable": "./target/thumbv7em-none-eabihf/debug/examples/serial-dma-rx",
+            "configFiles": [
+                "interface/stlink.cfg",
+                "target/stm32f4x.cfg"
+            ],
+            "cwd": "${workspaceRoot}"
         }
     ]
 }
\ No newline at end of file
diff --git a/examples/serial-dma-rx.rs b/examples/serial-dma-rx.rs
new file mode 100644
index 0000000000000000000000000000000000000000..90cd775e24f3a481aad293d6a746f2f601add972
--- /dev/null
+++ b/examples/serial-dma-rx.rs
@@ -0,0 +1,40 @@
+//! Serial interface echo server
+//!
+//! In this example every received byte will be sent back to the sender. You can test this example
+//! with serial terminal emulator like `minicom`.
+#![deny(unsafe_code)]
+//#![deny(warnings)]
+#![no_std]
+
+#[macro_use(singleton)]
+extern crate cortex_m;
+extern crate stm32f4x_hal as f4;
+
+use cortex_m::asm;
+use f4::prelude::*;
+use f4::serial::Serial;
+use f4::stm32f4x;
+
+fn main() {
+    let p = stm32f4x::Peripherals::take().unwrap();
+
+    let mut flash = p.FLASH.constrain();
+    let mut rcc = p.RCC.constrain();
+    let mut gpioa = p.GPIOA.split(&mut rcc.ahb1);
+    let streams = p.DMA1.split(&mut rcc.ahb1);
+    let rx_stream = streams.0.into_channel4(); // S5<C4>
+
+    let clocks = rcc.cfgr.freeze(&mut flash.acr);
+
+    let tx = gpioa.pa2.into_af7(&mut gpioa.moder, &mut gpioa.afrl);
+    let rx = gpioa.pa3.into_af7(&mut gpioa.moder, &mut gpioa.afrl);
+
+    let serial = Serial::usart2(p.USART2, (tx, rx), 115_200.bps(), clocks, &mut rcc.apb1);
+    let (mut tx, mut rx) = serial.split();
+
+    let buf = singleton!(: [u8; 8] = [0; 8]).unwrap();
+
+    let (_buf, _c, _rx) = rx.read_exact(rx_stream, buf).wait();
+
+    asm::bkpt();
+}
diff --git a/examples/serial-dma-tx.rs b/examples/serial-dma-tx.rs
index 40cc1df827396910178cbbdaa38a56def9819afa..ec64cdd36afdf204e81da94cfcf794e6f377b405 100644
--- a/examples/serial-dma-tx.rs
+++ b/examples/serial-dma-tx.rs
@@ -1,13 +1,13 @@
-//! Serial interface echo server
-//!
-//! In this example every received byte will be sent back to the sender. You can test this example
-//! with serial terminal emulator like `minicom`.
+//! Serial interface with dma
+
 #![deny(unsafe_code)]
-//#![deny(warnings)]
+#![deny(warnings)]
 #![no_std]
 
+extern crate cortex_m;
 extern crate stm32f4x_hal as f4;
 
+use cortex_m::asm;
 use f4::prelude::*;
 use f4::serial::Serial;
 use f4::stm32f4x;
@@ -27,17 +27,17 @@ fn main() {
     let rx = gpioa.pa3.into_af7(&mut gpioa.moder, &mut gpioa.afrl);
 
     let serial = Serial::usart2(p.USART2, (tx, rx), 115_200.bps(), clocks, &mut rcc.apb1);
-    let (mut tx, mut rx) = serial.split();
+    let (tx, _) = serial.split();
 
     let (_, c, tx) = tx.write_all(tx_stream, b"The quick brown fox").wait();
 
-    // asm::bkpt();
+    asm::bkpt();
 
     let (_, c, tx) = tx.write_all(c, b" jumps").wait();
 
-    // asm::bkpt();
+    asm::bkpt();
 
     tx.write_all(c, b" over the lazy dog.").wait();
 
-    // asm::bkpt();
+    asm::bkpt();
 }
diff --git a/src/dma.rs b/src/dma.rs
index f176508fefcf06a42c0b55e284d39f6a96a66dba..ce9e0cd01c672ee140fbe5a0543f30197e2c04b5 100644
--- a/src/dma.rs
+++ b/src/dma.rs
@@ -126,7 +126,9 @@ pub unsafe trait UsartTxStream<USART> {
     fn start_transfer(&mut self, ndtr: u16, par: u32, m0: u32);
 }
 
-pub unsafe trait UsartRxStream<USART> {}
+pub unsafe trait UsartRxStream<USART> {
+    fn start_transfer(&mut self, ndtr: u16, par: u32, m0: u32);
+}
 
 pub mod dma1 {
     use core::sync::atomic::{self, Ordering};
@@ -155,7 +157,6 @@ pub mod dma1 {
     pub struct S7<CHANNEL> {
         _channel: PhantomData<CHANNEL>,
     }
-    //pub struct S7<CHANNEL> {}
 
     // Channels === Alternate function
     pub struct C0;
@@ -196,11 +197,15 @@ pub mod dma1 {
 
     unsafe impl super::UsartTxStream<USART2> for S6<C4> {
         fn start_transfer(&mut self, ndtr: u16, par: u32, m0: u32) {
-            start_transfer_s6_c4(ndtr, par, m0);
+            start_transfer_s6(4, ndtr, par, m0);
+        }
+    }
+
+    unsafe impl super::UsartRxStream<USART2> for S5<C4> {
+        fn start_transfer(&mut self, ndtr: u16, par: u32, m0: u32) {
+            start_transfer_s5(4, ndtr, par, m0);
         }
     }
-    unsafe impl super::UsartRxStream<USART2> for S5<C4> {}
-    unsafe impl super::UsartRxStream<USART2> for S7<C6> {}
 
     pub struct Streams(pub S5<C0>, pub S6<C0>, pub S7<C0>);
 
@@ -226,7 +231,8 @@ pub mod dma1 {
 
     use cortex_m::asm;
 
-    fn start_transfer_s6_c4(ndtr: u16, par: u32, m0: u32) {
+    // should be generalized by macro
+    fn start_transfer_s6(channel: u8, ndtr: u16, par: u32, m0: u32) {
         let dma = unsafe { &*DMA1::ptr() };
 
         // nr data transfers
@@ -248,7 +254,7 @@ pub mod dma1 {
         // en: Disabled
         dma.s6cr.modify(|_, w| unsafe {
             w.chsel()
-                .bits(4) // channel 4
+                .bits(channel) // channel 4
                 .pl()
                 .bits(0b01) // medium priority
                 .msize()
@@ -264,11 +270,44 @@ pub mod dma1 {
                 .dir()
                 .bits(1) // memory -> peripheral
                 .en()
-                .set_bit() // setup
+                .set_bit() // enable
         });
         //        dma.s6cr.modify(|_, w| w.en().set_bit());
     }
 
+    fn start_transfer_s5(channel: u8, ndtr: u16, par: u32, m0: u32) {
+        let dma = unsafe { &*DMA1::ptr() };
+
+        // nr data transfers
+        dma.s5ndtr.write(|w| unsafe { w.ndt().bits(ndtr) });
+        // peripheral address
+        dma.s5par.write(|w| unsafe { w.bits(par) });
+        // memory address 0
+        dma.s5m0ar.write(|w| unsafe { w.bits(m0) });
+        // RX DMA transfer
+
+        dma.s5cr.modify(|_, w| unsafe {
+            w.chsel()
+                .bits(channel) // channel 4
+                .pl()
+                .bits(0b01) // medium priority
+                .msize()
+                .bits(0b00) // memory 8 bits
+                .psize()
+                .bits(0b00) // peripheral 8 bits
+                .minc()  // memory increment
+                .set_bit()
+                .circ() 
+                .clear_bit() // not circular
+                .pinc()
+                .clear_bit() // no peripheral increment
+                .dir()
+                .bits(0) //  peripheral -> memory
+                .en() 
+                .set_bit() // setup
+        });
+    }
+
     impl DmaExt for DMA1 {
         type Streams = Streams;
 
@@ -317,4 +356,25 @@ pub mod dma1 {
             (self.buffer, self.stream, self.payload)
         }
     }
+
+    impl<BUFFER, PAYLOAD, MODE> Transfer<MODE, BUFFER, S5<C4>, PAYLOAD> {
+        pub fn wait(mut self) -> (BUFFER, S5<C4>, PAYLOAD) {
+            // XXX should we check for transfer errors here?
+            // The manual says "A DMA transfer error can be generated by reading
+            // from or writing to a reserved address space". I think it's impossible
+            // to get to that state with our type safe API and *safe* Rust.
+            let dma = unsafe { &*DMA1::ptr() };
+
+            while dma.hisr.read().tcif5().bit_is_clear() {}
+            dma.hifcr.write(|w| w.ctcif5().set_bit());
+            dma.s5cr.modify(|_, w| w.en().clear_bit());
+
+            // TODO can we weaken this compiler barrier?
+            // NOTE(compiler_fence) operations on `buffer` should not be reordered
+            // before the previous statement, which marks the DMA transfer as done
+            atomic::compiler_fence(Ordering::SeqCst);
+
+            (self.buffer, self.stream, self.payload)
+        }
+    }
 }
diff --git a/src/serial.rs b/src/serial.rs
index 5a16c3d69cd1f09b8860706e9d0af2bbcc19e293..a059023443762d2003231b01d1479babcc83df00 100644
--- a/src/serial.rs
+++ b/src/serial.rs
@@ -13,7 +13,7 @@ use cast::u16;
 use hal::serial;
 use nb;
 use stm32f4x::{USART1, USART2, USART6};
-use dma::{Static, Transfer, UsartTxStream, R};
+use dma::{Static, Transfer, UsartRxStream, UsartTxStream, R, W};
 
 // usart2
 use gpio::gpioa::{PA2, PA3};
@@ -101,7 +101,13 @@ pub struct Tx<USART> {
 
 macro_rules! hal {
     ($(
-        $USARTX:ident: ($usartX:ident, $APB:ident, $usartXen:ident, $usartXrst:ident, $pclkX:ident),
+        $USARTX:ident: (
+            $usartX:ident,
+            $APB:ident,
+            $usartXen:ident,
+            $usartXrst:ident,
+            $pclkX:ident
+        ),
     )+) => {
         $(
             impl<TX, RX> Serial<$USARTX, (TX, RX)> {
@@ -255,6 +261,7 @@ macro_rules! hal {
                     }
                 }
             }
+
             impl Tx<$USARTX> {
                 pub fn write_all<A, B, S>(
                     self, // should be mutable?
@@ -267,24 +274,53 @@ macro_rules! hal {
                     S: UsartTxStream<$USARTX>
                 {
                     {
-                        let buf :&[u8] = buffer.borrow();
+                        let buffer :&[u8] = buffer.borrow();
+
+                        // TODO can we weaken this compiler barrier?
+                        // NOTE(compiler_fence) operations on `buffer` should not be reordered after
+                        // the next statement, which starts the DMA transfer
+                        atomic::compiler_fence(Ordering::SeqCst);
 
                         // ntdr, par, m0
                         tx_stream.start_transfer(
-                            u16(buf.len()).unwrap(),
+                            u16(buffer.len()).unwrap(),
                             unsafe { &(*$USARTX::ptr()).dr as *const _ as usize as u32 },
-                            buf.as_ptr() as u32
+                            buffer.as_ptr() as u32
                         );
+                    }
+
+                    Transfer::r(buffer, tx_stream, self)
+                }
+            }
+
+            impl Rx<$USARTX> {
+                pub fn read_exact<A, S>(
+                    self,
+                    mut rx_stream: S,
+                    buffer: &'static mut A,
+                ) -> Transfer<W, &'static mut A, S, Self>
+                where
+                    A: Unsize<[u8]>,
+                    S: UsartRxStream<$USARTX>
+                {
+                    {
+                        let buffer: &[u8] = buffer;
 
                         // TODO can we weaken this compiler barrier?
                         // NOTE(compiler_fence) operations on `buffer` should not be reordered after
                         // the next statement, which starts the DMA transfer
-                         atomic::compiler_fence(Ordering::SeqCst);
+                        atomic::compiler_fence(Ordering::SeqCst);
+
+                        rx_stream.start_transfer(
+                            u16(buffer.len()).unwrap(),
+                            unsafe { &(*$USARTX::ptr()).dr as *const _ as usize as u32 },
+                            buffer.as_ptr() as u32
+                        );
                     }
-                    Transfer::r(buffer, tx_stream, self)
+
+                    Transfer::w(buffer, rx_stream, self)
                 }
             }
-
         )+
     }
 }