mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-23 18:14:13 +08:00
516 lines
10 KiB
C
516 lines
10 KiB
C
/* Test-driver for the remote-virtual-component simulator framework
|
|
for GDB, the GNU Debugger.
|
|
|
|
Copyright 2006-2023 Free Software Foundation, Inc.
|
|
|
|
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/>. */
|
|
|
|
/* Avoid any problems whatsoever building this program if we're not
|
|
also building hardware support. */
|
|
|
|
#if !WITH_HW
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
return 2;
|
|
}
|
|
#else
|
|
|
|
/* This must come before any other includes. */
|
|
#include "defs.h"
|
|
|
|
#include "getopt.h"
|
|
#include "libiberty.h"
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <errno.h>
|
|
|
|
/* Not guarded in dv-sockser.c, so why here. */
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
|
|
enum rv_command {
|
|
RV_READ_CMD = 0,
|
|
RV_WRITE_CMD = 1,
|
|
RV_IRQ_CMD = 2,
|
|
RV_MEM_RD_CMD = 3,
|
|
RV_MEM_WR_CMD = 4,
|
|
RV_MBOX_HANDLE_CMD = 5,
|
|
RV_MBOX_PUT_CMD = 6,
|
|
RV_WATCHDOG_CMD = 7
|
|
};
|
|
|
|
enum opts { OPT_PORT = 1, OPT_TIMEOUT, OPT_VERBOSE };
|
|
|
|
struct option longopts[] =
|
|
{
|
|
{"port", required_argument, NULL, OPT_PORT},
|
|
{"timeout", required_argument, NULL, OPT_TIMEOUT},
|
|
{"verbose", no_argument, NULL, OPT_VERBOSE},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
int port = 10000;
|
|
time_t timeout = 30000;
|
|
char *progname = "(unknown)";
|
|
int verbose = 0;
|
|
|
|
/* Required forward-declarations. */
|
|
static void handle_input_file (int, char *);
|
|
|
|
/* Set up a "server" listening to the port in PORT for a raw TCP
|
|
connection. Return a file descriptor for the connection or -1 on
|
|
error. */
|
|
|
|
static int setupsocket (void)
|
|
{
|
|
int s;
|
|
socklen_t len;
|
|
int reuse = 1;
|
|
struct sockaddr_in sa_in;
|
|
struct sockaddr_in from;
|
|
|
|
len = sizeof (from);
|
|
memset (&from, 0, len);
|
|
memset (&sa_in, 0, sizeof (sa_in));
|
|
|
|
s = socket (AF_INET, SOCK_STREAM, 0);
|
|
if (s == -1)
|
|
return -1;
|
|
|
|
if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof reuse) != 0)
|
|
return -1;
|
|
|
|
sa_in.sin_port = htons (port);
|
|
sa_in.sin_family = AF_INET;
|
|
|
|
if (bind (s, (struct sockaddr *) & sa_in, sizeof sa_in) < 0)
|
|
return -1;
|
|
|
|
if (listen (s, 1) < 0)
|
|
return -1;
|
|
|
|
return accept (s, (struct sockaddr *) &from, &len);
|
|
}
|
|
|
|
/* Basic host-to-little-endian 32-bit value. Could use the BFD
|
|
machinery, but let's avoid it for this only dependency. */
|
|
|
|
static void
|
|
h2le32 (unsigned char *dest, unsigned int val)
|
|
{
|
|
dest[0] = val & 255;
|
|
dest[1] = (val >> 8) & 255;
|
|
dest[2] = (val >> 16) & 255;
|
|
dest[3] = (val >> 24) & 255;
|
|
}
|
|
|
|
/* Send a blob of data. */
|
|
|
|
static void
|
|
send_output (int fd, unsigned char *buf, int nbytes)
|
|
{
|
|
while (nbytes > 0)
|
|
{
|
|
ssize_t written = write (fd, buf, nbytes);
|
|
if (written < 0)
|
|
{
|
|
fprintf (stderr, "%s: write to socket failed: %s\n",
|
|
progname, strerror (errno));
|
|
exit (2);
|
|
}
|
|
nbytes -= written;
|
|
}
|
|
}
|
|
|
|
/* Receive a blob of data, NBYTES large. Compare to the first NCOMP
|
|
bytes of BUF; if not a match, write error message to stderr and
|
|
exit (2). Else put it in buf. */
|
|
|
|
static void
|
|
expect_input (int fd, unsigned char *buf, int nbytes, int ncomp)
|
|
{
|
|
unsigned char byt;
|
|
int i;
|
|
|
|
for (i = 0; i < nbytes; i++)
|
|
{
|
|
int r;
|
|
|
|
do
|
|
{
|
|
errno = 0;
|
|
r = read (fd, &byt, 1);
|
|
}
|
|
while (r <= 0 && (r == 0 || errno == EAGAIN));
|
|
|
|
if (r != 1)
|
|
{
|
|
fprintf (stderr, "%s: read from socket failed: %s",
|
|
progname, strerror (errno));
|
|
exit (2);
|
|
}
|
|
|
|
if (i < ncomp && byt != buf[i])
|
|
{
|
|
int j;
|
|
fprintf (stderr, "%s: unexpected input,\n ", progname);
|
|
if (i == 0)
|
|
fprintf (stderr, "nothing,");
|
|
else
|
|
for (j = 0; j < i; j++)
|
|
fprintf (stderr, "%02x", buf[j]);
|
|
fprintf (stderr, "\nthen %02x instead of %02x\n", byt, buf[i]);
|
|
exit (2);
|
|
}
|
|
else
|
|
buf[i] = byt;
|
|
}
|
|
}
|
|
|
|
/* Handle everything about a nil-terminated line of input.
|
|
Call exit (2) on error with error text on stderr. */
|
|
|
|
static void
|
|
handle_input (int fd, char *buf, char *fname, int lineno)
|
|
{
|
|
int nbytes = 0;
|
|
int n = -1;
|
|
char *s = buf + 2;
|
|
unsigned int data;
|
|
static unsigned char bytes[1024];
|
|
int i;
|
|
|
|
memset (bytes, 0, sizeof bytes);
|
|
lineno++;
|
|
|
|
if (buf[1] != ',')
|
|
goto syntax_error;
|
|
|
|
switch (buf[0])
|
|
{
|
|
/* Comment characters and empty lines. */
|
|
case 0: case '!': case '#':
|
|
break;
|
|
|
|
/* Include another file. */
|
|
case '@':
|
|
handle_input_file (fd, s);
|
|
break;
|
|
|
|
/* Raw input (to be expected). */
|
|
case 'i':
|
|
do
|
|
{
|
|
n = -1;
|
|
sscanf (s, "%02x%n", &data, &n);
|
|
s += n;
|
|
if (n > 0)
|
|
bytes[nbytes++] = data;
|
|
}
|
|
while (n > 0);
|
|
expect_input (fd, bytes, nbytes, nbytes);
|
|
if (verbose)
|
|
{
|
|
printf ("i,");
|
|
for (i = 0; i < nbytes; i++)
|
|
printf ("%02x", bytes[i]);
|
|
printf ("\n");
|
|
}
|
|
break;
|
|
|
|
/* Raw output (to be written). */
|
|
case 'o':
|
|
do
|
|
{
|
|
n = -1;
|
|
sscanf (s, "%02x%n", &data, &n);
|
|
if (n > 0)
|
|
{
|
|
s += n;
|
|
bytes[nbytes++] = data;
|
|
}
|
|
}
|
|
while (n > 0);
|
|
if (*s != 0)
|
|
goto syntax_error;
|
|
send_output (fd, bytes, nbytes);
|
|
if (verbose)
|
|
{
|
|
printf ("o,");
|
|
for (i = 0; i < nbytes; i++)
|
|
printf ("%02x", bytes[i]);
|
|
printf ("\n");
|
|
}
|
|
break;
|
|
|
|
/* Read a register. */
|
|
case 'r':
|
|
{
|
|
unsigned int addr;
|
|
sscanf (s, "%x,%x%n", &addr, &data, &n);
|
|
if (n < 0 || s[n] != 0)
|
|
goto syntax_error;
|
|
bytes[0] = 11;
|
|
bytes[1] = 0;
|
|
bytes[2] = RV_READ_CMD;
|
|
h2le32 (bytes + 3, addr);
|
|
expect_input (fd, bytes, 11, 7);
|
|
h2le32 (bytes + 7, data);
|
|
send_output (fd, bytes, 11);
|
|
if (verbose)
|
|
printf ("r,%x,%x\n", addr, data);
|
|
}
|
|
break;
|
|
|
|
/* Write a register. */
|
|
case 'w':
|
|
{
|
|
unsigned int addr;
|
|
sscanf (s, "%x,%x%n", &addr, &data, &n);
|
|
if (n < 0 || s[n] != 0)
|
|
goto syntax_error;
|
|
bytes[0] = 11;
|
|
bytes[1] = 0;
|
|
bytes[2] = RV_WRITE_CMD;
|
|
h2le32 (bytes + 3, addr);
|
|
h2le32 (bytes + 7, data);
|
|
expect_input (fd, bytes, 11, 11);
|
|
send_output (fd, bytes, 11);
|
|
if (verbose)
|
|
printf ("w,%x,%x\n", addr, data);
|
|
}
|
|
break;
|
|
|
|
/* Wait for some milliseconds. */
|
|
case 't':
|
|
{
|
|
int del = 0;
|
|
struct timeval to;
|
|
sscanf (s, "%d%n", &del, &n);
|
|
if (n < 0 || s[n] != 0 || del == 0)
|
|
goto syntax_error;
|
|
|
|
to.tv_sec = del / 1000;
|
|
to.tv_usec = (del % 1000) * 1000;
|
|
|
|
if (select (0, NULL, NULL, NULL, &to) != 0)
|
|
{
|
|
fprintf (stderr, "%s: problem waiting for %d ms:\n %s\n",
|
|
progname, del, strerror (errno));
|
|
exit (2);
|
|
}
|
|
if (verbose)
|
|
printf ("t,%d\n", del);
|
|
}
|
|
break;
|
|
|
|
/* Expect a watchdog command. */
|
|
case 'W':
|
|
if (*s != 0)
|
|
goto syntax_error;
|
|
bytes[0] = 3;
|
|
bytes[1] = 0;
|
|
bytes[2] = RV_WATCHDOG_CMD;
|
|
expect_input (fd, bytes, 3, 3);
|
|
if (verbose)
|
|
printf ("W\n");
|
|
break;
|
|
|
|
/* Send an IRQ notification. */
|
|
case 'I':
|
|
sscanf (s, "%x%n", &data, &n);
|
|
if (n < 0 || s[n] != 0)
|
|
goto syntax_error;
|
|
bytes[0] = 7;
|
|
bytes[1] = 0;
|
|
bytes[2] = RV_IRQ_CMD;
|
|
h2le32 (bytes + 3, data);
|
|
send_output (fd, bytes, 7);
|
|
if (verbose)
|
|
printf ("I,%x\n", data);
|
|
break;
|
|
|
|
/* DMA store (to CPU). */
|
|
case 's':
|
|
{
|
|
unsigned int addr;
|
|
sscanf (s, "%x,%n", &addr, &n);
|
|
|
|
if (n < 0 || s[n] == 0)
|
|
goto syntax_error;
|
|
s += n;
|
|
do
|
|
{
|
|
n = -1;
|
|
sscanf (s, "%02x%n", &data, &n);
|
|
if (n > 0)
|
|
{
|
|
s += n;
|
|
bytes[11 + nbytes++] = data;
|
|
}
|
|
}
|
|
while (n > 0);
|
|
|
|
if (*s != 0)
|
|
goto syntax_error;
|
|
h2le32 (bytes, nbytes + 11);
|
|
bytes[2] = RV_MEM_WR_CMD;
|
|
h2le32 (bytes + 3, addr);
|
|
h2le32 (bytes + 7, nbytes);
|
|
send_output (fd, bytes, nbytes + 11);
|
|
if (verbose)
|
|
{
|
|
printf ("s,%x,", addr);
|
|
for (i = 0; i < nbytes; i++)
|
|
printf ("%02x", bytes[i]);
|
|
printf ("\n");
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* DMA load (from CPU). */
|
|
case 'l':
|
|
{
|
|
unsigned int addr;
|
|
sscanf (s, "%x,%n", &addr, &n);
|
|
|
|
if (n < 0 || s[n] == 0)
|
|
goto syntax_error;
|
|
s += n;
|
|
do
|
|
{
|
|
n = -1;
|
|
sscanf (s, "%02x%n", &data, &n);
|
|
if (n > 0)
|
|
{
|
|
s += n;
|
|
bytes[11 + nbytes++] = data;
|
|
}
|
|
}
|
|
while (n > 0);
|
|
|
|
if (*s != 0)
|
|
goto syntax_error;
|
|
h2le32 (bytes, nbytes + 11);
|
|
bytes[0] = 11;
|
|
bytes[1] = 0;
|
|
bytes[2] = RV_MEM_RD_CMD;
|
|
h2le32 (bytes + 3, addr);
|
|
h2le32 (bytes + 7, nbytes);
|
|
send_output (fd, bytes, 11);
|
|
bytes[0] = (nbytes + 11) & 255;
|
|
bytes[1] = ((nbytes + 11) >> 8) & 255;
|
|
expect_input (fd, bytes, nbytes + 11, nbytes + 11);
|
|
if (verbose)
|
|
{
|
|
printf ("l,%x,", addr);
|
|
for (i = 0; i < nbytes; i++)
|
|
printf ("%02x", bytes[i]);
|
|
printf ("\n");
|
|
}
|
|
}
|
|
break;
|
|
|
|
syntax_error:
|
|
default:
|
|
fprintf (stderr, "%s: invalid command line in %s:%d:\n %s",
|
|
progname, fname, lineno, strerror (errno));
|
|
exit (2);
|
|
}
|
|
}
|
|
|
|
/* Loop over the contents of FNAME, using handle_input to parse each line.
|
|
Errors to stderr, exit (2). */
|
|
|
|
static void
|
|
handle_input_file (int fd, char *fname)
|
|
{
|
|
static char buf[2048] = {0};
|
|
int lineno = 0;
|
|
FILE *f = fopen (fname, "r");
|
|
|
|
if (f == NULL)
|
|
{
|
|
fprintf (stderr, "%s: problem opening %s: %s\n",
|
|
progname, fname, strerror (errno));
|
|
exit (2);
|
|
}
|
|
|
|
/* Let's cut the buffer short, so we always get a newline. */
|
|
while (fgets (buf, sizeof (buf) - 1, f) != NULL)
|
|
{
|
|
buf[strlen (buf) - 1] = 0;
|
|
lineno++;
|
|
handle_input (fd, buf, fname, lineno);
|
|
}
|
|
|
|
fclose (f);
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
int optc;
|
|
int fd;
|
|
int i;
|
|
|
|
progname = argv[0];
|
|
while ((optc = getopt_long (argc, argv, "", longopts, NULL)) != -1)
|
|
switch (optc)
|
|
{
|
|
case OPT_PORT:
|
|
port = atoi (optarg);
|
|
break;
|
|
|
|
case OPT_TIMEOUT:
|
|
timeout = (time_t) atoi (optarg);
|
|
break;
|
|
|
|
case OPT_VERBOSE:
|
|
verbose = 1;
|
|
break;
|
|
}
|
|
|
|
fd = setupsocket ();
|
|
if (fd == -1)
|
|
{
|
|
fprintf (stderr, "%s: problem setting up the connection: %s\n",
|
|
progname, strerror (errno));
|
|
exit (2);
|
|
}
|
|
|
|
for (i = optind; i < argc; i++)
|
|
handle_input_file (fd, argv[i]);
|
|
|
|
/* FIXME: option-controlled test for remaining input? */
|
|
close (fd);
|
|
return 1;
|
|
}
|
|
#endif
|