| /* This file is part of the program psim. |
| |
| Copyright (C) 1994-1997, Andrew Cagney <cagney@highland.com.au> |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, see <http://www.gnu.org/licenses/>. |
| |
| */ |
| |
| |
| #ifndef _HW_MEMORY_C_ |
| #define _HW_MEMORY_C_ |
| |
| #ifndef STATIC_INLINE_HW_MEMORY |
| #define STATIC_INLINE_HW_MEMORY STATIC_INLINE |
| #endif |
| |
| #include <stdlib.h> |
| |
| #include "device_table.h" |
| |
| /* DEVICE |
| |
| |
| memory - description of system memory |
| |
| |
| DESCRIPTION |
| |
| |
| This device describes the size and location of the banks of |
| physical memory within the simulation. |
| |
| In addition, this device supports the "claim" and "release" methods |
| that can be used by OpenBoot client programs to manage the |
| allocation of physical memory. |
| |
| |
| PROPERTIES |
| |
| |
| reg = { <address> <size> } (required) |
| |
| Each pair specify one bank of memory. |
| |
| available = { <address> <size> } (automatic) |
| |
| Each pair specifies a block of memory that is currently unallocated. |
| |
| |
| BUGS |
| |
| |
| OpenFirmware doesn't make it clear if, when releasing memory the |
| same address + size pair as was used during the claim should be |
| specified. |
| |
| It is assumed that #size-cells and #address-cells for the parent |
| node of this device are both one i.e. an address or size can be |
| specified using a single memory cell (word). |
| |
| Significant work will be required before the <<memory>> device can |
| support 64bit addresses (#address-cells equal two). |
| |
| */ |
| |
| typedef struct _memory_reg_spec { |
| unsigned_cell base; |
| unsigned_cell size; |
| } memory_reg_spec; |
| |
| typedef struct _hw_memory_chunk hw_memory_chunk; |
| struct _hw_memory_chunk { |
| unsigned_word address; |
| unsigned_word size; |
| int available; |
| hw_memory_chunk *next; |
| }; |
| |
| typedef struct _hw_memory_device { |
| hw_memory_chunk *heap; |
| } hw_memory_device; |
| |
| |
| static void * |
| hw_memory_create(const char *name, |
| const device_unit *unit_address, |
| const char *args) |
| { |
| hw_memory_device *hw_memory = ZALLOC(hw_memory_device); |
| return hw_memory; |
| } |
| |
| |
| static void |
| hw_memory_set_available(device *me, |
| hw_memory_device *hw_memory) |
| { |
| hw_memory_chunk *chunk = NULL; |
| memory_reg_spec *available = NULL; |
| int nr_available = 0; |
| int curr = 0; |
| int sizeof_available = 0; |
| /* determine the nr of available chunks */ |
| chunk = hw_memory->heap; |
| nr_available = 0; |
| while (chunk != NULL) { |
| if (chunk->available) |
| nr_available += 1; |
| ASSERT(chunk->next == NULL |
| || chunk->address < chunk->next->address); |
| ASSERT(chunk->next == NULL |
| || chunk->address + chunk->size == chunk->next->address); |
| chunk = chunk->next; |
| } |
| /* now create the available struct */ |
| ASSERT(nr_available > 0); |
| sizeof_available = sizeof(memory_reg_spec) * nr_available; |
| available = zalloc(sizeof_available); |
| chunk = hw_memory->heap; |
| curr = 0; |
| while (chunk != NULL) { |
| if (chunk->available) { |
| available[curr].base = H2BE_cell(chunk->address); |
| available[curr].size = H2BE_cell(chunk->size); |
| curr += 1; |
| } |
| chunk = chunk->next; |
| } |
| /* update */ |
| device_set_array_property(me, "available", available, sizeof_available); |
| free(available); |
| } |
| |
| |
| static void |
| hw_memory_init_address(device *me) |
| { |
| hw_memory_device *hw_memory = (hw_memory_device*)device_data(me); |
| |
| /* free up any previous structures */ |
| { |
| hw_memory_chunk *curr_chunk = hw_memory->heap; |
| hw_memory->heap = NULL; |
| while (curr_chunk != NULL) { |
| hw_memory_chunk *dead_chunk = curr_chunk; |
| curr_chunk = dead_chunk->next; |
| dead_chunk->next = NULL; |
| free(dead_chunk); |
| } |
| } |
| |
| /* attach memory regions according to the "reg" property */ |
| { |
| int reg_nr; |
| reg_property_spec reg; |
| for (reg_nr = 0; |
| device_find_reg_array_property(me, "reg", reg_nr, ®); |
| reg_nr++) { |
| int i; |
| /* check that the entry meets restrictions */ |
| for (i = 0; i < reg.address.nr_cells - 1; i++) |
| if (reg.address.cells[i] != 0) |
| device_error(me, "Only single celled addresses supported"); |
| for (i = 0; i < reg.size.nr_cells - 1; i++) |
| if (reg.size.cells[i] != 0) |
| device_error(me, "Only single celled sizes supported"); |
| /* attach the range */ |
| device_attach_address(device_parent(me), |
| attach_raw_memory, |
| 0 /*address space*/, |
| reg.address.cells[reg.address.nr_cells - 1], |
| reg.size.cells[reg.size.nr_cells - 1], |
| access_read_write_exec, |
| me); |
| } |
| } |
| |
| /* create the initial `available memory' data structure */ |
| if (device_find_property(me, "available") != NULL) { |
| hw_memory_chunk **curr_chunk = &hw_memory->heap; |
| int cell_nr; |
| signed_cell dummy; |
| int nr_cells = device_find_integer_array_property(me, "available", 0, &dummy); |
| if ((nr_cells % 2) != 0) |
| device_error(me, "property \"available\" invalid - contains an odd number of cells"); |
| for (cell_nr = 0; |
| cell_nr < nr_cells; |
| cell_nr += 2) { |
| hw_memory_chunk *new_chunk = ZALLOC(hw_memory_chunk); |
| device_find_integer_array_property(me, "available", cell_nr, |
| (signed_cell *)&new_chunk->address); |
| device_find_integer_array_property(me, "available", cell_nr + 1, |
| (signed_cell *)&new_chunk->size); |
| new_chunk->available = 1; |
| *curr_chunk = new_chunk; |
| curr_chunk = &new_chunk->next; |
| } |
| } |
| else { |
| hw_memory_chunk **curr_chunk = &hw_memory->heap; |
| int reg_nr; |
| reg_property_spec reg; |
| for (reg_nr = 0; |
| device_find_reg_array_property(me, "reg", reg_nr, ®); |
| reg_nr++) { |
| hw_memory_chunk *new_chunk; |
| new_chunk = ZALLOC(hw_memory_chunk); |
| new_chunk->address = reg.address.cells[reg.address.nr_cells - 1]; |
| new_chunk->size = reg.size.cells[reg.size.nr_cells - 1]; |
| new_chunk->available = 1; |
| *curr_chunk = new_chunk; |
| curr_chunk = &new_chunk->next; |
| } |
| } |
| |
| /* initialize the alloc property for this device */ |
| hw_memory_set_available(me, hw_memory); |
| } |
| |
| static void |
| hw_memory_instance_delete(device_instance *instance) |
| { |
| return; |
| } |
| |
| static int |
| hw_memory_instance_claim(device_instance *instance, |
| int n_stack_args, |
| unsigned_cell stack_args[/*n_stack_args*/], |
| int n_stack_returns, |
| unsigned_cell stack_returns[/*n_stack_returns*/]) |
| { |
| hw_memory_device *hw_memory = device_instance_data(instance); |
| device *me = device_instance_device(instance); |
| int stackp = 0; |
| unsigned_word alignment; |
| unsigned_cell size; |
| unsigned_cell address; |
| hw_memory_chunk *chunk = NULL; |
| |
| /* get the alignment from the stack */ |
| if (n_stack_args < stackp + 1) |
| device_error(me, "claim - incorrect number of arguments (alignment missing)"); |
| alignment = stack_args[stackp]; |
| stackp++; |
| |
| /* get the size from the stack */ |
| { |
| int i; |
| int nr_cells = device_nr_size_cells(device_parent(me)); |
| if (n_stack_args < stackp + nr_cells) |
| device_error(me, "claim - incorrect number of arguments (size missing)"); |
| for (i = 0; i < nr_cells - 1; i++) { |
| if (stack_args[stackp] != 0) |
| device_error(me, "claim - multi-cell sizes not supported"); |
| stackp++; |
| } |
| size = stack_args[stackp]; |
| stackp++; |
| } |
| |
| /* get the address from the stack */ |
| { |
| int nr_cells = device_nr_address_cells(device_parent(me)); |
| if (alignment != 0) { |
| if (n_stack_args != stackp) { |
| if (n_stack_args == stackp + nr_cells) |
| DTRACE(memory, ("claim - extra address argument ignored\n")); |
| else |
| device_error(me, "claim - incorrect number of arguments (optional addr)"); |
| } |
| address = 0; |
| } |
| else { |
| int i; |
| if (n_stack_args != stackp + nr_cells) |
| device_error(me, "claim - incorrect number of arguments (addr missing)"); |
| for (i = 0; i < nr_cells - 1; i++) { |
| if (stack_args[stackp] != 0) |
| device_error(me, "claim - multi-cell addresses not supported"); |
| stackp++; |
| } |
| address = stack_args[stackp]; |
| } |
| } |
| |
| /* check that there is space for the result */ |
| if (n_stack_returns != 0 |
| && n_stack_returns != device_nr_address_cells(device_parent(me))) |
| device_error(me, "claim - invalid number of return arguments"); |
| |
| /* find a chunk candidate, either according to address or alignment */ |
| if (alignment == 0) { |
| chunk = hw_memory->heap; |
| while (chunk != NULL) { |
| if ((address + size) <= (chunk->address + chunk->size)) |
| break; |
| chunk = chunk->next; |
| } |
| if (chunk == NULL || address < chunk->address || !chunk->available) |
| device_error(me, "failed to allocate %ld bytes at 0x%lx", |
| (unsigned long)size, (unsigned long)address); |
| DTRACE(memory, ("claim - address=0x%lx size=0x%lx\n", |
| (unsigned long)address, |
| (unsigned long)size)); |
| } |
| else { |
| /* adjust the alignment so that it is a power of two */ |
| unsigned_word align_mask = 1; |
| while (align_mask < alignment && align_mask != 0) |
| align_mask <<= 1; |
| if (align_mask == 0) |
| device_error(me, "alignment 0x%lx is to large", (unsigned long)alignment); |
| align_mask -= 1; |
| /* now find an aligned chunk that fits */ |
| chunk = hw_memory->heap; |
| while (chunk != NULL) { |
| address = ((chunk->address + align_mask) & ~align_mask); |
| if ((chunk->available) |
| && (chunk->address + chunk->size >= address + size)) |
| break; |
| chunk = chunk->next; |
| } |
| if (chunk == NULL) |
| device_error(me, "failed to allocate %ld bytes with alignment %ld", |
| (unsigned long)size, (unsigned long)alignment); |
| DTRACE(memory, ("claim - size=0x%lx alignment=%ld (0x%lx), address=0x%lx\n", |
| (unsigned long)size, |
| (unsigned long)alignment, |
| (unsigned long)alignment, |
| (unsigned long)address)); |
| } |
| |
| /* break off a bit before this chunk if needed */ |
| ASSERT(address >= chunk->address); |
| if (address > chunk->address) { |
| hw_memory_chunk *next_chunk = ZALLOC(hw_memory_chunk); |
| /* insert a new chunk */ |
| next_chunk->next = chunk->next; |
| chunk->next = next_chunk; |
| /* adjust the address/size */ |
| next_chunk->address = address; |
| next_chunk->size = chunk->address + chunk->size - next_chunk->address; |
| next_chunk->available = 1; |
| chunk->size = next_chunk->address - chunk->address; |
| /* make this new chunk the one to allocate */ |
| chunk = next_chunk; |
| } |
| ASSERT(address == chunk->address); |
| |
| /* break off a bit after this chunk if needed */ |
| ASSERT(address + size <= chunk->address + chunk->size); |
| if (address + size < chunk->address + chunk->size) { |
| hw_memory_chunk *next_chunk = ZALLOC(hw_memory_chunk); |
| /* insert it in to the list */ |
| next_chunk->next = chunk->next; |
| chunk->next = next_chunk; |
| /* adjust the address/size */ |
| next_chunk->address = address + size; |
| next_chunk->size = chunk->address + chunk->size - next_chunk->address; |
| next_chunk->available = 1; |
| chunk->size = next_chunk->address - chunk->address; |
| } |
| ASSERT(address + size == chunk->address + chunk->size); |
| |
| /* now allocate/return it */ |
| chunk->available = 0; |
| hw_memory_set_available(device_instance_device(instance), hw_memory); |
| if (n_stack_returns > 0) { |
| int i; |
| for (i = 0; i < n_stack_returns - 1; i++) |
| stack_returns[i] = 0; |
| stack_returns[n_stack_returns - 1] = address; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int |
| hw_memory_instance_release(device_instance *instance, |
| int n_stack_args, |
| unsigned_cell stack_args[/*n_stack_args*/], |
| int n_stack_returns, |
| unsigned_cell stack_returns[/*n_stack_returns*/]) |
| { |
| hw_memory_device *hw_memory = device_instance_data(instance); |
| device *me = device_instance_device(instance); |
| unsigned_word length; |
| unsigned_word address; |
| int stackp = 0; |
| hw_memory_chunk *chunk; |
| |
| /* get the length from the stack */ |
| { |
| int i; |
| int nr_cells = device_nr_size_cells(device_parent(me)); |
| if (n_stack_args < stackp + nr_cells) |
| device_error(me, "release - incorrect number of arguments (length missing)"); |
| for (i = 0; i < nr_cells - 1; i++) { |
| if (stack_args[stackp] != 0) |
| device_error(me, "release - multi-cell length not supported"); |
| stackp++; |
| } |
| length = stack_args[stackp]; |
| stackp++; |
| } |
| |
| /* get the address from the stack */ |
| { |
| int i; |
| int nr_cells = device_nr_address_cells(device_parent(me)); |
| if (n_stack_args != stackp + nr_cells) |
| device_error(me, "release - incorrect number of arguments (addr missing)"); |
| for (i = 0; i < nr_cells - 1; i++) { |
| if (stack_args[stackp] != 0) |
| device_error(me, "release - multi-cell addresses not supported"); |
| stackp++; |
| } |
| address = stack_args[stackp]; |
| } |
| |
| /* returns ok */ |
| if (n_stack_returns != 0) |
| device_error(me, "release - nonzero number of results"); |
| |
| /* try to free the corresponding memory chunk */ |
| chunk = hw_memory->heap; |
| while (chunk != NULL) { |
| if (chunk->address == address |
| && chunk->size == length) { |
| /* an exact match */ |
| if (chunk->available) |
| device_error(me, "memory chunk 0x%lx (size 0x%lx) already available", |
| (unsigned long)address, |
| (unsigned long)length); |
| else { |
| /* free this chunk */ |
| DTRACE(memory, ("release - address=0x%lx, length=0x%lx\n", |
| (unsigned long) address, |
| (unsigned long) length)); |
| chunk->available = 1; |
| break; |
| } |
| } |
| else if (chunk->address >= address |
| && chunk->address + chunk->size <= address + length) { |
| /* a sub region */ |
| if (!chunk->available) { |
| DTRACE(memory, ("release - address=0x%lx, size=0x%lx within region 0x%lx length 0x%lx\n", |
| (unsigned long) chunk->address, |
| (unsigned long) chunk->size, |
| (unsigned long) address, |
| (unsigned long) length)); |
| chunk->available = 1; |
| } |
| } |
| chunk = chunk->next; |
| } |
| if (chunk == NULL) { |
| printf_filtered("warning: released chunks within region 0x%lx..0x%lx\n", |
| (unsigned long)address, |
| (unsigned long)(address + length - 1)); |
| } |
| |
| /* check for the chance to merge two adjacent available memory chunks */ |
| chunk = hw_memory->heap; |
| while (chunk != NULL) { |
| if (chunk->available |
| && chunk->next != NULL && chunk->next->available) { |
| /* adjacent */ |
| hw_memory_chunk *delete = chunk->next; |
| ASSERT(chunk->address + chunk->size == delete->address); |
| chunk->size += delete->size; |
| chunk->next = delete->next; |
| free(delete); |
| } |
| else { |
| chunk = chunk->next; |
| } |
| } |
| |
| /* update the corresponding property */ |
| hw_memory_set_available(device_instance_device(instance), hw_memory); |
| |
| return 0; |
| } |
| |
| |
| static device_instance_methods hw_memory_instance_methods[] = { |
| { "claim", hw_memory_instance_claim }, |
| { "release", hw_memory_instance_release }, |
| { NULL, }, |
| }; |
| |
| static device_instance_callbacks const hw_memory_instance_callbacks = { |
| hw_memory_instance_delete, |
| NULL /*read*/, NULL /*write*/, NULL /*seek*/, |
| hw_memory_instance_methods |
| }; |
| |
| static device_instance * |
| hw_memory_create_instance(device *me, |
| const char *path, |
| const char *args) |
| { |
| return device_create_instance_from(me, NULL, |
| device_data(me), /* nothing better */ |
| path, args, |
| &hw_memory_instance_callbacks); |
| } |
| |
| static device_callbacks const hw_memory_callbacks = { |
| { hw_memory_init_address, }, |
| { NULL, }, /* address */ |
| { NULL, }, /* IO */ |
| { NULL, }, /* DMA */ |
| { NULL, }, /* interrupt */ |
| { NULL, }, /* unit */ |
| hw_memory_create_instance, |
| }; |
| |
| const device_descriptor hw_memory_device_descriptor[] = { |
| { "memory", hw_memory_create, &hw_memory_callbacks }, |
| { NULL }, |
| }; |
| |
| #endif /* _HW_MEMORY_C_ */ |