//@+leo-ver=4
//@+node:@file host/trimedia.c
//@@language c
//@<< lud-base #includes >>
//@+node:<< lud-base #includes >>
#ifdef CONFIG_SMP
#define __SMP__
#endif

#include <asm/segment.h>
#include <asm/io.h>
#include <asm/page.h>
#include <asm/semaphore.h>
#include <asm/atomic.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/byteorder.h>

#include <linux/module.h>

#include <linux/version.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/pci.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/spinlock.h>
#include <linux/kmod.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0))
#include <linux/device.h>
#include <linux/cdev.h>
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,18))
#include <linux/compiler.h>
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif /* LINUX_VERSION_CODE */

#include <stdarg.h>


#include "compat.h"
#include "lud_mmio.h"
#include "luddite-host.h"
#include "luddite/version.h"
#include "luddite/os.h"
#include "exported.h"

#include "core.h"
#include "procfs.h"
#include "node.h"
#include "helpers.h"
#include "channels.h"
//@-node:<< lud-base #includes >>
//@nl
//@+others
//@+node:checkTarget
// NAB: Prevent command overwrite on extremely loaded system
//#define TM_BORED_USEC	100000
#define TM_BORED_USEC	2000000

//@+at
//@nonl
// ************************************************************************ *
// * Low level Communications                         * *
// ************************************************************************
//@-at
//@@c

static int checkTarget(LUD_DEV* m)
{
	unsigned int vec = 1<<m->target_int_vec;
	return (((MMIO_TMread(m,IPENDING)&vec)==vec)|readl((IOTYPE)m->target_comm_vec));
}
//@-node:checkTarget
//@+node:intTarget

static void intTarget(LUD_DEV* m)
{
	unsigned int vec = 1<<m->target_int_vec;
	vec |= MMIO_TMread(m, IPENDING);
	MMIO_TMwrite(vec,m,IPENDING);
}
//@-node:intTarget
//@+node:tm_send_command
//@+at
//@nonl
// * * Default implementation of lud_send_command(). * Send a command to the
// TriMedia. There is no reply. * * @param tm  The link to use. * @param com
// The command to send. * @param a0  The first argument to the command. *
// @param a1  The second argument to the command. * @param a2  The third
// argument to the command. * @param a3  The fourth argument to the command.
//@-at
//@@c

void tm_send_command(LUD_DEV_LINK* tm,int com,int a0,int a1,int a2, int a3)
{
	LUD_DEV* m;
	LUD_COMMAND* hcom;

	if (unlikely(tm == NULL)) {
		printk(KERN_EMERG "bug: tm_send_command: Null link\n");
		return ;
	}
	m = lud_devices[tm->minor].luddev;
	hcom = (LUD_COMMAND*) m->target_comm_addr;

	down(&lud_devices[tm->minor].sem);

	if (checkTarget(m)) {
		int counter = 0;
		while (checkTarget(m)) {
			udelay(1);
			counter++;
			if (counter > TM_BORED_USEC) {
				printk("TM got bored waiting hog=0x%x\n",((unsigned int*)m->status)[0]);
				break;
			}
		}
	}

	if (readl((IOTYPE)m->target_comm_vec)!=0) {
		printk("TM %d not ready %x for com=%x,args=%x %x %x %x\n",
			tm->minor,readl((IOTYPE)m->target_comm_vec),com,a0,a1,a2,a3);
		printk("com=0x%x, reply=0x%x, %x %x %x %x\n",
				readl((IOTYPE)&hcom->command),
				readl((IOTYPE)&hcom->reply.pointer),
				readl((IOTYPE)&hcom->arg0),
				readl((IOTYPE)&hcom->arg1),
				readl((IOTYPE)&hcom->arg2),
				readl((IOTYPE)&hcom->arg3));
	}

	writel(com, (IOTYPE)&hcom->command);
	writel(LUD_REPLY_CPU_NONE, (IOTYPE)&hcom->reply.cpu);
	writel(0, (IOTYPE)&hcom->reply.pointer);
	writel(a0, (IOTYPE)&hcom->arg0);
	writel(a1, (IOTYPE)&hcom->arg1);
	writel(a2, (IOTYPE)&hcom->arg2);
	writel(a3, (IOTYPE)&hcom->arg3);

	writel(LUD_CREATE_HANDLE(tm->minor,tm->major),(IOTYPE)m->target_comm_vec);

	/* interrupt the Target */
	intTarget(m);

	up(&lud_devices[tm->minor].sem);
}
//@-node:tm_send_command
//@+node:tm_send_request
//@+at
//@nonl
// * * Default implementation of lud_send_request(). * Send a request to the
// TriMedia, and wait for a reply. * Can be interrupted by a Linux signal -
// e.g. Ctrl-C or SIGKILL. * * If the TriMedia side used send_ack_verbose,
// then on return the * verbose reply data will be stored in tm->retval0
// through * tm->retval3, and the return value of this function will be *
// equivalent to tm->retval0 (unless an error occurs). * * @param tm  The link
// to use. Only one request may be in progress *       on a single
// LUD_DEV_LINK at any one time. You can use *       lud_clone_link() to have
// unlimited numbers of LUD_DEV_LINK *       structures and hence unlimited
// simultaneous requests. * @param com The command to send. * @param a0  The
// first argument to the command. * @param a1  The second argument to the
// command. * @param a2  The third argument to the command. * @param a3  The
// fourth argument to the command. * @return   The result from the TriMedia,
// or -1 if the process is *       interrupted (e.g. by Ctrl-C or SIGKILL).
//@-at
//@@c

