/* ster_vga.c 
 * Copyright (C) 2002 by Wojciech M. Zabolotny
 * Significantly based on examples given by Ori Pomerantz in
 * "Kernel Module Programming Guide"
 * and by Alessandro Rubini in "Linux Device Drivers"
 * 
 * Prosty sterownik, pozwalający odwoływać się do pamięci 
 * tryby tekstowego karty VGA
 */


/* Standardowe zbiory nagłówkowe */

#include <linux/kernel.h>   
#include <linux/module.h>
MODULE_LICENSE("GPL v2");
/* Deal with CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif        

#define read_write_t int
#define count_t int
/* Zbiory nagłówkowe dla urządzeń znakowych */
#include <linux/fs.h>
#include <linux/wrapper.h>
#include <linux/mm.h>
#include <linux/config.h>
#include <asm/io.h>
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif

/* Kody błędów */
//#include <errno.h>

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
#include <asm/uaccess.h>  /* for put_user */
#endif                             

#define SUCCESS 0
#define DEVICE_NAME "ster1_vga"

/* Zmienna globalna określająca sposób odwoływania się do pamięci */
int tryb=0;
MODULE_PARM(tryb,"i");

void * fmem=NULL; //Wskaźnik na interesujący nas obszar pamięci
int phys_addr = 0xb8000;
MODULE_PARM(phys_addr,"i");
int phys_len = 0x8000;
MODULE_PARM(phys_len,"i");
int debug=0;
MODULE_PARM(debug,"i");

/* Obsługa mapowania pamięci */
void ster1_vma_open (struct vm_area_struct * area)
{ MOD_INC_USE_COUNT ; }

void ster1_vma_close (struct vm_area_struct * area)
{ MOD_DEC_USE_COUNT ; }

static struct vm_operations_struct ster1_vm_ops = {
  ster1_vma_open,
  ster1_vma_close,
 };

read_write_t ster1_read(struct file *filp,
	char *buf,size_t count, loff_t *off)
{
  /* Unikamy wyjścia poza koniec zbioru */
  if ( (*off)+count >=  phys_len) count = phys_len-(*off);
  if (count == 0) return 0;
  switch(tryb) {
  case 0:
    {  
      int i;
      if(debug) printk("<1>Uzywam readb\n");
      for(i=0;i<count;i++) {
	unsigned char c = readb(fmem+(*off)+i);
	__copy_to_user(buf+i,&c,1);
      }
    }
    break;
  case 1:
    {  
      int i;
      if(debug) printk("<1>Uzywam wskaznikow\n");
      for(i=0;i<count;i++) {
	unsigned char c = *((unsigned char *)fmem+(*off)+i);
	__copy_to_user(buf+i,&c,1);
      }
    }
    break;
  case 2:
    if(debug) printk("<1>Uzywam bezposredniego kopiowania\n");
    __copy_to_user(buf,fmem+(*off),count);
  }
  (*off) += count;
  return count;
}
	
read_write_t ster1_write(struct file *filp,
	const char *buf,size_t count, loff_t *off)
{
  /* Sprawdzamy, czy jest miejsce na urządzeniu */
  if ( (*off)+count >=  phys_len) count = phys_len-(*off);
  if (count == 0) return -ENOSPC;
  switch(tryb) {
  case 0:
    {  
      int i;
      if(debug) printk("<1>Uzywam writeb\n");
      for(i=0;i<count;i++) {
	unsigned char c;
	__copy_from_user(&c,buf+i,1);
	writeb(c,fmem+(*off)+i);
      }
    }
    break;
  case 1:
    {  
      int i;
      if(debug) printk("<1>Uzywam wskaznikow\n");
      for(i=0;i<count;i++) {
	unsigned char c;
	__copy_from_user(&c,buf+i,1);
	*((unsigned char *)fmem+(*off)+i)=c;
      }
    }
    break;
  case 2:
    if(debug) printk("<1>Uzywam bezposredniego kopiowania\n");
    __copy_from_user(fmem+(*off),buf,count);
  }
  (*off) += count;
  return count;
}	

/*
Implementacja metody mmap
*/
int ster1_mmap(struct file *filp,
	       struct vm_area_struct *vma)
{
  unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
  unsigned long physical = phys_addr + off;
  unsigned long vsize = vma->vm_end - vma->vm_start;
  unsigned long psize = phys_len - off;
  if(vsize>psize)
    return -EINVAL;
  remap_page_range(vma->vm_start, physical, vsize, vma->vm_page_prot);
  if (vma->vm_ops)
    return -EINVAL; //To nie powinno się zdarzyć
  vma->vm_ops = &ster1_vm_ops;
  ster1_vma_open(vma); //Bo tym razem nie było wywołania open(vma)
  return 0;
}

/*
Implementacja funkcji otwarcia urządzenia
 */
static int ster1_open(struct inode *inode, 
                       struct file *file)
{
  MOD_INC_USE_COUNT;
  return SUCCESS;
}


//Funkcja zwalniania urządzenia

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static int ster1_release(struct inode *inode, 
                          struct file *file)
#else 
static void ster1_release(struct inode *inode, 
                           struct file *file)
#endif
{
#ifdef DEBUG
  printk ("<1>device_release(%p,%p)\n", inode, file);
#endif
  MOD_DEC_USE_COUNT;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
  return 0;
#endif
}


struct file_operations Fops = {
  .read=ster1_read, /* read */
  .write=ster1_write, /* read */
  .open=ster1_open,
  .release=ster1_release,  /* a.k.a. close */
  .mmap=ster1_mmap
};

static int Major=0;

/* Inicjalizacja modułu */
int init_module()
{
  /* Register the character device (at least try) */
  Major = register_chrdev(0, 
                          DEVICE_NAME,
                          &Fops);

  /* Tworzymy wskaźniki niezbędne do sięgania do interesującej nas
     pamięci fizycznej */
  fmem = ioremap(phys_addr, phys_len);
  if(!fmem) {
            printk ("<1>%s device failed with %d\n",
            "Sorry, allocating the pointer",
            Major);
    return Major;
  }
  /* Negative values signify an error */
  if (Major < 0) {
    printk ("<1>%s device failed with %d\n",
            "Sorry, registering the character",
            Major);
    return Major;
  }
  printk ("<1>%s The major device number is %d.\n",
          "Registeration is a success.",
          Major);
  printk ("<1>If you want to talk to the device driver,\n");
  printk ("<1>you'll have to create a device file. \n");
  printk ("<1>We suggest you use:\n");
  printk ("<1>mknod <name> c %d <minor>\n", Major);
  printk ("<1>You can try different minor numbers %s",
          "and see what happens.\n");
  return 0;
}

/* Usuwanie modułu - sprzątamy po sobie */
void cleanup_module()
{
  int ret;
  if(fmem) iounmap(fmem);
  /* Odrejestrowujemy urządzenie */
  ret = unregister_chrdev(Major, DEVICE_NAME);
  /* If there's an error, report it */ 
  if (ret < 0)
    printk("<1>Error in unregister_chrdev: %d\n", ret);
}
