/* Disk functions
   
   Copyright (C) 1996 Pete A. Zaitcev
   		 1996,1997 Jakub Jelinek
   
   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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "silo.h"

static int net = 0;
static int floppy = 0;
static int fd;
static unsigned long long seekp;
char bootdevice[4096];
static char currentdevice[4096];

int open (char *device)
{
    strcpy (currentdevice, device);
    net = 0;
    floppy = 0;
    seekp = 0xffffffffffffffffULL;
    switch (prom_vers) {
    case PROM_V0:
    	{
        	char buffer[20], *p;
        
	        if (strlen (device) < 20) {
        	    /* v0 prom likes to paint in devopen parameter sometimes... */
	            strcpy (buffer, device);
        	    p = buffer;
	        } else p = device;
		fd = (*romvec->pv_v0devops.v0_devopen) (p);
		if (device[0] == 'f' && device[1] == 'd')
		    floppy = 1;
		else if ((device[0] == 'l' || device[0] == 'i') && device[1] == 'e')
		    net = 1;
	}
	break;
    case PROM_V2:
    case PROM_V3:
    	{
    		int node;
	    	char buffer[20];
    	
		fd = (*romvec->pv_v2devops.v2_dev_open) (device);
		if ((unsigned)(fd + 1) > 1) {
		    node = (romvec->pv_v2devops.v2_inst2pkg) (fd);
		    prom_getstring (node, "device_type", buffer, 20);
	    	    if (!strcmp (buffer, "network"))
	                net = 1;
	        }
	}
	break;
    case PROM_P1275:
        {
        	int node;
        	char buffer [20];
        	
        	fd = p1275_cmd ("open", 1, device);
#ifdef TESTING        	
printf ("Opening %s %x\n", device, fd);
#endif
        	if ((unsigned)(fd + 1) > 1) {
		    node = p1275_cmd ("instance-to-package", 1, fd);
		    prom_getstring (node, "device_type", buffer, 20);
	    	    if (!strcmp (buffer, "network"))
	                net = 1;
	        }
        }
        break;
    }
    if (fd == 0 || fd == -1) {
	printf ("\nFatal error: Couldn't open device %s\n", device);
	return -1;
    }
    return 0;
}

extern char boot_part;

int diskinit ()
{
    fd = 0;
    if (prom_vers == PROM_V0) {
	struct linux_arguments_v0 *ap = *romvec->pv_v0bootargs;
	char *s = bootdevice;
	int unit;

	*s++ = ap->boot_dev[0];
	*s++ = ap->boot_dev[1];
	*s++ = '(';
	*s++ = (ap->boot_dev_ctrl & 07) + '0';
	*s++ = ',';
	if ((*s = ap->boot_dev_unit / 10 + '0') != '0')
	    s++;
	*s++ = ap->boot_dev_unit % 10 + '0';
	*s++ = ','; 
	*s++ = boot_part + '0';
	*s++ = ')';
	*s = 0;
    } else {
        char *p;
        if (prom_vers == PROM_P1275)
            prom_getproperty (prom_chosen, "bootpath", bootdevice, sizeof(bootdevice));
        else
	    strcpy (bootdevice, *romvec->pv_v2bootargs.bootpath);
	p = strchr (bootdevice, ':');
	if (!p) {
	    p = strchr (bootdevice, 0); *p++ = ':'; *p++ = boot_part + 'a'; *p = 0;
	} else if (p[1] >= 'a' && p[1] <= 'z' && !p[2])
	    p[1] = boot_part + 'a';
    }
    return open (bootdevice);
}

void reopen(void)
{
    char c;
    
    c = *currentdevice;
    close ();
    *currentdevice = c;
    open (currentdevice);
}

