| /* The common simulator framework for GDB, the GNU Debugger. | |
| Copyright 2002-2022 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 <ctype.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include "hw-main.h" | |
| #include "hw-base.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 * const *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_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; | |
| } | |
| } | |
| } |