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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
|
/**
* Copyright (C) Advanced Micro Devices, Inc. All rights reserved.
*
* You may not use this software and documentation (if any) (collectively, the
* "Materials") except in compliance with the terms and conditions of the
* Software License Agreement included with the Materials or otherwise as set
* forth in writing and signed by you and an authorized signatory of AMD.
*
* If you do not have a copy of the Software License Agreement, contact your AMD
* representative for a copy. You agree that you will not reverse engineer or
* decompile the Materials, in whole or in part, except as allowed by applicable
* law.
*
* THE MATERIALS ARE DISTRIBUTED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
* REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
*/
#include "dc_memory_pool.h"
#include <linux/atomic.h>
enum {
DC_MEMORY_POOL_SENTINEL = -1,
DC_MEMORY_POOL_ACQUIRED = -2,
DC_MEMORY_POOL_RELEASED = -3,
// Correct autoincrement for negative numbers
DC_MEMORY_POOL_ENUM_SIZE_IMPL,
DC_MEMORY_POOL_ENUM_SIZE = 1 - DC_MEMORY_POOL_ENUM_SIZE_IMPL,
};
// Align everything to size of page to avoid false sharing
struct dc_memory_pool_page {
__aligned(PAGE_SIZE) char _dummy[PAGE_SIZE];
};
// Generation counter protects against ABA-problem
union index_t {
struct {
int32_t value;
uint32_t generation;
} s;
int64_t raw;
};
struct dc_memory_pool {
// Values and pointers constant after initialization, can share page
__aligned(PAGE_SIZE) size_t size;
size_t capacity;
void *unaligned_pool;
void *unaligned_memory;
struct dc_memory_pool_page *memory;
atomic_t *free_list;
// Updated every operation, use separate page to avoid false sharing
__aligned(PAGE_SIZE) atomic64_t free_head; // index_t
char _reserved2[PAGE_SIZE - sizeof(atomic64_t)];
};
static_assert(sizeof(struct dc_memory_pool) == 2 * PAGE_SIZE);
static_assert(offsetof(struct dc_memory_pool, free_head) == PAGE_SIZE);
static size_t divide_ceiling(size_t x, size_t d)
{
return (x + d - 1) / d;
}
static size_t round_up_to_multiple(size_t x, size_t m)
{
return divide_ceiling(x, m) * m;
}
static size_t size_in_pages(size_t x)
{
return divide_ceiling(x, PAGE_SIZE);
}
static void *page_align_up(void *p)
{
intptr_t i = (intptr_t)p;
i = (intptr_t)round_up_to_multiple((size_t)i, PAGE_SIZE);
return (void *)i;
}
__must_check struct dc_memory_pool *dc_memory_pool_create(size_t size,
size_t capacity)
{
if (!size || !capacity)
return NULL;
// Limited by split between size and generation in index_t
if (capacity >= (uint32_t)-DC_MEMORY_POOL_ENUM_SIZE)
return NULL;
// uint32_t because that's what alloc functions take
const uint32_t block_pages = (uint32_t)size_in_pages(size);
const uint32_t lines = (uint32_t)capacity * block_pages;
const uint32_t padded_struct_size =
sizeof(struct dc_memory_pool) + PAGE_SIZE;
void *unaligned_pool = kzalloc(padded_struct_size, GFP_KERNEL);
if (!unaligned_pool)
return NULL;
struct dc_memory_pool *pool = page_align_up(unaligned_pool);
// Compound literals create temporary in debug driver, exceeding stack size
pool->size = size;
pool->capacity = capacity;
pool->unaligned_pool = unaligned_pool;
pool->unaligned_memory = kcalloc(lines + 1, PAGE_SIZE, GFP_KERNEL);
pool->memory = page_align_up(pool->unaligned_memory);
pool->free_list = kcalloc((uint32_t)capacity, sizeof(atomic_t), GFP_KERNEL);
pool->free_head = (atomic64_t)ATOMIC_INIT(0);
if (!pool->unaligned_memory || !pool->free_list) {
dc_memory_pool_destroy(pool);
return NULL;
}
for (int32_t i = 0; i < (int32_t)capacity; i++)
atomic_set_release(&pool->free_list[i], i + 1);
atomic_set_release(&pool->free_list[capacity - 1],
DC_MEMORY_POOL_SENTINEL);
return pool;
}
void dc_memory_pool_destroy(struct dc_memory_pool *pool)
{
if (!pool)
return;
kfree(pool->free_list);
kfree(pool->unaligned_memory);
kfree(pool->unaligned_pool);
}
static int32_t dc_memory_pool_get_index(const struct dc_memory_pool *pool,
const void *memory)
{
if (!pool || !memory) {
ASSERT(false);
return DC_MEMORY_POOL_SENTINEL;
}
// Direct pointer arithmetic would be UB if called by false `owns()`
if ((uintptr_t)memory < (uintptr_t)pool->memory)
return DC_MEMORY_POOL_SENTINEL;
const uintptr_t distance = (uintptr_t)memory - (uintptr_t)pool->memory;
const size_t block_size = size_in_pages(pool->size) * PAGE_SIZE;
const size_t i = (size_t)distance / block_size;
if (distance % (uintptr_t)block_size != 0)
return DC_MEMORY_POOL_SENTINEL;
if (i >= pool->capacity)
return DC_MEMORY_POOL_SENTINEL;
return (int32_t)i;
}
static void *dc_memory_pool_get_page(const struct dc_memory_pool *pool,
int32_t index)
{
if (!pool) {
ASSERT(false);
return NULL;
}
if (index < 0 || index >= (int32_t)pool->capacity) {
ASSERT(false);
return NULL;
}
return &pool->memory[(size_t)index * size_in_pages(pool->size)];
}
__must_check void *dc_memory_pool_acquire(struct dc_memory_pool *pool)
{
if (!pool) {
ASSERT(false);
return NULL;
}
// CAS-atomic `out = head; head = head->next;`
union index_t old_head = {
.raw = atomic64_read_acquire(&pool->free_head),
};
union index_t new_head = {
.raw = 0,
};
int32_t i = 0;
do {
i = old_head.s.value;
if (i == DC_MEMORY_POOL_SENTINEL)
return NULL;
new_head = (union index_t){
.s.value = atomic_read_acquire(&pool->free_list[i]),
.s.generation = old_head.s.generation + 1,
};
} while (!atomic64_try_cmpxchg(&pool->free_head, &old_head.raw,
new_head.raw));
atomic_set_release(&pool->free_list[i], DC_MEMORY_POOL_ACQUIRED);
return dc_memory_pool_get_page(pool, i);
}
void dc_memory_pool_release(struct dc_memory_pool *pool, void *memory)
{
if (!dc_memory_pool_owns(pool, memory)) {
// Likely acquired in different pool or (racing?) double free
ASSERT(false);
return;
}
const int32_t i = dc_memory_pool_get_index(pool, memory);
if (atomic_xchg(&pool->free_list[i], DC_MEMORY_POOL_RELEASED) !=
DC_MEMORY_POOL_ACQUIRED) {
// Double free from two racing threads, use krefs to sync
ASSERT(false);
return;
}
// CAS-atomic `in->next = head; head = in;`
union index_t old_head = {
.raw = atomic64_read_acquire(&pool->free_head),
};
union index_t new_head = {
.raw = 0,
};
do {
atomic_set_release(&pool->free_list[i], old_head.s.value);
new_head = (union index_t){
.s.value = i,
.s.generation = old_head.s.generation + 1,
};
} while (!atomic64_try_cmpxchg(&pool->free_head, &old_head.raw,
new_head.raw));
}
__must_check bool dc_memory_pool_owns(const struct dc_memory_pool *pool,
const void *memory)
{
const int32_t i = dc_memory_pool_get_index(pool, memory);
if (i < 0)
return false;
if (atomic_read_acquire(&pool->free_list[i]) != DC_MEMORY_POOL_ACQUIRED)
return false;
return true;
}
__must_check size_t dc_memory_pool_size(const struct dc_memory_pool *pool)
{
ASSERT(pool);
return pool->size;
}
__must_check size_t dc_memory_pool_capacity(const struct dc_memory_pool *pool)
{
ASSERT(pool);
return pool->capacity;
}
|