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  }