/*
 * scsiata.cpp
 *
 * Home page of code is: http://smartmontools.sourceforge.net
 *
 * Copyright (C) 2006 Douglas Gilbert <dougg@torque.net>
 *
 * 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, or (at your option)
 * any later version.
 *
 * You should have received a copy of the GNU General Public License
 * (for example COPYING); if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * The code in this file is based on the SCSI to ATA Translation (SAT)
 * draft found at http://www.t10.org . The original draft used for this
 * code is sat-r08.pdf which is not too far away from becoming a
 * standard. The SAT commands of interest to smartmontools are the
 * ATA PASS THROUGH SCSI (16) and ATA PASS THROUGH SCSI (12) defined in
 * section 12 of that document.
 *
 * With more transports "hiding" SATA disks (and other S-ATAPI devices)
 * behind a SCSI command set, accessing special features like SMART
 * information becomes a challenge. The SAT standard offers ATA PASS
 * THROUGH commands for special usages. Note that the SAT layer may
 * be inside a generic OS layer (e.g. libata in linux), in a host
 * adapter (HA or HBA) firmware, or somewhere on the interconnect
 * between the host computer and the SATA devices (e.g. a RAID made
 * of SATA disks and the RAID talks "SCSI" to the host computer).
 * Note that in the latter case, this code does not solve the
 * addressing issue (i.e. which SATA disk to address behind the logical
 * SCSI (RAID) interface).
 * 
 */

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

#include "config.h"
#include "int64.h"
#include "extern.h"
#include "scsicmds.h"
#include "scsiata.h"
#include "utility.h"

const char *scsiata_c_cvsid="$Id: scsiata.cpp,v 1.7 2006/08/09 20:40:19 chrfranke Exp $"
CONFIG_H_CVSID EXTERN_H_CVSID INT64_H_CVSID SCSICMDS_H_CVSID SCSIATA_H_CVSID UTILITY_H_CVSID;

/* for passing global control variables */
extern smartmonctrl *con;

#define DEF_SAT_ATA_PASSTHRU_SIZE 16


// cdb[0]: ATA PASS THROUGH (16) SCSI command opcode byte (0x85)
// cdb[1]: multiple_count, protocol + extend
// cdb[2]: offline, ck_cond, t_dir, byte_block + t_length
// cdb[3]: features (15:8)
// cdb[4]: features (7:0)
// cdb[5]: sector_count (15:8)
// cdb[6]: sector_count (7:0)
// cdb[7]: lba_low (15:8)
// cdb[8]: lba_low (7:0)
// cdb[9]: lba_mid (15:8)
// cdb[10]: lba_mid (7:0)
// cdb[11]: lba_high (15:8)
// cdb[12]: lba_high (7:0)
// cdb[13]: device
// cdb[14]: (ata) command
// cdb[15]: control (SCSI, leave as zero)
//
// 24 bit lba (from MSB): cdb[12] cdb[10] cdb[8]
// 48 bit lba (from MSB): cdb[11] cdb[9] cdb[7] cdb[12] cdb[10] cdb[8]
//
//
// cdb[0]: ATA PASS THROUGH (12) SCSI command opcode byte (0xa1)
// cdb[1]: multiple_count, protocol + extend
// cdb[2]: offline, ck_cond, t_dir, byte_block + t_length
// cdb[3]: features (7:0)
// cdb[4]: sector_count (7:0)
// cdb[5]: lba_low (7:0)
// cdb[6]: lba_mid (7:0)
// cdb[7]: lba_high (7:0)
// cdb[8]: device
// cdb[9]: (ata) command
// cdb[10]: reserved
// cdb[11]: control (SCSI, leave as zero)
//
//
// ATA status return descriptor (component of descriptor sense data)
// des[0]: descriptor code (0x9)
// des[1]: additional descriptor length (0xc)
// des[2]: extend (bit 0)
// des[3]: error
// des[4]: sector_count (15:8)
// des[5]: sector_count (7:0)
// des[6]: lba_low (15:8)
// des[7]: lba_low (7:0)
// des[8]: lba_mid (15:8)
// des[9]: lba_mid (7:0)
// des[10]: lba_high (15:8)
// des[11]: lba_high (7:0)
// des[12]: device
// des[13]: status