int tm_send_request(LUD_DEV_LINK* tm,int com,int a0,int a1,int a2, int a3)
{
	LUD_DEV* m = lud_devices[tm->minor].luddev;
	LUD_COMMAND* hcom = (LUD_COMMAND*) m->target_comm_addr;

	/* printk(KERN_CRIT "Luddite sending request:        reply=%#08x, args=%#08x,%#08x,%#08x,%#08x, com=%#08x\n",(int)tm,a0,a1,a2,a3,com);  */

	/* Prevent the LUD_DEV_LINK from being freed until we get an acknowlegement.
	 * This is important because we send the target a pointer to
	 * the LUD_DEV_LINK, then we could get interrupted (Ctrl-C) and return
	 * to the caller before getting the acknowlegement.  The caller
	 * might then free the LUD_DEV_LINK, and then we'd get the acknowlegement
	 * and use an invalid pointer - kernel panic!
	 *
	 * The fix is to reference-count the LUD_DEV_LINK.  'freecounter'
	 * is 1 initially - we only have a reference to the LUD_DEV_LINK on
	 * the host.  It is incremented to 2 here, since we're about to
	 * send a reference onto the Target.  The interrupt handler and the
	 * lud_free_link() function both decrement the 'freecounter',
	 * and the actual kfree() takes place when it reaches zero.
	 *
	 * Note that if the Target crashes, this is a memory leak.  However,
	 * that is preferable to a risk of a crash.  A fix for the memory
	 * leak would be to keep a list of LUD_LINKs which have been freed
	 * in Linux, and kfree() them when the lud-core.o module is
	 * unloaded, or when the board-specific driver (lud-pci or
	 * lud-pnx8525) is unloaded.  That has not yet been implemented.
	 *
	 * 'freecounter' is an atomic_t, so we don't need spinlocks
	 * even though it's accessed from an ISR.
	 */
	atomic_inc(&tm->freecounter);

	down(&lud_devices[tm->minor].sem);

	if (checkTarget(m)) {
		int counter = 0;
		while (checkTarget(m)) {
			udelay(1);
			counter++;
			if (counter > TM_BORED_USEC) {
				printk("TM got bored waiting hog=0x%lx\n",((unsigned long*)m->status)[0]);
				break;
			}
		}
	}

	if (readl((IOTYPE)m->target_comm_vec)!=0) {
		printk("TM %d not ready %x for r=%x,rep=%p,args=%x %x %x %x\n",
			tm->minor,readl((IOTYPE)m->target_comm_vec),com,tm,a0,a1,a2,a3);
		printk("com=0x%x, rp=%x reply=%x %x %x %x\n",
				readl((IOTYPE)&hcom->command),
				readl((IOTYPE)&hcom->reply.pointer),
				readl((IOTYPE)&hcom->arg0),
				readl((IOTYPE)&hcom->arg1),
				readl((IOTYPE)&hcom->arg2),
				readl((IOTYPE)&hcom->arg3));
	}

	writel(com, (IOTYPE)&hcom->command);
	writel(LUD_REPLY_CPU_HOST, (IOTYPE)&hcom->reply.cpu);
	writel(addr_to_token(tm), (IOTYPE)&hcom->reply.pointer) ;
	writel(a0, (IOTYPE)&hcom->arg0);
	writel(a1, (IOTYPE)&hcom->arg1);
	writel(a2, (IOTYPE)&hcom->arg2);
	writel(a3, (IOTYPE)&hcom->arg3);

	writel(LUD_CREATE_HANDLE(tm->minor,tm->major),(IOTYPE)m->target_comm_vec);

	tm->status = 0;

	/* interrupt the Target */
	intTarget(m);

	up(&lud_devices[tm->minor].sem);

	/* Wait for a reply - i.e. for (tm->status != 0) to become true */
	if (wait_event_interruptible(tm->q, (tm->status != 0)))
	{
		/* We were interrupted (by Ctrl-C or another signal) */
		return -1;
	}

	return tm->retval0;
}
//@-node:tm_send_request
//@+node:tm_send_ack
//@+at
//@nonl
// * * Default implementation of lud_send_ack(). * Send an acknowledgement to
// the TriMedia. This is used * to send a reply to a recieved request, it
// should not * be used to initiate communication. * * @param tm  The link to
// use. * @param reply An opaque number passed by the TriMedia *       when it
// sent the request. * @param what The reply to send.
//@-at
//@@c

