/*  cdrdao - write audio CD-Rs in disc-at-once mode
 *
 *  Copyright (C) 1998-2001  Andreas Mueller <andreas@daneb.de>
 *
 *  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.
 */

/* SCSI interface implemenation for FreeBSD.
 * Written by Max Khon <fjoe@iclub.nsu.ru>
 */

#include <config.h>

#include <fcntl.h>
#include <stdio.h>
#include <string.h>

#include <camlib.h>
#include <cam/scsi/scsi_message.h>

#include "ScsiIf.h"
#include "util.h"

#define DEF_RETRY_COUNT 1

#include "decodeSense.cc"

class ScsiIfImpl {
public:
	char *			devname;
	struct cam_device *	dev;
	union ccb *		ccb;
	int			timeout;	/* timeout in ms */
};

ScsiIf::ScsiIf(const char *devname)
{
	impl_ = new ScsiIfImpl;
	impl_->devname = strdupCC(devname);
	impl_->dev = NULL;
	impl_->ccb = NULL;
	impl_->timeout = 5000;

	maxDataLen_ = 32 * 1024;
	vendor_[0] = 0;
	product_[0] = 0;
	revision_[0] = 0;
}

ScsiIf::~ScsiIf()
{
	if (impl_->ccb)
		cam_freeccb(impl_->ccb);
	if (impl_->dev) 
		cam_close_device(impl_->dev);
	delete[] impl_->devname;
	delete impl_;
}

// opens scsi device
// return: 0: OK
//         1: device could not be opened
//         2: inquiry failed
int ScsiIf::init()
{
	if ((impl_->dev = cam_open_device(impl_->devname, O_RDWR)) == NULL) {
		message(-2, "%s", cam_errbuf);
		return 1;
	}
  
	impl_->ccb = cam_getccb(impl_->dev);
	if (impl_->ccb == NULL) {
		message(-2, "init: error allocating ccb");
		return 1;
	}

	if (inquiry()) 
		return 2;

	return 0;
}

// Sets given timeout value in seconds and returns old timeout.
// return: old timeout
int ScsiIf::timeout(int t)
{
	int old = impl_->timeout;
	impl_->timeout = t*1000;
	return old/1000;
}

// sends a scsi command and receives data
// return 0: OK
//        1: scsi command failed (os level, no sense data available)
//        2: scsi command failed (sense data available)
int ScsiIf::sendCmd(const unsigned char *cmd, int cmdLen, 
		    const unsigned char *dataOut, int dataOutLen,
		    unsigned char *dataIn, int dataInLen,
		    int showMessage)
{
	int		retval;
	int		flags = CAM_DIR_NONE;
	u_int8_t *	data_ptr;
	size_t		data_len;

	bzero(impl_->ccb, sizeof(union ccb));
	bcopy(cmd, &impl_->ccb->csio.cdb_io.cdb_bytes, cmdLen);

	if (dataOut && dataOutLen > 0) {
		data_ptr = (u_int8_t*) dataOut;
		data_len = dataOutLen;
		flags = CAM_DIR_OUT;
	}
	else if (dataIn && dataInLen > 0) {
		data_ptr = dataIn;
		data_len = dataInLen;
		flags = CAM_DIR_IN;
	}

	cam_fill_csio(&impl_->ccb->csio,
		      DEF_RETRY_COUNT,
		      NULL,
		      flags | CAM_DEV_QFRZDIS,
		      MSG_SIMPLE_Q_TAG,
		      data_ptr,
		      data_len,
		      SSD_FULL_SIZE,
		      cmdLen,
		      impl_->timeout);
	if ((retval = cam_send_ccb(impl_->dev, impl_->ccb)) < 0
	||  (impl_->ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
		if (retval < 0) {
			message(-2, "sendCmd: error sending command");
			return 1;
		}

		if ((impl_->ccb->ccb_h.status & CAM_STATUS_MASK) ==
		    CAM_SCSI_STATUS_ERROR) {
                        if (showMessage)
			        printError();

			return 2;
		}
		return 1;
	}

	return 0;
}

const unsigned char *ScsiIf::getSense(int &len) const
{
	len = impl_->ccb->csio.sense_len;
	return (const unsigned char*) &impl_->ccb->csio.sense_data;
}

void ScsiIf::printError()
{
        decodeSense((const unsigned char*) &impl_->ccb->csio.sense_data,
		    impl_->ccb->csio.sense_len);
}

int ScsiIf::inquiry()
{
	int i;
	struct scsi_inquiry_data inq_data;

	bzero(impl_->ccb, sizeof(union ccb));
	bzero(&inq_data, sizeof(inq_data));

	scsi_inquiry(&impl_->ccb->csio,
		     DEF_RETRY_COUNT,
		     NULL,
		     MSG_SIMPLE_Q_TAG,
		     (u_int8_t*) &inq_data,
		     sizeof(inq_data),
		     0,
		     0,
		     SSD_FULL_SIZE,
		     impl_->timeout);
	impl_->ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;

	if (cam_send_ccb(impl_->dev, impl_->ccb) < 0) {
		if ((impl_->ccb->ccb_h.status & CAM_STATUS_MASK) !=
		    CAM_SCSI_STATUS_ERROR) {
			message(-2, "%s", cam_errbuf);
			return 1;
		}

		printError();
		return 1;
	}

	strncpy(vendor_, inq_data.vendor, 8);
	vendor_[8] = 0;

	strncpy(product_, inq_data.product, 16);
	product_[16] = 0;

	strncpy(revision_, inq_data.revision, 4);
	revision_[4] = 0;

	for (i = 7; i >= 0 && vendor_[i] == ' '; i--)
		vendor_[i] = 0;

	for (i = 15; i >= 0 && product_[i] == ' '; i--)
		product_[i] = 0;

	for (i = 3; i >= 0 && revision_[i] == ' '; i--)
		revision_[i] = 0;

	return 0;
}

ScsiIf::ScanData *ScsiIf::scan(int *len)
{
  *len = 0;
  return NULL;
}

#include "ScsiIf-common.cc"
