/*

  P-Code interpreter (to run the apple pascal system)
  Copyright (C) 2000 Mario Klebsch

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

  $Log: Diskio.c,v $
  Revision 1.5  2001/06/07 19:57:53  mario
  Implementierung vom Diskettenzugriff mittels read(), write() & lseek()
  um Systeme ohne mmap() zu unterstützen.

  Revision 1.4  2001/05/26 15:13:29  mario
  Diverse kleine Fehler behoben, fehlende #includes, Labels ohne Statement
  dahinter, ...

  Revision 1.3  2001/05/21 19:06:48  mario
  Mode-Parameter bei den Disk-IO-Routinen entfernt

  Revision 1.2  2001/05/20 13:12:02  mario
  CVS-Idents und Logs eingefügt


*/

#ident "$Id: Diskio.c,v 1.5 2001/06/07 19:57:53 mario Exp $";

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef USE_MMAP
#include <sys/mman.h>
#endif

#include "psystem.h"
#include "Memory.h"
#include "Diskio.h"

static int DskTable[16]={0,14,13,12,11,10,9,8,7,6,5,4,3,2,1,15};

static struct Unit
{
  int		Fd;
  byte		*Data;
  size_t	Size;
  int		RdOnly;
  int		*Translate;
} Units[MAX_UNIT];

void DiskRead(word Unit, word Addr, Integer AddrOffset,
	      word Len, word BlockNo)
{
  int		i;
  size_t	Offset = BlockNo*512;
  int		Track  = BlockNo/8;
  int		Sector = (BlockNo&7)*2;
  struct Unit *u;

  assert(Unit<MAX_UNIT);
  u=&Units[Unit];
  if ( (u->Fd<0) && !u->Data )
    {
      IoError(9);
      return;
    }
  if ((Offset+Len)>u->Size)
    {
      IoError(64);
      return;
    }

  while (Len)
    {
      int Size=256;
      int Sec=Sector;
      if (Len<Size)
	Size=Len;
      if (u->Translate)
	Sec=u->Translate[Sector];
      if (u->Data)
	for (i=0; i<Size; i++)
	  MemWrByte(Addr, AddrOffset+i,
		    u->Data[(Track*16+Sec)*256+i]);
      else
	{
	  char Buf[256];
	  if ( (lseek(u->Fd, (Track*16+Sec)*256, SEEK_SET) <0) ||
	       ( read(u->Fd, Buf, Size) != Size) )
	    {
	      IoError(64);
	      return;
	    }
	  for (i=0; i<Size; i++)
	    MemWrByte(Addr, AddrOffset+i, Buf[i] );
	}
      AddrOffset +=Size;
      Len-=Size;
      Sector++;
      if (Sector>=16)
	{
	  Track++;
	  Sector=0;
	}
    }
  IoError(0);
}

void DiskWrite(word Unit, word Addr, Integer AddrOffset,
	       word Len, word BlockNo)
{
  int		i;
  size_t	Offset = BlockNo*512;
  int		Track  = BlockNo/8;
  int		Sector = (BlockNo&7)*2;
  struct Unit *u;

  assert(Unit<MAX_UNIT);
  u=&Units[Unit];
  if ( (u->Fd<0) && !u->Data )
    {
      IoError(9);
      return;
    }
  else if (u->RdOnly)
    {
      IoError(16);
      return;
    }
  else if ((Offset+Len)>u->Size)
    {
      IoError(64);
      return;
    }

  while (Len)
    {
      int Size=256;
      int Sec=Sector;
      if (Len<Size)
	Size=Len;
      if (u->Translate)
	Sec=u->Translate[Sector];
      if (u->Data)
	for (i=0; i<Size; i++)
	  u->Data[(Track*16+Sec)*256+i] =
	    MemRdByte(Addr, AddrOffset+i );
	else
	  {
	    char Buf[256];
	    for (i=0; i<Size; i++)
	      Buf[i] =	MemRdByte(Addr, AddrOffset+i );
	    if ( (lseek(u->Fd, (Track*16+Sec)*256, SEEK_SET) <0) ||
		 ( write(u->Fd, Buf, Size) != Size) )
	      {
		IoError(64);
		return;
	      }
	  }
      AddrOffset +=Size;
      Len-=Size;
      Sector++;
      if (Sector>=16)
	{
	  Track++;
	  Sector=0;
	}
    }
  IoError(0);
}

void DiskClear(word Unit)
{
  assert(Unit<MAX_UNIT);
  IoError(0);
}

void DiskStat(word Unit)
{
  struct Unit *u;

  assert(Unit<MAX_UNIT);
  u=&Units[Unit];
  if ( (u->Fd<0) && !u->Data )
    IoError(9);
  else
    IoError(0);
}

void DiskUmount(word Unit)
{
  struct Unit *u;

  assert(Unit<MAX_UNIT);
  u=&Units[Unit];
  if (u->Data)
    {
#ifdef USE_MMAP
      munmap(u->Data, u->Size);
#else
      free(u->Data);
#endif
      u->Data=NULL;
    }
  if (u->Fd>=0)
    {
      close(u->Fd);
      u->Fd=-1;
    }
}

int DiskMount(word Unit, const char *FileName, enum DiskMode Mode)
{
  int	fd;
  struct stat buf;
  struct Unit *u;

  assert(Unit<MAX_UNIT);
  u=&Units[Unit];
  if ((fd=open(FileName,(Mode==ReadWrite)?O_RDWR:O_RDONLY))<0)
    {
      perror(FileName);
      return(-1);
    }
  if (fstat(fd, &buf)<0)
    {
      perror(FileName);
      close(fd);
      return(-1);
    }
  DiskUmount(Unit);

  u->Fd=fd;
  u->Size=buf.st_size;
  u->RdOnly=(Mode==ReadOnly);
  u->Translate=NULL;

  if ( (strlen(FileName)>4) &&
       (strcmp(FileName+strlen(FileName)-4,".dsk")==0) )
    u->Translate=DskTable;
#ifdef USE_MMAP
  u->Data=mmap(NULL, u->Size,
	       (Mode==ReadOnly)?PROT_READ:PROT_READ|PROT_WRITE,
	       (Mode==Forget)?MAP_PRIVATE:MAP_SHARED,
	       u->Fd, 0);
  close(Unit[Unit].Fd);
  Unit[Unit].Fd=-1;
#else
  if (Mode==Forget)
    {
      u->Data=malloc(u->Size);
      assert(u->Data);
      if (read(u->Fd,u->Data,u->Size) !=
	  u->Size)
	{
	  perror(FileName);
	  free(u->Data);
	  u->Data=NULL;
	  close(u->Fd);
	  u->Fd=-1;
	  return(-1);
	}
      close(u->Fd);
      u->Fd=-1;
    }
#endif
  return(0);
}

void DiskInit(void)
{
  int i;
  for (i=0; i<MAX_UNIT; i++)
    {
      Units[i].Data=NULL;
      Units[i].Fd=-1;
    }
}
