Skip to content

Commit

Permalink
pmem: increase coverage, improve smoketest
Browse files Browse the repository at this point in the history
Improve pmem.CopyTest() doc.
  • Loading branch information
maruel committed Apr 13, 2017
1 parent d7d338d commit 961773a
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 59 deletions.
18 changes: 8 additions & 10 deletions host/pmem/alloc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
package pmem

import (
"errors"
"fmt"
"io"
"reflect"
"unsafe"
Expand Down Expand Up @@ -67,13 +65,13 @@ func (m *MemAlloc) Close() error {
//
// The allocated memory is uncached.
func Alloc(size int) (*MemAlloc, error) {
if size&(pageSize-1) != 0 {
return nil, fmt.Errorf("pmem: allocated memory must be rounded to %d bytes", pageSize)
if size == 0 || size&(pageSize-1) != 0 {
return nil, wrapf("allocated memory must be rounded to %d bytes", pageSize)
}
if isLinux {
return allocLinux(size)
}
return nil, errors.New("pmem: memory allocation is not supported on this platform")
return nil, wrapf("memory allocation is not supported on this platform")
}

//
Expand All @@ -89,7 +87,7 @@ func uallocMemLocked(size int) ([]byte, error) {
}
if err := mlock(b); err != nil {
munmap(b)
return nil, fmt.Errorf("pmem: locking %d bytes failed: %v", size, err)
return nil, wrapf("locking %d bytes failed: %v", size, err)
}
}
return b, err
Expand All @@ -104,7 +102,7 @@ func allocLinux(size int) (*MemAlloc, error) {
// individual page alive with their initial allocation. When done release
// each individual page.
if size > pageSize {
return nil, errors.New("large allocation is not yet implemented")
return nil, wrapf("large allocation is not yet implemented")
}
// First allocate a chunk of user space memory.
b, err := uallocMemLocked(size)
Expand All @@ -119,13 +117,13 @@ func allocLinux(size int) (*MemAlloc, error) {
return nil, err
}
if pages[i] == 0 {
return nil, fmt.Errorf("pmem: failed to read page %d", i)
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, fmt.Errorf("pmem: 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 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])
}
}

Expand All @@ -141,7 +139,7 @@ func virtToPhys(virt uintptr) (uint64, error) {
}
if physPage&(1<<63) == 0 {
// If high bit is not set, the page doesn't exist.
return 0, fmt.Errorf("pmem: 0x%08x has no physical address", virt)
return 0, wrapf("0x%08x has no physical address", virt)
}
// Strip flags. See linux documentation on kernel.org for more details.
physPage &^= 0x1FF << 55
Expand Down
19 changes: 19 additions & 0 deletions host/pmem/alloc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2017 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 "testing"

func TestAlloc(t *testing.T) {
if m, err := Alloc(0); m != nil || err == nil {
t.Fatal("0 bytes")
}
if m, err := Alloc(1); m != nil || err == nil {
t.Fatal("not 4096 bytes")
}
if m, err := Alloc(4096); m != nil || err == nil {
t.Fatal("not expected to succeed; e.g. it's known to be broken")
}
}
29 changes: 20 additions & 9 deletions host/pmem/mem_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,38 @@

package pmem

import (
"fmt"
"syscall"
)
import "syscall"

const isLinux = true

func mmap(fd uintptr, offset int64, length int) ([]byte, error) {
return syscall.Mmap(int(fd), offset, length, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
v, err := syscall.Mmap(int(fd), offset, length, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
return nil, wrapf("failed to memory map: %v", err)
}
return v, nil
}

func munmap(b []byte) error {
return syscall.Munmap(b)
if err := syscall.Munmap(b); err != nil {
return wrapf("failed to unmap memory: %v", err)
}
return nil

}

func mlock(b []byte) error {
return syscall.Mlock(b)
if err := syscall.Mlock(b); err != nil {
return wrapf("failed to lock memory: %v", err)
}
return nil
}

func munlock(b []byte) error {
return syscall.Munlock(b)
if err := syscall.Munlock(b); err != nil {
return wrapf("failed to unlock memory: %v", err)
}
return nil
}

// uallocMem allocates user space memory.
Expand All @@ -39,7 +50,7 @@ func uallocMem(size int) ([]byte, error) {
// See /sys/kernel/mm/hugepages but both C.H.I.P. running Jessie and Raspbian
// Jessie do not expose huge pages. :(
if err != nil {
return nil, fmt.Errorf("phys: allocating %d bytes failed: %v", size, err)
return nil, wrapf("allocating %d bytes failed: %v", size, err)
}
return b, err
}
10 changes: 4 additions & 6 deletions host/pmem/mem_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,22 @@

package pmem

import "errors"

const isLinux = false

func mmap(fd uintptr, offset int64, length int) ([]byte, error) {
return nil, errors.New("syscall.Mmap() not implemented on this OS")
return nil, wrapf("syscall.Mmap() not implemented on this OS")
}

func munmap(b []byte) error {
return errors.New("syscall.Munmap() not implemented on this OS")
return wrapf("syscall.Munmap() not implemented on this OS")
}

func mlock(b []byte) error {
return errors.New("syscall.Mlock() not implemented on this OS")
return wrapf("syscall.Mlock() not implemented on this OS")
}

func munlock(b []byte) error {
return errors.New("syscall.Munlock() not implemented on this OS")
return wrapf("syscall.Munlock() not implemented on this OS")
}

// uallocMem allocates user space memory.
Expand Down
31 changes: 19 additions & 12 deletions host/pmem/smoketest.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,25 @@ package pmem
import (
"bytes"
"encoding/hex"
"fmt"
"math/rand"
)

// CopyTest is used by CPU drivers to verify that the DMA engine works
// correctly.
//
// It allocates a buffer of `size` via the `alloc` function provided. It fills
// the source buffer with random data and copies it to the destination with
// `holeSize` bytes as an untouched header and footer. This is done to confirm
// misaligned copying works.
// CopyTest allocates two buffer via `alloc`, once as the source and one as the
// destination. It fills the source with random data and the destination with
// 0x11.
//
// The function `f` being tested is only given the buffer physical addresses and
// must copy the data without other help.
func CopyTest(size, holeSize int, alloc func(size int) (Mem, error), f func(pDst, pSrc uint64) error) error {
// `copyMem` is expected to copy the memory from pSrc to pDst, with an offset
// of `hole` and size `size-2*hole`.
//
// The function `copyMem` being tested is only given the buffer physical
// addresses and must copy the data without other help. It is expected to
//
// This confirm misaligned DMA copying works.
// leverage the host's DMA engine.
func CopyTest(size, holeSize int, alloc func(size int) (Mem, error), copyMem func(pDst, pSrc uint64) error) error {
pSrc, err2 := alloc(size)
if err2 != nil {
return err2
Expand All @@ -43,19 +47,22 @@ func CopyTest(size, holeSize int, alloc func(size int) (Mem, error), f func(pDst
copy(pSrc.Bytes(), src[:])

// Run the driver supplied memory copying code.
if err := f(pSrc.PhysAddr(), pDst.PhysAddr()); err != nil {
if err := copyMem(pDst.PhysAddr(), pSrc.PhysAddr()); err != nil {
return err
}

// Verifications.
for i := 0; i < holeSize; i++ {
if dst[i] != 0x11 {
return fmt.Errorf("DMA corrupted the buffer header: %s", hex.EncodeToString(dst[:holeSize]))
return wrapf("DMA corrupted the buffer header: %s", hex.EncodeToString(dst[:holeSize]))
}
if dst[size-1-i] != 0x11 {
return fmt.Errorf("DMA corrupted the buffer footer: %s", hex.EncodeToString(dst[size-1-holeSize:]))
return wrapf("DMA corrupted the buffer footer: %s", hex.EncodeToString(dst[size-1-holeSize:]))
}
}

// Headers and footers were not corupted in the destination. Verify the inner
// view that should match.
x := src[:size-2*holeSize]
y := dst[holeSize : size-holeSize]
if !bytes.Equal(x, y) {
Expand All @@ -75,7 +82,7 @@ func CopyTest(size, holeSize int, alloc func(size int) (Mem, error), f func(pDst
if len(y) > 32 {
y = y[:32]
}
return fmt.Errorf("DMA corrupted the buffer at offset %d:\n%s\n%s", offset, hex.EncodeToString(x), hex.EncodeToString(y))
return wrapf("DMA corrupted the buffer at offset %d:\n%s\n%s", offset, hex.EncodeToString(x), hex.EncodeToString(y))
}
return nil
}
111 changes: 111 additions & 0 deletions host/pmem/smoketest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2017 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 (
"errors"
"testing"
"unsafe"
)

func TestSmokeTest_fail(t *testing.T) {
count := 0
alloc := func(size int) (Mem, error) {
if count == 0 {
return nil, errors.New("oops")
}
count--
return allocRam(size)
}

if CopyTest(1024, 1, alloc, copyOk) == nil {
t.Fatal("first alloc failed")
}
count = 1
if CopyTest(1024, 1, alloc, copyOk) == nil {
t.Fatal("second alloc failed")
}

copyFail := func(d, s uint64) error {
return errors.New("oops")
}
if CopyTest(1024, 1, allocRam, copyFail) == nil {
t.Fatal("copy failed")
}

copyNop := func(d, s uint64) error {
return nil
}
if CopyTest(1024, 1, allocRam, copyNop) == nil {
t.Fatal("no copy")
}

copyPartial := func(d, s uint64) error {
return copyRam(d, s, 1024, 2)
}
if CopyTest(1024, 1, allocRam, copyPartial) == nil {
t.Fatal("copy corrupted")
}

copyHdr := func(d, s uint64) error {
toSlice(d)[0] = 0
return nil
}
if CopyTest(1024, 1, allocRam, copyHdr) == nil {
t.Fatal("header corrupted")
}

copyFtr := func(d, s uint64) error {
toSlice(d)[1023] = 0
return copyRam(d, s, 1024, 1)
}
if CopyTest(1024, 1, allocRam, copyFtr) == nil {
t.Fatal("footer corrupted")
}

copyOffset := func(d, s uint64) error {
copyRam(d, s, 1024, 1)
toSlice(d)[3] = 0
return nil
}
if CopyTest(1024, 1, allocRam, copyOffset) == nil {
t.Fatal("copy corrupted")
}
}

func TestSmokeTest(t *testing.T) {
// Successfully copy the memory.
if err := CopyTest(1024, 1, allocRam, copyOk); err != nil {
t.Fatal(err)
}
}

// allocRam allocates memory and fake it is physical memory.
func allocRam(size int) (Mem, error) {
p := make([]byte, size)
return &MemAlloc{
View: View{
Slice: Slice(p),
orig: p,
phys: uint64(uintptr(unsafe.Pointer(&p))),
},
}, nil
}

func copyOk(d, s uint64) error {
return copyRam(d, s, 1024, 1)
}

// copyRam copies the memory.
func copyRam(pDst, pSrc uint64, size, hole int) error {
dst := toSlice(pDst)
src := toSlice(pSrc)
copy(dst[hole:size-hole], src)
return nil
}

func toSlice(p uint64) []byte {
return *(*[]byte)(unsafe.Pointer(uintptr(p)))
}
Loading

0 comments on commit 961773a

Please sign in to comment.