/*****************************************************************************/

/*
 *	hdrvcomm.c  -- HDLC driver communications.
 *
 *	Copyright (C) 1996  Thomas Sailer (sailer@ife.ee.ethz.ch)
 *
 *	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 2 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, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Please note that the GPL allows you to use the driver, NOT the radio.
 *  In order to use the radio, you need a license from the communications
 *  authority of your country.
 *
 *
 * History:
 *   0.1  10.5.97  Started
 */

/*****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/byteorder.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <linux/if.h>
#include <linux/if_ether.h>
#include "hdrvcomm.h"
#include "usersmdiag.h"

/* ---------------------------------------------------------------------- */

#ifdef HDRVC_KERNEL
static int kernel_mode = 1;
#endif /* HDRVC_KERNEL */
static char *if_name = "bc0";
static char *prg_name;
static int fd = -1;
static struct ifreq ifr_h;
static int promisc = 0;
static int msqid = -1;

/* ---------------------------------------------------------------------- */

static void terminate(void)
{
#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		if (ioctl(fd, SIOCSIFFLAGS, &ifr_h) < 0) {
			perror("ioctl (SIOCSIFFLAGS)");
			exit(1);
		}
	}
#endif /* HDRVC_KERNEL */
	exit(0);
}

/* ---------------------------------------------------------------------- */

static void terminate_sig(int signal)
{
	printf("signal %i caught\n", signal);
	terminate();
}

/* ---------------------------------------------------------------------- */

int hdrvc_recvpacket(char *pkt, int maxlen)
{
	struct ifreq ifr_new;
	struct sockaddr from;
	int from_len = sizeof(from);

#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		if (!promisc) {
			struct sockaddr sa;
			
			strcpy(sa.sa_data, if_name);
			sa.sa_family = AF_INET;
			if (bind(fd, &sa, sizeof(struct sockaddr)) < 0) {
				fprintf(stderr, "%s: Error %s (%i) bind failed\n",
					prg_name, strerror(errno), errno);
				exit(-2);
			}
			ifr_new = ifr_h;
			ifr_new.ifr_flags |= IFF_PROMISC;
			if (ioctl(fd, SIOCSIFFLAGS, &ifr_new) < 0) {
				perror("ioctl (SIOCSIFFLAGS)");
				exit(1);
			}
			signal(SIGTERM, terminate_sig);
			signal(SIGQUIT, terminate_sig);
			if (atexit((void (*)(void))terminate)) {
				perror("atexit");
				terminate();
			}
			promisc = 1;
			fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
		}
		if (!pkt || maxlen < 2)
			return 0;
		return recvfrom(fd, pkt, maxlen, 0, &from, &from_len);
	}
#endif /* HDRVC_KERNEL */
	return -1;
}

/* ---------------------------------------------------------------------- */

int hdrvc_getfd(void)
{
	return fd;
}

/* ---------------------------------------------------------------------- */

char *hdrvc_ifname(void)
{
	return if_name;
}

/* ---------------------------------------------------------------------- */

void hdrvc_args(int *argc, char *argv[], char *def_if)
{
	int ac, i;

	if (def_if)
		if_name = def_if;
	if (!argc || !argv || (ac = *argc) < 1)
		return;
	prg_name = argv[0];
	for (i = 1; i < ac-1; i++) {
#ifdef HDRVC_KERNEL
		if (!strcmp(argv[i], "-i")) {
			kernel_mode = 1;
			if_name = argv[i+1];
			ac -= 2;
			if (i < ac)
				memmove(argv+i, argv+i+2, (ac-i) * sizeof(void *));
			i--;
		} else 
#endif /* HDRVC_KERNEL */
			if (!strcmp(argv[i], "-u")) {
#ifdef HDRVC_KERNEL
				kernel_mode = 0;
#endif /* HDRVC_KERNEL */
				if_name = argv[i+1];
				ac -= 2;
				if (i < ac)
					memmove(argv+i, argv+i+2, (ac-i) * sizeof(void *));
				i--;
			}
	}
	*argc = ac;
}

/* ---------------------------------------------------------------------- */

