/*-
 * Copyright (c) 1991 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Van Jacobson and Steven McCanne of Lawrence Berkeley Laboratory.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Header: remote.c,v 1.24 91/03/22 15:35:15 mccanne Exp $;
 */

#ifndef lint
static char sccsid[] = "@(#)remote.c	6.5 (Berkeley) 5/8/91";
#endif /* not lint */

#include "param.h"

#include <stdio.h>
#include <varargs.h>

#include <signal.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/file.h>

#include "defs.h"
#include "frame.h"
#include "inferior.h"
#include "wait.h"

#include "kgdb_proto.h"

static FILE *kiodebug;
static int icache = 1;
extern int kernel_debugging;

static int remote_cache_valid;
static int remote_instub;

static void remote_signal();
static void remote_debug();
static void print_msg();

static int remote_mtu;
static int (*send_msg)();
static int (*recv_msg)();
static void (*closelink)();

static u_char *inbuffer;
static u_char *outbuffer;

/*
 * Statistics.
 */
static int remote_ierrs;
static int remote_oerrs;
static int remote_seqerrs;
static int remote_spurious;

#define PUTCMD(cmd) m_xchg(cmd, (u_char *)0, 0, (u_char *)0, (int *)0)

/*
 * Send an outbound message to the remote machine and read the reply.
 * Either or both message buffers may be NULL.
 */
static int
m_xchg(type, out, outlen, in, inlen)
	int type;
	u_char *out;
	int outlen;
	u_char *in;
	int *inlen;
{
	register int err, (*send)() = send_msg, (*recv)() = recv_msg;
	int ack;
	static int seqbit = 0;

	if (!remote_instub) {
		remote_instub = 1;
		PUTCMD(KGDB_EXEC);
	}

	seqbit ^= KGDB_SEQ;
	while (1) {
		err = (*send)(type | seqbit, out, outlen);
		if (err) {
			++remote_oerrs;
			if (kiodebug)
				remote_debug("send error %d\n", err);
		}
		if (kiodebug)
			print_msg(type | seqbit, out, outlen, 'O');

	recv:
		err = (*recv)(&ack, in, inlen);
		if (err) {
			++remote_ierrs;
			if (kiodebug)
				remote_debug("recv error %d\n", err);
			remote_cache_valid = 0;
		} else if (kiodebug)
			print_msg(ack, in, inlen ? *inlen : 0, 'I');

		if (err)
			continue;

		if ((ack & KGDB_ACK) == 0 || KGDB_CMD(ack) != KGDB_CMD(type)) {
			++remote_spurious;
			continue;
		}
		if ((ack & KGDB_SEQ) ^ seqbit) {
			++remote_seqerrs;
			goto recv;
		}
		return ack;
	}
}

/*
 * Wait for the specified message type.  Discard anything else.
 * (this is used by 'remote-signal' to help us resync with other side.)
 */
static void
m_recv(type, in, inlen)
	int type;
	u_char *in;
	int *inlen;
{
	int reply, err;

	while (1) {
		err = (*recv_msg)(&reply, in, inlen);
		if (err) {
			++remote_ierrs;
			if (kiodebug)
				remote_debug("recv error %d\n", err);
		} else if (kiodebug)
			print_msg(reply, in, inlen ? *inlen : 0, 'I');

		if (KGDB_CMD(reply) == type)
			return;
		++remote_spurious;
	}
}

/*
 * Send a message.  Do not wait for *any* response from the other side.
 * Some other thread of control will pick up the ack that will be generated.
 */
static void
m_send(type, buf, len)
	int type;
	u_char *buf;
	int len;
{
	int err;

	if (!remote_instub) {
		remote_instub = 1;
		PUTCMD(KGDB_EXEC);
	}

	err = (*send_msg)(type, buf, len);
	if (err) {
		++remote_ierrs;
		if (kiodebug)
			remote_debug("[send error %d] ", err);
	}
	if (kiodebug)
		print_msg(type, buf, len, 'O');
}

