/* Copyright 1999 PSO Systems Inc */

/*
 * BSD compatible routines that are OS independent.  OS dependent portions
 * are in OS specific xxx_port.c file.  When necessary a generic call is made
 * to the OS specific layer which resides in xxx_port.c that actually
 * makes the OS specific system calls or routines.
 *
 * Written by: PSO Systems Inc
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/mount.h>
#include <sys/device.h>
#include <sys/reboot.h>
#include <sys/conf.h>
#include <sys/disklabel.h>
#include <sys/queue.h>

#include <sys/types.h>
#include <sys/param.h>
#include <sys/mbuf.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/protosw.h>
#include <sys/socketvar.h>
#include <net/netisr.h>

#define HZ 100

#include <sys/debug.h>
#include "bsd_only.h"
#include "common.h"

int hz;
int tick;
int tickadj;
int msize = MSIZE;
int mclbytes = MCLBYTES;

extern struct timeval timeval_tick_get();
extern void * memory_alloc(int sz);
extern void send_signal(int tid, int sig);
extern unsigned int timer_tick_rate_get();
extern void inet_ntoa_b(struct in_addr in, char *b);
extern int fd_get_new();
extern void fd_free(int fd);

void *splimp();
void *splhigh();
void *splclock();
void *splnet();
void *splsoftnet();
void splx(void *);
void release_all_spl_temporarily(int);
void recapture_all_tempory_spl(int);

void *splsoftnet_mutex = 0;
void *splnet_mutex = 0;
void *splimp_mutex = 0;
void *splhigh_mutex = 0;
void *splclock_mutex = 0;

int bsd_compat_initialized = 0;

/*
 * a number of initializations required for this port
 */
void 
init_bsd_compat()
{
	int s;

        if (bsd_compat_initialized)
                return;

        bsd_compat_initialized++;

	dbg_trace("init_bsd_compat\n");
	init_rtos_port(HZ);	/* sets hz (timer_tick_rate) */
	hz = timer_tick_rate_get();  
	tick = 1000000 / HZ;
	tickadj = 240000 / ( 60 * HZ);	/* can adjust 240ms in 60s */

	dbg_trace("init_bsd_compat: mutex init\n");

	splsoftnet_mutex = (void *)alloc_mutex(1);
	splnet_mutex = (void *)alloc_mutex(1);
	splimp_mutex = (void *)alloc_mutex(1);
	splhigh_mutex = (void *)alloc_mutex(1);
	splclock_mutex = (void *)alloc_mutex(1);

	init_mutex(splsoftnet_mutex);
	init_mutex(splnet_mutex);
	init_mutex(splimp_mutex);
	init_mutex(splhigh_mutex);
	init_mutex(splclock_mutex);

	release_mutex(splsoftnet_mutex, 1);
	release_mutex(splnet_mutex, 1);
	release_mutex(splimp_mutex, 1);
	release_mutex(splhigh_mutex, 1);
	release_mutex(splclock_mutex, 1);

	dbg_trace("bsd stack init\n");

	/*
	 * similar order as init_main.c in BSD 
	 */

	mbinit();
	soinit();
	s = (int)splimp();
	ifinit();
	domaininit();
	splx((void *)s);
	dbg_trace("init_bsd_compat: returning\n");
}

/* We run in single address space, so just use bcopy */
int 
copyin(const void *from, void *to, size_t len)
{
	bcopy(from, to, len);
	return 0;
}

int 
copyout(const void *from, void *to, size_t len)
{
	bcopy(from, to, len);
	return 0;
}

/* Unlike original BSD we have a thread per network driver that does bulk
of the work required in moving data from HW, as opposed to doing that
work in interrupt level code.  The reason we dedicate a thread per network
driver is to prioritize the I/O per device.  Each net device can have
its own associated network I/O thread which provides context for doing
I/O movement.  This is arguably better than the way some RTOS uses one
network thread everything through one network thread. */

