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