/*
 * Open a connection to a remote debugger.  
 * NAME is the filename used for communication.  
 */
void
remote_open(name, from_tty)
	char *name;
	int from_tty;
{
	int bufsize;

	remote_debugging = 0;
	if (sl_open(name, &send_msg, &recv_msg, &closelink, &remote_mtu,
		    &bufsize))
		return;
	if (from_tty)
		printf("Remote debugging using %s\n", name);
	remote_debugging = 1;

	remote_cache_valid = 0;

	inbuffer = (u_char *)malloc(bufsize);
	outbuffer = (u_char *)malloc(bufsize);

	remote_signal();

	remote_ierrs = 0;
	remote_oerrs = 0;
	remote_spurious = 0;
}

/*
 * Close the open connection to the remote debugger. Use this when you want
 * to detach and do something else with your gdb.  
 */
void
remote_close(from_tty)
	int from_tty;
{
	if (!remote_debugging)
		error("remote debugging not enabled");

	remote_debugging = 0;
	/*
	 * Take remote machine out of debug mode.
	 */
	(void)PUTCMD(KGDB_KILL);
	(*closelink)();
	if (from_tty)
		printf("Ending remote debugging\n");

	free((char *)inbuffer);
	free((char *)outbuffer);
}

/*
 * Tell the remote machine to resume.
 */
int
remote_resume(step, signal)
	int step, signal;
{
	if (!step) {
		(void)PUTCMD(KGDB_CONT);
		remote_instub = 0;
	} else {
#ifdef NO_SINGLE_STEP
		single_step(0);
#else
		(void)PUTCMD(KGDB_STEP);
#endif
	}
}

/*
 * Wait until the remote machine stops, then return, storing status in STATUS
 * just as `wait' would.
 */
int
remote_wait(status)
	WAITTYPE *status;
{
	int len;

	WSETEXIT((*status), 0);
	/*
	 * When the machine stops, it will send us a KGDB_SIGNAL message,
	 * so we wait for one of these.
	 */
	m_recv(KGDB_SIGNAL, inbuffer, &len);
	WSETSTOP((*status), inbuffer[0]);
}

/*
 * Register context as of last remote_fetch_registers().
 */
static char reg_cache[REGISTER_BYTES];

/*
 * Read the remote registers into the block REGS.
 */
void
remote_fetch_registers(regs)
	char *regs;
{
	int regno, len, rlen, ack;
	u_char *cp, *ep;

	regno = -1;
	do {
		outbuffer[0] = regno + 1;
		ack = m_xchg(remote_cache_valid ? 
			         KGDB_REG_R|KGDB_DELTA : KGDB_REG_R,
			     outbuffer, 1, inbuffer, &len);
		cp = inbuffer;
		ep = cp + len;
		while (cp < ep) {
			regno = *cp++;
			rlen = REGISTER_RAW_SIZE(regno);
			bcopy((char *)cp, 
			      &reg_cache[REGISTER_BYTE(regno)], rlen);
			cp += rlen;
		}
	} while (ack & KGDB_MORE);

	remote_cache_valid = 1;
	bcopy(reg_cache, regs, REGISTER_BYTES);
}

/*
 * Store the remote registers from the contents of the block REGS.
 */
void
remote_store_registers(regs)
	char *regs;
{
	u_char *cp, *ep;
	int regno, off, rlen;

	cp = outbuffer;
	ep = cp + remote_mtu;

	for (regno = 0; regno < NUM_REGS; ++regno) {
		off = REGISTER_BYTE(regno);
		rlen = REGISTER_RAW_SIZE(regno);
		if (!remote_cache_valid ||
		    bcmp(&regs[off], &reg_cache[off], rlen) != 0) {
			if (cp + rlen + 1 >= ep) {
				(void)m_xchg(KGDB_REG_W, 
					     outbuffer, cp - outbuffer, 
					     (u_char *)0, (int *)0);
				cp = outbuffer;
			}
			*cp++ = regno;
			bcopy(&regs[off], cp, rlen);
			cp += rlen;
		}
	}
	if (cp != outbuffer)
		(void)m_xchg(KGDB_REG_W, outbuffer, cp - outbuffer, 
			     (u_char *)0, (int *)0);
	bcopy(regs, reg_cache, REGISTER_BYTES);
}