int 
init_netisr_handler(struct ifnet *ifp, void (*func)(int), 
	int priority, int stacksize)
{
	char *name;
	int retval;

	if (!ifp)
		panic("init_netisr_handler: null ifp");
	
	name = ifp->if_xname;
	ifp->if_isr = 0;
	retval = create_thread(name, func, priority, stacksize, ifp);
	if (retval < 0)
		panic("init_netisr_handler: can't create I/O thread");

	/* Note: we always allocate if_wait mutex */
	ifp->if_wait = (void *)alloc_mutex(1);
	init_mutex(ifp->if_wait);

	ifp->if_thread_flags = 0;
	ifp->if_thread = retval;
	return retval;
}

/*
 * Note: unlike original BSD version, our schenetisr takes two arguments.
 * the first arg 'ifp' is added to keep track of the interface originating
 * the request.  Also note the addition and use of if_isr which is used
 * to keep track of the number of outstanding requests.  They are used
 * by the driver specific threads while in a processing loop.
 * Via use of if_wait, the driver threads are woken up if they are 
 * not already doing something.
 */
void 
schednetisr(struct ifnet *ifp, int isr)
{
	if (!ifp)
		panic("schednetisr: null ifp");
	if (!isr)
		panic("schednetisr: null isr");
	if ((ifp->if_flags & IFF_UP) == 0)
		panic("schednetisr: interface is down\n");

	ifp->if_isr++;

	if (isr == NETISR_ARP)
		ifp->if_thread_flags |= IFF_THREAD_ARP;
	else if (isr != NETISR_IP)
		panic("schednetisr: bad isr %d\n",isr);

	if ((ifp->if_thread_flags & IFF_THREAD_BUSY) == 0)
		bsd_wakeup(ifp->if_wait);		
}

#if 0 /* XXX taken out to make sure you don't call it */
int 
suser(struct ucred *cred, u_short *acflag)
{
	printf("suser: unsupported\n");
	return 0;
}
#endif

/* FIXME make this better */
void 
panic(const char *str, ...)
{
	dbg_printf("panic: %s\n", str);	/* TODO: better */
	context_suspend();
}

/*
 * Return the best possible estimate of the time in the timeval
 * to which tvp points.  We do this by returning the current time
 * plus the amount of time since the last clock interrupt (clock.c:clkread).
 *
 * Check that this time is no less than any previously-reported time,
 * which could happen around the time of a clock adjustment.  Just for fun,
 * we guarantee that the time will be greater than the value obtained by a
 * previous call.
 */
void 
microtime(tvp)
        register struct timeval *tvp;
{
        int s = (int)splhigh();
        static struct timeval lasttime;			/* XXX */

        *tvp = timeval_tick_get();
        while (tvp->tv_usec > 1000000) {
                tvp->tv_sec++;
                tvp->tv_usec -= 1000000;
        }
        if (tvp->tv_sec == lasttime.tv_sec &&
            tvp->tv_usec <= lasttime.tv_usec &&
            (tvp->tv_usec = lasttime.tv_usec + 1) > 1000000) {
                tvp->tv_sec++;
                tvp->tv_usec -= 1000000;
        }
        lasttime = *tvp;
        splx((void *)s);
}

void 
psignal(int pid, int sig)
{
	send_signal(pid, sig);
}

/* generic kernel uiomove */
int 
uiomove(buf, n, uio)
	register void *buf;
	register int n;
	register struct uio *uio;
{
	register struct iovec *iov;
	u_int cnt;
	int error = 0;
	char *cp = buf;

#ifdef DIAGNOSTIC
	if (uio->uio_rw != UIO_READ && uio->uio_rw != UIO_WRITE)
		panic("uiomove: mode");
	if (uio->uio_segflg == UIO_USERSPACE && uio->uio_procp != curproc)
		panic("uiomove proc");
#endif
	while (n > 0 && uio->uio_resid) {
		iov = uio->uio_iov;
		cnt = iov->iov_len;
		if (cnt == 0) {
			uio->uio_iov++;
			uio->uio_iovcnt--;
			continue;
		}
		if (cnt > n)
			cnt = n;
		switch (uio->uio_segflg) {

		case UIO_USERSPACE:
			if (uio->uio_rw == UIO_READ)
				error = copyout(cp, iov->iov_base, cnt);
			else
				error = copyin(iov->iov_base, cp, cnt);
			if (error)
				return (error);
			break;

		case UIO_SYSSPACE:
			if (uio->uio_rw == UIO_READ)
				memcpy(iov->iov_base, cp, cnt);
			else
				memcpy(cp, iov->iov_base, cnt);
			break;
		}
		(caddr_t)iov->iov_base += cnt;
		iov->iov_len -= cnt;
		uio->uio_resid -= cnt;
		uio->uio_offset += cnt;
		cp += cnt;
		n -= cnt;
	}
	return (error);
}