void hdrvc_init(void)
{
#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		if ((fd = socket(PF_INET, SOCK_PACKET, htons(ETH_P_AX25))) < 0) {
			fprintf(stderr, "%s: Error %s (%i), cannot open %s\n", prg_name, 
				strerror(errno), errno, if_name);
			exit(-1);
		}
		strcpy(ifr_h.ifr_name, if_name);
		if (ioctl(fd, SIOCGIFFLAGS, &ifr_h) < 0) {
			fprintf(stderr, "%s: Error %s (%i), cannot ioctl %s\n", prg_name, 
				strerror(errno), errno, if_name);
			exit(-1);
		}
	} else 
#endif /* HDRVC_KERNEL */
	{
		key_t k = ftok(if_name, USERSM_KEY_PROJ);
		
		if (k == (key_t)-1) {
			fprintf(stderr, "%s: Error %s (%i), cannot ftok on %s\n", prg_name, 
				strerror(errno), errno, if_name);
			exit(-1);
		}
		if ((msqid = msgget(k, 0700)) < 0) {
			fprintf(stderr, "%s: Error %s (%i), cannot msgget %d\n", prg_name, 
				strerror(errno), errno, k);
			exit(-1);
		}
	}
}

/* ---------------------------------------------------------------------- */

extern __inline__ void hdrvc_sendmsg(struct usersmmsg *msg, int len)
{
	if (msgsnd(msqid, (struct msgbuf *)msg, len+sizeof(msg->hdr)-sizeof(long), 0) < 0) {
		perror("msgsnd");
		exit(1);
	}
}

extern __inline__ int hdrvc_recvmsg(struct usersmmsg *msg, int maxlen, long type) 
{
	int len;

	if ((len = msgrcv(msqid, (struct msgbuf *)msg, maxlen-sizeof(long), type, MSG_NOERROR)) < 0) {
		perror("msgrcv");
		exit(1);
	}
#if 0
	for (;;) {
		if ((len = msgrcv(msqid, (struct msgbuf *)msg, maxlen-sizeof(long), type, IPC_NOWAIT|MSG_NOERROR)) >= 0) 
			return len+sizeof(long);
		if (errno != ENOMSG) { 
			perror("msgrcv");
			exit(1);
		}
		usleep(250000);
	}
#endif 
	return len+sizeof(long);
}

/* ---------------------------------------------------------------------- */

#ifdef HDRVC_KERNEL

int hdrvc_hdlcdrv_ioctl(int cmd, struct hdlcdrv_ioctl *par)
{
	struct ifreq ifr = ifr_h;
	
	if (!kernel_mode) {
		errno = EINVAL;
		return -1;
	}
	ifr.ifr_data = (caddr_t)par;
	par->cmd = cmd;
	return ioctl(fd, SIOCDEVPRIVATE, &ifr);
}

/* ---------------------------------------------------------------------- */

int hdrvc_sm_ioctl(int cmd, struct sm_ioctl *par)
{
	struct ifreq ifr = ifr_h;
	
	if (!kernel_mode) {
		errno = EINVAL;
		return -1;
	}
	ifr.ifr_data = (caddr_t)par;
	par->cmd = cmd;
	return ioctl(fd, SIOCDEVPRIVATE, &ifr);
}

/* ---------------------------------------------------------------------- */

int hdrvc_baycom_ioctl(int cmd, struct baycom_ioctl *par)
{
	struct ifreq ifr = ifr_h;
	
	if (!kernel_mode) {
		errno = EINVAL;
		return -1;
	}
	ifr.ifr_data = (caddr_t)par;
	par->cmd = cmd;
	return ioctl(fd, SIOCDEVPRIVATE, &ifr);
}

/* ---------------------------------------------------------------------- */

unsigned int hdrvc_get_ifflags(void)
{
	struct ifreq ifr;

	if (kernel_mode) {
		memcpy(&ifr, &ifr_h, sizeof(ifr));
		if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
			perror("ioctl: SIOCGIFFLAGS");
			exit(-1);
		}
		return ifr.ifr_flags;
	}
	return IFF_UP | IFF_RUNNING;
}

/* ---------------------------------------------------------------------- */

