diff options
Diffstat (limited to '')
-rw-r--r-- | os_linux.cpp | 3637 |
1 files changed, 3637 insertions, 0 deletions
diff --git a/os_linux.cpp b/os_linux.cpp new file mode 100644 index 0000000..aa6ac4f --- /dev/null +++ b/os_linux.cpp @@ -0,0 +1,3637 @@ +/* + * os_linux.cpp + * + * Home page of code is: https://www.smartmontools.org + * + * Copyright (C) 2003-11 Bruce Allen + * Copyright (C) 2003-11 Doug Gilbert <dgilbert@interlog.com> + * Copyright (C) 2008-22 Christian Franke + * + * Original AACRaid code: + * Copyright (C) 2014 Raghava Aditya <raghava.aditya@pmcs.com> + * + * Original Areca code: + * Copyright (C) 2008-12 Hank Wu <hank@areca.com.tw> + * Copyright (C) 2008 Oliver Bock <brevilo@users.sourceforge.net> + * + * Original MegaRAID code: + * Copyright (C) 2008 Jordan Hargrave <jordan_hargrave@dell.com> + * + * 3ware code was derived from code that was: + * + * Written By: Adam Radford <linux@3ware.com> + * Modifications By: Joel Jacobson <linux@3ware.com> + * Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * Brad Strand <linux@3ware.com> + * + * Copyright (C) 1999-2003 3ware Inc. + * + * Kernel compatibility By: Andre Hedrick <andre@suse.com> + * Non-Copyright (C) 2000 Andre Hedrick <andre@suse.com> + * + * Other ars of this file are derived from code that was + * + * Copyright (C) 1999-2000 Michael Cornwell <cornwell@acm.org> + * Copyright (C) 2000 Andre Hedrick <andre@linux-ide.org> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <errno.h> +#include <fcntl.h> +#include <glob.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_ioctl.h> +#include <scsi/sg.h> +#include <linux/bsg.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/utsname.h> +#include <unistd.h> +#include <stddef.h> // for offsetof() +#include <sys/uio.h> +#include <sys/types.h> +#include <dirent.h> +#ifdef HAVE_SYS_SYSMACROS_H +// glibc 2.25: The inclusion of <sys/sysmacros.h> by <sys/types.h> is +// deprecated. A warning is printed if major(), minor() or makedev() +// is used but <sys/sysmacros.h> is not included. +#include <sys/sysmacros.h> +#endif +#ifdef HAVE_LIBSELINUX +#include <selinux/selinux.h> +#endif + +#include "atacmds.h" +#include "os_linux.h" +#include "scsicmds.h" +#include "utility.h" +#include "cciss.h" +#include "megaraid.h" +#include "sssraid.h" +#include "aacraid.h" +#include "nvmecmds.h" + +#include "dev_interface.h" +#include "dev_ata_cmd_set.h" +#include "dev_areca.h" + +// "include/uapi/linux/nvme_ioctl.h" from Linux kernel sources +#include "linux_nvme_ioctl.h" // nvme_passthru_cmd, NVME_IOCTL_ADMIN_CMD + +#ifndef ENOTSUP +#define ENOTSUP ENOSYS +#endif + +#define ARGUSED(x) ((void)(x)) + +const char * os_linux_cpp_cvsid = "$Id: os_linux.cpp 5457 2023-02-17 16:21:30Z chrfranke $" + OS_LINUX_H_CVSID; +extern unsigned char failuretest_permissive; + +namespace os_linux { // No need to publish anything, name provided for Doxygen + +///////////////////////////////////////////////////////////////////////////// +/// Shared open/close routines + +class linux_smart_device +: virtual public /*implements*/ smart_device +{ +public: + explicit linux_smart_device(int flags, int retry_flags = -1) + : smart_device(never_called), + m_fd(-1), + m_flags(flags), m_retry_flags(retry_flags) + { } + + virtual ~linux_smart_device(); + + virtual bool is_open() const override; + + virtual bool open() override; + + virtual bool close() override; + +protected: + /// Return filedesc for derived classes. + int get_fd() const + { return m_fd; } + + void set_fd(int fd) + { m_fd = fd; } + +private: + int m_fd; ///< filedesc, -1 if not open. + int m_flags; ///< Flags for ::open() + int m_retry_flags; ///< Flags to retry ::open(), -1 if no retry +}; + +linux_smart_device::~linux_smart_device() +{ + if (m_fd >= 0) + ::close(m_fd); +} + +bool linux_smart_device::is_open() const +{ + return (m_fd >= 0); +} + +bool linux_smart_device::open() +{ + m_fd = ::open(get_dev_name(), m_flags); + + if (m_fd < 0 && errno == EROFS && m_retry_flags != -1) + // Retry + m_fd = ::open(get_dev_name(), m_retry_flags); + + if (m_fd < 0) { + if (errno == EBUSY && (m_flags & O_EXCL)) + // device is locked + return set_err(EBUSY, + "The requested controller is used exclusively by another process!\n" + "(e.g. smartctl or smartd)\n" + "Please quit the impeding process or try again later..."); + return set_err((errno==ENOENT || errno==ENOTDIR) ? ENODEV : errno); + } + + if (m_fd >= 0) { + // sets FD_CLOEXEC on the opened device file descriptor. The + // descriptor is otherwise leaked to other applications (mail + // sender) which may be considered a security risk and may result + // in AVC messages on SELinux-enabled systems. + if (-1 == fcntl(m_fd, F_SETFD, FD_CLOEXEC)) + // TODO: Provide an error printing routine in class smart_interface + pout("fcntl(set FD_CLOEXEC) failed, errno=%d [%s]\n", errno, strerror(errno)); + } + + return true; +} + +// equivalent to close(file descriptor) +bool linux_smart_device::close() +{ + int fd = m_fd; m_fd = -1; + if (::close(fd) < 0) + return set_err(errno); + return true; +} + +// examples for smartctl +static const char smartctl_examples[] = + "=================================================== SMARTCTL EXAMPLES =====\n\n" + " smartctl --all /dev/sda (Prints all SMART information)\n\n" + " smartctl --smart=on --offlineauto=on --saveauto=on /dev/sda\n" + " (Enables SMART on first disk)\n\n" + " smartctl --test=long /dev/sda (Executes extended disk self-test)\n\n" + " smartctl --attributes --log=selftest --quietmode=errorsonly /dev/sda\n" + " (Prints Self-Test & Attribute errors)\n" + " smartctl --all --device=3ware,2 /dev/sda\n" + " smartctl --all --device=3ware,2 /dev/twe0\n" + " smartctl --all --device=3ware,2 /dev/twa0\n" + " smartctl --all --device=3ware,2 /dev/twl0\n" + " (Prints all SMART info for 3rd ATA disk on 3ware RAID controller)\n" + " smartctl --all --device=hpt,1/1/3 /dev/sda\n" + " (Prints all SMART info for the SATA disk attached to the 3rd PMPort\n" + " of the 1st channel on the 1st HighPoint RAID controller)\n" + " smartctl --all --device=areca,3/1 /dev/sg2\n" + " (Prints all SMART info for 3rd ATA disk of the 1st enclosure\n" + " on Areca RAID controller)\n" + ; + +///////////////////////////////////////////////////////////////////////////// +/// Linux ATA support + +class linux_ata_device +: public /*implements*/ ata_device_with_command_set, + public /*extends*/ linux_smart_device +{ +public: + linux_ata_device(smart_interface * intf, const char * dev_name, const char * req_type); + +protected: + virtual int ata_command_interface(smart_command_set command, int select, char * data) override; +}; + +linux_ata_device::linux_ata_device(smart_interface * intf, const char * dev_name, const char * req_type) +: smart_device(intf, dev_name, "ata", req_type), + linux_smart_device(O_RDONLY | O_NONBLOCK) +{ +} + +// 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 linux one. +// DETAILED DESCRIPTION OF ARGUMENTS +// device: is the file descriptor provided by open() +// 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" + +#define BUFFER_LENGTH (4+512) + +int linux_ata_device::ata_command_interface(smart_command_set command, int select, char * data) +{ + unsigned char buff[BUFFER_LENGTH]; + // positive: bytes to write to caller. negative: bytes to READ from + // caller. zero: non-data command + int copydata=0; + + const int HDIO_DRIVE_CMD_OFFSET = 4; + + // See struct hd_drive_cmd_hdr in hdreg.h. Before calling ioctl() + // buff[0]: ATA COMMAND CODE REGISTER + // buff[1]: ATA SECTOR NUMBER REGISTER == LBA LOW REGISTER + // buff[2]: ATA FEATURES REGISTER + // buff[3]: ATA SECTOR COUNT REGISTER + + // Note that on return: + // buff[2] contains the ATA SECTOR COUNT REGISTER + + // clear out buff. Large enough for HDIO_DRIVE_CMD (4+512 bytes) + memset(buff, 0, BUFFER_LENGTH); + + buff[0]=ATA_SMART_CMD; + switch (command){ + case CHECK_POWER_MODE: + buff[0]=ATA_CHECK_POWER_MODE; + copydata=1; + break; + case READ_VALUES: + buff[2]=ATA_SMART_READ_VALUES; + buff[3]=1; + copydata=512; + break; + case READ_THRESHOLDS: + buff[2]=ATA_SMART_READ_THRESHOLDS; + buff[1]=buff[3]=1; + copydata=512; + break; + case READ_LOG: + buff[2]=ATA_SMART_READ_LOG_SECTOR; + buff[1]=select; + buff[3]=1; + copydata=512; + break; + case WRITE_LOG: + break; + case IDENTIFY: + buff[0]=ATA_IDENTIFY_DEVICE; + buff[3]=1; + copydata=512; + break; + case PIDENTIFY: + buff[0]=ATA_IDENTIFY_PACKET_DEVICE; + buff[3]=1; + copydata=512; + break; + case ENABLE: + buff[2]=ATA_SMART_ENABLE; + buff[1]=1; + break; + case DISABLE: + buff[2]=ATA_SMART_DISABLE; + buff[1]=1; + break; + case STATUS: + // this command only says if SMART is working. It could be + // replaced with STATUS_CHECK below. + buff[2]=ATA_SMART_STATUS; + break; + case AUTO_OFFLINE: + // NOTE: According to ATAPI 4 and UP, this command is obsolete + // select == 241 for enable but no data transfer. Use TASK ioctl. + buff[1]=ATA_SMART_AUTO_OFFLINE; + buff[2]=select; + break; + case AUTOSAVE: + // select == 248 for enable but no data transfer. Use TASK ioctl. + buff[1]=ATA_SMART_AUTOSAVE; + buff[2]=select; + break; + case IMMEDIATE_OFFLINE: + buff[2]=ATA_SMART_IMMEDIATE_OFFLINE; + buff[1]=select; + break; + case STATUS_CHECK: + // This command uses HDIO_DRIVE_TASK and has different syntax than + // the other commands. + buff[1]=ATA_SMART_STATUS; + break; + default: + pout("Unrecognized command %d in linux_ata_command_interface()\n" + "Please contact " PACKAGE_BUGREPORT "\n", command); + errno=ENOSYS; + return -1; + } + + // This command uses the HDIO_DRIVE_TASKFILE ioctl(). This is the + // only ioctl() that can be used to WRITE data to the disk. + if (command==WRITE_LOG) { + unsigned char task[sizeof(ide_task_request_t)+512]; + ide_task_request_t *reqtask=(ide_task_request_t *) task; + task_struct_t *taskfile=(task_struct_t *) reqtask->io_ports; + + memset(task, 0, sizeof(task)); + + taskfile->data = 0; + taskfile->feature = ATA_SMART_WRITE_LOG_SECTOR; + taskfile->sector_count = 1; + taskfile->sector_number = select; + taskfile->low_cylinder = 0x4f; + taskfile->high_cylinder = 0xc2; + taskfile->device_head = 0; + taskfile->command = ATA_SMART_CMD; + + reqtask->data_phase = TASKFILE_OUT; + reqtask->req_cmd = IDE_DRIVE_TASK_OUT; + reqtask->out_size = 512; + reqtask->in_size = 0; + + // copy user data into the task request structure + memcpy(task+sizeof(ide_task_request_t), data, 512); + + if (ioctl(get_fd(), HDIO_DRIVE_TASKFILE, task)) { + if (errno==EINVAL) + pout("Kernel lacks HDIO_DRIVE_TASKFILE support; compile kernel with CONFIG_IDE_TASK_IOCTL set\n"); + return -1; + } + return 0; + } + + // There are two different types of ioctls(). The HDIO_DRIVE_TASK + // one is this: + if (command==STATUS_CHECK || command==AUTOSAVE || command==AUTO_OFFLINE){ + // NOT DOCUMENTED in /usr/src/linux/include/linux/hdreg.h. You + // have to read the IDE driver source code. Sigh. + // buff[0]: ATA COMMAND CODE REGISTER + // buff[1]: ATA FEATURES REGISTER + // buff[2]: ATA SECTOR_COUNT + // buff[3]: ATA SECTOR NUMBER + // buff[4]: ATA CYL LO REGISTER + // buff[5]: ATA CYL HI REGISTER + // buff[6]: ATA DEVICE HEAD + + unsigned const char normal_lo=0x4f, normal_hi=0xc2; + unsigned const char failed_lo=0xf4, failed_hi=0x2c; + buff[4]=normal_lo; + buff[5]=normal_hi; + + if (ioctl(get_fd(), HDIO_DRIVE_TASK, buff)) { + if (errno==EINVAL) { + pout("Error SMART Status command via HDIO_DRIVE_TASK failed"); + pout("Rebuild older linux 2.2 kernels with HDIO_DRIVE_TASK support added\n"); + } + else + syserror("Error SMART Status command failed"); + return -1; + } + + // Cyl low and Cyl high unchanged means "Good SMART status" + if (buff[4]==normal_lo && buff[5]==normal_hi) + return 0; + + // These values mean "Bad SMART status" + if (buff[4]==failed_lo && buff[5]==failed_hi) + return 1; + + // We haven't gotten output that makes sense; print out some debugging info + syserror("Error SMART Status command failed"); + pout("Please get assistance from " PACKAGE_URL "\n"); + pout("Register values returned from SMART Status command are:\n"); + pout("ST =0x%02x\n",(int)buff[0]); + pout("ERR=0x%02x\n",(int)buff[1]); + pout("NS =0x%02x\n",(int)buff[2]); + pout("SC =0x%02x\n",(int)buff[3]); + pout("CL =0x%02x\n",(int)buff[4]); + pout("CH =0x%02x\n",(int)buff[5]); + pout("SEL=0x%02x\n",(int)buff[6]); + return -1; + } + +#if 1 + // Note to people doing ports to other OSes -- don't worry about + // this block -- you can safely ignore it. I have put it here + // because under linux when you do IDENTIFY DEVICE to a packet + // device, it generates an ugly kernel syslog error message. This + // is harmless but frightens users. So this block detects packet + // devices and make IDENTIFY DEVICE fail "nicely" without a syslog + // error message. + // + // If you read only the ATA specs, it appears as if a packet device + // *might* respond to the IDENTIFY DEVICE command. This is + // misleading - it's because around the time that SFF-8020 was + // incorporated into the ATA-3/4 standard, the ATA authors were + // sloppy. See SFF-8020 and you will see that ATAPI devices have + // *always* had IDENTIFY PACKET DEVICE as a mandatory part of their + // command set, and return 'Command Aborted' to IDENTIFY DEVICE. + if (command==IDENTIFY || command==PIDENTIFY){ + unsigned short deviceid[256]; + // check the device identity, as seen when the system was booted + // or the device was FIRST registered. This will not be current + // if the user has subsequently changed some of the parameters. If + // device is a packet device, swap the command interpretations. + if (!ioctl(get_fd(), HDIO_GET_IDENTITY, deviceid) && (deviceid[0] & 0x8000)) + buff[0]=(command==IDENTIFY)?ATA_IDENTIFY_PACKET_DEVICE:ATA_IDENTIFY_DEVICE; + } +#endif + + // We are now doing the HDIO_DRIVE_CMD type ioctl. + if ((ioctl(get_fd(), HDIO_DRIVE_CMD, buff))) + return -1; + + // CHECK POWER MODE command returns information in the Sector Count + // register (buff[3]). Copy to return data buffer. + if (command==CHECK_POWER_MODE) + buff[HDIO_DRIVE_CMD_OFFSET]=buff[2]; + + // if the command returns data then copy it back + if (copydata) + memcpy(data, buff+HDIO_DRIVE_CMD_OFFSET, copydata); + + return 0; +} + +// >>>>>> Start of general SCSI specific linux code + +/* Linux specific code. + * Historically smartmontools (and smartsuite before it) used the + * SCSI_IOCTL_SEND_COMMAND ioctl which is available to all linux device + * nodes that use the SCSI subsystem. A better interface has been available + * via the SCSI generic (sg) driver but this involves the extra step of + * mapping disk devices (e.g. /dev/sda) to the corresponding sg device + * (e.g. /dev/sg2). In the linux kernel 2.6 series most of the facilities of + * the sg driver have become available via the SG_IO ioctl which is available + * on all SCSI devices (on SCSI tape devices from lk 2.6.6). Now in lk 5.17 + * the SCSI_IOCTL_SEND_COMMAND ioctl is still present but deprecated sending + * a warning to the log the first time (after power up) it is used. The SG_IO + * Version 3 interface is the most widely used (circa lk 5.17 in 2022) and is + * available on the primary block device name (e.g. /dev/sdc) for all SCSI + * disks (and tapes) including all USB attached storage and all ATA/SATA + * storage. */ + +#define MAX_DXFER_LEN 1024 /* can be increased if necessary */ +#define SEND_IOCTL_RESP_SENSE_LEN 16 /* ioctl limitation */ +#define SG_IO_RESP_SENSE_LEN 64 /* large enough see buffer */ +#define LSCSI_DRIVER_MASK 0xf /* mask out "suggestions" */ +#define LSCSI_DRIVER_SENSE 0x8 /* alternate CHECK CONDITION indication */ +#define LSCSI_DID_ERROR 0x7 /* Need to work around aacraid driver quirk */ +#define LSCSI_DRIVER_TIMEOUT 0x6 +#define LSCSI_DID_TIME_OUT 0x3 +#define LSCSI_DID_BUS_BUSY 0x2 +#define LSCSI_DID_NO_CONNECT 0x1 + + +enum lk_sg_io_ifc_t { + SG_IO_USE_DETECT = 0, + SG_IO_UNSUPP = 1, + SG_IO_USE_V3 = 3, + SG_IO_USE_V4 = 4, +}; + +static enum lk_sg_io_ifc_t sg_io_interface = SG_IO_USE_DETECT; + + +/* Preferred implementation for issuing SCSI commands in linux. This + * function uses the SG_IO ioctl. Return 0 if command issued successfully + * (various status values should still be checked). If the SCSI command + * cannot be issued then a negative errno value is returned. */ +static int sg_io_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report, + enum lk_sg_io_ifc_t sg_io_ifc) +{ + /* we are filling structures for both versions, but using only one requested */ + struct sg_io_hdr io_hdr_v3; + struct sg_io_v4 io_hdr_v4; + +#ifdef SCSI_CDB_CHECK + bool ok = is_scsi_cdb(iop->cmnd, iop->cmnd_len); + if (! ok) { + int n = iop->cmnd_len; + const unsigned char * ucp = iop->cmnd; + + pout(">>>>>>>> %s: cdb seems invalid, opcode=0x%x, len=%d, cdb:\n", + __func__, ((n > 0) ? ucp[0] : 0), n); + if (n > 0) { + if (n > 16) + pout(" <<truncating to first 16 bytes>>\n"); + dStrHex((const uint8_t *)ucp, ((n > 16) ? 16 : n), 1); + } + } +#endif + +#if 0 + if (report > 0) { + int k, j; + const unsigned char * ucp = iop->cmnd; + const char * np; + char buff[256]; + const int sz = (int)sizeof(buff); + + pout(">>>> %s: sg_io_ifc=%d\n", __func__, (int)sg_io_ifc); + np = scsi_get_opcode_name(ucp); + j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>"); + for (k = 0; k < (int)iop->cmnd_len; ++k) + j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]); + if ((report > 1) && + (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) { + int trunc = (iop->dxfer_len > 256) ? 1 : 0; + + snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing " + "data, len=%d%s:\n", (int)iop->dxfer_len, + (trunc ? " [only first 256 bytes shown]" : "")); + dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); + } + else + snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n"); + pout("%s", buff); + pout("%s\n", buff); + } +#endif + + memset(&io_hdr_v3, 0, sizeof(struct sg_io_hdr)); + memset(&io_hdr_v4, 0, sizeof(struct sg_io_v4)); + + io_hdr_v3.interface_id = 'S'; + io_hdr_v3.cmd_len = iop->cmnd_len; + io_hdr_v3.mx_sb_len = iop->max_sense_len; + io_hdr_v3.dxfer_len = iop->dxfer_len; + io_hdr_v3.dxferp = iop->dxferp; + io_hdr_v3.cmdp = iop->cmnd; + io_hdr_v3.sbp = iop->sensep; + /* sg_io_hdr interface timeout has millisecond units. Timeout of 0 + defaults to 60 seconds. */ + io_hdr_v3.timeout = ((0 == iop->timeout) ? 60 : iop->timeout) * 1000; + + io_hdr_v4.guard = 'Q'; + io_hdr_v4.request_len = iop->cmnd_len; + io_hdr_v4.request = __u64(iop->cmnd); + io_hdr_v4.max_response_len = iop->max_sense_len; + io_hdr_v4.response = __u64(iop->sensep); + io_hdr_v4.timeout = ((0 == iop->timeout) ? 60 : iop->timeout) * 1000; // msec + + switch (iop->dxfer_dir) { + case DXFER_NONE: + io_hdr_v3.dxfer_direction = SG_DXFER_NONE; + break; + case DXFER_FROM_DEVICE: + io_hdr_v3.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr_v4.din_xfer_len = iop->dxfer_len; + io_hdr_v4.din_xferp = __u64(iop->dxferp); + break; + case DXFER_TO_DEVICE: + io_hdr_v3.dxfer_direction = SG_DXFER_TO_DEV; + io_hdr_v4.dout_xfer_len = iop->dxfer_len; + io_hdr_v4.dout_xferp = __u64(iop->dxferp); + break; + default: + pout("do_scsi_cmnd_io: bad dxfer_dir\n"); + return -EINVAL; + } + + iop->resp_sense_len = 0; + iop->scsi_status = 0; + iop->resid = 0; + + void * io_hdr = NULL; + + switch (sg_io_ifc) { + case SG_IO_USE_V3: + io_hdr = &io_hdr_v3; + break; + case SG_IO_USE_V4: + io_hdr = &io_hdr_v4; + break; + default: + // should never be reached + errno = EOPNOTSUPP; + return -errno; + } + + if (ioctl(dev_fd, SG_IO, io_hdr) < 0) { + if (report) + pout(" SG_IO ioctl failed, errno=%d [%s], SG_IO_V%d\n", errno, + strerror(errno), (int)sg_io_ifc); + return -errno; + } + + unsigned int sg_driver_status = 0, sg_transport_status = 0, sg_info = 0, + sg_duration = 0; + + if (sg_io_ifc == SG_IO_USE_V3) { + iop->resid = io_hdr_v3.resid; + iop->scsi_status = io_hdr_v3.status; + sg_driver_status = io_hdr_v3.driver_status; + sg_transport_status = io_hdr_v3.host_status; + sg_info = io_hdr_v3.info; + iop->resp_sense_len = io_hdr_v3.sb_len_wr; + sg_duration = io_hdr_v3.duration; + } + + if (sg_io_ifc == SG_IO_USE_V4) { + switch (iop->dxfer_dir) { + case DXFER_NONE: + iop->resid = 0; + break; + case DXFER_FROM_DEVICE: + iop->resid = io_hdr_v4.din_resid; + break; + case DXFER_TO_DEVICE: + iop->resid = io_hdr_v4.dout_resid; + break; + } + iop->scsi_status = io_hdr_v4.device_status; + sg_driver_status = io_hdr_v4.driver_status; + sg_transport_status = io_hdr_v4.transport_status; + sg_info = io_hdr_v4.info; + iop->resp_sense_len = io_hdr_v4.response_len; + sg_duration = io_hdr_v4.duration; + } + if (sg_duration) { } // silence warning + +#if 0 + if (report > 0) { + pout(" scsi_status=0x%x, sg_transport_status=0x%x, sg_driver_status=0x%x\n" + " sg_info=0x%x sg_duration=%d milliseconds resid=%d\n", iop->scsi_status, + sg_transport_status, sg_driver_status, sg_info, + sg_duration, iop->resid); + + if (report > 1) { + if (DXFER_FROM_DEVICE == iop->dxfer_dir) { + int trunc, len; + + len = iop->dxfer_len - iop->resid; + trunc = (len > 256) ? 1 : 0; + if (len > 0) { + pout(" Incoming data, len=%d%s:\n", len, + (trunc ? " [only first 256 bytes shown]" : "")); + dStrHex(iop->dxferp, (trunc ? 256 : len), 1); + } else + pout(" Incoming data trimmed to nothing by resid\n"); + } + } + } +#endif + + if (sg_info & SG_INFO_CHECK) { /* error or warning */ + int masked_driver_status = (LSCSI_DRIVER_MASK & sg_driver_status); + + if (0 != sg_transport_status) { + if ((LSCSI_DID_NO_CONNECT == sg_transport_status) || + (LSCSI_DID_BUS_BUSY == sg_transport_status) || + (LSCSI_DID_TIME_OUT == sg_transport_status)) + return -ETIMEDOUT; + else + /* Check for DID_ERROR - workaround for aacraid driver quirk */ + if (LSCSI_DID_ERROR != sg_transport_status) { + return -EIO; /* catch all if not DID_ERR */ + } + } + if (0 != masked_driver_status) { + if (LSCSI_DRIVER_TIMEOUT == masked_driver_status) + return -ETIMEDOUT; + else if (LSCSI_DRIVER_SENSE != masked_driver_status) + return -EIO; + } + if (LSCSI_DRIVER_SENSE == masked_driver_status) + iop->scsi_status = SCSI_STATUS_CHECK_CONDITION; + if ((SCSI_STATUS_CHECK_CONDITION == iop->scsi_status) && + iop->sensep && (iop->resp_sense_len > 0)) { + if (report > 1) { + pout(" >>> Sense buffer, len=%d:\n", + (int)iop->resp_sense_len); + dStrHex(iop->sensep, iop->resp_sense_len , 1); + } + } + if (report) { + if (SCSI_STATUS_CHECK_CONDITION == iop->scsi_status && iop->sensep) { + if ((iop->sensep[0] & 0x7f) > 0x71) + pout(" status=%x: [desc] sense_key=%x asc=%x ascq=%x\n", + iop->scsi_status, iop->sensep[1] & 0xf, + iop->sensep[2], iop->sensep[3]); + else + pout(" status=%x: sense_key=%x asc=%x ascq=%x\n", + iop->scsi_status, iop->sensep[2] & 0xf, + iop->sensep[12], iop->sensep[13]); + } + else + pout(" status=0x%x\n", iop->scsi_status); + } + } + return 0; +} + +/* SCSI command transmission interface function, linux version. + * Returns 0 if SCSI command successfully launched and response + * received. Even when 0 is returned the caller should check + * scsi_cmnd_io::scsi_status for SCSI defined errors and warnings + * (e.g. CHECK CONDITION). If the SCSI command could not be issued + * (e.g. device not present or timeout) or some other problem + * (e.g. timeout) then returns a negative errno value */ +static int do_normal_scsi_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, + int report) +{ + int res; + + /* implementation relies on static sg_io_interface variable. If not + * previously set tries the SG_IO ioctl. If that succeeds assume + * that SG_IO ioctl functional. If it fails with an errno value + * other than ENODEV (no device) or a permissions problem then + * assume the SG_IO_USE_V3 interface. */ + switch (sg_io_interface) { + case SG_IO_USE_DETECT: + /* ignore report argument */ + /* Try SG_IO V3 first */ + if (0 == (res = sg_io_cmnd_io(dev_fd, iop, report, SG_IO_USE_V3))) { + sg_io_interface = SG_IO_USE_V3; + return 0; + } else if ((-ENODEV == res) || (-EACCES == res) || (-EPERM == res)) + return res; /* wait until we see a device */ + /* See if we can use SG_IO V4 * */ + if (0 == (res = sg_io_cmnd_io(dev_fd, iop, report, SG_IO_USE_V4))) { + sg_io_interface = SG_IO_USE_V4; + return 0; + } else if ((-ENODEV == res) || (-EACCES == res) || (-EPERM == res)) + return res; /* wait until we see a device */ + sg_io_interface = SG_IO_UNSUPP; + /* FALLTHRU */ + case SG_IO_UNSUPP: + /* previously called SCSI_IOCTL_SEND_COMMAND ioctl which has now + * been removed. The SG_IO_USE_V3 is most widely used now in Linux + * (circa 2022), try it again. */ + sg_io_interface = SG_IO_USE_V3; + /* FALLTHRU */ + case SG_IO_USE_V3: + case SG_IO_USE_V4: + /* use SG_IO V3 or V4 ioctl, depending on availabiliy */ + return sg_io_cmnd_io(dev_fd, iop, report, sg_io_interface); + default: + pout(">>>> do_scsi_cmnd_io: bad sg_io_interface=%d\n", + (int)sg_io_interface); + sg_io_interface = SG_IO_USE_DETECT; + return -EIO; /* report error and reset state */ + } +} + +// >>>>>> End of general SCSI specific linux code + +///////////////////////////////////////////////////////////////////////////// +/// Standard SCSI support + +class linux_scsi_device +: public /*implements*/ scsi_device, + public /*extends*/ linux_smart_device +{ +public: + linux_scsi_device(smart_interface * intf, const char * dev_name, + const char * req_type, bool scanning = false); + + virtual smart_device * autodetect_open() override; + + virtual bool scsi_pass_through(scsi_cmnd_io * iop) override; + +private: + bool m_scanning; ///< true if created within scan_smart_devices +}; + +linux_scsi_device::linux_scsi_device(smart_interface * intf, + const char * dev_name, const char * req_type, bool scanning /*= false*/) +: smart_device(intf, dev_name, "scsi", req_type), + // If opened with O_RDWR, a SATA disk in standby mode + // may spin-up after device close(). + linux_smart_device(O_RDONLY | O_NONBLOCK), + m_scanning(scanning) +{ +} + +bool linux_scsi_device::scsi_pass_through(scsi_cmnd_io * iop) +{ + int status = do_normal_scsi_cmnd_io(get_fd(), iop, scsi_debugmode); + if (status < 0) + return set_err(-status); + return true; +} + +///////////////////////////////////////////////////////////////////////////// +/// PMC AacRAID support + +class linux_aacraid_device +:public scsi_device, + public /*extends */ linux_smart_device +{ +public: + linux_aacraid_device(smart_interface *intf, const char *dev_name, + unsigned int host, unsigned int channel, unsigned int device); + + virtual ~linux_aacraid_device(); + + virtual bool open() override; + + virtual bool scsi_pass_through(scsi_cmnd_io *iop) override; + +private: + //Device Host number + int aHost; + + //Channel(Lun) of the device + int aLun; + + //Id of the device + int aId; + +}; + +linux_aacraid_device::linux_aacraid_device(smart_interface *intf, + const char *dev_name, unsigned int host, unsigned int channel, unsigned int device) + : smart_device(intf,dev_name,"aacraid","aacraid"), + linux_smart_device(O_RDWR|O_NONBLOCK), + aHost(host), aLun(channel), aId(device) +{ + set_info().info_name = strprintf("%s [aacraid_disk_%02d_%02d_%d]",dev_name,aHost,aLun,aId); + set_info().dev_type = strprintf("aacraid,%d,%d,%d",aHost,aLun,aId); +} + +linux_aacraid_device::~linux_aacraid_device() +{ +} + +bool linux_aacraid_device::open() +{ + //Create the character device name based on the host number + //Required for get stats from disks connected to different controllers + char dev_name[128]; + snprintf(dev_name, sizeof(dev_name), "/dev/aac%d", aHost); + + //Initial open of dev name to check if it exists + int afd = ::open(dev_name,O_RDWR); + + if(afd < 0 && errno == ENOENT) { + + FILE *fp = fopen("/proc/devices","r"); + if(NULL == fp) + return set_err(errno,"cannot open /proc/devices:%s", + strerror(errno)); + + char line[256]; + int mjr = -1; + + while(fgets(line,sizeof(line),fp) !=NULL) { + int nc = -1; + if(sscanf(line,"%d aac%n",&mjr,&nc) == 1 + && nc > 0 && '\n' == line[nc]) + break; + mjr = -1; + } + + //work with /proc/devices is done + fclose(fp); + + if (mjr < 0) + return set_err(ENOENT, "aac entry not found in /proc/devices"); + + //Create misc device file in /dev/ used for communication with driver + if(mknod(dev_name, S_IFCHR|0600, makedev(mjr,aHost))) + return set_err(errno,"cannot create %s:%s",dev_name,strerror(errno)); + + afd = ::open(dev_name,O_RDWR); + } + + if(afd < 0) + return set_err(errno,"cannot open %s:%s",dev_name,strerror(errno)); + + set_fd(afd); + return true; +} + +bool linux_aacraid_device::scsi_pass_through(scsi_cmnd_io *iop) +{ + int report = scsi_debugmode; + + if (report > 0) { + int k, j; + const unsigned char * ucp = iop->cmnd; + const char * np; + char buff[256]; + const int sz = (int)sizeof(buff); + + np = scsi_get_opcode_name(ucp); + j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>"); + for (k = 0; k < (int)iop->cmnd_len; ++k) + j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]); + if ((report > 1) && + (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) { + int trunc = (iop->dxfer_len > 256) ? 1 : 0; + + snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing " + "data, len=%d%s:\n", (int)iop->dxfer_len, + (trunc ? " [only first 256 bytes shown]" : "")); + dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); + } + else + snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n"); + + pout("%s", buff); + } + + + //return test commands + if (iop->cmnd[0] == 0x00) + return true; + + user_aac_reply *pReply; + + #ifdef ENVIRONMENT64 + // Create user 64 bit request + user_aac_srb64 *pSrb; + uint8_t aBuff[sizeof(user_aac_srb64) + sizeof(user_aac_reply)] = {0,}; + + pSrb = (user_aac_srb64*)aBuff; + pSrb->count = sizeof(user_aac_srb64) - sizeof(user_sgentry64); + + #elif defined(ENVIRONMENT32) + //Create user 32 bit request + user_aac_srb32 *pSrb; + uint8_t aBuff[sizeof(user_aac_srb32) + sizeof(user_aac_reply)] = {0,}; + + pSrb = (user_aac_srb32*)aBuff; + pSrb->count = sizeof(user_aac_srb32) - sizeof(user_sgentry32); + #endif + + pSrb->function = SRB_FUNCTION_EXECUTE_SCSI; + //channel is 0 always + pSrb->channel = 0; + pSrb->id = aId; + pSrb->lun = aLun; + pSrb->timeout = 0; + + pSrb->retry_limit = 0; + pSrb->cdb_size = iop->cmnd_len; + + switch(iop->dxfer_dir) { + case DXFER_NONE: + pSrb->flags = SRB_NoDataXfer; + break; + case DXFER_FROM_DEVICE: + pSrb->flags = SRB_DataIn; + break; + case DXFER_TO_DEVICE: + pSrb->flags = SRB_DataOut; + break; + default: + pout("aacraid: bad dxfer_dir\n"); + return set_err(EINVAL, "aacraid: bad dxfer_dir\n"); + } + + if(iop->dxfer_len > 0) { + + #ifdef ENVIRONMENT64 + pSrb->sg64.count = 1; + pSrb->sg64.sg64[0].addr64.lo32 = ((intptr_t)iop->dxferp) & + 0x00000000ffffffff; + pSrb->sg64.sg64[0].addr64.hi32 = ((intptr_t)iop->dxferp) >> 32; + + pSrb->sg64.sg64[0].length = (uint32_t)iop->dxfer_len; + pSrb->count += pSrb->sg64.count * sizeof(user_sgentry64); + #elif defined(ENVIRONMENT32) + pSrb->sg32.count = 1; + pSrb->sg32.sg32[0].addr32 = (intptr_t)iop->dxferp; + + pSrb->sg32.sg32[0].length = (uint32_t)iop->dxfer_len; + pSrb->count += pSrb->sg32.count * sizeof(user_sgentry32); + #endif + + } + + pReply = (user_aac_reply*)(aBuff+pSrb->count); + + memcpy(pSrb->cdb,iop->cmnd,iop->cmnd_len); + + int rc = 0; + errno = 0; + rc = ioctl(get_fd(),FSACTL_SEND_RAW_SRB,pSrb); + + if (rc != 0) + return set_err(errno, "aacraid send_raw_srb: %d.%d = %s", + aLun, aId, strerror(errno)); + +/* see kernel aacraid.h and MSDN SCSI_REQUEST_BLOCK documentation */ +#define SRB_STATUS_SUCCESS 0x1 +#define SRB_STATUS_ERROR 0x4 +#define SRB_STATUS_NO_DEVICE 0x08 +#define SRB_STATUS_SELECTION_TIMEOUT 0x0a +#define SRB_STATUS_AUTOSENSE_VALID 0x80 + + iop->scsi_status = pReply->scsi_status; + + if (pReply->srb_status == (SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR) + && iop->scsi_status == SCSI_STATUS_CHECK_CONDITION) { + memcpy(iop->sensep, pReply->sense_data, pReply->sense_data_size); + iop->resp_sense_len = pReply->sense_data_size; + return true; /* request completed with sense data */ + } + + switch (pReply->srb_status & 0x3f) { + + case SRB_STATUS_SUCCESS: + return true; /* request completed successfully */ + + case SRB_STATUS_NO_DEVICE: + return set_err(EIO, "aacraid: Device %d %d does not exist", aLun, aId); + + case SRB_STATUS_SELECTION_TIMEOUT: + return set_err(EIO, "aacraid: Device %d %d not responding", aLun, aId); + + default: + return set_err(EIO, "aacraid result: %d.%d = 0x%x", + aLun, aId, pReply->srb_status); + } +} + + +///////////////////////////////////////////////////////////////////////////// +/// LSI MegaRAID support + +class linux_megaraid_device +: public /* implements */ scsi_device, + public /* extends */ linux_smart_device +{ +public: + linux_megaraid_device(smart_interface *intf, const char *name, + unsigned int tgt); + + virtual ~linux_megaraid_device(); + + virtual smart_device * autodetect_open() override; + + virtual bool open() override; + virtual bool close() override; + + virtual bool scsi_pass_through(scsi_cmnd_io *iop) override; + +private: + unsigned int m_disknum; + unsigned int m_hba; + int m_fd; + + bool (linux_megaraid_device::*pt_cmd)(int cdblen, void *cdb, int dataLen, void *data, + int senseLen, void *sense, int report, int direction); + bool megasas_cmd(int cdbLen, void *cdb, int dataLen, void *data, + int senseLen, void *sense, int report, int direction); + bool megadev_cmd(int cdbLen, void *cdb, int dataLen, void *data, + int senseLen, void *sense, int report, int direction); +}; + +linux_megaraid_device::linux_megaraid_device(smart_interface *intf, + const char *dev_name, unsigned int tgt) + : smart_device(intf, dev_name, "megaraid", "megaraid"), + linux_smart_device(O_RDWR | O_NONBLOCK), + m_disknum(tgt), m_hba(0), + m_fd(-1), pt_cmd(0) +{ + set_info().info_name = strprintf("%s [megaraid_disk_%02d]", dev_name, m_disknum); + set_info().dev_type = strprintf("megaraid,%d", tgt); +} + +linux_megaraid_device::~linux_megaraid_device() +{ + if (m_fd >= 0) + ::close(m_fd); +} + +smart_device * linux_megaraid_device::autodetect_open() +{ + int report = scsi_debugmode; + + // Open device + if (!open()) + return this; + + // The code below is based on smartd.cpp:SCSIFilterKnown() + if (strcmp(get_req_type(), "megaraid")) + return this; + + // Get INQUIRY + unsigned char req_buff[64] = {0, }; + int req_len = 36; + if (scsiStdInquiry(this, req_buff, req_len)) { + close(); + set_err(EIO, "INQUIRY failed"); + return this; + } + + int avail_len = req_buff[4] + 5; + int len = (avail_len < req_len ? avail_len : req_len); + if (len < 36) + return this; + + if (report) + pout("Got MegaRAID inquiry.. %s\n", req_buff+8); + + // Use INQUIRY to detect type + { + // SAT? + ata_device * newdev = smi()->autodetect_sat_device(this, req_buff, len); + if (newdev) // NOTE: 'this' is now owned by '*newdev' + return newdev; + } + + // Nothing special found + return this; +} + +bool linux_megaraid_device::open() +{ + int mjr; + int report = scsi_debugmode; + + if (sscanf(get_dev_name(), "/dev/bus/%u", &m_hba) == 0) { + if (!linux_smart_device::open()) + return false; + /* Get device HBA */ + struct sg_scsi_id sgid; + if (ioctl(get_fd(), SG_GET_SCSI_ID, &sgid) == 0) { + m_hba = sgid.host_no; + } + else if (ioctl(get_fd(), SCSI_IOCTL_GET_BUS_NUMBER, &m_hba) != 0) { + int err = errno; + linux_smart_device::close(); + return set_err(err, "can't get bus number"); + } // we don't need this device anymore + linux_smart_device::close(); + } + /* Perform mknod of device ioctl node */ + FILE * fp = fopen("/proc/devices", "r"); + if (fp) { + char line[128]; + while (fgets(line, sizeof(line), fp) != NULL) { + int n1 = 0; + if (sscanf(line, "%d megaraid_sas_ioctl%n", &mjr, &n1) == 1 && n1 == 22) { + n1=mknod("/dev/megaraid_sas_ioctl_node", S_IFCHR|0600, makedev(mjr, 0)); + if(report > 0) + pout("Creating /dev/megaraid_sas_ioctl_node = %d\n", n1 >= 0 ? 0 : errno); + if (n1 >= 0 || errno == EEXIST) + break; + } + else if (sscanf(line, "%d megadev%n", &mjr, &n1) == 1 && n1 == 11) { + n1=mknod("/dev/megadev0", S_IFCHR|0600, makedev(mjr, 0)); + if(report > 0) + pout("Creating /dev/megadev0 = %d\n", n1 >= 0 ? 0 : errno); + if (n1 >= 0 || errno == EEXIST) + break; + } + } + fclose(fp); + } + + /* Open Device IOCTL node */ + if ((m_fd = ::open("/dev/megaraid_sas_ioctl_node", O_RDWR)) >= 0) { + pt_cmd = &linux_megaraid_device::megasas_cmd; + } + else if ((m_fd = ::open("/dev/megadev0", O_RDWR)) >= 0) { + pt_cmd = &linux_megaraid_device::megadev_cmd; + } + else { + int err = errno; + linux_smart_device::close(); + return set_err(err, "cannot open /dev/megaraid_sas_ioctl_node or /dev/megadev0"); + } + set_fd(m_fd); + return true; +} + +bool linux_megaraid_device::close() +{ + if (m_fd >= 0) + ::close(m_fd); + m_fd = -1; m_hba = 0; pt_cmd = 0; + set_fd(m_fd); + return true; +} + +bool linux_megaraid_device::scsi_pass_through(scsi_cmnd_io *iop) +{ + int report = scsi_debugmode; + + if (report > 0) { + int k, j; + const unsigned char * ucp = iop->cmnd; + const char * np; + char buff[256]; + const int sz = (int)sizeof(buff); + + np = scsi_get_opcode_name(ucp); + j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>"); + for (k = 0; k < (int)iop->cmnd_len; ++k) + j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]); + if ((report > 1) && + (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) { + int trunc = (iop->dxfer_len > 256) ? 1 : 0; + + snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing " + "data, len=%d%s:\n", (int)iop->dxfer_len, + (trunc ? " [only first 256 bytes shown]" : "")); + dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); + } + else + snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n"); + pout("%s", buff); + } + + // Controller rejects Test Unit Ready + if (iop->cmnd[0] == 0x00) + return true; + + if (iop->cmnd[0] == SAT_ATA_PASSTHROUGH_12 || iop->cmnd[0] == SAT_ATA_PASSTHROUGH_16) { + // Controller does not return ATA output registers in SAT sense data + if (iop->cmnd[2] & (1 << 5)) // chk_cond + return set_err(ENOSYS, "ATA return descriptor not supported by controller firmware"); + } + // SMART WRITE LOG SECTOR causing media errors + if ((iop->cmnd[0] == SAT_ATA_PASSTHROUGH_16 // SAT16 WRITE LOG + && iop->cmnd[14] == ATA_SMART_CMD && iop->cmnd[3]==0 && iop->cmnd[4] == ATA_SMART_WRITE_LOG_SECTOR) || + (iop->cmnd[0] == SAT_ATA_PASSTHROUGH_12 // SAT12 WRITE LOG + && iop->cmnd[9] == ATA_SMART_CMD && iop->cmnd[3] == ATA_SMART_WRITE_LOG_SECTOR)) + { + if(!failuretest_permissive) + return set_err(ENOSYS, "SMART WRITE LOG SECTOR may cause problems, try with -T permissive to force"); + } + if (pt_cmd == NULL) + return false; + return (this->*pt_cmd)(iop->cmnd_len, iop->cmnd, + iop->dxfer_len, iop->dxferp, + iop->max_sense_len, iop->sensep, report, iop->dxfer_dir); +} + +/* Issue passthrough scsi command to PERC5/6 controllers */ +bool linux_megaraid_device::megasas_cmd(int cdbLen, void *cdb, + int dataLen, void *data, + int /*senseLen*/, void * /*sense*/, int /*report*/, int dxfer_dir) +{ + struct megasas_pthru_frame *pthru; + struct megasas_iocpacket uio; + + memset(&uio, 0, sizeof(uio)); + pthru = &uio.frame.pthru; + pthru->cmd = MFI_CMD_PD_SCSI_IO; + pthru->cmd_status = 0xFF; + pthru->scsi_status = 0x0; + pthru->target_id = m_disknum; + pthru->lun = 0; + pthru->cdb_len = cdbLen; + pthru->timeout = 0; + switch (dxfer_dir) { + case DXFER_NONE: + pthru->flags = MFI_FRAME_DIR_NONE; + break; + case DXFER_FROM_DEVICE: + pthru->flags = MFI_FRAME_DIR_READ; + break; + case DXFER_TO_DEVICE: + pthru->flags = MFI_FRAME_DIR_WRITE; + break; + default: + pout("megasas_cmd: bad dxfer_dir\n"); + return set_err(EINVAL, "megasas_cmd: bad dxfer_dir\n"); + } + + if (dataLen > 0) { + pthru->sge_count = 1; + pthru->data_xfer_len = dataLen; + pthru->sgl.sge32[0].phys_addr = (intptr_t)data; + pthru->sgl.sge32[0].length = (uint32_t)dataLen; + } + memcpy(pthru->cdb, cdb, cdbLen); + + uio.host_no = m_hba; + if (dataLen > 0) { + uio.sge_count = 1; + uio.sgl_off = offsetof(struct megasas_pthru_frame, sgl); + uio.sgl[0].iov_base = data; + uio.sgl[0].iov_len = dataLen; + } + + errno = 0; + int rc = ioctl(m_fd, MEGASAS_IOC_FIRMWARE, &uio); + if (pthru->cmd_status || rc != 0) { + if (pthru->cmd_status == 12) { + return set_err(EIO, "megasas_cmd: Device %d does not exist\n", m_disknum); + } + return set_err((errno ? errno : EIO), "megasas_cmd result: %d.%d = %d/%d", + m_hba, m_disknum, errno, + pthru->cmd_status); + } + return true; +} + +/* Issue passthrough scsi commands to PERC2/3/4 controllers */ +bool linux_megaraid_device::megadev_cmd(int cdbLen, void *cdb, + int dataLen, void *data, + int /*senseLen*/, void * /*sense*/, int /*report*/, int /* dir */) +{ + struct uioctl_t uio; + int rc; + + /* Don't issue to the controller */ + if (m_disknum == 7) + return false; + + memset(&uio, 0, sizeof(uio)); + uio.inlen = dataLen; + uio.outlen = dataLen; + + memset(data, 0, dataLen); + uio.ui.fcs.opcode = 0x80; // M_RD_IOCTL_CMD + uio.ui.fcs.adapno = MKADAP(m_hba); + + uio.data.pointer = (uint8_t *)data; + + uio.mbox.cmd = MEGA_MBOXCMD_PASSTHRU; + uio.mbox.xferaddr = (intptr_t)&uio.pthru; + + uio.pthru.ars = 1; + uio.pthru.timeout = 2; + uio.pthru.channel = 0; + uio.pthru.target = m_disknum; + uio.pthru.cdblen = cdbLen; + uio.pthru.reqsenselen = MAX_REQ_SENSE_LEN; + uio.pthru.dataxferaddr = (intptr_t)data; + uio.pthru.dataxferlen = dataLen; + memcpy(uio.pthru.cdb, cdb, cdbLen); + + rc=ioctl(m_fd, MEGAIOCCMD, &uio); + if (uio.pthru.scsistatus || rc != 0) { + return set_err((errno ? errno : EIO), "megadev_cmd result: %d.%d = %d/%d", + m_hba, m_disknum, errno, + uio.pthru.scsistatus); + } + return true; +} + +///////////////////////////////////////////////////////////////////////////// +/// 3SNIC RAID support + +class linux_sssraid_device +: public /* implements */ scsi_device, + public /* extends */ linux_smart_device +{ +public: + linux_sssraid_device(smart_interface *intf, const char *name, + unsigned int eid, unsigned int sid); + + virtual bool scsi_pass_through(scsi_cmnd_io *iop) override; + +private: + unsigned int m_eid; + unsigned int m_sid; + + bool scsi_cmd(scsi_cmnd_io *iop); +}; + +linux_sssraid_device::linux_sssraid_device(smart_interface *intf, + const char *dev_name, unsigned int eid, unsigned int sid) + : smart_device(intf, dev_name, "sssraid", "sssraid"), + linux_smart_device(O_RDWR | O_NONBLOCK), + m_eid(eid), m_sid(sid) +{ + set_info().info_name = strprintf("%s [sssraid_disk_%02d_%02d]", dev_name, eid, sid); + set_info().dev_type = strprintf("sssraid,%d,%d", eid, sid); +} + +bool linux_sssraid_device::scsi_pass_through(scsi_cmnd_io *iop) +{ + int report = scsi_debugmode; + if (report > 0) { + int k, j; + const unsigned char * ucp = iop->cmnd; + const char * np; + char buff[256]; + const int sz = (int)sizeof(buff); + + np = scsi_get_opcode_name(ucp); + j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>"); + for (k = 0; k < (int)iop->cmnd_len; ++k) + j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]); + if ((report > 1) && (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) { + int trunc = (iop->dxfer_len > 256) ? 1 : 0; + + snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing " + "data, len=%d%s:\n", (int)iop->dxfer_len, + (trunc ? " [only first 256 bytes shown]" : "")); + dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); + } + else + snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n"); + pout("%s", buff); + } + + bool r = scsi_cmd(iop); + return r; +} + +/* Issue passthrough scsi commands to sssraid controllers */ +bool linux_sssraid_device::scsi_cmd(scsi_cmnd_io *iop) +{ + struct sg_io_v4 io_hdr_v4{}; + struct cmd_scsi_passthrough scsi_param{}; + unsigned char sense_buff[96] = { 0 }; + struct bsg_ioctl_cmd bsg_param{}; + scsi_param.sense_buffer = sense_buff; + scsi_param.sense_buffer_len = 96; + scsi_param.cdb_len = iop->cmnd_len; + memcpy(scsi_param.cdb, iop->cmnd, iop->cmnd_len); + scsi_param.loc.enc_id = m_eid; + scsi_param.loc.slot_id = m_sid; + + io_hdr_v4.guard = 'Q'; + io_hdr_v4.protocol = BSG_PROTOCOL_SCSI; + io_hdr_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT; + io_hdr_v4.response = (uintptr_t)sense_buff; + io_hdr_v4.max_response_len = ADM_SCSI_CDB_SENSE_MAX_LEN; + io_hdr_v4.request_len = sizeof(struct bsg_ioctl_cmd); + io_hdr_v4.request = (uintptr_t)(&bsg_param); + io_hdr_v4.timeout = BSG_APPEND_TIMEOUT_MS + DEFAULT_CONMMAND_TIMEOUT_MS; + + switch (iop->dxfer_dir) { + case DXFER_NONE: + bsg_param.ioctl_pthru.opcode = ADM_RAID_SET; + break; + case DXFER_FROM_DEVICE: + io_hdr_v4.din_xferp = (uintptr_t)iop->dxferp; + io_hdr_v4.din_xfer_len = iop->dxfer_len; + bsg_param.ioctl_pthru.opcode = ADM_RAID_READ; + break; + case DXFER_TO_DEVICE: + io_hdr_v4.dout_xferp = (uintptr_t)iop->dxferp; + io_hdr_v4.dout_xfer_len = iop->dxfer_len; + bsg_param.ioctl_pthru.opcode = ADM_RAID_WRITE; + break; + default: + pout("scsi_cmd: bad dxfer_dir\n"); + return set_err(EINVAL, "scsi_cmd: bad dxfer_dir\n"); + } + + bsg_param.msgcode = ADM_BSG_MSGCODE_SCSI_PTHRU; + bsg_param.ioctl_pthru.timeout_ms = DEFAULT_CONMMAND_TIMEOUT_MS; + bsg_param.ioctl_pthru.info_1.subopcode = ADM_CMD_SCSI_PASSTHROUGH; + bsg_param.ioctl_pthru.addr = (uintptr_t)iop->dxferp; + bsg_param.ioctl_pthru.data_len = iop->dxfer_len; + + bsg_param.ioctl_pthru.info_0.cdb_len = scsi_param.cdb_len; + bsg_param.ioctl_pthru.sense_addr = (uintptr_t)scsi_param.sense_buffer; + bsg_param.ioctl_pthru.info_0.res_sense_len = scsi_param.sense_buffer_len; + io_hdr_v4.response = (uintptr_t)scsi_param.sense_buffer; + io_hdr_v4.response_len = scsi_param.sense_buffer_len; + bsg_param.ioctl_pthru.info_3.eid = scsi_param.loc.enc_id; + bsg_param.ioctl_pthru.info_3.sid = scsi_param.loc.slot_id; + bsg_param.ioctl_pthru.info_4.did = scsi_param.loc.did; + bsg_param.ioctl_pthru.info_4.did_flag = scsi_param.loc.flag; + + memcpy(&bsg_param.ioctl_pthru.cdw16, scsi_param.cdb, scsi_param.cdb_len); + + int r = ioctl(get_fd(), SG_IO, &io_hdr_v4); + if (r != 0) { + return set_err((errno ? errno : EIO), "scsi_cmd ioctl failed: %d %d,%d", + errno, scsi_param.loc.enc_id, scsi_param.loc.slot_id); + } + + iop->scsi_status = io_hdr_v4.device_status; + + int len = ( iop->max_sense_len < io_hdr_v4.max_response_len ) ? + iop->max_sense_len : io_hdr_v4.max_response_len; + + if (iop->sensep && len > 0) { + memcpy(iop->sensep, reinterpret_cast<void *>(io_hdr_v4.response), len); + iop->resp_sense_len = len; + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +/// CCISS RAID support + +#ifdef HAVE_LINUX_CCISS_IOCTL_H + +class linux_cciss_device +: public /*implements*/ scsi_device, + public /*extends*/ linux_smart_device +{ +public: + linux_cciss_device(smart_interface * intf, const char * name, unsigned char disknum); + + virtual bool scsi_pass_through(scsi_cmnd_io * iop) override; + +private: + unsigned char m_disknum; ///< Disk number. +}; + +linux_cciss_device::linux_cciss_device(smart_interface * intf, + const char * dev_name, unsigned char disknum) +: smart_device(intf, dev_name, "cciss", "cciss"), + linux_smart_device(O_RDWR | O_NONBLOCK), + m_disknum(disknum) +{ + set_info().info_name = strprintf("%s [cciss_disk_%02d]", dev_name, disknum); +} + +bool linux_cciss_device::scsi_pass_through(scsi_cmnd_io * iop) +{ + int status = cciss_io_interface(get_fd(), m_disknum, iop, scsi_debugmode); + if (status < 0) + return set_err(-status); + return true; +} + +#endif // HAVE_LINUX_CCISS_IOCTL_H + +///////////////////////////////////////////////////////////////////////////// +/// AMCC/3ware RAID support + +class linux_escalade_device +: public /*implements*/ ata_device, + public /*extends*/ linux_smart_device +{ +public: + enum escalade_type_t { + AMCC_3WARE_678K, + AMCC_3WARE_678K_CHAR, + AMCC_3WARE_9000_CHAR, + AMCC_3WARE_9700_CHAR + }; + + linux_escalade_device(smart_interface * intf, const char * dev_name, + escalade_type_t escalade_type, int disknum); + + virtual bool open() override; + + virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override; + +private: + escalade_type_t m_escalade_type; ///< Controller type + int m_disknum; ///< Disk number. +}; + +linux_escalade_device::linux_escalade_device(smart_interface * intf, const char * dev_name, + escalade_type_t escalade_type, int disknum) +: smart_device(intf, dev_name, "3ware", "3ware"), + linux_smart_device(O_RDONLY | O_NONBLOCK), + m_escalade_type(escalade_type), m_disknum(disknum) +{ + set_info().info_name = strprintf("%s [3ware_disk_%02d]", dev_name, disknum); +} + +/* This function will setup and fix device nodes for a 3ware controller. */ +#define MAJOR_STRING_LENGTH 3 +#define DEVICE_STRING_LENGTH 32 +#define NODE_STRING_LENGTH 16 +static int setup_3ware_nodes(const char *nodename, const char *driver_name) +{ + int tw_major = 0; + int index = 0; + char majorstring[MAJOR_STRING_LENGTH+1]; + char device_name[DEVICE_STRING_LENGTH+1]; + char nodestring[NODE_STRING_LENGTH]; + struct stat stat_buf; + FILE *file; + int retval = 0; +#ifdef HAVE_LIBSELINUX + security_context_t orig_context = NULL; + security_context_t node_context = NULL; + int selinux_enabled = is_selinux_enabled(); + int selinux_enforced = security_getenforce(); +#endif + + /* First try to open up /proc/devices */ + if (!(file = fopen("/proc/devices", "r"))) { + pout("Error opening /proc/devices to check/create 3ware device nodes\n"); + syserror("fopen"); + return 0; // don't fail here: user might not have /proc ! + } + + /* Attempt to get device major number */ + while (EOF != fscanf(file, "%3s %32s", majorstring, device_name)) { + majorstring[MAJOR_STRING_LENGTH]='\0'; + device_name[DEVICE_STRING_LENGTH]='\0'; + if (!strncmp(device_name, nodename, DEVICE_STRING_LENGTH)) { + tw_major = atoi(majorstring); + break; + } + } + fclose(file); + + /* See if we found a major device number */ + if (!tw_major) { + pout("No major number for /dev/%s listed in /proc/devices. Is the %s driver loaded?\n", nodename, driver_name); + return 2; + } +#ifdef HAVE_LIBSELINUX + /* Prepare a database of contexts for files in /dev + * and save the current context */ + if (selinux_enabled) { + if (matchpathcon_init_prefix(NULL, "/dev") < 0) + pout("Error initializing contexts database for /dev"); + if (getfscreatecon(&orig_context) < 0) { + pout("Error retrieving original SELinux fscreate context"); + if (selinux_enforced) { + matchpathcon_fini(); + return 6; + } + } + } +#endif + /* Now check if nodes are correct */ + for (index=0; index<16; index++) { + snprintf(nodestring, sizeof(nodestring), "/dev/%s%d", nodename, index); +#ifdef HAVE_LIBSELINUX + /* Get context of the node and set it as the default */ + if (selinux_enabled) { + if (matchpathcon(nodestring, S_IRUSR | S_IWUSR, &node_context) < 0) { + pout("Could not retrieve context for %s", nodestring); + if (selinux_enforced) { + retval = 6; + break; + } + } + if (setfscreatecon(node_context) < 0) { + pout ("Error setting default fscreate context"); + if (selinux_enforced) { + retval = 6; + break; + } + } + } +#endif + /* Try to stat the node */ + if ((stat(nodestring, &stat_buf))) { + pout("Node %s does not exist and must be created. Check the udev rules.\n", nodestring); + /* Create a new node if it doesn't exist */ + if (mknod(nodestring, S_IFCHR|0600, makedev(tw_major, index))) { + pout("problem creating 3ware device nodes %s", nodestring); + syserror("mknod"); + retval = 3; + break; + } else { +#ifdef HAVE_LIBSELINUX + if (selinux_enabled && node_context) { + freecon(node_context); + node_context = NULL; + } +#endif + continue; + } + } + + /* See if nodes major and minor numbers are correct */ + if ((tw_major != (int)(major(stat_buf.st_rdev))) || + (index != (int)(minor(stat_buf.st_rdev))) || + (!S_ISCHR(stat_buf.st_mode))) { + pout("Node %s has wrong major/minor number and must be created anew." + " Check the udev rules.\n", nodestring); + /* Delete the old node */ + if (unlink(nodestring)) { + pout("problem unlinking stale 3ware device node %s", nodestring); + syserror("unlink"); + retval = 4; + break; + } + + /* Make a new node */ + if (mknod(nodestring, S_IFCHR|0600, makedev(tw_major, index))) { + pout("problem creating 3ware device nodes %s", nodestring); + syserror("mknod"); + retval = 5; + break; + } + } +#ifdef HAVE_LIBSELINUX + if (selinux_enabled && node_context) { + freecon(node_context); + node_context = NULL; + } +#endif + } + +#ifdef HAVE_LIBSELINUX + if (selinux_enabled) { + if(setfscreatecon(orig_context) < 0) { + pout("Error re-setting original fscreate context"); + if (selinux_enforced) + retval = 6; + } + if(orig_context) + freecon(orig_context); + if(node_context) + freecon(node_context); + matchpathcon_fini(); + } +#endif + return retval; +} + +bool linux_escalade_device::open() +{ + if (m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR || + m_escalade_type == AMCC_3WARE_678K_CHAR) { + // the device nodes for these controllers are dynamically assigned, + // so we need to check that they exist with the correct major + // numbers and if not, create them + const char * node = (m_escalade_type == AMCC_3WARE_9700_CHAR ? "twl" : + m_escalade_type == AMCC_3WARE_9000_CHAR ? "twa" : + "twe" ); + const char * driver = (m_escalade_type == AMCC_3WARE_9700_CHAR ? "3w-sas" : + m_escalade_type == AMCC_3WARE_9000_CHAR ? "3w-9xxx" : + "3w-xxxx" ); + if (setup_3ware_nodes(node, driver)) + return set_err((errno ? errno : ENXIO), "setup_3ware_nodes(\"%s\", \"%s\") failed", node, driver); + } + // Continue with default open + return linux_smart_device::open(); +} + +// TODO: Function no longer useful +//void printwarning(smart_command_set command); + +#ifndef SCSI_IOCTL_SEND_COMMAND +#define SCSI_IOCTL_SEND_COMMAND 1 +#endif + +// 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 linux interface to the 3ware 3w-xxxx 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 15) in the RAID array +// escalade_type indicates the type of controller type, and if scsi or char interface is used +// 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" + +/* 512 is the max payload size: increase if needed */ +#define BUFFER_LEN_678K ( sizeof(TW_Ioctl) ) // 1044 unpacked, 1041 packed +#define BUFFER_LEN_678K_CHAR ( sizeof(TW_New_Ioctl)+512-1 ) // 1539 unpacked, 1536 packed +#define BUFFER_LEN_9000 ( sizeof(TW_Ioctl_Buf_Apache)+512-1 ) // 2051 unpacked, 2048 packed +#define TW_IOCTL_BUFFER_SIZE ( MAX(MAX(BUFFER_LEN_678K, BUFFER_LEN_9000), BUFFER_LEN_678K_CHAR) ) + +bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) +{ + if (!ata_cmd_is_ok(in, + true, // data_out_support + false, // TODO: multi_sector_support + true) // ata_48bit_support + ) + return false; + + // Used by both the SCSI and char interfaces + TW_Passthru *passthru=NULL; + char ioctl_buffer[TW_IOCTL_BUFFER_SIZE]; + + // only used for SCSI device interface + TW_Ioctl *tw_ioctl=NULL; + TW_Output *tw_output=NULL; + + // only used for 6000/7000/8000 char device interface + TW_New_Ioctl *tw_ioctl_char=NULL; + + // only used for 9000 character device interface + TW_Ioctl_Buf_Apache *tw_ioctl_apache=NULL; + + memset(ioctl_buffer, 0, TW_IOCTL_BUFFER_SIZE); + + // TODO: Handle controller differences by different classes + if (m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR) { + tw_ioctl_apache = (TW_Ioctl_Buf_Apache *)ioctl_buffer; + tw_ioctl_apache->driver_command.control_code = TW_IOCTL_FIRMWARE_PASS_THROUGH; + tw_ioctl_apache->driver_command.buffer_length = 512; /* payload size */ + passthru = (TW_Passthru *)&(tw_ioctl_apache->firmware_command.command.oldcommand); + } + else if (m_escalade_type==AMCC_3WARE_678K_CHAR) { + tw_ioctl_char = (TW_New_Ioctl *)ioctl_buffer; + tw_ioctl_char->data_buffer_length = 512; + passthru = (TW_Passthru *)&(tw_ioctl_char->firmware_command); + } + else if (m_escalade_type==AMCC_3WARE_678K) { + tw_ioctl = (TW_Ioctl *)ioctl_buffer; + tw_ioctl->cdb[0] = TW_IOCTL; + tw_ioctl->opcode = TW_ATA_PASSTHRU; + tw_ioctl->input_length = 512; // correct even for non-data commands + tw_ioctl->output_length = 512; // correct even for non-data commands + tw_output = (TW_Output *)tw_ioctl; + passthru = (TW_Passthru *)&(tw_ioctl->input_data); + } + else { + return set_err(ENOSYS, + "Unrecognized escalade_type %d in linux_3ware_command_interface(disk %d)\n" + "Please contact " PACKAGE_BUGREPORT "\n", (int)m_escalade_type, m_disknum); + } + + // Same for (almost) all commands - but some reset below + passthru->byte0.opcode = TW_OP_ATA_PASSTHRU; + passthru->request_id = 0xFF; + passthru->unit = m_disknum; + passthru->status = 0; + passthru->flags = 0x1; + + // Set registers + { + const ata_in_regs_48bit & r = in.in_regs; + passthru->features = r.features_16; + passthru->sector_count = r.sector_count_16; + passthru->sector_num = r.lba_low_16; + passthru->cylinder_lo = r.lba_mid_16; + passthru->cylinder_hi = r.lba_high_16; + passthru->drive_head = r.device; + passthru->command = r.command; + } + + // Is this a command that reads or returns 512 bytes? + // passthru->param values are: + // 0x0 - non data command without TFR write check, + // 0x8 - non data command with TFR write check, + // 0xD - data command that returns data to host from device + // 0xF - data command that writes data from host to device + // passthru->size values are 0x5 for non-data and 0x07 for data + bool readdata = false; + if (in.direction == ata_cmd_in::data_in) { + readdata=true; + passthru->byte0.sgloff = 0x5; + passthru->size = 0x7; // TODO: Other value for multi-sector ? + passthru->param = 0xD; + // For 64-bit to work correctly, up the size of the command packet + // in dwords by 1 to account for the 64-bit single sgl 'address' + // field. Note that this doesn't agree with the typedefs but it's + // right (agree with kernel driver behavior/typedefs). + if ((m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR) + && sizeof(long) == 8) + passthru->size++; + } + else if (in.direction == ata_cmd_in::no_data) { + // Non data command -- but doesn't use large sector + // count register values. + passthru->byte0.sgloff = 0x0; + passthru->size = 0x5; + passthru->param = 0x8; + passthru->sector_count = 0x0; + } + else if (in.direction == ata_cmd_in::data_out) { + if (m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR) + memcpy(tw_ioctl_apache->data_buffer, in.buffer, in.size); + else if (m_escalade_type == AMCC_3WARE_678K_CHAR) + memcpy(tw_ioctl_char->data_buffer, in.buffer, in.size); + else { + // COMMAND NOT SUPPORTED VIA SCSI IOCTL INTERFACE + // memcpy(tw_output->output_data, data, 512); + // printwarning(command); // TODO: Parameter no longer valid + return set_err(ENOTSUP, "DATA OUT not supported for this 3ware controller type"); + } + passthru->byte0.sgloff = 0x5; + passthru->size = 0x7; // TODO: Other value for multi-sector ? + passthru->param = 0xF; // PIO data write + if ((m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR) + && sizeof(long) == 8) + passthru->size++; + } + else + return set_err(EINVAL); + + // Now send the command down through an ioctl() + int ioctlreturn; + if (m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR) + ioctlreturn=ioctl(get_fd(), TW_IOCTL_FIRMWARE_PASS_THROUGH, tw_ioctl_apache); + else if (m_escalade_type==AMCC_3WARE_678K_CHAR) + ioctlreturn=ioctl(get_fd(), TW_CMD_PACKET_WITH_DATA, tw_ioctl_char); + else + ioctlreturn=ioctl(get_fd(), SCSI_IOCTL_SEND_COMMAND, tw_ioctl); + + // Deal with the different error cases + if (ioctlreturn) { + if (AMCC_3WARE_678K==m_escalade_type + && in.in_regs.command==ATA_SMART_CMD + && ( in.in_regs.features == ATA_SMART_AUTO_OFFLINE + || in.in_regs.features == ATA_SMART_AUTOSAVE ) + && in.in_regs.lba_low) { + // error here is probably a kernel driver whose version is too old + // printwarning(command); // TODO: Parameter no longer valid + return set_err(ENOTSUP, "Probably kernel driver too old"); + } + return set_err(EIO); + } + + // The passthru structure is valid after return from an ioctl if: + // - we are using the character interface OR + // - we are using the SCSI interface and this is a NON-READ-DATA command + // For SCSI interface, note that we set passthru to a different + // value after ioctl(). + if (AMCC_3WARE_678K==m_escalade_type) { + if (readdata) + passthru=NULL; + else + passthru=(TW_Passthru *)&(tw_output->output_data); + } + + // See if the ATA command failed. Now that we have returned from + // the ioctl() call, if passthru is valid, then: + // - passthru->status contains the 3ware controller STATUS + // - passthru->command contains the ATA STATUS register + // - passthru->features contains the ATA ERROR register + // + // Check bits 0 (error bit) and 5 (device fault) of the ATA STATUS + // If bit 0 (error bit) is set, then ATA ERROR register is valid. + // While we *might* decode the ATA ERROR register, at the moment it + // doesn't make much sense: we don't care in detail why the error + // happened. + + if (passthru && (passthru->status || (passthru->command & 0x21))) { + return set_err(EIO); + } + + // If this is a read data command, copy data to output buffer + if (readdata) { + if (m_escalade_type == AMCC_3WARE_9700_CHAR || m_escalade_type == AMCC_3WARE_9000_CHAR) + memcpy(in.buffer, tw_ioctl_apache->data_buffer, in.size); + else if (m_escalade_type==AMCC_3WARE_678K_CHAR) + memcpy(in.buffer, tw_ioctl_char->data_buffer, in.size); + else + memcpy(in.buffer, tw_output->output_data, in.size); + } + + // Return register values + if (passthru) { + ata_out_regs_48bit & r = out.out_regs; + r.error = passthru->features; + r.sector_count_16 = passthru->sector_count; + r.lba_low_16 = passthru->sector_num; + r.lba_mid_16 = passthru->cylinder_lo; + r.lba_high_16 = passthru->cylinder_hi; + r.device = passthru->drive_head; + r.status = passthru->command; + } + + // look for nonexistent devices/ports + if ( in.in_regs.command == ATA_IDENTIFY_DEVICE + && !nonempty(in.buffer, in.size)) { + return set_err(ENODEV, "No drive on port %d", m_disknum); + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +/// Areca RAID support + +/////////////////////////////////////////////////////////////////// +// SATA(ATA) device behind Areca RAID Controller +class linux_areca_ata_device +: public /*implements*/ areca_ata_device, + public /*extends*/ linux_smart_device +{ +public: + linux_areca_ata_device(smart_interface * intf, const char * dev_name, int disknum, int encnum = 1); + virtual smart_device * autodetect_open() override; + virtual bool arcmsr_lock() override; + virtual bool arcmsr_unlock() override; + virtual int arcmsr_do_scsi_io(struct scsi_cmnd_io * iop) override; +}; + +/////////////////////////////////////////////////////////////////// +// SAS(SCSI) device behind Areca RAID Controller +class linux_areca_scsi_device +: public /*implements*/ areca_scsi_device, + public /*extends*/ linux_smart_device +{ +public: + linux_areca_scsi_device(smart_interface * intf, const char * dev_name, int disknum, int encnum = 1); + virtual smart_device * autodetect_open() override; + virtual bool arcmsr_lock() override; + virtual bool arcmsr_unlock() override; + virtual int arcmsr_do_scsi_io(struct scsi_cmnd_io * iop) override; +}; + +// Looks in /proc/scsi to suggest correct areca devices +static int find_areca_in_proc() +{ + const char* proc_format_string="host\tchan\tid\tlun\ttype\topens\tqdepth\tbusy\tonline\n"; + + // check data formwat + FILE *fp=fopen("/proc/scsi/sg/device_hdr", "r"); + if (!fp) { + pout("Unable to open /proc/scsi/sg/device_hdr for reading\n"); + return 1; + } + + // get line, compare to format + char linebuf[256]; + linebuf[255]='\0'; + char *out = fgets(linebuf, 256, fp); + fclose(fp); + if (!out) { + pout("Unable to read contents of /proc/scsi/sg/device_hdr\n"); + return 2; + } + + if (strcmp(linebuf, proc_format_string)) { + // wrong format! + // Fix this by comparing only tokens not white space!! + pout("Unexpected format %s in /proc/scsi/sg/device_hdr\n", proc_format_string); + return 3; + } + + // Format is understood, now search for correct device + fp=fopen("/proc/scsi/sg/devices", "r"); + if (!fp) return 1; + int host, chan, id, lun, type, opens, qdepth, busy, online; + int dev=-1; + // search all lines of /proc/scsi/sg/devices + while (9 == fscanf(fp, "%d %d %d %d %d %d %d %d %d", &host, &chan, &id, &lun, &type, &opens, &qdepth, &busy, &online)) { + dev++; + if (id == 16 && type == 3) { + // devices with id=16 and type=3 might be Areca controllers + pout("Device /dev/sg%d appears to be an Areca controller.\n", dev); + } + } + fclose(fp); + return 0; +} + +// Areca RAID Controller(SATA Disk) +linux_areca_ata_device::linux_areca_ata_device(smart_interface * intf, const char * dev_name, int disknum, int encnum) +: smart_device(intf, dev_name, "areca", "areca"), + linux_smart_device(O_RDWR | O_EXCL | O_NONBLOCK) +{ + set_disknum(disknum); + set_encnum(encnum); + set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum); +} + +smart_device * linux_areca_ata_device::autodetect_open() +{ + // autodetect device type + int is_ata = arcmsr_get_dev_type(); + if(is_ata < 0) + { + set_err(EIO); + return this; + } + + if(is_ata == 1) + { + // SATA device + return this; + } + + // SAS device + smart_device_auto_ptr newdev(new linux_areca_scsi_device(smi(), get_dev_name(), get_disknum(), get_encnum())); + close(); + delete this; + newdev->open(); // TODO: Can possibly pass open fd + + return newdev.release(); +} + +int linux_areca_ata_device::arcmsr_do_scsi_io(struct scsi_cmnd_io * iop) +{ + int ioctlreturn = 0; + + if(!is_open()) { + if(!open()){ + find_areca_in_proc(); + } + } + + ioctlreturn = do_normal_scsi_cmnd_io(get_fd(), iop, scsi_debugmode); + if ( ioctlreturn || iop->scsi_status ) + { + // errors found + return -1; + } + + return ioctlreturn; +} + +bool linux_areca_ata_device::arcmsr_lock() +{ + return true; +} + +bool linux_areca_ata_device::arcmsr_unlock() +{ + return true; +} + +// Areca RAID Controller(SAS Device) +linux_areca_scsi_device::linux_areca_scsi_device(smart_interface * intf, const char * dev_name, int disknum, int encnum) +: smart_device(intf, dev_name, "areca", "areca"), + linux_smart_device(O_RDWR | O_EXCL | O_NONBLOCK) +{ + set_disknum(disknum); + set_encnum(encnum); + set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum); +} + +smart_device * linux_areca_scsi_device::autodetect_open() +{ + return this; +} + +int linux_areca_scsi_device::arcmsr_do_scsi_io(struct scsi_cmnd_io * iop) +{ + int ioctlreturn = 0; + + if(!is_open()) { + if(!open()){ + find_areca_in_proc(); + } + } + + ioctlreturn = do_normal_scsi_cmnd_io(get_fd(), iop, scsi_debugmode); + if ( ioctlreturn || iop->scsi_status ) + { + // errors found + return -1; + } + + return ioctlreturn; +} + +bool linux_areca_scsi_device::arcmsr_lock() +{ + return true; +} + +bool linux_areca_scsi_device::arcmsr_unlock() +{ + return true; +} + +///////////////////////////////////////////////////////////////////////////// +/// Marvell support + +class linux_marvell_device +: public /*implements*/ ata_device_with_command_set, + public /*extends*/ linux_smart_device +{ +public: + linux_marvell_device(smart_interface * intf, const char * dev_name, const char * req_type); + +protected: + virtual int ata_command_interface(smart_command_set command, int select, char * data); +}; + +linux_marvell_device::linux_marvell_device(smart_interface * intf, + const char * dev_name, const char * req_type) +: smart_device(intf, dev_name, "marvell", req_type), + linux_smart_device(O_RDONLY | O_NONBLOCK) +{ +} + +int linux_marvell_device::ata_command_interface(smart_command_set command, int select, char * data) +{ + typedef struct { + int inlen; + int outlen; + char cmd[540]; + } mvsata_scsi_cmd; + + int copydata = 0; + mvsata_scsi_cmd smart_command; + unsigned char *buff = (unsigned char *)&smart_command.cmd[6]; + // See struct hd_drive_cmd_hdr in hdreg.h + // buff[0]: ATA COMMAND CODE REGISTER + // buff[1]: ATA SECTOR NUMBER REGISTER + // buff[2]: ATA FEATURES REGISTER + // buff[3]: ATA SECTOR COUNT REGISTER + + // clear out buff. Large enough for HDIO_DRIVE_CMD (4+512 bytes) + memset(&smart_command, 0, sizeof(smart_command)); + smart_command.inlen = 540; + smart_command.outlen = 540; + smart_command.cmd[0] = 0xC; //Vendor-specific code + smart_command.cmd[4] = 6; //command length + + buff[0] = ATA_SMART_CMD; + switch (command){ + case CHECK_POWER_MODE: + buff[0]=ATA_CHECK_POWER_MODE; + break; + case READ_VALUES: + buff[2]=ATA_SMART_READ_VALUES; + copydata=buff[3]=1; + break; + case READ_THRESHOLDS: + buff[2]=ATA_SMART_READ_THRESHOLDS; + copydata=buff[1]=buff[3]=1; + break; + case READ_LOG: + buff[2]=ATA_SMART_READ_LOG_SECTOR; + buff[1]=select; + copydata=buff[3]=1; + break; + case IDENTIFY: + buff[0]=ATA_IDENTIFY_DEVICE; + copydata=buff[3]=1; + break; + case PIDENTIFY: + buff[0]=ATA_IDENTIFY_PACKET_DEVICE; + copydata=buff[3]=1; + break; + case ENABLE: + buff[2]=ATA_SMART_ENABLE; + buff[1]=1; + break; + case DISABLE: + buff[2]=ATA_SMART_DISABLE; + buff[1]=1; + break; + case STATUS: + case STATUS_CHECK: + // this command only says if SMART is working. It could be + // replaced with STATUS_CHECK below. + buff[2] = ATA_SMART_STATUS; + break; + case AUTO_OFFLINE: + buff[2]=ATA_SMART_AUTO_OFFLINE; + buff[3]=select; // YET NOTE - THIS IS A NON-DATA COMMAND!! + break; + case AUTOSAVE: + buff[2]=ATA_SMART_AUTOSAVE; + buff[3]=select; // YET NOTE - THIS IS A NON-DATA COMMAND!! + break; + case IMMEDIATE_OFFLINE: + buff[2]=ATA_SMART_IMMEDIATE_OFFLINE; + buff[1]=select; + break; + default: + pout("Unrecognized command %d in mvsata_os_specific_handler()\n", command); + errno = EINVAL; + return -1; + } + // There are two different types of ioctls(). The HDIO_DRIVE_TASK + // one is this: + // We are now doing the HDIO_DRIVE_CMD type ioctl. + if (ioctl(get_fd(), SCSI_IOCTL_SEND_COMMAND, (void *)&smart_command)) + return -1; + + if (command==CHECK_POWER_MODE) { + // LEON -- CHECK THIS PLEASE. THIS SHOULD BE THE SECTOR COUNT + // REGISTER, AND IT MIGHT BE buff[2] NOT buff[3]. Bruce + data[0]=buff[3]; + return 0; + } + + // Always succeed on a SMART status, as a disk that failed returned + // buff[4]=0xF4, buff[5]=0x2C, i.e. "Bad SMART status" (see below). + if (command == STATUS) + return 0; + //Data returned is starting from 0 offset + if (command == STATUS_CHECK) + { + // Cyl low and Cyl high unchanged means "Good SMART status" + if (buff[4] == 0x4F && buff[5] == 0xC2) + return 0; + // These values mean "Bad SMART status" + if (buff[4] == 0xF4 && buff[5] == 0x2C) + return 1; + // We haven't gotten output that makes sense; print out some debugging info + syserror("Error SMART Status command failed"); + pout("Please get assistance from %s\n",PACKAGE_BUGREPORT); + pout("Register values returned from SMART Status command are:\n"); + pout("CMD =0x%02x\n",(int)buff[0]); + pout("FR =0x%02x\n",(int)buff[1]); + pout("NS =0x%02x\n",(int)buff[2]); + pout("SC =0x%02x\n",(int)buff[3]); + pout("CL =0x%02x\n",(int)buff[4]); + pout("CH =0x%02x\n",(int)buff[5]); + pout("SEL=0x%02x\n",(int)buff[6]); + return -1; + } + + if (copydata) + memcpy(data, buff, 512); + return 0; +} + +///////////////////////////////////////////////////////////////////////////// +/// Highpoint RAID support + +class linux_highpoint_device +: public /*implements*/ ata_device_with_command_set, + public /*extends*/ linux_smart_device +{ +public: + linux_highpoint_device(smart_interface * intf, const char * dev_name, + unsigned char controller, unsigned char channel, unsigned char port); + +protected: + virtual int ata_command_interface(smart_command_set command, int select, char * data); + +private: + unsigned char m_hpt_data[3]; ///< controller/channel/port +}; + +linux_highpoint_device::linux_highpoint_device(smart_interface * intf, const char * dev_name, + unsigned char controller, unsigned char channel, unsigned char port) +: smart_device(intf, dev_name, "hpt", "hpt"), + linux_smart_device(O_RDONLY | O_NONBLOCK) +{ + m_hpt_data[0] = controller; m_hpt_data[1] = channel; m_hpt_data[2] = port; + set_info().info_name = strprintf("%s [hpt_disk_%u/%u/%u]", dev_name, m_hpt_data[0], m_hpt_data[1], m_hpt_data[2]); +} + +// this implementation is derived from ata_command_interface with a header +// packing for highpoint linux driver ioctl interface +// +// ioctl(fd,HPTIO_CTL,buff) +// ^^^^^^^^^ +// +// structure of hpt_buff +// +----+----+----+----+--------------------.....---------------------+ +// | 1 | 2 | 3 | 4 | 5 | +// +----+----+----+----+--------------------.....---------------------+ +// +// 1: The target controller [ int ( 4 Bytes ) ] +// 2: The channel of the target controllee [ int ( 4 Bytes ) ] +// 3: HDIO_ ioctl call [ int ( 4 Bytes ) ] +// available from ${LINUX_KERNEL_SOURCE}/Documentation/ioctl/hdio +// 4: the pmport that disk attached, [ int ( 4 Bytes ) ] +// if no pmport device, set to 1 or leave blank +// 5: data [ void * ( var leangth ) ] +// +#define STRANGE_BUFFER_LENGTH (4+512*0xf8) + +int linux_highpoint_device::ata_command_interface(smart_command_set command, int select, char * data) +{ + unsigned char hpt_buff[4*sizeof(int) + STRANGE_BUFFER_LENGTH]; + unsigned int *hpt = (unsigned int *)hpt_buff; + unsigned char *buff = &hpt_buff[4*sizeof(int)]; + int copydata = 0; + const int HDIO_DRIVE_CMD_OFFSET = 4; + + memset(hpt_buff, 0, 4*sizeof(int) + STRANGE_BUFFER_LENGTH); + hpt[0] = m_hpt_data[0]; // controller id + hpt[1] = m_hpt_data[1]; // channel number + hpt[3] = m_hpt_data[2]; // pmport number + + buff[0]=ATA_SMART_CMD; + switch (command){ + case CHECK_POWER_MODE: + buff[0]=ATA_CHECK_POWER_MODE; + copydata=1; + break; + case READ_VALUES: + buff[2]=ATA_SMART_READ_VALUES; + buff[3]=1; + copydata=512; + break; + case READ_THRESHOLDS: + buff[2]=ATA_SMART_READ_THRESHOLDS; + buff[1]=buff[3]=1; + copydata=512; + break; + case READ_LOG: + buff[2]=ATA_SMART_READ_LOG_SECTOR; + buff[1]=select; + buff[3]=1; + copydata=512; + break; + case WRITE_LOG: + break; + case IDENTIFY: + buff[0]=ATA_IDENTIFY_DEVICE; + buff[3]=1; + copydata=512; + break; + case PIDENTIFY: + buff[0]=ATA_IDENTIFY_PACKET_DEVICE; + buff[3]=1; + copydata=512; + break; + case ENABLE: + buff[2]=ATA_SMART_ENABLE; + buff[1]=1; + break; + case DISABLE: + buff[2]=ATA_SMART_DISABLE; + buff[1]=1; + break; + case STATUS: + buff[2]=ATA_SMART_STATUS; + break; + case AUTO_OFFLINE: + buff[2]=ATA_SMART_AUTO_OFFLINE; + buff[3]=select; + break; + case AUTOSAVE: + buff[2]=ATA_SMART_AUTOSAVE; + buff[3]=select; + break; + case IMMEDIATE_OFFLINE: + buff[2]=ATA_SMART_IMMEDIATE_OFFLINE; + buff[1]=select; + break; + case STATUS_CHECK: + buff[1]=ATA_SMART_STATUS; + break; + default: + pout("Unrecognized command %d in linux_highpoint_command_interface()\n" + "Please contact " PACKAGE_BUGREPORT "\n", command); + errno=ENOSYS; + return -1; + } + + if (command==WRITE_LOG) { + unsigned char task[4*sizeof(int)+sizeof(ide_task_request_t)+512]; + unsigned int *hpt_tf = (unsigned int *)task; + ide_task_request_t *reqtask = (ide_task_request_t *)(&task[4*sizeof(int)]); + task_struct_t *taskfile = (task_struct_t *)reqtask->io_ports; + + memset(task, 0, sizeof(task)); + + hpt_tf[0] = m_hpt_data[0]; // controller id + hpt_tf[1] = m_hpt_data[1]; // channel number + hpt_tf[3] = m_hpt_data[2]; // pmport number + hpt_tf[2] = HDIO_DRIVE_TASKFILE; // real hd ioctl + + taskfile->data = 0; + taskfile->feature = ATA_SMART_WRITE_LOG_SECTOR; + taskfile->sector_count = 1; + taskfile->sector_number = select; + taskfile->low_cylinder = 0x4f; + taskfile->high_cylinder = 0xc2; + taskfile->device_head = 0; + taskfile->command = ATA_SMART_CMD; + + reqtask->data_phase = TASKFILE_OUT; + reqtask->req_cmd = IDE_DRIVE_TASK_OUT; + reqtask->out_size = 512; + reqtask->in_size = 0; + + memcpy(task+sizeof(ide_task_request_t)+4*sizeof(int), data, 512); + + if (ioctl(get_fd(), HPTIO_CTL, task)) + return -1; + + return 0; + } + + if (command==STATUS_CHECK){ + unsigned const char normal_lo=0x4f, normal_hi=0xc2; + unsigned const char failed_lo=0xf4, failed_hi=0x2c; + buff[4]=normal_lo; + buff[5]=normal_hi; + + hpt[2] = HDIO_DRIVE_TASK; + + if (ioctl(get_fd(), HPTIO_CTL, hpt_buff)) + return -1; + + if (buff[4]==normal_lo && buff[5]==normal_hi) + return 0; + + if (buff[4]==failed_lo && buff[5]==failed_hi) + return 1; + + syserror("Error SMART Status command failed"); + pout("Please get assistance from " PACKAGE_URL "\n"); + pout("Register values returned from SMART Status command are:\n"); + pout("CMD=0x%02x\n",(int)buff[0]); + pout("FR =0x%02x\n",(int)buff[1]); + pout("NS =0x%02x\n",(int)buff[2]); + pout("SC =0x%02x\n",(int)buff[3]); + pout("CL =0x%02x\n",(int)buff[4]); + pout("CH =0x%02x\n",(int)buff[5]); + pout("SEL=0x%02x\n",(int)buff[6]); + return -1; + } + +#if 1 + if (command==IDENTIFY || command==PIDENTIFY) { + unsigned char deviceid[4*sizeof(int)+512*sizeof(char)]; + unsigned int *hpt_id = (unsigned int *)deviceid; + + hpt_id[0] = m_hpt_data[0]; // controller id + hpt_id[1] = m_hpt_data[1]; // channel number + hpt_id[3] = m_hpt_data[2]; // pmport number + + hpt_id[2] = HDIO_GET_IDENTITY; + if (!ioctl(get_fd(), HPTIO_CTL, deviceid) && (deviceid[4*sizeof(int)] & 0x8000)) + buff[0]=(command==IDENTIFY)?ATA_IDENTIFY_PACKET_DEVICE:ATA_IDENTIFY_DEVICE; + } +#endif + + hpt[2] = HDIO_DRIVE_CMD; + if ((ioctl(get_fd(), HPTIO_CTL, hpt_buff))) + return -1; + + if (command==CHECK_POWER_MODE) + buff[HDIO_DRIVE_CMD_OFFSET]=buff[2]; + + if (copydata) + memcpy(data, buff+HDIO_DRIVE_CMD_OFFSET, copydata); + + return 0; +} + +#if 0 // TODO: Migrate from 'smart_command_set' to 'ata_in_regs' OR remove the function +// Utility function for printing warnings +void printwarning(smart_command_set command){ + static int printed[4]={0,0,0,0}; + const char* message= + "can not be passed through the 3ware 3w-xxxx driver. This can be fixed by\n" + "applying a simple 3w-xxxx driver patch that can be found here:\n" + PACKAGE_URL "\n" + "Alternatively, upgrade your 3w-xxxx driver to version 1.02.00.037 or greater.\n\n"; + + if (command==AUTO_OFFLINE && !printed[0]) { + printed[0]=1; + pout("The SMART AUTO-OFFLINE ENABLE command (smartmontools -o on option/Directive)\n%s", message); + } + else if (command==AUTOSAVE && !printed[1]) { + printed[1]=1; + pout("The SMART AUTOSAVE ENABLE command (smartmontools -S on option/Directive)\n%s", message); + } + else if (command==STATUS_CHECK && !printed[2]) { + printed[2]=1; + pout("The SMART RETURN STATUS return value (smartmontools -H option/Directive)\n%s", message); + } + else if (command==WRITE_LOG && !printed[3]) { + printed[3]=1; + pout("The SMART WRITE LOG command (smartmontools -t selective) only supported via char /dev/tw[ae] interface\n"); + } + + return; +} +#endif + +///////////////////////////////////////////////////////////////////////////// +/// SCSI open with autodetection support + +smart_device * linux_scsi_device::autodetect_open() +{ + // Open device + if (!open()) + return this; + + // No Autodetection if device type was specified by user + bool sat_only = false; + if (*get_req_type()) { + // Detect SAT if device object was created by scan_smart_devices(). + if (!(m_scanning && !strcmp(get_req_type(), "sat"))) + return this; + sat_only = true; + } + + // The code below is based on smartd.cpp:SCSIFilterKnown() + + // Get INQUIRY + unsigned char req_buff[64] = {0, }; + int req_len = 36; + if (scsiStdInquiry(this, req_buff, req_len)) { + // Marvell controllers fail on a 36 bytes StdInquiry, but 64 suffices + // watch this spot ... other devices could lock up here + req_len = 64; + if (scsiStdInquiry(this, req_buff, req_len)) { + // device doesn't like INQUIRY commands + close(); + set_err(EIO, "INQUIRY failed"); + return this; + } + } + + int avail_len = req_buff[4] + 5; + int len = (avail_len < req_len ? avail_len : req_len); + if (len < 36) { + if (sat_only) { + close(); + set_err(EIO, "INQUIRY too short for SAT"); + } + return this; + } + + // Use INQUIRY to detect type + if (!sat_only) { + + // 3ware ? + if (!memcmp(req_buff + 8, "3ware", 5) || !memcmp(req_buff + 8, "AMCC", 4)) { + close(); + set_err(EINVAL, "AMCC/3ware controller, please try adding '-d 3ware,N',\n" + "you may need to replace %s with /dev/twlN, /dev/twaN or /dev/tweN", get_dev_name()); + return this; + } + + // DELL? + if (!memcmp(req_buff + 8, "DELL PERC", 12) || !memcmp(req_buff + 8, "MegaRAID", 8) + || !memcmp(req_buff + 16, "PERC ", 5) || !memcmp(req_buff + 8, "LSI\0",4) + ) { + close(); + set_err(EINVAL, "DELL or MegaRaid controller, please try adding '-d megaraid,N'"); + return this; + } + + // Marvell ? + if (len >= 42 && !memcmp(req_buff + 36, "MVSATA", 6)) { + //pout("Device %s: using '-d marvell' for ATA disk with Marvell driver\n", get_dev_name()); + close(); + smart_device_auto_ptr newdev( + new linux_marvell_device(smi(), get_dev_name(), get_req_type()) + ); + newdev->open(); // TODO: Can possibly pass open fd + delete this; + return newdev.release(); + } + } + + // SAT or USB ? + { + smart_device * newdev = smi()->autodetect_sat_device(this, req_buff, len); + if (newdev) + // NOTE: 'this' is now owned by '*newdev' + return newdev; + } + + // Nothing special found + + if (sat_only) { + close(); + set_err(EIO, "Not a SAT device"); + } + return this; +} + +///////////////////////////////////////////////////////////////////////////// +/// NVMe support + +class linux_nvme_device +: public /*implements*/ nvme_device, + public /*extends*/ linux_smart_device +{ +public: + linux_nvme_device(smart_interface * intf, const char * dev_name, + const char * req_type, unsigned nsid); + + virtual bool open() override; + + virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) override; +}; + +linux_nvme_device::linux_nvme_device(smart_interface * intf, const char * dev_name, + const char * req_type, unsigned nsid) +: smart_device(intf, dev_name, "nvme", req_type), + nvme_device(nsid), + linux_smart_device(O_RDONLY | O_NONBLOCK) +{ +} + +bool linux_nvme_device::open() +{ + if (!linux_smart_device::open()) + return false; + + if (!get_nsid()) { + // Use actual NSID (/dev/nvmeXnN) if available, + // else use broadcast namespace (/dev/nvmeX) + int nsid = ioctl(get_fd(), NVME_IOCTL_ID, (void*)0); + set_nsid(nsid); + } + + return true; +} + +bool linux_nvme_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) +{ + nvme_passthru_cmd pt; + memset(&pt, 0, sizeof(pt)); + + pt.opcode = in.opcode; + pt.nsid = in.nsid; + pt.addr = (uint64_t)in.buffer; + pt.data_len = in.size; + pt.cdw10 = in.cdw10; + pt.cdw11 = in.cdw11; + pt.cdw12 = in.cdw12; + pt.cdw13 = in.cdw13; + pt.cdw14 = in.cdw14; + pt.cdw15 = in.cdw15; + // Kernel default for NVMe admin commands is 60 seconds + // pt.timeout_ms = 60 * 1000; + + int status = ioctl(get_fd(), NVME_IOCTL_ADMIN_CMD, &pt); + + if (status < 0) + return set_err(errno, "NVME_IOCTL_ADMIN_CMD: %s", strerror(errno)); + + if (status > 0) + return set_nvme_err(out, status); + + out.result = pt.result; + return true; +} + + +////////////////////////////////////////////////////////////////////// +// USB bridge ID detection + +// Read USB ID from /sys file +static bool read_id(const std::string & path, unsigned short & id) +{ + FILE * f = fopen(path.c_str(), "r"); + if (!f) + return false; + int n = -1; + bool ok = (fscanf(f, "%hx%n", &id, &n) == 1 && n == 4); + fclose(f); + return ok; +} + +// Get USB bridge ID for "sdX" or "sgN" +static bool get_usb_id(const char * name, unsigned short & vendor_id, + unsigned short & product_id, unsigned short & version) +{ + // Only "sdX" or "sgN" supported + if (!(name[0] == 's' && (name[1] == 'd' || name[1] == 'g') && !strchr(name, '/'))) + return false; + + // Start search at dir referenced by symlink + // "/sys/block/sdX/device" or + // "/sys/class/scsi_generic/sgN" + // -> "/sys/devices/.../usb*/.../host*/target*/..." + std::string dir = strprintf("/sys/%s/%s%s", + (name[1] == 'd' ? "block" : "class/scsi_generic"), name, + (name[1] == 'd' ? "/device" : "")); + + // Stop search at "/sys/devices" + struct stat st; + if (stat("/sys/devices", &st)) + return false; + ino_t stop_ino = st.st_ino; + + // Search in parent directories until "idVendor" is found, + // fail if "/sys/devices" reached or too many iterations + int cnt = 0; + do { + dir += "/.."; + if (!(++cnt < 10 && !stat(dir.c_str(), &st) && st.st_ino != stop_ino)) + return false; + } while (access((dir + "/idVendor").c_str(), 0)); + + if (scsi_debugmode > 1) { + pout("Found idVendor in: %s\n", dir.c_str()); + char * p = realpath(dir.c_str(), (char *)0); + if (p) { + pout(" realpath: %s\n", p); + free(p); + } + } + + // Read IDs + if (!( read_id(dir + "/idVendor", vendor_id) + && read_id(dir + "/idProduct", product_id) + && read_id(dir + "/bcdDevice", version) )) + return false; + + if (scsi_debugmode > 1) + pout("USB ID = 0x%04x:0x%04x (0x%03x)\n", vendor_id, product_id, version); + return true; +} + +////////////////////////////////////////////////////////////////////// +/// Linux interface + +class linux_smart_interface +: public /*implements*/ smart_interface +{ +public: + virtual std::string get_os_version_str() override; + + virtual std::string get_app_examples(const char * appname) override; + + virtual bool scan_smart_devices(smart_device_list & devlist, + const smart_devtype_list & types, const char * pattern = 0) override; + +protected: + virtual ata_device * get_ata_device(const char * name, const char * type) override; + + virtual scsi_device * get_scsi_device(const char * name, const char * type) override; + + virtual nvme_device * get_nvme_device(const char * name, const char * type, + unsigned nsid) override; + + virtual smart_device * autodetect_smart_device(const char * name) override; + + virtual smart_device * get_custom_smart_device(const char * name, const char * type) override; + + virtual std::string get_valid_custom_dev_types_str() override; + +private: + static constexpr int devxy_to_n_max = 701; // "/dev/sdzz" + static int devxy_to_n(const char * name, bool debug); + + void get_dev_list(smart_device_list & devlist, const char * pattern, + bool scan_scsi, bool (* p_dev_sdxy_seen)[devxy_to_n_max+1], + bool scan_nvme, const char * req_type, bool autodetect); + + bool get_dev_megasas(smart_device_list & devlist); + smart_device * missing_option(const char * opt); + int megasas_dcmd_cmd(int bus_no, uint32_t opcode, void *buf, + size_t bufsize, uint8_t *mbox, size_t mboxlen, uint8_t *statusp); + int megasas_pd_add_list(int bus_no, smart_device_list & devlist); + bool get_dev_sssraid(smart_device_list & devlist); + int sssraid_pd_add_list(int bus_no, smart_device_list & devlist); + int sssraid_pdlist_cmd(int bus_no, uint16_t start_idx, void *buf, size_t bufsize, uint8_t *statusp); +}; + +std::string linux_smart_interface::get_os_version_str() +{ + struct utsname u; + if (!uname(&u)) + return strprintf("%s-linux-%s", u.machine, u.release); + else + return SMARTMONTOOLS_BUILD_HOST; +} + +std::string linux_smart_interface::get_app_examples(const char * appname) +{ + if (!strcmp(appname, "smartctl")) + return smartctl_examples; + return ""; +} + +// "/dev/sdXY" -> 0-devxy_to_n_max +// "/dev/disk/by-id/NAME" -> "../../sdXY" -> 0-devxy_to_n_max +// Other -> -1 +int linux_smart_interface::devxy_to_n(const char * name, bool debug) +{ + const char * xy; + char dest[256]; + if (str_starts_with(name, "/dev/sd")) { + // Assume "/dev/sdXY" + xy = name + sizeof("/dev/sd") - 1; + } + else { + // Assume "/dev/disk/by-id/NAME", check link target + int sz = readlink(name, dest, sizeof(dest)-1); + if (!(0 < sz && sz < (int)sizeof(dest))) + return -1; + dest[sz] = 0; + if (!str_starts_with(dest, "../../sd")) + return -1; + if (debug) + pout("%s -> %s\n", name, dest); + xy = dest + sizeof("../../sd") - 1; + } + + char x = xy[0]; + if (!('a' <= x && x <= 'z')) + return -1; + char y = xy[1]; + if (!y) + // "[a-z]" -> 0-25 + return x - 'a'; + + if (!('a' <= y && y <= 'z' && !xy[2])) + return -1; + // "[a-z][a-z]" -> 26-701 + STATIC_ASSERT((('z' - 'a' + 1) * ('z' - 'a' + 1) + ('z' - 'a')) == devxy_to_n_max); + return (x - 'a' + 1) * ('z' - 'a' + 1) + (y - 'a'); +} + +void linux_smart_interface::get_dev_list(smart_device_list & devlist, + const char * pattern, bool scan_scsi, bool (* p_dev_sdxy_seen)[devxy_to_n_max+1], + bool scan_nvme, const char * req_type, bool autodetect) +{ + bool debug = (ata_debugmode || scsi_debugmode || nvme_debugmode); + + // Use glob to look for any directory entries matching the pattern + glob_t globbuf; + memset(&globbuf, 0, sizeof(globbuf)); + int retglob = glob(pattern, GLOB_ERR, NULL, &globbuf); + if (retglob) { + // glob failed: free memory and return + globfree(&globbuf); + + if (debug) + pout("glob(3) error %d for pattern %s\n", retglob, pattern); + + if (retglob == GLOB_NOSPACE) + throw std::bad_alloc(); + return; + } + + // did we find too many paths? + const int max_pathc = 1024; + int n = (int)globbuf.gl_pathc; + if (n > max_pathc) { + pout("glob(3) found %d > MAX=%d devices matching pattern %s: ignoring %d paths\n", + n, max_pathc, pattern, n - max_pathc); + n = max_pathc; + } + + // now step through the list returned by glob. + for (int i = 0; i < n; i++) { + const char * name = globbuf.gl_pathv[i]; + + if (p_dev_sdxy_seen) { + // Follow "/dev/disk/by-id/*" symlink and check for duplicate "/dev/sdXY" + int dev_n = devxy_to_n(name, debug); + if (!(0 <= dev_n && dev_n <= devxy_to_n_max)) + continue; + if ((*p_dev_sdxy_seen)[dev_n]) { + if (debug) + pout("%s: duplicate, ignored\n", name); + continue; + } + (*p_dev_sdxy_seen)[dev_n] = true; + } + + smart_device * dev; + if (autodetect) { + dev = autodetect_smart_device(name); + if (!dev) + continue; + } + else if (scan_scsi) + dev = new linux_scsi_device(this, name, req_type, true /*scanning*/); + else if (scan_nvme) + dev = new linux_nvme_device(this, name, req_type, 0 /* use default nsid */); + else + dev = new linux_ata_device(this, name, req_type); + devlist.push_back(dev); + } + + // free memory + globfree(&globbuf); +} + +// getting devices from LSI SAS MegaRaid, if available +bool linux_smart_interface::get_dev_megasas(smart_device_list & devlist) +{ + /* Scanning of disks on MegaRaid device */ + /* Perform mknod of device ioctl node */ + int mjr, n1; + char line[128]; + bool scan_megasas = false; + FILE * fp = fopen("/proc/devices", "r"); + if (!fp) + return false; + while (fgets(line, sizeof(line), fp) != NULL) { + n1=0; + if (sscanf(line, "%d megaraid_sas_ioctl%n", &mjr, &n1) == 1 && n1 == 22) { + scan_megasas = true; + n1=mknod("/dev/megaraid_sas_ioctl_node", S_IFCHR|0600, makedev(mjr, 0)); + if(scsi_debugmode > 0) + pout("Creating /dev/megaraid_sas_ioctl_node = %d\n", n1 >= 0 ? 0 : errno); + if (n1 >= 0 || errno == EEXIST) + break; + } + } + fclose(fp); + + if(!scan_megasas) + return false; + + // getting bus numbers with megasas devices + // we are using sysfs to get list of all scsi hosts + DIR * dp = opendir ("/sys/class/scsi_host/"); + if (dp != NULL) + { + struct dirent *ep; + while ((ep = readdir (dp)) != NULL) { + unsigned int host_no = 0; + if (!sscanf(ep->d_name, "host%u", &host_no)) + continue; + /* proc_name should be megaraid_sas */ + char sysfsdir[256]; + snprintf(sysfsdir, sizeof(sysfsdir) - 1, + "/sys/class/scsi_host/host%u/proc_name", host_no); + if((fp = fopen(sysfsdir, "r")) == NULL) + continue; + if(fgets(line, sizeof(line), fp) != NULL && !strncmp(line,"megaraid_sas",12)) { + megasas_pd_add_list(host_no, devlist); + } + fclose(fp); + } + (void) closedir (dp); + } else { /* sysfs not mounted ? */ + for(unsigned i = 0; i <=16; i++) // trying to add devices on first 16 buses + megasas_pd_add_list(i, devlist); + } + return true; +} + +// getting devices from 3SNIC Raid, if available +bool linux_smart_interface::get_dev_sssraid(smart_device_list & devlist) +{ + /* Scanning of disks on sssraid device */ + char line[128]; + FILE * fp = NULL; + + // getting bus numbers with 3snic sas devices + // we are using sysfs to get list of all scsi hosts + DIR * dp = opendir ("/sys/class/scsi_host/"); + if (dp != NULL) + { + struct dirent *ep; + while ((ep = readdir (dp)) != NULL) { + unsigned int host_no = 0; + if (!sscanf(ep->d_name, "host%u", &host_no)) + continue; + /* proc_name should be sssraid */ + char sysfsdir[256]; + snprintf(sysfsdir, sizeof(sysfsdir) - 1, + "/sys/class/scsi_host/host%u/proc_name", host_no); + if((fp = fopen(sysfsdir, "r")) == NULL) + continue; + if(fgets(line, sizeof(line), fp) != NULL && !strncmp(line,"sssraid",7)) { + sssraid_pd_add_list(host_no, devlist); + } + fclose(fp); + } + (void) closedir (dp); + } else { /* sysfs not mounted ? */ + for(unsigned i = 0; i <=16; i++) // trying to add devices on first 16 buses + sssraid_pd_add_list(i, devlist); + } + return true; +} + +bool linux_smart_interface::scan_smart_devices(smart_device_list & devlist, + const smart_devtype_list & types, const char * pattern /*= 0*/) +{ + if (pattern) + return set_err(EINVAL, "DEVICESCAN with pattern not implemented yet"); + + // Scan type list + bool by_id = false; + const char * type_ata = 0, * type_scsi = 0, * type_sat = 0, * type_nvme = 0; + for (unsigned i = 0; i < types.size(); i++) { + const char * type = types[i].c_str(); + if (!strcmp(type, "by-id")) + by_id = true; + else if (!strcmp(type, "ata")) + type_ata = "ata"; + else if (!strcmp(type, "scsi")) + type_scsi = "scsi"; + else if (!strcmp(type, "sat")) + type_sat = "sat"; + else if (!strcmp(type, "nvme")) + type_nvme = "nvme"; + else + return set_err(EINVAL, "Invalid type '%s', valid arguments are: by-id, ata, scsi, sat, nvme", + type); + } + // Use default if no type specified + if (!(type_ata || type_scsi || type_sat || type_nvme)) { + type_ata = type_scsi = type_sat = ""; +#ifdef WITH_NVME_DEVICESCAN // TODO: Remove when NVMe support is no longer EXPERIMENTAL + type_nvme = ""; +#endif + } + + if (type_ata) + get_dev_list(devlist, "/dev/hd[a-t]", false, 0, false, type_ata, false); + + if (type_scsi || type_sat) { + // "sat" detection will be later handled in linux_scsi_device::autodetect_open() + const char * type_scsi_sat = ((type_scsi && type_sat) ? "" // detect both + : (type_scsi ? type_scsi : type_sat)); + bool autodetect = !*type_scsi_sat; // If no type specified, detect USB also + + bool dev_sdxy_seen[devxy_to_n_max+1] = {false, }; + bool (*p_dev_sdxy_seen)[devxy_to_n_max+1] = 0; + if (by_id) { + // Scan unique symlinks first + get_dev_list(devlist, "/dev/disk/by-id/*", true, &dev_sdxy_seen, false, + type_scsi_sat, autodetect); + p_dev_sdxy_seen = &dev_sdxy_seen; // Check for duplicates below + } + + get_dev_list(devlist, "/dev/sd[a-z]", true, p_dev_sdxy_seen, false, type_scsi_sat, autodetect); + get_dev_list(devlist, "/dev/sd[a-z][a-z]", true, p_dev_sdxy_seen, false, type_scsi_sat, autodetect); + + // get device list from the megaraid device + get_dev_megasas(devlist); + // get device list from the sssraid device + get_dev_sssraid(devlist); + } + + if (type_nvme) { + get_dev_list(devlist, "/dev/nvme[0-9]", false, 0, true, type_nvme, false); + get_dev_list(devlist, "/dev/nvme[1-9][0-9]", false, 0, true, type_nvme, false); + } + + return true; +} + +ata_device * linux_smart_interface::get_ata_device(const char * name, const char * type) +{ + return new linux_ata_device(this, name, type); +} + +scsi_device * linux_smart_interface::get_scsi_device(const char * name, const char * type) +{ + return new linux_scsi_device(this, name, type); +} + +nvme_device * linux_smart_interface::get_nvme_device(const char * name, const char * type, + unsigned nsid) +{ + return new linux_nvme_device(this, name, type, nsid); +} + +smart_device * linux_smart_interface::missing_option(const char * opt) +{ + return set_err_np(EINVAL, "requires option '%s'", opt); +} + +int +linux_smart_interface::megasas_dcmd_cmd(int bus_no, uint32_t opcode, void *buf, + size_t bufsize, uint8_t *mbox, size_t mboxlen, uint8_t *statusp) +{ + struct megasas_iocpacket ioc; + + if ((mbox != NULL && (mboxlen == 0 || mboxlen > MFI_MBOX_SIZE)) || + (mbox == NULL && mboxlen != 0)) + { + errno = EINVAL; + return (-1); + } + + memset(&ioc, 0, sizeof(ioc)); + struct megasas_dcmd_frame * dcmd = &ioc.frame.dcmd; + ioc.host_no = bus_no; + if (mbox) + memcpy(dcmd->mbox.w, mbox, mboxlen); + dcmd->cmd = MFI_CMD_DCMD; + dcmd->timeout = 0; + dcmd->flags = 0; + dcmd->data_xfer_len = bufsize; + dcmd->opcode = opcode; + + if (bufsize > 0) { + dcmd->sge_count = 1; + dcmd->data_xfer_len = bufsize; + dcmd->sgl.sge32[0].phys_addr = (intptr_t)buf; + dcmd->sgl.sge32[0].length = (uint32_t)bufsize; + ioc.sge_count = 1; + ioc.sgl_off = offsetof(struct megasas_dcmd_frame, sgl); + ioc.sgl[0].iov_base = buf; + ioc.sgl[0].iov_len = bufsize; + } + + int fd; + if ((fd = ::open("/dev/megaraid_sas_ioctl_node", O_RDWR)) < 0) { + return (errno); + } + + int r = ioctl(fd, MEGASAS_IOC_FIRMWARE, &ioc); + ::close(fd); + if (r < 0) { + return (r); + } + + if (statusp != NULL) + *statusp = dcmd->cmd_status; + else if (dcmd->cmd_status != MFI_STAT_OK) { + fprintf(stderr, "command %x returned error status %x\n", + opcode, dcmd->cmd_status); + errno = EIO; + return (-1); + } + return (0); +} + +int +linux_smart_interface::megasas_pd_add_list(int bus_no, smart_device_list & devlist) +{ + /* + * Keep fetching the list in a loop until we have a large enough + * buffer to hold the entire list. + */ + megasas_pd_list * list = 0; + for (unsigned list_size = 1024; ; ) { + list = reinterpret_cast<megasas_pd_list *>(realloc(list, list_size)); + if (!list) + throw std::bad_alloc(); + memset(list, 0, list_size); + if (megasas_dcmd_cmd(bus_no, MFI_DCMD_PD_GET_LIST, list, list_size, NULL, 0, + NULL) < 0) + { + free(list); + return (-1); + } + if (list->size <= list_size) + break; + list_size = list->size; + } + + // adding all SCSI devices + for (unsigned i = 0; i < list->count; i++) { + if(list->addr[i].scsi_dev_type) + continue; /* non disk device found */ + char line[128]; + snprintf(line, sizeof(line) - 1, "/dev/bus/%d", bus_no); + smart_device * dev = new linux_megaraid_device(this, line, list->addr[i].device_id); + devlist.push_back(dev); + } + free(list); + return (0); +} + +int +linux_smart_interface::sssraid_pdlist_cmd(int bus_no, uint16_t start_idx_param, void *buf, size_t bufsize, uint8_t *statusp) +{ + struct sg_io_v4 io_hdr_v4{}; + unsigned char sense_buff[ADM_SCSI_CDB_SENSE_MAX_LEN] = { 0 }; + struct bsg_ioctl_cmd bsg_param{}; + u8 cmd_param[24] = { 0 }; + + io_hdr_v4.guard = 'Q'; + io_hdr_v4.protocol = BSG_PROTOCOL_SCSI; + io_hdr_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT; + io_hdr_v4.response = (uintptr_t)sense_buff; + io_hdr_v4.max_response_len = ADM_SCSI_CDB_SENSE_MAX_LEN; + io_hdr_v4.request_len = sizeof(struct bsg_ioctl_cmd); + io_hdr_v4.request = (uintptr_t)(&bsg_param); + io_hdr_v4.timeout = BSG_APPEND_TIMEOUT_MS + DEFAULT_CONMMAND_TIMEOUT_MS; + + if (bufsize >0) { + io_hdr_v4.din_xferp = (uintptr_t)buf; + io_hdr_v4.din_xfer_len = bufsize; + } + + bsg_param.msgcode = 0; + bsg_param.ioctl_r64.opcode = ADM_RAID_READ; + bsg_param.ioctl_r64.timeout_ms = DEFAULT_CONMMAND_TIMEOUT_MS; + bsg_param.ioctl_r64.info_0.subopcode = ADM_CMD_SHOW_PDLIST; + bsg_param.ioctl_r64.addr = (uintptr_t)buf; + bsg_param.ioctl_r64.info_1.data_len = bufsize; + bsg_param.ioctl_r64.data_len = bufsize; + bsg_param.ioctl_r64.info_1.param_len = sizeof(struct cmd_pdlist_idx); + + struct cmd_pdlist_idx *p_cmd_param = (struct cmd_pdlist_idx *)(&cmd_param); + p_cmd_param->start_idx = start_idx_param; + p_cmd_param->count = CMD_PDLIST_ONCE_NUM; + memcpy((u32*)&bsg_param.ioctl_r64.cdw10, cmd_param, sizeof(struct cmd_pdlist_idx)); + + int fd; + char line[128]; + snprintf(line, sizeof(line) - 1, "/dev/bsg/sssraid%d", bus_no); + if ((fd = ::open(line, O_RDONLY)) < 0) { + pout("open %s error %d\n", line, fd); + return (errno); + } + + int r = ioctl(fd, SG_IO, &io_hdr_v4); + ::close(fd); + if (r < 0) { + return (r); + } + + if (statusp != NULL) { + *statusp = (io_hdr_v4.transport_status << 0x8) | io_hdr_v4.device_status; + pout("statusp = 0x%x\n", *statusp); + if (*statusp) { + pout("controller returns an error - 0x%x", *statusp); + return (-1); + } + } + return (0); +} + +int +linux_smart_interface::sssraid_pd_add_list(int bus_no, smart_device_list & devlist) +{ + unsigned disk_num = 0; + struct cmd_pdlist_entry pdlist[CMD_PDS_MAX_NUM]{}; + while (disk_num < CMD_PDS_MAX_NUM) { + struct cmd_show_pdlist list{}; + if (sssraid_pdlist_cmd(bus_no, disk_num, &list, sizeof(struct cmd_show_pdlist), NULL) < 0) + { + return (-1); + } + if (list.num == 0) + break; + memcpy(&pdlist[disk_num], list.disks, list.num * sizeof(struct cmd_pdlist_entry)); + disk_num += list.num; + if (list.num < CMD_PDLIST_ONCE_NUM) + break; + } + + // adding all SCSI devices + for (unsigned i = 0; i < disk_num; i++) { + if(!(pdlist[i].interface == ADM_DEVICE_TYPE_SATA || pdlist[i].interface == ADM_DEVICE_TYPE_SAS + || pdlist[i].interface == ADM_DEVICE_TYPE_NVME)) + continue; /* non disk device found */ + char line[128]; + snprintf(line, sizeof(line) - 1, "/dev/bsg/sssraid%d", bus_no); + smart_device * dev = new linux_sssraid_device(this, line, (unsigned int)pdlist[i].enc_id, (unsigned int)pdlist[i].slot_id); + devlist.push_back(dev); + } + return (0); +} + +// Return kernel release as integer ("2.6.31" -> 206031) +static unsigned get_kernel_release() +{ + struct utsname u; + if (uname(&u)) + return 0; + unsigned x = 0, y = 0, z = 0; + if (!(sscanf(u.release, "%u.%u.%u", &x, &y, &z) == 3 + && x < 100 && y < 100 && z < 1000 )) + return 0; + return x * 100000 + y * 1000 + z; +} + +// Check for SCSI host proc_name "hpsa" and HPSA raid_level +static bool is_hpsa_in_raid_mode(const char * name) +{ + char path[128]; + snprintf(path, sizeof(path), "/sys/block/%s/device", name); + char * syshostpath = realpath(path, (char *)0); + if (!syshostpath) + return false; + + char * syshost = strrchr(syshostpath, '/'); + if (!syshost) { + free(syshostpath); + return false; + } + + char * hostsep = strchr(++syshost, ':'); + if (hostsep) + *hostsep = 0; + + snprintf(path, sizeof(path), "/sys/class/scsi_host/host%s/proc_name", syshost); + free(syshostpath); + int fd = open(path, O_RDONLY); + if (fd < 0) + return false; + + char proc_name[32]; + ssize_t n = read(fd, proc_name, sizeof(proc_name) - 1); + close(fd); + if (n < 4) + return false; + + proc_name[n] = 0; + if (proc_name[n - 1] == '\n') + proc_name[n - 1] = 0; + + if (scsi_debugmode > 1) + pout("%s -> %s: \"%s\"\n", name, path, proc_name); + + if (strcmp(proc_name, "hpsa")) + return false; + + // See: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/scsi/hpsa.c?id=6417f03132a6952cd17ddd8eaddbac92b61b17e0#n693 + snprintf(path, sizeof(path), "/sys/block/%s/device/raid_level", name); + fd = open(path, O_RDONLY); + if (fd < 0) + return false; + + char raid_level[4]; + n = read(fd, raid_level, sizeof(raid_level) - 1); + close(fd); + if (n < 3) + return false; + raid_level[n] = 0; + + if (strcmp(raid_level, "N/A")) + return true; + + return false; +} + +// Guess device type (ata or scsi) based on device name (Linux +// specific) SCSI device name in linux can be sd, sr, scd, st, nst, +// osst, nosst and sg. +smart_device * linux_smart_interface::autodetect_smart_device(const char * name) +{ + const char * test_name = name; + + // Dereference symlinks + struct stat st; + std::string pathbuf; + if (!lstat(name, &st) && S_ISLNK(st.st_mode)) { + char * p = realpath(name, (char *)0); + if (p) { + pathbuf = p; + free(p); + test_name = pathbuf.c_str(); + } + } + + // Remove the leading /dev/... if it's there + static const char dev_prefix[] = "/dev/"; + if (str_starts_with(test_name, dev_prefix)) + test_name += strlen(dev_prefix); + + // form /dev/h* or h* + if (str_starts_with(test_name, "h")) + return new linux_ata_device(this, name, ""); + + // form /dev/ide/* or ide/* + if (str_starts_with(test_name, "ide/")) + return new linux_ata_device(this, name, ""); + + // form /dev/s* or s* + if (str_starts_with(test_name, "s")) { + + // Try to detect possible USB->(S)ATA bridge + unsigned short vendor_id = 0, product_id = 0, version = 0; + if (get_usb_id(test_name, vendor_id, product_id, version)) { + const char * usbtype = get_usb_dev_type_by_id(vendor_id, product_id, version); + if (!usbtype) + return nullptr; + + // Kernels before 2.6.29 do not support the sense data length + // required for SAT ATA PASS-THROUGH(16) + if (!strcmp(usbtype, "sat") && get_kernel_release() < 206029) + usbtype = "sat,12"; + + // Return SAT/USB device for this type + // (Note: linux_scsi_device::autodetect_open() will not be called in this case) + return get_scsi_passthrough_device(usbtype, new linux_scsi_device(this, name, "")); + } + + // Fail if hpsa driver and device is using RAID + if (is_hpsa_in_raid_mode(test_name)) + return missing_option("-d cciss,N"); + + // No USB bridge or hpsa driver found, assume regular SCSI device + return new linux_scsi_device(this, name, ""); + } + + // form /dev/scsi/* or scsi/* + if (str_starts_with(test_name, "scsi/")) + return new linux_scsi_device(this, name, ""); + + // form /dev/bsg/* or bsg/* + if (str_starts_with(test_name, "bsg/")) + return new linux_scsi_device(this, name, ""); + + // form /dev/ns* or ns* + if (str_starts_with(test_name, "ns")) + return new linux_scsi_device(this, name, ""); + + // form /dev/os* or os* + if (str_starts_with(test_name, "os")) + return new linux_scsi_device(this, name, ""); + + // form /dev/nos* or nos* + if (str_starts_with(test_name, "nos")) + return new linux_scsi_device(this, name, ""); + + // form /dev/nvme* or nvme* + if (str_starts_with(test_name, "nvme")) + return new linux_nvme_device(this, name, "", 0 /* use default nsid */); + + // form /dev/tw[ael]* or tw[ael]* + if (str_starts_with(test_name, "tw") && strchr("ael", test_name[2])) + return missing_option("-d 3ware,N"); + + // form /dev/cciss/* or cciss/* + if (str_starts_with(test_name, "cciss/")) + return missing_option("-d cciss,N"); + + // we failed to recognize any of the forms + return nullptr; +} + +smart_device * linux_smart_interface::get_custom_smart_device(const char * name, const char * type) +{ + // Marvell ? + // TODO: Remove after smartmontools 7.4 + if (!strcmp(type, "marvell")) + return set_err_np(EINVAL, + "The device type 'marvell' is deprecated and will be removed in a\n" + "future version of smartmontools. If you still need this device type, please\n" + "use '-d marvell,force' and inform " PACKAGE_BUGREPORT ); + if (!strcmp(type, "marvell,force")) + return new linux_marvell_device(this, name, type); + + // 3Ware ? + int disknum = -1, n1 = -1, n2 = -1; + if (sscanf(type, "3ware,%n%d%n", &n1, &disknum, &n2) == 1 || n1 == 6) { + if (n2 != (int)strlen(type)) + return set_err_np(EINVAL, "Option -d 3ware,N requires N to be a non-negative integer"); + if (!(0 <= disknum && disknum <= 127)) + return set_err_np(EINVAL, "Option -d 3ware,N (N=%d) must have 0 <= N <= 127", disknum); + + if (!strncmp(name, "/dev/twl", 8)) + return new linux_escalade_device(this, name, linux_escalade_device::AMCC_3WARE_9700_CHAR, disknum); + else if (!strncmp(name, "/dev/twa", 8)) + return new linux_escalade_device(this, name, linux_escalade_device::AMCC_3WARE_9000_CHAR, disknum); + else if (!strncmp(name, "/dev/twe", 8)) + return new linux_escalade_device(this, name, linux_escalade_device::AMCC_3WARE_678K_CHAR, disknum); + else + return new linux_escalade_device(this, name, linux_escalade_device::AMCC_3WARE_678K, disknum); + } + + // Areca? + disknum = n1 = n2 = -1; + int encnum = 1; + if (sscanf(type, "areca,%n%d/%d%n", &n1, &disknum, &encnum, &n2) >= 1 || n1 == 6) { + if (!(1 <= disknum && disknum <= 128)) + return set_err_np(EINVAL, "Option -d areca,N/E (N=%d) must have 1 <= N <= 128", disknum); + if (!(1 <= encnum && encnum <= 8)) + return set_err_np(EINVAL, "Option -d areca,N/E (E=%d) must have 1 <= E <= 8", encnum); + return new linux_areca_ata_device(this, name, disknum, encnum); + } + + // Highpoint ? + int controller = -1, channel = -1; disknum = 1; + n1 = n2 = -1; int n3 = -1; + if (sscanf(type, "hpt,%n%d/%d%n/%d%n", &n1, &controller, &channel, &n2, &disknum, &n3) >= 2 || n1 == 4) { + int len = strlen(type); + if (!(n2 == len || n3 == len)) + return set_err_np(EINVAL, "Option '-d hpt,L/M/N' supports 2-3 items"); + if (!(1 <= controller && controller <= 8)) + return set_err_np(EINVAL, "Option '-d hpt,L/M/N' invalid controller id L supplied"); + if (!(1 <= channel && channel <= 128)) + return set_err_np(EINVAL, "Option '-d hpt,L/M/N' invalid channel number M supplied"); + if (!(1 <= disknum && disknum <= 15)) + return set_err_np(EINVAL, "Option '-d hpt,L/M/N' invalid pmport number N supplied"); + return new linux_highpoint_device(this, name, controller, channel, disknum); + } + +#ifdef HAVE_LINUX_CCISS_IOCTL_H + // CCISS ? + disknum = n1 = n2 = -1; + if (sscanf(type, "cciss,%n%d%n", &n1, &disknum, &n2) == 1 || n1 == 6) { + if (n2 != (int)strlen(type)) + return set_err_np(EINVAL, "Option -d cciss,N requires N to be a non-negative integer"); + if (!(0 <= disknum && disknum <= 127)) + return set_err_np(EINVAL, "Option -d cciss,N (N=%d) must have 0 <= N <= 127", disknum); + return get_sat_device("sat,auto", new linux_cciss_device(this, name, disknum)); + } +#endif // HAVE_LINUX_CCISS_IOCTL_H + + // MegaRAID ? + if (sscanf(type, "megaraid,%d", &disknum) == 1) { + return new linux_megaraid_device(this, name, disknum); + } + + // SSSRAID + unsigned eid = -1, sid = -1; + if (sscanf(type, "sssraid,%u,%u", &eid, &sid) == 2) { + return get_sat_device("sat,auto", new linux_sssraid_device(this, name, eid, sid)); + } + + //aacraid? + unsigned host, chan, device; + if (sscanf(type, "aacraid,%u,%u,%u", &host, &chan, &device) == 3) { + //return new linux_aacraid_device(this,name,channel,device); + return get_sat_device("sat,auto", + new linux_aacraid_device(this, name, host, chan, device)); + + } + + return nullptr; +} + +std::string linux_smart_interface::get_valid_custom_dev_types_str() +{ + return "areca,N/E, 3ware,N, hpt,L/M/N, megaraid,N, aacraid,H,L,ID, sssraid,E,S" +#ifdef HAVE_LINUX_CCISS_IOCTL_H + ", cciss,N" +#endif + ; +} + +} // namespace + +///////////////////////////////////////////////////////////////////////////// +/// Initialize platform interface and register with smi() + +void smart_interface::init() +{ + static os_linux::linux_smart_interface the_interface; + smart_interface::set(&the_interface); +} |