/* bsd kernel style timeout functions */
void 
timeout(func, timo, arg, ticks)
        void (*func) __P((void *));
	void *timo;
	void *arg;
	register int ticks;
{
	timeout_install(func, timo, arg, ticks);
}

void 
untimeout(func, timo, arg)
        void (*func) __P((void *));
	void *timo;
        void *arg; 
{
	timeout_cancel(func, timo, arg);
}

/* tsleep <-> bsd_wakeup */

int 
tsleep(ident, priority, wmesg, timo)
        void *ident;
        int priority, timo;
        const char *wmesg;
{
	int recapture = 0;

	if (!ident) 
		panic("tsleep");
#ifdef DEBUG1
	printf("tsleep: 0x%x <%s>\n", ident, wmesg);
#endif
	recapture = context_flag_value() & PSO_SPLSTUFF;
	if (recapture) 
		release_all_spl_temporarily(recapture);
	obtain_mutex(ident, timo, 0);
	if (recapture) 
		recapture_all_tempory_spl(recapture);
	return 0;
}

/* originally called wakeup() in Unix */

void 
bsd_wakeup(ident)
        register void *ident;
{
	if (!ident)
		panic("bsd_wakeup");
#ifdef DEBUG1
	printf("bsd_wakeup: 0x%x\n", ident);
#endif
	release_mutex(ident, 0);
}

/*
 * General routine to allocate a hash table.
 * Allocate enough memory to hold at least `elements' list-head pointers.
 * Return a pointer to the allocated space and set *hashmask to a pattern
 * suitable for masking a value to use as an index into the returned array.
 */
void * 
hashinit(elements, type, flags, hashmask)
        int elements, type, flags;
        u_long *hashmask;
{
        long hashsize;
        LIST_HEAD(generic, generic) *hashtbl;
        int i;

        if (elements <= 0)
                panic("hashinit: bad cnt");
        for (hashsize = 1; hashsize < elements; hashsize <<= 1)
                continue;
        hashtbl = malloc((u_long)hashsize * sizeof(*hashtbl), type, flags);
        for (i = 0; i < hashsize; i++)
                LIST_INIT(&hashtbl[i]);
        *hashmask = hashsize - 1;
        return (hashtbl);
}

/*
 * These pool routines should be optimized better.  For now they
 * rely on standard malloc/free included in the library (Doug Lea's
 * algorithm) which is quite good.  However, it might be better
 * and more deterministic to port BSD original pool routines.
 * However advantage of simple approach as it stands now is that
 * memory gets used better since no seperate statically carved
 * memory region is not required.  All memory is shared among all
 * tasks and system, as well as clients of pools (like mbufs).
 * Still, we may need to change this later for a number of reasons.
 */
void
pool_init(pp, size, align, ioff, flags, wchan, pagesz, alloc, release, mtype)
        struct pool     *pp;
        size_t          size;
        u_int           align;
        u_int           ioff;
        int             flags;
        char            *wchan;
        size_t          pagesz;
        void            *(*alloc) __P((unsigned long, int, int));
        void            (*release) __P((void *, unsigned long, int));
        int             mtype;
{
        pp->pr_size = ALIGN(size);
}

void * 
pool_get(pp, flags)
        struct pool *pp;
        int flags;
{
	if (pp->pr_size <= 0)
		panic("pool_get: insane pr_size");
	return ((void *)memory_alloc(pp->pr_size));
}

void 
pool_put(pp, v)
        struct pool *pp;
        void *v;
{
	memory_free(v);
}

/*
 * Validate parameters and get old / set new parameters
 * for an integer-valued sysctl function.
 */
