/* This file contains the device dependent part of the drivers for the
 * following special files:
 *     /dev/null	- null device (data sink)
 *     /dev/mem		- absolute memory
 *     /dev/kmem	- kernel virtual memory
 *     /dev/ram		- RAM disk
 *
 * The file contains one entry point:
 *
 *   mem_task:	main entry when system is brought up
 *
 *  Changes:
 *	20 Apr  1992 by Kees J. Bot: device dependent/independent split
 */

#include "kernel.h"
#include "driver.h"
#include <sys/ioctl.h>

#define NR_RAMS            4	/* number of RAM-type devices */

PRIVATE struct device m_geom[NR_RAMS];	/* Base and size of each RAM disk */
PRIVATE int m_device;		/* current device */

FORWARD _PROTOTYPE( struct device *m_prepare, (int device) );
FORWARD _PROTOTYPE( int m_schedule, (int proc_nr, struct iorequest_s *iop) );
FORWARD _PROTOTYPE( int m_do_open, (struct driver *dp, message *m_ptr) );
FORWARD _PROTOTYPE( void m_init, (void) );
FORWARD _PROTOTYPE( int m_ioctl, (struct driver *dp, message *m_ptr) );
FORWARD _PROTOTYPE( void m_geometry, (struct partition *entry) );


/* Entry points to this driver. */
PRIVATE struct driver m_dtab = {
  no_name,	/* current device's name */
  m_do_open,	/* open or mount */
  do_nop,	/* nothing on a close */
  m_ioctl,	/* specify ram disk geometry */
  m_prepare,	/* prepare for I/O on a given minor device */
  m_schedule,	/* do the I/O */
  nop_finish,	/* schedule does the work, no need to be smart */
  nop_cleanup,	/* nothing's dirty */
  m_geometry,	/* memory device "geometry" */
};


/*===========================================================================*
 *				mem_task				     *
 *===========================================================================*/
PUBLIC void mem_task()
{
  m_init();
  driver_task(&m_dtab);
}


/*===========================================================================*
 *				m_prepare				     *
 *===========================================================================*/
PRIVATE struct device *m_prepare(device)
int device;
{
/* Prepare for I/O on a device. */

  if (device < 0 || device >= NR_RAMS) return(NIL_DEV);
  m_device = device;

  return(&m_geom[device]);
}


/*===========================================================================*
 *				m_schedule				     *
 *===========================================================================*/
PRIVATE int m_schedule(proc_nr, iop)
int proc_nr;			/* process doing the request */
struct iorequest_s *iop;	/* pointer to read or write request */
{
/* Read or write /dev/null, /dev/mem, /dev/kmem, or /dev/ram. */

  int device, count, opcode;
  phys_bytes mem_phys, user_phys;
  struct device *dv;

  /* Type of request */
  opcode = iop->io_request & ~OPTIONAL_IO;

  /* Get minor device number and check for /dev/null. */
  device = m_device;
  dv = &m_geom[device];

  /* Determine address where data is to go or to come from. */
  user_phys = numap(proc_nr, (vir_bytes) iop->io_buf,
  						(vir_bytes) iop->io_nbytes);
  if (user_phys == 0) return(iop->io_nbytes = EINVAL);

  if (device == NULL_DEV) {
	/* /dev/null: Black hole. */
	if (opcode == DEV_WRITE) iop->io_nbytes = 0;
	count = 0;
  } else {
	/* /dev/mem, /dev/kmem, or /dev/ram: Check for EOF */
	if (iop->io_position >= dv->dv_size) return(OK);
	count = iop->io_nbytes;
	if (iop->io_position + count > dv->dv_size)
		count = dv->dv_size - iop->io_position;
  }

  /* Set up 'mem_phys' for /dev/mem, /dev/kmem, or /dev/ram */
  mem_phys = dv->dv_base + iop->io_position;

  /* Book the number of bytes to be transferred in advance. */
  iop->io_nbytes -= count;

  if (count == 0) return(OK);

  /* Copy the data. */
  if (opcode == DEV_READ)
	phys_copy(mem_phys, user_phys, (phys_bytes) count);
  else
	phys_copy(user_phys, mem_phys, (phys_bytes) count);

  return(OK);
}


/*============================================================================*
 *				m_do_open				      *
 *============================================================================*/
