diff options
Diffstat (limited to '')
-rw-r--r-- | dev_areca.cpp | 695 |
1 files changed, 695 insertions, 0 deletions
diff --git a/dev_areca.cpp b/dev_areca.cpp new file mode 100644 index 0000000..5b47eac --- /dev/null +++ b/dev_areca.cpp @@ -0,0 +1,695 @@ +/* + * 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 // For debugging areca code +static void dumpdata(unsigned char *block, int len) +{ + int ln = (len / 16) + 1; // total line# + 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 the line# and the HEX data + // if a line data length < 16 then append the space to the tail of line to reach 16 chars + 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(" "); + } + } + + // print ASCII char + 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() +{ + +} + +// PURPOSE +// This is an interface routine meant to isolate the OS dependent +// parts of the code, and to provide a debugging interface. Each +// different port and OS needs to provide it's own interface. This +// is the Windows interface to the Areca "arcmsr" driver. It allows ATA +// commands to be passed through the SCSI driver. +// DETAILED DESCRIPTION OF ARGUMENTS +// fd: is the file descriptor provided by open() +// disknum is the disk number (0 to 127) in the RAID array +// command: defines the different 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 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 ) + { + // command for writing data to driver + case ARCMSR_WRITE_WQBUFFER: + if ( data && data_len ) + { + sBuf.srbioctl.Length = data_len; + memcpy((unsigned char *)sBuf.ioctldatabuffer, (unsigned char *)data, data_len); + } + /* FALLTHRU */ + // commands for clearing related buffer of driver + case ARCMSR_CLEAR_RQBUFFER: + case ARCMSR_CLEAR_WQBUFFER: + cdb[0] = 0x3B; //SCSI_WRITE_BUF command; + break; + // command for reading data from driver + case ARCMSR_READ_RQBUFFER: + // command for identifying driver + case ARCMSR_RETURN_CODE_3F: + cdb[0] = 0x3C; //SCSI_READ_BUF command; + dir = DXFER_FROM_DEVICE; + break; + default: + // unknown arcmsr commands + 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 ) + { + // if succeeded, just returns the length of outgoing data + return data_len; + } + + if ( sBuf.srbioctl.Length ) + { + memcpy(ptr, &sBuf.ioctldatabuffer[0], sBuf.srbioctl.Length); + ptr += sBuf.srbioctl.Length; + total += sBuf.srbioctl.Length; + // the returned bytes enough to compute payload 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 ) + { + // valid header, let's compute the returned payload length, + // we expected the total length is + // payload + 3 bytes header + 2 bytes length + 1 byte checksum + expected = areca_return_packet[4] * 256 + areca_return_packet[3] + 6; + } + } + + if ( total >= 7 && total >= expected ) + { + //printf("total bytes received = %d, expected length = %d\n", total, expected); + + // ------ Okay! we received enough -------- + break; + } + } + } + + // Deal with the different error cases + if ( arcmsr_cmd == ARCMSR_RETURN_CODE_3F ) + { + // Silence the ARCMSR_IOCTL_RETURN_CODE_3F's error, no pout(...) + 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; + + // ----- ADD CHECKSUM ----- + 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 ) // Prefix + Checksum + { + return -1; + } + + if(!arcmsr_unlock()) + { + return -1; + } + + // ----- VERIFY THE CHECKSUM ----- + 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/* SATA Controllers */ || + (ctlr_type == 0x03 /* SAS Controllers */ && return_buff[0x52] & 0x01 /* SATA devices behind SAS Controller */) ) + { + // SATA device + return 1; + } + + // SAS device + return 0; +} + +bool generic_areca_device::arcmsr_ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) +{ + // ATA input registers + 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]; // [in/out] buffer for outgoing/incoming data + } sATA_INPUT_REGISTERS; + + // ATA output registers + // Note: The output registers is re-sorted for areca internal use only + 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; + + // Areca packet format for outgoing: + // B[0~2] : 3 bytes header, fixed value 0x5E, 0x01, 0x61 + // B[3~4] : 2 bytes command length + variant data length, little endian + // B[5] : 1 bytes areca defined command code, ATA passthrough command code is 0x1c + // B[6~last-1] : variant bytes payload data + // B[last] : 1 byte checksum, simply sum(B[3] ~ B[last -1]) + // + // + // header 3 bytes length 2 bytes cmd 1 byte payload data x bytes cs 1 byte + // +--------------------------------------------------------------------------------+ + // + 0x5E 0x01 0x61 | 0x00 0x00 | 0x1c | .................... | 0x00 | + // +--------------------------------------------------------------------------------+ + // + + //Areca packet format for incoming: + // B[0~2] : 3 bytes header, fixed value 0x5E, 0x01, 0x61 + // B[3~4] : 2 bytes payload length, little endian + // B[5~last-1] : variant bytes returned payload data + // B[last] : 1 byte checksum, simply sum(B[3] ~ B[last -1]) + // + // + // header 3 bytes length 2 bytes payload data x bytes cs 1 byte + // +-------------------------------------------------------------------+ + // + 0x5E 0x01 0x61 | 0x00 0x00 | .................... | 0x00 | + // +-------------------------------------------------------------------+ + 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; + + // For debugging +#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); + + // ----- BEGIN TO SETUP HEADERS ------- + 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 defined code for ATA passthrough command + + // ----- BEGIN TO SETUP PAYLOAD DATA ----- + memcpy(&areca_packet[7], "SmrT", 4); // areca defined password + ata_cmd = (sATA_INPUT_REGISTERS *)&areca_packet[12]; + + // Set registers + { + 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; + // the command will read data + areca_packet[6] = 0x13; + } + else if ( in.direction == ata_cmd_in::no_data ) + { + // the commands will return no data + areca_packet[6] = 0x15; + } + else if (in.direction == ata_cmd_in::data_out) + { + // the commands will write data + memcpy(ata_cmd->data, in.buffer, in.size); + areca_packet[6] = 0x14; + } + else { + // COMMAND NOT SUPPORTED VIA ARECA IOCTL INTERFACE + return set_err(ENOSYS); + } + + areca_packet[11] = get_disknum() - 1; // disk# + areca_packet[19] = get_encnum() - 1; // enc# + + // ----- BEGIN TO SEND TO ARECA DRIVER ------ + 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()); + } + } + + // returns with data + if (readdata) + { + memcpy(in.buffer, &return_buff[7], in.size); + } + + // Return register values + { + 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) +{ + // Areca packet format for outgoing: + // B[0~2] : 3 bytes header, fixed value 0x5E, 0x01, 0x61 + // B[3~4] : 2 bytes command length + variant data length, little endian + // B[5] : 1 bytes areca defined command code + // B[6~last-1] : variant bytes payload data + // B[last] : 1 byte checksum, simply sum(B[3] ~ B[last -1]) + // + // + // header 3 bytes length 2 bytes cmd 1 byte payload data x bytes cs 1 byte + // +--------------------------------------------------------------------------------+ + // + 0x5E 0x01 0x61 | 0x00 0x00 | 0x1c | .................... | 0x00 | + // +--------------------------------------------------------------------------------+ + // + + //Areca packet format for incoming: + // B[0~2] : 3 bytes header, fixed value 0x5E, 0x01, 0x61 + // B[3~4] : 2 bytes payload length, little endian + // B[5~last-1] : variant bytes returned payload data + // B[last] : 1 byte checksum, simply sum(B[3] ~ B[last -1]) + // + // + // header 3 bytes length 2 bytes payload data x bytes cs 1 byte + // +-------------------------------------------------------------------+ + // + 0x5E 0x01 0x61 | 0x00 0x00 | .................... | 0x00 | + // +-------------------------------------------------------------------+ + 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); + + // ----- BEGIN TO SETUP HEADERS ------- + 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; + + // ----- BEGIN TO SETUP PAYLOAD DATA ----- + areca_packet[6] = 0x16; // scsi pass through + memcpy(&areca_packet[7], "SmrT", 4); // areca defined password + areca_packet[12] = iop->cmnd_len; // cdb length + memcpy( &areca_packet[35], iop->cmnd, iop->cmnd_len); // cdb + areca_packet[15] = (unsigned char)iop->dxfer_len; // 15(LSB) ~ 18(MSB): data length ( max=512 bytes) + 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 { + // COMMAND NOT SUPPORTED VIA ARECA IOCTL INTERFACE + return set_err(ENOSYS); + } + + areca_packet[11] = get_disknum() - 1; // disk# + areca_packet[19] = get_encnum() - 1; // enc# + + // ----- BEGIN TO SEND TO ARECA DRIVER ------ + 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) // 7 bytes if port is empty + 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); // need? + memcpy(iop->dxferp, &return_buff[15], in_data_len); + } + + if(scsi_status == 0xE1 /* Underrun, actual data length < requested data length */) + { + // don't care, just ignore + scsi_status = 0x0; + } + + if(scsi_status != 0x00 && scsi_status != SCSI_STATUS_CHECK_CONDITION) + { + return set_err(EIO); + } + + if(scsi_status == SCSI_STATUS_CHECK_CONDITION) + { + // 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_multi_sector | // TODO + 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); +} + + + + |