// 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 ( "fmt" "io" "os" "reflect" "sync" "unsafe" "periph.io/x/host/v3/fs" ) // Slice can be transparently viewed as []byte, []uint32 or a struct. type Slice []byte // Uint32 returns a view of the byte slice as a []uint32. func (s *Slice) Uint32() []uint32 { // It's important to make a copy here. h := new(reflect.SliceHeader) h.Data = (*reflect.SliceHeader)(unsafe.Pointer(s)).Data h.Len = (*reflect.SliceHeader)(unsafe.Pointer(s)).Len / 4 h.Cap = (*reflect.SliceHeader)(unsafe.Pointer(s)).Cap / 4 return *(*[]uint32)(unsafe.Pointer(h)) } // Bytes implements Mem. func (s *Slice) Bytes() []byte { return *s } // AsPOD implements Mem. func (s *Slice) AsPOD(pp interface{}) error { if pp == nil { return wrapf("require Ptr, got nil") } vpp := reflect.ValueOf(pp) if elemSize, err := isPS(len(*s), vpp); err == nil { p := vpp.Elem() t := p.Type().Elem() if elemSize > len(*s) { return wrapf("can't map slice of struct %s (size %d) on [%d]byte", t, elemSize, len(*s)) } nbElems := len(*s) / elemSize // Use casting black magic to set the internal slice headers. hdr := (*reflect.SliceHeader)(unsafe.Pointer(p.UnsafeAddr())) hdr.Data = ((*reflect.SliceHeader)(unsafe.Pointer(s))).Data hdr.Len = nbElems hdr.Cap = nbElems return nil } size, err := isPP(vpp) if err != nil { return err } p := vpp.Elem() t := p.Type().Elem() if size > len(*s) { return wrapf("can't map struct %s (size %d) on [%d]byte", t, size, len(*s)) } // Use casting black magic to read the internal slice headers. dest := unsafe.Pointer(((*reflect.SliceHeader)(unsafe.Pointer(s))).Data) // Use reflection black magic to write to the original pointer. p.Set(reflect.NewAt(t, dest)) return nil } // View represents a view of physical memory memory mapped into user space. // // It is usually used to map CPU registers into user space, usually I/O // registers and the likes. // // It is not required to call Close(), the kernel will clean up on process // shutdown. type View struct { Slice orig []uint8 // Reference rounded to the lowest 4Kb page containing Slice. phys uint64 // physical address of the base of Slice. } // Close unmaps the memory from the user address space. // // This is done naturally by the OS on process teardown (when the process // exits) so this is not a hard requirement to call this function. func (v *View) Close() error { return munmap(v.orig) } // PhysAddr implements Mem. func (v *View) PhysAddr() uint64 { return v.phys } // MapGPIO returns a CPU specific memory mapping of the CPU I/O registers using // /dev/gpiomem. // // At the moment, /dev/gpiomem is only supported on Raspbian Jessie via a // specific kernel driver. func MapGPIO() (*View, error) { if isLinux { return mapGPIOLinux() } return nil, wrapf("/dev/gpiomem is not supported on this platform") } // Map returns a memory mapped view of arbitrary physical memory range using OS // provided functionality. // // Maps size of memory, rounded on a 4kb window. // // This function is dangerous and should be used wisely. It normally requires // super privileges (root). On Linux, it leverages /dev/mem. func Map(base uint64, size int) (*View, error) { if isLinux { return mapLinux(base, size) } return nil, wrapf("physical memory mapping is not supported on this platform") } // MapAsPOD is a leaky shorthand of calling Map(base, sizeof(v)) then AsPOD(v). // // There is no way to reclaim the memory map. // // A slice cannot be used, as it does not have inherent size. Use an aray // instead. func MapAsPOD(base uint64, i interface{}) error { // Automatically determine the necessary size. Because of this, slice of // unspecified length cannot be used here. if i == nil { return wrapf("require Ptr, got nil") } v := reflect.ValueOf(i) size, err := isPP(v) if err != nil { return err } m, err := Map(base, size) if err != nil { return err } return m.AsPOD(i) } // // Keep a cache of open file handles instead of opening and closing repeatedly. var ( mu sync.Mutex gpioMemErr error gpioMemView *View devMem fileIO devMemErr error openFile = openFileOrig ) type fileIO interface { io.Closer io.Seeker io.Reader Fd() uintptr } func openFileOrig(path string, flag int) (fileIO, error) { f, err := fs.Open(path, flag) if err != nil { return nil, err } return f, nil } // mapGPIOLinux is purely Raspbian specific. func mapGPIOLinux() (*View, error) { mu.Lock() defer mu.Unlock() if gpioMemView == nil && gpioMemErr == nil { if f, err := openFile("/dev/gpiomem", os.O_RDWR|os.O_SYNC); err == nil { defer f.Close() if i, err2 := mmap(f.Fd(), 0, pageSize); err2 == nil { gpioMemView = &View{Slice: i, orig: i, phys: 0} } else { gpioMemErr = wrapf("failed to memory map in user space GPIO memory: %w", err2) } } else { gpioMemErr = wrapf("failed to open GPIO memory: %w", err) } } return gpioMemView, gpioMemErr } // mapLinux leverages /dev/mem to map a view of physical memory. func mapLinux(base uint64, size int) (*View, error) { f, err := openDevMemLinux() if err != nil { return nil, err } // Align base and size at 4Kb. offset := int(base & 0xFFF) i, err := mmap(f.Fd(), int64(base&^0xFFF), (size+offset+0xFFF)&^0xFFF) if err != nil { return nil, wrapf("mapping at 0x%x failed: %w", base, err) } return &View{Slice: i[offset : offset+size], orig: i, phys: base + uint64(offset)}, nil } func openDevMemLinux() (fileIO, error) { mu.Lock() defer mu.Unlock() if devMem == nil && devMemErr == nil { if devMem, devMemErr = openFile("/dev/mem", os.O_RDWR|os.O_SYNC); devMemErr != nil { devMemErr = wrapf("failed to open physical memory: %v", devMemErr) } } return devMem, devMemErr } func isAcceptableInner(t reflect.Type) error { switch k := t.Kind(); k { case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: return nil case reflect.Array: return isAcceptableInner(t.Elem()) case reflect.Struct: for i := 0; i < t.NumField(); i++ { if err := isAcceptableInner(t.Field(i).Type); err != nil { return err } } return nil default: return wrapf("require Ptr to Ptr to a POD type, got Ptr to Ptr to %s", k) } } // isPP makes sure it is a pointer to a nil-pointer to something. It does // sanity checks to reduce likelihood of a panic(). func isPP(pp reflect.Value) (int, error) { if k := pp.Kind(); k != reflect.Ptr { return 0, wrapf("require Ptr, got %s of %s", k, pp.Type().Name()) } p := pp.Elem() if k := p.Kind(); k != reflect.Ptr { return 0, wrapf("require Ptr to Ptr, got %s", k) } if !p.IsNil() { return 0, wrapf("require Ptr to Ptr to be nil") } // p.Elem() can't be used since it's a nil pointer. Use the type instead. t := p.Type().Elem() if err := isAcceptableInner(t); err != nil { return 0, err } return int(t.Size()), nil } // isPS makes sure it is a pointer to a nil-slice of something. It does // sanity checks to reduce likelihood of a panic(). func isPS(bufSize int, ps reflect.Value) (int, error) { if k := ps.Kind(); k != reflect.Ptr { return 0, wrapf("require Ptr, got %s of %s", k, ps.Type().Name()) } s := ps.Elem() if k := s.Kind(); k != reflect.Slice { return 0, wrapf("require Ptr to Slice, got %s", k) } if !s.IsNil() { return 0, wrapf("require Ptr to Slice to be nil") } // s.Elem() can't be used since it's a nil slice. Use the type instead. t := s.Type().Elem() if err := isAcceptableInner(t); err != nil { return 0, err } return int(t.Size()), nil } func wrapf(format string, a ...interface{}) error { return fmt.Errorf("pmem: "+format, a...) }