* dev_areca.cpp
*
* Home page of code is: https://www.smartmontools.org
*
* Copyright (C) 2012 Hank Wu <hank@areca.com.tw>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "config.h"
#include "dev_interface.h"
#include "dev_areca.h"
const char * dev_areca_cpp_cvsid = "$Id: dev_areca.cpp 5337 2022-02-27 07:53:55Z dpgilbert $"
DEV_ARECA_H_CVSID;
#include "atacmds.h"
#include "scsicmds.h"
#include <errno.h>
#if 0
static void dumpdata(unsigned char *block, int len)
{
int ln = (len / 16) + 1;
unsigned char c;
int pos = 0;
printf(" Address = %p, Length = (0x%x)%d\n", block, len, len);
printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F ASCII \n");
printf("=====================================================================\n");
for ( int l = 0; l < ln && len; l++ )
{
printf("%02X | ", l);
for ( pos = 0; pos < 16 && len; pos++, len-- )
{
c = block[l*16+pos];
printf("%02X ", c);
}
if ( pos < 16 )
{
for ( int loop = pos; loop < 16; loop++ )
{
printf(" ");
}
}
for ( int loop = 0; loop < pos; loop++ )
{
c = block[l*16+loop];
if ( c >= 0x20 && c <= 0x7F )
{
printf("%c", c);
}
else
{
printf(".");
}
}
printf("\n");
}
printf("=====================================================================\n");
}
#endif
generic_areca_device::generic_areca_device(smart_interface * intf, const char * dev_name, int disknum, int encnum)
: smart_device(intf, dev_name, "areca", "areca"),
m_disknum(disknum),
m_encnum(encnum)
{
set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum);
}
generic_areca_device::~generic_areca_device()
{
}
int generic_areca_device::arcmsr_command_handler(unsigned long arcmsr_cmd, unsigned char *data, int data_len)
{
if (arcmsr_cmd >= ARCMSR_CMD_TOTAL)
return -1;
static const unsigned int cmds[ARCMSR_CMD_TOTAL] =
{
ARCMSR_IOCTL_READ_RQBUFFER,
ARCMSR_IOCTL_WRITE_WQBUFFER,
ARCMSR_IOCTL_CLEAR_RQBUFFER,
ARCMSR_IOCTL_CLEAR_WQBUFFER,
ARCMSR_IOCTL_RETURN_CODE_3F
};
int ioctlreturn = 0;
sSRB_BUFFER sBuf;
struct scsi_cmnd_io iop = {};
int dir = DXFER_TO_DEVICE;
uint8_t cdb[10]={};
uint8_t sense[32]={};
unsigned char *areca_return_packet;
int total = 0;
int expected = -1;
unsigned char return_buff[2048]={0};
unsigned char *ptr = &return_buff[0];
memset((unsigned char *)&sBuf, 0, sizeof(sBuf));
sBuf.srbioctl.HeaderLength = sizeof(sARCMSR_IO_HDR);
memcpy(sBuf.srbioctl.Signature, ARECA_SIG_STR, strlen(ARECA_SIG_STR));
sBuf.srbioctl.Timeout = 10000;
sBuf.srbioctl.ControlCode = cmds[arcmsr_cmd];
switch ( arcmsr_cmd )
{
case ARCMSR_WRITE_WQBUFFER:
if ( data && data_len )
{
sBuf.srbioctl.Length = data_len;
memcpy((unsigned char *)sBuf.ioctldatabuffer, (unsigned char *)data, data_len);
}
case ARCMSR_CLEAR_RQBUFFER:
case ARCMSR_CLEAR_WQBUFFER:
cdb[0] = 0x3B;
break;
case ARCMSR_READ_RQBUFFER:
case ARCMSR_RETURN_CODE_3F:
cdb[0] = 0x3C;
dir = DXFER_FROM_DEVICE;
break;
default:
return -1;
}
cdb[1] = 0x01;
cdb[2] = 0xf0;
cdb[5] = cmds[arcmsr_cmd] >> 24;
cdb[6] = cmds[arcmsr_cmd] >> 16;
cdb[7] = cmds[arcmsr_cmd] >> 8;
cdb[8] = cmds[arcmsr_cmd] & 0x0F;
iop.dxfer_dir = dir;
iop.dxfer_len = sizeof(sBuf);
iop.dxferp = (unsigned char *)&sBuf;
iop.cmnd = cdb;
iop.cmnd_len = sizeof(cdb);
iop.sensep = sense;
iop.max_sense_len = sizeof(sense);
iop.timeout = SCSI_TIMEOUT_DEFAULT;
while ( 1 )
{
ioctlreturn = arcmsr_do_scsi_io(&iop);
if(ioctlreturn || iop.scsi_status)
{
break;
}
if ( arcmsr_cmd != ARCMSR_READ_RQBUFFER )
{
return data_len;
}
if ( sBuf.srbioctl.Length )
{
memcpy(ptr, &sBuf.ioctldatabuffer[0], sBuf.srbioctl.Length);
ptr += sBuf.srbioctl.Length;
total += sBuf.srbioctl.Length;
if ( expected < 0 && total >= 5 )
{
areca_return_packet = (unsigned char *)&return_buff[0];
if ( areca_return_packet[0] == 0x5E &&
areca_return_packet[1] == 0x01 &&
areca_return_packet[2] == 0x61 )
{
expected = areca_return_packet[4] * 256 + areca_return_packet[3] + 6;
}
}
if ( total >= 7 && total >= expected )
{
break;
}
}
}
if ( arcmsr_cmd == ARCMSR_RETURN_CODE_3F )
{
return -4;
}
if ( ioctlreturn )
{
pout("do_scsi_cmnd_io with write buffer failed code = %x\n", ioctlreturn);
return -2;
}
if ( iop.scsi_status )
{
pout("io_hdr.scsi_status with write buffer failed code = %x\n", iop.scsi_status);
return -3;
}
if ( data )
{
memcpy(data, return_buff, total);
}
return total;
}
bool generic_areca_device::arcmsr_probe()
{
if(!is_open())
{
open();
}
if(arcmsr_command_handler(ARCMSR_RETURN_CODE_3F, NULL, 0) != 0)
{
return false;
}
return true;
}
int generic_areca_device::arcmsr_ui_handler(unsigned char *areca_packet, int areca_packet_len, unsigned char *result)
{
int expected = 0;
unsigned char return_buff[2048];
unsigned char cs = 0;
int cs_pos = 0;
cs_pos = areca_packet_len - 1;
for(int i = 3; i < cs_pos; i++)
{
areca_packet[cs_pos] += areca_packet[i];
}
if(!arcmsr_lock())
{
return -1;
}
expected = arcmsr_command_handler(ARCMSR_CLEAR_RQBUFFER, NULL, 0);
if (expected==-3) {
return set_err(EIO);
}
arcmsr_command_handler(ARCMSR_CLEAR_WQBUFFER, NULL, 0);
expected = arcmsr_command_handler(ARCMSR_WRITE_WQBUFFER, areca_packet, areca_packet_len);
if ( expected > 0 )
{
expected = arcmsr_command_handler(ARCMSR_READ_RQBUFFER, return_buff, sizeof(return_buff));
}
if ( expected < 3 + 1 )
{
return -1;
}
if(!arcmsr_unlock())
{
return -1;
}
cs = 0;
for ( int loop = 3; loop < expected - 1; loop++ )
{
cs += return_buff[loop];
}
if ( return_buff[expected - 1] != cs )
{
return -1;
}
memcpy(result, return_buff, expected);
return expected;
}
int generic_areca_device::arcmsr_get_controller_type()
{
int expected = 0;
unsigned char return_buff[2048];
unsigned char areca_packet[] = {0x5E, 0x01, 0x61, 0x01, 0x00, 0x23, 0x00};
memset(return_buff, 0, sizeof(return_buff));
expected = arcmsr_ui_handler(areca_packet, sizeof(areca_packet), return_buff);
if ( expected < 0 )
{
return -1;
}
return return_buff[0xc2];
}
int generic_areca_device::arcmsr_get_dev_type()
{
int expected = 0;
unsigned char return_buff[2048];
int ctlr_type = -1;
int encnum = get_encnum();
int disknum = get_disknum();
unsigned char areca_packet[] = {0x5E, 0x01, 0x61, 0x03, 0x00, 0x22,
(unsigned char)(disknum - 1), (unsigned char)(encnum - 1), 0x00};
memset(return_buff, 0, sizeof(return_buff));
expected = arcmsr_ui_handler(areca_packet, sizeof(areca_packet), return_buff);
if ( expected < 0 )
{
return -1;
}
ctlr_type = arcmsr_get_controller_type();
if( ctlr_type < 0 )
{
return ctlr_type;
}
if( ctlr_type == 0x02 ||
(ctlr_type == 0x03 && return_buff[0x52] & 0x01 ) )
{
return 1;
}
return 0;
}
bool generic_areca_device::arcmsr_ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
{
typedef struct _ATA_INPUT_REGISTERS
{
unsigned char features;
unsigned char sector_count;
unsigned char sector_number;
unsigned char cylinder_low;
unsigned char cylinder_high;
unsigned char device_head;
unsigned char command;
unsigned char reserved[8];
unsigned char data[512];
} sATA_INPUT_REGISTERS;
typedef struct _ATA_OUTPUT_REGISTERS
{
unsigned char error;
unsigned char status;
unsigned char sector_count;
unsigned char sector_number;
unsigned char cylinder_low;
unsigned char cylinder_high;
} sATA_OUTPUT_REGISTERS;
unsigned char areca_packet[640];
int areca_packet_len = sizeof(areca_packet);
unsigned char return_buff[2048];
int expected = 0;
sATA_INPUT_REGISTERS *ata_cmd;
#if 0
memset(sInq, 0, sizeof(sInq));
scsiStdInquiry(fd, (unsigned char *)sInq, (int)sizeof(sInq));
dumpdata((unsigned char *)sInq, sizeof(sInq));
#endif
memset(areca_packet, 0, areca_packet_len);
areca_packet[0] = 0x5E;
areca_packet[1] = 0x01;
areca_packet[2] = 0x61;
areca_packet[3] = (unsigned char)((areca_packet_len - 6) & 0xff);
areca_packet[4] = (unsigned char)(((areca_packet_len - 6) >> 8) & 0xff);
areca_packet[5] = 0x1c;
memcpy(&areca_packet[7], "SmrT", 4);
ata_cmd = (sATA_INPUT_REGISTERS *)&areca_packet[12];
{
const ata_in_regs & r = in.in_regs;
ata_cmd->features = r.features;
ata_cmd->sector_count = r.sector_count;
ata_cmd->sector_number = r.lba_low;
ata_cmd->cylinder_low = r.lba_mid;
ata_cmd->cylinder_high = r.lba_high;
ata_cmd->device_head = r.device;
ata_cmd->command = r.command;
}
bool readdata = false;
if (in.direction == ata_cmd_in::data_in) {
readdata = true;
areca_packet[6] = 0x13;
}
else if ( in.direction == ata_cmd_in::no_data )
{
areca_packet[6] = 0x15;
}
else if (in.direction == ata_cmd_in::data_out)
{
memcpy(ata_cmd->data, in.buffer, in.size);
areca_packet[6] = 0x14;
}
else {
return set_err(ENOSYS);
}
areca_packet[11] = get_disknum() - 1;
areca_packet[19] = get_encnum() - 1;
expected = arcmsr_ui_handler(areca_packet, areca_packet_len, return_buff);
if ( expected < 0 )
{
return set_err(EIO);
}
sATA_OUTPUT_REGISTERS *ata_out = (sATA_OUTPUT_REGISTERS *)&return_buff[5] ;
if ( ata_out->status )
{
if ( in.in_regs.command == ATA_IDENTIFY_DEVICE
&& !nonempty((unsigned char *)in.buffer, in.size))
{
return set_err(ENODEV, "No drive on port %d", get_disknum());
}
}
if (readdata)
{
memcpy(in.buffer, &return_buff[7], in.size);
}
{
ata_out_regs & r = out.out_regs;
r.error = ata_out->error;
r.sector_count = ata_out->sector_count;
r.lba_low = ata_out->sector_number;
r.lba_mid = ata_out->cylinder_low;
r.lba_high = ata_out->cylinder_high;
r.status = ata_out->status;
}
return true;
}
bool generic_areca_device::arcmsr_scsi_pass_through(struct scsi_cmnd_io * iop)
{
unsigned char areca_packet[640];
int areca_packet_len = sizeof(areca_packet);
unsigned char return_buff[2048];
int expected = 0;
if (iop->cmnd_len > 16) {
set_err(EINVAL, "cmnd_len too large");
return false;
}
memset(areca_packet, 0, areca_packet_len);
areca_packet[0] = 0x5E;
areca_packet[1] = 0x01;
areca_packet[2] = 0x61;
areca_packet[3] = (unsigned char)((areca_packet_len - 6) & 0xff);
areca_packet[4] = (unsigned char)(((areca_packet_len - 6) >> 8) & 0xff);
areca_packet[5] = 0x1c;
areca_packet[6] = 0x16;
memcpy(&areca_packet[7], "SmrT", 4);
areca_packet[12] = iop->cmnd_len;
memcpy( &areca_packet[35], iop->cmnd, iop->cmnd_len);
areca_packet[15] = (unsigned char)iop->dxfer_len;
areca_packet[16] = (unsigned char)(iop->dxfer_len >> 8);
areca_packet[17] = (unsigned char)(iop->dxfer_len >> 16);
areca_packet[18] = (unsigned char)(iop->dxfer_len >> 24);
if(iop->dxfer_dir == DXFER_TO_DEVICE)
{
areca_packet[13] |= 0x01;
memcpy(&areca_packet[67], iop->dxferp, iop->dxfer_len);
}
else if (iop->dxfer_dir == DXFER_FROM_DEVICE)
{
}
else if( iop->dxfer_dir == DXFER_NONE)
{
}
else {
return set_err(ENOSYS);
}
areca_packet[11] = get_disknum() - 1;
areca_packet[19] = get_encnum() - 1;
expected = arcmsr_ui_handler(areca_packet, areca_packet_len, return_buff);
if (expected < 0)
return set_err(EIO, "arcmsr_scsi_pass_through: I/O error");
if (expected < 15)
return set_err(EIO, "arcmsr_scsi_pass_through: missing data (%d bytes, expected %d)", expected, 15);
int scsi_status = return_buff[5];
int in_data_len = return_buff[11] | return_buff[12] << 8 | return_buff[13] << 16 | return_buff[14] << 24;
if (iop->dxfer_dir == DXFER_FROM_DEVICE)
{
memset(iop->dxferp, 0, iop->dxfer_len);
memcpy(iop->dxferp, &return_buff[15], in_data_len);
}
if(scsi_status == 0xE1 )
{
scsi_status = 0x0;
}
if(scsi_status != 0x00 && scsi_status != SCSI_STATUS_CHECK_CONDITION)
{
return set_err(EIO);
}
if(scsi_status == SCSI_STATUS_CHECK_CONDITION)
{
iop->scsi_status = SCSI_STATUS_CHECK_CONDITION;
iop->resp_sense_len = 4;
iop->sensep[0] = return_buff[7];
iop->sensep[1] = return_buff[8];
iop->sensep[2] = return_buff[9];
iop->sensep[3] = return_buff[10];
}
return true;
}
areca_ata_device::areca_ata_device(smart_interface * intf, const char * dev_name, int disknum, int encnum)
: smart_device(intf, dev_name, "areca", "areca")
{
set_encnum(encnum);
set_disknum(disknum);
set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum);
}
areca_ata_device::~areca_ata_device()
{
}
bool areca_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
{
if (!ata_cmd_is_supported(in,
ata_device::supports_data_out |
ata_device::supports_output_regs |
ata_device::supports_48bit_hi_null,
"Areca")
)
return false;
return arcmsr_ata_pass_through(in, out);
}
areca_scsi_device::areca_scsi_device(smart_interface * intf, const char * dev_name, int disknum, int encnum)
: smart_device(intf, dev_name, "areca", "areca")
{
set_encnum(encnum);
set_disknum(disknum);
set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum);
}
areca_scsi_device::~areca_scsi_device()
{
}
bool areca_scsi_device::scsi_pass_through(struct scsi_cmnd_io * iop)
{
return arcmsr_scsi_pass_through(iop);
}