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  }