/*      $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);
}