diff options
Diffstat (limited to '')
-rw-r--r-- | os_freebsd.cpp | 2536 |
1 files changed, 2536 insertions, 0 deletions
diff --git a/os_freebsd.cpp b/os_freebsd.cpp new file mode 100644 index 0000000..333f7c7 --- /dev/null +++ b/os_freebsd.cpp @@ -0,0 +1,2536 @@ +/* + * os_freebsd.cpp + * + * Home page of code is: https://www.smartmontools.org + * + * Copyright (C) 2003-10 Eduard Martinescu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/param.h> +#include <sys/endian.h> +#include <stdio.h> +#include <sys/types.h> +#include <dirent.h> +#include <fcntl.h> +#include <err.h> +#include <errno.h> +#include <camlib.h> +#include <cam/scsi/scsi_message.h> +#include <cam/scsi/scsi_pass.h> +#if defined(__DragonFly__) +#include <sys/nata.h> +#else +#include <sys/ata.h> +#endif +#include <sys/stat.h> +#include <unistd.h> +#include <sys/uio.h> +#include <glob.h> +#include <stddef.h> +#include <paths.h> +#include <sys/utsname.h> + +#include "config.h" + +// set by /usr/include/sys/ata.h, suppress warning +#undef ATA_READ_LOG_EXT +#include "atacmds.h" +#include "scsicmds.h" +#include "cciss.h" +#include "utility.h" +#include "os_freebsd.h" + +#include "dev_interface.h" +#include "dev_ata_cmd_set.h" +#include "dev_areca.h" + +#define USBDEV "/dev/usb" +#if defined(__FreeBSD_version) + +// This way we define one variable for the GNU/kFreeBSD and FreeBSD +#define FREEBSDVER __FreeBSD_version +#else +#define FREEBSDVER __FreeBSD_kernel_version +#endif + +#if (FREEBSDVER >= 800000) +#include <libusb20_desc.h> +#include <libusb20.h> +#elif defined(__DragonFly__) +#include <bus/usb/usb.h> +#include <bus/usb/usbhid.h> +#else +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> +#endif + +// based on "/sys/dev/nvme/nvme.h" from FreeBSD kernel sources +#include "freebsd_nvme_ioctl.h" // NVME_PASSTHROUGH_CMD, nvme_completion_is_error + +#define CONTROLLER_3WARE_9000_CHAR 0x01 +#define CONTROLLER_3WARE_678K_CHAR 0x02 + +#ifndef PATHINQ_SETTINGS_SIZE +#define PATHINQ_SETTINGS_SIZE 128 +#endif + +const char *os_XXXX_c_cvsid="$Id: os_freebsd.cpp 5468 2023-03-14 20:01:29Z chrfranke $" \ +ATACMDS_H_CVSID CCISS_H_CVSID CONFIG_H_CVSID OS_FREEBSD_H_CVSID SCSICMDS_H_CVSID UTILITY_H_CVSID; + +#define NO_RETURN 0 +#define BAD_SMART 1 +#define NO_DISK_3WARE 2 +#define BAD_KERNEL 3 +#define MAX_MSG 3 + +// Utility function for printing warnings +void printwarning(int msgNo, const char* extra) { + + if (msgNo >= 0 && msgNo <= MAX_MSG) { + static int printed[] = {0,0,0,0}; + if (!printed[msgNo]) { + + static const char* message[]={ + "The SMART RETURN STATUS return value (smartmontools -H option/Directive)\n can not be retrieved with this version of ATAng, please do not rely on this value\nYou should update to at least 5.2\n", + + "Error SMART Status command failed\nPlease get assistance from \n" PACKAGE_URL "\nRegister values returned from SMART Status command are:\n", + + "You must specify a DISK # for 3ware drives with -d 3ware,<n> where <n> begins with 1 for first disk drive\n", + + "ATA support is not provided for this kernel version. Please ugrade to a recent 5-CURRENT kernel (post 09/01/2003 or so)\n" + }; + + printed[msgNo] = 1; + pout("%s", message[msgNo]); + if (extra) + pout("%s",extra); + } + } + return; +} + +// Interface to ATA devices behind 3ware escalade RAID controller cards. See os_linux.c + +#define BUFFER_LEN_678K_CHAR ( sizeof(struct twe_usercommand) ) // 520 +#define BUFFER_LEN_9000_CHAR ( sizeof(TW_OSLI_IOCTL_NO_DATA_BUF) + sizeof(TWE_Command) ) // 2048 +#define TW_IOCTL_BUFFER_SIZE ( MAX(BUFFER_LEN_678K_CHAR, BUFFER_LEN_9000_CHAR) ) + +#ifndef ATA_DEVICE +#define ATA_DEVICE "/dev/ata" +#endif + +#define ARGUSED(x) ((void)(x)) + +extern unsigned char failuretest_permissive; + +///////////////////////////////////////////////////////////////////////////// + +namespace os_freebsd { // No need to publish anything, name provided for Doxygen + +///////////////////////////////////////////////////////////////////////////// +/// Implement shared open/close routines with old functions. + +class freebsd_smart_device +: virtual public /*implements*/ smart_device +{ +public: + explicit freebsd_smart_device() + : smart_device(never_called), + m_fd(-1) { } + + virtual ~freebsd_smart_device(); + + virtual bool is_open() const; + + virtual bool open(); + + virtual bool close(); + +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. +}; + +#ifdef __GLIBC__ +static inline void * reallocf(void *ptr, size_t size) { + void *rv = realloc(ptr, size); + if((rv == NULL) && (size != 0)) + free(ptr); + return rv; + } +#endif + +freebsd_smart_device::~freebsd_smart_device() +{ + if (m_fd >= 0) + os_freebsd::freebsd_smart_device::close(); +} + +// migration from the old_style +unsigned char m_controller_type; +unsigned char m_controller_port; + +// examples for smartctl +static const char smartctl_examples[] = + "=================================================== SMARTCTL EXAMPLES =====\n\n" + " smartctl -a /dev/ad0 (Prints all SMART information)\n\n" + " smartctl --smart=on --offlineauto=on --saveauto=on /dev/ad0\n" + " (Enables SMART on first disk)\n\n" + " smartctl -t long /dev/ad0 (Executes extended disk self-test)\n\n" + " smartctl --attributes --log=selftest --quietmode=errorsonly /dev/ad0\n" + " (Prints Self-Test & Attribute errors)\n" + " (Prints Self-Test & Attribute errors)\n\n" + " smartctl -a --device=3ware,2 /dev/twa0\n" + " smartctl -a --device=3ware,2 /dev/twe0\n" + " smartctl -a --device=3ware,2 /dev/tws0\n" + " (Prints all SMART information for ATA disk on\n" + " third port of first 3ware RAID controller)\n" + " smartctl -a --device=cciss,0 /dev/ciss0\n" + " (Prints all SMART information for first disk \n" + " on Common Interface for SCSI-3 Support driver)\n" + " smartctl -a --device=areca,3/1 /dev/arcmsr0\n" + " (Prints all SMART information for 3rd disk in the 1st enclosure \n" + " on first ARECA RAID controller)\n" + " smartctl -a --device=megaraid,3 /dev/mrsas0\n" + " (Prints all SMART information for 3rd disk\n" + " on first LSI RAID controller)\n" + + ; + +bool freebsd_smart_device::is_open() const +{ + return (m_fd >= 0); +} + + +bool freebsd_smart_device::open() +{ + const char *dev = get_dev_name(); + if ((m_fd = ::open(dev,O_RDONLY))<0) { + set_err(errno); + return false; + } + return true; +} + +bool freebsd_smart_device::close() +{ + int failed = 0; + // close device, if open + if (is_open()) + failed=::close(get_fd()); + + set_fd(-1); + + if(failed) return false; + else return true; +} + +///////////////////////////////////////////////////////////////////////////// +/// Implement standard ATA support + +class freebsd_ata_device +: public /*implements*/ ata_device, + public /*extends*/ freebsd_smart_device +{ +public: + freebsd_ata_device(smart_interface * intf, const char * dev_name, const char * req_type); + virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override; + +protected: + virtual int do_cmd(struct ata_ioc_request* request, bool is_48bit_cmd); +}; + +freebsd_ata_device::freebsd_ata_device(smart_interface * intf, const char * dev_name, const char * req_type) +: smart_device(intf, dev_name, "ata", req_type), + freebsd_smart_device() +{ +} + +int freebsd_ata_device::do_cmd( struct ata_ioc_request* request, bool is_48bit_cmd) +{ + int fd = get_fd(), ret; + ARGUSED(is_48bit_cmd); // no support for 48 bit commands in the IOCATAREQUEST + ret = ioctl(fd, IOCATAREQUEST, request); + if (ret) set_err(errno); + return ret; +} + + + +bool freebsd_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) +{ + bool ata_48bit = false; // no ata_48bit_support via IOCATAREQUEST + if(!strcmp("atacam",get_dev_type())) // enable for atacam interface + ata_48bit = true; + + if (!ata_cmd_is_ok(in, + true, // data_out_support + true, // multi_sector_support + ata_48bit) + ) { + set_err(ENOSYS, "48-bit ATA commands not implemented for legacy controllers"); + return false; + } + + struct ata_ioc_request request; + memset(&request, 0, sizeof(struct ata_ioc_request)); + + request.timeout=SCSI_TIMEOUT_DEFAULT; + request.u.ata.command=in.in_regs.command; + request.u.ata.feature=in.in_regs.features; + + request.u.ata.count = in.in_regs.sector_count_16; + request.u.ata.lba = in.in_regs.lba_48; + + switch (in.direction) { + case ata_cmd_in::no_data: + request.flags=ATA_CMD_CONTROL; + break; + case ata_cmd_in::data_in: + request.flags=ATA_CMD_READ | ATA_CMD_CONTROL; + request.data=(char *)in.buffer; + request.count=in.size; + break; + case ata_cmd_in::data_out: + request.flags=ATA_CMD_WRITE | ATA_CMD_CONTROL; + request.data=(char *)in.buffer; + request.count=in.size; + break; + default: + return set_err(ENOSYS); + } + + clear_err(); + errno = 0; + if (do_cmd(&request, in.in_regs.is_48bit_cmd())) + return false; + if (request.error) + return set_err(EIO, "request failed, error code 0x%02x", request.error); + + out.out_regs.error = request.error; + out.out_regs.sector_count_16 = request.u.ata.count; + out.out_regs.lba_48 = request.u.ata.lba; + + return true; +} + +#if FREEBSDVER > 800100 +class freebsd_atacam_device : public freebsd_ata_device +{ +public: + freebsd_atacam_device(smart_interface * intf, const char * dev_name, const char * req_type) + : smart_device(intf, dev_name, "atacam", req_type), freebsd_ata_device(intf, dev_name, req_type) + {} + + virtual bool open(); + virtual bool close(); + +protected: + int m_fd; + struct cam_device *m_camdev; + + virtual int do_cmd( struct ata_ioc_request* request , bool is_48bit_cmd); +}; + +bool freebsd_atacam_device::open(){ + const char *dev = get_dev_name(); + + if ((m_camdev = cam_open_device(dev, O_RDWR)) == NULL) { + set_err(errno); + return false; + } + set_fd(m_camdev->fd); + return true; +} + +bool freebsd_atacam_device::close(){ + cam_close_device(m_camdev); + set_fd(-1); + return true; +} + +int freebsd_atacam_device::do_cmd( struct ata_ioc_request* request, bool is_48bit_cmd) +{ + union ccb ccb; + int camflags; + + // 48bit commands are broken in ATACAM before r242422/HEAD + // and may cause system hang + // First version with working support should be FreeBSD 9.2.0/RELEASE + +#if (FREEBSDVER < 902001) + if(!strcmp("ata",m_camdev->sim_name) && is_48bit_cmd) { + set_err(ENOSYS, "48-bit ATA commands not implemented for legacy controllers"); + return -1; + } +#endif + + memset(&ccb, 0, sizeof(ccb)); + + if (request->count == 0) + camflags = CAM_DIR_NONE; + else if (request->flags & ATA_CMD_READ) + camflags = CAM_DIR_IN; + else + camflags = CAM_DIR_OUT; + + cam_fill_ataio(&ccb.ataio, + 0, + NULL, + camflags, + MSG_SIMPLE_Q_TAG, + (u_int8_t*)request->data, + request->count, + request->timeout * 1000); // timeout in seconds + + ccb.ataio.cmd.flags = CAM_ATAIO_NEEDRESULT | + (is_48bit_cmd ? CAM_ATAIO_48BIT : 0); + // ata_28bit_cmd + ccb.ataio.cmd.command = request->u.ata.command; + ccb.ataio.cmd.features = request->u.ata.feature; + ccb.ataio.cmd.lba_low = request->u.ata.lba; + ccb.ataio.cmd.lba_mid = request->u.ata.lba >> 8; + ccb.ataio.cmd.lba_high = request->u.ata.lba >> 16; + // ata_48bit cmd + ccb.ataio.cmd.lba_low_exp = request->u.ata.lba >> 24; + ccb.ataio.cmd.lba_mid_exp = request->u.ata.lba >> 32; + ccb.ataio.cmd.lba_high_exp = request->u.ata.lba >> 40; + ccb.ataio.cmd.device = 0x40 | ((request->u.ata.lba >> 24) & 0x0f); + ccb.ataio.cmd.sector_count = request->u.ata.count; + ccb.ataio.cmd.sector_count_exp = request->u.ata.count >> 8;; + + ccb.ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (cam_send_ccb(m_camdev, &ccb) < 0) { + set_err(EIO, "cam_send_ccb failed"); + return -1; + } + + if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + if(scsi_debugmode > 0) + cam_error_print(m_camdev, &ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); + set_err(EIO); + return -1; + } + + request->u.ata.lba = + ((u_int64_t)(ccb.ataio.res.lba_low)) | + ((u_int64_t)(ccb.ataio.res.lba_mid) << 8) | + ((u_int64_t)(ccb.ataio.res.lba_high) << 16) | + ((u_int64_t)(ccb.ataio.res.lba_low_exp) << 24) | + ((u_int64_t)(ccb.ataio.res.lba_mid_exp) << 32) | + ((u_int64_t)(ccb.ataio.res.lba_high_exp) << 40); + + request->u.ata.count = ccb.ataio.res.sector_count | (ccb.ataio.res.sector_count_exp << 8); + request->error = ccb.ataio.res.error; + + return 0; +} + +#endif + +///////////////////////////////////////////////////////////////////////////// +/// NVMe support + +class freebsd_nvme_device +: public /*implements*/ nvme_device, + public /*extends*/ freebsd_smart_device +{ +public: + freebsd_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; +}; + +freebsd_nvme_device::freebsd_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), + freebsd_smart_device() +{ +} + +bool freebsd_nvme_device::open() +{ + const char *dev = get_dev_name(); + if (!strnstr(dev, NVME_CTRLR_PREFIX, strlen(NVME_CTRLR_PREFIX))) { + set_err(EINVAL, "NVMe controller controller/namespace ids must begin with '%s'", + NVME_CTRLR_PREFIX); + return false; + } + + int nsid = -1, ctrlid = -1; + char tmp; + + if(sscanf(dev, NVME_CTRLR_PREFIX"%d%c", &ctrlid, &tmp) == 1) + { + if(ctrlid < 0) { + set_err(EINVAL, "Invalid NVMe controller number"); + return false; + } + nsid = 0xFFFFFFFF; // broadcast id + } + else if (sscanf(dev, NVME_CTRLR_PREFIX"%d" NVME_NS_PREFIX "%d%c", + &ctrlid, &nsid, &tmp) == 2) + { + if(ctrlid < 0 || nsid < 0) { + set_err(EINVAL, "Invalid NVMe controller/namespace number"); + return false; + } + } + else { + set_err(EINVAL, "Invalid NVMe controller/namespace syntax"); + return false; + } + + // we should always open controller, not namespace device + char full_path[64]; + snprintf(full_path, sizeof(full_path), NVME_CTRLR_PREFIX"%d", ctrlid); + + int fd; + if ((fd = ::open(full_path, O_RDWR))<0) { + set_err(errno); + return false; + } + set_fd(fd); + + if (!get_nsid()) { + set_nsid(nsid); + } + + return true; +} + +bool freebsd_nvme_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) +{ + // nvme_passthru_cmd pt; + struct nvme_pt_command pt; + struct nvme_completion *cp_p; + memset(&pt, 0, sizeof(pt)); + +#if __FreeBSD_version >= 1200058 && __FreeBSD_version < 1200081 + pt.cmd.opc_fuse = NVME_CMD_SET_OPC(in.opcode); +#else + pt.cmd.opc = in.opcode; +#endif + pt.cmd.nsid = htole32(in.nsid); + pt.buf = in.buffer; + pt.len = in.size; + pt.cmd.cdw10 = htole32(in.cdw10); + pt.cmd.cdw11 = htole32(in.cdw11); + pt.cmd.cdw12 = htole32(in.cdw12); + pt.cmd.cdw13 = htole32(in.cdw13); + pt.cmd.cdw14 = htole32(in.cdw14); + pt.cmd.cdw15 = htole32(in.cdw15); + pt.is_read = 1; // should we use in.direction()? + + int status = ioctl(get_fd(), NVME_PASSTHROUGH_CMD, &pt); + + if (status < 0) + return set_err(errno, "NVME_PASSTHROUGH_CMD: %s", strerror(errno)); +#if __FreeBSD_version >= 1200058 + nvme_completion_swapbytes(&pt.cpl); +#endif + cp_p = &pt.cpl; + out.result=cp_p->cdw0; // Command specific result (DW0) + + if (nvme_completion_is_error(cp_p)) { /* ignore DNR and More bits */ + return set_nvme_err(out, nvme_completion_is_error(&pt.cpl)); + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +/// Implement AMCC/3ware RAID support + +class freebsd_escalade_device +: public /*implements*/ ata_device, + public /*extends*/ freebsd_smart_device +{ +public: + freebsd_escalade_device(smart_interface * intf, const char * dev_name, + int escalade_type, int disknum); + +protected: + virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override; + virtual bool open() override; + +private: + int m_escalade_type; ///< Type string for escalade_command_interface(). + int m_disknum; ///< Disk number. +}; + +freebsd_escalade_device::freebsd_escalade_device(smart_interface * intf, const char * dev_name, + int escalade_type, int disknum) +: smart_device(intf, dev_name, "3ware", "3ware"), + freebsd_smart_device(), + m_escalade_type(escalade_type), m_disknum(disknum) +{ + set_info().info_name = strprintf("%s [3ware_disk_%02d]", dev_name, disknum); +} + +bool freebsd_escalade_device::open() +{ + const char *dev = get_dev_name(); + int fd; + + if ((fd = ::open(dev,O_RDWR))<0) { + set_err(errno); + return false; + } + set_fd(fd); + return true; +} + +bool freebsd_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) +{ + // to hold true file descriptor + int fd = get_fd(); + + if (!ata_cmd_is_ok(in, + true, // data_out_support + false, // TODO: multi_sector_support + true) // ata_48bit_support + ) + return false; + + struct twe_usercommand* cmd_twe = NULL; + TW_OSLI_IOCTL_NO_DATA_BUF* cmd_twa = NULL; + TWE_Command_ATA* ata = NULL; + + // Used by both the SCSI and char interfaces + char ioctl_buffer[TW_IOCTL_BUFFER_SIZE]; + + if (m_disknum < 0) { + printwarning(NO_DISK_3WARE,NULL); + return false; + } + + memset(ioctl_buffer, 0, TW_IOCTL_BUFFER_SIZE); + + if (m_escalade_type==CONTROLLER_3WARE_9000_CHAR) { + cmd_twa = (TW_OSLI_IOCTL_NO_DATA_BUF*)ioctl_buffer; + cmd_twa->pdata = ((TW_OSLI_IOCTL_WITH_PAYLOAD*)cmd_twa)->payload.data_buf; + cmd_twa->driver_pkt.buffer_length = in.size; + // using "old" packet format to speak with SATA devices + ata = (TWE_Command_ATA*)&cmd_twa->cmd_pkt.command.cmd_pkt_7k; + } else if (m_escalade_type==CONTROLLER_3WARE_678K_CHAR) { + cmd_twe = (struct twe_usercommand*)ioctl_buffer; + ata = &cmd_twe->tu_command.ata; + } 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); + } + + ata->opcode = TWE_OP_ATA_PASSTHROUGH; + + // Same for (almost) all commands - but some reset below + ata->request_id = 0xFF; + ata->unit = m_disknum; + ata->status = 0; + ata->flags = 0x1; + ata->size = 0x5; // TODO: multisector support + // Set registers + { + const ata_in_regs_48bit & r = in.in_regs; + ata->features = r.features_16; + ata->sector_count = r.sector_count_16; + ata->sector_num = r.lba_low_16; + ata->cylinder_lo = r.lba_mid_16; + ata->cylinder_hi = r.lba_high_16; + ata->drive_head = r.device; + ata->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) { + if (m_escalade_type==CONTROLLER_3WARE_678K_CHAR) { + cmd_twe->tu_data = in.buffer; + cmd_twe->tu_size = 512; + } + + readdata=true; + ata->sgl_offset = 0x5; + ata->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 (sizeof(long)==8) + // ata->size++; + } + else if (in.direction == ata_cmd_in::no_data) { + // Non data command -- but doesn't use large sector + // count register values. + ata->sgl_offset = 0x0; + ata->param = 0x8; + ata->sector_count = 0x0; + } + else if (in.direction == ata_cmd_in::data_out) { + ata->sgl_offset = 0x5; + ata->param = 0xF; // PIO data write + if (m_escalade_type==CONTROLLER_3WARE_678K_CHAR) { + cmd_twe->tu_data = in.buffer; + cmd_twe->tu_size = 512; + } + else if (m_escalade_type==CONTROLLER_3WARE_9000_CHAR) { + memcpy(cmd_twa->pdata, in.buffer, in.size); + } + } + else + return set_err(EINVAL); + + // 3WARE controller can NOT have packet device internally + if (in.in_regs.command == ATA_IDENTIFY_PACKET_DEVICE) { + return set_err(ENODEV, "No drive on port %d", m_disknum); + } + + // Now send the command down through an ioctl() + int ioctlreturn; + if (m_escalade_type==CONTROLLER_3WARE_9000_CHAR) { + ioctlreturn=ioctl(fd,TW_OSL_IOCTL_FIRMWARE_PASS_THROUGH,cmd_twa); + } else { + ioctlreturn=ioctl(fd,TWEIO_COMMAND,cmd_twe); + } + + // Deal with the different error cases + if (ioctlreturn) { + return set_err(EIO); + } + + // See if the ATA command failed. Now that we have returned from + // the ioctl() call, if passthru is valid, then: + // - ata->status contains the 3ware controller STATUS + // - ata->command contains the ATA STATUS register + // - ata->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 (ata->status || (ata->command & 0x21)) { + if (scsi_debugmode) + pout("Command failed, ata.status=(0x%2.2x), ata.command=(0x%2.2x), ata.flags=(0x%2.2x)\n",ata->status,ata->command,ata->flags); + return set_err(EIO); + } + + // If this is a read data command, copy data to output buffer + if (readdata) { + if (m_escalade_type==CONTROLLER_3WARE_9000_CHAR) + memcpy(in.buffer, cmd_twa->pdata, in.size); + else if(m_escalade_type==CONTROLLER_3WARE_678K_CHAR) { + memcpy(in.buffer, cmd_twe->tu_data, in.size); // untested + } + } + // Return register values + if (ata) { + ata_out_regs_48bit & r = out.out_regs; + r.error = ata->features; + r.sector_count_16 = ata->sector_count; + r.lba_low_16 = ata->sector_num; + r.lba_mid_16 = ata->cylinder_lo; + r.lba_high_16 = ata->cylinder_hi; + r.device = ata->drive_head; + r.status = ata->command; + } + // look for nonexistent devices/ports + if (in.in_regs.command == ATA_IDENTIFY_DEVICE + && !nonempty((unsigned char *)in.buffer, in.size)) { + return set_err(ENODEV, "No drive on port %d", m_disknum); + } + return true; +} + +///////////////////////////////////////////////////////////////////////////// +/// LSI MegaRAID support + +class freebsd_megaraid_device +: public /* implements */ scsi_device, + public /* extends */ freebsd_smart_device +{ +public: + freebsd_megaraid_device(smart_interface *intf, const char *name, + unsigned int tgt); + + virtual ~freebsd_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 (freebsd_megaraid_device::*pt_cmd)(int cdblen, void *cdb, int dataLen, void *data, + int senseLen, void *sense, int report, int direction, int timeout); + bool megasas_cmd(int cdbLen, void *cdb, int dataLen, void *data, + int senseLen, void *sense, int report, int direction, int timeout); +}; + +freebsd_megaraid_device::freebsd_megaraid_device(smart_interface *intf, + const char *dev_name, unsigned int tgt) + : smart_device(intf, dev_name, "megaraid", "megaraid"), + freebsd_smart_device(), + 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); +} + +freebsd_megaraid_device::~freebsd_megaraid_device() +{ + if (m_fd >= 0) + ::close(m_fd); +} + +smart_device * freebsd_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 freebsd_megaraid_device::open() +{ + /* Open Device IOCTL node */ + if ((m_fd = ::open(get_dev_name(), O_RDWR)) >= 0) { + pt_cmd = &freebsd_megaraid_device::megasas_cmd; + } + else { + int err = errno; + freebsd_smart_device::close(); + return set_err(err, "cannot open %s",get_dev_name()); + } + set_fd(m_fd); + return true; +} + +bool freebsd_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 freebsd_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, iop->timeout); +} + +bool freebsd_megaraid_device::megasas_cmd(int cdbLen, void *cdb, + int dataLen, void *data, + int senseLen, void * sense, int /*report*/, int dxfer_dir, int timeout) +{ + struct mfi_pass_frame * pthru; + struct mfi_ioc_packet uio; + + pthru = (struct mfi_pass_frame *)&uio.mfi_frame.raw; + memset(&uio, 0, sizeof(uio)); + + pthru->header.cmd = MFI_CMD_PD_SCSI_IO; + pthru->header.cmd_status = 0; + pthru->header.scsi_status = 0x0; + pthru->header.target_id = m_disknum; + pthru->header.lun_id = 0; // FIXME, should be bus number? + + pthru->header.sense_len = senseLen; + pthru->sense_addr_lo = (uintptr_t)sense ; + pthru->sense_addr_hi = (uintptr_t)((uint64_t)sense >> 32); + + pthru->header.cdb_len = cdbLen; + pthru->header.timeout = timeout; + switch (dxfer_dir) { + case DXFER_FROM_DEVICE: + pthru->header.flags = MFI_FRAME_DIR_READ; + break; + case DXFER_TO_DEVICE: + pthru->header.flags = MFI_FRAME_DIR_WRITE; + break; + case DXFER_NONE: + pthru->header.flags = MFI_FRAME_DIR_NONE; + break; + } + + if (dataLen > 0) { + uio.mfi_sge_count = 1; + uio.mfi_sgl_off = offsetof(struct mfi_pass_frame,sgl); + uio.mfi_sgl[0].iov_base = data; + uio.mfi_sgl[0].iov_len = dataLen; + + pthru->header.sg_count = 1; + pthru->header.data_len = dataLen; + // tested on amd64 kernel in native and 32bit mode + pthru->sgl.sg64[0].addr = (intptr_t)data; + pthru->sgl.sg64[0].len = (uint32_t)dataLen; + } + memcpy(pthru->cdb, cdb, cdbLen); + + uio.mfi_adapter_no = m_hba; + uio.mfi_sense_len = senseLen; + uio.mfi_sense_off = offsetof(struct mfi_pass_frame, sense_addr_lo); + + errno = 0; + int rc = ioctl(m_fd, MFI_CMD, &uio); + + if (pthru->header.cmd_status || rc != 0) { + if (pthru->header.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->header.cmd_status); + } + return true; +} + + +///////////////////////////////////////////////////////////////////////////// +/// Implement Highpoint RAID support with old functions + +class freebsd_highpoint_device +: public /*implements*/ ata_device_with_command_set, + public /*extends*/ freebsd_smart_device +{ +public: + freebsd_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) override; + virtual bool open() override; + +private: + unsigned char m_hpt_data[3]; ///< controller/channel/port +}; + + +freebsd_highpoint_device::freebsd_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"), + freebsd_smart_device() +{ + 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]); +} + +bool freebsd_highpoint_device::open() +{ + const char *dev = get_dev_name(); + int fd; + + if ((fd = ::open(dev,O_RDWR))<0) { + set_err(errno); + return false; + } + set_fd(fd); + return true; +} + +int freebsd_highpoint_device::ata_command_interface(smart_command_set command, int select, char * data) +{ + int fd=get_fd(); + int ids[2]; + HPT_IOCTL_PARAM param; + HPT_CHANNEL_INFO_V2 info; + unsigned char* buff[512 + 2 * sizeof(HPT_PASS_THROUGH_HEADER)]; + PHPT_PASS_THROUGH_HEADER pide_pt_hdr, pide_pt_hdr_out; + + // get internal deviceid + ids[0] = m_hpt_data[0] - 1; + ids[1] = m_hpt_data[1] - 1; + + memset(¶m, 0, sizeof(HPT_IOCTL_PARAM)); + + param.magic = HPT_IOCTL_MAGIC; + param.ctrl_code = HPT_IOCTL_GET_CHANNEL_INFO_V2; + param.in = (unsigned char *)ids; + param.in_size = sizeof(unsigned int) * 2; + param.out = (unsigned char *)&info; + param.out_size = sizeof(HPT_CHANNEL_INFO_V2); + + if (m_hpt_data[2]==1) { + param.ctrl_code = HPT_IOCTL_GET_CHANNEL_INFO; + param.out_size = sizeof(HPT_CHANNEL_INFO); + } + if (ioctl(fd, HPT_DO_IOCONTROL, ¶m)!=0 || + info.devices[m_hpt_data[2]-1]==0) { + return -1; + } + + // perform smart action + memset(buff, 0, sizeof(buff)); + pide_pt_hdr = (PHPT_PASS_THROUGH_HEADER)buff; + + pide_pt_hdr->lbamid = 0x4f; + pide_pt_hdr->lbahigh = 0xc2; + pide_pt_hdr->command = ATA_SMART_CMD; + pide_pt_hdr->id = info.devices[m_hpt_data[2] - 1]; + + switch (command){ + case READ_VALUES: + pide_pt_hdr->feature=ATA_SMART_READ_VALUES; + pide_pt_hdr->protocol=HPT_READ; + break; + case READ_THRESHOLDS: + pide_pt_hdr->feature=ATA_SMART_READ_THRESHOLDS; + pide_pt_hdr->protocol=HPT_READ; + break; + case READ_LOG: + pide_pt_hdr->feature=ATA_SMART_READ_LOG_SECTOR; + pide_pt_hdr->lbalow=select; + pide_pt_hdr->protocol=HPT_READ; + break; + case IDENTIFY: + pide_pt_hdr->command=ATA_IDENTIFY_DEVICE; + pide_pt_hdr->protocol=HPT_READ; + break; + case ENABLE: + pide_pt_hdr->feature=ATA_SMART_ENABLE; + break; + case DISABLE: + pide_pt_hdr->feature=ATA_SMART_DISABLE; + break; + case AUTO_OFFLINE: + pide_pt_hdr->feature=ATA_SMART_AUTO_OFFLINE; + pide_pt_hdr->sectorcount=select; + break; + case AUTOSAVE: + pide_pt_hdr->feature=ATA_SMART_AUTOSAVE; + pide_pt_hdr->sectorcount=select; + break; + case IMMEDIATE_OFFLINE: + pide_pt_hdr->feature=ATA_SMART_IMMEDIATE_OFFLINE; + pide_pt_hdr->lbalow=select; + break; + case STATUS_CHECK: + case STATUS: + pide_pt_hdr->feature=ATA_SMART_STATUS; + break; + case CHECK_POWER_MODE: + pide_pt_hdr->command=ATA_CHECK_POWER_MODE; + break; + case WRITE_LOG: + memcpy(buff+sizeof(HPT_PASS_THROUGH_HEADER), data, 512); + pide_pt_hdr->feature=ATA_SMART_WRITE_LOG_SECTOR; + pide_pt_hdr->lbalow=select; + pide_pt_hdr->protocol=HPT_WRITE; + break; + default: + pout("Unrecognized command %d in highpoint_command_interface()\n" + "Please contact " PACKAGE_BUGREPORT "\n", command); + errno=ENOSYS; + return -1; + } + if (pide_pt_hdr->protocol!=0) { + pide_pt_hdr->sectors = 1; + pide_pt_hdr->sectorcount = 1; + } + + memset(¶m, 0, sizeof(HPT_IOCTL_PARAM)); + + param.magic = HPT_IOCTL_MAGIC; + param.ctrl_code = HPT_IOCTL_IDE_PASS_THROUGH; + param.in = (unsigned char *)buff; + param.in_size = sizeof(HPT_PASS_THROUGH_HEADER) + (pide_pt_hdr->protocol==HPT_READ ? 0 : pide_pt_hdr->sectors * 512); + param.out = (unsigned char *)buff+param.in_size; + param.out_size = sizeof(HPT_PASS_THROUGH_HEADER) + (pide_pt_hdr->protocol==HPT_READ ? pide_pt_hdr->sectors * 512 : 0); + + pide_pt_hdr_out = (PHPT_PASS_THROUGH_HEADER)param.out; + + if ((ioctl(fd, HPT_DO_IOCONTROL, ¶m)!=0) || + (pide_pt_hdr_out->command & 1)) { + return -1; + } + + if (command==STATUS_CHECK) + { + unsigned const char normal_lo=0x4f, normal_hi=0xc2; + unsigned const char failed_lo=0xf4, failed_hi=0x2c; + unsigned char low,high; + + high = pide_pt_hdr_out->lbahigh; + low = pide_pt_hdr_out->lbamid; + + // Cyl low and Cyl high unchanged means "Good SMART status" + if (low==normal_lo && high==normal_hi) + return 0; + + // These values mean "Bad SMART status" + if (low==failed_lo && high==failed_hi) + return 1; + + // We haven't gotten output that makes sense; print out some debugging info + char buf[512]; + snprintf(buf, sizeof(buf), + "CMD=0x%02x\nFR =0x%02x\nNS =0x%02x\nSC =0x%02x\nCL =0x%02x\nCH =0x%02x\nRETURN =0x%04x\n", + (int)pide_pt_hdr_out->command, + (int)pide_pt_hdr_out->feature, + (int)pide_pt_hdr_out->sectorcount, + (int)pide_pt_hdr_out->lbalow, + (int)pide_pt_hdr_out->lbamid, + (int)pide_pt_hdr_out->lbahigh, + (int)pide_pt_hdr_out->sectors); + printwarning(BAD_SMART,buf); + } + else if (command==CHECK_POWER_MODE) + data[0] = pide_pt_hdr_out->sectorcount & 0xff; + else if (pide_pt_hdr->protocol==HPT_READ) + memcpy(data, (unsigned char *)buff + 2 * sizeof(HPT_PASS_THROUGH_HEADER), + pide_pt_hdr->sectors * 512); + return 0; +} + + +///////////////////////////////////////////////////////////////////////////// +/// Standard SCSI support + +class freebsd_scsi_device +: public /*implements*/ scsi_device, + public /*extends*/ freebsd_smart_device +{ +public: + freebsd_scsi_device(smart_interface * intf, const char * dev_name, const char * req_type); + + virtual smart_device * autodetect_open() override; + + virtual bool scsi_pass_through(scsi_cmnd_io * iop) override; + + virtual bool open() override; + + virtual bool close() override; + +private: + struct cam_device *m_camdev; +}; + +bool freebsd_scsi_device::open(){ + const char *dev = get_dev_name(); + + if ((m_camdev = cam_open_device(dev, O_RDWR)) == NULL) { + set_err(errno); + return false; + } + set_fd(m_camdev->fd); + return true; +} + +bool freebsd_scsi_device::close(){ + cam_close_device(m_camdev); + set_fd(-1); + return true; +} + +freebsd_scsi_device::freebsd_scsi_device(smart_interface * intf, + const char * dev_name, const char * req_type) +: smart_device(intf, dev_name, "scsi", req_type), + freebsd_smart_device(), + m_camdev(0) +{ +} + + +bool freebsd_scsi_device::scsi_pass_through(scsi_cmnd_io * iop) +{ + union ccb *ccb; + + if (scsi_debugmode) { + unsigned int k; + const unsigned char * ucp = iop->cmnd; + const char * np; + + np = scsi_get_opcode_name(ucp); + pout(" [%s: ", np ? np : "<unknown opcode>"); + for (k = 0; k < iop->cmnd_len; ++k) + pout("%02x ", ucp[k]); + if ((scsi_debugmode > 1) && + (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) { + int trunc = (iop->dxfer_len > 256) ? 1 : 0; + + pout("]\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 + pout("]\n"); + } + + if(m_camdev==NULL) { + if (scsi_debugmode) + pout(" error: camdev=0!\n"); + return set_err(ENOTTY); + } + + if (!(ccb = cam_getccb(m_camdev))) { + if (scsi_debugmode) + pout(" error allocating ccb\n"); + return set_err(ENOMEM); + } + + // mfi SAT layer is known to be buggy + if(!strcmp("mfi",m_camdev->sim_name)) { + 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 + && 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 + && 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"); + } + } + // clear out structure, except for header that was filled in for us + memset(&(&ccb->ccb_h)[1], 0, sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); + + cam_fill_csio(&ccb->csio, + /* retries */ 1, + /* cbfcnp */ NULL, + /* flags */ (iop->dxfer_dir == DXFER_NONE ? CAM_DIR_NONE :(iop->dxfer_dir == DXFER_FROM_DEVICE ? CAM_DIR_IN : CAM_DIR_OUT)), + /* tagaction */ MSG_SIMPLE_Q_TAG, + /* dataptr */ iop->dxferp, + /* datalen */ iop->dxfer_len, + /* senselen */ iop->max_sense_len, + /* cdblen */ iop->cmnd_len, + /* timeout (converted to seconds) */ iop->timeout*1000); + memcpy(ccb->csio.cdb_io.cdb_bytes,iop->cmnd,iop->cmnd_len); + + if (cam_send_ccb(m_camdev,ccb) < 0) { + if (scsi_debugmode) { + pout(" error sending SCSI ccb\n"); + cam_error_print(m_camdev,ccb,CAM_ESF_ALL,CAM_EPF_ALL,stderr); + } + cam_freeccb(ccb); + return set_err(EIO); + } + + if (scsi_debugmode) { + pout(" CAM status=0x%x, SCSI status=0x%x, resid=0x%x\n", + ccb->ccb_h.status, ccb->csio.scsi_status, ccb->csio.resid); + if ((scsi_debugmode > 1) && (DXFER_FROM_DEVICE == iop->dxfer_dir)) { + int trunc, len; + + len = iop->dxfer_len - ccb->csio.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"); + } + } + + if (((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) && ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_SCSI_STATUS_ERROR)) { + if (scsi_debugmode) + cam_error_print(m_camdev,ccb,CAM_ESF_ALL,CAM_EPF_ALL,stderr); + cam_freeccb(ccb); + return set_err(EIO); + } + + iop->resid = ccb->csio.resid; + iop->scsi_status = ccb->csio.scsi_status; + if (iop->sensep && (ccb->ccb_h.status & CAM_AUTOSNS_VALID) != 0) { + if (scsi_debugmode) + pout(" sense_len=0x%x, sense_resid=0x%x\n", + ccb->csio.sense_len, ccb->csio.sense_resid); + iop->resp_sense_len = ccb->csio.sense_len - ccb->csio.sense_resid; + /* Some SCSI controller device drivers miscalculate the sense_resid + field so cap resp_sense_len on max_sense_len. */ + if (iop->resp_sense_len > iop->max_sense_len) + iop->resp_sense_len = iop->max_sense_len; + if (iop->resp_sense_len > 0) { + memcpy(iop->sensep, &(ccb->csio.sense_data), iop->resp_sense_len); + if (scsi_debugmode) { + if (scsi_debugmode > 1) { + pout(" >>> Sense buffer, len=%zu:\n", iop->resp_sense_len); + dStrHex(iop->sensep, iop->resp_sense_len, 1); + } + if ((iop->sensep[0] & 0x7f) > 0x71) + pout(" status=0x%x: [desc] sense_key=0x%x asc=0x%x ascq=0x%x\n", + iop->scsi_status, iop->sensep[1] & 0xf, + iop->sensep[2], iop->sensep[3]); + else + pout(" status=0x%x: sense_key=0x%x asc=0x%x ascq=0x%x\n", + iop->scsi_status, iop->sensep[2] & 0xf, + iop->sensep[12], iop->sensep[13]); + } + } + else if (scsi_debugmode) + pout(" status=0x%x\n", iop->scsi_status); + } + else if (scsi_debugmode) + pout(" status=0x%x\n", iop->scsi_status); + + cam_freeccb(ccb); + + // mfip replacing PDT of the device so response does not make a sense + // this sets PDT to 00h - direct-access block device + if((!strcmp("mfi", m_camdev->sim_name) || !strcmp("mpt", m_camdev->sim_name)) + && iop->cmnd[0] == INQUIRY) { + if (scsi_debugmode) { + pout(" device on %s controller, patching PDT\n", m_camdev->sim_name); + } + iop->dxferp[0] = iop->dxferp[0] & 0xe0; + } + + return true; +} + + +///////////////////////////////////////////////////////////////////////////// +/// Areca RAID support + +/////////////////////////////////////////////////////////////////// +// SATA(ATA) device behind Areca RAID Controller +class freebsd_areca_ata_device +: public /*implements*/ areca_ata_device, + public /*extends*/ freebsd_smart_device +{ +public: + freebsd_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 freebsd_areca_scsi_device +: public /*implements*/ areca_scsi_device, + public /*extends*/ freebsd_smart_device +{ +public: + freebsd_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; +}; + + +// Areca RAID Controller(SATA Disk) +freebsd_areca_ata_device::freebsd_areca_ata_device(smart_interface * intf, const char * dev_name, int disknum, int encnum) +: smart_device(intf, dev_name, "areca", "areca"), + freebsd_smart_device() +{ + set_disknum(disknum); + set_encnum(encnum); + set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum); +} + + +smart_device * freebsd_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 freebsd_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 freebsd_areca_ata_device::arcmsr_do_scsi_io(struct scsi_cmnd_io * iop) +{ + int ioctlreturn = 0; + + if(!is_open()) { + if(!open()){ + } + } + + ioctlreturn = ioctl(get_fd(), ((sSRB_BUFFER *)(iop->dxferp))->srbioctl.ControlCode, iop->dxferp); + if (ioctlreturn) + { + // errors found + return -1; + } + return 0; +} + +bool freebsd_areca_ata_device::arcmsr_lock() +{ + return true; +} + + +bool freebsd_areca_ata_device::arcmsr_unlock() +{ + return true; +} + + +// Areca RAID Controller(SAS Device) +freebsd_areca_scsi_device::freebsd_areca_scsi_device(smart_interface * intf, const char * dev_name, int disknum, int encnum) +: smart_device(intf, dev_name, "areca", "areca"), + freebsd_smart_device() +{ + set_disknum(disknum); + set_encnum(encnum); + set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum); +} + +smart_device * freebsd_areca_scsi_device::autodetect_open() +{ + return this; +} + +int freebsd_areca_scsi_device::arcmsr_do_scsi_io(struct scsi_cmnd_io * iop) +{ + int ioctlreturn = 0; + + if(!is_open()) { + if(!open()){ + } + } + ioctlreturn = ioctl(get_fd(), ((sSRB_BUFFER *)(iop->dxferp))->srbioctl.ControlCode, iop->dxferp); + if (ioctlreturn) + { + // errors found + return -1; + } + + return 0; +} + +bool freebsd_areca_scsi_device::arcmsr_lock() +{ + return true; +} + + +bool freebsd_areca_scsi_device::arcmsr_unlock() +{ + return true; +} + + +///////////////////////////////////////////////////////////////////////////// +/// Implement CCISS RAID support with old functions + +class freebsd_cciss_device +: public /*implements*/ scsi_device, + public /*extends*/ freebsd_smart_device +{ +public: + freebsd_cciss_device(smart_interface * intf, const char * name, unsigned char disknum); + + virtual bool scsi_pass_through(scsi_cmnd_io * iop) override; + virtual bool open() override; + +private: + unsigned char m_disknum; ///< Disk number. +}; + +bool freebsd_cciss_device::open() +{ + const char *dev = get_dev_name(); + int fd; + if ((fd = ::open(dev,O_RDWR))<0) { + set_err(errno); + return false; + } + set_fd(fd); + return true; +} + +freebsd_cciss_device::freebsd_cciss_device(smart_interface * intf, + const char * dev_name, unsigned char disknum) +: smart_device(intf, dev_name, "cciss", "cciss"), + freebsd_smart_device(), + m_disknum(disknum) +{ + set_info().info_name = strprintf("%s [cciss_disk_%02d]", dev_name, disknum); +} + +bool freebsd_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; + // not reached + return true; +} + + +///////////////////////////////////////////////////////////////////////////// +/// SCSI open with autodetection support + +smart_device * freebsd_scsi_device::autodetect_open() +{ + // Open device + if (!open()) + return this; + + // No Autodetection if device type was specified by user + if (*get_req_type()) + return this; + + // 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) + return this; + + // Use INQUIRY to detect type + + // 3ware ? + if (!memcmp(req_buff + 8, "3ware", 5) || !memcmp(req_buff + 8, "AMCC", 4) || + !strcmp("tws",m_camdev->sim_name) || !strcmp("twa",m_camdev->sim_name)) { + close(); + set_err(EINVAL, "3ware/LSI controller, please try adding '-d 3ware,N',\n" + "you may need to replace %s with /dev/twaN, /dev/tweN or /dev/twsN", 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, use '-d megaraid,N'"); + return this; + } + + // SAT or USB, skip MFI controllers because of bugs + { + smart_device * newdev = smi()->autodetect_sat_device(this, req_buff, len); + if (newdev) { + // NOTE: 'this' is now owned by '*newdev' + if(!strcmp("mfi",m_camdev->sim_name)) { + newdev->close(); + newdev->set_err(ENOSYS, "SATA device detected,\n" + "MegaRAID SAT layer is reportedly buggy, use '-d sat' to try anyhow"); + } + return newdev; + } + } + + // Nothing special found + return this; +} + + +///////////////////////////////////////////////////////////////////////////// +/// Implement platform interface with old functions. + +class freebsd_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 char * type, + const char * pattern = 0) override; + +protected: + virtual ata_device * get_ata_device(const char * name, const char * type) override; + +#if FREEBSDVER > 800100 + virtual ata_device * get_atacam_device(const char * name, const char * type); +#endif + + 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: + bool get_nvme_devlist(smart_device_list & devlist, const char * type); + bool get_dev_megaraid(smart_device_list & devlist); + int megaraid_pd_add_list(const char * devname, smart_device_list & devlist); + int megaraid_dcmd_cmd(const char * devname, uint32_t opcode, void *buf, + size_t bufsize, uint8_t *mbox, size_t mboxlen, uint8_t *statusp); +}; + + +////////////////////////////////////////////////////////////////////// + +std::string freebsd_smart_interface::get_os_version_str() +{ + struct utsname osname; + uname(&osname); + return strprintf("%s %s %s", osname.sysname, osname.release, osname.machine); +} + +std::string freebsd_smart_interface::get_app_examples(const char * appname) +{ + if (!strcmp(appname, "smartctl")) + return smartctl_examples; + return ""; +} + +ata_device * freebsd_smart_interface::get_ata_device(const char * name, const char * type) +{ + return new freebsd_ata_device(this, name, type); +} + +#if FREEBSDVER > 800100 +ata_device * freebsd_smart_interface::get_atacam_device(const char * name, const char * type) +{ + return new freebsd_atacam_device(this, name, type); +} +#endif + +scsi_device * freebsd_smart_interface::get_scsi_device(const char * name, const char * type) +{ + return new freebsd_scsi_device(this, name, type); +} + +nvme_device * freebsd_smart_interface::get_nvme_device(const char * name, const char * type, + unsigned nsid) +{ + return new freebsd_nvme_device(this, name, type, nsid); +} + +// we are using CAM subsystem XPT enumerator to found all CAM (scsi/usb/ada/...) +// devices on system despite of it's names +// +// If any errors occur, leave errno set as it was returned by the +// system call, and return <0. +// +// arguments: +// names: resulting array +// show_all - export duplicate device name or not +// +// Return values: +// -1: error +// >=0: number of discovered devices + +bool get_dev_names_cam(std::vector<std::string> & names, bool show_all) +{ + int fd; + if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) { + if (errno == ENOENT) /* There are no CAM device on this computer */ + return 0; + int serrno = errno; + pout("%s control device couldn't opened: %s\n", XPT_DEVICE, strerror(errno)); + errno = serrno; + return false; + } + + union ccb ccb; + memset(&ccb, 0, sizeof(union ccb)); + + ccb.ccb_h.path_id = CAM_XPT_PATH_ID; + ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; + ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; + + ccb.ccb_h.func_code = XPT_DEV_MATCH; + int bufsize = sizeof(struct dev_match_result) * MAX_NUM_DEV; + ccb.cdm.match_buf_len = bufsize; + // TODO: Use local buffer instead of malloc() if possible + ccb.cdm.matches = (struct dev_match_result *)malloc(bufsize); + memset(ccb.cdm.matches, 0, bufsize); // clear ccb.cdm.matches structure + + if (ccb.cdm.matches == NULL) { + close(fd); + throw std::bad_alloc(); + } + ccb.cdm.num_matches = 0; + ccb.cdm.num_patterns = 0; + ccb.cdm.pattern_buf_len = 0; + + /* + * We do the ioctl multiple times if necessary, in case there are + * more than MAX_NUM_DEV nodes in the EDT. + */ + int skip_device = 0, skip_bus = 0, changed = 0; // TODO: bool + std::string devname; + do { + if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { + int serrno = errno; + pout("error sending CAMIOCOMMAND ioctl: %s\n", strerror(errno)); + free(ccb.cdm.matches); + close(fd); + errno = serrno; + return false; + } + + if ((ccb.ccb_h.status != CAM_REQ_CMP) + || ((ccb.cdm.status != CAM_DEV_MATCH_LAST) + && (ccb.cdm.status != CAM_DEV_MATCH_MORE))) { + pout("got CAM error %#x, CDM error %d\n", ccb.ccb_h.status, ccb.cdm.status); + free(ccb.cdm.matches); + close(fd); + errno = ENXIO; + return false; + } + + for (unsigned i = 0; i < ccb.cdm.num_matches; i++) { + struct device_match_result *dev_result; + struct periph_match_result *periph_result; + + if (ccb.cdm.matches[i].type == DEV_MATCH_BUS) { + struct bus_match_result *bus_result; + + bus_result = &ccb.cdm.matches[i].result.bus_result; + + if (strcmp(bus_result->dev_name,"xpt") == 0) /* skip XPT bus at all */ + skip_bus = 1; + else + skip_bus = 0; + changed = 1; + } else if (ccb.cdm.matches[i].type == DEV_MATCH_DEVICE) { + dev_result = &ccb.cdm.matches[i].result.device_result; + + if (dev_result->flags & DEV_RESULT_UNCONFIGURED || skip_bus == 1) + skip_device = 1; + else + skip_device = 0; + + // skip ses devices + if (dev_result->inq_data.device == T_ENCLOSURE) + skip_device = 1; + + // /* Shall we skip non T_DIRECT devices ? */ + // if (dev_result->inq_data.device != T_DIRECT) + // skip_device = 1; + changed = 1; + } else if (ccb.cdm.matches[i].type == DEV_MATCH_PERIPH && + (skip_device == 0 || show_all)) { + /* One device may be populated as many peripherals (pass0 & da0 for example). + * We are searching for best name + */ + periph_result = &ccb.cdm.matches[i].result.periph_result; + /* Prefer non-"pass" names */ + if (devname.empty() || strncmp(periph_result->periph_name, "pass", 4) != 0) { + devname = strprintf("%s%s%d", _PATH_DEV, periph_result->periph_name, periph_result->unit_number); + } + changed = 0; + }; + if ((changed == 1 || show_all) && !devname.empty()) { + names.push_back(devname); + devname.erase(); + changed = 0; + }; + } + + } while ((ccb.ccb_h.status == CAM_REQ_CMP) && (ccb.cdm.status == CAM_DEV_MATCH_MORE)); + + if (!devname.empty()) + names.push_back(devname); + + free(ccb.cdm.matches); + close(fd); + return true; +} + +// we are using ATA subsystem enumerator to found all ATA devices on system +// despite of it's names +// +// If any errors occur, leave errno set as it was returned by the +// system call, and return <0. + +// Return values: +// -1: error +// >=0: number of discovered devices +int get_dev_names_ata(char*** names) { + struct ata_ioc_devices devices; + int fd=-1,maxchannel,serrno=-1,n=0; + char **mp = NULL; + + *names=NULL; + + if ((fd = open(ATA_DEVICE, O_RDWR)) < 0) { + if (errno == ENOENT) /* There are no ATA device on this computer */ + return 0; + serrno = errno; + pout("%s control device can't be opened: %s\n", ATA_DEVICE, strerror(errno)); + n = -1; + goto end; + }; + + if (ioctl(fd, IOCATAGMAXCHANNEL, &maxchannel) < 0) { + serrno = errno; + pout("ioctl(IOCATAGMAXCHANNEL) on /dev/ata failed: %s\n", strerror(errno)); + n = -1; + goto end; + }; + + // allocate space for up to MAX_NUM_DEV number of ATA devices + mp = (char **)calloc(MAX_NUM_DEV, sizeof(char*)); + if (mp == NULL) { + serrno=errno; + pout("Out of memory constructing scan device list (on line %d)\n", __LINE__); + n = -1; + goto end; + }; + + for (devices.channel = 0; devices.channel < maxchannel && n < MAX_NUM_DEV; devices.channel++) { + int j; + + if (ioctl(fd, IOCATADEVICES, &devices) < 0) { + if (errno == ENXIO) + continue; /* such channel not exist */ + pout("ioctl(IOCATADEVICES) on %s channel %d failed: %s\n", ATA_DEVICE, devices.channel, strerror(errno)); + n = -1; + goto end; + }; + for (j=0;j<=1 && n<MAX_NUM_DEV;j++) { + if (devices.name[j][0] != '\0') { + asprintf(mp+n, "%s%s", _PATH_DEV, devices.name[j]); + if (mp[n] == NULL) { + pout("Out of memory constructing scan ATA device list (on line %d)\n", __LINE__); + n = -1; + goto end; + }; + n++; + }; + }; + }; + if (n <= 0) + goto end; + mp = (char **)reallocf(mp,n*(sizeof (char*))); // shrink to correct size + if (mp == NULL) { + serrno=errno; + pout("Out of memory constructing scan device list (on line %d)\n", __LINE__); + n = -1; + goto end; + }; + +end: + if (fd>=0) + close(fd); + if (n <= 0) { + free(mp); + mp = NULL; + } + + *names=mp; + + if (serrno>-1) + errno=serrno; + return n; +} + + + +bool freebsd_smart_interface::scan_smart_devices(smart_device_list & devlist, + const char * type, const char * pattern /*= 0*/) +{ + if (pattern) { + set_err(EINVAL, "DEVICESCAN with pattern not implemented yet"); + return false; + } + +#ifdef WITH_NVME_DEVICESCAN // TODO: Remove when NVMe support is no longer EXPERIMENTAL + bool scan_nvme = !type || !strcmp(type, "nvme"); +#else + bool scan_nvme = type && !strcmp(type, "nvme"); +#endif + + // Make namelists + char * * atanames = 0; int numata = 0; + if (!type || !strcmp(type, "ata")) { + numata = get_dev_names_ata(&atanames); + if (numata < 0) { + set_err(ENOMEM); + return false; + } + } + + std::vector<std::string> scsinames; + if (!type || !strcmp(type, "scsi")) { // do not export duplicated names + if (!get_dev_names_cam(scsinames, false)) { + set_err(errno); + return false; + } + } + + // Add to devlist + int i; + if (type==NULL) + type=""; + for (i = 0; i < numata; i++) { + ata_device * atadev = get_ata_device(atanames[i], type); + if (atadev) + devlist.push_back(atadev); + free(atanames[i]); + } + if(numata) free(atanames); + + for (i = 0; i < (int)scsinames.size(); i++) { + if(!*type) { // try USB autodetection if no type specified + smart_device * smartdev = autodetect_smart_device(scsinames[i].c_str()); + if(smartdev) + devlist.push_back(smartdev); + } + else { + scsi_device * scsidev = get_scsi_device(scsinames[i].c_str(), type); + if (scsidev) + devlist.push_back(scsidev); + } + } + + // add devices from LSI MegaRaid controllers + get_dev_megaraid(devlist); + + if (scan_nvme) + get_nvme_devlist(devlist, type); + return true; +} + +bool freebsd_smart_interface::get_nvme_devlist(smart_device_list & devlist, + const char * type) +{ + char ctrlpath[64]; + + for (int ctrlr = 0;; ctrlr++) { + snprintf(ctrlpath, sizeof(ctrlpath), "%s%d", NVME_CTRLR_PREFIX, ctrlr); + int fd = ::open(ctrlpath, O_RDWR); + if (fd < 0) + break; + ::close(fd); + nvme_device * nvmedev = get_nvme_device(ctrlpath, type, 0); + if (nvmedev) + devlist.push_back(nvmedev); + else + break; + } + return true; +} + +// getting devices from LSI SAS MegaRaid, if available +bool freebsd_smart_interface::get_dev_megaraid(smart_device_list & devlist) +{ + /* Scanning of disks on MegaRaid device */ + char ctrlpath[64]; + + // trying to add devices on first 32 buses, same as StorCLI does + for(unsigned i = 0; i <=32; i++) { + snprintf(ctrlpath, sizeof(ctrlpath), "%s%u", MFI_CTRLR_PREFIX, i); + megaraid_pd_add_list(ctrlpath, devlist); + snprintf(ctrlpath, sizeof(ctrlpath), "%s%u", MRSAS_CTRLR_PREFIX, i); + megaraid_pd_add_list(ctrlpath, devlist); + } + return true; +} + +int +freebsd_smart_interface::megaraid_dcmd_cmd(const char * devname, uint32_t opcode, void *buf, + size_t bufsize, uint8_t *mbox, size_t mboxlen, uint8_t *statusp) +{ + struct mfi_ioc_packet ioc; + struct mfi_dcmd_frame * dcmd; + + if ((mbox != NULL && (mboxlen == 0 || mboxlen > MFI_MBOX_SIZE)) || + (mbox == NULL && mboxlen != 0)) + { + errno = EINVAL; + return (-1); + } + + memset(&ioc, 0, sizeof(ioc)); + dcmd = (struct mfi_dcmd_frame *)&ioc.mfi_frame.raw; + + if (mbox) + memcpy(dcmd->mbox, mbox, mboxlen); + dcmd->header.cmd = MFI_CMD_DCMD; + dcmd->header.data_len = bufsize; + dcmd->opcode = opcode; + + if (bufsize > 0) { + ioc.mfi_sge_count = 1; + ioc.mfi_sgl_off = offsetof(struct mfi_dcmd_frame,sgl); + ioc.mfi_sgl[0].iov_base = buf; + ioc.mfi_sgl[0].iov_len = bufsize; + dcmd->header.sg_count = 1; + dcmd->header.data_len = bufsize; + // tested on amd64 kernel in native and 32bit mode + dcmd->sgl.sg64[0].addr = (intptr_t)buf; + dcmd->sgl.sg64[0].len = (uint32_t)bufsize; + } + + int fd; + if ((fd = ::open(devname, O_RDWR)) < 0) { + return (errno); + } + // We are using MFI_CMD as it seems to be supported by all LSI BSD drivers + int r = ioctl(fd, MFI_CMD, &ioc); + ::close(fd); + if (r < 0) { + return (r); + } + + if (statusp != NULL) + *statusp = dcmd->header.cmd_status; + else if (dcmd->header.cmd_status != MFI_STAT_OK) { + fprintf(stderr, "command %x returned error status %x\n", + opcode, dcmd->header.cmd_status); + errno = EIO; + return (-1); + } + return (0); +} + +int +freebsd_smart_interface::megaraid_pd_add_list(const char * devname, smart_device_list & devlist) +{ + /* + * Keep fetching the list in a loop until we have a large enough + * buffer to hold the entire list. + */ + mfi_pd_list * list = 0; + for (unsigned list_size = 1024; ; ) { + list = reinterpret_cast<mfi_pd_list *>(realloc(list, list_size)); + if (!list) + throw std::bad_alloc(); + memset(list, 0, list_size); + if (megaraid_dcmd_cmd(devname, 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 */ + smart_device * dev = new freebsd_megaraid_device(this, devname, list->addr[i].device_id); + devlist.push_back(dev); + } + free(list); + return (0); +} + +#if (FREEBSDVER < 800000) // without this build fail on FreeBSD 8 +static char done[USB_MAX_DEVICES]; + +static int usbdevinfo(int f, int a, int rec, int busno, unsigned short & vendor_id, + unsigned short & product_id, unsigned short & version) +{ + + struct usb_device_info di; + int e, p, i; + char devname[256]; + + snprintf(devname, sizeof(devname),"umass%d",busno); + + di.udi_addr = a; + e = ioctl(f, USB_DEVICEINFO, &di); + if (e) { + if (errno != ENXIO) + printf("addr %d: I/O error\n", a); + return 0; + } + done[a] = 1; + + // list devices + for (i = 0; i < USB_MAX_DEVNAMES; i++) { + if (di.udi_devnames[i][0]) { + if(strcmp(di.udi_devnames[i],devname)==0) { + // device found! + vendor_id = di.udi_vendorNo; + product_id = di.udi_productNo; + version = di.udi_releaseNo; + return 1; + // FIXME + } + } + } + if (!rec) + return 0; + for (p = 0; p < di.udi_nports; p++) { + int s = di.udi_ports[p]; + if (s >= USB_MAX_DEVICES) { + continue; + } + if (s == 0) + printf("addr 0 should never happen!\n"); + else { + if(usbdevinfo(f, s, 1, busno, vendor_id, product_id, version)) return 1; + } + } + return 0; +} +#endif + + +static int usbdevlist(int busno,unsigned short & vendor_id, + unsigned short & product_id, unsigned short & version) +{ +#if (FREEBSDVER >= 800000) // libusb2 interface + struct libusb20_device *pdev = NULL; + struct libusb20_backend *pbe; + uint32_t matches = 0; + char buf[128]; // do not change! + char devname[128]; + uint8_t n; + struct LIBUSB20_DEVICE_DESC_DECODED *pdesc; + + pbe = libusb20_be_alloc_default(); + + while ((pdev = libusb20_be_device_foreach(pbe, pdev))) { + matches++; + + if (libusb20_dev_open(pdev, 0)) { + warnx("libusb20_dev_open: could not open device"); + return 0; + } + + pdesc=libusb20_dev_get_device_desc(pdev); + + snprintf(devname, sizeof(devname),"umass%d:",busno); + for (n = 0; n != 255; n++) { + if (libusb20_dev_get_iface_desc(pdev, n, buf, sizeof(buf))) + break; + if (buf[0] == 0) + continue; + if(strncmp(buf,devname,strlen(devname))==0){ + // found! + vendor_id = pdesc->idVendor; + product_id = pdesc->idProduct; + version = pdesc->bcdDevice; + libusb20_dev_close(pdev); + libusb20_be_free(pbe); + return 1; + } + } + + libusb20_dev_close(pdev); + } + + if (matches == 0) { + printf("No device match or lack of permissions.\n"); + } + + libusb20_be_free(pbe); + + return false; +#else // freebsd < 8.0 USB stack, ioctl interface + + int i, a, rc; + char buf[50]; + int ncont; + + for (ncont = 0, i = 0; i < 10; i++) { + snprintf(buf, sizeof(buf), "%s%d", USBDEV, i); + int f = open(buf, O_RDONLY); + if (f >= 0) { + memset(done, 0, sizeof done); + for (a = 1; a < USB_MAX_DEVICES; a++) { + if (!done[a]) { + rc = usbdevinfo(f, a, 1, busno,vendor_id, product_id, version); + if(rc) return 1; + } + + } + close(f); + } else { + if (errno == ENOENT || errno == ENXIO) + continue; + warn("%s", buf); + } + ncont++; + } + return 0; +#endif +} + +smart_device * freebsd_smart_interface::autodetect_smart_device(const char * name) +{ + unsigned short vendor_id = 0, product_id = 0, version = 0; + struct cam_device *cam_dev; + union ccb ccb; + int i; + const char * test_name = name; + + memset(&ccb, 0, sizeof(ccb)); + + // if dev_name null, or string length zero + if (!name || !*name) + return 0; + + // 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(); + } + } + + // check ATA bus + char * * atanames = 0; int numata = 0; + numata = get_dev_names_ata(&atanames); + if (numata > 0) { + // check ATA/ATAPI devices + for (i = 0; i < numata; i++) { + if(!strcmp(atanames[i],test_name)) { + for (int c = i; c < numata; c++) free(atanames[c]); + free(atanames); + return new freebsd_ata_device(this, test_name, ""); + } + else free(atanames[i]); + } + free(atanames); + } + else { + if (numata < 0) + pout("Unable to get ATA device list\n"); + } + + // check CAM + std::vector<std::string> scsinames; + if (!get_dev_names_cam(scsinames, true)) + pout("Unable to get CAM device list\n"); + else if (!scsinames.empty()) { + // check all devices on CAM bus + for (i = 0; i < (int)scsinames.size(); i++) { + if(strcmp(scsinames[i].c_str(), test_name)==0) + { // our disk device is CAM + if(strncmp(scsinames[i].c_str(), "/dev/pmp", strlen("/dev/pmp")) == 0) { + pout("Skipping port multiplier [%s]\n", scsinames[i].c_str()); + set_err(EINVAL); + return 0; + } + if ((cam_dev = cam_open_device(test_name, O_RDWR)) == NULL) { + // open failure + set_err(errno); + return 0; + } + // zero the payload + memset(&(&ccb.ccb_h)[1], 0, PATHINQ_SETTINGS_SIZE); + ccb.ccb_h.func_code = XPT_PATH_INQ; // send PATH_INQ to the device + if (ioctl(cam_dev->fd, CAMIOCOMMAND, &ccb) == -1) { + warn("Get Transfer Settings CCB failed\n" + "%s", strerror(errno)); + cam_close_device(cam_dev); + return 0; + } + // now check if we are working with USB device, see umass.c + if(strcmp(ccb.cpi.dev_name,"umass-sim") == 0) { // USB device found + int bus=ccb.cpi.unit_number; // unit_number will match umass number + cam_close_device(cam_dev); + if(usbdevlist(bus,vendor_id, product_id, version)){ + const char * usbtype = get_usb_dev_type_by_id(vendor_id, product_id, version); + if (usbtype) + return get_scsi_passthrough_device(usbtype, new freebsd_scsi_device(this, test_name, "")); + } + return 0; + } +#if FREEBSDVER > 800100 + // check if we have ATA device connected to CAM (ada) + if(ccb.cpi.protocol == PROTO_ATA){ + cam_close_device(cam_dev); + return new freebsd_atacam_device(this, test_name, ""); + } +#endif + // close cam device, we don`t need it anymore + cam_close_device(cam_dev); + // handle as usual scsi + return new freebsd_scsi_device(this, test_name, ""); + } + } + } + // device is LSI raid supported by mfi driver + if(!strncmp("/dev/mfid", test_name, strlen("/dev/mfid"))) { + set_err(EINVAL, "To access disks on LSI RAID load mfip.ko and use /dev/passX or use -d 'megaraid,N' with /dev/mfiX devices"); + return 0; + } + + if(!strncmp(MFI_CTRLR_PREFIX, test_name, strlen(MFI_CTRLR_PREFIX)) || !strncmp(MRSAS_CTRLR_PREFIX, test_name, strlen(MRSAS_CTRLR_PREFIX))) { + set_err(EINVAL, "To access disks on %s use '-d megaraid,N' device type", test_name); + return 0; + } + + // form /dev/nvme* or nvme* + if(!strncmp("/dev/nvme", test_name, strlen("/dev/nvme"))) + return new freebsd_nvme_device(this, name, "", 0 /* use default nsid */); + if(!strncmp("/dev/nvd", test_name, strlen("/dev/nvd"))) + set_err(EINVAL, "To monitor NVMe disks use /dev/nvme* device names"); + + // device type unknown + return 0; +} + + +smart_device * freebsd_smart_interface::get_custom_smart_device(const char * name, const char * type) +{ + int disknum = -1, n1 = -1, n2 = -1; + + if (sscanf(type, "3ware,%n%d%n", &n1, &disknum, &n2) == 1 || n1 == 6) { + // 3Ware ? + static const char * fbsd_dev_twe_ctrl = "/dev/twe"; + static const char * fbsd_dev_twa_ctrl = "/dev/twa"; + static const char * fbsd_dev_tws_ctrl = "/dev/tws"; + int contr = -1; + + if (n2 != (int)strlen(type)) { + set_err(EINVAL, "Option -d 3ware,N requires N to be a non-negative integer"); + return 0; + } + if (!(0 <= disknum && disknum <= 127)) { + set_err(EINVAL, "Option -d 3ware,N (N=%d) must have 0 <= N <= 127", disknum); + return 0; + } + + // guess 3ware device type based on device name + if (str_starts_with(name, fbsd_dev_twa_ctrl) || + str_starts_with(name, fbsd_dev_tws_ctrl) ) { + contr=CONTROLLER_3WARE_9000_CHAR; + } + if (!strncmp(fbsd_dev_twe_ctrl, name, strlen(fbsd_dev_twe_ctrl))){ + contr=CONTROLLER_3WARE_678K_CHAR; + } + + if(contr == -1){ + set_err(EINVAL, "3ware controller type unknown, use %sX, %sX or %sX devices", + fbsd_dev_twe_ctrl, fbsd_dev_twa_ctrl, fbsd_dev_tws_ctrl); + return 0; + } + return new freebsd_escalade_device(this, name, contr, disknum); + } + + // 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)) { + set_err(EINVAL, "Option '-d hpt,L/M/N' supports 2-3 items"); + return 0; + } + if (!(1 <= controller && controller <= 8)) { + set_err(EINVAL, "Option '-d hpt,L/M/N' invalid controller id L supplied"); + return 0; + } + if (!(1 <= channel && channel <= 128)) { + set_err(EINVAL, "Option '-d hpt,L/M/N' invalid channel number M supplied"); + return 0; + } + if (!(1 <= disknum && disknum <= 15)) { + set_err(EINVAL, "Option '-d hpt,L/M/N' invalid pmport number N supplied"); + return 0; + } + return new freebsd_highpoint_device(this, name, controller, channel, disknum); + } + + // CCISS ? + disknum = n1 = n2 = -1; + if (sscanf(type, "cciss,%n%d%n", &n1, &disknum, &n2) == 1 || n1 == 6) { + if (n2 != (int)strlen(type)) { + set_err(EINVAL, "Option -d cciss,N requires N to be a non-negative integer"); + return 0; + } + if (!(0 <= disknum && disknum <= 127)) { + set_err(EINVAL, "Option -d cciss,N (N=%d) must have 0 <= N <= 127", disknum); + return 0; + } + return get_sat_device("sat,auto", new freebsd_cciss_device(this, name, disknum)); + } +#if FREEBSDVER > 800100 + // adaX devices ? + if(!strcmp(type,"atacam")) + return new freebsd_atacam_device(this, name, ""); +#endif + // 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)) { + set_err(EINVAL, "Option -d areca,N/E (N=%d) must have 1 <= N <= 128", disknum); + return 0; + } + if (!(1 <= encnum && encnum <= 8)) { + set_err(EINVAL, "Option -d areca,N/E (E=%d) must have 1 <= E <= 8", encnum); + return 0; + } + return new freebsd_areca_ata_device(this, name, disknum, encnum); + } + + if (sscanf(type, "megaraid,%d", &disknum) == 1) { + return new freebsd_megaraid_device(this, name, disknum); + } + + return 0; +} + +std::string freebsd_smart_interface::get_valid_custom_dev_types_str() +{ + return "3ware,N, hpt,L/M/N, cciss,N, areca,N/E, megaraid,N" +#if FREEBSDVER > 800100 + ", atacam" +#endif + ; +} + +} // namespace + +///////////////////////////////////////////////////////////////////////////// +/// Initialize platform interface and register with smi() + +void smart_interface::init() +{ + static os_freebsd::freebsd_smart_interface the_interface; + smart_interface::set(&the_interface); +} |