int 
sysctl_int(oldp, oldlenp, newp, newlen, valp)
        void *oldp;
        size_t *oldlenp;
        void *newp; 
        size_t newlen;
        int *valp;
{
        int error = 0;

        if (oldp && *oldlenp < sizeof(int))
                return (ENOMEM);
        if (newp && newlen != sizeof(int))
                return (EINVAL);
        *oldlenp = sizeof(int);
        if (oldp)
                error = copyout(valp, oldp, sizeof(int));
        if (error == 0 && newp)
                error = copyin(newp, valp, sizeof(int));
        return (error);
}

/*
 * As above, but read-only.
 */
int 
sysctl_rdint(oldp, oldlenp, newp, val)
        void *oldp;
        size_t *oldlenp;
        void *newp;
        int val;
{
        int error = 0;

        if (oldp && *oldlenp < sizeof(int))
                return (ENOMEM);
        if (newp)
                return (EPERM);
        *oldlenp = sizeof(int);
        if (oldp)
                error = copyout((caddr_t)&val, oldp, sizeof(int));
        return (error);
}


/*
 * Create a new open file structure and allocate
 * a file decriptor for the process that refers to it.
 */
int 
falloc(p, resultfp, resultfd)
        register struct proc *p;
        struct file **resultfp;
        int *resultfd;
{
	*resultfd = fd_get_new();
        return 0;
}

/*
 * Free a file descriptor.
 */
void 
ffree(fp)
        register struct file *fp;
{
	int fd;

	fd = (int)fp;		/* XXX consider fp pointer == fd */
	fd_free(fd);
}

/* FIXME  this probably belongs in a libarary somewhere */
void 
inet_netof_string(char *addr, char *net)
{
	struct in_addr addr1, addr2;

	addr1.s_addr = inet_addr(addr);
	addr2.s_addr = in_netof(addr1);
	inet_ntoa_b(addr2, net);
}

/* Some routines used for socket select support */

int 
sock_read_select(struct socket *so, void *selnode)
{
	if (soreadable(so)) {
		selwakeup_node(selnode);
		return 1;
	}
	return 0;
}

int 
sock_write_select(struct socket *so, void *selnode)
{
	if (sowriteable(so)) {
		selwakeup_node(selnode);
		return 1;
	}
	return 0;
}

void 
sock_read_select_indicate(struct socket *so, void *selnode)
{
	selrecord(so->so_rcv.sb_sel_wait, selnode);
	so->so_rcv.sb_flags |= SB_SEL;
}

void 
sock_write_select_indicate(struct socket *so, void *selnode)
{
	selrecord( so->so_snd.sb_sel_wait, selnode);
	so->so_snd.sb_flags |= SB_SEL;
}

void 
sock_read_select_cancel(struct socket *so, void *selnode)
{
	selrecord_read_cancel(so, so->so_rcv.sb_sel_wait, selnode);
}

void 
sock_write_select_cancel(struct socket *so, void *selnode)
{
	selrecord_write_cancel(so, so->so_snd.sb_sel_wait, selnode);
}

void 
sock_read_select_nomore(struct socket *so)
{
	so->so_rcv.sb_flags &= ~SB_SEL;
}

void 
sock_write_select_nomore(struct socket *so)
{
	so->so_snd.sb_flags &= ~SB_SEL;
}

/* Some hacks to "emulate" Unix SPL stuff.  We provide mutex protection via
semaphores.  We do not lock out interrupts like Unix.  */

void * 
splsoftnet()
{
	void *retval;

#ifdef OPTIMIZE_SPL
	if (context_flag_value() & PSO_SPLSOFTNET)
		return 0;
#endif
	retval = (void *)obtain_mutex(splsoftnet_mutex, 0, 1);
	insert_context_flag(PSO_SPLSOFTNET);
	return retval;
}

void * 
splimp()
{
	void *retval;

#ifdef OPTIMIZE_SPL
	if (context_flag_value() & PSO_SPLIMP)
		return 0;
#endif
	retval = (void *)obtain_mutex(splimp_mutex, 0, 1);
	insert_context_flag(PSO_SPLIMP);
	return retval;
}

void * 
splhigh()
{
	void *retval;


#ifdef OPTIMIZE_SPL
	if (context_flag_value() & PSO_SPLHIGH)
		return 0;
#endif
	retval = (void *)obtain_mutex(splhigh_mutex, 0, 1);
	insert_context_flag(PSO_SPLHIGH);
	return retval;
}

