Commit | Line | Data |
---|---|---|
c906108c SS |
1 | /* This file is part of the program psim. |
2 | ||
3 | Copyright (C) 1994-1997, Andrew Cagney <cagney@highland.com.au> | |
4 | ||
5 | This program is free software; you can redistribute it and/or modify | |
6 | it under the terms of the GNU General Public License as published by | |
3fd725ef | 7 | the Free Software Foundation; either version 3 of the License, or |
c906108c SS |
8 | (at your option) any later version. |
9 | ||
10 | This program is distributed in the hope that it will be useful, | |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | GNU General Public License for more details. | |
14 | ||
15 | You should have received a copy of the GNU General Public License | |
51b318de | 16 | along with this program; if not, see <http://www.gnu.org/licenses/>. |
c906108c SS |
17 | |
18 | */ | |
19 | ||
20 | ||
21 | #ifndef _HW_MEMORY_C_ | |
22 | #define _HW_MEMORY_C_ | |
23 | ||
24 | #ifndef STATIC_INLINE_HW_MEMORY | |
25 | #define STATIC_INLINE_HW_MEMORY STATIC_INLINE | |
26 | #endif | |
27 | ||
ceac4ecf JS |
28 | #include <stdlib.h> |
29 | ||
c906108c SS |
30 | #include "device_table.h" |
31 | ||
32 | /* DEVICE | |
33 | ||
34 | ||
35 | memory - description of system memory | |
36 | ||
37 | ||
38 | DESCRIPTION | |
39 | ||
40 | ||
41 | This device describes the size and location of the banks of | |
42 | physical memory within the simulation. | |
43 | ||
44 | In addition, this device supports the "claim" and "release" methods | |
45 | that can be used by OpenBoot client programs to manage the | |
46 | allocation of physical memory. | |
47 | ||
48 | ||
49 | PROPERTIES | |
50 | ||
51 | ||
52 | reg = { <address> <size> } (required) | |
53 | ||
54 | Each pair specify one bank of memory. | |
55 | ||
56 | available = { <address> <size> } (automatic) | |
57 | ||
58 | Each pair specifies a block of memory that is currently unallocated. | |
59 | ||
60 | ||
61 | BUGS | |
62 | ||
63 | ||
64 | OpenFirmware doesn't make it clear if, when releasing memory the | |
65 | same address + size pair as was used during the claim should be | |
66 | specified. | |
67 | ||
68 | It is assumed that #size-cells and #address-cells for the parent | |
69 | node of this device are both one i.e. an address or size can be | |
70 | specified using a single memory cell (word). | |
71 | ||
72 | Significant work will be required before the <<memory>> device can | |
73 | support 64bit addresses (#address-cells equal two). | |
74 | ||
75 | */ | |
76 | ||
77 | typedef struct _memory_reg_spec { | |
78 | unsigned_cell base; | |
79 | unsigned_cell size; | |
80 | } memory_reg_spec; | |
81 | ||
82 | typedef struct _hw_memory_chunk hw_memory_chunk; | |
83 | struct _hw_memory_chunk { | |
84 | unsigned_word address; | |
85 | unsigned_word size; | |
86 | int available; | |
87 | hw_memory_chunk *next; | |
88 | }; | |
89 | ||
90 | typedef struct _hw_memory_device { | |
91 | hw_memory_chunk *heap; | |
92 | } hw_memory_device; | |
93 | ||
94 | ||
95 | static void * | |
96 | hw_memory_create(const char *name, | |
97 | const device_unit *unit_address, | |
98 | const char *args) | |
99 | { | |
100 | hw_memory_device *hw_memory = ZALLOC(hw_memory_device); | |
101 | return hw_memory; | |
102 | } | |
103 | ||
104 | ||
105 | static void | |
106 | hw_memory_set_available(device *me, | |
107 | hw_memory_device *hw_memory) | |
108 | { | |
109 | hw_memory_chunk *chunk = NULL; | |
110 | memory_reg_spec *available = NULL; | |
111 | int nr_available = 0; | |
112 | int curr = 0; | |
113 | int sizeof_available = 0; | |
114 | /* determine the nr of available chunks */ | |
115 | chunk = hw_memory->heap; | |
116 | nr_available = 0; | |
117 | while (chunk != NULL) { | |
118 | if (chunk->available) | |
119 | nr_available += 1; | |
120 | ASSERT(chunk->next == NULL | |
121 | || chunk->address < chunk->next->address); | |
122 | ASSERT(chunk->next == NULL | |
123 | || chunk->address + chunk->size == chunk->next->address); | |
124 | chunk = chunk->next; | |
125 | } | |
126 | /* now create the available struct */ | |
127 | ASSERT(nr_available > 0); | |
128 | sizeof_available = sizeof(memory_reg_spec) * nr_available; | |
129 | available = zalloc(sizeof_available); | |
130 | chunk = hw_memory->heap; | |
131 | curr = 0; | |
132 | while (chunk != NULL) { | |
133 | if (chunk->available) { | |
134 | available[curr].base = H2BE_cell(chunk->address); | |
135 | available[curr].size = H2BE_cell(chunk->size); | |
136 | curr += 1; | |
137 | } | |
138 | chunk = chunk->next; | |
139 | } | |
140 | /* update */ | |
141 | device_set_array_property(me, "available", available, sizeof_available); | |
d79fe0d6 | 142 | free(available); |
c906108c SS |
143 | } |
144 | ||
145 | ||
146 | static void | |
147 | hw_memory_init_address(device *me) | |
148 | { | |
149 | hw_memory_device *hw_memory = (hw_memory_device*)device_data(me); | |
150 | ||
151 | /* free up any previous structures */ | |
152 | { | |
153 | hw_memory_chunk *curr_chunk = hw_memory->heap; | |
154 | hw_memory->heap = NULL; | |
155 | while (curr_chunk != NULL) { | |
156 | hw_memory_chunk *dead_chunk = curr_chunk; | |
157 | curr_chunk = dead_chunk->next; | |
158 | dead_chunk->next = NULL; | |
d79fe0d6 | 159 | free(dead_chunk); |
c906108c SS |
160 | } |
161 | } | |
162 | ||
163 | /* attach memory regions according to the "reg" property */ | |
164 | { | |
165 | int reg_nr; | |
166 | reg_property_spec reg; | |
167 | for (reg_nr = 0; | |
168 | device_find_reg_array_property(me, "reg", reg_nr, ®); | |
169 | reg_nr++) { | |
170 | int i; | |
171 | /* check that the entry meets restrictions */ | |
172 | for (i = 0; i < reg.address.nr_cells - 1; i++) | |
173 | if (reg.address.cells[i] != 0) | |
174 | device_error(me, "Only single celled addresses supported"); | |
175 | for (i = 0; i < reg.size.nr_cells - 1; i++) | |
176 | if (reg.size.cells[i] != 0) | |
177 | device_error(me, "Only single celled sizes supported"); | |
178 | /* attach the range */ | |
179 | device_attach_address(device_parent(me), | |
180 | attach_raw_memory, | |
181 | 0 /*address space*/, | |
182 | reg.address.cells[reg.address.nr_cells - 1], | |
183 | reg.size.cells[reg.size.nr_cells - 1], | |
184 | access_read_write_exec, | |
185 | me); | |
186 | } | |
187 | } | |
188 | ||
189 | /* create the initial `available memory' data structure */ | |
190 | if (device_find_property(me, "available") != NULL) { | |
191 | hw_memory_chunk **curr_chunk = &hw_memory->heap; | |
192 | int cell_nr; | |
193 | unsigned_cell dummy; | |
194 | int nr_cells = device_find_integer_array_property(me, "available", 0, &dummy); | |
195 | if ((nr_cells % 2) != 0) | |
196 | device_error(me, "property \"available\" invalid - contains an odd number of cells"); | |
197 | for (cell_nr = 0; | |
198 | cell_nr < nr_cells; | |
199 | cell_nr += 2) { | |
200 | hw_memory_chunk *new_chunk = ZALLOC(hw_memory_chunk); | |
201 | device_find_integer_array_property(me, "available", cell_nr, | |
202 | &new_chunk->address); | |
203 | device_find_integer_array_property(me, "available", cell_nr + 1, | |
204 | &new_chunk->size); | |
205 | new_chunk->available = 1; | |
206 | *curr_chunk = new_chunk; | |
207 | curr_chunk = &new_chunk->next; | |
208 | } | |
209 | } | |
210 | else { | |
211 | hw_memory_chunk **curr_chunk = &hw_memory->heap; | |
212 | int reg_nr; | |
213 | reg_property_spec reg; | |
214 | for (reg_nr = 0; | |
215 | device_find_reg_array_property(me, "reg", reg_nr, ®); | |
216 | reg_nr++) { | |
217 | hw_memory_chunk *new_chunk; | |
218 | new_chunk = ZALLOC(hw_memory_chunk); | |
219 | new_chunk->address = reg.address.cells[reg.address.nr_cells - 1]; | |
220 | new_chunk->size = reg.size.cells[reg.size.nr_cells - 1]; | |
221 | new_chunk->available = 1; | |
222 | *curr_chunk = new_chunk; | |
223 | curr_chunk = &new_chunk->next; | |
224 | } | |
225 | } | |
226 | ||
227 | /* initialize the alloc property for this device */ | |
228 | hw_memory_set_available(me, hw_memory); | |
229 | } | |
230 | ||
231 | static void | |
232 | hw_memory_instance_delete(device_instance *instance) | |
233 | { | |
234 | return; | |
235 | } | |
236 | ||
237 | static int | |
238 | hw_memory_instance_claim(device_instance *instance, | |
239 | int n_stack_args, | |
240 | unsigned_cell stack_args[/*n_stack_args*/], | |
241 | int n_stack_returns, | |
242 | unsigned_cell stack_returns[/*n_stack_returns*/]) | |
243 | { | |
244 | hw_memory_device *hw_memory = device_instance_data(instance); | |
245 | device *me = device_instance_device(instance); | |
246 | int stackp = 0; | |
247 | unsigned_word alignment; | |
248 | unsigned_cell size; | |
249 | unsigned_cell address; | |
250 | hw_memory_chunk *chunk = NULL; | |
251 | ||
252 | /* get the alignment from the stack */ | |
253 | if (n_stack_args < stackp + 1) | |
254 | device_error(me, "claim - incorrect number of arguments (alignment missing)"); | |
255 | alignment = stack_args[stackp]; | |
256 | stackp++; | |
257 | ||
258 | /* get the size from the stack */ | |
259 | { | |
260 | int i; | |
261 | int nr_cells = device_nr_size_cells(device_parent(me)); | |
262 | if (n_stack_args < stackp + nr_cells) | |
263 | device_error(me, "claim - incorrect number of arguments (size missing)"); | |
264 | for (i = 0; i < nr_cells - 1; i++) { | |
265 | if (stack_args[stackp] != 0) | |
266 | device_error(me, "claim - multi-cell sizes not supported"); | |
267 | stackp++; | |
268 | } | |
269 | size = stack_args[stackp]; | |
270 | stackp++; | |
271 | } | |
272 | ||
273 | /* get the address from the stack */ | |
274 | { | |
275 | int nr_cells = device_nr_address_cells(device_parent(me)); | |
276 | if (alignment != 0) { | |
277 | if (n_stack_args != stackp) { | |
278 | if (n_stack_args == stackp + nr_cells) | |
279 | DTRACE(memory, ("claim - extra address argument ignored\n")); | |
280 | else | |
281 | device_error(me, "claim - incorrect number of arguments (optional addr)"); | |
282 | } | |
283 | address = 0; | |
284 | } | |
285 | else { | |
286 | int i; | |
287 | if (n_stack_args != stackp + nr_cells) | |
288 | device_error(me, "claim - incorrect number of arguments (addr missing)"); | |
289 | for (i = 0; i < nr_cells - 1; i++) { | |
290 | if (stack_args[stackp] != 0) | |
291 | device_error(me, "claim - multi-cell addresses not supported"); | |
292 | stackp++; | |
293 | } | |
294 | address = stack_args[stackp]; | |
295 | } | |
296 | } | |
297 | ||
298 | /* check that there is space for the result */ | |
299 | if (n_stack_returns != 0 | |
300 | && n_stack_returns != device_nr_address_cells(device_parent(me))) | |
301 | device_error(me, "claim - invalid number of return arguments"); | |
302 | ||
303 | /* find a chunk candidate, either according to address or alignment */ | |
304 | if (alignment == 0) { | |
305 | chunk = hw_memory->heap; | |
306 | while (chunk != NULL) { | |
307 | if ((address + size) <= (chunk->address + chunk->size)) | |
308 | break; | |
309 | chunk = chunk->next; | |
310 | } | |
311 | if (chunk == NULL || address < chunk->address || !chunk->available) | |
312 | device_error(me, "failed to allocate %ld bytes at 0x%lx", | |
313 | (unsigned long)size, (unsigned long)address); | |
314 | DTRACE(memory, ("claim - address=0x%lx size=0x%lx\n", | |
315 | (unsigned long)address, | |
316 | (unsigned long)size)); | |
317 | } | |
318 | else { | |
319 | /* adjust the alignment so that it is a power of two */ | |
320 | unsigned_word align_mask = 1; | |
321 | while (align_mask < alignment && align_mask != 0) | |
322 | align_mask <<= 1; | |
323 | if (align_mask == 0) | |
324 | device_error(me, "alignment 0x%lx is to large", (unsigned long)alignment); | |
325 | align_mask -= 1; | |
326 | /* now find an aligned chunk that fits */ | |
327 | chunk = hw_memory->heap; | |
328 | while (chunk != NULL) { | |
329 | address = ((chunk->address + align_mask) & ~align_mask); | |
330 | if ((chunk->available) | |
331 | && (chunk->address + chunk->size >= address + size)) | |
332 | break; | |
333 | chunk = chunk->next; | |
334 | } | |
335 | if (chunk == NULL) | |
336 | device_error(me, "failed to allocate %ld bytes with alignment %ld", | |
337 | (unsigned long)size, (unsigned long)alignment); | |
338 | DTRACE(memory, ("claim - size=0x%lx alignment=%ld (0x%lx), address=0x%lx\n", | |
339 | (unsigned long)size, | |
340 | (unsigned long)alignment, | |
341 | (unsigned long)alignment, | |
342 | (unsigned long)address)); | |
343 | } | |
344 | ||
345 | /* break off a bit before this chunk if needed */ | |
346 | ASSERT(address >= chunk->address); | |
347 | if (address > chunk->address) { | |
348 | hw_memory_chunk *next_chunk = ZALLOC(hw_memory_chunk); | |
349 | /* insert a new chunk */ | |
350 | next_chunk->next = chunk->next; | |
351 | chunk->next = next_chunk; | |
352 | /* adjust the address/size */ | |
353 | next_chunk->address = address; | |
354 | next_chunk->size = chunk->address + chunk->size - next_chunk->address; | |
355 | next_chunk->available = 1; | |
356 | chunk->size = next_chunk->address - chunk->address; | |
357 | /* make this new chunk the one to allocate */ | |
358 | chunk = next_chunk; | |
359 | } | |
360 | ASSERT(address == chunk->address); | |
361 | ||
362 | /* break off a bit after this chunk if needed */ | |
363 | ASSERT(address + size <= chunk->address + chunk->size); | |
364 | if (address + size < chunk->address + chunk->size) { | |
365 | hw_memory_chunk *next_chunk = ZALLOC(hw_memory_chunk); | |
366 | /* insert it in to the list */ | |
367 | next_chunk->next = chunk->next; | |
368 | chunk->next = next_chunk; | |
369 | /* adjust the address/size */ | |
370 | next_chunk->address = address + size; | |
371 | next_chunk->size = chunk->address + chunk->size - next_chunk->address; | |
372 | next_chunk->available = 1; | |
373 | chunk->size = next_chunk->address - chunk->address; | |
374 | } | |
375 | ASSERT(address + size == chunk->address + chunk->size); | |
376 | ||
377 | /* now allocate/return it */ | |
378 | chunk->available = 0; | |
379 | hw_memory_set_available(device_instance_device(instance), hw_memory); | |
380 | if (n_stack_returns > 0) { | |
381 | int i; | |
382 | for (i = 0; i < n_stack_returns - 1; i++) | |
383 | stack_returns[i] = 0; | |
384 | stack_returns[n_stack_returns - 1] = address; | |
385 | } | |
386 | ||
387 | return 0; | |
388 | } | |
389 | ||
390 | ||
391 | static int | |
392 | hw_memory_instance_release(device_instance *instance, | |
393 | int n_stack_args, | |
394 | unsigned_cell stack_args[/*n_stack_args*/], | |
395 | int n_stack_returns, | |
396 | unsigned_cell stack_returns[/*n_stack_returns*/]) | |
397 | { | |
398 | hw_memory_device *hw_memory = device_instance_data(instance); | |
399 | device *me = device_instance_device(instance); | |
400 | unsigned_word length; | |
401 | unsigned_word address; | |
402 | int stackp = 0; | |
403 | hw_memory_chunk *chunk; | |
404 | ||
405 | /* get the length from the stack */ | |
406 | { | |
407 | int i; | |
408 | int nr_cells = device_nr_size_cells(device_parent(me)); | |
409 | if (n_stack_args < stackp + nr_cells) | |
410 | device_error(me, "release - incorrect number of arguments (length missing)"); | |
411 | for (i = 0; i < nr_cells - 1; i++) { | |
412 | if (stack_args[stackp] != 0) | |
413 | device_error(me, "release - multi-cell length not supported"); | |
414 | stackp++; | |
415 | } | |
416 | length = stack_args[stackp]; | |
417 | stackp++; | |
418 | } | |
419 | ||
420 | /* get the address from the stack */ | |
421 | { | |
422 | int i; | |
423 | int nr_cells = device_nr_address_cells(device_parent(me)); | |
424 | if (n_stack_args != stackp + nr_cells) | |
425 | device_error(me, "release - incorrect number of arguments (addr missing)"); | |
426 | for (i = 0; i < nr_cells - 1; i++) { | |
427 | if (stack_args[stackp] != 0) | |
428 | device_error(me, "release - multi-cell addresses not supported"); | |
429 | stackp++; | |
430 | } | |
431 | address = stack_args[stackp]; | |
432 | } | |
433 | ||
434 | /* returns ok */ | |
435 | if (n_stack_returns != 0) | |
436 | device_error(me, "release - nonzero number of results"); | |
437 | ||
438 | /* try to free the corresponding memory chunk */ | |
439 | chunk = hw_memory->heap; | |
440 | while (chunk != NULL) { | |
441 | if (chunk->address == address | |
442 | && chunk->size == length) { | |
443 | /* an exact match */ | |
444 | if (chunk->available) | |
445 | device_error(me, "memory chunk 0x%lx (size 0x%lx) already available", | |
446 | (unsigned long)address, | |
447 | (unsigned long)length); | |
448 | else { | |
449 | /* free this chunk */ | |
450 | DTRACE(memory, ("release - address=0x%lx, length=0x%lx\n", | |
451 | (unsigned long) address, | |
452 | (unsigned long) length)); | |
453 | chunk->available = 1; | |
454 | break; | |
455 | } | |
456 | } | |
457 | else if (chunk->address >= address | |
458 | && chunk->address + chunk->size <= address + length) { | |
459 | /* a sub region */ | |
460 | if (!chunk->available) { | |
461 | DTRACE(memory, ("release - address=0x%lx, size=0x%lx within region 0x%lx length 0x%lx\n", | |
462 | (unsigned long) chunk->address, | |
463 | (unsigned long) chunk->size, | |
464 | (unsigned long) address, | |
465 | (unsigned long) length)); | |
466 | chunk->available = 1; | |
467 | } | |
468 | } | |
469 | chunk = chunk->next; | |
470 | } | |
471 | if (chunk == NULL) { | |
472 | printf_filtered("warning: released chunks within region 0x%lx..0x%lx\n", | |
473 | (unsigned long)address, | |
474 | (unsigned long)(address + length - 1)); | |
475 | } | |
476 | ||
477 | /* check for the chance to merge two adjacent available memory chunks */ | |
478 | chunk = hw_memory->heap; | |
479 | while (chunk != NULL) { | |
480 | if (chunk->available | |
481 | && chunk->next != NULL && chunk->next->available) { | |
482 | /* adjacent */ | |
483 | hw_memory_chunk *delete = chunk->next; | |
484 | ASSERT(chunk->address + chunk->size == delete->address); | |
485 | chunk->size += delete->size; | |
486 | chunk->next = delete->next; | |
d79fe0d6 | 487 | free(delete); |
c906108c SS |
488 | } |
489 | else { | |
490 | chunk = chunk->next; | |
491 | } | |
492 | } | |
493 | ||
494 | /* update the corresponding property */ | |
495 | hw_memory_set_available(device_instance_device(instance), hw_memory); | |
496 | ||
497 | return 0; | |
498 | } | |
499 | ||
500 | ||
501 | static device_instance_methods hw_memory_instance_methods[] = { | |
502 | { "claim", hw_memory_instance_claim }, | |
503 | { "release", hw_memory_instance_release }, | |
504 | { NULL, }, | |
505 | }; | |
506 | ||
507 | static device_instance_callbacks const hw_memory_instance_callbacks = { | |
508 | hw_memory_instance_delete, | |
509 | NULL /*read*/, NULL /*write*/, NULL /*seek*/, | |
510 | hw_memory_instance_methods | |
511 | }; | |
512 | ||
513 | static device_instance * | |
514 | hw_memory_create_instance(device *me, | |
515 | const char *path, | |
516 | const char *args) | |
517 | { | |
518 | return device_create_instance_from(me, NULL, | |
519 | device_data(me), /* nothing better */ | |
520 | path, args, | |
521 | &hw_memory_instance_callbacks); | |
522 | } | |
523 | ||
524 | static device_callbacks const hw_memory_callbacks = { | |
525 | { hw_memory_init_address, }, | |
526 | { NULL, }, /* address */ | |
527 | { NULL, }, /* IO */ | |
528 | { NULL, }, /* DMA */ | |
529 | { NULL, }, /* interrupt */ | |
530 | { NULL, }, /* unit */ | |
531 | hw_memory_create_instance, | |
532 | }; | |
533 | ||
534 | const device_descriptor hw_memory_device_descriptor[] = { | |
535 | { "memory", hw_memory_create, &hw_memory_callbacks }, | |
536 | { NULL }, | |
537 | }; | |
538 | ||
539 | #endif /* _HW_MEMORY_C_ */ |