int read (char *buff, int size, unsigned long long offset)
{
    if (!size)
	return 0;
    if (prom_vers == PROM_V0) {
    	if (net)
    	    return (*romvec->pv_v0devops.v0_rdnetdev) (fd, size, buff);
	else {
	    char buffer[512];
	    int i = 0, j, rc, ret = 0;

	    if (offset & 0x1ff) {
	        if (size > 512 - (offset & 0x1ff))
		    i = 512 - (offset & 0x1ff);
	        else
		    i = size;
		for (j = 0; j < 5; j++) {
	        	rc = (*romvec->pv_v0devops.v0_rdblkdev) (fd, 1, (unsigned)(offset >> 9), buffer);
	        	if (rc) break;
	        	reopen();
	        }
	        if (rc != 1)
		    return -1;
	        memcpy (buff, buffer + (offset & 0x1ff), i);
	        buff += i;
	        size -= i;
	        offset = ((offset + 512) & ~0x1ffULL);
	        ret = i;
	    }
	    if (size >> 9) {
		for (j = 0; j < 5; j++) {
	        	rc = (*romvec->pv_v0devops.v0_rdblkdev) (fd, size >> 9, (unsigned)(offset >> 9), buff);
	        	if (rc) break;
	        	reopen();
	        }
	        if (rc != size >> 9)
		    return -1;
	        i = (size & (~0x1ff));
	        ret += i;
	        buff += i;
	        offset += i;
	    }
	    size &= 0x1ff;
	    if (size) {
		for (j = 0; j < 5; j++) {
	        	rc = (*romvec->pv_v0devops.v0_rdblkdev) (fd, 1, (unsigned)(offset >> 9), buffer);
	        	if (rc) break;
	        	reopen();
	        }
	        if (rc != 1)
		    return -1;
	        memcpy (buff, buffer, size);
	        ret += size;
	    }
	    return ret;
	}
    } else {
	int rc;
	
	if (!net) {
	    if (prom_vers != PROM_P1275) {
		    if (((romvec->pv_printrev >> 16) < 2 || 
		         ((romvec->pv_printrev >> 16) == 2 && (romvec->pv_printrev && 0xffff) < 6)) 
		        && offset >= 0x40000000) {
		    	printf ("Buggy old PROMs don't allow reading past 1GB from start of the disk. Send complains to SMCC\n");
	    		return -1;
	    	    }
	    }
	    if (seekp != offset) {
	    	if (prom_vers == PROM_P1275) {
		        if ((rc = p1275_cmd ("seek", 3, fd, (unsigned)(offset >> 32), (unsigned)offset)) == -1)
			    return -1;
#ifdef TESTING			    
			printf ("Seek returns %d\n", rc);
#endif			
	    	} else {
		        if ((*romvec->pv_v2devops.v2_dev_seek) (fd, (unsigned)(offset >> 32), (unsigned)offset) == -1)
			    return -1;
		}
	        seekp = offset;
	    }
	}
	if (prom_vers == PROM_P1275) {
		rc = p1275_cmd ("read", 3, fd, buff, size);
#ifdef TESTING		
{
unsigned char *b = buff;
printf ("Reading %x %d %d %x %x %x %x %x %x %x %x %x\n", fd, size, rc, (unsigned)offset, b[0],b[1],b[2],b[3],b[4],b[5],b[6],b[7]);
}
#endif
	} else {
		rc = (*romvec->pv_v2devops.v2_dev_read) (fd, buff, size);
	}
	if (!net) {
	    seekp += size;
	    if (rc == size)
	        return size;
	} else
	    return rc;
    }
    return -1;
}

int xmit (char *buff, int size)
{
    if (!net) return -1;
    switch (prom_vers) {
    case PROM_V0:
    	return (*romvec->pv_v0devops.v0_wrnetdev) (fd, size, buff);
    case PROM_V2:
    case PROM_V3:
    	return (*romvec->pv_v2devops.v2_dev_write) (fd, buff, size);
    case PROM_P1275:
    	return p1275_cmd ("write", 3, fd, buff, size);
    default:
    	return -1;
    }
}

void close ()
{
    if (*currentdevice) {
    	switch (prom_vers) {
    	case PROM_V0:
	    (*romvec->pv_v0devops.v0_devclose) (fd); break;
	case PROM_V2:
	case PROM_V3:
	    (*romvec->pv_v2devops.v2_dev_close) (fd); break;
	case PROM_P1275:
	    p1275_cmd ("close", 1, fd); break;
	}
    }
    *currentdevice = 0;
}

int setdisk (char *device)
{
    if (!strcmp (currentdevice, device)) {
	return 0;
    }
    close ();
    return open (device);
}
