/*
 * RTL POSIX I/O support
 *
 *  Copyright (C) 1999 FSM Labs (http://www.fsmlabs.com/)
 *  Written by Michael Barabanov <baraban@fsmlabs.com>
 *
 *  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 <rtl_posixio.h>
#include <sys/mman.h>
#include <errno.h>
#include <unistd.h>
#include <rtl.h>

#define MAX_RTL_FILES 128

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 chrdevs[MAX_CHRDEV] = {
	{ NULL, NULL },
};

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

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



/* we assume all RTL 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_CHRDEV; i ++) {
		if (chrdevs[i].name && !strcmp(chrdevs[i].name, devname)) {
			goto found_major;
		}
	}
/* 	conpr("rtl_posixio: dev entry %s not found\n"); */
	__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 = chrdevs[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 0;
}

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 0;
}

/*
off_t lseek(int fd, off_t offset, int whence)
{
	int ret;
	CHECKFD(fd);
	if (!(rtl_files[fd] . f_op -> llseek)) {
		__set_errno(ESPIPE);
		return (off_t) -1;
	ret = rtl_files[fd] . f_op -> llseek (&rtl_files[fd], buf, count, &rtl_files[fd].f_pos);
	if (ret < 0) {
		__set_errno(-ret);
		return -1;
	}
	return 0;

}
*/

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)
{
	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;
}


#ifdef CONFIG_RTL_DEVMEM_SUPPORT

#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 (phys_to_virt(*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 (buf, phys_to_virt(*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 */
	*result = phys_to_virt (offset);
	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,
	NULL,
	rtl_mem_mmap,
	rtl_mem_open,
	rtl_mem_release
};
#endif


int init_module(void)
{
#ifdef CONFIG_RTL_DEVMEM_SUPPORT
	if (rtl_register_chrdev (MEM_MAJOR, "mem", &rtl_mem_fops)) {
		printk ("RTL /dev/mem: unable to get RTL major %d\n", MEM_MAJOR);
		return -EIO;
	}
#endif
	return 0;
}

void cleanup_module(void)
{
#ifdef CONFIG_RTL_DEVMEM_SUPPORT
	rtl_unregister_chrdev(MEM_MAJOR, "mem");
#endif
}
