/*
 * Helper functions that wrap raw l4 syscalls.
 *
 * Copyright (C) 2007-2009 Bahadir Bilgehan Balban
 */

#ifndef __L4LIB_SYSLIB_H__
#define __L4LIB_SYSLIB_H__

#include <stdio.h>
#include <l4lib/arch/syscalls.h>
#include <l4/macros.h>

/*
 * NOTE:
 * Its best to use these wrappers because they generalise the way
 * common ipc data like sender id, error, ipc tag are passed
 * between ipc parties.
 *
 * The arguments to l4_ipc() are used by the microkernel to initiate
 * the ipc. Any data passed in message registers may or may not be
 * a duplicate of this data, but the distinction is that anything
 * that is passed via the mrs are meant to be used by the other party
 * participating in the ipc.
 */

/* For system call arguments */
#define L4SYS_ARG0	(MR_UNUSED_START)
#define	L4SYS_ARG1	(MR_UNUSED_START + 1)
#define L4SYS_ARG2	(MR_UNUSED_START + 2)
#define L4SYS_ARG3	(MR_UNUSED_START + 3)


#define L4_IPC_TAG_MASK			0x00000FFF


/*
 * Servers get sender.
 */
static inline l4id_t l4_get_sender(void)
{
	return (l4id_t)read_mr(MR_SENDER);
}

/*
 * When doing an ipc the sender never has to be explicitly set in
 * the utcb via this function since this information is found out
 * by the microkernel by checking the system caller's id. This is
 * only used for restoring the sender on the utcb in order to
 * complete an earlier ipc.
 */
static inline void l4_set_sender(l4id_t sender)
{
	write_mr(MR_SENDER, sender);
}

static inline unsigned int l4_set_ipc_size(unsigned int word, unsigned int size)
{
	word &= ~L4_IPC_FLAGS_SIZE_MASK;
	word |= ((size << L4_IPC_FLAGS_SIZE_SHIFT) & L4_IPC_FLAGS_SIZE_MASK);
	return word;
}

static inline unsigned int l4_get_ipc_size(unsigned int word)
{
	return (word & L4_IPC_FLAGS_SIZE_MASK) >> L4_IPC_FLAGS_SIZE_SHIFT;
}

static inline unsigned int l4_set_ipc_msg_index(unsigned int word, unsigned int index)
{
	/* FIXME: Define MR_PRIMARY_TOTAL, MR_TOTAL etc. and use MR_TOTAL HERE! */
	BUG_ON(index > UTCB_SIZE);

	word &= ~L4_IPC_FLAGS_MSG_INDEX_MASK;
	word |= (index << L4_IPC_FLAGS_MSG_INDEX_SHIFT) &
		 L4_IPC_FLAGS_MSG_INDEX_MASK;
	return word;
}

static inline unsigned int l4_get_ipc_msg_index(unsigned int word)
{
	return (word & L4_IPC_FLAGS_MSG_INDEX_MASK)
	       >> L4_IPC_FLAGS_MSG_INDEX_SHIFT;
}

static inline unsigned int l4_set_ipc_flags(unsigned int word, unsigned int flags)
{
	word &= ~L4_IPC_FLAGS_TYPE_MASK;
	word |= flags & L4_IPC_FLAGS_TYPE_MASK;
	return word;
}

static inline unsigned int l4_get_ipc_flags(unsigned int word)
{
	return word & L4_IPC_FLAGS_TYPE_MASK;
}

static inline unsigned int l4_get_tag(void)
{
	return read_mr(MR_TAG) & L4_IPC_TAG_MASK;
}

static inline void l4_set_tag(unsigned int tag)
{
	unsigned int tag_flags = read_mr(MR_TAG);

	tag_flags &= ~L4_IPC_TAG_MASK;
	tag_flags |= tag & L4_IPC_TAG_MASK;

	write_mr(MR_TAG, tag_flags);
}

/* Servers:
 * Sets the message register for returning errors back to client task.
 * These are usually posix error codes.
 */
static inline void l4_set_retval(int retval)
{
	write_mr(MR_RETURN, retval);
}

/* Clients:
 * Learn result of request.
 */
static inline int l4_get_retval(void)
{
	return read_mr(MR_RETURN);
}

/*
 * If we're about to do another ipc, this saves the last ipc's
 * parameters such as the sender and tag information.
 * Any previously saved data in save slots are destroyed.
 */
static inline void l4_save_ipcregs(void)
{
	l4_get_utcb()->saved_sender = l4_get_sender();
	l4_get_utcb()->saved_tag = l4_get_tag();
}

static inline void l4_restore_ipcregs(void)
{
	l4_set_tag(l4_get_utcb()->saved_tag);
	l4_set_sender(l4_get_utcb()->saved_sender);
}

static inline l4id_t self_tid(void)
{
	struct task_ids ids;

	l4_getid(&ids);
	return ids.tid;
}

static inline int l4_send_full(l4id_t to, unsigned int tag)
{
	l4_set_tag(tag);
	return l4_ipc(to, L4_NILTHREAD, L4_IPC_FLAGS_FULL);
}

static inline int l4_receive_full(l4id_t from)
{
	return l4_ipc(L4_NILTHREAD, from, L4_IPC_FLAGS_FULL);
}

