/* 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"

typedef int (*Read0) (int dev, int num_blks, int blk_st, char *buf);
typedef int (*Seek2) (int, int, int);
typedef int (*Read2) (int, char *, int);

typedef struct {
    int fd_;
    int net_;
    int seekp_;
    int floppy_;
    Read0 read0_;
    Seek2 seek2_;
    Read2 read2_;
    const struct linux_romvec *promvec;
} Disk;

static Disk disk0;
char bootdevice[4096];
static char currentdevice[4096];

int open (char *device)
{
    register Disk *t = &disk0;

    strcpy (currentdevice, device);
    t->net_ = 0;
    t->floppy_ = 0;
    if (t->promvec->pv_romvers < 2) {
        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;
	t->fd_ = (*t->promvec->pv_v0devops.v0_devopen) (p);
	if (device[0] == 'f' && device[1] == 'd')
	    t->floppy_ = 1;
	else if ((device[0] == 'l' || device[0] == 'i') && device[1] == 'e')
	    t->net_ = 1;
    } else {
    	int node;
    	char buffer[20];
    	
	t->fd_ = (*t->promvec->pv_v2devops.v2_dev_open) (device);
	if ((unsigned)(t->fd_ + 1) > 1) {
	    node = (*t->promvec->pv_v2devops.v2_inst2pkg) (t->fd_);
	    prom_getstring (node, "device_type", buffer, 20);
	    if (!strcmp (buffer, "network"))
	        t->net_ = 1;
	}
    }
    if (t->fd_ == 0 || t->fd_ == -1) {
	printf ("\nFatal error: Couldn't open device %s\n", device);
	return -1;
    }
    return 0;
}

extern char boot_part;

int diskinit (const struct linux_romvec *promvec)
{
    register Disk *t = &disk0;

    t->promvec = promvec;
    t->fd_ = 0;
    t->seekp_ = -1;
    t->read0_ = 0;
    t->seek2_ = 0;
    t->read2_ = 0;
    if (promvec->pv_romvers < 2) {
	struct linux_arguments_v0 *ap = *promvec->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;
	t->read0_ = promvec->pv_v0devops.v0_rdblkdev;
    } else {
        char *p;
	t->seek2_ = (Seek2) promvec->pv_v2devops.v2_dev_seek;
	t->read2_ = promvec->pv_v2devops.v2_dev_read;
	strcpy (bootdevice, *promvec->pv_v2bootargs.bootpath);
	p = strchr (bootdevice, 0);
	if (*(p - 2) == ':' && *(p - 1) >= 'a' && *(p - 1) <= 'h')
	    *(p - 1) = boot_part + 'a';
	else
	    *p++ = ':'; *p++ = boot_part + 'a'; *p = 0;
    }
    return open (bootdevice);
}

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

int read (char *buff, int size, int offset)
{
    register Disk *t = &disk0;

    if (!size)
	return 0;
    if (t->read0_) {
    	if (t->net_)
    	    return (*t->promvec->pv_v0devops.v0_rdnetdev) (t->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 = (*t->read0_) (t->fd_, 1, 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) & 0x1ff);
	        ret = i;
	    }
	    if (size >> 9) {
		for (j = 0; j < 5; j++) {
	        	rc = (*t->read0_) (t->fd_, size >> 9, 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 = (*t->read0_) (t->fd_, 1, offset >> 9, buffer);
	        	if (rc) break;
	        	reopen();
	        }
	        if (rc != 1)
		    return -1;
	        memcpy (buff, buffer, size);
	        ret += size;
	    }
	    return ret;
	}
    } else {
	int rc;
	
	if (!t->net_) {
	    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 (t->seekp_ != offset) {
	        if ((*t->seek2_) (t->fd_, 0, offset) == -1)
		    return -1;
	        t->seekp_ = offset;
	    }
	}
	rc = (*t->read2_) (t->fd_, buff, size);
	if (!t->net_) {
	    t->seekp_ += size;
	    if (rc == size)
	        return size;
	} else
	    return rc;
    }
    return -1;
}

int xmit (char *buff, int size)
{
    register Disk *t = &disk0;
    
    if (!t->net_) return -1;
    if (t->read0_)
    	return (*t->promvec->pv_v0devops.v0_wrnetdev) (t->fd_, size, buff);
    else
    	return (*t->promvec->pv_v2devops.v2_dev_write) (t->fd_, buff, size);
}

void close ()
{
    if (*currentdevice) {
        if (disk0.promvec->pv_romvers < 2)
	    (*disk0.promvec->pv_v0devops.v0_devclose) (disk0.fd_);
        else
	    (*disk0.promvec->pv_v2devops.v2_dev_close) (disk0.fd_);
    }
    *currentdevice = 0;
}

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