/*
 * (C) Finite State Machine Labs Inc. 1999-2001 <business@fsmlabs.com>
 *
 * Released under the terms of GPL 2.
 * Open RTLinux makes use of a patented process described in
 * US Patent 5,995,745. Use of this process is governed
 * by the Open RTLinux Patent License which can be obtained from
 * www.fsmlabs.com/PATENT or by sending email to
 * licensequestions@fsmlabs.com
 */
/*
 *  Originally derived from the Linux VFS code
 *  Copyright (C) 1991-1993  Linus Torvalds
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>

//#include <linux/config.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/stat.h>
#include <linux/fcntl.h>
#include <linux/errno.h>
#include <linux/ctype.h>
#include <asm/io.h>

#include <rtl_posixio.h>
//#include <sys/mman.h>
#include <posix/errno.h>
//#include <unistd.h>
#include <rtl.h>
#include <rtl_core.h>

MODULE_LICENSE("GPL");

#define MAX_RTL_FILES 128
#define MAX_RTL_CHRDEV 255

static struct rtl_file rtl_files [MAX_RTL_FILES] /* TODO __attribute__ ((aligned (64))) */ = {
	{ NULL, 0 },
};


struct device_struct {
	const char * name;
	struct rtl_file_operations * fops;
};

static struct device_struct rtldevs[MAX_RTL_CHRDEV] = {
	{ NULL, NULL },
};

int rtl_register_rtldev(unsigned int major, const char * name, struct rtl_file_operations *fops)
{
	if (major >= MAX_RTL_CHRDEV)
		return -EINVAL;
	if (rtldevs[major].fops && rtldevs[major].fops != fops) {
		return -EBUSY;
	}
	rtldevs[major].name = name;
	rtldevs[major].fops = fops;
	return 0;
}

int rtl_unregister_rtldev(unsigned int major, const char * name)
{
	if (major >= MAX_RTL_CHRDEV)
		return -EINVAL;
	if (!rtldevs[major].fops)
		return -EINVAL;
	if (strcmp(rtldevs[major].name, name))
		return -EINVAL;
	rtldevs[major].name = NULL;
	rtldevs[major].fops = NULL;
	return 0;
}



/* we assume all RTLinux file names are of the form /dev/devicename<number> */
int open(const char *pathname, int flags)
{
	int i;
	int minor;
	int major;
	char devname[200];
	char *p;
	int ret;

	if (strncmp(pathname, "/dev/", 5)) {
		__set_errno(ENOENT);
		return -1; /* here we can use some other name resolution scheme */
	}
	i = 0;
	while (i < sizeof(devname) - 1 && *(pathname + 5 + i) && !isdigit(*(pathname + 5 + i))) {
		devname[i] = *(pathname + 5 + i);
		i++;
	}
	devname[i] = 0;
	if (isdigit(*(pathname + 5 + i))) {
		minor = simple_strtoul (pathname + 5 + i, &p, 10);
	} else if (!*(pathname + 5 + i)) {
		minor = 0; /* like /dev/mem */
	} else {
		__set_errno(ENOENT);
		return -1;
	}
	
	for (i = 0; i < MAX_RTL_CHRDEV; i ++) {
		if (rtldevs[i].name && !strcmp(rtldevs[i].name, devname)) {
			goto found_major;
		}
	}
 	rtl_printf("rtl_posixio: dev entry %s not found\n", pathname); 
	__set_errno(ENOENT);
	return -1;
found_major:
	major = i;

	for (i = 0; i < MAX_RTL_FILES; i ++) {
		if (!rtl_files[i].f_op) {
			goto found_free;
		}
	}
	__set_errno(ENOMEM);
	return -1;
found_free:
	rtl_files[i].f_op = rtldevs[major].fops;
	rtl_files[i].f_minor = minor;
	rtl_files[i].f_flags = flags;

	ret = rtl_files[i].f_op->open(&rtl_files[i]);
	if (ret < 0) {
		__set_errno(-ret);
		return -1;
	}
	return i;
}


#define CHECKFD(fd) \
	if ((unsigned int) (fd) >= MAX_RTL_FILES || !rtl_files[fd].f_op) { \
		__set_errno(EBADF); \
		return -1; \
	}

extern int rtl_get_minor(int fd)
{
	CHECKFD(fd);
	return RTL_MINOR_FROM_FILEPTR(&rtl_files[fd]);
}

