| /*  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 | 
 |  | 
 | /* This must come before any other includes.  */ | 
 | #include "defs.h" | 
 |  | 
 | #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_ */ |