// PURPOSE
//   This interface routine takes ATA SMART commands and packages
//   them in the SAT-defined ATA PASS THROUGH SCSI commands. There are
//   two available SCSI commands: a 12 byte and 16 byte variant; the
//   one used is chosen via con->satpassthrulen .
// DETAILED DESCRIPTION OF ARGUMENTS
//   device: is the file descriptor provided by (a SCSI dvice type) open()
//   command: defines the different ATA operations.
//   select: additional input data if needed (which log, which type of
//           self-test).
//   data:   location to write output data, if needed (512 bytes).
//     Note: not all commands use all arguments.
// RETURN VALUES
//  -1 if the command failed
//   0 if the command succeeded,
//   STATUS_CHECK routine: 
//  -1 if the command failed
//   0 if the command succeeded and disk SMART status is "OK"
//   1 if the command succeeded and disk SMART status is "FAILING"

int sat_command_interface(int device, smart_command_set command, int select,
                          char *data)
{
    struct scsi_cmnd_io io_hdr;
    struct scsi_sense_disect sinfo;
    struct sg_scsi_sense_hdr ssh;
    unsigned char cdb[SAT_ATA_PASSTHROUGH_16LEN];
    unsigned char sense[32];
    const unsigned char * ucp;
    int status, len;
    int copydata = 0;
    int outlen = 0;
    int extend = 0;
    int chk_cond = 0;   /* set to 1 to read register(s) back */
    int protocol = 3;   /* non-data */
    int t_dir = 1;      /* 0 -> to device, 1 -> from device */
    int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */
    int t_length = 0;   /* 0 -> no data transferred */
    int feature = 0;
    int ata_command = 0;
    int sector_count = 0;
    int lba_low = 0;
    int lba_mid = 0;
    int lba_high = 0;
    int passthru_size = DEF_SAT_ATA_PASSTHRU_SIZE;

    memset(cdb, 0, sizeof(cdb));
    memset(sense, 0, sizeof(sense));

    ata_command = ATA_SMART_CMD;
    switch (command) {
    case CHECK_POWER_MODE:
        ata_command = ATA_CHECK_POWER_MODE;
        chk_cond = 1;
        copydata = 1;
        break;
    case READ_VALUES:           /* READ DATA */
        feature = ATA_SMART_READ_VALUES;
        sector_count = 1;     /* one (512 byte) block */
        protocol = 4;   /* PIO data-in */
        t_length = 2;   /* sector count holds count */
        copydata = 512;
        break;
    case READ_THRESHOLDS:       /* obsolete */
        feature = ATA_SMART_READ_THRESHOLDS;
        sector_count = 1;     /* one (512 byte) block */
        lba_low = 1;
        protocol = 4;   /* PIO data-in */
        t_length = 2;   /* sector count holds count */
        copydata=512;
        break;
    case READ_LOG:
        feature = ATA_SMART_READ_LOG_SECTOR;
        sector_count = 1;     /* one (512 byte) block */
        lba_low = select;
        protocol = 4;   /* PIO data-in */
        t_length = 2;   /* sector count holds count */
        copydata = 512;
        break;
    case WRITE_LOG:
        feature = ATA_SMART_WRITE_LOG_SECTOR;
        sector_count = 1;     /* one (512 byte) block */
        lba_low = select;
        protocol = 5;   /* PIO data-out */
        t_length = 2;   /* sector count holds count */
        t_dir = 0;      /* to device */
        outlen = 512;
        break;
    case IDENTIFY:
        ata_command = ATA_IDENTIFY_DEVICE;
        sector_count = 1;     /* one (512 byte) block */
        protocol = 4;   /* PIO data-in */
        t_length = 2;   /* sector count holds count */
        copydata = 512;
        break;
    case PIDENTIFY:
        ata_command = ATA_IDENTIFY_PACKET_DEVICE;
        sector_count = 1;     /* one (512 byte) block */
        protocol = 4;   /* PIO data-in */
        t_length = 2;   /* sector count (7:0) holds count */
        copydata = 512;
        break;
    case ENABLE:
        feature = ATA_SMART_ENABLE;
        lba_low = 1;
        break;
    case DISABLE:
        feature = ATA_SMART_DISABLE;
        lba_low = 1;
        break;
    case STATUS:
        // this command only says if SMART is working.  It could be
        // replaced with STATUS_CHECK below.
        feature = ATA_SMART_STATUS;
        chk_cond = 1;
        break;
    case AUTO_OFFLINE:
        feature = ATA_SMART_AUTO_OFFLINE;
        sector_count = select;   // YET NOTE - THIS IS A NON-DATA COMMAND!!
        break;
    case AUTOSAVE:
        feature = ATA_SMART_AUTOSAVE;
        sector_count = select;   // YET NOTE - THIS IS A NON-DATA COMMAND!!
        break;
    case IMMEDIATE_OFFLINE:
        feature = ATA_SMART_IMMEDIATE_OFFLINE;
        lba_low = select;
        break;
    case STATUS_CHECK:
        // This command uses HDIO_DRIVE_TASK and has different syntax than
        // the other commands.
        feature = ATA_SMART_STATUS;      /* SMART RETURN STATUS */
        chk_cond = 1;
        break;
    default:
        pout("Unrecognized command %d in sat_command_interface()\n"
             "Please contact " PACKAGE_BUGREPORT "\n", command);
        errno=ENOSYS;
        return -1;
    }
    if (ATA_SMART_CMD == ata_command) {
        lba_mid = 0x4f;
        lba_high = 0xc2;
    }

    if ((SAT_ATA_PASSTHROUGH_12LEN == con->satpassthrulen) ||
        (SAT_ATA_PASSTHROUGH_16LEN == con->satpassthrulen))
        passthru_size = con->satpassthrulen;
    cdb[0] = (SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ?
             SAT_ATA_PASSTHROUGH_12 : SAT_ATA_PASSTHROUGH_16;

    cdb[1] = (protocol << 1) | extend;
    cdb[2] = (chk_cond << 5) | (t_dir << 3) |
             (byte_block << 2) | t_length;
    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 3 : 4] = feature;
    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 4 : 6] = sector_count;
    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 5 : 8] = lba_low;
    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 6 : 10] = lba_mid;
    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 7 : 12] = lba_high;
    cdb[(SAT_ATA_PASSTHROUGH_12LEN == passthru_size) ? 9 : 14] = ata_command;

    memset(&io_hdr, 0, sizeof(io_hdr));
    if (0 == t_length) {
        io_hdr.dxfer_dir = DXFER_NONE;
        io_hdr.dxfer_len = 0;
    } else if (t_dir) {         /* from device */
        io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
        io_hdr.dxfer_len = copydata;
        io_hdr.dxferp = (unsigned char *)data;
        memset(data, 0, copydata); /* prefill with zeroes */
    } else {                    /* to device */
        io_hdr.dxfer_dir = DXFER_TO_DEVICE;
        io_hdr.dxfer_len = outlen;
        io_hdr.dxferp = (unsigned char *)data;
    }
    io_hdr.cmnd = cdb;
    io_hdr.cmnd_len = passthru_size;
    io_hdr.sensep = sense;
    io_hdr.max_sense_len = sizeof(sense);
    io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;

    status = do_scsi_cmnd_io(device, &io_hdr, con->reportscsiioctl);
    if (0 != status) {
        if (con->reportscsiioctl > 0)
            pout("sat_command_interface: do_scsi_cmnd_io() failed, "
                 "status=%d\n", status);
        return -1;
    }
    if (chk_cond) {     /* expecting SAT specific sense data */
        ucp = NULL;
        if (sg_scsi_normalize_sense(io_hdr.sensep, io_hdr.resp_sense_len,
                                    &ssh)) {
            /* look for SAT extended ATA status return descriptor (9) */
            ucp = sg_scsi_sense_desc_find(io_hdr.sensep,
                                          io_hdr.resp_sense_len, 9);
            if (ucp) {
                len = ucp[1] + 2;
                if (len < 12)
                    len = 12;
                else if (len > 14)
                    len = 14;
                if (con->reportscsiioctl > 1) {
                    pout("Values from ATA status return descriptor are:\n");
                    dStrHex((const char *)ucp, len, 1);
                }
                if (ATA_CHECK_POWER_MODE == cdb[14])
                    data[0] = ucp[5];      /* sector count (0:7) */
                else if (STATUS_CHECK == command) {
                    if ((ucp[9] == 0x4f) && (ucp[11] == 0xc2))
                        return 0;    /* GOOD smart status */
                    if ((ucp[9] == 0xf4) && (ucp[11] == 0x2c))
                        return 1;    // smart predicting failure, "bad" status
                    // We haven't gotten output that makes sense so
                    // print out some debugging info
                    syserror("Error SMART Status command failed");
                    pout("Please get assistance from " PACKAGE_HOMEPAGE "\n");
                    pout("Values from ATA status return descriptor are:\n");
                    dStrHex((const char *)ucp, len, 1);
                    return -1;
                }
            }
        }
        if (ucp == NULL) {
            chk_cond = 0;       /* not the type of sense data expected */
            if (t_dir && (t_length > 0))
                data[0] = 0;
        }
    }
    if (0 == chk_cond) {
        ucp = NULL;
        if (sg_scsi_normalize_sense(io_hdr.sensep, io_hdr.resp_sense_len,
                                    &ssh)) {
            if ((ssh.response_code >= 0x72) &&
                ((SCSI_SK_NO_SENSE == ssh.sense_key) ||
                 (SCSI_SK_RECOVERED_ERR == ssh.sense_key)) &&
                (0 == ssh.asc) &&
                (SCSI_ASCQ_ATA_PASS_THROUGH == ssh.ascq)) {
                /* look for SAT extended ATA status return descriptor (9) */
                ucp = sg_scsi_sense_desc_find(io_hdr.sensep,
                                              io_hdr.resp_sense_len, 9);
                if (ucp) {
                    if (con->reportscsiioctl > 0) {
                        pout("Values from ATA status return descriptor are:\n");
                        len = ucp[1] + 2;
                        if (len < 12)
                            len = 12;
                        else if (len > 14)
                            len = 14;
                        dStrHex((const char *)ucp, len, 1);
                    }
                    return -1;
                }
            }
            scsi_do_sense_disect(&io_hdr, &sinfo);
            status = scsiSimpleSenseFilter(&sinfo);
            if (0 != status) {
                if (con->reportscsiioctl > 0)
                    pout("sat_command_interface: scsi error: %s\n",
                         scsiErrString(status));
                return -1;
            }
        }
    }
    return 0;
}

