/*
 * DATlib   Copyright (c) 1995-1996 Marcus Meissner &
 *          Friedrich-Alexander Universitaet Erlangen-Nuernberg (FAU), IMMD4
 *          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 Marcus Meissner
 *    at the Friedrich-Alexander Universitaet Erlangen-Nuernberg (FAU), IMMD4
 *
 * 4. The name of the University or 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 UNIVERSITY AND 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 UNIVERSITY OR 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.
 */

/* $Id: uscsicmd.c,v 1.3 1997/06/02 18:44:18 msmeissn Exp $
 *
 * $Log: uscsicmd.c,v $
 * Revision 1.3  1997/06/02 18:44:18  msmeissn
 * NULLs for extended_inquiry and read_buffer
 *
 * Revision 1.2  1997/04/24 10:56:03  msmeissn
 * *** empty log message ***
 *
 * Revision 1.1  1997/03/16 20:28:55  msmeissn
 * Initial revision
 *
 *
 */

/* 
 * Implementation der USCSICMD calls der datlib
 */

#if defined(sun)
#define HAS_USCSICMD
#ifndef _BIT_FIELDS_HTOL
#define _BIT_FIELDS_HTOL
#endif /* _BIT_FIELDS_HTOL */
#endif /* sun */

#ifdef HAS_USCSICMD
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/mtio.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <errno.h>

int errno;

/* weird including dingens */
#ifdef sun
#include <sys/condvar.h>
#include <sys/scsi/impl/uscsi.h>
#include <sys/scsi/generic/commands.h>
#include <sys/scsi/generic/inquiry.h>
#include <sys/scsi/targets/stdef.h>
#endif /* sun */

#include "datlib.h"

static int
rq_sense_out(caddr_t rqbuf) {
	fprintf(stderr,"Request Sense reports:\n");
	if ((rqbuf[0]&0x7f)!=0x70) {
		fprintf(stderr,"\tInvalid header.\n");
		return -1;
	}
	fprintf(stderr,"\tCurrent command read filemark: %s\n",(rqbuf[2]&0x80)?"yes":"no");
	fprintf(stderr,"\tEarly warning passed: %s\n",(rqbuf[2]&0x40)?"yes":"no");
	fprintf(stderr,"\tIncorrect blocklengt: %s\n",(rqbuf[2]&0x20)?"yes":"no");
	fprintf(stderr,"\tSense Key: 0x%01x\n",rqbuf[2]&0xf);
	if (rqbuf[0]&0x80)
		fprintf(stderr,"\tResidual Length: %d\n",rqbuf[3]*0x1000000+rqbuf[4]*0x10000+rqbuf[5]*0x100+rqbuf[6]);
	fprintf(stderr,"\tAdditional Sense Length: %d\n",rqbuf[7]);
	fprintf(stderr,"\tAdditional Sense Code: 0x%02x\n",rqbuf[12]);
	fprintf(stderr,"\tAdditional Sense Code Qualifier: 0x%02x\n",rqbuf[13]);
	if (rqbuf[15]&0x80) {
		fprintf(stderr,"\tIllegal Param is in %s\n",(rqbuf[15]&0x40)?"the CDB":"the Data Out Phase");
		if (rqbuf[15]&0x8) {
			fprintf(stderr,"Pointer at %d, bit %d\n",rqbuf[16]*256+rqbuf[17],rqbuf[15]&0x7);
		}
	}
	return 0;
}


int uscsicmd_create_partition(int fd,int size,int size_kind);
int uscsicmd_delete_partition(int fd);
int uscsicmd_switch_active_partition(int fd,int targetpart);
int uscsicmd_query_active_partition(int fd);
int uscsicmd_set_compression(int fd,int compression);
int uscsicmd_set_decompression(int fd,int decompression);
int uscsicmd_query_compression(int fd);
int uscsicmd_query_decompression(int fd);
int uscsicmd_get_log_page(int fd,int nr,unsigned char *logpage,int logsize);
int uscsicmd_get_mode_page(int fd,int nr,unsigned char *modepage,int modesize);
int uscsicmd_set_mode_page(int fd,unsigned char *modepage,int modesize);
int uscsicmd_get_inquiry(int fd,unsigned char *inqbuf,int inqsize);
int uscsicmd_request_sense(int fd,unsigned char *inqbuf,int inqsize);

