/* Copyright (c)1994-2000 Begemot Computer Associates. All rights reserved.
 * See the file COPYRIGHT for details of redistribution and use. */

# include "proc.h"

RCSID("$Id: device.c 457 2000-12-01 12:27:00Z hbb $")

/*
 * generic device support
 */


/* # define TICK_DEBUG */


/*
 * structure to register external device driver programs.
 * Each external program has a pipe on fd 0 to p11.
 * P11 is notified by an SIGIO if one of the pipes has something
 * to read. It selects all pipe fds and calls the device's async routine
 * for each ready file descriptor.
 */
typedef struct async async_t;
struct async {
	iodev_t	*dev;		/* device that registered asynchonuous notification */
	void	*argp;		/* call argument */
	pid_t	pid;		/* external driver pid */
};

/*
 * struct to describe the state of the line clock.
 * tinterval starts at 1000/clock_rate milliseconds.
 * Every clock_rate ticks we look at the host clock and adapt the
 * interval
 */
typedef struct ltc ltc_t;
struct ltc {
	int	tinterval;	/* repeat interval */
	int	trunning;	/* timer is running */
	int	check;		/* next check if 0 */

	struct timeval start;	/* when was the clock startet */
	long	elapsed;	/* how many seconds have elapsed since start */
	int	lbolt;		/* ticks in current second */

	int	lag;		/* delay the switch */
	int	direct;
};

/* XXX experimental */
# define LAG 1

/*
 * This table is indexed by the number of seconds the emulated clock
 * is wrong. It gives the number of microseconds by which the i-timer
 * should be changed.
 */
static int adapt[] = {
	    0,   100,   200,   500,  1000,  2000,  5000, 10000, 10000, 10000,
	10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000,
};
# define NADAPT	(sizeof(adapt)/sizeof(adapt[0]))

/*
 * struct for timeout table.
 */
typedef struct timeout timeout_t;
struct timeout {
	void	(*func)(void *);/* function to be called */
	void	*arg;		/* call argument */
	u_int	time;		/* interval, 0 means disabled */
	u_int	curr;		/* rest in current interval */
};

static fd_set	fd_devices;		/* pipes to external drivers */
static async_t	async[FD_DEVICES];	/* external driver table */
static int	asy_init;		/* true if initialized */

static timeout_t timeouts[MAXTIMEOUT];	/* timeout table */
static int	ntimeouts;		/* current number of registered timeouts */

static ltc_t	timer;			/* line clock */


static void	onsigio(int);
static void	onalarm(int);
static void	kill_children(void);
static void	dev_pcmd(iocmd_t *tab, int pos);
static void	dev_pusage(iocmd_t *tab, int pos);
static void	set_fd_async(int fd);


static void	block(sigset_t *, int);
static void	unblock(sigset_t *);


/*
 * Initialize the clock
 */
void
init_timer()
{
	timer.tinterval = 1000000 / globopts.clock_rate;
	timer.trunning = 0;
}

/*
 * catch signals used by device support (SIGALRM and SIGIO)
 */
void
catch_io_sigs(void)
{
	catch_signal(SIGIO, onsigio);
	catch_signal(SIGALRM, onalarm);
}


/*
 * functions to block/unblock signals during global data manipulation
 */
static void
block(sigset_t *oset, int sig)
{
	sigset_t set;

	sigemptyset(&set);
	sigaddset(&set, sig);
	if(sigprocmask(SIG_BLOCK, &set, oset))
		panic("sigprocmask(SIG_BLOCK) failed: %s", strerror(errno));
}

static void
unblock(sigset_t *oset)
{
	if(sigprocmask(SIG_SETMASK, oset, NULL))
		panic("sigprocmask(SIG_SETMASK) failed: %s", strerror(errno));
}


static void
dotimer(struct timeval *oval)
{
	struct itimerval tval;

	tval.it_interval.tv_usec = timer.tinterval;
	tval.it_interval.tv_sec = 0;

	if(oval != NULL && timerisset(oval))
		tval.it_value = *oval;
	else
		tval.it_value = tval.it_interval;

	if(setitimer(ITIMER_REAL, &tval, 0))
		panic("setitimer: %s", strerror(errno));
}

/*
 * start timer if it is not already running
 */
void
start_timer(void)
{

	if(timer.trunning)
		return;

	dotimer(NULL);

	timer.trunning = 1;
	timer.check = globopts.clock_rate;
	timer.elapsed = 0;
	timer.lbolt = globopts.clock_rate;
	gettimeofday(&timer.start, NULL);
}


/*
 * stop timer if it is running
 * return old running state
 */
