/*
 * os_qnxnto.cpp
 *
 * Home page of code is: https://www.smartmontools.org
 *
 * Copyright (C) 2007 Joerg Hering
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

// This is needed for the various HAVE_* macros and PROJECT_* macros.
#include "config.h"

// These are needed to define prototypes and structures for the
// functions defined below
#include <smartmon/atacmds.h>
#include <smartmon/scsicmds.h>
#include <smartmon/utility.h>

// This is to include whatever structures and prototypes you define in
// os_generic.h
#include "os_qnxnto.h"
#include <errno.h>

// This is here to prevent compiler warnings for unused arguments of
// functions.
#define ARGUSED(x) ((void)(x))

//----------------------------------------------------------------------------------------------
// private Functions
static int ata_sense_data(void *sdata,int *error,int *key,int *asc,int *ascq);
static int ata_interpret_sense(struct cam_pass_thru *cpt,void *sense,int *status,int rcount);
static int ata_pass_thru(int fd,struct cam_pass_thru *pcpt);

// print examples for smartctl.  You should modify this function so
// that the device paths are sensible for your OS, and to eliminate
// unsupported commands (eg, 3ware controllers).
void print_smartctl_examples(){
  printf("=================================================== SMARTCTL EXAMPLES =====\n\n"
         "  smartctl -a /dev/hd0                       (Prints all SMART information)\n\n"
         "  smartctl --smart=on --offlineauto=on --saveauto=on /dev/hd0\n"
         "                                              (Enables SMART on first disk)\n\n"
         "  smartctl -t long /dev/hd0              (Executes extended disk self-test)\n\n"
         "  smartctl --attributes --log=selftest --quietmode=errorsonly /dev/hd0\n"
         "                                      (Prints Self-Test & Attribute errors)\n"
         "  smartctl -a --device=3ware,2 /dev/sda\n"
         "          (Prints all SMART info for 3rd ATA disk on 3ware RAID controller)\n"
         );
  return;
}

// tries to guess device type given the name (a path).  See utility.h
// for return values.
static const char *net_dev_prefix = "/dev/";
static const char *net_dev_ata_disk = "hd";

int guess_device_type (const char* dev_name)
{
int len,dev_prefix_len;
  dev_prefix_len=strlen(net_dev_prefix);
  if(!dev_name||!(len=strlen(dev_name)))
    return(CONTROLLER_UNKNOWN);
  if (!strncmp(net_dev_prefix,dev_name,dev_prefix_len))
   {
    if(len<=dev_prefix_len)
      return(CONTROLLER_UNKNOWN);
    else
      dev_name += dev_prefix_len;
   }
  if(!strncmp(net_dev_ata_disk,dev_name,strlen(net_dev_ata_disk)))
    return(CONTROLLER_ATA);
  return(CONTROLLER_UNKNOWN);
}

// makes a list of ATA or SCSI devices for the DEVICESCAN directive of
// smartd.  Returns number N of devices, or -1 if out of
// memory. Allocates N+1 arrays: one of N pointers (devlist); the
// other N arrays each contain null-terminated character strings.  In
// the case N==0, no arrays are allocated because the array of 0
// pointers has zero length, equivalent to calling malloc(0).
int make_device_names (char*** devlist, const char* name) {
  ARGUSED(devlist);
  ARGUSED(name);
  return 0;
}

// Like open().  Return non-negative integer handle, only used by the
// functions below.  type=="ATA" or "SCSI".  If you need to store
// extra information about your devices, create a private internal
// array within this file (see os_freebsd.cpp for an example).  If you
// can not open the device (permission denied, does not exist, etc)
// set errno as open() does and return <0.
int deviceopen(const char *pathname, char *type)
{
  if(!strcmp(type, "ATA"))
    return(open(pathname,O_RDWR|O_NONBLOCK));
  else
    return(-1);
}

// Like close().  Acts only on integer handles returned by
// deviceopen() above.
int deviceclose(int fd)
{
  return(close(fd));
}
//----------------------------------------------------------------------------------------------
// Interface to ATA devices.  See os_linux.cpp for the canonical example.
// DETAILED DESCRIPTION OF ARGUMENTS
//   device: is the integer handle provided by deviceopen()
//   command: defines the different operations, see atacmds.h
//   select: additional input data IF NEEDED (which log, which type of
//           self-test).
//   data:   location to write output data, IF NEEDED (1 or 512 bytes).
//   Note: not all commands use all arguments.
// RETURN VALUES (for all commands BUT command==STATUS_CHECK)
//  -1 if the command failed
//   0 if the command succeeded,
// RETURN VALUES if command==STATUS_CHECK
//  -1 if the command failed OR the disk SMART status can't be determined
//   0 if the command succeeded and disk SMART status is "OK"
//   1 if the command succeeded and disk SMART status is "FAILING"
int ata_command_interface(int fd,smart_command_set command,int select,char *data)
{
struct cam_pass_thru cpt;
ATA_SENSE            sense;
CDB                  *cdb;
int                  status,rc;
  memset(&cpt,0x00,sizeof(struct cam_pass_thru));
  cdb=(CDB *)cpt.cam_cdb;
  rc=-1;
  switch(command)
   {
    case READ_VALUES:
         cpt.cam_flags                  = CAM_DIR_IN;
         cpt.cam_cdb_len                = 16;
         cpt.cam_dxfer_len              = 512;
         cpt.cam_data_ptr               = (uint32_t)data;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_PIO_DATA_IN;
         cdb->ata_pass_thru.flags       = ATA_FLG_T_DIR|ATA_FLG_TLEN_STPSIU;
         cdb->ata_pass_thru.command     = ATA_SMART_CMD;
         cdb->ata_pass_thru.features    = ATA_SMART_READ_VALUES;
         cdb->ata_pass_thru.lba_mid     = ATA_SMART_LBA_MID_SIG;
         cdb->ata_pass_thru.lba_high    = ATA_SMART_LBA_HI_SIG;
         break;
    case READ_THRESHOLDS:
         cpt.cam_flags                  = CAM_DIR_IN;
         cpt.cam_cdb_len                = 16;
         cpt.cam_dxfer_len              = 512;
         cpt.cam_data_ptr               = (uint32_t)data;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_PIO_DATA_IN;
         cdb->ata_pass_thru.flags       = ATA_FLG_T_DIR|ATA_FLG_TLEN_STPSIU;
         cdb->ata_pass_thru.command     = ATA_SMART_CMD;
         cdb->ata_pass_thru.features    = ATA_SMART_READ_THRESHOLDS;
         cdb->ata_pass_thru.lba_mid     = ATA_SMART_LBA_MID_SIG;
         cdb->ata_pass_thru.lba_high    = ATA_SMART_LBA_HI_SIG;
         break;
    case READ_LOG:
         cpt.cam_flags                  = CAM_DIR_IN;
         cpt.cam_cdb_len                = 16;
         cpt.cam_dxfer_len              = 512;
         cpt.cam_data_ptr               = (uint32_t)data;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_PIO_DATA_IN;
         cdb->ata_pass_thru.flags       = ATA_FLG_T_DIR|ATA_FLG_TLEN_STPSIU;
         cdb->ata_pass_thru.command     = ATA_SMART_CMD;
         cdb->ata_pass_thru.features    = ATA_SMART_READ_LOG_SECTOR;
         cdb->ata_pass_thru.sector_count= 1;
         cdb->ata_pass_thru.lba_low     = select;
         cdb->ata_pass_thru.lba_mid     = ATA_SMART_LBA_MID_SIG;
         cdb->ata_pass_thru.lba_high    = ATA_SMART_LBA_HI_SIG;
         break;
    case WRITE_LOG:
         return(-1);
         break;
    case IDENTIFY:
         cpt.cam_flags                  = CAM_DIR_IN;
         cpt.cam_cdb_len                = 16;
         cpt.cam_dxfer_len              = 512;
         cpt.cam_data_ptr               = (uint32_t)data;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_PIO_DATA_IN;
         cdb->ata_pass_thru.flags       = ATA_FLG_T_DIR|ATA_FLG_TLEN_STPSIU;
         cdb->ata_pass_thru.command     = ATA_IDENTIFY_DEVICE;
         break;
    case PIDENTIFY:
         cpt.cam_flags                  = CAM_DIR_IN;
         cpt.cam_cdb_len                = 16;
         cpt.cam_dxfer_len              = 512;
         cpt.cam_data_ptr               = (uint32_t)data;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_PIO_DATA_IN;
         cdb->ata_pass_thru.flags       = ATA_FLG_T_DIR|ATA_FLG_TLEN_STPSIU;
         cdb->ata_pass_thru.command     = ATA_IDENTIFY_PACKET_DEVICE;
         break;
    case ENABLE:
         cpt.cam_flags                  = CAM_DIR_NONE;
         cpt.cam_cdb_len                = 16;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_DATA_NONE;
         cdb->ata_pass_thru.command     = ATA_SMART_CMD;
         cdb->ata_pass_thru.features    = ATA_SMART_ENABLE;
         cdb->ata_pass_thru.lba_mid     = ATA_SMART_LBA_MID_SIG;
         cdb->ata_pass_thru.lba_high    = ATA_SMART_LBA_HI_SIG;
         break;
    case DISABLE:
         cpt.cam_flags                  = CAM_DIR_NONE;
         cpt.cam_cdb_len                = 16;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_DATA_NONE;
         cdb->ata_pass_thru.command     = ATA_SMART_CMD;
         cdb->ata_pass_thru.features    = ATA_SMART_DISABLE;
         cdb->ata_pass_thru.lba_mid     = ATA_SMART_LBA_MID_SIG;
         cdb->ata_pass_thru.lba_high    = ATA_SMART_LBA_HI_SIG;
         break;
    case AUTO_OFFLINE:
    // NOTE: According to ATAPI 4 and UP, this command is obsolete 
         cpt.cam_flags                  = CAM_DIR_NONE;
         cpt.cam_cdb_len                = 16;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_DATA_NONE;
         cdb->ata_pass_thru.command     = ATA_SMART_CMD;
         cdb->ata_pass_thru.features    = ATA_SMART_AUTO_OFFLINE;
         cdb->ata_pass_thru.lba_low     = select;
         cdb->ata_pass_thru.lba_mid     = ATA_SMART_LBA_MID_SIG;
         cdb->ata_pass_thru.lba_high    = ATA_SMART_LBA_HI_SIG;
         break;
    case AUTOSAVE:
         cpt.cam_flags                  = CAM_DIR_NONE;
         cpt.cam_cdb_len                = 16;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_DATA_NONE;
         cdb->ata_pass_thru.command     = ATA_SMART_CMD;
         cdb->ata_pass_thru.features    = ATA_SMART_AUTOSAVE;
         cdb->ata_pass_thru.sector_count= select;
         cdb->ata_pass_thru.lba_mid     = ATA_SMART_LBA_MID_SIG;
         cdb->ata_pass_thru.lba_high    = ATA_SMART_LBA_HI_SIG;
         break;
    case IMMEDIATE_OFFLINE:
    // NOTE: According to ATAPI 4 and UP, this command is obsolete 
         cpt.cam_flags                  = CAM_DIR_NONE;
         cpt.cam_cdb_len                = 16;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_DATA_NONE;
         cdb->ata_pass_thru.command     = ATA_SMART_CMD;
         cdb->ata_pass_thru.features    = ATA_SMART_IMMEDIATE_OFFLINE;
         cdb->ata_pass_thru.lba_low     = select;
         cdb->ata_pass_thru.lba_mid     = ATA_SMART_LBA_MID_SIG;
         cdb->ata_pass_thru.lba_high    = ATA_SMART_LBA_HI_SIG;
         break;
    case STATUS_CHECK:
    // same command, no HDIO in NetBSD 
    case STATUS:
         cpt.cam_flags                  = CAM_DIR_NONE;
         cpt.cam_cdb_len                = 16;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_DATA_NONE;
         cdb->ata_pass_thru.flags       = ATA_FLG_CK_COND;
         cdb->ata_pass_thru.command     = ATA_SMART_CMD;
         cdb->ata_pass_thru.features    = ATA_SMART_STATUS;
         cdb->ata_pass_thru.lba_mid     = ATA_SMART_LBA_MID_SIG;
         cdb->ata_pass_thru.lba_high    = ATA_SMART_LBA_HI_SIG;
         break;
    case CHECK_POWER_MODE:
         cpt.cam_flags                  = CAM_DIR_NONE;
         cpt.cam_cdb_len                = 16;
         cpt.cam_sense_len              = sizeof(sense);
         cpt.cam_sense_ptr              = (uint32_t)&sense;
         cdb->ata_pass_thru.opcode      = SC_ATA_PT16;
         cdb->ata_pass_thru.protocol    = ATA_PROTO_DATA_NONE;
         cdb->ata_pass_thru.flags       = ATA_FLG_CK_COND;
         cdb->ata_pass_thru.command     = ATA_CHECK_POWER_MODE;
         break;
    default:
         lib_printf("Unrecognized command %d in ata_command_interface()\n", command);
         errno=ENOSYS;
         return(-1);
   }
// execute now
  if((status=ata_pass_thru(fd,&cpt))==EOK)
   {
    rc=status==EOK?0:-1;
    if(cpt.cam_status!=CAM_REQ_CMP)
     {
      ata_interpret_sense(&cpt,&sense,&status,0);
      if(command==STATUS||command==STATUS_CHECK)
        rc=((sense.desc.lba_high<<8)|sense.desc.lba_mid)==ATA_SMART_SIG?0:1;
     }
   }
  if(command==CHECK_POWER_MODE)
    data[0]=cdb->ata_pass_thru.sector_count;
// finish
  return(rc);
}
//----------------------------------------------------------------------------------------------
// Interface to SCSI devices.  See os_linux.c
int do_scsi_cmnd_io(int fd,struct scsi_cmnd_io * iop,int report)
{
  ARGUSED(fd);
  ARGUSED(iop);
  ARGUSED(report);
  return -ENOSYS;
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
static int ata_sense_data(void *sdata,int *error,int *key,int *asc,int *ascq)
{
SCSI_SENSE	*sf;
SCSI_SENSE_DESCRIPTOR	*sd;
  sf=(SCSI_SENSE *)sdata;
  sd=(SCSI_SENSE_DESCRIPTOR *)sdata;
  *error=sf->error;
  if(*error & SENSE_DATA_FMT_DESCRIPTOR)
   {
    *key=sd->sense & SK_MSK;
    *asc=sd->asc;
    *ascq=sd->ascq;
   }
  else
   {
    *key=sf->sense & SK_MSK;
    *asc=sf->asc;
    *ascq=sf->ascq;
   }
  return(CAM_SUCCESS);
}
//----------------------------------------------------------------------------------------------
static int ata_interpret_sense(struct cam_pass_thru *cpt,void *sense,int *status,int rcount)
{
int retry;
int key;
int asc;
int ascq;
int error;
  *status=EIO;
  retry=CAM_TRUE;
  if(cpt->cam_status&CAM_AUTOSNS_VALID)
   {
    ata_sense_data(sense,&error,&key,&asc,&ascq);
    switch(key)
     {
      case SK_NO_SENSE:					// No sense data (no error)
           retry=CAM_FALSE;
           *status=EOK;
           break;
      case SK_RECOVERED:					// Recovered error
	   switch(asc)
            {
             case ASC_ATA_PASS_THRU:
                  switch(ascq)
                   {
                    case ASCQ_ATA_PASS_THRU_INFO_AVAIL:
                         break;
                    default:
                         break;
                   }
                  break;
             default:
                  break;
            }
           retry=CAM_FALSE;
           *status=EOK;
           break;
      case SK_NOT_RDY:					// Device not ready
           *status=EAGAIN;
           switch(asc)
            {
             case ASC_NOT_READY:
                  switch(ascq)
                   {
                    case ASCQ_BECOMING_READY:
                    case ASCQ_CAUSE_NOT_REPORTABLE:
                    default:
                    retry=CAM_FALSE;
                    break;
                   }
                  break;
             case ASC_MEDIA_NOT_PRESENT:
                  *status=ENXIO;
                  retry=CAM_FALSE;
                  break;
            }
           break;
      case SK_MEDIUM:						// Medium error
      case SK_HARDWARE:					// Hardware error
           retry=CAM_FALSE;
           *status=EIO;
           break;
      case SK_ILLEGAL:					// Illegal Request (bad command)
           retry=CAM_FALSE;
           *status=EINVAL;
           break;
      case SK_UNIT_ATN:					// Unit Attention
           switch(asc)
            {
             case ASC_MEDIUM_CHANGED:
                  *status=ESTALE;
                  retry=CAM_FALSE;
                  break;
             case ASC_BUS_RESET:
                  break;
            }
           break;
      case SK_DATA_PROT:					// Data Protect
           retry=CAM_FALSE;
           *status=EROFS;
           break;
      case SK_VENDOR:						// Vendor Specific
      case SK_CPY_ABORT:					// Copy Aborted
           retry=CAM_FALSE;
           *status=EIO;
           break;
      case SK_CMD_ABORT:					// Aborted Command
           retry=CAM_FALSE;
           *status=ECANCELED;
           break;
      case SK_EQUAL:						// Equal
      case SK_VOL_OFL:					// Volume Overflow
      case SK_MISCMP:						// Miscompare
      case SK_RESERVED:					// Reserved
           break; 
     }
    if(*status==EOK)
     {
      switch(cpt->cam_status&CAM_STATUS_MASK) 
       {
        case CAM_REQ_CMP_ERR:			// CCB request completed with an err
             retry=CAM_FALSE;
             *status=EIO;
             break;
        case CAM_BUSY:					// CAM subsystem is busy
             *status=EAGAIN;
             break;
        case CAM_REQ_INVALID:			// CCB request is invalid
        case CAM_PATH_INVALID:			// Path ID supplied is invalid
        case CAM_DEV_NOT_THERE:			// SCSI device not installed/there
        case CAM_SEL_TIMEOUT:			// Target selection timeout
        case CAM_LUN_INVALID:			// LUN supplied is invalid
        case CAM_TID_INVALID:			// Target ID supplied is invalid
             retry=CAM_FALSE;
             *status=ENXIO;
             break;
        case CAM_CMD_TIMEOUT:			// Command timeout
             *status=rcount?EAGAIN:EIO;
             break;
        case CAM_MSG_REJECT_REC:		// Message reject received
        case CAM_SCSI_BUS_RESET:		// SCSI bus reset sent/received
        case CAM_UNCOR_PARITY:			// Uncorrectable parity err occurred
        case CAM_AUTOSENSE_FAIL:		// Autosense: Request sense cmd fail
        case CAM_NO_HBA:				// No HBA detected Error
        case CAM_DATA_RUN_ERR:			// Data overrun/underrun error
             retry=CAM_FALSE;
             *status=EIO;
             break;
        case CAM_UNEXP_BUSFREE:			// Unexpected BUS free
        case CAM_SEQUENCE_FAIL:			// Target bus phase sequence failure
             *status=EIO;
             break;
        case CAM_PROVIDE_FAIL:			// Unable to provide requ. capability
             retry=CAM_FALSE;
             *status=ENOTTY;
             break;
        case CAM_CCB_LEN_ERR:			// CCB length supplied is inadequate
        case CAM_BDR_SENT:				// A SCSI BDR msg was sent to target
        case CAM_REQ_TERMIO:			// CCB request terminated by the host
        case CAM_FUNC_NOTAVAIL:			// The requ. func is not available
        case CAM_NO_NEXUS:				// Nexus is not established
        case CAM_IID_INVALID:			// The initiator ID is invalid
        case CAM_CDB_RECVD:				// The SCSI CDB has been received
             retry=CAM_FALSE;
             *status=EIO;
             break;
        case CAM_SCSI_BUSY:				// SCSI bus busy
             *status=EAGAIN;
             break;
       }
     }
   }
  return(retry);
}
//----------------------------------------------------------------------------------------------
static int ata_pass_thru(int fd,struct cam_pass_thru *pcpt)
{
int    icnt;
int    status;
iov_t  iov[3];
struct cam_pass_thru	cpt;
  cpt=*pcpt;
  icnt=1;
  SETIOV(&iov[0],&cpt,sizeof(cpt));
  cpt.cam_timeout=cpt.cam_timeout?cpt.cam_timeout:CAM_TIME_DEFAULT;
  if(cpt.cam_sense_len)
   {
    SETIOV(&iov[1],(void *)cpt.cam_sense_ptr,cpt.cam_sense_len);
    cpt.cam_sense_ptr=sizeof(cpt);
    icnt++;
   }
  if(cpt.cam_dxfer_len)
   {
    SETIOV(&iov[2],(void *)cpt.cam_data_ptr,cpt.cam_dxfer_len);
    cpt.cam_data_ptr=(paddr_t)sizeof(cpt)+cpt.cam_sense_len;
    icnt++;
   }
  if((status=devctlv(fd,DCMD_CAM_PASS_THRU,icnt,icnt,iov,iov,NULL)))
    lib_printf("ata_pass_thru devctl:  %s\n",strerror(status));
  pcpt->cam_status=cpt.cam_status;
  pcpt->cam_scsi_status=cpt.cam_scsi_status;
  return(status);
}
//----------------------------------------------------------------------------------------------