int hdrvc_diag(struct sm_diag_data *diag)
{
	struct sm_ioctl smi;
	int ret;
	struct usersmmsg msg;

	if (!diag) {
		errno = EINVAL;
		return -1;
	}
	if (kernel_mode) {
		memcpy(&smi.data.diag, diag, sizeof(smi.data.diag));
		ret = hdrvc_sm_ioctl(SMCTL_DIAGNOSE, &smi);
		memcpy(diag, &smi.data.diag, sizeof(smi.data.diag));
		return ret;
	}
	msg.hdr.type = USERSM_CMD_REQ_DIAG;
	msg.hdr.channel = 0;
	msg.data.diag.mode = diag->mode;
	msg.data.diag.flags = diag->flags;
	msg.data.diag.samplesperbit = diag->samplesperbit;
	msg.data.diag.datalen = diag->datalen;
	hdrvc_sendmsg(&msg, sizeof(msg.data.diag));
	ret = hdrvc_recvmsg(&msg, sizeof(msg), USERSM_CMD_ACK_DIAG);
	if (ret < 0)
		return ret;
	if (ret < sizeof(msg.data.diag) || ret < sizeof(msg.data.diag)+msg.data.diag.datalen*sizeof(short)) {
		errno = EIO;
		return -1;
	}
	diag->mode = msg.data.diag.mode;
	diag->flags = msg.data.diag.flags;
	diag->samplesperbit = msg.data.diag.samplesperbit;
	diag->datalen = msg.data.diag.datalen;
	memcpy(diag->data, msg.data.diag_out.samples, msg.data.diag.datalen*sizeof(short));
	return 0;
}

#endif /* HDRVC_KERNEL */

/* ---------------------------------------------------------------------- */

int hdrvc_get_samples(void)
{
	int ret;
	struct hdlcdrv_ioctl bi;
	
#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		ret = hdrvc_hdlcdrv_ioctl(HDLCDRVCTL_GETSAMPLES, &bi);
		if (ret < 0) 
			return ret;
		return bi.data.bits & 0xff;
	}
#endif /* HDRVC_KERNEL */
	errno = EAGAIN;
	return -1;
}

/* ---------------------------------------------------------------------- */

int hdrvc_get_bits(void)
{
	int ret;
	struct hdlcdrv_ioctl bi;
	
#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		ret = hdrvc_hdlcdrv_ioctl(HDLCDRVCTL_GETBITS, &bi);
		if (ret < 0) 
			return ret;
		return bi.data.bits & 0xff;
	}
#endif /* HDRVC_KERNEL */
	errno = EAGAIN;
	return -1;
}

/* ---------------------------------------------------------------------- */

int hdrvc_get_channel_access_param(struct hdrvc_channel_params *par)
{
	struct hdlcdrv_ioctl hi;
	int ret;
	struct usersmmsg msg;

#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		if ((ret = hdrvc_hdlcdrv_ioctl(HDLCDRVCTL_GETCHANNELPAR, &hi)) < 0)
			return ret;
		if (!par) {
			errno = EINVAL;
			return -1;
		}
		par->tx_delay = hi.data.cp.tx_delay;
		par->tx_tail = hi.data.cp.tx_tail;
		par->slottime = hi.data.cp.slottime;
		par->ppersist = hi.data.cp.ppersist;
		par->fulldup = hi.data.cp.fulldup;
		return 0;
	}
#endif /* HDRVC_KERNEL */
	msg.hdr.type = USERSM_CMD_REQ_CHACCESS_PAR;
	msg.hdr.channel = 0;
	hdrvc_sendmsg(&msg, 0);
	ret = hdrvc_recvmsg(&msg, sizeof(msg), USERSM_CMD_ACK_CHACCESS_PAR);
	if (ret < 0)
		return ret;
	if (ret < sizeof(msg.data.cp)) {
		errno = EIO;
		return -1;
	}
	par->tx_delay = msg.data.cp.tx_delay;
	par->tx_tail = msg.data.cp.tx_tail;
	par->slottime = msg.data.cp.slottime;
	par->ppersist = msg.data.cp.ppersist;
	par->fulldup = msg.data.cp.fulldup;
	return 0;
}

/* ---------------------------------------------------------------------- */

int hdrvc_set_channel_access_param(struct hdrvc_channel_params par)
{
	int ret;
	struct usersmmsg msg;
	
#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		struct hdlcdrv_ioctl hi;
		
		hi.data.cp.tx_delay = par.tx_delay;
		hi.data.cp.tx_tail = par.tx_tail;
		hi.data.cp.slottime = par.slottime;
		hi.data.cp.ppersist = par.ppersist;
		hi.data.cp.fulldup = par.fulldup;
		if ((ret = hdrvc_hdlcdrv_ioctl(HDLCDRVCTL_SETCHANNELPAR, &hi)) < 0)
			return ret;
		return 0;
	}