static struct datlib_functions datlib_uscsicmd_funs={
	"uscsicmd",
	NULL,
	uscsicmd_create_partition,
	uscsicmd_delete_partition,
	uscsicmd_switch_active_partition,
	uscsicmd_query_active_partition,
	uscsicmd_set_compression,
	uscsicmd_set_decompression,
	uscsicmd_query_compression,
	uscsicmd_query_decompression,
	uscsicmd_get_log_page,
	uscsicmd_get_mode_page,
	uscsicmd_set_mode_page,
	uscsicmd_get_inquiry,
	uscsicmd_request_sense,
	NULL,/*extended_inquiry*/
	NULL,/*read_buffer*/
};

enum drivetype { DRIVE_HP,DRIVE_ARCHIVE,DRIVE_SONY };

struct datlib_functions *
datlib_uscsicmd_init(int fd) {
	unsigned char	inqbuf[100];

	if (fd<0) {
		errno=EBADF;
		return NULL;
	}
	if (-1==uscsicmd_get_inquiry(fd,inqbuf,100))
		return NULL;
	/* FIXME: media changers? */
	if (inqbuf[0]!=DTYPE_SEQUENTIAL) {
		fprintf(stderr,"uscsicmd.c:init:Not a sequential access device.\n");
		errno=ENOTTY;
		return NULL;
	}
	if ((inqbuf[2]&0x3)!=2) {
		fprintf(stderr,"uscsicmd.c:init:Need to operate in SCSI-2 mode.\n");
		errno=ENOTTY;
		return NULL;
	}
	if (strncmp((char*)inqbuf+8,"ARCHIVE Python ",strlen("ARCHIVE Python"))) {
		inqbuf[inqbuf[4]+5]='\0';/* terminate string */
/*		fprintf(stderr,"uscsicmd.c:init:Warning: Drive is not a ARCHIVE Python (%s detected), but continuing anyway.\n",inqbuf+4);*/
	}
	return &datlib_uscsicmd_funs;
}

enum drivetype
uscsicmd_get_drivetype(int fd) {
	char	buf[100];

	if (-1==uscsicmd_get_inquiry(fd,buf,100))
		return -1;
	if (!strncmp((char*)buf+8,"HP ",strlen("HP ")))
		return DRIVE_HP;
	if (!strncmp((char*)buf+8,"SONY ",strlen("SONY ")))
		return DRIVE_SONY;
	if (!strncmp((char*)buf+8,"ARCHIVE ",strlen("ARCHIVE ")))
		return DRIVE_ARCHIVE;
	return 0;
}


int 
uscsicmd_create_partition(int fd,int size,int size_kind) {
	char			buf[100];
	enum	drivetype	dt=uscsicmd_get_drivetype(fd);
	int			dsize;

	if (size_kind<SIZE_BYTE || size_kind>SIZE_MBYTE)
		RETURN(-EINVAL);
	switch (dt) {
	default:
		if (size_kind!=SIZE_MBYTE) {
			fprintf(stderr,"uscsicmd_create_partition: drive only supports MByte sized partitions.\n");
			return -1;
		}
		break;
	case DRIVE_ARCHIVE:
		/* supports all types */
		break;
	}
	if (size<=0 || size>65535)
		RETURN(-EINVAL);
	if (-1 == uscsicmd_get_mode_page(fd,0x11,buf,27))
		return -1;

	/* Parameter List Header */
	buf[0]=0x00;
	/* this is a mode page */
	buf[15]=0x01;/* nr. of partitions ... */
	buf[16]=0x20|(size_kind<<3);/* DO PARTITIONING, depending on kind */

	dsize = 27;
	switch (dt) {
	default: /* possible FIXME */
	case DRIVE_HP:
	case DRIVE_ARCHIVE:
		buf[13]=0x08; /* length */
		buf[20]=(size>>8)&0xff;
		buf[21]=size&0xff;/* 1 Megabyte */
		dsize = 22;
		break;
	case DRIVE_SONY: {
		int	ysize,xsize;

		buf[13]=0x0a;
		dsize = 24;
		/* determine complete size */
		/* 20,21 - first, 22,23 - second partition */
		xsize = buf[20]*256+buf[21]+buf[22]*256+buf[23];
		ysize = xsize-size;
		buf[20] = ysize>>8;
		buf[21] = ysize&0xff;
		buf[22] = size>>8;
		buf[23] = size&0xff;
		break;
	}
	}
	return uscsicmd_set_mode_page(fd,buf,dsize);
}