int close(int fd)
{
	CHECKFD(fd);
	rtl_files[fd].f_op->release(&rtl_files[fd]);
	rtl_files[fd].f_op = NULL;
	return 0;
}

ssize_t write(int fd, const void *buf, size_t count)
{
	int ret;
	CHECKFD(fd);
	ret = rtl_files[fd] . f_op -> write (&rtl_files[fd], buf, count, &rtl_files[fd].f_pos);
	if (ret < 0) {
		__set_errno(-ret);
		return -1;
	}
	return ret;
}

ssize_t read(int fd, void *buf, size_t count)
{
	int ret;
	CHECKFD(fd);
	ret = rtl_files[fd] . f_op -> read (&rtl_files[fd], buf, count, &rtl_files[fd].f_pos);
	if (ret < 0) {
		__set_errno(-ret);
		return -1;
	}
	return ret;
}

/* TODO lseek */

caddr_t  mmap(void  *start,  size_t length, int prot , int flags, int fd, off_t offset)
{
	int ret;
	caddr_t result;
	if ((unsigned int) (fd) >= MAX_RTL_FILES || !rtl_files[fd].f_op) {
		__set_errno(EBADF);
		return (caddr_t) -1;
	}
	if (! rtl_files[fd] . f_op -> mmap) {
		__set_errno(EINVAL);
		return (caddr_t) -1;
	}

	ret = rtl_files[fd] . f_op -> mmap (&rtl_files[fd], start, length, prot, flags, offset, &result);

	if (ret != 0) {
		__set_errno(-ret);
		return (caddr_t) -1;
	}
	return result;
}


int munmap(void *start, size_t length)
{
	iounmap (start);
	return 0;
}


int ioctl(int fd, int request, ...)
{
	int ret;
	va_list list;
	unsigned long arg;
	va_start (list, request);
	arg = va_arg(list, unsigned long);
	va_end (list);
	
	CHECKFD(fd);
	ret = rtl_files[fd] . f_op -> ioctl (&rtl_files[fd], request, arg);
	if (ret < 0) {
		__set_errno(-ret);
		return -1;
	}
	return 0;
}


#ifndef CONFIG_RTL_DEVMEM_SUPPORT
int init_module(void) {return 0;}
void cleanup_module(void){return; }
#else 
#include <asm/io.h>
static int rtl_mem_open (struct rtl_file *filp)
{
	return 0;
}

static int rtl_mem_release (struct rtl_file *filp)
{
	filp->f_pos = 0;
	return 0;
}


static ssize_t rtl_mem_write(struct rtl_file *filp, const char *buf, size_t count, loff_t* ppos)
{
	memcpy_toio (*ppos, buf, count);
	*ppos += count;
	return count;

}

static ssize_t rtl_mem_read(struct rtl_file *filp, char *buf, size_t count, loff_t* ppos)
{
	memcpy_fromio (buf, (long) *ppos, count);
	*ppos += count;
	return count;
}


static int rtl_mem_mmap (struct rtl_file *file, void  *start,  size_t length, int prot , int flags, off_t offset, caddr_t *result)
{
	/* TODO need to fail if MAP_FIXED was specified etc */
	if (!rtl_rt_system_is_idle()) {
		return -EAGAIN;
	}
	*result = ioremap (offset, length);
	if (!*result) {
		return -EINVAL;
	}
	return 0;
}


static loff_t rtl_mem_llseek(struct rtl_file *file, loff_t offset, int origin)
{
	if (origin != SEEK_SET) {
		return -EINVAL;
	}
	return file->f_pos = offset;
}

static struct rtl_file_operations rtl_mem_fops = {
       	rtl_mem_llseek,
	rtl_mem_read,
	rtl_mem_write,
	NULL,
	rtl_mem_mmap,
	rtl_mem_open,
	rtl_mem_release
};


int init_module(void)
{
	if (rtl_register_rtldev (MEM_MAJOR, "mem", &rtl_mem_fops)) {
		printk ("RTLinux /dev/mem: unable to get RTLinux major %d\n", MEM_MAJOR);
		return -EIO;
	}
	return 0;
}

void cleanup_module(void)
{
	rtl_unregister_rtldev(MEM_MAJOR, "mem");
}
#endif

EXPORT_SYMBOL(rtl_register_rtldev);
EXPORT_SYMBOL(rtl_unregister_rtldev);
