// Copyright 2016 The Periph Authors. All rights reserved. // Use of this source code is governed under the Apache License, Version 2.0 // that can be found in the LICENSE file. package pmem import ( "bytes" "io" "io/ioutil" "reflect" "sync" "unsafe" ) const pageSize = 4096 // Mem represents a section of memory that is usable by the DMA controller. // // Since this is physically allocated memory, that could potentially have been // allocated in spite of OS consent, for example by asking the GPU directly, it // is important to call Close() before process exit. type Mem interface { io.Closer // Bytes returns the user space memory mapped buffer address as a slice of // bytes. // // It is the raw view of the memory from this process. Bytes() []byte // AsPOD initializes a pointer to a POD (plain old data) to point to the // memory mapped region. // // pp must be a pointer to: // // - pointer to a base size type (uint8, int64, float32, etc) // - struct // - array of the above // - slice of the above // // and the value must be nil. Returns an error otherwise. // // If a pointer to a slice is passed in, it is initialized to the length and // capacity set to the maximum number of elements this slice can represent. // // The pointer initialized points to the same address as Bytes(). AsPOD(pp interface{}) error // PhysAddr is the physical address. It can be either 32 bits or 64 bits, // depending on the bitness of the OS kernel, not on the user mode build, // e.g. you could have compiled on a 32 bits Go toolchain but running on a // 64 bits kernel. PhysAddr() uint64 } // MemAlloc represents contiguous physically locked memory that was allocated. // // The memory is mapped in user space. // // MemAlloc implements Mem. type MemAlloc struct { View } // Close unmaps the physical memory allocation. func (m *MemAlloc) Close() error { if err := munlock(m.orig); err != nil { return err } return munmap(m.orig) } // Alloc allocates a continuous chunk of physical memory. // // Size must be rounded to 4Kb. Allocations of 4Kb will normally succeed. // Allocations larger than 64Kb will likely fail due to kernel memory // fragmentation; rebooting the host or reducing the number of running programs // may help. // // The allocated memory is uncached. func Alloc(size int) (*MemAlloc, error) { if size == 0 || size&(pageSize-1) != 0 { return nil, wrapf("allocated memory must be rounded to %d bytes", pageSize) } if isLinux && !isWSL() { return allocLinux(size) } return nil, wrapf("memory allocation is not supported on this platform") } // var ( wslOnce sync.Once isWSLValue bool ) // uallocMemLocked allocates user space memory and requests the OS to have the // chunk to be locked into physical memory. func uallocMemLocked(size int) ([]byte, error) { // It is important to write to the memory so it is forced to be present. b, err := uallocMem(size) if err == nil { for i := range b { b[i] = 0 } if err = mlock(b); err != nil { // Ignore the unmap error. _ = munmap(b) return nil, wrapf("locking %d bytes failed: %v", size, err) } } return b, err } // allocLinux allocates physical memory and returns a user view to it. func allocLinux(size int) (*MemAlloc, error) { // TODO(maruel): Implement the "shotgun approach". Allocate a ton of 4Kb // pages and lock them. Then look at their physical pages and only keep the // one useful. Then create a linear mapping in memory to simplify the user // mode with a single linear user space virtual address but keep the // individual page alive with their initial allocation. When done release // each individual page. if size > pageSize { return nil, wrapf("large allocation is not yet implemented") } // First allocate a chunk of user space memory. b, err := uallocMemLocked(size) if err != nil { return nil, err } pages := make([]uint64, (size+pageSize-1)/pageSize) // Figure out the physical memory addresses. for i := range pages { pages[i], err = virtToPhys(toRaw(b[pageSize*i:])) if err != nil { return nil, err } if pages[i] == 0 { return nil, wrapf("failed to read page %d", i) } } for i := 1; i < len(pages); i++ { // Fail if the memory is not contiguous. if pages[i] != pages[i-1]+pageSize { return nil, wrapf("failed to allocate %d bytes of continugous physical memory; page %d =0x%x; page %d=0x%x", size, i, pages[i], i-1, pages[i-1]) } } return &MemAlloc{View{Slice: b, phys: pages[0], orig: b}}, nil } // virtToPhys returns the physical memory address backing a virtual // memory address. func virtToPhys(virt uintptr) (uint64, error) { physPage, err := ReadPageMap(virt) if err != nil { return 0, err } if physPage&(1<<63) == 0 { // If high bit is not set, the page doesn't exist. return 0, wrapf("0x%08x has no physical address", virt) } // Strip flags. See linux documentation on kernel.org for more details. physPage &^= 0x1FF << 55 return physPage * pageSize, nil } func toRaw(b []byte) uintptr { h := (*reflect.SliceHeader)(unsafe.Pointer(&b)) return h.Data } // isWSL returns true if running under Windows Subsystem for Linux. func isWSL() bool { wslOnce.Do(func() { if c, err := ioutil.ReadFile("/proc/sys/kernel/osrelease"); err == nil { isWSLValue = bytes.Contains(c, []byte("Microsoft")) } }) return isWSLValue } var _ Mem = &MemAlloc{}