static inline int l4_sendrecv_full(l4id_t to, l4id_t from, unsigned int tag)
{
	int err;

	BUG_ON(to == L4_NILTHREAD || from == L4_NILTHREAD);
	l4_set_tag(tag);

	err = l4_ipc(to, from, L4_IPC_FLAGS_FULL);

	return err;
}

static inline int l4_send_extended(l4id_t to, unsigned int tag,
				   unsigned int size, void *buf)
{
	unsigned int flags = 0;

	l4_set_tag(tag);

	/* Set up flags word for extended ipc */
	flags = l4_set_ipc_flags(flags, L4_IPC_FLAGS_EXTENDED);
	flags = l4_set_ipc_size(flags, size);
	flags = l4_set_ipc_msg_index(flags, L4SYS_ARG0);

	/* Write buffer pointer to MR index that we specified */
	write_mr(L4SYS_ARG0, (unsigned long)buf);

	return l4_ipc(to, L4_NILTHREAD, flags);
}

static inline int l4_receive_extended(l4id_t from, unsigned int size, void *buf)
{
	unsigned int flags = 0;

	/* Indicate extended receive */
	flags = l4_set_ipc_flags(flags, L4_IPC_FLAGS_EXTENDED);

	/* How much data is accepted */
	flags = l4_set_ipc_size(flags, size);

	/* Indicate which MR index buffer pointer is stored */
	flags = l4_set_ipc_msg_index(flags, L4SYS_ARG0);

	/* Set MR with buffer to receive data */
	write_mr(L4SYS_ARG0, (unsigned long)buf);

	return l4_ipc(L4_NILTHREAD, from, flags);
}

/*
 * Return result value as extended IPC.
 *
 * Extended IPC copies up to 2KB user address space buffers.
 * Along with such an ipc, a return value is sent using a primary
 * mr that is used as the return register.
 *
 * It may not be desirable to return a payload on certain conditions,
 * (such as an error return value) So a nopayload field is provided.
 */
static inline int l4_return_extended(int retval, unsigned int size,
				     void *buf, int nopayload)
{
	unsigned int flags = 0;
	l4id_t sender = l4_get_sender();

	l4_set_retval(retval);

	/* Set up flags word for extended ipc */
	flags = l4_set_ipc_flags(flags, L4_IPC_FLAGS_EXTENDED);
	flags = l4_set_ipc_msg_index(flags, L4SYS_ARG0);

	/* Write buffer pointer to MR index that we specified */
	write_mr(L4SYS_ARG0, (unsigned long)buf);

	if (nopayload)
		flags = l4_set_ipc_size(flags, 0);
	else
		flags = l4_set_ipc_size(flags, size);

	return l4_ipc(sender, L4_NILTHREAD, flags);
}

static inline int l4_sendrecv_extended(l4id_t to, l4id_t from,
				       unsigned int tag, void *buf)
{
	/* Need to imitate sendrecv but with extended send/recv flags */
	return 0;
}

static inline int l4_send(l4id_t to, unsigned int tag)
{
	l4_set_tag(tag);

	return l4_ipc(to, L4_NILTHREAD, 0);
}

static inline int l4_sendrecv(l4id_t to, l4id_t from, unsigned int tag)
{
	int err;

	BUG_ON(to == L4_NILTHREAD || from == L4_NILTHREAD);
	l4_set_tag(tag);

	err = l4_ipc(to, from, 0);

	return err;
}

static inline int l4_receive(l4id_t from)
{
	return l4_ipc(L4_NILTHREAD, from, 0);
}

static inline void l4_print_mrs()
{
	printf("Message registers: 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n",
	       read_mr(0), read_mr(1), read_mr(2), read_mr(3),
	       read_mr(4), read_mr(5));
}

/* Servers:
 * Return the ipc result back to requesting task.
 */
static inline int l4_ipc_return(int retval)
{
	l4id_t sender = l4_get_sender();

	l4_set_retval(retval);

	/* Setting the tag would overwrite retval so we l4_send without tagging */
	return l4_ipc(sender, L4_NILTHREAD, 0);
}

void *l4_new_virtual(int npages);
void *l4_del_virtual(void *virt, int npages);

/* A helper that translates and maps a physical address to virtual */
static inline void *l4_map_helper(void *phys, int npages)
{
	struct task_ids ids;
	void *virt = l4_new_virtual(npages);

	l4_getid(&ids);
	l4_map(phys, virt, npages, MAP_USR_RW_FLAGS, ids.tid);
	return virt;
}


/* A helper that translates and maps a physical address to virtual */
static inline void *l4_unmap_helper(void *virt, int npages)
{
	struct task_ids ids;

	l4_getid(&ids);
	l4_unmap(virt, npages, ids.tid);
	l4_del_virtual(virt, npages);
	return 0;
}

#define L4_EXIT_MASK		0xFFFF

static inline void l4_exit(unsigned int exit_code)
{
	struct task_ids ids;
	l4_getid(&ids);
	l4_thread_control(THREAD_DESTROY |
			  (exit_code & L4_EXIT_MASK),
			  &ids);
}

#endif /* __L4LIB_SYSLIB_H__ */
