blob: 719b2fcb74b5ac78ed74403d908433f3466cee5f [file] [log] [blame]
/* Test-driver for the remote-virtual-component simulator framework
for GDB, the GNU Debugger.
Copyright 2006-2021 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>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#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;
FILE *f;
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