/*
 *  linux/fs/xip2/file.c, Version 1
 *
 * (C) Copyright IBM Corp. 2002,2004
 * Author(s): Carsten Otte <cotte@de.ibm.com>
 * derived from second extended filesystem (ext2)
 */

#include <linux/fs.h>
#include <linux/xip2_fs.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <asm/uaccess.h>
#include <asm/page.h>
#include <asm/pgtable.h>

/*
 * We have mostly NULL's here: the current defaults are ok for
 * the xip2 filesystem. for mmap, we use a special implementation
 * that provides execute in place functionality
 */
struct file_operations xip2_file_operations = {
	llseek:		generic_file_llseek,
	read:		xip2_file_read,
	write:		generic_file_write,
	ioctl:		xip2_ioctl,
	mmap:		xip2_file_mmap,
	open:		generic_file_open,
};

struct inode_operations xip2_file_inode_operations = {};

static struct vm_operations_struct xip2_file_vm_ops = {
	nopage:		xip2_nopage_in_place,
};

/* This is used for a general mmap of a disk file */

int xip2_file_mmap(struct file * file, struct vm_area_struct * vma)
{
	struct address_space *mapping = file->f_dentry->d_inode->i_mapping;
	struct inode *inode = mapping->host;

	if ((vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE)) {
		if (!mapping->a_ops->writepage)
			return -EINVAL;
}
	if (!mapping->a_ops->readpage)
		return -ENOEXEC;
	UPDATE_ATIME(inode);
	vma->vm_ops = &xip2_file_vm_ops;
	return 0;
}

struct page * xip2_nopage_in_place(struct vm_area_struct * area, 
				   unsigned long address, int unused)
{
	int error;
	unsigned long blockno=ULONG_MAX;
	void* block_ptr;
	struct file *file = area->vm_file;
	struct address_space *mapping = file->f_dentry->d_inode->i_mapping;
	struct inode *inode = mapping->host;
	unsigned long pgoff;

	pgoff = ((address - area->vm_start) >> PAGE_CACHE_SHIFT) + 
		area->vm_pgoff;
	error=xip2_get_block(inode, pgoff, &blockno, 0);
	if (error) {
		printk ("XIP2-FS: xip2_nopage_in_place could not fullfill "
			"page request\n");
		return NULL;
	}
	block_ptr = xip2_maread(inode->i_sb->u.xip2_sb.mem_area, blockno, 
				PAGE_SIZE);
	if (!block_ptr) 
		return virt_to_page(empty_zero_page);
	return virt_to_page(block_ptr);
}

void xip2_do_file_read(struct file * filp, loff_t *ppos, read_descriptor_t * desc)
{
	struct address_space *mapping = filp->f_dentry->d_inode->i_mapping;
	struct inode *inode = mapping->host;
	unsigned long block,offset,rdlen,count, iblock, lblock, blockno;
	void* block_ptr,* cpystart;
	int error,cpycount;

	iblock = (*ppos)/PAGE_SIZE;
	offset = (*ppos)%PAGE_SIZE;
	rdlen = desc->count;
	if ((*ppos)+desc->count > inode->i_size)
		rdlen = inode->i_size - (*ppos);
	lblock = (*ppos + rdlen) / PAGE_SIZE;
	count = 0;
	for (block = iblock; block <= lblock; block++) {
		error=xip2_get_block(inode, block, &blockno, 0);
		if (error) {
			desc->error = error;
			desc->written = count;
			return;
}
		block_ptr = xip2_maread (inode->i_sb->u.xip2_sb.mem_area, 
					 blockno, PAGE_SIZE);
		if (block_ptr) {
			if (block == iblock) {
				cpystart = block_ptr + offset;
				cpycount = PAGE_SIZE - offset;
	} else {
				cpystart = block_ptr;
				cpycount = PAGE_SIZE;
			}
		} else {
			// there is no block assigned, copy zeros over
			if (block == iblock) {
				cpystart = empty_zero_page;
				cpycount = PAGE_SIZE - offset;
			} else {
				cpystart = empty_zero_page;
				cpycount = PAGE_SIZE;
		}
	}
		if (cpycount > rdlen-count) {
			cpycount = rdlen-count;
			if (block!=lblock) BUG();
}
		if (copy_to_user(desc->buf+count, cpystart, cpycount)) {
			desc->error = -EFAULT;
			desc->written = count;
			return;
}
		count += cpycount;
	}
	if (rdlen-count>0) BUG();
	desc->error = 0;
	desc->written = count;
	*ppos+=count;
	return;
}


ssize_t xip2_file_read(struct file * filp, char * buf, size_t count, loff_t *ppos)
{
	ssize_t retval;

	if ((ssize_t) count < 0)
		return -EINVAL;

	retval = -EFAULT;
	if (access_ok(VERIFY_WRITE, buf, count)) {
		retval = 0;

		if (count) {
			read_descriptor_t desc;

			desc.written = 0;
			desc.count = count;
			desc.buf = buf;
			desc.error = 0;
			xip2_do_file_read(filp, ppos, &desc);

			retval = desc.written;
			if (!retval)
				retval = desc.error;
	}
}
	return retval;
}