void tm_send_ack(LUD_DEV_LINK* tm,LUD_REPLY reply,int what)
{
	LUD_DEV* m;
	LUD_COMMAND* hcom;

	if (unlikely(tm == NULL)) {
		printk(KERN_EMERG "bug: tm_send_ack: Null link\n");
		return ;
	}

	m = lud_devices[tm->minor].luddev;
	hcom = (LUD_COMMAND*) m->target_comm_addr;

	down(&lud_devices[tm->minor].sem);

	if (checkTarget(m)) {
		int counter = 0;
		while (checkTarget(m)) {
			udelay(1);
			counter++;
			if (counter > TM_BORED_USEC) {
				printk("TM got bored waiting hog=0x%x\n",((int*)m->status)[0]);
				break;
			}
		}
	}

	if (readl((IOTYPE)m->target_comm_vec)!=0) {
		printk("TM %d not ready %x for ack to %x,arg=%x\n",
			tm->minor,readl((IOTYPE)m->target_comm_vec),reply.pointer,what);
		printk("com=0x%x, rp=%x reply=%x %x %x %x\n",
				readl((IOTYPE)&hcom->command),
				readl((IOTYPE)&hcom->reply.pointer),
				readl((IOTYPE)&hcom->arg0),
				readl((IOTYPE)&hcom->arg1),
				readl((IOTYPE)&hcom->arg2),
				readl((IOTYPE)&hcom->arg3));
	}

	writel(LUD_SYS_REQUEST_ACK, (IOTYPE)&hcom->command);
	writel(reply.cpu, (IOTYPE)&hcom->reply.cpu);
	writel(reply.pointer, (IOTYPE)&hcom->reply.pointer);
	writel(what, (IOTYPE)&hcom->arg0);

	writel(LUD_CREATE_HANDLE(tm->minor,tm->major),(IOTYPE)m->target_comm_vec);

	/* interrupt the Target */
	intTarget(m);

	up(&lud_devices[tm->minor].sem);
}
//@-node:tm_send_ack
//@-others
//@-node:@file host/trimedia.c
//@-leo