#endif /* HDRVC_KERNEL */
	msg.hdr.type = USERSM_CMD_SET_CHACCESS_PAR;
	msg.hdr.channel = 0;
	msg.data.cp.tx_delay = par.tx_delay;
	msg.data.cp.tx_tail = par.tx_tail;
	msg.data.cp.slottime = par.slottime;
	msg.data.cp.ppersist = par.ppersist;
	msg.data.cp.fulldup = par.fulldup;
	hdrvc_sendmsg(&msg, sizeof(msg.data.cp));
	return 0;	
}

/* ---------------------------------------------------------------------- */

int hdrvc_calibrate(int calib)
{
	struct hdlcdrv_ioctl bhi;
	struct usersmmsg msg;

#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		bhi.data.calibrate = calib;
		return hdrvc_hdlcdrv_ioctl(HDLCDRVCTL_CALIBRATE, &bhi);
	}
#endif /* HDRVC_KERNEL */
	msg.hdr.type = USERSM_CMD_CALIBRATE;
	msg.hdr.channel = 0;
	msg.data.calib = calib;
	hdrvc_sendmsg(&msg, sizeof(msg.data.calib));
	return 0;
}

/* ---------------------------------------------------------------------- */

int hdrvc_get_channel_state(struct hdrvc_channel_state *st)
{
	struct hdlcdrv_ioctl bhi;
	int ret;
	struct usersmmsg msg;

	if (!st) {
		errno = EINVAL;
		return -1;
	}
#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		ret = hdrvc_hdlcdrv_ioctl(HDLCDRVCTL_GETSTAT, &bhi);
		if (ret >= 0) {
			st->ptt = bhi.data.cs.ptt;
			st->dcd = bhi.data.cs.dcd;
			st->ptt_keyed = bhi.data.cs.ptt_keyed;
			st->tx_packets = bhi.data.cs.tx_packets;
			st->tx_errors = bhi.data.cs.tx_errors;
			st->rx_packets = bhi.data.cs.rx_packets;
			st->rx_errors = bhi.data.cs.rx_errors;
		}
		return ret;
	}
#endif /* HDRVC_KERNEL */
	msg.hdr.type = USERSM_CMD_REQ_CHANNELSTATE;
	msg.hdr.channel = 0;
	hdrvc_sendmsg(&msg, 0);
	ret = hdrvc_recvmsg(&msg, sizeof(msg), USERSM_CMD_ACK_CHANNELSTATE);
	if (ret < 0)
		return ret;
	if (ret < sizeof(msg.data.cs)) {
		errno = EIO;
		return -1;
	}
	st->ptt = msg.data.cs.ptt;
	st->dcd = msg.data.cs.dcd;
	st->ptt_keyed = msg.data.cs.ptt_keyed;
	st->tx_packets = msg.data.cs.tx_packets;
	st->tx_errors = msg.data.cs.tx_errors;
	st->rx_packets = msg.data.cs.rx_packets;
	st->rx_errors = msg.data.cs.rx_errors;
	return 0;
}

/* ---------------------------------------------------------------------- */

int hdrvc_diag2(unsigned int mode, unsigned int flags, short *data, 
		unsigned int maxdatalen, unsigned int *samplesperbit)
{
	int ret;
	struct usersmmsg msg;
	static unsigned int modeconvusersm[4] = {
		USERSM_DIAGMODE_OFF, USERSM_DIAGMODE_INPUT, USERSM_DIAGMODE_DEMOD, 
		USERSM_DIAGMODE_CONSTELLATION
	};
	
	if (mode > HDRVC_DIAGMODE_CONSTELLATION) {
		errno = EINVAL;
		return -1;
	}
#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		struct sm_ioctl smi;
		static unsigned int modeconvsm[4] = {
			SM_DIAGMODE_OFF, SM_DIAGMODE_INPUT, SM_DIAGMODE_DEMOD, SM_DIAGMODE_CONSTELLATION
		};

		smi.data.diag.mode = modeconvsm[mode];
		smi.data.diag.flags = (flags & HDRVC_DIAGFLAG_DCDGATE) ? SM_DIAGFLAG_DCDGATE : 0;
		smi.data.diag.samplesperbit = 0;
		smi.data.diag.datalen = maxdatalen;
		smi.data.diag.data = data;
		if ((ret = hdrvc_sm_ioctl(SMCTL_DIAGNOSE, &smi)) < 0)
			return ret;
		if (samplesperbit)
			*samplesperbit = smi.data.diag.samplesperbit;
		if (smi.data.diag.mode != modeconvsm[mode] || !(smi.data.diag.flags & SM_DIAGFLAG_VALID))
			return 0;
		return smi.data.diag.datalen;
	}
