github.com/f-secure-foundry/tamago@v0.0.0-20220307101044-d73fcdd7f11b/dma/dma.go (about) 1 // First-fit memory allocator for DMA buffers 2 // https://github.com/f-secure-foundry/tamago 3 // 4 // Copyright (c) F-Secure Corporation 5 // https://foundry.f-secure.com 6 // 7 // Use of this source code is governed by the license 8 // that can be found in the LICENSE file. 9 10 // Package dma provides primitives for direct memory allocation and alignment, 11 // it is primarily used in bare metal device driver operation to avoid passing 12 // Go pointers for DMA purposes. 13 // 14 // This package is only meant to be used with `GOOS=tamago GOARCH=arm` as 15 // supported by the TamaGo framework for bare metal Go on ARM SoCs, see 16 // https://github.com/f-secure-foundry/tamago. 17 package dma 18 19 import ( 20 "container/list" 21 "reflect" 22 "sync" 23 "unsafe" 24 ) 25 26 type block struct { 27 // pointer address 28 addr uint32 29 // buffer size 30 size int 31 // distinguish regular (`Alloc`/`Free`) and reserved 32 // (`Reserve`/`Release`) blocks. 33 res bool 34 } 35 36 // Region represents a memory region allocated for DMA purposes. 37 type Region struct { 38 sync.Mutex 39 40 Start uint32 41 Size int 42 43 freeBlocks *list.List 44 usedBlocks map[uint32]*block 45 } 46 47 var dma *Region 48 49 // Init initializes a memory region for DMA buffer allocation, the application 50 // must guarantee that the passed memory range is never used by the Go 51 // runtime (defining runtime.ramStart and runtime.ramSize accordingly). 52 func (dma *Region) Init() { 53 // initialize a single block to fit all available memory 54 b := &block{ 55 addr: dma.Start, 56 size: dma.Size, 57 } 58 59 dma.Lock() 60 defer dma.Unlock() 61 62 dma.freeBlocks = list.New() 63 dma.freeBlocks.PushFront(b) 64 65 dma.usedBlocks = make(map[uint32]*block) 66 } 67 68 // Reserve allocates a slice of bytes for DMA purposes, by placing its data 69 // within the DMA region, with optional alignment. It returns the slice along 70 // with its data allocation address. The buffer can be freed up with Release(). 71 // 72 // Reserving buffers with Reserve() allows applications to pre-allocate DMA 73 // regions, avoiding unnecessary memory copy operations when performance is a 74 // concern. Reserved buffers cause Alloc() and Read() to return without any 75 // allocation or memory copy. 76 // 77 // Great care must be taken on reserved buffer as: 78 // * buf contents are uninitialized (unlike when using Alloc()) 79 // * buf slices remain in reserved space but only the original buf 80 // can be subject of Release() 81 // 82 // The optional alignment must be a power of 2 and word alignment is always 83 // enforced (0 == 4). 84 func (dma *Region) Reserve(size int, align int) (addr uint32, buf []byte) { 85 if size == 0 { 86 return 87 } 88 89 dma.Lock() 90 defer dma.Unlock() 91 92 b := dma.alloc(size, align) 93 b.res = true 94 95 dma.usedBlocks[b.addr] = b 96 97 hdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) 98 hdr.Data = uintptr(unsafe.Pointer(uintptr(b.addr))) 99 hdr.Len = size 100 hdr.Cap = hdr.Len 101 102 return b.addr, buf 103 } 104 105 // Reserved returns whether a slice of bytes data is allocated within the DMA 106 // buffer region, it is used to determine whether the passed buffer has been 107 // previously allocated by this package with Reserve(). 108 func (dma *Region) Reserved(buf []byte) (res bool, addr uint32) { 109 addr = uint32(uintptr(unsafe.Pointer(&buf[0]))) 110 res = addr >= dma.Start && addr+uint32(len(buf)) <= dma.Start+uint32(dma.Size) 111 112 return 113 } 114 115 // Alloc reserves a memory region for DMA purposes, copying over a buffer and 116 // returning its allocation address, with optional alignment. The region can be 117 // freed up with Free(). 118 // 119 // If the argument is a buffer previously created with Reserve(), then its 120 // address is return without any re-allocation. 121 // 122 // The optional alignment must be a power of 2 and word alignment is always 123 // enforced (0 == 4). 124 func (dma *Region) Alloc(buf []byte, align int) (addr uint32) { 125 size := len(buf) 126 127 if size == 0 { 128 return 0 129 } 130 131 if res, addr := Reserved(buf); res { 132 return addr 133 } 134 135 dma.Lock() 136 defer dma.Unlock() 137 138 b := dma.alloc(len(buf), align) 139 b.write(0, buf) 140 141 dma.usedBlocks[b.addr] = b 142 143 return b.addr 144 } 145 146 // Read reads exactly len(buf) bytes from a memory region address into a 147 // buffer, the region must have been previously allocated with Alloc(). 148 // 149 // The offset and buffer size are used to retrieve a slice of the memory 150 // region, a panic occurs if these parameters are not compatible with the 151 // initial allocation for the address. 152 // 153 // If the argument is a buffer previously created with Reserve(), then the 154 // function returns without modifying it, as it is assumed for the buffer to be 155 // already updated. 156 func (dma *Region) Read(addr uint32, off int, buf []byte) { 157 size := len(buf) 158 159 if addr == 0 || size == 0 { 160 return 161 } 162 163 if res, _ := Reserved(buf); res { 164 return 165 } 166 167 dma.Lock() 168 defer dma.Unlock() 169 170 b, ok := dma.usedBlocks[addr] 171 172 if !ok { 173 panic("read of unallocated pointer") 174 } 175 176 if off+size > b.size { 177 panic("invalid read parameters") 178 } 179 180 b.read(off, buf) 181 } 182 183 // Write writes buffer contents to a memory region address, the region must 184 // have been previously allocated with Alloc(). 185 // 186 // An offset can be passed to write a slice of the memory region, a panic 187 // occurs if the offset is not compatible with the initial allocation for the 188 // address. 189 func (dma *Region) Write(addr uint32, off int, buf []byte) { 190 size := len(buf) 191 192 if addr == 0 || size == 0 { 193 return 194 } 195 196 dma.Lock() 197 defer dma.Unlock() 198 199 b, ok := dma.usedBlocks[addr] 200 201 if !ok { 202 return 203 } 204 205 if off+size > b.size { 206 panic("invalid write parameters") 207 } 208 209 b.write(off, buf) 210 } 211 212 // Free frees the memory region stored at the passed address, the region must 213 // have been previously allocated with Alloc(). 214 func (dma *Region) Free(addr uint32) { 215 dma.freeBlock(addr, false) 216 } 217 218 // Release frees the memory region stored at the passed address, the region 219 // must have been previously allocated with Reserve(). 220 func (dma *Region) Release(addr uint32) { 221 dma.freeBlock(addr, true) 222 } 223 224 func (dma *Region) freeBlock(addr uint32, res bool) { 225 if addr == 0 { 226 return 227 } 228 229 dma.Lock() 230 defer dma.Unlock() 231 232 b, ok := dma.usedBlocks[addr] 233 234 if !ok { 235 return 236 } 237 238 if b.res != res { 239 return 240 } 241 242 dma.free(b) 243 delete(dma.usedBlocks, addr) 244 } 245 246 // Init initializes the global memory region for DMA buffer allocation, the 247 // application must guarantee that the passed memory range is never used by the 248 // Go runtime (defining runtime.ramStart and runtime.ramSize accordingly). 249 // 250 // The global region is used throughout the tamago package for all DMA 251 // allocations. 252 // 253 // Separate DMA regions can be allocated in other areas (e.g. external RAM) by 254 // the application using Region.Init(). 255 func Init(start uint32, size int) { 256 dma = &Region{ 257 Start: start, 258 Size: size, 259 } 260 261 dma.Init() 262 } 263 264 // Default returns the global DMA region instance. 265 func Default() *Region { 266 return dma 267 } 268 269 // Reserve is the equivalent of Region.Reserve() on the global DMA region. 270 func Reserve(size int, align int) (addr uint32, buf []byte) { 271 return dma.Reserve(size, align) 272 } 273 274 // Reserved is the equivalent of Region.Reserved() on the global DMA region. 275 func Reserved(buf []byte) (res bool, addr uint32) { 276 return dma.Reserved(buf) 277 } 278 279 // Alloc is the equivalent of Region.Alloc() on the global DMA region. 280 func Alloc(buf []byte, align int) (addr uint32) { 281 return dma.Alloc(buf, align) 282 } 283 284 // Read is the equivalent of Region.Read() on the global DMA region. 285 func Read(addr uint32, off int, buf []byte) { 286 dma.Read(addr, off, buf) 287 } 288 289 // Write is the equivalent of Region.Write() on the global DMA region. 290 func Write(addr uint32, off int, buf []byte) { 291 dma.Write(addr, off, buf) 292 } 293 294 // Free is the equivalent of Region.Free() on the global DMA region. 295 func Free(addr uint32) { 296 dma.Free(addr) 297 } 298 299 // Release is the equivalent of Region.Release() on the global DMA region. 300 func Release(addr uint32) { 301 dma.Release(addr) 302 }