]>
Commit | Line | Data |
---|---|---|
58f07778 DD |
1 | /***********************license start*************** |
2 | * Author: Cavium Networks | |
3 | * | |
4 | * Contact: support@caviumnetworks.com | |
5 | * This file is part of the OCTEON SDK | |
6 | * | |
7 | * Copyright (c) 2003-2008 Cavium Networks | |
8 | * | |
9 | * This file is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License, Version 2, as | |
11 | * published by the Free Software Foundation. | |
12 | * | |
13 | * This file is distributed in the hope that it will be useful, but | |
14 | * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty | |
15 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or | |
16 | * NONINFRINGEMENT. See the GNU General Public License for more | |
17 | * details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this file; if not, write to the Free Software | |
21 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
22 | * or visit http://www.gnu.org/licenses/. | |
23 | * | |
24 | * This file may also be available under a different license from Cavium. | |
25 | * Contact Cavium Networks for more information | |
26 | ***********************license end**************************************/ | |
27 | ||
28 | /* | |
29 | * Simple allocate only memory allocator. Used to allocate memory at | |
30 | * application start time. | |
31 | */ | |
32 | ||
26dd3e4f | 33 | #include <linux/export.h> |
58f07778 DD |
34 | #include <linux/kernel.h> |
35 | ||
36 | #include <asm/octeon/cvmx.h> | |
37 | #include <asm/octeon/cvmx-spinlock.h> | |
38 | #include <asm/octeon/cvmx-bootmem.h> | |
39 | ||
40 | /*#define DEBUG */ | |
41 | ||
42 | ||
43 | static struct cvmx_bootmem_desc *cvmx_bootmem_desc; | |
44 | ||
45 | /* See header file for descriptions of functions */ | |
46 | ||
9438a86a SH |
47 | /** |
48 | * This macro returns the size of a member of a structure. | |
49 | * Logically it is the same as "sizeof(s::field)" in C++, but | |
50 | * C lacks the "::" operator. | |
51 | */ | |
52 | #define SIZEOF_FIELD(s, field) sizeof(((s *)NULL)->field) | |
53 | ||
54 | /** | |
55 | * This macro returns a member of the | |
56 | * cvmx_bootmem_named_block_desc_t structure. These members can't | |
57 | * be directly addressed as they might be in memory not directly | |
58 | * reachable. In the case where bootmem is compiled with | |
59 | * LINUX_HOST, the structure itself might be located on a remote | |
60 | * Octeon. The argument "field" is the member name of the | |
61 | * cvmx_bootmem_named_block_desc_t to read. Regardless of the type | |
62 | * of the field, the return type is always a uint64_t. The "addr" | |
63 | * parameter is the physical address of the structure. | |
64 | */ | |
65 | #define CVMX_BOOTMEM_NAMED_GET_FIELD(addr, field) \ | |
66 | __cvmx_bootmem_desc_get(addr, \ | |
67 | offsetof(struct cvmx_bootmem_named_block_desc, field), \ | |
68 | SIZEOF_FIELD(struct cvmx_bootmem_named_block_desc, field)) | |
69 | ||
70 | /** | |
71 | * This function is the implementation of the get macros defined | |
72 | * for individual structure members. The argument are generated | |
73 | * by the macros inorder to read only the needed memory. | |
74 | * | |
75 | * @param base 64bit physical address of the complete structure | |
76 | * @param offset Offset from the beginning of the structure to the member being | |
77 | * accessed. | |
78 | * @param size Size of the structure member. | |
79 | * | |
80 | * @return Value of the structure member promoted into a uint64_t. | |
81 | */ | |
82 | static inline uint64_t __cvmx_bootmem_desc_get(uint64_t base, int offset, | |
83 | int size) | |
84 | { | |
85 | base = (1ull << 63) | (base + offset); | |
86 | switch (size) { | |
87 | case 4: | |
88 | return cvmx_read64_uint32(base); | |
89 | case 8: | |
90 | return cvmx_read64_uint64(base); | |
91 | default: | |
92 | return 0; | |
93 | } | |
94 | } | |
95 | ||
58f07778 DD |
96 | /* |
97 | * Wrapper functions are provided for reading/writing the size and | |
98 | * next block values as these may not be directly addressible (in 32 | |
99 | * bit applications, for instance.) Offsets of data elements in | |
100 | * bootmem list, must match cvmx_bootmem_block_header_t. | |
101 | */ | |
102 | #define NEXT_OFFSET 0 | |
103 | #define SIZE_OFFSET 8 | |
104 | ||
105 | static void cvmx_bootmem_phy_set_size(uint64_t addr, uint64_t size) | |
106 | { | |
107 | cvmx_write64_uint64((addr + SIZE_OFFSET) | (1ull << 63), size); | |
108 | } | |
109 | ||
110 | static void cvmx_bootmem_phy_set_next(uint64_t addr, uint64_t next) | |
111 | { | |
112 | cvmx_write64_uint64((addr + NEXT_OFFSET) | (1ull << 63), next); | |
113 | } | |
114 | ||
115 | static uint64_t cvmx_bootmem_phy_get_size(uint64_t addr) | |
116 | { | |
117 | return cvmx_read64_uint64((addr + SIZE_OFFSET) | (1ull << 63)); | |
118 | } | |
119 | ||
120 | static uint64_t cvmx_bootmem_phy_get_next(uint64_t addr) | |
121 | { | |
122 | return cvmx_read64_uint64((addr + NEXT_OFFSET) | (1ull << 63)); | |
123 | } | |
124 | ||
125 | void *cvmx_bootmem_alloc_range(uint64_t size, uint64_t alignment, | |
126 | uint64_t min_addr, uint64_t max_addr) | |
127 | { | |
128 | int64_t address; | |
129 | address = | |
130 | cvmx_bootmem_phy_alloc(size, min_addr, max_addr, alignment, 0); | |
131 | ||
132 | if (address > 0) | |
133 | return cvmx_phys_to_ptr(address); | |
134 | else | |
135 | return NULL; | |
136 | } | |
137 | ||
138 | void *cvmx_bootmem_alloc_address(uint64_t size, uint64_t address, | |
139 | uint64_t alignment) | |
140 | { | |
141 | return cvmx_bootmem_alloc_range(size, alignment, address, | |
142 | address + size); | |
143 | } | |
144 | ||
145 | void *cvmx_bootmem_alloc(uint64_t size, uint64_t alignment) | |
146 | { | |
147 | return cvmx_bootmem_alloc_range(size, alignment, 0, 0); | |
148 | } | |
149 | ||
9438a86a SH |
150 | void *cvmx_bootmem_alloc_named_range_once(uint64_t size, uint64_t min_addr, |
151 | uint64_t max_addr, uint64_t align, | |
152 | char *name, | |
153 | void (*init) (void *)) | |
154 | { | |
155 | int64_t addr; | |
156 | void *ptr; | |
157 | uint64_t named_block_desc_addr; | |
158 | ||
159 | named_block_desc_addr = (uint64_t) | |
160 | cvmx_bootmem_phy_named_block_find(name, | |
161 | (uint32_t)CVMX_BOOTMEM_FLAG_NO_LOCKING); | |
162 | ||
163 | if (named_block_desc_addr) { | |
164 | addr = CVMX_BOOTMEM_NAMED_GET_FIELD(named_block_desc_addr, | |
165 | base_addr); | |
166 | return cvmx_phys_to_ptr(addr); | |
167 | } | |
168 | ||
169 | addr = cvmx_bootmem_phy_named_block_alloc(size, min_addr, max_addr, | |
170 | align, name, | |
171 | (uint32_t)CVMX_BOOTMEM_FLAG_NO_LOCKING); | |
172 | ||
173 | if (addr < 0) | |
174 | return NULL; | |
175 | ptr = cvmx_phys_to_ptr(addr); | |
176 | ||
177 | if (init) | |
178 | init(ptr); | |
179 | else | |
180 | memset(ptr, 0, size); | |
181 | ||
182 | return ptr; | |
183 | } | |
184 | EXPORT_SYMBOL(cvmx_bootmem_alloc_named_range_once); | |
185 | ||
6fa044ab DD |
186 | void *cvmx_bootmem_alloc_named_range(uint64_t size, uint64_t min_addr, |
187 | uint64_t max_addr, uint64_t align, | |
188 | char *name) | |
189 | { | |
190 | int64_t addr; | |
191 | ||
192 | addr = cvmx_bootmem_phy_named_block_alloc(size, min_addr, max_addr, | |
193 | align, name, 0); | |
194 | if (addr >= 0) | |
195 | return cvmx_phys_to_ptr(addr); | |
196 | else | |
197 | return NULL; | |
198 | } | |
199 | ||
200 | void *cvmx_bootmem_alloc_named_address(uint64_t size, uint64_t address, | |
201 | char *name) | |
202 | { | |
203 | return cvmx_bootmem_alloc_named_range(size, address, address + size, | |
204 | 0, name); | |
205 | } | |
206 | ||
207 | void *cvmx_bootmem_alloc_named(uint64_t size, uint64_t alignment, char *name) | |
208 | { | |
209 | return cvmx_bootmem_alloc_named_range(size, 0, 0, alignment, name); | |
210 | } | |
211 | EXPORT_SYMBOL(cvmx_bootmem_alloc_named); | |
212 | ||
58f07778 DD |
213 | int cvmx_bootmem_free_named(char *name) |
214 | { | |
215 | return cvmx_bootmem_phy_named_block_free(name, 0); | |
216 | } | |
217 | ||
218 | struct cvmx_bootmem_named_block_desc *cvmx_bootmem_find_named_block(char *name) | |
219 | { | |
220 | return cvmx_bootmem_phy_named_block_find(name, 0); | |
221 | } | |
6fa044ab | 222 | EXPORT_SYMBOL(cvmx_bootmem_find_named_block); |
58f07778 DD |
223 | |
224 | void cvmx_bootmem_lock(void) | |
225 | { | |
226 | cvmx_spinlock_lock((cvmx_spinlock_t *) &(cvmx_bootmem_desc->lock)); | |
227 | } | |
228 | ||
229 | void cvmx_bootmem_unlock(void) | |
230 | { | |
231 | cvmx_spinlock_unlock((cvmx_spinlock_t *) &(cvmx_bootmem_desc->lock)); | |
232 | } | |
233 | ||
234 | int cvmx_bootmem_init(void *mem_desc_ptr) | |
235 | { | |
236 | /* Here we set the global pointer to the bootmem descriptor | |
237 | * block. This pointer will be used directly, so we will set | |
238 | * it up to be directly usable by the application. It is set | |
239 | * up as follows for the various runtime/ABI combinations: | |
240 | * | |
241 | * Linux 64 bit: Set XKPHYS bit | |
242 | * Linux 32 bit: use mmap to create mapping, use virtual address | |
70342287 RB |
243 | * CVMX 64 bit: use physical address directly |
244 | * CVMX 32 bit: use physical address directly | |
58f07778 DD |
245 | * |
246 | * Note that the CVMX environment assumes the use of 1-1 TLB | |
247 | * mappings so that the physical addresses can be used | |
248 | * directly | |
249 | */ | |
250 | if (!cvmx_bootmem_desc) { | |
251 | #if defined(CVMX_ABI_64) | |
252 | /* Set XKPHYS bit */ | |
253 | cvmx_bootmem_desc = cvmx_phys_to_ptr(CAST64(mem_desc_ptr)); | |
254 | #else | |
255 | cvmx_bootmem_desc = (struct cvmx_bootmem_desc *) mem_desc_ptr; | |
256 | #endif | |
257 | } | |
258 | ||
259 | return 0; | |
260 | } | |
261 | ||
262 | /* | |
263 | * The cvmx_bootmem_phy* functions below return 64 bit physical | |
264 | * addresses, and expose more features that the cvmx_bootmem_functions | |
265 | * above. These are required for full memory space access in 32 bit | |
266 | * applications, as well as for using some advance features. Most | |
267 | * applications should not need to use these. | |
268 | */ | |
269 | ||
270 | int64_t cvmx_bootmem_phy_alloc(uint64_t req_size, uint64_t address_min, | |
271 | uint64_t address_max, uint64_t alignment, | |
272 | uint32_t flags) | |
273 | { | |
274 | ||
275 | uint64_t head_addr; | |
276 | uint64_t ent_addr; | |
277 | /* points to previous list entry, NULL current entry is head of list */ | |
278 | uint64_t prev_addr = 0; | |
279 | uint64_t new_ent_addr = 0; | |
280 | uint64_t desired_min_addr; | |
281 | ||
282 | #ifdef DEBUG | |
283 | cvmx_dprintf("cvmx_bootmem_phy_alloc: req_size: 0x%llx, " | |
284 | "min_addr: 0x%llx, max_addr: 0x%llx, align: 0x%llx\n", | |
285 | (unsigned long long)req_size, | |
286 | (unsigned long long)address_min, | |
287 | (unsigned long long)address_max, | |
288 | (unsigned long long)alignment); | |
289 | #endif | |
290 | ||
291 | if (cvmx_bootmem_desc->major_version > 3) { | |
292 | cvmx_dprintf("ERROR: Incompatible bootmem descriptor " | |
293 | "version: %d.%d at addr: %p\n", | |
294 | (int)cvmx_bootmem_desc->major_version, | |
295 | (int)cvmx_bootmem_desc->minor_version, | |
296 | cvmx_bootmem_desc); | |
297 | goto error_out; | |
298 | } | |
299 | ||
300 | /* | |
301 | * Do a variety of checks to validate the arguments. The | |
302 | * allocator code will later assume that these checks have | |
303 | * been made. We validate that the requested constraints are | |
304 | * not self-contradictory before we look through the list of | |
305 | * available memory. | |
306 | */ | |
307 | ||
308 | /* 0 is not a valid req_size for this allocator */ | |
309 | if (!req_size) | |
310 | goto error_out; | |
311 | ||
312 | /* Round req_size up to mult of minimum alignment bytes */ | |
313 | req_size = (req_size + (CVMX_BOOTMEM_ALIGNMENT_SIZE - 1)) & | |
314 | ~(CVMX_BOOTMEM_ALIGNMENT_SIZE - 1); | |
315 | ||
316 | /* | |
317 | * Convert !0 address_min and 0 address_max to special case of | |
318 | * range that specifies an exact memory block to allocate. Do | |
319 | * this before other checks and adjustments so that this | |
320 | * tranformation will be validated. | |
321 | */ | |
322 | if (address_min && !address_max) | |
323 | address_max = address_min + req_size; | |
324 | else if (!address_min && !address_max) | |
325 | address_max = ~0ull; /* If no limits given, use max limits */ | |
326 | ||
327 | ||
328 | /* | |
329 | * Enforce minimum alignment (this also keeps the minimum free block | |
330 | * req_size the same as the alignment req_size. | |
331 | */ | |
332 | if (alignment < CVMX_BOOTMEM_ALIGNMENT_SIZE) | |
333 | alignment = CVMX_BOOTMEM_ALIGNMENT_SIZE; | |
334 | ||
335 | /* | |
336 | * Adjust address minimum based on requested alignment (round | |
337 | * up to meet alignment). Do this here so we can reject | |
338 | * impossible requests up front. (NOP for address_min == 0) | |
339 | */ | |
340 | if (alignment) | |
2a5d6651 | 341 | address_min = ALIGN(address_min, alignment); |
58f07778 DD |
342 | |
343 | /* | |
344 | * Reject inconsistent args. We have adjusted these, so this | |
345 | * may fail due to our internal changes even if this check | |
346 | * would pass for the values the user supplied. | |
347 | */ | |
348 | if (req_size > address_max - address_min) | |
349 | goto error_out; | |
350 | ||
351 | /* Walk through the list entries - first fit found is returned */ | |
352 | ||
353 | if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) | |
354 | cvmx_bootmem_lock(); | |
355 | head_addr = cvmx_bootmem_desc->head_addr; | |
356 | ent_addr = head_addr; | |
357 | for (; ent_addr; | |
358 | prev_addr = ent_addr, | |
359 | ent_addr = cvmx_bootmem_phy_get_next(ent_addr)) { | |
360 | uint64_t usable_base, usable_max; | |
361 | uint64_t ent_size = cvmx_bootmem_phy_get_size(ent_addr); | |
362 | ||
363 | if (cvmx_bootmem_phy_get_next(ent_addr) | |
364 | && ent_addr > cvmx_bootmem_phy_get_next(ent_addr)) { | |
365 | cvmx_dprintf("Internal bootmem_alloc() error: ent: " | |
366 | "0x%llx, next: 0x%llx\n", | |
367 | (unsigned long long)ent_addr, | |
368 | (unsigned long long) | |
369 | cvmx_bootmem_phy_get_next(ent_addr)); | |
370 | goto error_out; | |
371 | } | |
372 | ||
373 | /* | |
374 | * Determine if this is an entry that can satisify the | |
375 | * request Check to make sure entry is large enough to | |
376 | * satisfy request. | |
377 | */ | |
378 | usable_base = | |
2a5d6651 | 379 | ALIGN(max(address_min, ent_addr), alignment); |
58f07778 DD |
380 | usable_max = min(address_max, ent_addr + ent_size); |
381 | /* | |
382 | * We should be able to allocate block at address | |
383 | * usable_base. | |
384 | */ | |
385 | ||
386 | desired_min_addr = usable_base; | |
387 | /* | |
388 | * Determine if request can be satisfied from the | |
389 | * current entry. | |
390 | */ | |
391 | if (!((ent_addr + ent_size) > usable_base | |
392 | && ent_addr < address_max | |
393 | && req_size <= usable_max - usable_base)) | |
394 | continue; | |
395 | /* | |
396 | * We have found an entry that has room to satisfy the | |
397 | * request, so allocate it from this entry. If end | |
398 | * CVMX_BOOTMEM_FLAG_END_ALLOC set, then allocate from | |
399 | * the end of this block rather than the beginning. | |
400 | */ | |
401 | if (flags & CVMX_BOOTMEM_FLAG_END_ALLOC) { | |
402 | desired_min_addr = usable_max - req_size; | |
403 | /* | |
404 | * Align desired address down to required | |
405 | * alignment. | |
406 | */ | |
407 | desired_min_addr &= ~(alignment - 1); | |
408 | } | |
409 | ||
410 | /* Match at start of entry */ | |
411 | if (desired_min_addr == ent_addr) { | |
412 | if (req_size < ent_size) { | |
413 | /* | |
414 | * big enough to create a new block | |
415 | * from top portion of block. | |
416 | */ | |
417 | new_ent_addr = ent_addr + req_size; | |
418 | cvmx_bootmem_phy_set_next(new_ent_addr, | |
419 | cvmx_bootmem_phy_get_next(ent_addr)); | |
420 | cvmx_bootmem_phy_set_size(new_ent_addr, | |
421 | ent_size - | |
422 | req_size); | |
423 | ||
424 | /* | |
425 | * Adjust next pointer as following | |
426 | * code uses this. | |
427 | */ | |
428 | cvmx_bootmem_phy_set_next(ent_addr, | |
429 | new_ent_addr); | |
430 | } | |
431 | ||
432 | /* | |
433 | * adjust prev ptr or head to remove this | |
434 | * entry from list. | |
435 | */ | |
436 | if (prev_addr) | |
437 | cvmx_bootmem_phy_set_next(prev_addr, | |
438 | cvmx_bootmem_phy_get_next(ent_addr)); | |
439 | else | |
440 | /* | |
441 | * head of list being returned, so | |
442 | * update head ptr. | |
443 | */ | |
444 | cvmx_bootmem_desc->head_addr = | |
445 | cvmx_bootmem_phy_get_next(ent_addr); | |
446 | ||
447 | if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) | |
448 | cvmx_bootmem_unlock(); | |
449 | return desired_min_addr; | |
450 | } | |
451 | /* | |
452 | * block returned doesn't start at beginning of entry, | |
453 | * so we know that we will be splitting a block off | |
454 | * the front of this one. Create a new block from the | |
455 | * beginning, add to list, and go to top of loop | |
456 | * again. | |
457 | * | |
458 | * create new block from high portion of | |
459 | * block, so that top block starts at desired | |
460 | * addr. | |
461 | */ | |
462 | new_ent_addr = desired_min_addr; | |
463 | cvmx_bootmem_phy_set_next(new_ent_addr, | |
464 | cvmx_bootmem_phy_get_next | |
465 | (ent_addr)); | |
466 | cvmx_bootmem_phy_set_size(new_ent_addr, | |
467 | cvmx_bootmem_phy_get_size | |
468 | (ent_addr) - | |
469 | (desired_min_addr - | |
470 | ent_addr)); | |
471 | cvmx_bootmem_phy_set_size(ent_addr, | |
472 | desired_min_addr - ent_addr); | |
473 | cvmx_bootmem_phy_set_next(ent_addr, new_ent_addr); | |
474 | /* Loop again to handle actual alloc from new block */ | |
475 | } | |
476 | error_out: | |
477 | /* We didn't find anything, so return error */ | |
478 | if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) | |
479 | cvmx_bootmem_unlock(); | |
480 | return -1; | |
481 | } | |
482 | ||
483 | int __cvmx_bootmem_phy_free(uint64_t phy_addr, uint64_t size, uint32_t flags) | |
484 | { | |
485 | uint64_t cur_addr; | |
70342287 | 486 | uint64_t prev_addr = 0; /* zero is invalid */ |
58f07778 DD |
487 | int retval = 0; |
488 | ||
489 | #ifdef DEBUG | |
490 | cvmx_dprintf("__cvmx_bootmem_phy_free addr: 0x%llx, size: 0x%llx\n", | |
491 | (unsigned long long)phy_addr, (unsigned long long)size); | |
492 | #endif | |
493 | if (cvmx_bootmem_desc->major_version > 3) { | |
494 | cvmx_dprintf("ERROR: Incompatible bootmem descriptor " | |
495 | "version: %d.%d at addr: %p\n", | |
496 | (int)cvmx_bootmem_desc->major_version, | |
497 | (int)cvmx_bootmem_desc->minor_version, | |
498 | cvmx_bootmem_desc); | |
499 | return 0; | |
500 | } | |
501 | ||
502 | /* 0 is not a valid size for this allocator */ | |
503 | if (!size) | |
504 | return 0; | |
505 | ||
506 | if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) | |
507 | cvmx_bootmem_lock(); | |
508 | cur_addr = cvmx_bootmem_desc->head_addr; | |
509 | if (cur_addr == 0 || phy_addr < cur_addr) { | |
510 | /* add at front of list - special case with changing head ptr */ | |
511 | if (cur_addr && phy_addr + size > cur_addr) | |
70342287 | 512 | goto bootmem_free_done; /* error, overlapping section */ |
58f07778 DD |
513 | else if (phy_addr + size == cur_addr) { |
514 | /* Add to front of existing first block */ | |
515 | cvmx_bootmem_phy_set_next(phy_addr, | |
516 | cvmx_bootmem_phy_get_next | |
517 | (cur_addr)); | |
518 | cvmx_bootmem_phy_set_size(phy_addr, | |
519 | cvmx_bootmem_phy_get_size | |
520 | (cur_addr) + size); | |
521 | cvmx_bootmem_desc->head_addr = phy_addr; | |
522 | ||
523 | } else { | |
524 | /* New block before first block. OK if cur_addr is 0 */ | |
525 | cvmx_bootmem_phy_set_next(phy_addr, cur_addr); | |
526 | cvmx_bootmem_phy_set_size(phy_addr, size); | |
527 | cvmx_bootmem_desc->head_addr = phy_addr; | |
528 | } | |
529 | retval = 1; | |
530 | goto bootmem_free_done; | |
531 | } | |
532 | ||
533 | /* Find place in list to add block */ | |
534 | while (cur_addr && phy_addr > cur_addr) { | |
535 | prev_addr = cur_addr; | |
536 | cur_addr = cvmx_bootmem_phy_get_next(cur_addr); | |
537 | } | |
538 | ||
539 | if (!cur_addr) { | |
540 | /* | |
541 | * We have reached the end of the list, add on to end, | |
542 | * checking to see if we need to combine with last | |
543 | * block | |
544 | */ | |
545 | if (prev_addr + cvmx_bootmem_phy_get_size(prev_addr) == | |
546 | phy_addr) { | |
547 | cvmx_bootmem_phy_set_size(prev_addr, | |
548 | cvmx_bootmem_phy_get_size | |
549 | (prev_addr) + size); | |
550 | } else { | |
551 | cvmx_bootmem_phy_set_next(prev_addr, phy_addr); | |
552 | cvmx_bootmem_phy_set_size(phy_addr, size); | |
553 | cvmx_bootmem_phy_set_next(phy_addr, 0); | |
554 | } | |
555 | retval = 1; | |
556 | goto bootmem_free_done; | |
557 | } else { | |
558 | /* | |
559 | * insert between prev and cur nodes, checking for | |
560 | * merge with either/both. | |
561 | */ | |
562 | if (prev_addr + cvmx_bootmem_phy_get_size(prev_addr) == | |
563 | phy_addr) { | |
564 | /* Merge with previous */ | |
565 | cvmx_bootmem_phy_set_size(prev_addr, | |
566 | cvmx_bootmem_phy_get_size | |
567 | (prev_addr) + size); | |
568 | if (phy_addr + size == cur_addr) { | |
569 | /* Also merge with current */ | |
570 | cvmx_bootmem_phy_set_size(prev_addr, | |
571 | cvmx_bootmem_phy_get_size(cur_addr) + | |
572 | cvmx_bootmem_phy_get_size(prev_addr)); | |
573 | cvmx_bootmem_phy_set_next(prev_addr, | |
574 | cvmx_bootmem_phy_get_next(cur_addr)); | |
575 | } | |
576 | retval = 1; | |
577 | goto bootmem_free_done; | |
578 | } else if (phy_addr + size == cur_addr) { | |
579 | /* Merge with current */ | |
580 | cvmx_bootmem_phy_set_size(phy_addr, | |
581 | cvmx_bootmem_phy_get_size | |
582 | (cur_addr) + size); | |
583 | cvmx_bootmem_phy_set_next(phy_addr, | |
584 | cvmx_bootmem_phy_get_next | |
585 | (cur_addr)); | |
586 | cvmx_bootmem_phy_set_next(prev_addr, phy_addr); | |
587 | retval = 1; | |
588 | goto bootmem_free_done; | |
589 | } | |
590 | ||
591 | /* It is a standalone block, add in between prev and cur */ | |
592 | cvmx_bootmem_phy_set_size(phy_addr, size); | |
593 | cvmx_bootmem_phy_set_next(phy_addr, cur_addr); | |
594 | cvmx_bootmem_phy_set_next(prev_addr, phy_addr); | |
595 | ||
596 | } | |
597 | retval = 1; | |
598 | ||
599 | bootmem_free_done: | |
600 | if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) | |
601 | cvmx_bootmem_unlock(); | |
602 | return retval; | |
603 | ||
604 | } | |
605 | ||
606 | struct cvmx_bootmem_named_block_desc * | |
607 | cvmx_bootmem_phy_named_block_find(char *name, uint32_t flags) | |
608 | { | |
609 | unsigned int i; | |
610 | struct cvmx_bootmem_named_block_desc *named_block_array_ptr; | |
611 | ||
612 | #ifdef DEBUG | |
613 | cvmx_dprintf("cvmx_bootmem_phy_named_block_find: %s\n", name); | |
614 | #endif | |
615 | /* | |
616 | * Lock the structure to make sure that it is not being | |
617 | * changed while we are examining it. | |
618 | */ | |
619 | if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) | |
620 | cvmx_bootmem_lock(); | |
621 | ||
622 | /* Use XKPHYS for 64 bit linux */ | |
623 | named_block_array_ptr = (struct cvmx_bootmem_named_block_desc *) | |
624 | cvmx_phys_to_ptr(cvmx_bootmem_desc->named_block_array_addr); | |
625 | ||
626 | #ifdef DEBUG | |
627 | cvmx_dprintf | |
628 | ("cvmx_bootmem_phy_named_block_find: named_block_array_ptr: %p\n", | |
629 | named_block_array_ptr); | |
630 | #endif | |
631 | if (cvmx_bootmem_desc->major_version == 3) { | |
632 | for (i = 0; | |
633 | i < cvmx_bootmem_desc->named_block_num_blocks; i++) { | |
634 | if ((name && named_block_array_ptr[i].size | |
635 | && !strncmp(name, named_block_array_ptr[i].name, | |
636 | cvmx_bootmem_desc->named_block_name_len | |
637 | - 1)) | |
638 | || (!name && !named_block_array_ptr[i].size)) { | |
639 | if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) | |
640 | cvmx_bootmem_unlock(); | |
641 | ||
642 | return &(named_block_array_ptr[i]); | |
643 | } | |
644 | } | |
645 | } else { | |
646 | cvmx_dprintf("ERROR: Incompatible bootmem descriptor " | |
647 | "version: %d.%d at addr: %p\n", | |
648 | (int)cvmx_bootmem_desc->major_version, | |
649 | (int)cvmx_bootmem_desc->minor_version, | |
650 | cvmx_bootmem_desc); | |
651 | } | |
652 | if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) | |
653 | cvmx_bootmem_unlock(); | |
654 | ||
655 | return NULL; | |
656 | } | |
657 | ||
658 | int cvmx_bootmem_phy_named_block_free(char *name, uint32_t flags) | |
659 | { | |
660 | struct cvmx_bootmem_named_block_desc *named_block_ptr; | |
661 | ||
662 | if (cvmx_bootmem_desc->major_version != 3) { | |
663 | cvmx_dprintf("ERROR: Incompatible bootmem descriptor version: " | |
664 | "%d.%d at addr: %p\n", | |
665 | (int)cvmx_bootmem_desc->major_version, | |
666 | (int)cvmx_bootmem_desc->minor_version, | |
667 | cvmx_bootmem_desc); | |
668 | return 0; | |
669 | } | |
670 | #ifdef DEBUG | |
671 | cvmx_dprintf("cvmx_bootmem_phy_named_block_free: %s\n", name); | |
672 | #endif | |
673 | ||
674 | /* | |
675 | * Take lock here, as name lookup/block free/name free need to | |
676 | * be atomic. | |
677 | */ | |
678 | cvmx_bootmem_lock(); | |
679 | ||
680 | named_block_ptr = | |
681 | cvmx_bootmem_phy_named_block_find(name, | |
682 | CVMX_BOOTMEM_FLAG_NO_LOCKING); | |
683 | if (named_block_ptr) { | |
684 | #ifdef DEBUG | |
685 | cvmx_dprintf("cvmx_bootmem_phy_named_block_free: " | |
686 | "%s, base: 0x%llx, size: 0x%llx\n", | |
687 | name, | |
688 | (unsigned long long)named_block_ptr->base_addr, | |
689 | (unsigned long long)named_block_ptr->size); | |
690 | #endif | |
691 | __cvmx_bootmem_phy_free(named_block_ptr->base_addr, | |
692 | named_block_ptr->size, | |
693 | CVMX_BOOTMEM_FLAG_NO_LOCKING); | |
694 | named_block_ptr->size = 0; | |
695 | /* Set size to zero to indicate block not used. */ | |
696 | } | |
697 | ||
698 | cvmx_bootmem_unlock(); | |
70342287 | 699 | return named_block_ptr != NULL; /* 0 on failure, 1 on success */ |
58f07778 | 700 | } |
6fa044ab DD |
701 | |
702 | int64_t cvmx_bootmem_phy_named_block_alloc(uint64_t size, uint64_t min_addr, | |
703 | uint64_t max_addr, | |
704 | uint64_t alignment, | |
705 | char *name, | |
706 | uint32_t flags) | |
707 | { | |
708 | int64_t addr_allocated; | |
709 | struct cvmx_bootmem_named_block_desc *named_block_desc_ptr; | |
710 | ||
711 | #ifdef DEBUG | |
712 | cvmx_dprintf("cvmx_bootmem_phy_named_block_alloc: size: 0x%llx, min: " | |
713 | "0x%llx, max: 0x%llx, align: 0x%llx, name: %s\n", | |
714 | (unsigned long long)size, | |
715 | (unsigned long long)min_addr, | |
716 | (unsigned long long)max_addr, | |
717 | (unsigned long long)alignment, | |
718 | name); | |
719 | #endif | |
720 | if (cvmx_bootmem_desc->major_version != 3) { | |
721 | cvmx_dprintf("ERROR: Incompatible bootmem descriptor version: " | |
722 | "%d.%d at addr: %p\n", | |
723 | (int)cvmx_bootmem_desc->major_version, | |
724 | (int)cvmx_bootmem_desc->minor_version, | |
725 | cvmx_bootmem_desc); | |
726 | return -1; | |
727 | } | |
728 | ||
729 | /* | |
730 | * Take lock here, as name lookup/block alloc/name add need to | |
731 | * be atomic. | |
732 | */ | |
733 | if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) | |
734 | cvmx_spinlock_lock((cvmx_spinlock_t *)&(cvmx_bootmem_desc->lock)); | |
735 | ||
736 | /* Get pointer to first available named block descriptor */ | |
737 | named_block_desc_ptr = | |
738 | cvmx_bootmem_phy_named_block_find(NULL, | |
739 | flags | CVMX_BOOTMEM_FLAG_NO_LOCKING); | |
740 | ||
741 | /* | |
742 | * Check to see if name already in use, return error if name | |
743 | * not available or no more room for blocks. | |
744 | */ | |
745 | if (cvmx_bootmem_phy_named_block_find(name, | |
746 | flags | CVMX_BOOTMEM_FLAG_NO_LOCKING) || !named_block_desc_ptr) { | |
747 | if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) | |
748 | cvmx_spinlock_unlock((cvmx_spinlock_t *)&(cvmx_bootmem_desc->lock)); | |
749 | return -1; | |
750 | } | |
751 | ||
752 | ||
753 | /* | |
754 | * Round size up to mult of minimum alignment bytes We need | |
755 | * the actual size allocated to allow for blocks to be | |
4a79fb21 | 756 | * coalesced when they are freed. The alloc routine does the |
6fa044ab DD |
757 | * same rounding up on all allocations. |
758 | */ | |
2a5d6651 | 759 | size = ALIGN(size, CVMX_BOOTMEM_ALIGNMENT_SIZE); |
6fa044ab DD |
760 | |
761 | addr_allocated = cvmx_bootmem_phy_alloc(size, min_addr, max_addr, | |
762 | alignment, | |
763 | flags | CVMX_BOOTMEM_FLAG_NO_LOCKING); | |
764 | if (addr_allocated >= 0) { | |
765 | named_block_desc_ptr->base_addr = addr_allocated; | |
766 | named_block_desc_ptr->size = size; | |
767 | strncpy(named_block_desc_ptr->name, name, | |
768 | cvmx_bootmem_desc->named_block_name_len); | |
769 | named_block_desc_ptr->name[cvmx_bootmem_desc->named_block_name_len - 1] = 0; | |
770 | } | |
771 | ||
772 | if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) | |
773 | cvmx_spinlock_unlock((cvmx_spinlock_t *)&(cvmx_bootmem_desc->lock)); | |
774 | return addr_allocated; | |
775 | } | |
abe77f90 RB |
776 | |
777 | struct cvmx_bootmem_desc *cvmx_bootmem_get_desc(void) | |
778 | { | |
779 | return cvmx_bootmem_desc; | |
780 | } |