#endif /* HDRVC_KERNEL */
	msg.hdr.type = USERSM_CMD_REQ_DIAG;
	msg.hdr.channel = 0;
	msg.data.diag.mode = modeconvusersm[mode];
	msg.data.diag.flags = (flags & HDRVC_DIAGFLAG_DCDGATE) ? USERSM_DIAGFLAG_DCDGATE : 0;
	msg.data.diag.samplesperbit = 0;
	msg.data.diag.datalen = maxdatalen;
	hdrvc_sendmsg(&msg, sizeof(msg.data.diag));
	ret = hdrvc_recvmsg(&msg, sizeof(msg), USERSM_CMD_ACK_DIAG);
	if (ret < 0)
		return ret;
	if (ret < sizeof(msg.data.diag) || ret < sizeof(msg.data.diag)+msg.data.diag.datalen*sizeof(short)) {
		errno = EIO;
		return -1;
	}
	if (samplesperbit)
		*samplesperbit = msg.data.diag.samplesperbit;
	if (msg.data.diag.mode != modeconvusersm[mode] || !(msg.data.diag.flags & USERSM_DIAGFLAG_VALID))
		return 0;
	if (!data || msg.data.diag.datalen <= 0)
		return 0;
	memcpy(data, msg.data.diag_out.samples, msg.data.diag.datalen*sizeof(short));
	return msg.data.diag.datalen;
}

/* ---------------------------------------------------------------------- */

int hdrvc_get_driver_name(char *buf, int bufsz)
{
	int ret;
	struct usersmmsg msg;

#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		struct hdlcdrv_ioctl bhi;

		ret = hdrvc_hdlcdrv_ioctl(HDLCDRVCTL_DRIVERNAME, &bhi);
		if (ret < 0)
			return ret;
		strncpy(buf, bhi.data.modename, bufsz);
		return 0;
	}
#endif /* HDRVC_KERNEL */
	msg.hdr.type = USERSM_CMD_REQ_DRVNAME;
	msg.hdr.channel = 0;
	hdrvc_sendmsg(&msg, 0);
	ret = hdrvc_recvmsg(&msg, sizeof(msg), USERSM_CMD_ACK_DRVNAME);
	if (ret < 0)
		return ret;
	if (ret < 1) {
		errno = EIO;
		return -1;
	}
	if (bufsz < ret)
		ret = bufsz;
	strncpy(buf, msg.data.by, ret);
	buf[ret-1] = 0;
	return 0;
}

/* ---------------------------------------------------------------------- */

int hdrvc_get_mode_name(char *buf, int bufsz)
{
	int ret;
	struct usersmmsg msg;

#ifdef HDRVC_KERNEL
	if (kernel_mode) {
		struct hdlcdrv_ioctl bhi;

		ret = hdrvc_hdlcdrv_ioctl(HDLCDRVCTL_GETMODE, &bhi);
		if (ret < 0)
			return ret;
		strncpy(buf, bhi.data.modename, bufsz);
		return 0;
	}
#endif /* HDRVC_KERNEL */
	msg.hdr.type = USERSM_CMD_REQ_DRVMODE;
	msg.hdr.channel = 0;
	hdrvc_sendmsg(&msg, 0);
	ret = hdrvc_recvmsg(&msg, sizeof(msg), USERSM_CMD_ACK_DRVMODE);
	if (ret < 0)
		return ret;
	if (ret < 1) {
		errno = EIO;
		return -1;
	}
	if (bufsz < ret)
		ret = bufsz;
	strncpy(buf, msg.data.by, ret);
	buf[ret-1] = 0;
	return 0;
}

/* ---------------------------------------------------------------------- */
