├── .cargo └── config ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples ├── bbqueue.rs ├── heapless-box.rs ├── load-elimination.rs ├── maybeuninit.rs ├── stack-buffer.rs ├── static-buffer.rs ├── unsound-asref.rs ├── unsound-non-pointer.rs ├── unsound-non-static.rs └── unsound-pin.rs ├── memory.x ├── openocd.gdb └── src ├── lib.rs └── traits.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7em-none-eabihf] 2 | runner = "arm-none-eabi-gdb -q -x openocd.gdb" 3 | rustflags = [ 4 | "-C", "link-arg=-Tlink.x", 5 | ] 6 | 7 | [build] 8 | target = "thumbv7em-none-eabihf" 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aligned" 5 | version = "0.3.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "eb1ce8b3382016136ab1d31a1b5ce807144f8b7eb2d5f16b2108f0f07edceb94" 8 | dependencies = [ 9 | "as-slice", 10 | ] 11 | 12 | [[package]] 13 | name = "as-slice" 14 | version = "0.1.3" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "37dfb65bc03b2bc85ee827004f14a6817e04160e3b1a28931986a666a9290e70" 17 | dependencies = [ 18 | "generic-array 0.12.3", 19 | "generic-array 0.13.2", 20 | "stable_deref_trait", 21 | ] 22 | 23 | [[package]] 24 | name = "bare-metal" 25 | version = "0.2.5" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" 28 | dependencies = [ 29 | "rustc_version", 30 | ] 31 | 32 | [[package]] 33 | name = "bbqueue" 34 | version = "0.4.4" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "0318183776c79a7f24c8dffb5b2664f3bcfc48b5b164416ef16019c02c0345de" 37 | dependencies = [ 38 | "generic-array 0.13.2", 39 | ] 40 | 41 | [[package]] 42 | name = "byteorder" 43 | version = "1.3.4" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 46 | 47 | [[package]] 48 | name = "cortex-m" 49 | version = "0.6.2" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "2954942fbbdd49996704e6f048ce57567c3e1a4e2dc59b41ae9fde06a01fc763" 52 | dependencies = [ 53 | "aligned", 54 | "bare-metal", 55 | "volatile-register", 56 | ] 57 | 58 | [[package]] 59 | name = "cortex-m-rt" 60 | version = "0.6.12" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "00d518da72bba39496024b62607c1d8e37bcece44b2536664f1132a73a499a28" 63 | dependencies = [ 64 | "cortex-m-rt-macros", 65 | "r0", 66 | ] 67 | 68 | [[package]] 69 | name = "cortex-m-rt-macros" 70 | version = "0.1.8" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647" 73 | dependencies = [ 74 | "proc-macro2", 75 | "quote", 76 | "syn", 77 | ] 78 | 79 | [[package]] 80 | name = "cortex-m-semihosting" 81 | version = "0.3.5" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "113ef0ecffee2b62b58f9380f4469099b30e9f9cbee2804771b4203ba1762cfa" 84 | dependencies = [ 85 | "cortex-m", 86 | ] 87 | 88 | [[package]] 89 | name = "dma-poc" 90 | version = "0.1.0" 91 | dependencies = [ 92 | "as-slice", 93 | "bbqueue", 94 | "cortex-m", 95 | "cortex-m-rt", 96 | "cortex-m-semihosting", 97 | "heapless", 98 | "panic-semihosting", 99 | "stable_deref_trait", 100 | "stm32f3", 101 | ] 102 | 103 | [[package]] 104 | name = "generic-array" 105 | version = "0.12.3" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 108 | dependencies = [ 109 | "typenum", 110 | ] 111 | 112 | [[package]] 113 | name = "generic-array" 114 | version = "0.13.2" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" 117 | dependencies = [ 118 | "typenum", 119 | ] 120 | 121 | [[package]] 122 | name = "hash32" 123 | version = "0.1.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" 126 | dependencies = [ 127 | "byteorder", 128 | ] 129 | 130 | [[package]] 131 | name = "heapless" 132 | version = "0.5.4" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "8ffa511365b12346c5fbe759d82f80d3aa70d9f1ba01955594f84a1a6bbab985" 135 | dependencies = [ 136 | "as-slice", 137 | "generic-array 0.13.2", 138 | "hash32", 139 | "stable_deref_trait", 140 | ] 141 | 142 | [[package]] 143 | name = "panic-semihosting" 144 | version = "0.5.3" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "c03864ac862876c16a308f5286f4aa217f1a69ac45df87ad3cd2847f818a642c" 147 | dependencies = [ 148 | "cortex-m", 149 | "cortex-m-semihosting", 150 | ] 151 | 152 | [[package]] 153 | name = "proc-macro2" 154 | version = "1.0.10" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" 157 | dependencies = [ 158 | "unicode-xid", 159 | ] 160 | 161 | [[package]] 162 | name = "quote" 163 | version = "1.0.3" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" 166 | dependencies = [ 167 | "proc-macro2", 168 | ] 169 | 170 | [[package]] 171 | name = "r0" 172 | version = "0.2.2" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" 175 | 176 | [[package]] 177 | name = "rustc_version" 178 | version = "0.2.3" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 181 | dependencies = [ 182 | "semver", 183 | ] 184 | 185 | [[package]] 186 | name = "semver" 187 | version = "0.9.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 190 | dependencies = [ 191 | "semver-parser", 192 | ] 193 | 194 | [[package]] 195 | name = "semver-parser" 196 | version = "0.7.0" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 199 | 200 | [[package]] 201 | name = "stable_deref_trait" 202 | version = "1.1.1" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" 205 | 206 | [[package]] 207 | name = "stm32f3" 208 | version = "0.10.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "40809cfa10dd7d7ee4c414addb1145ce1214e2123489e1eba3d13045a3de32d5" 211 | dependencies = [ 212 | "bare-metal", 213 | "cortex-m", 214 | "cortex-m-rt", 215 | "vcell", 216 | ] 217 | 218 | [[package]] 219 | name = "syn" 220 | version = "1.0.17" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" 223 | dependencies = [ 224 | "proc-macro2", 225 | "quote", 226 | "unicode-xid", 227 | ] 228 | 229 | [[package]] 230 | name = "typenum" 231 | version = "1.11.2" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" 234 | 235 | [[package]] 236 | name = "unicode-xid" 237 | version = "0.2.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 240 | 241 | [[package]] 242 | name = "vcell" 243 | version = "0.1.2" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c" 246 | 247 | [[package]] 248 | name = "volatile-register" 249 | version = "0.2.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" 252 | dependencies = [ 253 | "vcell", 254 | ] 255 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dma-poc" 3 | version = "0.1.0" 4 | authors = ["Jan Teske "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | as-slice = "0.1" 9 | cortex-m = "0.6" 10 | cortex-m-rt = "0.6" 11 | cortex-m-semihosting = "0.3" 12 | panic-semihosting = "0.5" 13 | 14 | [dependencies.stable_deref_trait] 15 | version = "1" 16 | default-features = false 17 | 18 | [dependencies.stm32f3] 19 | version = "0.10" 20 | features = ["rt", "stm32f303"] 21 | 22 | [dev-dependencies] 23 | bbqueue = "0.4" 24 | heapless = "0.5" 25 | 26 | [profile.release] 27 | codegen-units = 1 28 | debug = true 29 | lto = true 30 | opt-level = 3 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repo was created to document the progress of the ["DMA API Documentation" 2 | focus project]. 3 | 4 | This README lists the current state of our discussions regarding the topic 5 | of defining a safe DMA API. The repository also contains a prototype 6 | implementation of these ideas, as well as a number of examples that demonstrate 7 | their usability. 8 | 9 | Further work on this concept can be found in the [`embedded-dma`] crate. 10 | 11 | 12 | ["DMA API Documentation" focus project]: https://github.com/rust-embedded/wg/blob/master/projects/in-progress/0440-dma-api-documentation.md 13 | [`embedded-dma`]: https://github.com/rust-embedded/embedded-dma 14 | 15 | # DMA Buffer Type 16 | 17 | Assuming we have a `Transfer` struct abstracting a DMA transfer: 18 | 19 | ```rust 20 | struct Transfer { 21 | buffer: B, // the buffer DMA reads from/writes to 22 | payload: P, // owned DMA channel + peripheral 23 | } 24 | ``` 25 | 26 | **Question:** What buffer types `B` are safe to use? 27 | 28 | 29 | ## Specific Types 30 | 31 | This section lists a few specific types that are commonly used with DMA and 32 | should be supported by whatever we come up with. 33 | 34 | Note: Most of the below types reference `[u8]` buffers. Aside from `u8`, we'd 35 | like to support at least the larger word sizes `u16` and `u32`. 36 | 37 | ### Safe for DMA reads and writes 38 | 39 | - `&'static mut [u8]` 40 | - `alloc::boxed::Box<[u8]>` 41 | - `alloc::vec::Vec` 42 | - `bbqueue::GrantW<'static, N>` 43 | - `heapless::pool::Box<[u8], _>` 44 | 45 | ### Safe for DMA reads only 46 | 47 | Shared/read-only references: 48 | 49 | - `&'static [u8]` 50 | - `alloc::rc::Rc<[u8]>` 51 | - `alloc::arc::Arc<[u8]>` 52 | - `bbqueue::GrantR<'static, N>` 53 | 54 | Have invalid byte patterns: 55 | 56 | - `alloc::string::String` 57 | 58 | 59 | ## Requirements 60 | 61 | ### Requirement 1: `B` must be a pointer 62 | 63 | That is, `B` must point to another location in memory where the actual 64 | buffer is located. 65 | 66 | That is, the actual buffer must not be part of the `Transfer` struct. 67 | Otherwise is would be moved around the stack when the `Transfer` struct 68 | is passed between functions. Its address would change without the DMA 69 | knowing, and the DMA would read from/write to invalid memory locations. 70 | 71 | See [examples/unsound-non-pointer.rs]. 72 | 73 | #### Solutions 74 | 75 | - Requiring `B` to fulfill the `StableDeref` bound enforces this requirement. 76 | 77 | ```rust 78 | B: Deref + StableDeref // for DMA reads 79 | B: DerefMut + StableDeref // for DMA writes 80 | ``` 81 | 82 | - **[unsound]** Wrapping `B` in a `Pin` does *not* satisfy this requirement as 83 | `Pin` doesn't provide sufficient guarantees. 84 | 85 | See [examples/unsound-pin.rs]. 86 | 87 | 88 | ### Requirement 2: `B::Target` must be stable 89 | 90 | That is, dereferencing `B` must always yield the same memory location. 91 | Otherwise it is not guaranteed that the buffer stays valid for the whole 92 | duration of the DMA transfer. 93 | 94 | As an example, consider this unstable buffer type: 95 | 96 | ```rust 97 | struct UnstableBuffer { 98 | inner: Box<[u8; 16]>, 99 | } 100 | 101 | impl DerefMut for UnstableBuffer { 102 | fn deref_mut(&mut self) -> &mut [u8] { 103 | self.inner = Box::new([0; 16]); 104 | &mut self.inner 105 | } 106 | } 107 | ``` 108 | 109 | Every time we request a mutable reference to this buffer, it frees the 110 | buffer it currently holds and returns a new one. If DMA is already running 111 | on the old buffer, it would then access freed memory, which is unsafe. 112 | 113 | This requirement can be lifted if we can ensure that no call to `B::deref_mut` 114 | happens after a DMA transfer was started on `B`. This seems overly restrictive, 115 | since it makes it impossible to, e.g., to provide the user with a way to write 116 | to the part of the buffer the DMA doesn't currently access. 117 | 118 | #### Solutions 119 | 120 | - Requiring `B` to fulfill the `StableDeref` bound enforces this requirement. 121 | 122 | 123 | ### Requirement 3: `B::Target` must stay valid if `B` is forgotten 124 | 125 | Calling [`mem::forget`] is safe. This means it is possible, in safe Rust, to 126 | lose our handle to the `Transfer` struct without having its destructor run. 127 | This means we cannot rely on the `Transfer` destructor to guarantee memory 128 | safety (by stopping or waitings for the DMA transfer). This means, `B::Target` 129 | must remain a valid (i.e. not freed) buffer, as long as `B` is not dropped. 130 | 131 | This requirement does not hold for, e.g., references to stack buffers. 132 | See [examples/unsound-non-static.rs]. 133 | 134 | #### Solutions 135 | 136 | - Adding a `'static` bound, together with the bounds from Requirement 1, 137 | enforces this requirement. This way, we allow only: 138 | 139 | 1. Pointers to static memory (`&'static T`, `MyBuffer<'static, T>`): 140 | 141 | Buffers in static memory will never be freed, so the DMA can safely 142 | continue accessing them in the background. 143 | 144 | 2. Owned pointer types (`Box`, `Vec`, `Rc`, `String`, ...): 145 | 146 | Since those pointers own the memory they point to, that memory won't 147 | be freed until the pointer is dropped. Since `mem::forget` explicitly 148 | does not drop whatever is passed to it, the memory will not be freed 149 | either. 150 | 151 | 3. Shared pointer types (`Rc`, `Arc`): 152 | 153 | The memory those pointers reference won't be dropped as long as there 154 | is still a (shared) reference to it (i.e. reference count > 0). Again, 155 | since `mem::forget` prevents dropping, a shared pointer's reference 156 | will never be given back, so the referenced buffer won't be freed. 157 | 158 | - The `'static` bound can be dropped if the user of the `Transfer` promises 159 | to never call `mem::forget` on it or leak it in any other way. This cannot 160 | be expressed in the type system, unfortunately. But we can provide an `unsafe` constructor method for `Transfer` and document this requirement there. 161 | 162 | 163 | ### Requirement 4: `B::Target` must be valid for every possible byte pattern 164 | 165 | When doing a DMA write into `B::Target`, we have no way to ensure at the 166 | type system-level that the DMA writes only values that are valid according 167 | to `B::Target`'s type. Since [producing an invalid value leads to UB][ub], 168 | we can only allow target types for which all byte patterns are known to be 169 | valid values. 170 | 171 | This is only a necessary requirement for DMA writes. It might be sensible to 172 | enforce it for DMA reads too, though, for the sake of symmetry and sanity. 173 | 174 | #### Solutions 175 | 176 | - Allow only the common DMA buffer types `[u8]`, `[u16]`, `[u32]`. These 177 | are known to be always valid, regardless of the underlying byte pattern. 178 | It's not clear if there is any practical need for supporting other types, 179 | especially because everything can be cast to a `[u8]` if necessary. 180 | 181 | - Introduce a new marker trait for types that are valid for every byte pattern 182 | and bound `B::Target` on that. There is prior art in [`zerocopy::FromBytes`]. 183 | This would introduce additional maintenance effort, since we probably don't 184 | want to depend on `zerocopy` directly, so we'd have to implement that 185 | ourselves. 186 | 187 | 188 | [`mem::forget`]: https://doc.rust-lang.org/core/mem/fn.forget.html 189 | [`zerocopy::FromBytes`]: https://docs.rs/zerocopy/0.3.0/zerocopy/trait.FromBytes.html 190 | [examples/unsound-non-pointer.rs]: examples/unsound-non-pointer.rs 191 | [examples/unsound-non-static.rs]: examples/unsound-non-static.rs 192 | [examples/unsound-pin.rs]: examples/unsound-pin.rs 193 | [ub]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html 194 | 195 | 196 | ## Unsafe Traits 197 | 198 | Given the above requirements and solutions, we end up with with the following 199 | minimal trait bounds: 200 | 201 | ```rust 202 | unsafe trait Word {} 203 | unsafe impl Word for u8 {} 204 | unsafe impl Word for u16 {} 205 | unsafe impl Word for u32 {} 206 | 207 | // for DMA reads: 208 | B: Deref + StableDeref + 'static, 209 | 210 | // for DMA writes: 211 | B: DerefMut + StableDeref + 'static 212 | ``` 213 | 214 | These bounds allow only `[Word]` buffers, which makes them too restrictive 215 | for some practical use-cases. We also want to support: 216 | 217 | - `[Word; N]` 218 | - `CustomWrapper([Word; N])` 219 | - `MaybeUninit([Word; N])` 220 | - ... (what else?) 221 | 222 | To do so, we need to introduce another trait bound for `B::Target`, instead 223 | of fixing it to `[Word]`. 224 | 225 | Existing DMA implementations usually use `As{Mut}Slice` or 226 | `As{Ref,Mut}<[Word]>` for the purpose. While this is probably sound for the 227 | DMA read case (i.e. it cannot lead to memory unsafety), Using `AsMutSlice`/ 228 | `AsMut` for DMA writes allows breaking Requirement 2 ("stable buffer") again, 229 | since we cannot trust the implementation of `as_mut_slice`/`as_mut` to behave 230 | in a sane way, similarly to how we couldn't trust `deref_mut`. 231 | 232 | The only way around this (?) is to introduce another `unsafe` trait to enforce 233 | our safety requirements. We have the choice between another marker trait akin 234 | to `StableDeref` that ensures `AsMutSlice`/`AsMut` is safe to use, and a more 235 | general `DmaBuffer` trait that enforces all the DMA safety requirements. The 236 | latter choice seems preferable since it is more flexible and more readable 237 | as it encapsulates the notion of a DMA buffer into a single trait bound instead 238 | of four. 239 | 240 | Thus we suggest introducing the following two new DMA traits (naming still 241 | subject to bike-shedding): 242 | 243 | ```rust 244 | unsafe trait DmaReadBuffer { 245 | type Word; 246 | 247 | fn dma_read_buffer(&self) -> (*const Self::Word, usize); 248 | } 249 | 250 | pub unsafe trait DmaWriteBuffer { 251 | type Word; 252 | 253 | fn dma_write_buffer(&mut self) -> (*mut Self::Word, usize); 254 | } 255 | ``` 256 | 257 | Both traits provide access to the start and the length (in `Word`s) of the 258 | DMA buffer. They do so using a raw pointer and a separate length value instead 259 | of a single slice return value. The reason for this design is that it makes it 260 | possible to also support uninitialized `MaybeUninit` values, for which creating 261 | references would be undefined behavior. 262 | 263 | The DMA safety requirements must be fulfilled by any type that wants to safely 264 | implement the above traits. 265 | 266 | We can provide blanket implementations for common DMA types that we know 267 | to be safe. For example: 268 | 269 | ```rust 270 | unsafe impl DmaReadBuffer for B 271 | where 272 | B: Deref + StableDeref, 273 | B::Target: AsSlice, 274 | W: Word, 275 | { 276 | type Word = W; 277 | 278 | fn dma_read_buffer(&self) -> (*const Self::Word, usize) { 279 | let slice = self.as_slice(); 280 | (slice.as_ptr(), slice.len()) 281 | } 282 | } 283 | ``` 284 | 285 | Note that this blanked impl does not require `'static` on `B`, so 286 | Requirement 3 is not enforced here. This is to enable using stack-based 287 | DMA buffers with a `Transfer` implementation that supports this (via an 288 | `unsafe` constructor). Functions that want to take a DMA buffer without 289 | `unsafe` still need to specify the `'static` bound in addition to 290 | `DmaReadBuffer`. 291 | 292 | 293 | ## Open Questions 294 | 295 | - Are the above requirements on `B` and `B::Target` enough to ensure safe DMA? 296 | - Can we find counter examples that fulfill them and still lead to unsafe 297 | or undefined behavior? 298 | 299 | - Is the "Specific Types" section above complete? Are we missing any important 300 | types used as DMA buffers in real-world projects? 301 | 302 | - Are the proposed `unsafe` traits a good fit for real-world use cases? 303 | - Are there scenarios where they would be insufficient? 304 | - Are there better (more ergonomic) ways to enforce the DMA safety 305 | requirements? 306 | - Issue: [#1](https://github.com/ra-kete/dma-poc/issues/1) 307 | 308 | - Do we want to discuss alignment here? 309 | - Probably not, can be done separately. 310 | - We should just make sure our final recommendation doesn't prevent 311 | common approaches to specifying alignment requirements. 312 | 313 | 314 | ## How to Help 315 | 316 | Any feedback and the content of this document is welcome! You can talk directly 317 | to the project contributors (@korken89, @thalesfragoso, @ra-kete) or open 318 | an issue if you feel any of this needs more consideration. 319 | 320 | There are a couple of "Open Questions" listed above that we are not entirely 321 | sure about yet, so feedback from the embedded Rust community would be especially 322 | welcome here. In particular, please have a look at Issue 323 | [#1](https://github.com/ra-kete/dma-poc/issues/1) discussing DMA traits. 324 | -------------------------------------------------------------------------------- /examples/bbqueue.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates performing a DMA read into bbqueue Grant. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use bbqueue::{consts::*, BBBuffer, ConstBBBuffer}; 7 | use core::ops::{Deref, DerefMut}; 8 | use cortex_m_rt::entry; 9 | use cortex_m_semihosting::hprintln; 10 | use dma_poc::Transfer; 11 | use stable_deref_trait::StableDeref; 12 | 13 | // Since bbqueue's grant types don't (yet) implement `StableDeref`, 14 | // we need to wrap them here to add that ourselves. 15 | 16 | struct R(bbqueue::GrantR<'static, U32>); 17 | 18 | impl Deref for R { 19 | type Target = [u8]; 20 | fn deref(&self) -> &[u8] { 21 | &*self.0 22 | } 23 | } 24 | 25 | unsafe impl StableDeref for R {} 26 | 27 | struct W(bbqueue::GrantW<'static, U32>); 28 | 29 | impl Deref for W { 30 | type Target = [u8]; 31 | fn deref(&self) -> &[u8] { 32 | &*self.0 33 | } 34 | } 35 | 36 | impl DerefMut for W { 37 | fn deref_mut(&mut self) -> &mut [u8] { 38 | &mut *self.0 39 | } 40 | } 41 | 42 | unsafe impl StableDeref for W {} 43 | 44 | static BB: BBBuffer = BBBuffer(ConstBBBuffer::new()); 45 | 46 | #[entry] 47 | fn main() -> ! { 48 | let (mut prod, mut cons) = BB.try_split().unwrap(); 49 | 50 | // prepare the src 51 | let mut wgr = prod.grant_exact(16).unwrap(); 52 | wgr.copy_from_slice(b"THIS IS DMADATA!"); 53 | wgr.commit(16); 54 | 55 | let src = cons.read().unwrap(); 56 | let dst = prod.grant_exact(16).unwrap(); 57 | 58 | let transfer = Transfer::start(R(src), W(dst)); 59 | let (_dma, src, dst) = transfer.wait().expect("Transfer error"); 60 | 61 | assert_eq!(*src, *dst); 62 | 63 | hprintln!("Transfer finished successfully").unwrap(); 64 | loop { 65 | continue; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/heapless-box.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates performing a DMA read into a heapless `Box`. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use cortex_m_rt::entry; 7 | use cortex_m_semihosting::hprintln; 8 | use dma_poc::Transfer; 9 | use heapless::{ 10 | pool, 11 | pool::singleton::{Box, Pool}, 12 | }; 13 | 14 | const SRC: &[u8; 16] = b"THIS IS DMADATA!"; 15 | 16 | pool!(P: [u8; 16]); 17 | 18 | #[entry] 19 | fn main() -> ! { 20 | static mut MEMORY: [u8; 1024] = [0; 1024]; 21 | P::grow(MEMORY); 22 | 23 | let transfer = start(); 24 | let (_dma, src, dst) = transfer.wait().expect("Transfer error"); 25 | 26 | assert_eq!(src, *dst); 27 | 28 | hprintln!("Transfer finished successfully").unwrap(); 29 | loop { 30 | continue; 31 | } 32 | } 33 | 34 | #[inline(never)] 35 | fn start() -> Transfer<&'static [u8], Box

> { 36 | let dst = P::alloc().unwrap().freeze(); 37 | Transfer::start(SRC, dst) 38 | } 39 | -------------------------------------------------------------------------------- /examples/load-elimination.rs: -------------------------------------------------------------------------------- 1 | //! This example shows that the DMA abstraction is not made unsound by 2 | //! LLVM's load elimination. 3 | //! 4 | //! Note: This works only thanks to the compiler fences in `Transfer`'s 5 | //! methods. If they are removed, the `assert_eq` will fail. 6 | //! 7 | //! Here is a smaller self-contained example of how fences prevent load 8 | //! elimination (look at the generated LLVM IR): 9 | //! https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=3d71954aa7ced56b5507f467a32c4c0b 10 | 11 | #![no_std] 12 | #![no_main] 13 | 14 | use cortex_m_rt::entry; 15 | use cortex_m_semihosting::hprintln; 16 | use dma_poc::Transfer; 17 | 18 | const SRC: &[u8; 16] = b"THIS IS DMADATA!"; 19 | 20 | #[entry] 21 | fn main() -> ! { 22 | let mut dst = [0; 16]; 23 | 24 | let x = b'X'; 25 | dst[8] = x; 26 | 27 | let transfer = unsafe { Transfer::start_nonstatic(SRC, &mut dst) }; 28 | let (_dma, _src, dst) = transfer.wait().expect("Transfer error"); 29 | 30 | // If the compiler eliminated this load and used the known value 'X' 31 | // instead, this assert would fail. 32 | assert_eq!(dst[8], b'D'); 33 | 34 | hprintln!("Transfer finished successfully").unwrap(); 35 | loop { 36 | continue; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/maybeuninit.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates performing a DMA read into a static buffer. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use core::mem::MaybeUninit; 7 | use cortex_m_rt::entry; 8 | use cortex_m_semihosting::hprintln; 9 | use dma_poc::Transfer; 10 | 11 | const SRC: &[u8; 16] = b"THIS IS DMADATA!"; 12 | static mut DST: MaybeUninit<[u8; 16]> = MaybeUninit::uninit(); 13 | 14 | #[entry] 15 | fn main() -> ! { 16 | let transfer = start(); 17 | let (_dma, src, dst) = transfer.wait().expect("Transfer error"); 18 | 19 | let dst = unsafe { dst.assume_init() }; 20 | assert_eq!(src, dst); 21 | 22 | hprintln!("Transfer finished successfully").unwrap(); 23 | loop { 24 | continue; 25 | } 26 | } 27 | 28 | #[inline(never)] 29 | fn start() -> Transfer<&'static [u8], &'static mut MaybeUninit<[u8; 16]>> { 30 | let dst = unsafe { &mut DST }; 31 | Transfer::start(SRC, dst) 32 | } 33 | -------------------------------------------------------------------------------- /examples/stack-buffer.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates performing a DMA read into a stack buffer. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use cortex_m_rt::entry; 7 | use cortex_m_semihosting::hprintln; 8 | use dma_poc::Transfer; 9 | 10 | #[entry] 11 | fn main() -> ! { 12 | let src = b"THIS IS DMADATA!"; 13 | let mut dst = [0; 16]; 14 | 15 | // Note: This is only safe as long as we don't `mem::forget` the transfer. 16 | let transfer = unsafe { Transfer::start_nonstatic(src, &mut dst) }; 17 | let (_dma, src, dst) = transfer.wait().expect("Transfer error"); 18 | 19 | assert_eq!(src, dst); 20 | 21 | hprintln!("Transfer finished successfully").unwrap(); 22 | loop { 23 | continue; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/static-buffer.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates performing a DMA read into a static buffer. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use cortex_m_rt::entry; 7 | use cortex_m_semihosting::hprintln; 8 | use dma_poc::Transfer; 9 | 10 | const SRC: &[u8; 16] = b"THIS IS DMADATA!"; 11 | static mut DST: [u8; 16] = [0; 16]; 12 | 13 | #[entry] 14 | fn main() -> ! { 15 | let transfer = start(); 16 | let (_dma, src, dst) = transfer.wait().expect("Transfer error"); 17 | 18 | assert_eq!(src, dst); 19 | 20 | hprintln!("Transfer finished successfully").unwrap(); 21 | loop { 22 | continue; 23 | } 24 | } 25 | 26 | #[inline(never)] 27 | fn start() -> Transfer<&'static [u8], &'static mut [u8]> { 28 | let dst = unsafe { &mut DST }; 29 | Transfer::start(SRC, dst) 30 | } 31 | -------------------------------------------------------------------------------- /examples/unsound-asref.rs: -------------------------------------------------------------------------------- 1 | //! This is a PoC that shows that using `AsRef` (or `AsSlice`) as a bound 2 | //! for the `B::Target` of a DMA write is unsafe. 3 | //! 4 | //! The PoC uses `AsRef` rather than `AsSlice` because it is easier to 5 | //! implement that way (`heapless::String` doesn't implement `AsSlice`). 6 | //! However, the issue also applies to `AsSlice`. 7 | 8 | #![no_std] 9 | #![no_main] 10 | 11 | use core::{ 12 | ops::DerefMut, 13 | str, 14 | sync::atomic::{self, Ordering}, 15 | }; 16 | use cortex_m_rt::entry; 17 | use cortex_m_semihosting::hprintln; 18 | use dma_poc::Dma; 19 | use heapless::{consts::*, String}; 20 | use stable_deref_trait::StableDeref; 21 | 22 | /// Transfer implementation that bounds `B::Target` to `AsRef<[u8]>`. 23 | /// 24 | /// Note: Left out the `Drop` impl for simplicity, it wouldn't help here. 25 | pub struct Transfer { 26 | dma: Dma, 27 | buffer: B, 28 | } 29 | 30 | impl Transfer { 31 | pub fn start(src: &'static [u8], dst: B) -> Self 32 | where 33 | B: DerefMut + StableDeref + 'static, 34 | B::Target: AsRef<[u8]>, 35 | { 36 | let slice = dst.as_ref(); 37 | 38 | let mut dma = Dma::mem2mem(); 39 | dma.set_paddr(src.as_ptr() as u32); 40 | dma.set_maddr(slice.as_ptr() as u32); 41 | dma.set_ndt(slice.len() as u16); 42 | 43 | atomic::compiler_fence(Ordering::Release); 44 | dma.enable(); 45 | 46 | Transfer { dma, buffer: dst } 47 | } 48 | 49 | pub fn wait(mut self) -> Result<(Dma, B), ()> { 50 | while !self.dma.transfer_complete() { 51 | if self.dma.transfer_error() { 52 | return Err(()); 53 | } 54 | } 55 | atomic::compiler_fence(Ordering::Acquire); 56 | 57 | self.dma.disable(); 58 | 59 | Ok((self.dma, self.buffer)) 60 | } 61 | } 62 | 63 | const SRC: &[u8; 16] = b"invalid utf8: \xc3\x28"; 64 | static mut DST: String = String(heapless::i::String::new()); 65 | 66 | #[entry] 67 | fn main() -> ! { 68 | let transfer = start(); 69 | let (_dma, dst) = transfer.wait().expect("Transfer error"); 70 | 71 | // this panics 72 | str::from_utf8(dst.as_ref()).expect("invalid data in String"); 73 | 74 | hprintln!("Transfer finished successfully").unwrap(); 75 | loop { 76 | continue; 77 | } 78 | } 79 | 80 | #[inline(never)] 81 | fn start() -> Transfer<&'static mut String> { 82 | let dst = unsafe { &mut DST }; 83 | for _ in 0..16 { 84 | dst.push('\x00').unwrap(); 85 | } 86 | 87 | Transfer::start(SRC, dst) 88 | } 89 | -------------------------------------------------------------------------------- /examples/unsound-non-pointer.rs: -------------------------------------------------------------------------------- 1 | //! This is a PoC that shows that using a non-pointer buffer is unsafe. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use as_slice::AsSlice; 7 | use core::sync::atomic::{self, Ordering}; 8 | use cortex_m_rt::entry; 9 | use cortex_m_semihosting::hprintln; 10 | use dma_poc::Dma; 11 | 12 | /// Transfer implementation that doesn't restrict `B` to be a pointer type. 13 | /// 14 | /// Note: Left out the `Drop` impl for simplicity, it wouldn't help here. 15 | pub struct Transfer { 16 | dma: Dma, 17 | buffer: B, 18 | } 19 | 20 | impl Transfer { 21 | pub fn start(src: &'static [u8], dst: B) -> Self 22 | where 23 | B: AsSlice, 24 | { 25 | let slice = dst.as_slice(); 26 | 27 | let mut dma = Dma::mem2mem(); 28 | dma.set_paddr(src.as_ptr() as u32); 29 | dma.set_maddr(slice.as_ptr() as u32); 30 | dma.set_ndt(slice.len() as u16); 31 | 32 | atomic::compiler_fence(Ordering::Release); 33 | dma.enable(); 34 | 35 | Transfer { dma, buffer: dst } 36 | } 37 | 38 | pub fn wait(mut self) -> Result<(Dma, B), ()> { 39 | while !self.dma.transfer_complete() { 40 | if self.dma.transfer_error() { 41 | return Err(()); 42 | } 43 | } 44 | atomic::compiler_fence(Ordering::Acquire); 45 | 46 | self.dma.disable(); 47 | 48 | Ok((self.dma, self.buffer)) 49 | } 50 | } 51 | 52 | const SRC: &[u8; 16] = b"THIS IS DMADATA!"; 53 | 54 | #[entry] 55 | fn main() -> ! { 56 | let transfer = start(); 57 | let (_dma, dst) = transfer.wait().expect("Transfer error"); 58 | 59 | // this panics 60 | assert_eq!(dst, *SRC); 61 | 62 | hprintln!("Transfer finished successfully").unwrap(); 63 | loop { 64 | continue; 65 | } 66 | } 67 | 68 | #[inline(never)] 69 | fn start() -> Transfer<[u8; 16]> { 70 | let dst = [0; 16]; 71 | Transfer::start(SRC, dst) 72 | } 73 | -------------------------------------------------------------------------------- /examples/unsound-non-static.rs: -------------------------------------------------------------------------------- 1 | //! This is a PoC that shows that using a non-'static buffer is unsafe in the 2 | //! face of `mem::forget`. 3 | 4 | #![no_std] 5 | #![no_main] 6 | 7 | use core::mem; 8 | use cortex_m_rt::entry; 9 | use cortex_m_semihosting::hprintln; 10 | use dma_poc::Transfer; 11 | 12 | const SRC: &[u8; 16] = b"THIS IS DMADATA!"; 13 | 14 | #[entry] 15 | fn main() -> ! { 16 | corrupt_stack(); 17 | use_stack(); 18 | 19 | loop { 20 | continue; 21 | } 22 | } 23 | 24 | #[inline(never)] 25 | fn corrupt_stack() { 26 | let mut dst = [0_u8; 16]; 27 | 28 | // for some reason necessary to trigger the panic 29 | hprintln!("{}", dst[0]).unwrap(); 30 | 31 | let transfer = unsafe { Transfer::start_nonstatic(SRC, &mut dst) }; 32 | mem::forget(transfer); 33 | 34 | // `dst` gets freed here, but the DMA transfer continues writing to it. 35 | } 36 | 37 | #[inline(never)] 38 | fn use_stack() { 39 | let buf = [0_u8; 16]; 40 | 41 | // this panics 42 | assert_eq!(buf, [0; 16]); 43 | } 44 | -------------------------------------------------------------------------------- /examples/unsound-pin.rs: -------------------------------------------------------------------------------- 1 | //! This is a PoC that shows that using `Pin` is not sufficient to ensure 2 | //! a DMA buffer is fixed in memory. 3 | 4 | #![no_std] 5 | #![no_main] 6 | 7 | use as_slice::AsSlice; 8 | use core::{ 9 | ops::{Deref, DerefMut}, 10 | pin::Pin, 11 | sync::atomic::{self, Ordering}, 12 | }; 13 | use cortex_m_rt::entry; 14 | use cortex_m_semihosting::hprintln; 15 | use dma_poc::Dma; 16 | 17 | /// Transfer implementation that attempts to use `Pin` instead of 18 | /// `StableDeref` to ensure the DMA buffer is stable in memory. 19 | /// 20 | /// Note: Left out the `Drop` impl for simplicity, it wouldn't help here. 21 | pub struct Transfer { 22 | dma: Dma, 23 | buffer: Pin, 24 | } 25 | 26 | impl Transfer { 27 | pub fn start(src: &'static [u8], dst: Pin) -> Self 28 | where 29 | B: DerefMut + 'static, 30 | B::Target: AsSlice + Unpin, 31 | { 32 | let slice = dst.as_slice(); 33 | 34 | let mut dma = Dma::mem2mem(); 35 | dma.set_paddr(src.as_ptr() as u32); 36 | dma.set_maddr(slice.as_ptr() as u32); 37 | dma.set_ndt(slice.len() as u16); 38 | 39 | atomic::compiler_fence(Ordering::Release); 40 | dma.enable(); 41 | 42 | Transfer { dma, buffer: dst } 43 | } 44 | 45 | pub fn wait(mut self) -> Result<(Dma, Pin), ()> { 46 | while !self.dma.transfer_complete() { 47 | if self.dma.transfer_error() { 48 | return Err(()); 49 | } 50 | } 51 | atomic::compiler_fence(Ordering::Acquire); 52 | 53 | self.dma.disable(); 54 | 55 | Ok((self.dma, self.buffer)) 56 | } 57 | } 58 | 59 | /// Using this buffer with DMA is unsafe since its allocated on the stack 60 | /// and will therefore move around. `Pin` doesn't prevent us from using 61 | /// this buffer type. 62 | #[derive(Debug)] 63 | struct Buffer([u8; 16]); 64 | 65 | impl Deref for Buffer { 66 | type Target = [u8]; 67 | fn deref(&self) -> &[u8] { 68 | &self.0 69 | } 70 | } 71 | 72 | impl DerefMut for Buffer { 73 | fn deref_mut(&mut self) -> &mut [u8] { 74 | &mut self.0 75 | } 76 | } 77 | 78 | const SRC: &[u8; 16] = b"THIS IS DMADATA!"; 79 | 80 | #[entry] 81 | fn main() -> ! { 82 | let transfer = start(); 83 | let (_dma, dst) = transfer.wait().expect("Transfer error"); 84 | 85 | // this panics 86 | assert_eq!(*dst, *SRC); 87 | 88 | hprintln!("Transfer finished successfully").unwrap(); 89 | loop { 90 | continue; 91 | } 92 | } 93 | 94 | #[inline(never)] 95 | fn start() -> Transfer { 96 | let dst = Buffer([0; 16]); 97 | Transfer::start(SRC, Pin::new(dst)) 98 | } 99 | -------------------------------------------------------------------------------- /memory.x: -------------------------------------------------------------------------------- 1 | /* Linker script for the STM32F303VCT6 */ 2 | MEMORY 3 | { 4 | CCRAM : ORIGIN = 0x10000000, LENGTH = 8K 5 | FLASH : ORIGIN = 0x08000000, LENGTH = 256K 6 | RAM : ORIGIN = 0x20000000, LENGTH = 40K 7 | } 8 | 9 | _stack_start = ORIGIN(RAM) + LENGTH(RAM); 10 | -------------------------------------------------------------------------------- /openocd.gdb: -------------------------------------------------------------------------------- 1 | target remote :3333 2 | set print asm-demangle on 3 | set print pretty on 4 | monitor arm semihosting enable 5 | load 6 | break DefaultHandler 7 | break HardFault 8 | continue 9 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | mod traits; 4 | 5 | use panic_semihosting as _; 6 | 7 | use core::sync::atomic::{self, Ordering}; 8 | use stm32f3::stm32f303 as pac; 9 | 10 | pub use traits::{DmaReadBuffer, DmaWriteBuffer}; 11 | 12 | /// Thin wrapper around the DMA1 peripheral, using channel 1. 13 | pub struct Dma(pac::DMA1); 14 | 15 | impl Dma { 16 | pub fn mem2mem() -> Self { 17 | let device = pac::Peripherals::take().unwrap(); 18 | 19 | // enable DMA1 peripheral 20 | device.RCC.ahbenr.modify(|_, w| w.dma1en().enabled()); 21 | 22 | // setup channel 1 for mem2mem transfer 23 | let dma1 = device.DMA1; 24 | dma1.ch1.cr.write(|w| { 25 | w.dir().from_peripheral(); 26 | w.pinc().enabled(); 27 | w.minc().enabled(); 28 | w.psize().bits8(); 29 | w.msize().bits8(); 30 | w.mem2mem().enabled() 31 | }); 32 | 33 | Self(dma1) 34 | } 35 | 36 | pub fn set_paddr(&mut self, addr: u32) { 37 | self.0.ch1.par.write(|w| w.pa().bits(addr)); 38 | } 39 | 40 | pub fn set_maddr(&mut self, addr: u32) { 41 | self.0.ch1.mar.write(|w| w.ma().bits(addr)); 42 | } 43 | 44 | pub fn set_ndt(&mut self, len: u16) { 45 | self.0.ch1.ndtr.write(|w| w.ndt().bits(len)); 46 | } 47 | 48 | pub fn enable(&mut self) { 49 | // clear interrupt flags 50 | self.0.ifcr.write(|w| w.cgif1().set_bit()); 51 | 52 | self.0.ch1.cr.modify(|_, w| w.en().enabled()); 53 | } 54 | 55 | pub fn disable(&mut self) { 56 | self.0.ch1.cr.modify(|_, w| w.en().disabled()); 57 | } 58 | 59 | pub fn transfer_complete(&self) -> bool { 60 | self.0.isr.read().tcif1().bit_is_set() 61 | } 62 | 63 | pub fn transfer_error(&self) -> bool { 64 | self.0.isr.read().teif1().bit_is_set() 65 | } 66 | } 67 | 68 | /// Safe abstraction of a DMA read transfer. 69 | pub struct Transfer { 70 | // always `Some` outside of `Drop::drop` 71 | inner: Option>, 72 | } 73 | 74 | impl Transfer { 75 | pub fn start(src: R, dst: W) -> Self 76 | where 77 | R: DmaReadBuffer + 'static, 78 | W: DmaWriteBuffer + 'static, 79 | { 80 | unsafe { Self::start_nonstatic(src, dst) } 81 | } 82 | 83 | /// # Safety 84 | /// 85 | /// If `dst` is not `'static`, callers must ensure that `mem::forget` 86 | /// is never called on the returned `Transfer`. 87 | pub unsafe fn start_nonstatic(src: R, mut dst: W) -> Self 88 | where 89 | R: DmaReadBuffer, 90 | W: DmaWriteBuffer, 91 | { 92 | let mut dma = Dma::mem2mem(); 93 | { 94 | let (src_ptr, src_len) = src.dma_read_buffer(); 95 | let (dst_ptr, dst_len) = dst.dma_write_buffer(); 96 | assert!(dst_len >= src_len); 97 | 98 | dma.set_paddr(src_ptr as *const u8 as u32); 99 | dma.set_maddr(dst_ptr as *mut u8 as u32); 100 | dma.set_ndt(src_len as u16); 101 | } 102 | 103 | // Prevent preceding reads/writes on the buffer from being moved past 104 | // the DMA enable modify (i.e. after the transfer has started). 105 | atomic::compiler_fence(Ordering::Release); 106 | 107 | dma.enable(); 108 | 109 | Transfer { 110 | inner: Some(TransferInner { dma, src, dst }), 111 | } 112 | } 113 | 114 | pub fn wait(mut self) -> Result<(Dma, R, W), ()> { 115 | let mut inner = self.inner.take().unwrap(); 116 | 117 | while !inner.dma.transfer_complete() { 118 | if inner.dma.transfer_error() { 119 | return Err(()); 120 | } 121 | } 122 | 123 | inner.stop(); 124 | 125 | Ok((inner.dma, inner.src, inner.dst)) 126 | } 127 | } 128 | 129 | struct TransferInner { 130 | dma: Dma, 131 | src: R, 132 | dst: W, 133 | } 134 | 135 | impl TransferInner { 136 | fn stop(&mut self) { 137 | self.dma.disable(); 138 | 139 | // Prevent subsequent reads/writes on the buffer from being moved 140 | // ahead of the DMA disable modify (i.e. before the transfer is 141 | // stopped). 142 | atomic::compiler_fence(Ordering::Acquire); 143 | } 144 | } 145 | 146 | impl Drop for Transfer { 147 | fn drop(&mut self) { 148 | if let Some(mut inner) = self.inner.take() { 149 | inner.stop(); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | //! `unsafe` traits for buffers usable with DMA. 2 | //! 3 | //! The traits defined here are concerned with ensuring requirements 4 | //! 1, 2, and 4 from the README. They ignore requirement 3 (`'static` bound) 5 | //! to make them useful for DMA on stack buffers too. Requirement 3 must be 6 | //! enforced by the `Transfer` implementation instead. 7 | 8 | use core::ops::{Deref, DerefMut}; 9 | use stable_deref_trait::StableDeref; 10 | 11 | /// Trait for buffers that can be given to DMA for reading. 12 | /// 13 | /// # Safety 14 | /// 15 | /// The implementing type must be safe to use for DMA reads. This means: 16 | /// 17 | /// - It must be a pointer that references the actual buffer. 18 | /// - The requirements documented on `dma_read_buffer` must be fulfilled. 19 | pub unsafe trait DmaReadBuffer { 20 | type Word; 21 | 22 | /// Provide a buffer usable for DMA reads. 23 | /// 24 | /// The return value is: 25 | /// 26 | /// - pointer to the start of the buffer 27 | /// - buffer size in words 28 | /// 29 | /// # Safety 30 | /// 31 | /// - This function must always return the same values, if called multiple 32 | /// times. 33 | /// - The memory specified by the returned pointer and size must not be 34 | /// freed as long as `self` is not dropped. 35 | fn dma_read_buffer(&self) -> (*const Self::Word, usize); 36 | } 37 | 38 | /// Trait for buffers that can be given to DMA for writing. 39 | /// 40 | /// # Safety 41 | /// 42 | /// The implementing type must be safe to use for DMA writes. This means: 43 | /// 44 | /// - It must be a pointer that references the actual buffer. 45 | /// - `Target` must be a type that is valid for any possible byte pattern. 46 | /// - The requirements documented on `dma_write_buffer` must be fulfilled. 47 | pub unsafe trait DmaWriteBuffer { 48 | type Word; 49 | 50 | /// Provide a buffer usable for DMA writes. 51 | /// 52 | /// The return value is: 53 | /// 54 | /// - pointer to the start of the buffer 55 | /// - buffer size in words 56 | /// 57 | /// # Safety 58 | /// 59 | /// - This function must always return the same values, if called multiple 60 | /// times. 61 | /// - The memory specified by the returned pointer and size must not be 62 | /// freed as long as `self` is not dropped. 63 | fn dma_write_buffer(&mut self) -> (*mut Self::Word, usize); 64 | } 65 | 66 | // Blanked implementations for common DMA buffer types. 67 | 68 | unsafe impl DmaReadBuffer for B 69 | where 70 | B: Deref + StableDeref, 71 | T: private::DmaReadTarget + ?Sized, 72 | { 73 | type Word = T::Word; 74 | 75 | fn dma_read_buffer(&self) -> (*const Self::Word, usize) { 76 | self.as_dma_read_buffer() 77 | } 78 | } 79 | 80 | unsafe impl DmaWriteBuffer for B 81 | where 82 | B: DerefMut + StableDeref, 83 | T: private::DmaWriteTarget + ?Sized, 84 | { 85 | type Word = T::Word; 86 | 87 | fn dma_write_buffer(&mut self) -> (*mut Self::Word, usize) { 88 | self.as_dma_write_buffer() 89 | } 90 | } 91 | 92 | /// This module contains traits and impls used by the blanket impls of 93 | /// the DMA buffer traits. 94 | /// 95 | /// It is kept private to prevent others from implementing these traits. 96 | /// Third-party code should impl the public DMA buffer traits directly. 97 | mod private { 98 | use core::mem::{self, MaybeUninit}; 99 | 100 | /// Trait for DMA word types used by the blanket implementations. 101 | /// 102 | /// # Safety 103 | /// 104 | /// Types that implement this trait must be valid for every possible byte 105 | /// pattern. This is to ensure that, whatever DMA writes into the buffer, 106 | /// we won't get UB due to invalid values. 107 | pub unsafe trait DmaWord {} 108 | 109 | unsafe impl DmaWord for u8 {} 110 | unsafe impl DmaWord for u16 {} 111 | unsafe impl DmaWord for u32 {} 112 | 113 | /// Trait for `Deref` targets used by the blanket `DmaReadBuffer` impl. 114 | /// 115 | /// This trait exists solely to work around 116 | /// https://github.com/rust-lang/rust/issues/20400. 117 | /// 118 | /// # Safety 119 | /// 120 | /// - `as_dma_read_buffer` must adhere to the safety requirements 121 | /// documented for `DmaReadBuffer::dma_read_buffer`. 122 | pub unsafe trait DmaReadTarget { 123 | type Word: DmaWord; 124 | 125 | fn as_dma_read_buffer(&self) -> (*const Self::Word, usize) { 126 | let ptr = self as *const _ as *const Self::Word; 127 | let len = mem::size_of_val(self) / mem::size_of::(); 128 | (ptr, len) 129 | } 130 | } 131 | 132 | /// Trait for `DerefMut` targets used by the blanket `DmaWriteBuffer` impl. 133 | /// 134 | /// This trait exists solely to work around 135 | /// https://github.com/rust-lang/rust/issues/20400. 136 | /// 137 | /// # Safety 138 | /// 139 | /// - `as_dma_write_buffer` must adhere to the safety requirements 140 | /// documented for `DmaWriteBuffer::dma_write_buffer`. 141 | pub unsafe trait DmaWriteTarget { 142 | type Word: DmaWord; 143 | 144 | fn as_dma_write_buffer(&mut self) -> (*mut Self::Word, usize) { 145 | let ptr = self as *mut _ as *mut Self::Word; 146 | let len = mem::size_of_val(self) / mem::size_of::(); 147 | (ptr, len) 148 | } 149 | } 150 | 151 | // Support DMA reads and writes on the Word types themselves. 152 | 153 | unsafe impl DmaReadTarget for W { 154 | type Word = W; 155 | } 156 | 157 | unsafe impl DmaWriteTarget for W { 158 | type Word = W; 159 | } 160 | 161 | // Support DMA reads and writes on slices. 162 | 163 | unsafe impl DmaReadTarget for [T] { 164 | type Word = T::Word; 165 | } 166 | 167 | unsafe impl DmaWriteTarget for [T] { 168 | type Word = T::Word; 169 | } 170 | 171 | // Support DMA reads and writes on arrays. 172 | 173 | macro_rules! dma_target_array_impls { 174 | ( $( $i:expr, )+ ) => { 175 | $( 176 | unsafe impl DmaReadTarget for [T; $i] { 177 | type Word = T::Word; 178 | } 179 | 180 | unsafe impl DmaWriteTarget for [T; $i] { 181 | type Word = T::Word; 182 | } 183 | )+ 184 | }; 185 | } 186 | 187 | #[rustfmt::skip] 188 | dma_target_array_impls!( 189 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 190 | 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 191 | 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 192 | 30, 31, 32, 193 | ); 194 | 195 | // Support DMA writes on MaybeUninit data. 196 | 197 | unsafe impl DmaWriteTarget for MaybeUninit { 198 | type Word = T::Word; 199 | } 200 | } 201 | --------------------------------------------------------------------------------