/*
	This is a program to test memory allocations from the
	top reserved memory. The memory is reserved using
	the LILO command append="mem=xxM", or interactively:
		LILO:  linux mem=XXM
	
	This program runs as a module and shows one possible
	way that the allocation could be made. In this case I'm using the 
	allocator program provided by Rubini.
	
	To access the program, the usual device mechanics is used.
	Create a device as follows:
		mknod /dev/ggmem c 93 0
		chmod og+rw /dev/ggmem
*/

#ifndef __KERNEL__
#  define __KERNEL__
#endif
#ifndef MODULE
#  define MODULE
#endif

#include <linux/module.h>
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/malloc.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <asm/io.h>

#include "sysdep.h"
#include "allocator.h"

#define ggmem_major 93
static char ggmem_name[] =  "ggmem";

int ggmem_open(struct inode *, struct file *);
int ggmem_ioctl(struct inode *, struct file *, unsigned int cmd,
		unsigned long arg);
int ggmem_release(struct inode *, struct file *);
int ggmem_mmap(struct file *file, struct vm_area_struct *vma);
		
struct file_operations ggmem_fops = {
	NULL,			/* lseek	*/
	NULL,			/* read		*/
	NULL,			/* write	*/
	NULL,			/* readdir	*/
	NULL,			/* select	*/
	ggmem_ioctl,		/* ioctl	*/
	ggmem_mmap,		/* mmap		*/
	ggmem_open,		/* open		*/
	NULL,			/* flush	*/
	ggmem_release,		/* release	*/
	NULL,
	};
	
static char test1[] = "0123456789";
volatile static unsigned long dmabuffer, dmaremap, dmasize;
	
int init_module(void)
{
	int retval;
	
	retval = register_chrdev(ggmem_major, ggmem_name, &ggmem_fops);
	if (retval < 0)
	{
		printk("ggmem: can't get major %d\n", ggmem_major);
		return(retval);
	}
//
//			Initialize the allocator logic
//
	retval = allocator_init();
	if (!retval)
		printk("ggmem: allocator ok\n");
	else
		printk("ggmem: allocator failed\n");
	return(retval);
}

void cleanup_module(void)
{
	unregister_chrdev(ggmem_major, ggmem_name);
	allocator_cleanup();
}

int ggmem_open(struct inode *inode, struct file *f)
{
//
//				Here one could initialize further
//				logic for handling the allocation
//				memory segments. If a fixed set is 
//				used, then keep it simple.
//
	return(0);
}

int ggmem_release(struct inode *inode, struct file *f)
{
//
//				Release whatever needs to be released.
//
	iounmap((void *) dmaremap);
	allocator_free_dma(dmabuffer);
	return(0);
}

//
//				This portion of the program gets a piece
//				of memory ready to be assigned as DMA
//				buffer, and also remapped for kernel use.
//				The buffer address and the size are given
//				to the user. A string is placed in the
//				memory segment for test purposes.
//

int ggmem_ioctl(struct inode *inode, struct file *f,
		unsigned int cmd, unsigned long arg)
{
	char test[100];
	unsigned long alloc_info[2];
	switch (cmd) {
		case 1:
//
//					Get the area address and the size of it.
//
			getas_alloc(&alloc_info[0], &alloc_info[1]);
			dmabuffer = allocator_allocate_dma(alloc_info[1], 
							   GFP_KERNEL);
//
//					This dmabuffer can be assigned
//					directly to the device. It's a
//					physical address. We are taking
//					all of the available memory.
//
			if (!dmabuffer)
				return(-1);
			printk("dmabuffer %lx abuff %lx asize %lx\n", dmabuffer,
				alloc_info[0], alloc_info[1]);
			alloc_info[0] = dmabuffer;
			dmasize = alloc_info[1] << 10;
			dmaremap = (unsigned long) ioremap(dmabuffer, dmasize);
//
//					This dmaremap is the address to be
//					used at kernel level. To test
//					it, we place a string in that memory
//					location, that later we can read from
//					user space, after doing the mmap.
//
			strcpy((char *) dmaremap, test1);
			strcpy(test, (char *) dmaremap);
			printk("remap %s, %lx\n", test, dmaremap);
                        copy_to_user ((uint32_t *) arg, alloc_info, 8);
//
//					We pass to the user the physical
//					address and the memory area size, 
//					that gets mapped to user space
//					by the mmap function.
//
			break;

		}
	return(0);
}
			
//
//				This is the remap done on behalf of mmap.
//				This is just pure magic. I have no idea
//				on how does it work, but this is the way
//				you must do it. How and where is the vma
//				structure gets filled, it's a complete
//				mistery.

int ggmem_mmap(struct file *file, struct vm_area_struct *vma)
{
	printk("vma %lx %lx %lx\n", vma->vm_start, vma->vm_offset,
				 vma->vm_end);
				 
	if (remap_page_range(vma->vm_start, vma->vm_offset, vma->vm_end - 
			     vma->vm_start, vma->vm_page_prot))
	{
		printk("remap failed\n");
		return(-1);
	}
	return(0);
}
			