/*
 * Store a chunk of memory into the remote host.
 * 'remote_addr' is the address in the remote memory space.
 * 'cp' is the address of the buffer in our space, and 'len' is
 * the number of bytes.  Returns an errno status.
 */
int
remote_write_inferior_memory(remote_addr, cp, len)
	CORE_ADDR remote_addr;
	u_char *cp;
	int len;
{
	int cnt;

	while (len > 0) {
		cnt = min(len, remote_mtu - 4);
		bcopy((char *)&remote_addr, outbuffer, 4);
		bcopy(cp, outbuffer + 4, cnt);
		(void)m_xchg(KGDB_MEM_W, outbuffer, cnt + 4, inbuffer, &len);

		if (inbuffer[0])
			return inbuffer[0];

		remote_addr += cnt;
		cp += cnt;
		len -= cnt;
	}
	return 0;
}

/*
 * Read memory data directly from the remote machine.
 * 'remote_addr' is the address in the remote memory space.
 * 'cp' is the address of the buffer in our space, and 'len' is
 * the number of bytes.  Returns an errno status.
 */
static int
remote_read_memory(remote_addr, cp, len)
	CORE_ADDR remote_addr;
	u_char *cp;
	int len;
{
	int cnt, inlen;

	while (len > 0) {
		cnt = min(len, remote_mtu - 1);
		outbuffer[0] = cnt;
		bcopy((char *)&remote_addr, (char *)&outbuffer[1], 4);

		(void)m_xchg(KGDB_MEM_R, outbuffer, 5, inbuffer, &inlen);

		if (inbuffer[0] != 0)
			return inbuffer[0];

		if (cnt != inlen - 1)
			/* XXX */
			error("remote_read_memory() request botched");

		bcopy((char *)&inbuffer[1], (char *)cp, cnt);

		remote_addr += cnt;
		cp += cnt;
		len -= cnt;
	}
	return 0;
}

int
remote_read_inferior_memory(remote_addr, cp, len)
	CORE_ADDR remote_addr;
	char *cp;
	int len;
{
	int stat = 0;

	if (icache) {
		extern CORE_ADDR text_start, text_end;
		CORE_ADDR xferend = remote_addr + len;

		if (remote_addr < text_end && text_start < xferend) {
			/*
			 * at least part of this xfer is in the text
			 * space -- xfer the overlap from the exec file.
			 */
			if (remote_addr >= text_start && xferend < text_end)
				return (xfer_core_file(remote_addr, cp, len));
			if (remote_addr >= text_start) {
				int i = text_end - remote_addr;

				if (stat = xfer_core_file(remote_addr, cp, i))
					return (stat);
				remote_addr += i;
				cp += i;
				len -= i;
			} else if (xferend <= text_end) {
				int i = xferend - text_start;

				len = text_start - remote_addr;
				if (stat = xfer_core_file(text_start,
							  cp + len, i))
					return (stat);
			}
		}
	}
	return remote_read_memory(remote_addr, cp, len);
}

/*
 * Signal the remote machine.  The remote end might be idle or it might
 * already be in debug mode -- we need to handle both case.  Thus, we use
 * the framing character as the wakeup byte, and send a SIGNAL packet.
 * If the remote host is idle, the framing character will wake it up.
 * If it is in the kgdb stub, then we will get a SIGNAL reply.
 */
static void
remote_signal()
{
	if (!remote_debugging)
		printf("Remote debugging not enabled.\n");
	else {
		remote_instub = 0;
		m_send(KGDB_SIGNAL, (u_char *)0, 0);
	}
}

