/*	$NetBSD: diskio.c,v 1.6 2014/03/26 18:04:34 christos Exp $	*/

/*
 * Copyright (c) 1995 Waldi Ravens.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *        This product includes software developed by Waldi Ravens.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <xhdi.h>
#include "libtos.h"
#include "aptck.h"
#include "ahdilbl.h"
#include <osbind.h>

struct pun_info {
	u_int16_t	puns;
	u_int8_t	pun[16];
	u_int32_t	part_start[16];
	u_int32_t	P_cookie;
	u_int32_t	*P_cookptr;
	u_int16_t	P_version;
	u_int16_t	P_max_sector;
	u_int32_t	reserved[16];
};

static char *	strbd    PROTO((char *, ...));
static int	setmami  PROTO((disk_t *, char *));
static int	setnames PROTO((disk_t *));
static int	setsizes PROTO((disk_t *));
static int	ahdi_compatible PROTO((void));

disk_t *
disk_open(char *name)
{
	disk_t	*dd;
	
	dd = xmalloc(sizeof *dd);
	memset(dd, 0, sizeof *dd);

	if (setmami(dd, name) || setnames(dd) || setsizes(dd)) {
		disk_close(dd);
		return(NULL);
	}
	return(dd);
}

void
disk_close(disk_t *dd)
{
	if (dd) {
		free(dd->product);
		free(dd->sname);
		free(dd->fname);
		free(dd->roots);
		free(dd->parts);
		free(dd);
	}
}

void *
disk_read(dd, start, count)
	disk_t	*dd;
	u_int	start,
		count;
{
	char	*buffer;
	int	bdev;
	long	e;

	buffer = xmalloc(count * dd->bsize);

	e = XHReadWrite(dd->major, dd->minor, 0, start, count, buffer);
	if (!e)
		return(buffer);
	if (e == -32 || (e == -1 && XHGetVersion() == -1)) {
		if (!ahdi_compatible())
		    fatal(-1, "AHDI 3.0 compatible harddisk driver required");
		bdev = BIOSDEV(dd->major, dd->minor);
		if (bdev && !bios_read(buffer, start, count, bdev))
			return(buffer);
	}

	free(buffer);
	return(NULL);
}

int
disk_write(dd, start, count, buffer)
	disk_t	*dd;
	u_int	start,
		count;
	void	*buffer;
{
	int	bdev;
	long	e;

	e = XHReadWrite(dd->major, dd->minor, 1, start, count, buffer);
	if (e == -32 || (e == -1 && XHGetVersion() == -1)) {
		if (!ahdi_compatible())
		    fatal(-1, "AHDI 3.0 compatible harddisk driver required");
		bdev = BIOSDEV(dd->major, dd->minor);
		if (bdev)
			e = bios_write(buffer, start, count, bdev);
	}

	return((int)e);
}

static int
ahdi_compatible(void)
{
	static int	ahdi_compat;

	if (!ahdi_compat) {
		long		oldsp = Super(0L);
		struct pun_info	*punp = *((struct pun_info **)0x0516);
		Super(oldsp);
		if (punp && punp->P_cookie == 0x41484449
				&& punp->P_cookptr == &punp->P_cookie
				&& punp->P_version >= 0x0300)
			ahdi_compat = 1;
	}
	return(ahdi_compat);
}

static int
setmami(disk_t *dd, char *name)
{
	char	*p = name;
	u_int	target, lun;
	bus_t	bus;

	if (*p == 'i') {
		bus = IDE;
		if (*++p < '0' || *p > '1') {
			if (*p)
			    error(-1, "%s: invalid IDE target `%c'", name, *p);
			else
			    error(-1, "%s: missing IDE target", name);
			return(-1);
		}
		target = *p++ - '0';
		lun = 0;
	} else {
		char	*b;

		if (*p == 'a') {
			bus = ACSI;
			b = "ACSI";
		} else if (*p == 's') {
			bus = SCSI;
			b = "SCSI";
		} else {
			error(-1, "%s: invalid DISK argument", name);
			return(-1);
		}
		if (*++p < '0' || *p > '7') {
			if (*p)
				error(-1, "%s: invalid %s target `%c'", name,
									b, *p);
			else
				error(-1, "%s: missing %s target", name, b);
			return(-1);
		}
		target = *p++ - '0';

		if (*p < '0' || *p > '7') {
			if (*p) {
				error(-1, "%s: invalid %s lun `%c'", name,
								     b, *p);
				return(-1);
			}
			lun = 0;
		} else
			lun = *p++ - '0';
	}
	if (*p) {
		error(-1, "%s: invalid DISK argument", name);
		return(-1);
	}
	dd->major = MAJOR(bus, target, lun);
	dd->minor = MINOR(bus, target, lun);
	return(0);
}

static int
setnames(disk_t *dd)
{
	char	sn[16], us[16], ls[16], *bs;
	int	b, u, l;

	b = BUS(dd->major, dd->minor);
	u = TARGET(dd->major, dd->minor);
	l = LUN(dd->major, dd->minor);

	switch (b) {
	case IDE:	bs = "IDE";
			break;
	case ACSI:	bs = "ACSI";
			break;
	case SCSI:	bs = "SCSI";
			break;
	default:	error(-1, "invalid bus no. %d", b);
			return(-1);
	}

	if (u < 0 || u > 7 || (b == IDE && u > 1)) {
		error(-1, "invalid %s target `%d'", bs, u);
		return(-1);
	}
	snprintf(us, sizeof(us), " target %d", u);

	if (l < 0 || l > 7 || (b == IDE && l > 0)) {
		error(-1, "invalid %s lun `%d'", bs, l);
		return(-1);
	}
	if (b == IDE) {
		snprintf(sn, sizeof(sn), "i%d", u);
		ls[0] = '\0';
	} else {
		snprintf(sn, sizeof(sn), "%c%d%d", tolower(*bs), u, l);
		snprintf(ls, sizeof(ls), " lun %d", l);
	}

	dd->fname = strbd(bs, us, ls, NULL);
	dd->sname = strbd(sn, NULL);
	return(0);
}

static int
setsizes(disk_t *dd)
{
	if (XHGetVersion() != -1) {
	    char	*p, prod[1024];

	    if (XHInqTarget2(dd->major, dd->minor, &dd->bsize, NULL, prod,
								sizeof(prod))) {
		if (XHInqTarget(dd->major, dd->minor, &dd->bsize, NULL, prod)) {
			error(-1, "%s: device not configured", dd->sname);
			return(-1);
		}
	    }
	    p = strrchr(prod, '\0');
	    while (isspace(*--p))
		*p = '\0';
	    dd->product = strbd(prod, NULL);
	    if (!XHGetCapacity(dd->major, dd->minor, &dd->msize, &dd->bsize))
		return(0);
	} else {
	    dd->product = strbd("unknown", NULL);
	    dd->bsize = AHDI_BSIZE;		/* XXX */
	}

	/* Trial&error search for last sector on medium */
	{
		u_int	u, l, m;
		void	*p, (*oldvec)();

		/* turn off etv_critic handler */
		oldvec = Setexc(257, bios_critic);

		u = (u_int)-2; l = 0;
		while (u != l) {
			m = l + ((u - l + 1) / 2);
			p = disk_read(dd, m, 1);
			free(p);
			if (p == NULL)
				u = m - 1;
			else
				l = m;
		}

		/* turn on etv_critic handler */
		(void)Setexc(257, oldvec);

		if (l) {
			dd->msize = l + 1;
			return(0);
		}
		error(-1, "%s: device not configured", dd->sname);
		return(-1);
	}
}

char *
strbd(char *string1)
{
	char		*p, *result;
	size_t		length = 1;
	va_list		ap;

	va_start(ap, string1);
	for (p = string1; p; p = va_arg(ap, char *))
		length += strlen(p);
	va_end(ap);

	*(result = xmalloc(length)) = '\0';

	va_start(ap, string1);
	for (p = string1; p; p = va_arg(ap, char *))
		strcat(result, p);
	va_end(ap);

	return(result);
}