PRIVATE int m_do_open(dp, m_ptr)
struct driver *dp;
message *m_ptr;
{
/* Check device number on open.  Give I/O privileges to a process opening
 * /dev/mem or /dev/kmem.
 */

  if (m_prepare(m_ptr->DEVICE) == NIL_DEV) return(ENXIO);

#if (CHIP == INTEL)
  if (m_device == MEM_DEV || m_device == KMEM_DEV)
	enable_iop(proc_addr(m_ptr->PROC_NR));
#endif

  return(OK);
}


/*===========================================================================*
 *				m_init					     *
 *===========================================================================*/
PRIVATE void m_init()
{
  /* Initialize this task. */
  extern int _end;

  m_geom[KMEM_DEV].dv_base = vir2phys(0);
  m_geom[KMEM_DEV].dv_size = vir2phys(&_end);

#if (CHIP == INTEL)
  if (!protected_mode) {
	m_geom[MEM_DEV].dv_size =   0x100000;	/* 1M for 8086 systems */
  } else {
#if _WORD_SIZE == 2
	m_geom[MEM_DEV].dv_size =  0x1000000;	/* 16M for 286 systems */
#else
	m_geom[MEM_DEV].dv_size = 0xFFFFFFFF;	/* 4G-1 for 386 systems */
#endif
  }
#else /* !(CHIP == INTEL) */
#if (CHIP == M68000)
  m_geom[MEM_DEV].dv_size = MEM_BYTES;
#else /* !(CHIP == M68000) */
#error /* memory limit not set up */
#endif /* !(CHIP == M68000) */
#endif /* !(CHIP == INTEL) */
}


/*===========================================================================*
 *				m_ioctl					     *
 *===========================================================================*/
PRIVATE int m_ioctl(dp, m_ptr)
struct driver *dp;
message *m_ptr;			/* pointer to read or write message */
{
/* Set parameters for one of the RAM disks. */

  unsigned long bytesize;
  unsigned base, size;
  struct memory *memp;
  static struct psinfo psinfo = { NR_TASKS, NR_PROCS, (vir_bytes) proc, 0, 0 };
  phys_bytes psinfo_phys;

  switch (m_ptr->REQUEST) {
  case MIOCRAMSIZE:
	/* FS sets the RAM disk size. */
	if (m_ptr->PROC_NR != FS_PROC_NR) return(EPERM);

	bytesize = m_ptr->POSITION * BLOCK_SIZE;
	size = (bytesize + CLICK_SHIFT-1) >> CLICK_SHIFT;

	/* Find a memory chunk big enough for the RAM disk. */
	memp= &mem[NR_MEMS];
	while ((--memp)->size < size) {
		if (memp == mem) panic("RAM disk is too big", NO_NUM);
	}
	base = memp->base;
	memp->base += size;
	memp->size -= size;

	m_geom[RAM_DEV].dv_base = (unsigned long) base << CLICK_SHIFT;
	m_geom[RAM_DEV].dv_size = bytesize;
	break;
  case MIOCSPSINFO:
	/* MM or FS set the address of their process table. */
	if (m_ptr->PROC_NR == MM_PROC_NR) {
		psinfo.mproc = (vir_bytes) m_ptr->ADDRESS;
	} else
	if (m_ptr->PROC_NR == FS_PROC_NR) {
		psinfo.fproc = (vir_bytes) m_ptr->ADDRESS;
	} else {
		return(EPERM);
	}
	break;
  case MIOCGPSINFO:
	/* The ps program wants the process table addresses. */
	psinfo_phys = numap(m_ptr->PROC_NR, (vir_bytes) m_ptr->ADDRESS,
							sizeof(psinfo));
	if (psinfo_phys == 0) return(EFAULT);
	phys_copy(vir2phys(&psinfo), psinfo_phys, (phys_bytes) sizeof(psinfo));
	break;
  default:
  	return(do_diocntl(&m_dtab, m_ptr));
  }
  return(OK);
}


/*============================================================================*
 *				m_geometry				      *
 *============================================================================*/
PRIVATE void m_geometry(entry)
struct partition *entry;
{
  /* Memory devices don't have a geometry, but the outside world insists. */
  entry->cylinders = (m_geom[m_device].dv_size >> SECTOR_SHIFT) / (64 * 32);
  entry->heads = 64;
  entry->sectors = 32;
}