int
uscsicmd_delete_partition(int fd) {
	char			buf[27];
	enum drivetype		dt=uscsicmd_get_drivetype(fd);
	int			size,res;

	if (-1==uscsicmd_get_mode_page(fd,0x11,buf,27))
		return -1;
	size = 22;
	/* build paramlist */
	/* Parameter List Header,
	 * just set byte 0 to 0, the rest stays the same
	 */
	buf[0]=	0x00;
	/* this is a mode page, starting at offset 12 */
	buf[15]	=0x00;	/* nr. of partitions ... 0 means delete*/
	buf[16]|=0x20;	/* DO PARTITIONING */
	size = 22;
	switch (dt) {
	case DRIVE_ARCHIVE:
		buf[13]	=0x06;
		size = 20;
		break;
	case DRIVE_SONY: {
		int	ysize,xsize;

		xsize = buf[20]*256+buf[21]+buf[22]*256+buf[23];
		buf[20] = xsize>>8;
		buf[21] = xsize&0xff;
		buf[22] = buf[23] =0;
		buf[13]	=0x0A;	/* shorten page */
		size = 24;
		break;
	}
	default:
		buf[20]	=0x00;
		buf[21]	=0x00;
		break;
	}
	return uscsicmd_set_mode_page(fd,buf,size);
}

int
uscsicmd_switch_active_partition(int fd,int target) {
	char		buf[100];

	if (target<0 || target>1)
		RETURN(-EINVAL);
	if (-1==uscsicmd_get_mode_page(fd,0x10,buf,100))
		return -1;
	buf[0]=0;
	buf[14]|=0x40;	/* Change Active Partition */
	buf[15]=target;/* target partition */
	return uscsicmd_set_mode_page(fd,buf,28);
}

int
uscsicmd_query_active_partition(int fd) {
	char		buf[100];

	if (-1==uscsicmd_get_mode_page(fd,0x10,buf,100))
		return -1;
	return buf[15]; /* partition nr */
}

int
uscsicmd_set_compression(int fd,int compress) {
	unsigned char		buf[100];

	/* get old page */
	if (-1==uscsicmd_get_mode_page(fd,0xf,buf,100))
		return -1;

	buf[0] =0x00;/* need to reset that */
	buf[2]|=0x10;
	/* only change values we need */
	if (compress)
		buf[14]|= 0x80;
	else
		buf[14]&=~0x80;
	if (compress==1) 
		compress=0x20;
	buf[19]=compress;  /* default compression */
	return uscsicmd_set_mode_page(fd,buf,28);
}

int
uscsicmd_set_decompression(int fd,int decompress) {
	unsigned char		buf[100];

	if (-1==uscsicmd_get_mode_page(fd,0xf,buf,100))
		return -1;

	buf[0]=0x00;/* need to reset that */
	/* only change values we need */
	buf[15]=decompress?0x80:0x00;/* Data Decompression Enable */
	buf[15]|=0x20;/*report error on decomp */
	buf[23]=decompress;/* default decompression */

	return uscsicmd_set_mode_page(fd,buf,28);
}

int
uscsicmd_query_compression(int fd) {
	unsigned char		buf[100];

	if (-1==uscsicmd_get_mode_page(fd,0xf,buf,100))
		return -1;
	return (buf[14]&0x80)?buf[19]:0;
}

int
uscsicmd_query_decompression(int fd) {
	char			buf[100];

	if (-1==uscsicmd_get_mode_page(fd,0xf,buf,100))
		return -1;
	return (buf[15]&0x80)?buf[23]:0;
}

int
uscsicmd_get_log_page(int fd,int nr,unsigned char *logpage,int logsize) {
	struct uscsi_cmd	cmd;
	char			cdb[10];
	char			*buf;
	if (logsize<=0)
		RETURN(-EINVAL);
	if (logsize>65535) logsize=65535;
			
	buf=(char*)malloc(logsize);
	cmd.uscsi_flags=USCSI_READ|USCSI_ISOLATE|USCSI_DIAGNOSE;
	cmd.uscsi_timeout=1000;
	cmd.uscsi_cdb=cdb;
	cmd.uscsi_cdblen=10;
	cmd.uscsi_bufaddr=buf;
	cmd.uscsi_buflen=logsize;
	cmd.uscsi_rqbuf=NULL;
	cmd.uscsi_rqlen=0;
	cmd.uscsi_resid=0;
	memset(buf,'\0',100);
	cdb[0]=SCMD_LOG_SENSE;
	cdb[1]=0x00;
	cdb[2]=  nr|0x40;
	cdb[3]=0x00;
	cdb[4]=0x00;
	cdb[5]=0x00;
	cdb[6]=0x00;
	cdb[7]=logsize>>8;
	cdb[8]=logsize&0xff;
	cdb[9]=0x00;
	if (-1==ioctl(fd,USCSICMD,&cmd)) {
		return -1; /* errno already set */
	}
	memcpy(logpage,buf,logsize);
	free(buf);
	return logsize;
}

