blob: af4cc50f1a4baf52e288d98567f242aae7092f42 [file] [log] [blame]
/* The common simulator framework for GDB, the GNU Debugger.
Copyright 2002-2021 Free Software Foundation, Inc.
Contributed by Andrew Cagney and Red Hat.
This file is part of GDB.
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/>. */
/* This must come before any other includes. */
#include "defs.h"
#include "hw-main.h"
#include "hw-base.h"
#include <string.h>
#include <stdlib.h>
#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 */
if (buf == NULL)
{
sizeof_buf = 1024;
buf = hw_malloc (leaf, sizeof_buf);
}
/* 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 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 */
free (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);
reg_entry++)
{
unsigned_word attach_address;
int attach_space;
unsigned attach_size;
if (!hw_unit_address_to_attach_address (hw_parent (hw),
&reg.address,
&attach_space,
&attach_address,
hw))
continue;
if (!hw_unit_size_to_attach_size (hw_parent (hw),
&reg.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;
}
}
}