/* Attempt an IDENTIFY DEVICE ATA command via SATL when packet_interface
   is 0 otherwise attempt IDENTIFY PACKET DEVICE. If successful
   return 1, else 0 */
int has_sat_pass_through(int device, int packet_interface)
{
    char data[512];
    smart_command_set command;

    command = packet_interface ? PIDENTIFY : IDENTIFY;
    if (0 == sat_command_interface(device, command, 0, data))
        return 1;
    else
        return 0;
}

/* Next two functions are borrowed from sg_lib.c in the sg3_utils
   package. Same copyrght owner, same license as this file. */
int sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len,
                            struct sg_scsi_sense_hdr * sshp)
{
    if (sshp)
        memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr));
    if ((NULL == sensep) || (0 == sb_len) || (0x70 != (0x70 & sensep[0])))
        return 0;
    if (sshp) {
        sshp->response_code = (0x7f & sensep[0]);
        if (sshp->response_code >= 0x72) {  /* descriptor format */
            if (sb_len > 1)
                sshp->sense_key = (0xf & sensep[1]);
            if (sb_len > 2)
                sshp->asc = sensep[2];
            if (sb_len > 3)
                sshp->ascq = sensep[3];
            if (sb_len > 7)
                sshp->additional_length = sensep[7];
        } else {                              /* fixed format */
            if (sb_len > 2)
                sshp->sense_key = (0xf & sensep[2]);
            if (sb_len > 7) {
                sb_len = (sb_len < (sensep[7] + 8)) ? sb_len :
                                                      (sensep[7] + 8);
                if (sb_len > 12)
                    sshp->asc = sensep[12];
                if (sb_len > 13)
                    sshp->ascq = sensep[13];
            }
        }
    }
    return 1;
}


const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep,
                                              int sense_len, int desc_type)
{
    int add_sen_len, add_len, desc_len, k;
    const unsigned char * descp;

    if ((sense_len < 8) || (0 == (add_sen_len = sensep[7])))
        return NULL;
    if ((sensep[0] < 0x72) || (sensep[0] > 0x73))
        return NULL;
    add_sen_len = (add_sen_len < (sense_len - 8)) ?
                         add_sen_len : (sense_len - 8);
    descp = &sensep[8];
    for (desc_len = 0, k = 0; k < add_sen_len; k += desc_len) {
        descp += desc_len;
        add_len = (k < (add_sen_len - 1)) ? descp[1]: -1;
        desc_len = add_len + 2;
        if (descp[0] == desc_type)
            return descp;
        if (add_len < 0) /* short descriptor ?? */
            break;
    }
    return NULL;
}


syntax highlighted by Code2HTML, v. 0.9.1