void * 
splclock()
{
	void *retval;


#ifdef OPTIMIZE_SPL
	if (context_flag_value() & PSO_SPLCLOCK)
		return 0;
#endif
	retval = (void *)obtain_mutex(splclock_mutex, 0, 1);
	insert_context_flag(PSO_SPLCLOCK);
	return retval;
}

void * 
splnet()
{
	void *retval;


#ifdef OPTIMIZE_SPL
	if (context_flag_value() & PSO_SPLNET)
		return 0;
#endif
	retval = (void *)obtain_mutex(splnet_mutex, 0, 1);
	insert_context_flag(PSO_SPLNET);
	return retval;
}

void 
splx(void *p)
{
#ifdef OPTIMIZE_SPL
	if (p == 0)
		return;
#else
	if (p == 0)
		;	/* don't remove any flags */
#endif
	else if (p == splsoftnet_mutex)	/* XXX not very efficient here */
		remove_context_flag(PSO_SPLSOFTNET);
	else if (p == splnet_mutex)
		remove_context_flag(PSO_SPLNET);
	else if (p == splclock_mutex)
		remove_context_flag(PSO_SPLCLOCK);
	else if (p == splimp_mutex)
		remove_context_flag(PSO_SPLIMP);
	else if (p == splhigh_mutex)
		remove_context_flag(PSO_SPLHIGH);
	else
		panic("splx: invalid spl mutex passed");

	release_mutex(p, 1);
}

/* for debugging */
void 
print_spl_mutex()
{
	printf("splsoftnet_mutex 0x%x\n",splsoftnet_mutex );
	printf("splnet_mutex 0x%x\n",splnet_mutex );
	printf("splimp_mutex 0x%x\n",splimp_mutex );
	printf("splhigh_mutex 0x%x\n",splhigh_mutex );
	printf("splclock_mutex 0x%x\n",splclock_mutex );
}

/* TODO: take care of cases where the same spl's are taken multiple times
and tsleep is subsequently called.  This is not yet correctly handled.  */

void 
release_all_spl_temporarily(int recapture)
{
	/* XXX not very efficient */
	if (recapture & PSO_SPLNET)
		splx(splnet_mutex);
	if (recapture & PSO_SPLSOFTNET)
		splx(splsoftnet_mutex);
	if (recapture & PSO_SPLHIGH)
		splx(splhigh_mutex);
	if (recapture & PSO_SPLCLOCK)
		splx(splclock_mutex);
	if (recapture & PSO_SPLIMP)
		splx(splimp_mutex);
}

void 
recapture_all_tempory_spl(int recapture)
{
	/* XXX not very efficient */
	if (recapture & PSO_SPLNET)
		splnet();
	if (recapture & PSO_SPLSOFTNET)
		splsoftnet();
	if (recapture & PSO_SPLHIGH)
		splhigh();
	if (recapture & PSO_SPLCLOCK)
		splclock();
	if (recapture & PSO_SPLIMP)
		splimp();
}

/*
 * Pseudo-random number generator for randomizing the profiling clock,
 * and whatever else we might use it for.  The result is uniform on
 * [0, 2^31 - 1].
 */
u_long
random()
{
	static u_long randseed = 1;
	register long x, hi, lo, t;

	/*
	 * Compute x[n + 1] = (7^5 * x[n]) mod (2^31 - 1).
	 * From "Random number generators: good ones are hard to find",
	 * Park and Miller, Communications of the ACM, vol. 31, no. 10,
	 * October 1988, p. 1195.
	 */
	x = timer_tick_get();
	hi = x / 127773;
	lo = x % 127773;
	t = 16807 * lo - 2836 * hi;
	if (t <= 0)
		t += 0x7fffffff;
	randseed = t;
	return (t);
}

/* wait 'n' microseconds */
void
delay(unsigned int n)
{
        delay_micro_seconds(n);
}

void
interrupt_establish(unsigned int irq, int (*func)(void *), void *arg)
{
	int vec;

	vec = (32 + irq) << 3;
	dbg_printf("interrupt_establish: irq 0x%x vec 0x%x\n", irq, vec);
	connect_interrupt_handler(vec, func, arg);
}
