/*
 *
 * $Id: k3bscsicommand_bsd.cpp 679320 2007-06-23 15:57:22Z trueg $
 * Copyright (C) 2003-2007 Sebastian Trueg <trueg@k3b.org>
 *
 * This file is part of the K3b project.
 * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
 *
 * 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.
 * See the file "COPYING" for the exact licensing terms.
 */

#include "k3bscsicommand.h"
#include "k3bdevice.h"

#include <k3bdebug.h>

#include <stdio.h>
#include <errno.h>
#include <camlib.h>
#include <cam/scsi/scsi_message.h>
#include <cam/scsi/scsi_pass.h>

#define ERRCODE(s)	((((s)[2]&0x0F)<<16)|((s)[12]<<8)|((s)[13]))
#define EMEDIUMTYPE	EINVAL
#define	ENOMEDIUM	ENODEV
#define CREAM_ON_ERRNO(s)	do {			\
    switch ((s)[12])					\
    {	case 0x04:	errno=EAGAIN;	break;		\
	case 0x20:	errno=ENODEV;	break;		\
	case 0x21:	if ((s)[13]==0)	errno=ENOSPC;	\
			else		errno=EINVAL;	\
			break;				\
	case 0x30:	errno=EMEDIUMTYPE;  break;	\
	case 0x3A:	errno=ENOMEDIUM;    break;	\
    }							\
} while(0)



class K3bDevice::ScsiCommand::Private
{
public:
  union ccb ccb;
};


void K3bDevice::ScsiCommand::clear()
{
  memset (&d->ccb,0,sizeof(ccb));
}


unsigned char& K3bDevice::ScsiCommand::operator[]( size_t i )
{
  if( d->ccb.csio.cdb_len < i+1 )
    d->ccb.csio.cdb_len = i+1;
  return d->ccb.csio.cdb_io.cdb_bytes[i];
}

int K3bDevice::ScsiCommand::transport( TransportDirection dir,
				       void* data,
				       size_t len )
{
  if( !m_device )
    return -1;

  m_device->usageLock();

  bool needToClose = false;
  if( !m_device->isOpen() ) {
    needToClose = true;
  }

  if( !m_device->open( true ) ) {
      m_device->usageUnlock();
      return -1;
  }
  d->ccb.ccb_h.path_id    = m_device->handle()->path_id;
  d->ccb.ccb_h.target_id  = m_device->handle()->target_id;
  d->ccb.ccb_h.target_lun = m_device->handle()->target_lun;

  k3bDebug() << "(K3bDevice::ScsiCommand) transport command " << TQString::number((int)d->ccb.csio.cdb_io.cdb_bytes[0], 16) << ", length: " << (int)d->ccb.csio.cdb_len << endl;
  int ret=0;
  int direction = CAM_DEV_TQFRZDIS;
  if (!len)
    direction |= CAM_DIR_NONE;
  else
    direction |= (dir & TR_DIR_READ)?CAM_DIR_IN : CAM_DIR_OUT;
  cam_fill_csio (&(d->ccb.csio), 1, 0 /* NULL */, direction, MSG_SIMPLE_TQ_TAG, (u_int8_t *)data, len, sizeof(d->ccb.csio.sense_data), d->ccb.csio.cdb_len, 30*1000);
  unsigned char * sense = (unsigned char *)&d->ccb.csio.sense_data;

  ret = cam_send_ccb(m_device->handle(), &d->ccb);

  if (ret < 0) {
      k3bDebug() << "(K3bDevice::ScsiCommand) transport failed: " << ret << endl;

      if( needToClose )
          m_device->close();

      m_device->usageUnlock();

      struct scsi_sense_data* senset = (struct scsi_sense_data*)sense;
      debugError( d->ccb.csio.cdb_io.cdb_bytes[0],
		  senset->error_code & SSD_ERRCODE,
		  senset->flags & SSD_KEY,
		  senset->add_sense_code,
		  senset->add_sense_code_qual );

      int result = (((senset->error_code & SSD_ERRCODE)<<24) & 0xF000 |
                    ((senset->flags & SSD_KEY)<<16)          & 0x0F00 |
                    (senset->add_sense_code<<8)              & 0x00F0 |
                    (senset->add_sense_code_qual)            & 0x000F );

      return result ? result : ret;
  }

  else if ((d->ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_RETQ_CMP) {
      if( needToClose )
          m_device->close();
      m_device->usageUnlock();
      return 0;
  }

  errno = EIO;
  // FreeBSD 5-CURRENT since 2003-08-24, including 5.2 fails to
  // pull sense data automatically, at least for ATAPI transport,
  // so I reach for it myself...
  if ((d->ccb.csio.scsi_status==SCSI_STATUS_CHECK_COND) &&
      !(d->ccb.ccb_h.status&CAM_AUTOSNS_VALID))
    {
      u_int8_t  _sense[18];
      u_int32_t resid=d->ccb.csio.resid;

      memset(_sense,0,sizeof(_sense));

      operator[](0)      = 0x03;	// REQUEST SENSE
      d->ccb.csio.cdb_io.cdb_bytes[4] = sizeof(_sense);
      d->ccb.csio.cdb_len   = 6;
      d->ccb.csio.ccb_h.flags |= CAM_DIR_IN|CAM_DIS_AUTOSENSE;
      d->ccb.csio.data_ptr  = _sense;
      d->ccb.csio.dxfer_len = sizeof(_sense);
      d->ccb.csio.sense_len = 0;

      ret = cam_send_ccb(m_device->handle(), &d->ccb);

      d->ccb.csio.resid = resid;
      if (ret<0)
	{
	  k3bDebug() << "(K3bDevice::ScsiCommand) transport failed (2): " << ret << endl;
	  ret = -1;
	  struct scsi_sense_data* senset = (struct scsi_sense_data*)sense;
	  debugError( d->ccb.csio.cdb_io.cdb_bytes[0],
		      senset->error_code & SSD_ERRCODE,
		      senset->flags & SSD_KEY,
		      senset->add_sense_code,
		      senset->add_sense_code_qual );

	  if( needToClose )
	    m_device->close();
          m_device->usageUnlock();

	  return -1;
	}
      if ((d->ccb.ccb_h.status&CAM_STATUS_MASK) != CAM_RETQ_CMP)
	{
	  k3bDebug() << "(K3bDevice::ScsiCommand) transport failed (3): " << ret << endl;
	  errno=EIO,-1;
	  ret = -1;
	  struct scsi_sense_data* senset = (struct scsi_sense_data*)sense;
	  debugError( d->ccb.csio.cdb_io.cdb_bytes[0],
		      senset->error_code & SSD_ERRCODE,
		      senset->flags & SSD_KEY,
		      senset->add_sense_code,
		      senset->add_sense_code_qual );

	  if( needToClose )
	    m_device->close();
          m_device->usageUnlock();

	  return -1;
	}

      memcpy(sense,_sense,sizeof(_sense));
    }

  ret = ERRCODE(sense);
  k3bDebug() << "(K3bDevice::ScsiCommand) transport failed (4): " << ret << endl;
  if (ret == 0)
    ret = -1;
  else
    CREAM_ON_ERRNO(((unsigned char *)&d->ccb.csio.sense_data));
  struct scsi_sense_data* senset = (struct scsi_sense_data*)sense;
  debugError( d->ccb.csio.cdb_io.cdb_bytes[0],
	      senset->error_code & SSD_ERRCODE,
	      senset->flags & SSD_KEY,
	      senset->add_sense_code,
	      senset->add_sense_code_qual );

  if( needToClose )
    m_device->close();
  m_device->usageUnlock();

  return ret;
}