blob: 9a3db171d1b3d884d3f6339556a83243ebe0aa54 [file] [log] [blame]
/* Simulator hardware option handling.
Copyright (C) 1998-2021 Free Software Foundation, Inc.
Contributed by Cygnus Support and Andrew Cagney.
This file is part of GDB, the GNU debugger.
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 "sim-main.h"
#include "sim-assert.h"
#include "sim-options.h"
#include "sim/callback.h"
#include "sim-hw.h"
#include "hw-tree.h"
#include "hw-device.h"
#include "hw-main.h"
#include "hw-base.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
struct sim_hw {
struct hw *tree;
int trace_p;
int info_p;
/* if called from a processor */
sim_cpu *cpu;
sim_cia cia;
};
struct hw *
sim_hw_parse (struct sim_state *sd,
const char *fmt,
...)
{
struct hw *current;
va_list ap;
va_start (ap, fmt);
current = hw_tree_vparse (STATE_HW (sd)->tree, fmt, ap);
va_end (ap);
return current;
}
struct printer {
struct sim_state *file;
void (*print) (struct sim_state *, const char *, va_list ap);
};
static void
do_print (void *file, const char *fmt, ...)
{
struct printer *p = file;
va_list ap;
va_start (ap, fmt);
p->print (p->file, fmt, ap);
va_end (ap);
}
void
sim_hw_print (struct sim_state *sd,
void (*print) (struct sim_state *, const char *, va_list ap))
{
struct printer p;
p.file = sd;
p.print = print;
hw_tree_print (STATE_HW (sd)->tree, do_print, &p);
}
/* command line options. */
enum {
OPTION_HW_INFO = OPTION_START,
OPTION_HW_TRACE,
OPTION_HW_DEVICE,
OPTION_HW_LIST,
OPTION_HW_FILE,
};
static DECLARE_OPTION_HANDLER (hw_option_handler);
static const OPTION hw_options[] =
{
{ {"hw-info", no_argument, NULL, OPTION_HW_INFO },
'\0', NULL, "List configurable hw regions",
hw_option_handler, NULL },
{ {"info-hw", no_argument, NULL, OPTION_HW_INFO },
'\0', NULL, NULL,
hw_option_handler, NULL },
{ {"hw-trace", optional_argument, NULL, OPTION_HW_TRACE },
'\0', "on|off", "Trace all hardware devices",
hw_option_handler, NULL },
{ {"trace-hw", optional_argument, NULL, OPTION_HW_TRACE },
'\0', NULL, NULL,
hw_option_handler, NULL },
{ {"hw-device", required_argument, NULL, OPTION_HW_DEVICE },
'\0', "DEVICE", "Add the specified device",
hw_option_handler, NULL },
{ {"hw-list", no_argument, NULL, OPTION_HW_LIST },
'\0', NULL, "List the device tree",
hw_option_handler, NULL },
{ {"hw-file", required_argument, NULL, OPTION_HW_FILE },
'\0', "FILE", "Add the devices listed in the file",
hw_option_handler, NULL },
{ {NULL, no_argument, NULL, 0}, '\0', NULL, NULL, NULL, NULL }
};
/* Copied from ../ppc/psim.c:psim_merge_device_file() */
static SIM_RC
merge_device_file (struct sim_state *sd,
const char *file_name)
{
FILE *description;
struct hw *current = STATE_HW (sd)->tree;
char *device_path = NULL;
size_t buf_size = 0;
ssize_t device_path_len;
/* try opening the file */
description = fopen (file_name, "r");
if (description == NULL)
{
perror (file_name);
return SIM_RC_FAIL;
}
while ((device_path_len = getline (&device_path, &buf_size, description)) > 0)
{
char *device;
char *next_line = NULL;
if (device_path[device_path_len - 1] == '\n')
device_path[--device_path_len] = '\0';
/* skip comments ("#" or ";") and blank lines lines */
for (device = device_path;
*device != '\0' && isspace (*device);
device++);
if (device[0] == '#'
|| device[0] == ';'
|| device[0] == '\0')
continue;
/* merge any appended lines */
while (device_path[device_path_len - 1] == '\\')
{
size_t next_buf_size = 0;
ssize_t next_line_len;
/* zap the `\' at the end of the line */
device_path[--device_path_len] = '\0';
/* get the next line */
next_line_len = getline (&next_line, &next_buf_size, description);
if (next_line_len <= 0)
break;
if (next_line[next_line_len - 1] == '\n')
next_line[--next_line_len] = '\0';
/* append the next line */
if (buf_size - device_path_len <= next_line_len)
{
ptrdiff_t offset = device - device_path;
buf_size += next_buf_size;
device_path = xrealloc (device_path, buf_size);
device = device_path + offset;
}
memcpy (device_path + device_path_len, next_line,
next_line_len + 1);
device_path_len += next_line_len;
}
free (next_line);
/* parse this line */
current = hw_tree_parse (current, "%s", device);
}
free (device_path);
fclose (description);
return SIM_RC_OK;
}
static SIM_RC
hw_option_handler (struct sim_state *sd, sim_cpu *cpu, int opt,
char *arg, int is_command)
{
switch (opt)
{
case OPTION_HW_INFO:
{
/* delay info until after the tree is finished */
STATE_HW (sd)->info_p = 1;
return SIM_RC_OK;
break;
}
case OPTION_HW_TRACE:
{
if (arg == NULL)
{
STATE_HW (sd)->trace_p = 1;
}
else if (strcmp (arg, "yes") == 0
|| strcmp (arg, "on") == 0)
{
STATE_HW (sd)->trace_p = 1;
}
else if (strcmp (arg, "no") == 0
|| strcmp (arg, "off") == 0)
{
STATE_HW (sd)->trace_p = 0;
}
else
{
sim_io_eprintf (sd, "Option --hw-trace ignored\n");
/* set tracing on all devices */
return SIM_RC_FAIL;
}
/* FIXME: Not very nice - see also hw-base.c */
if (STATE_HW (sd)->trace_p)
hw_tree_parse (STATE_HW (sd)->tree, "/global-trace? true");
return SIM_RC_OK;
break;
}
case OPTION_HW_DEVICE:
{
hw_tree_parse (STATE_HW (sd)->tree, "%s", arg);
return SIM_RC_OK;
}
case OPTION_HW_LIST:
{
sim_hw_print (sd, sim_io_vprintf);
return SIM_RC_OK;
}
case OPTION_HW_FILE:
{
return merge_device_file (sd, arg);
}
default:
sim_io_eprintf (sd, "Unknown hw option %d\n", opt);
return SIM_RC_FAIL;
}
return SIM_RC_FAIL;
}
/* "hw" module install handler.
This is called via sim_module_install to install the "hw" subsystem
into the simulator. */
static MODULE_INIT_FN sim_hw_init;
static MODULE_UNINSTALL_FN sim_hw_uninstall;
/* Provide a prototype to silence -Wmissing-prototypes. */
SIM_RC sim_install_hw (struct sim_state *sd);
/* Establish this object. */
SIM_RC
sim_install_hw (struct sim_state *sd)
{
SIM_ASSERT (STATE_MAGIC (sd) == SIM_MAGIC_NUMBER);
sim_add_option_table (sd, NULL, hw_options);
sim_module_add_uninstall_fn (sd, sim_hw_uninstall);
sim_module_add_init_fn (sd, sim_hw_init);
STATE_HW (sd) = ZALLOC (struct sim_hw);
STATE_HW (sd)->tree = hw_tree_create (sd, "core");
return SIM_RC_OK;
}
static SIM_RC
sim_hw_init (struct sim_state *sd)
{
/* FIXME: anything needed? */
hw_tree_finish (STATE_HW (sd)->tree);
if (STATE_HW (sd)->info_p)
sim_hw_print (sd, sim_io_vprintf);
return SIM_RC_OK;
}
/* Uninstall the "hw" subsystem from the simulator. */
static void
sim_hw_uninstall (struct sim_state *sd)
{
hw_tree_delete (STATE_HW (sd)->tree);
free (STATE_HW (sd));
STATE_HW (sd) = NULL;
}
/* Data transfers to/from the hardware device tree. There are several
cases. */
/* CPU: The simulation is running and the current CPU/CIA
initiates a data transfer. */
void
sim_cpu_hw_io_read_buffer (sim_cpu *cpu,
sim_cia cia,
struct hw *hw,
void *dest,
int space,
unsigned_word addr,
unsigned nr_bytes)
{
SIM_DESC sd = CPU_STATE (cpu);
STATE_HW (sd)->cpu = cpu;
STATE_HW (sd)->cia = cia;
if (hw_io_read_buffer (hw, dest, space, addr, nr_bytes) != nr_bytes)
sim_engine_abort (sd, cpu, cia, "broken CPU read");
}
void
sim_cpu_hw_io_write_buffer (sim_cpu *cpu,
sim_cia cia,
struct hw *hw,
const void *source,
int space,
unsigned_word addr,
unsigned nr_bytes)
{
SIM_DESC sd = CPU_STATE (cpu);
STATE_HW (sd)->cpu = cpu;
STATE_HW (sd)->cia = cia;
if (hw_io_write_buffer (hw, source, space, addr, nr_bytes) != nr_bytes)
sim_engine_abort (sd, cpu, cia, "broken CPU write");
}
/* SYSTEM: A data transfer is being initiated by the system. */
unsigned
sim_hw_io_read_buffer (struct sim_state *sd,
struct hw *hw,
void *dest,
int space,
unsigned_word addr,
unsigned nr_bytes)
{
STATE_HW (sd)->cpu = NULL;
return hw_io_read_buffer (hw, dest, space, addr, nr_bytes);
}
unsigned
sim_hw_io_write_buffer (struct sim_state *sd,
struct hw *hw,
const void *source,
int space,
unsigned_word addr,
unsigned nr_bytes)
{
STATE_HW (sd)->cpu = NULL;
return hw_io_write_buffer (hw, source, space, addr, nr_bytes);
}
/* Abort the simulation specifying HW as the reason */
void
hw_vabort (struct hw *me,
const char *fmt,
va_list ap)
{
const char *name;
char *msg;
/* find an identity */
if (me != NULL && hw_path (me) != NULL && hw_path (me) [0] != '\0')
name = hw_path (me);
else if (me != NULL && hw_name (me) != NULL && hw_name (me)[0] != '\0')
name = hw_name (me);
else if (me != NULL && hw_family (me) != NULL && hw_family (me)[0] != '\0')
name = hw_family (me);
else
name = "device";
/* construct an updated format string */
msg = alloca (strlen (name) + strlen (": ") + strlen (fmt) + 1);
strcpy (msg, name);
strcat (msg, ": ");
strcat (msg, fmt);
/* report the problem */
sim_engine_vabort (hw_system (me),
STATE_HW (hw_system (me))->cpu,
STATE_HW (hw_system (me))->cia,
msg, ap);
}
void
hw_abort (struct hw *me,
const char *fmt,
...)
{
va_list ap;
/* report the problem */
va_start (ap, fmt);
hw_vabort (me, fmt, ap);
va_end (ap);
}
void
sim_hw_abort (struct sim_state *sd,
struct hw *me,
const char *fmt,
...)
{
va_list ap;
va_start (ap, fmt);
if (me == NULL)
sim_engine_vabort (sd, NULL, NULL_CIA, fmt, ap);
else
hw_vabort (me, fmt, ap);
va_end (ap);
}
/* MISC routines to tie HW into the rest of the system */
void
hw_halt (struct hw *me,
int reason,
int status)
{
struct sim_state *sd = hw_system (me);
struct sim_hw *sim = STATE_HW (sd);
sim_engine_halt (sd, sim->cpu, NULL, sim->cia, reason, status);
}
struct _sim_cpu *
hw_system_cpu (struct hw *me)
{
return STATE_HW (hw_system (me))->cpu;
}
void
hw_trace (struct hw *me,
const char *fmt,
...)
{
if (hw_trace_p (me)) /* to be sure, to be sure */
{
va_list ap;
va_start (ap, fmt);
sim_io_eprintf (hw_system (me), "%s: ", hw_path (me));
sim_io_evprintf (hw_system (me), fmt, ap);
sim_io_eprintf (hw_system (me), "\n");
va_end (ap);
}
}
/* Based on gdb-4.17/sim/ppc/main.c:sim_io_read_stdin() */
int
do_hw_poll_read (struct hw *me,
do_hw_poll_read_method *read,
int sim_io_fd,
void *buf,
unsigned sizeof_buf)
{
int status = read (hw_system (me), sim_io_fd, buf, sizeof_buf);
if (status > 0)
return status;
else if (status == 0 && sizeof_buf == 0)
return 0;
else if (status == 0)
return HW_IO_EOF;
else /* status < 0 */
{
#ifdef EAGAIN
if (STATE_CALLBACK (hw_system (me))->last_errno == EAGAIN)
return HW_IO_NOT_READY;
else
return HW_IO_EOF;
#else
return HW_IO_EOF;
#endif
}
}