forked from google/periph
-
Notifications
You must be signed in to change notification settings - Fork 0
/
alloc.go
163 lines (149 loc) · 4.89 KB
/
alloc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// 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 (
"io"
"reflect"
"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 {
return allocLinux(size)
}
return nil, wrapf("memory allocation is not supported on this platform")
}
//
// 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 {
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 {
header := *(*reflect.SliceHeader)(unsafe.Pointer(&b))
return header.Data
}
var _ Mem = &MemAlloc{}