int
stop_timer(void)
{
	struct itimerval tval;

	if(!timer.trunning)
		return 0;

	tval.it_interval.tv_usec = tval.it_value.tv_usec = 0;
	tval.it_interval.tv_sec = tval.it_value.tv_sec = 0;
	setitimer(ITIMER_REAL, &tval, 0);
	timer.trunning = 0;

	return 1;
}


/*
 * this is the signal handler for the line clock, called each 20ms
 */
static void	
onalarm(int s UNUSED)
{
	timeout_t *t;
	struct timeval tv;
	struct itimerval oval;
	long secs;
	int newrate;
	int newdir;
	u_long diff;

	for(t = timeouts; t < &timeouts[ntimeouts]; t++)
		if(t->time && --t->curr == 0) {
			t->func(t->arg);
			t->curr = t->time;
		}

	if(--timer.lbolt == 0) {
		timer.lbolt = globopts.clock_rate;
		timer.elapsed++;
	}
	if(timer.check-- > 0)
		return;
	timer.check = globopts.clock_rate;

	/*
	 * Compute the number of real seconds that have elapsed and compare
	 * to the number we have elapsed. Be careful not to overflow (thanks
	 * to sms for detecting the overflow).
	 */
	gettimeofday(&tv, NULL);
	secs = tv.tv_sec - timer.start.tv_sec;
	secs += (tv.tv_usec - timer.start.tv_usec)/1000000;
	newrate = 1000000 / globopts.clock_rate;

	if(secs > timer.elapsed) {
		/* out clock is too slow */
		diff = secs - timer.elapsed;
		if(diff >= NADAPT) {
			warn("host clock probably jumpy; diff is %lu", diff);
			diff = NADAPT - 1;
		}
		newrate -= adapt[diff];
		newdir = -diff;

	} else if(secs < timer.elapsed) {
		/* our clock is too fast */
		diff = timer.elapsed - secs;
		if(diff >= NADAPT) {
			warn("host clock probably jumpy; diff is %lu", diff);
			diff = NADAPT - 1;
		}
		newrate += adapt[diff];
		newdir = +diff;
	} else
		newdir = 0;

	if(timer.lag)
		timer.lag--;

# ifdef TICK_DEBUG
printf("newdir=%d, timer.dir=%d, timer.lag=%d, timer.tinterval=%d, newrate=%d\n",
newdir, timer.direct, timer.lag, timer.tinterval, newrate);
# endif

	if(newrate != timer.tinterval) {
		if(timer.lag == 0
		 || timer.direct == 0
		 || (timer.direct > 0 && newdir > timer.direct)
		 || (timer.direct < 0 && newdir < timer.direct)) {
			if(getitimer(ITIMER_REAL, &oval))
				panic("getitimer: %s", strerror(errno));

			timer.tinterval = newrate;
			timer.lag = LAG;
			timer.direct = newdir;

			dotimer(&oval.it_value);

			if(verbose['t'])
				fprintf(stderr, "turning clock to %d msecs; "
					"real elapsed: %ld; emulated: %ld\n",
					newrate, secs, timer.elapsed);
		} else {
			if(verbose['t'])
				fprintf(stderr, "turning clock delayed -- %d\n",
					timer.lag);

		}
	}
}

/*
 * Make a file descriptor generate a signal if input is ready
 */
static void
set_fd_async(int fd)
{
# ifdef HAVE_STREAMS
	if(ioctl(fd, I_SETSIG, S_RDNORM))
		panic("ioctl(I_SETSIG): %s", strerror(errno));
# else
# ifdef HAVE_O_ASYNC
	int fl;

	if(fcntl(fd, F_SETOWN, getpid()))
		panic( "fcntl(F_SETOWN): %s", strerror(errno) );
	if((fl = fcntl(fd, F_GETFL, 0)) == -1)
		panic( "fcntl(F_GETFL): %s", strerror(errno) );
	if(fcntl(fd, F_SETFL, O_ASYNC | fl))
		panic( "fcntl(F_SETFL): %s", strerror(errno) );
# else
# endif
# endif
}

/*
 * start an asynchronous device handler.
 * 
 *
 * there should be a method for the calling driver to
 * be informed when the external process dies.
 * Perhaps we could add a flag to the async function for this case.
 *
 * input:
 *	prog	executable to start
 *	argv	argument vector for new process
 *	dev	device to be informed on possible input
 *	argp	call argument for device's async function
 *	afd	file descriptor to retain open (or -1) (is duped to 3)
 *	pp	to return new process's pid (if not NULL)
 *
 * returns:
 *	pipe files descriptor
 */