int
uscsicmd_set_mode_page(int fd,unsigned char *modepage,int modesize) {
	struct uscsi_cmd	cmd;
	char			cdb[6];

	if (modesize<=0)
		RETURN(-EINVAL);
	if (modesize>255) modesize=255;
			
	cmd.uscsi_flags=USCSI_WRITE|USCSI_DIAGNOSE;
	cmd.uscsi_timeout=1000;
	cmd.uscsi_cdb=cdb;
	cmd.uscsi_cdblen=6;
	cmd.uscsi_bufaddr=modepage;
	cmd.uscsi_buflen=modesize;
	cmd.uscsi_rqbuf=NULL;
	cmd.uscsi_rqlen=0;
	cmd.uscsi_resid=0;
	cdb[0]=SCMD_MODE_SELECT;
	cdb[1]=0x10; /* interpret as SCSI-2 command */
	cdb[2]=0x00;
	cdb[3]=0x00;
	cdb[4]=modesize;
	cdb[5]=0x00;
	return ioctl(fd,USCSICMD,&cmd);
}
int
uscsicmd_get_mode_page(int fd,int nr,unsigned char *modepage,int modesize) {
	struct uscsi_cmd	cmd;
	char			cdb[6];
	char			*buf;
	if (modesize<=0)
		RETURN(-EINVAL);
	if (modesize>255) modesize=255;
			
	buf=(char*)malloc(modesize);
	cmd.uscsi_flags=USCSI_READ|USCSI_ISOLATE|USCSI_DIAGNOSE;
	cmd.uscsi_timeout=1000;
	cmd.uscsi_cdb=cdb;
	cmd.uscsi_cdblen=6;
	cmd.uscsi_bufaddr=buf;
	cmd.uscsi_buflen=modesize;
	cmd.uscsi_rqbuf=NULL;
	cmd.uscsi_rqlen=0;
	cmd.uscsi_resid=0;
	memset(buf,'\0',modesize);
	cdb[0]=SCMD_MODE_SENSE;
	cdb[1]=0x00;
	cdb[2]=nr;
	cdb[3]=0x00;
	cdb[4]=modesize;
	cdb[5]=0x00;
	if (-1==ioctl(fd,USCSICMD,&cmd))
		return -1; /* errno already set */
	/* check size , note that buf[0] doesn't count itself into this...*/
	if (modesize>buf[0]+1);
		modesize=buf[0]+1;
	memcpy(modepage,buf,modesize);
	free(buf);
	return modesize;
}

int
uscsicmd_get_inquiry(int fd,unsigned char *inqbuf,int inqlen) {
	char	cdb[6];
	struct	uscsi_cmd	cmd;

	if (inqlen<0)	return EINVAL;
	if (inqlen>255)	inqlen=255;
	cmd.uscsi_flags=USCSI_ISOLATE|USCSI_DIAGNOSE|USCSI_READ;
	cmd.uscsi_timeout=1000;
	cmd.uscsi_cdb=cdb;
	memset(inqbuf,'\0',inqlen);
	cdb[0]=SCMD_INQUIRY;	
	cdb[1]=0x00;
	cdb[2]=0x00;
	cdb[3]=0x00;
	cdb[4]=inqlen;
	cdb[5]=0x00;
	cmd.uscsi_cdblen=6;
	cmd.uscsi_bufaddr=(caddr_t)inqbuf;
	cmd.uscsi_buflen=inqlen;
	if (-1==ioctl(fd,USCSICMD,&cmd))
		return -1;
	return 0;
}

int
uscsicmd_request_sense(int fd,unsigned char *inqbuf,int inqlen) {
	char	cdb[6];
	struct	uscsi_cmd	cmd;

	if (inqlen<0)	return EINVAL;
	if (inqlen>255)	inqlen=255;
	cmd.uscsi_flags=USCSI_ISOLATE|USCSI_DIAGNOSE|USCSI_READ;
	cmd.uscsi_timeout=1000;
	cmd.uscsi_cdb=cdb;
	memset(inqbuf,'\0',inqlen);
	cdb[0]=SCMD_REQUEST_SENSE;
	cdb[1]=0x00;
	cdb[2]=0x00;
	cdb[3]=0x00;
	cdb[4]=inqlen;
	cdb[5]=0x00;
	cmd.uscsi_cdblen=6;
	cmd.uscsi_bufaddr=(caddr_t)inqbuf;
	cmd.uscsi_buflen=inqlen;
	if (-1==ioctl(fd,USCSICMD,&cmd))
		return -1;
	return inqbuf[7]+8;
}
#else /* !HAS_USCSICMD */

#include <errno.h>
#include <stdio.h>
int errno;

struct datlib_functions*
datlib_uscsicmd_init(int fd) {
	errno=ENOENT;
	return NULL;
}
#endif
