github.com/usbarmory/tamago@v0.0.0-20240508072735-8612bbe1e454/dma/region.go (about) 1 // First-fit memory allocator for DMA buffers 2 // https://github.com/usbarmory/tamago 3 // 4 // Copyright (c) WithSecure Corporation 5 // https://foundry.withsecure.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 11 12 import ( 13 "container/list" 14 "sync" 15 "unsafe" 16 ) 17 18 // Region represents a memory region allocated for DMA purposes. 19 type Region struct { 20 sync.Mutex 21 22 start uint 23 size uint 24 25 freeBlocks *list.List 26 usedBlocks map[uint]*block 27 } 28 29 var dma *Region 30 31 // Default returns the global DMA region instance. 32 func Default() *Region { 33 return dma 34 } 35 36 // Init initializes a memory region with a single block that fits it. 37 func (r *Region) Init(start uint, size uint) { 38 r.start = start 39 r.size = size 40 41 b := &block{ 42 addr: start, 43 size: size, 44 } 45 46 r.freeBlocks = list.New() 47 r.freeBlocks.PushFront(b) 48 49 r.usedBlocks = make(map[uint]*block) 50 } 51 52 // Start returns the DMA region start address. 53 func (r *Region) Start() uint { 54 return r.start 55 } 56 57 // End returns the DMA region end address. 58 func (r *Region) End() uint { 59 return r.start + r.size 60 } 61 62 // Size returns the DMA region size. 63 func (r *Region) Size() uint { 64 return r.size 65 } 66 67 // FreeBlocks returns the DMA region free blocks addresses and size. 68 func (r *Region) FreeBlocks() map[uint]uint { 69 m := make(map[uint]uint) 70 71 for e := r.freeBlocks.Front(); e != nil; e = e.Next() { 72 b := e.Value.(*block) 73 m[b.addr] = b.size 74 } 75 76 return m 77 } 78 79 // UsedBlocks returns the DMA region allocated blocks addresses and size. 80 func (r *Region) UsedBlocks() map[uint]uint { 81 m := make(map[uint]uint) 82 83 for addr, b := range r.usedBlocks { 84 m[addr] = b.size 85 } 86 87 return m 88 } 89 90 // Reserve allocates a Slice of bytes for DMA purposes, by placing its data 91 // within the DMA region, with optional alignment. It returns the slice along 92 // with its data allocation address. The buffer can be freed up with Release(). 93 // 94 // Reserving buffers with Reserve() allows applications to pre-allocate DMA 95 // regions, avoiding unnecessary memory copy operations when performance is a 96 // concern. Reserved buffers cause Alloc() and Read() to return without any 97 // allocation or memory copy. 98 // 99 // Great care must be taken on reserved buffer as: 100 // - buf contents are uninitialized (unlike when using Alloc()) 101 // - buf slices remain in reserved space but only the original buf 102 // can be subject of Release() 103 // 104 // The optional alignment must be a power of 2 and word alignment is always 105 // enforced (0 == 4). 106 func (r *Region) Reserve(size int, align int) (addr uint, buf []byte) { 107 if size == 0 { 108 return 109 } 110 111 r.Lock() 112 defer r.Unlock() 113 114 b := r.alloc(uint(size), uint(align)) 115 b.res = true 116 117 r.usedBlocks[b.addr] = b 118 119 return b.addr, b.slice() 120 } 121 122 // Reserved returns whether a slice of bytes data is allocated within the DMA 123 // buffer region, it is used to determine whether the passed buffer has been 124 // previously allocated by this package with Reserve(). 125 func (r *Region) Reserved(buf []byte) (res bool, addr uint) { 126 addr = uint(uintptr(unsafe.Pointer(&buf[0]))) 127 res = addr >= r.start && addr+uint(len(buf)) <= r.start+r.size 128 129 return 130 } 131 132 // Alloc reserves a memory region for DMA purposes, copying over a buffer and 133 // returning its allocation address, with optional alignment. The region can be 134 // freed up with Free(). 135 // 136 // If the argument is a buffer previously created with Reserve(), then its 137 // address is return without any re-allocation. 138 // 139 // The optional alignment must be a power of 2 and word alignment is always 140 // enforced (0 == 4). 141 func (r *Region) Alloc(buf []byte, align int) (addr uint) { 142 size := len(buf) 143 144 if size == 0 { 145 return 0 146 } 147 148 if res, addr := Reserved(buf); res { 149 return addr 150 } 151 152 r.Lock() 153 defer r.Unlock() 154 155 b := r.alloc(uint(size), uint(align)) 156 b.write(0, buf) 157 158 r.usedBlocks[b.addr] = b 159 160 return b.addr 161 } 162 163 // Read reads exactly len(buf) bytes from a memory region address into a 164 // buffer, the region must have been previously allocated with Alloc(). 165 // 166 // The offset and buffer size are used to retrieve a slice of the memory 167 // region, a panic occurs if these parameters are not compatible with the 168 // initial allocation for the address. 169 // 170 // If the argument is a buffer previously created with Reserve(), then the 171 // function returns without modifying it, as it is assumed for the buffer to be 172 // already updated. 173 func (r *Region) Read(addr uint, off int, buf []byte) { 174 size := len(buf) 175 176 if addr == 0 || size == 0 { 177 return 178 } 179 180 if res, _ := Reserved(buf); res { 181 return 182 } 183 184 r.Lock() 185 defer r.Unlock() 186 187 b, ok := r.usedBlocks[addr] 188 189 if !ok { 190 panic("read of unallocated pointer") 191 } 192 193 if uint(off+size) > b.size { 194 panic("invalid read parameters") 195 } 196 197 b.read(uint(off), buf) 198 } 199 200 // Write writes buffer contents to a memory region address, the region must 201 // have been previously allocated with Alloc(). 202 // 203 // An offset can be passed to write a slice of the memory region, a panic 204 // occurs if the offset is not compatible with the initial allocation for the 205 // address. 206 func (r *Region) Write(addr uint, off int, buf []byte) { 207 size := len(buf) 208 209 if addr == 0 || size == 0 { 210 return 211 } 212 213 r.Lock() 214 defer r.Unlock() 215 216 b, ok := r.usedBlocks[addr] 217 218 if !ok { 219 return 220 } 221 222 if uint(off+size) > b.size { 223 panic("invalid write parameters") 224 } 225 226 b.write(uint(off), buf) 227 } 228 229 // Free frees the memory region stored at the passed address, the region must 230 // have been previously allocated with Alloc(). 231 func (r *Region) Free(addr uint) { 232 r.freeBlock(addr, false) 233 } 234 235 // Release frees the memory region stored at the passed address, the region 236 // must have been previously allocated with Reserve(). 237 func (r *Region) Release(addr uint) { 238 r.freeBlock(addr, true) 239 } 240 241 func (r *Region) defrag() { 242 var prevBlock *block 243 244 // find contiguous free blocks and combine them 245 for e := r.freeBlocks.Front(); e != nil; e = e.Next() { 246 b := e.Value.(*block) 247 248 if prevBlock != nil { 249 if prevBlock.addr+prevBlock.size == b.addr { 250 prevBlock.size += b.size 251 defer r.freeBlocks.Remove(e) 252 continue 253 } 254 } 255 256 prevBlock = e.Value.(*block) 257 } 258 } 259 260 func (r *Region) alloc(size uint, align uint) *block { 261 var e *list.Element 262 var freeBlock *block 263 var pad uint 264 265 if align == 0 { 266 // force word alignment 267 align = 4 268 } 269 270 // find suitable block 271 for e = r.freeBlocks.Front(); e != nil; e = e.Next() { 272 b := e.Value.(*block) 273 274 // pad to required alignment 275 pad = -b.addr & (align - 1) 276 277 if b.size >= size+pad { 278 freeBlock = b 279 size += pad 280 break 281 } 282 } 283 284 if freeBlock == nil { 285 panic("out of memory") 286 } 287 288 // allocate block from free linked list 289 defer r.freeBlocks.Remove(e) 290 291 // adjust block to desired size, add new block for remainder 292 if n := freeBlock.size - size; n != 0 { 293 newBlockAfter := &block{ 294 addr: freeBlock.addr + size, 295 size: n, 296 } 297 298 freeBlock.size = size 299 r.freeBlocks.InsertAfter(newBlockAfter, e) 300 } 301 302 if pad != 0 { 303 // claim padding space 304 newBlockBefore := &block{ 305 addr: freeBlock.addr, 306 size: pad, 307 } 308 309 freeBlock.addr += pad 310 freeBlock.size -= pad 311 r.freeBlocks.InsertBefore(newBlockBefore, e) 312 } 313 314 return freeBlock 315 } 316 317 func (r *Region) free(usedBlock *block) { 318 for e := r.freeBlocks.Front(); e != nil; e = e.Next() { 319 b := e.Value.(*block) 320 321 if b.addr > usedBlock.addr { 322 r.freeBlocks.InsertBefore(usedBlock, e) 323 r.defrag() 324 return 325 } 326 } 327 328 r.freeBlocks.PushBack(usedBlock) 329 } 330 331 func (r *Region) freeBlock(addr uint, res bool) { 332 if addr == 0 { 333 return 334 } 335 336 r.Lock() 337 defer r.Unlock() 338 339 b, ok := r.usedBlocks[addr] 340 341 if !ok { 342 return 343 } 344 345 if b.res != res { 346 return 347 } 348 349 r.free(b) 350 delete(r.usedBlocks, addr) 351 }