int
register_handler(char *prog, char *argv[], iodev_t *dev, void *argp, int afd, pid_t *pp)
{
	int	sd[2];
	int	pid;
	int	fd;
	async_t	*asy;
	sigset_t oset;
	char *full;

	block(&oset, SIGIO);

	if(!asy_init) {
		atexit(kill_children);
		asy_init = 1;
	}

	if(socketpair(PF_UNIX, SOCK_STREAM, 0, sd) < 0)
		panic("socketpair: %s", strerror(errno));

	switch(pid = fork()) {

	case -1:
		/* failure */
		panic("fork: %s", strerror(errno));
	case 0:
		/* child */
		(void)close(0);
		if(dup(sd[0]) != 0)
			abort();

		/*
		 * don't close fds 0 (it's the pipe), 2 (stderr) and
 		 * afd. We need afd because MMAP_INHERIT seems not to be
 		 * implemented so we need a shared file descriptor for
 		 * naming of the shared region (Urrgh, wouldn't it
		 * be _MUCH_ simpler to have plan9 rfork()?).
		 * dup afd to 3, so external program knows where to find it
		 */
		for(fd = getdtablesize() - 1; fd > 0; fd--)
			if(fd != 2 && fd != afd)
				close(fd);
		if(afd >= 0 && afd != 3) {
			if(dup2(afd, 3) < 0)
				panic("dup2(%d,%d): %s", afd, 3, strerror(errno));
			close(afd);
		}
		execv(prog, argv);
		if(globopts.iodir) {
			full = xalloc(strlen(prog) + strlen(globopts.iodir) + 2);
			sprintf(full, "%s/%s", globopts.iodir, prog);
			execv(full, argv);
		}
		panic("exec: %s - %s", prog, strerror(errno));

	}

	/* parent */
	close(sd[0]);

	asy = &async[sd[1]];
	asy->dev = dev;
	asy->argp = argp;
	asy->pid = pid;

	FD_SET(sd[1], &fd_devices);

	if(pp != NULL)
		*pp = pid;

	if(fcntl(sd[1], F_SETFL, O_NONBLOCK))
		panic( "fcntl(O_NONBLOCK): %s", strerror(errno) );

	set_fd_async(sd[1]);

	unblock(&oset);

	return sd[1];
}

/*
 * on exit kill all registered children
 * (but be honest and send a SIGTERM)
 */
