577 lines
13 KiB
C
577 lines
13 KiB
C
/* This file is part of the program psim.
|
|
|
|
Copyright (C) 1994-1996, 1998, 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 2 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, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
|
|
#include "hw-main.h"
|
|
#include "hw-base.h"
|
|
|
|
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#else
|
|
#ifdef HAVE_STRINGS_H
|
|
#include <strings.h>
|
|
#endif
|
|
#endif
|
|
|
|
#if HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "hw-config.h"
|
|
|
|
struct hw_base_data {
|
|
int finished_p;
|
|
const struct hw_descriptor *descriptor;
|
|
hw_delete_callback *to_delete;
|
|
};
|
|
|
|
static int
|
|
generic_hw_unit_decode (struct hw *bus,
|
|
const char *unit,
|
|
hw_unit *phys)
|
|
{
|
|
memset (phys, 0, sizeof (*phys));
|
|
if (unit == NULL)
|
|
return 0;
|
|
else
|
|
{
|
|
int nr_cells = 0;
|
|
const int max_nr_cells = hw_unit_nr_address_cells (bus);
|
|
while (1)
|
|
{
|
|
char *end = NULL;
|
|
unsigned long val;
|
|
val = strtoul (unit, &end, 0);
|
|
/* parse error? */
|
|
if (unit == end)
|
|
return -1;
|
|
/* two many cells? */
|
|
if (nr_cells >= max_nr_cells)
|
|
return -1;
|
|
/* save it */
|
|
phys->cells[nr_cells] = val;
|
|
nr_cells++;
|
|
unit = end;
|
|
/* more to follow? */
|
|
if (isspace (*unit) || *unit == '\0')
|
|
break;
|
|
if (*unit != ',')
|
|
return -1;
|
|
unit++;
|
|
}
|
|
if (nr_cells < max_nr_cells) {
|
|
/* shift everything to correct position */
|
|
int i;
|
|
for (i = 1; i <= nr_cells; i++)
|
|
phys->cells[max_nr_cells - i] = phys->cells[nr_cells - i];
|
|
for (i = 0; i < (max_nr_cells - nr_cells); i++)
|
|
phys->cells[i] = 0;
|
|
}
|
|
phys->nr_cells = max_nr_cells;
|
|
return max_nr_cells;
|
|
}
|
|
}
|
|
|
|
static int
|
|
generic_hw_unit_encode (struct hw *bus,
|
|
const hw_unit *phys,
|
|
char *buf,
|
|
int sizeof_buf)
|
|
{
|
|
int i;
|
|
int len;
|
|
char *pos = buf;
|
|
/* skip leading zero's */
|
|
for (i = 0; i < phys->nr_cells; i++)
|
|
{
|
|
if (phys->cells[i] != 0)
|
|
break;
|
|
}
|
|
/* don't output anything if empty */
|
|
if (phys->nr_cells == 0)
|
|
{
|
|
strcpy(pos, "");
|
|
len = 0;
|
|
}
|
|
else if (i == phys->nr_cells)
|
|
{
|
|
/* all zero */
|
|
strcpy(pos, "0");
|
|
len = 1;
|
|
}
|
|
else
|
|
{
|
|
for (; i < phys->nr_cells; i++)
|
|
{
|
|
if (pos != buf) {
|
|
strcat(pos, ",");
|
|
pos = strchr(pos, '\0');
|
|
}
|
|
if (phys->cells[i] < 10)
|
|
sprintf (pos, "%ld", (unsigned long)phys->cells[i]);
|
|
else
|
|
sprintf (pos, "0x%lx", (unsigned long)phys->cells[i]);
|
|
pos = strchr(pos, '\0');
|
|
}
|
|
len = pos - buf;
|
|
}
|
|
if (len >= sizeof_buf)
|
|
hw_abort (NULL, "generic_unit_encode - buffer overflow\n");
|
|
return len;
|
|
}
|
|
|
|
static int
|
|
generic_hw_unit_address_to_attach_address (struct hw *me,
|
|
const hw_unit *address,
|
|
int *attach_space,
|
|
unsigned_word *attach_address,
|
|
struct hw *client)
|
|
{
|
|
int i;
|
|
for (i = 0; i < address->nr_cells - 2; i++)
|
|
{
|
|
if (address->cells[i] != 0)
|
|
hw_abort (me, "Only 32bit addresses supported");
|
|
}
|
|
if (address->nr_cells >= 2)
|
|
*attach_space = address->cells[address->nr_cells - 2];
|
|
else
|
|
*attach_space = 0;
|
|
*attach_address = address->cells[address->nr_cells - 1];
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
generic_hw_unit_size_to_attach_size (struct hw *me,
|
|
const hw_unit *size,
|
|
unsigned *nr_bytes,
|
|
struct hw *client)
|
|
{
|
|
int i;
|
|
for (i = 0; i < size->nr_cells - 1; i++)
|
|
{
|
|
if (size->cells[i] != 0)
|
|
hw_abort (me, "Only 32bit sizes supported");
|
|
}
|
|
*nr_bytes = size->cells[0];
|
|
return *nr_bytes;
|
|
}
|
|
|
|
|
|
/* ignore/passthrough versions of each function */
|
|
|
|
static void
|
|
passthrough_hw_attach_address (struct hw *me,
|
|
int level,
|
|
int space,
|
|
address_word addr,
|
|
address_word nr_bytes,
|
|
struct hw *client) /*callback/default*/
|
|
{
|
|
if (hw_parent (me) == NULL)
|
|
hw_abort (client, "hw_attach_address: no parent attach method");
|
|
hw_attach_address (hw_parent (me), level,
|
|
space, addr, nr_bytes,
|
|
client);
|
|
}
|
|
|
|
static void
|
|
passthrough_hw_detach_address (struct hw *me,
|
|
int level,
|
|
int space,
|
|
address_word addr,
|
|
address_word nr_bytes,
|
|
struct hw *client) /*callback/default*/
|
|
{
|
|
if (hw_parent (me) == NULL)
|
|
hw_abort (client, "hw_attach_address: no parent attach method");
|
|
hw_detach_address (hw_parent (me), level,
|
|
space, addr, nr_bytes,
|
|
client);
|
|
}
|
|
|
|
static unsigned
|
|
panic_hw_io_read_buffer (struct hw *me,
|
|
void *dest,
|
|
int space,
|
|
unsigned_word addr,
|
|
unsigned nr_bytes)
|
|
{
|
|
hw_abort (me, "no io-read method");
|
|
return 0;
|
|
}
|
|
|
|
static unsigned
|
|
panic_hw_io_write_buffer (struct hw *me,
|
|
const void *source,
|
|
int space,
|
|
unsigned_word addr,
|
|
unsigned nr_bytes)
|
|
{
|
|
hw_abort (me, "no io-write method");
|
|
return 0;
|
|
}
|
|
|
|
static unsigned
|
|
passthrough_hw_dma_read_buffer (struct hw *me,
|
|
void *dest,
|
|
int space,
|
|
unsigned_word addr,
|
|
unsigned nr_bytes)
|
|
{
|
|
if (hw_parent (me) == NULL)
|
|
hw_abort (me, "no parent dma-read method");
|
|
return hw_dma_read_buffer (hw_parent (me), dest,
|
|
space, addr, nr_bytes);
|
|
}
|
|
|
|
static unsigned
|
|
passthrough_hw_dma_write_buffer (struct hw *me,
|
|
const void *source,
|
|
int space,
|
|
unsigned_word addr,
|
|
unsigned nr_bytes,
|
|
int violate_read_only_section)
|
|
{
|
|
if (hw_parent (me) == NULL)
|
|
hw_abort (me, "no parent dma-write method");
|
|
return hw_dma_write_buffer (hw_parent (me), source,
|
|
space, addr,
|
|
nr_bytes,
|
|
violate_read_only_section);
|
|
}
|
|
|
|
static void
|
|
ignore_hw_delete (struct hw *me)
|
|
{
|
|
/* NOP */
|
|
}
|
|
|
|
|
|
|
|
|
|
static const char *
|
|
full_name_of_hw (struct hw *leaf,
|
|
char *buf,
|
|
unsigned sizeof_buf)
|
|
{
|
|
/* get a buffer */
|
|
char full_name[1024];
|
|
if (buf == (char*)0)
|
|
{
|
|
buf = full_name;
|
|
sizeof_buf = sizeof (full_name);
|
|
}
|
|
|
|
/* use head recursion to construct the path */
|
|
|
|
if (hw_parent (leaf) == NULL)
|
|
/* root */
|
|
{
|
|
if (sizeof_buf < 1)
|
|
hw_abort (leaf, "buffer overflow");
|
|
*buf = '\0';
|
|
}
|
|
else
|
|
/* sub node */
|
|
{
|
|
char unit[1024];
|
|
full_name_of_hw (hw_parent (leaf), buf, sizeof_buf);
|
|
if (hw_unit_encode (hw_parent (leaf),
|
|
hw_unit_address (leaf),
|
|
unit + 1,
|
|
sizeof (unit) - 1)
|
|
> 0)
|
|
unit[0] = '@';
|
|
else
|
|
unit[0] = '\0';
|
|
if (strlen (buf) + strlen ("/") + strlen (hw_name (leaf)) + strlen (unit)
|
|
>= sizeof_buf)
|
|
hw_abort (leaf, "buffer overflow");
|
|
strcat (buf, "/");
|
|
strcat (buf, hw_name (leaf));
|
|
strcat (buf, unit);
|
|
}
|
|
|
|
/* return it usefully */
|
|
if (buf == full_name)
|
|
buf = hw_strdup (leaf, full_name);
|
|
return buf;
|
|
}
|
|
|
|
struct hw *
|
|
hw_create (struct sim_state *sd,
|
|
struct hw *parent,
|
|
const char *family,
|
|
const char *name,
|
|
const char *unit,
|
|
const char *args)
|
|
{
|
|
/* NOTE: HW must be allocated using ZALLOC, others use HW_ZALLOC */
|
|
struct hw *hw = ZALLOC (struct hw);
|
|
|
|
/* our identity */
|
|
hw->family_of_hw = hw_strdup (hw, family);
|
|
hw->name_of_hw = hw_strdup (hw, name);
|
|
hw->args_of_hw = hw_strdup (hw, args);
|
|
|
|
/* a hook into the system */
|
|
if (sd != NULL)
|
|
hw->system_of_hw = sd;
|
|
else if (parent != NULL)
|
|
hw->system_of_hw = hw_system (parent);
|
|
else
|
|
hw_abort (parent, "No system found");
|
|
|
|
/* in a tree */
|
|
if (parent != NULL)
|
|
{
|
|
struct hw **sibling = &parent->child_of_hw;
|
|
while ((*sibling) != NULL)
|
|
sibling = &(*sibling)->sibling_of_hw;
|
|
*sibling = hw;
|
|
hw->parent_of_hw = parent;
|
|
}
|
|
|
|
/* top of tree */
|
|
if (parent != NULL)
|
|
{
|
|
struct hw *root = parent;
|
|
while (root->parent_of_hw != NULL)
|
|
root = root->parent_of_hw;
|
|
hw->root_of_hw = root;
|
|
}
|
|
|
|
/* a unique identifier for the device on the parents bus */
|
|
if (parent != NULL)
|
|
{
|
|
hw_unit_decode (parent, unit, &hw->unit_address_of_hw);
|
|
}
|
|
|
|
/* Determine our path */
|
|
if (parent != NULL)
|
|
hw->path_of_hw = full_name_of_hw (hw, NULL, 0);
|
|
else
|
|
hw->path_of_hw = "/";
|
|
|
|
/* create our base type */
|
|
hw->base_of_hw = HW_ZALLOC (hw, struct hw_base_data);
|
|
hw->base_of_hw->finished_p = 0;
|
|
|
|
/* our callbacks */
|
|
set_hw_io_read_buffer (hw, panic_hw_io_read_buffer);
|
|
set_hw_io_write_buffer (hw, panic_hw_io_write_buffer);
|
|
set_hw_dma_read_buffer (hw, passthrough_hw_dma_read_buffer);
|
|
set_hw_dma_write_buffer (hw, passthrough_hw_dma_write_buffer);
|
|
set_hw_unit_decode (hw, generic_hw_unit_decode);
|
|
set_hw_unit_encode (hw, generic_hw_unit_encode);
|
|
set_hw_unit_address_to_attach_address (hw, generic_hw_unit_address_to_attach_address);
|
|
set_hw_unit_size_to_attach_size (hw, generic_hw_unit_size_to_attach_size);
|
|
set_hw_attach_address (hw, passthrough_hw_attach_address);
|
|
set_hw_detach_address (hw, passthrough_hw_detach_address);
|
|
set_hw_delete (hw, ignore_hw_delete);
|
|
|
|
/* locate a descriptor */
|
|
{
|
|
const struct hw_descriptor **table;
|
|
for (table = hw_descriptors;
|
|
*table != NULL;
|
|
table++)
|
|
{
|
|
const struct hw_descriptor *entry;
|
|
for (entry = *table;
|
|
entry->family != NULL;
|
|
entry++)
|
|
{
|
|
if (strcmp (family, entry->family) == 0)
|
|
{
|
|
hw->base_of_hw->descriptor = entry;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (hw->base_of_hw->descriptor == NULL)
|
|
{
|
|
hw_abort (parent, "Unknown device `%s'", family);
|
|
}
|
|
}
|
|
|
|
/* Attach dummy ports */
|
|
create_hw_alloc_data (hw);
|
|
create_hw_property_data (hw);
|
|
create_hw_port_data (hw);
|
|
create_hw_event_data (hw);
|
|
create_hw_handle_data (hw);
|
|
create_hw_instance_data (hw);
|
|
|
|
return hw;
|
|
}
|
|
|
|
|
|
int
|
|
hw_finished_p (struct hw *me)
|
|
{
|
|
return (me->base_of_hw->finished_p);
|
|
}
|
|
|
|
void
|
|
hw_finish (struct hw *me)
|
|
{
|
|
if (hw_finished_p (me))
|
|
hw_abort (me, "Attempt to finish finished device");
|
|
|
|
/* Fill in the (hopefully) defined address/size cells values */
|
|
if (hw_find_property (me, "#address-cells") != NULL)
|
|
me->nr_address_cells_of_hw_unit =
|
|
hw_find_integer_property (me, "#address-cells");
|
|
else
|
|
me->nr_address_cells_of_hw_unit = 2;
|
|
if (hw_find_property (me, "#size-cells") != NULL)
|
|
me->nr_size_cells_of_hw_unit =
|
|
hw_find_integer_property (me, "#size-cells");
|
|
else
|
|
me->nr_size_cells_of_hw_unit = 1;
|
|
|
|
/* Fill in the (hopefully) defined trace variable */
|
|
if (hw_find_property (me, "trace?") != NULL)
|
|
me->trace_of_hw_p = hw_find_boolean_property (me, "trace?");
|
|
/* allow global variable to define default tracing */
|
|
else if (! hw_trace_p (me)
|
|
&& hw_find_property (hw_root (me), "global-trace?") != NULL
|
|
&& hw_find_boolean_property (hw_root (me), "global-trace?"))
|
|
me->trace_of_hw_p = 1;
|
|
|
|
|
|
/* Allow the real device to override any methods */
|
|
me->base_of_hw->descriptor->to_finish (me);
|
|
me->base_of_hw->finished_p = 1;
|
|
}
|
|
|
|
|
|
void
|
|
hw_delete (struct hw *me)
|
|
{
|
|
/* give the object a chance to tidy up */
|
|
me->base_of_hw->to_delete (me);
|
|
|
|
delete_hw_instance_data (me);
|
|
delete_hw_handle_data (me);
|
|
delete_hw_event_data (me);
|
|
delete_hw_port_data (me);
|
|
delete_hw_property_data (me);
|
|
|
|
/* now unlink us from the tree */
|
|
if (hw_parent (me))
|
|
{
|
|
struct hw **sibling = &hw_parent (me)->child_of_hw;
|
|
while (*sibling != NULL)
|
|
{
|
|
if (*sibling == me)
|
|
{
|
|
*sibling = me->sibling_of_hw;
|
|
me->sibling_of_hw = NULL;
|
|
me->parent_of_hw = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* some sanity checks */
|
|
if (hw_child (me) != NULL)
|
|
{
|
|
hw_abort (me, "attempt to delete device with children");
|
|
}
|
|
if (hw_sibling (me) != NULL)
|
|
{
|
|
hw_abort (me, "attempt to delete device with siblings");
|
|
}
|
|
|
|
/* blow away all memory belonging to the device */
|
|
delete_hw_alloc_data (me);
|
|
|
|
/* finally */
|
|
zfree (me);
|
|
}
|
|
|
|
void
|
|
set_hw_delete (struct hw *hw, hw_delete_callback method)
|
|
{
|
|
hw->base_of_hw->to_delete = method;
|
|
}
|
|
|
|
|
|
/* Go through the devices various reg properties for those that
|
|
specify attach addresses */
|
|
|
|
|
|
void
|
|
do_hw_attach_regs (struct hw *hw)
|
|
{
|
|
static const char *(reg_property_names[]) = {
|
|
"attach-addresses",
|
|
"assigned-addresses",
|
|
"reg",
|
|
"alternate-reg" ,
|
|
NULL
|
|
};
|
|
const char **reg_property_name;
|
|
int nr_valid_reg_properties = 0;
|
|
for (reg_property_name = reg_property_names;
|
|
*reg_property_name != NULL;
|
|
reg_property_name++)
|
|
{
|
|
if (hw_find_property (hw, *reg_property_name) != NULL)
|
|
{
|
|
reg_property_spec reg;
|
|
int reg_entry;
|
|
for (reg_entry = 0;
|
|
hw_find_reg_array_property (hw, *reg_property_name, reg_entry,
|
|
®);
|
|
reg_entry++)
|
|
{
|
|
unsigned_word attach_address;
|
|
int attach_space;
|
|
unsigned attach_size;
|
|
if (!hw_unit_address_to_attach_address (hw_parent (hw),
|
|
®.address,
|
|
&attach_space,
|
|
&attach_address,
|
|
hw))
|
|
continue;
|
|
if (!hw_unit_size_to_attach_size (hw_parent (hw),
|
|
®.size,
|
|
&attach_size, hw))
|
|
continue;
|
|
hw_attach_address (hw_parent (hw),
|
|
0,
|
|
attach_space, attach_address, attach_size,
|
|
hw);
|
|
nr_valid_reg_properties++;
|
|
}
|
|
/* if first option matches don't try for any others */
|
|
if (reg_property_name == reg_property_names)
|
|
break;
|
|
}
|
|
}
|
|
}
|