static void
remote_signal_command()
{
	extern int stop_after_attach;

	if (!remote_debugging)
		error("Not debugging remote.");
	remote_cache_valid = 0;
	remote_signal();
	restart_remote();
}

/*
 * Print a message for debugging.
 */
static void
print_msg(type, buf, len, dir)
	int type;
	u_char *buf;
	int len;
	int dir;
{
	int i;
	char *s;

	switch (KGDB_CMD(type)) {
	case KGDB_MEM_R:	s = "memr"; break;
	case KGDB_MEM_W:	s = "memw"; break;
	case KGDB_REG_R:	s = "regr"; break;
	case KGDB_REG_W:	s = "regw"; break;
	case KGDB_CONT:		s = "cont"; break;
	case KGDB_STEP:		s = "step"; break;
	case KGDB_KILL:		s = "kill"; break;
	case KGDB_SIGNAL:	s = "sig "; break;
	case KGDB_EXEC:		s = "exec"; break;
	default:		s = "unk "; break;
	}
	remote_debug("%c %c%c%c%c %s (%02x): ", dir,
		     (type & KGDB_ACK) ? 'A' : '.',
		     (type & KGDB_DELTA) ? 'D' : '.',
		     (type & KGDB_MORE) ? 'M' : '.',
		     (type & KGDB_SEQ) ? '-' : '+',
		     s, type);
	if (buf)
		for (i = 0; i < len; ++i)
			remote_debug("%02x", buf[i]);
	remote_debug("\n");
}

static void
set_remote_text_refs_command(arg, from_tty)
	char *arg;
	int from_tty;
{
	icache = !parse_binary_operation("set remote-text-refs", arg);
}

static void
remote_debug_command(arg, from_tty)
	char *arg;
	int from_tty;
{
	char *name;

	if (kiodebug != 0 && kiodebug != stderr)
		(void)fclose(kiodebug);

	if (arg == 0) {
		kiodebug = 0;
		printf("Remote debugging off.\n");
		return;
	}
	if (arg[0] == '-') {
		kiodebug = stderr;
		name = "stderr";
	} else {
		kiodebug = fopen(arg, "w");
		if (kiodebug == 0) {
			printf("Cannot open '%s'.\n", arg);
			return;
		}
		name = arg;
	}
	printf("Remote debugging output routed to %s.\n", name);
}

/* ARGSUSED */
static void
remote_info(arg, from_tty)
	char *arg;
	int from_tty;
{
	printf("Using %s for text references.\n",
		icache? "local executable" : "remote");
	printf("Protocol debugging is %s.\n", kiodebug? "on" : "off");
	printf("%d spurious input messages.\n", remote_spurious);
	printf("%d input errors; %d output errors; %d sequence errors.\n", 
	       remote_ierrs, remote_oerrs, remote_seqerrs);
}

/* VARARGS */
static void
remote_debug(va_alist)
	va_dcl
{
	register char *cp;
	va_list ap;

	va_start(ap);
	cp = va_arg(ap, char *);
	(void)vfprintf(kiodebug, cp, ap);
	va_end(ap);
	fflush(kiodebug);
}

extern struct cmd_list_element *setlist;

void
_initialize_remote()
{
	add_com("remote-signal", class_run, remote_signal_command,
		"If remote debugging, send interrupt signal to remote.");
	add_cmd("remote-text-refs", class_support, 
		set_remote_text_refs_command,
"Enable/disable use of local executable for text segment references.\n\
If on, all memory read/writes go to remote.\n\
If off, text segment reads use the local executable.",
		&setlist);

	add_com("remote-debug", class_run, remote_debug_command,
"With a file name argument, enables output of remote protocol debugging\n\
messages to said file.  If file is `-', stderr is used.\n\
With no argument, remote debugging is disabled.");

	add_info("remote", remote_info,
		 "Show current settings of remote debugging options.");
}