static void
kill_children(void)
{
	async_t	*asy;

	signal(SIGCHLD, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	signal(SIGIO, SIG_IGN);
	for(asy = async; asy < &async[FD_DEVICES]; asy++)
		if(asy->pid)
			kill(asy->pid, SIGTERM);
}


/*
 * signal handler for notification of possible i/o.
 * select all async pipes and call device handlers for all selected
 */
static void 
onsigio(int s UNUSED)
{
	fd_set	set = fd_devices;
	int	fd;
	async_t	*asy;
	static struct timeval tv;

	if(select(FD_DEVICES+1, &set, 0, 0, &tv) > 0)
		for(fd = 0, asy = async; fd < FD_DEVICES; fd++, asy++)
			if(FD_ISSET(fd, &set))
				(*asy->dev->ioops->async)(asy->dev, asy->argp);
}


/*
 * register timer to be called each p msecs.
 * return index in timeout table
 */
int
register_timer(u_int p, void (*f)(void *), void *a)
{
	timeout_t *t;
	sigset_t oset;
	int	i;

	block(&oset, SIGALRM);

	if(ntimeouts == MAXTIMEOUT)
		panic("out of timeouts");

	t = &timeouts[i = ntimeouts++];

	t->func = f;
	t->time = t->curr = (p * globopts.clock_rate + 999) / 1000;
	t->arg = a;

	if(verbose['l'])
		inform("register_timer(%u,%p,%p) -> index=%d, t=%u",
			p, f, a, i, t->curr);

	unblock(&oset);

	return i; 
}

/*
 * set timer to new period
 */
void
reset_timer(u_int p, int i)
{
	timeout_t *t;
	sigset_t oset;

	block(&oset, SIGALRM);

	t = &timeouts[i];
	t->time = t->curr = (p * globopts.clock_rate + 999) / 1000;

	unblock(&oset);
}

/*
 * general command handler
 */
void
dev_command(iodev_t *dev, int argc, char **argv)
{
	iocmd_t	*p;
	int matches;
	iocmd_t	*match = NULL;	/* keep compiler happy */
	iocmd_t	*tab = dev->ioops->cmds;

	matches = 0;
	for(p = tab; p->name != NULL; p++)
		if(SUBSTR(argv[0], p->name)) {
			match = p;
			matches++;
		}

	if(matches > 1) {
		printf("ambiguous command (%d matches):\n", matches);
		for(p = tab; p->name != NULL; p++)
			if(SUBSTR(argv[0], p->name))
				dev_pusage(tab, p - tab);
		return;

	} else if(matches == 0) {
		printf("Unknown command for '%s'; try '?'\n", dev->conf->xname);
		return;
	}

	if((*match->func)(dev, --argc, ++argv)) {
		printf("Usage:");
		dev_pusage(tab, match - tab);
	}
}

/*
 * print help info for device
 */
int
dev_help(iodev_t *dev, int argc, char **argv)
{
	iocmd_t	*p;
	int matches;
	int i;
	iocmd_t *tab = dev->ioops->cmds;

	if(argc == 0) {
		/*
		 * list all
		 */
		printf("Available commands for device '%s':\n", dev->conf->xname);
		for(p = tab; p->name != NULL; p++)
			dev_pusage(tab, p - tab);
		return 0;
	}
	/*
	 * print matching commands for each argument
	 */
	for(i = 0; i < argc; i++) {
		for(matches = 0, p = tab; p->name != NULL; p++)
			if(SUBSTR(argv[i], p->name))
				matches++;
		if(matches == 0)
			printf("No match for '%s' for device '%s'.\n", argv[i], dev->conf->xname);
		else {
			printf("%d match%s for '%s' for device '%s':\n", matches, (matches > 1) ? "es" : "", argv[i], dev->conf->xname);
			for(p = tab; p->name != NULL; p++)
				if(SUBSTR(argv[i], p->name))
					dev_pusage(tab, p - tab);
		}
	}
	return 0;
}
/*
 * print command string with optional part in [] on stdout
 * do it the simple way: enlarge substring until we have only one
 * match left. This assumes the you have no command that is a substring
 * of a longer one, i.e. 'show' and 'showfile'.
 */
static void
dev_pcmd(iocmd_t *tab, int pos)
{
	int matches;
	iocmd_t	*p;
	u_int len;

	for(len = 1; len < strlen(tab[pos].name); len++) {
		matches = 0;
		for(p = tab; p->name != NULL; p++)
			if(strlen(p->name) >= len && strncmp(p->name, tab[pos].name, len) == 0)
				matches++;
		if(matches == 1) {
			printf("%.*s[%s]", (int)len, tab[pos].name, tab[pos].name + len);
			return;
		}
	}
	printf("%s", tab[pos].name);
}

/*
 * print usage string on stdout
 */
static void
dev_pusage(iocmd_t *tab, int pos)
{
	putchar('\t');
	dev_pcmd(tab, pos);
	printf(" %s\n", tab[pos].usage);
}

char *
ctrlname(iodev_t *dev, int flag)
{
	static char buf[MAXCTRLNAME+1];

	if(!flag && dev->conf->count == 1)
		return dev->conf->xname;

	sprintf(buf, "%s[%u]", dev->conf->xname, dev->instance);
	return buf;
}

/*
 * Generic DMA handler
 * Transfers a number of bytes from a buffer into memory (or opposite).
 * Returns number of bytes transfered. If this is not the original amount,
 * a non-existant memory trap occured.
 */
u_int
dma(void *buf, u_int bytes, u_int mem, dmadir_t direction, int *cmpflg)
{
	u_char	*a;		/* memory address */
	u_char	*end_mem;	/* 1st illegal memory address */
	u_char	*end;		/* transfer end */

	end_mem = (u_char *)proc.memops->addrphys(proc.mmg, proc.physmem);

	a = (u_char *)proc.memops->addrphys(proc.mmg, mem);
	if(mem & 1)
		a++;

	end = a + bytes;
	if(end > end_mem)
		bytes -= (end - end_mem);

	if(bytes) {
		switch(direction) {

		case ReadMem:
			memcpy(buf, a, bytes);
			break;

		case WriteMem:
			memcpy(a, buf, bytes);
			break;

		case CmpMem:
			*cmpflg = memcmp(a, buf, bytes);
			break;

		default:
			panic("bad switch: %s, %d", __FILE__, __LINE__);
		}
	}

	return bytes;
}

void
device_info(void)
{
	int fd, i;

	printf("fd_devices:");
	for(fd = 0; fd <= FD_DEVICES; fd++)
		if(FD_ISSET(fd, &fd_devices))
			printf(" %d", fd);
	printf("\n");

	printf("async:");
	for(i = 0; i < FD_DEVICES; i++)
		if(async[i].dev != NULL)
			printf(" %s(%lu)", ctrlname(async[i].dev, 0),
				(u_long)async[i].pid);
	printf("\n");

	printf("ltc: tinterval=%d trunning=%d check=%d\n", timer.tinterval,
		timer.trunning, timer.check);
	printf("     start=(%lu,%lu) elapsed=%ld lbolt=%d\n",
		timer.start.tv_sec, timer.start.tv_usec, timer.elapsed, timer.lbolt);
	printf("     lag=%d direct=%d\n", timer.lag, timer.direct);
}
