diff --git a/smartmontools/os_freebsd.cpp b/smartmontools/os_freebsd.cpp index 064610385aa7f4f53862e805d38a21699d2ef6bc..714ae215786aced36a9713a17a1155fedf11fa51 100644 --- a/smartmontools/os_freebsd.cpp +++ b/smartmontools/os_freebsd.cpp @@ -23,6 +23,7 @@ #include <camlib.h> #include <cam/scsi/scsi_message.h> #include <cam/scsi/scsi_pass.h> +#include <dev/usb/usb.h> #if defined(__DragonFly__) #include <sys/nata.h> #else @@ -30,12 +31,10 @@ #endif #include <sys/stat.h> #include <unistd.h> -#include <fcntl.h> #include <glob.h> -#include <fcntl.h> #include <stddef.h> #include <paths.h> - +#include <sys/utsname.h> #include "config.h" #include "int64.h" @@ -46,9 +45,29 @@ #include "extern.h" #include "os_freebsd.h" -static __unused const char *filenameandversion="$Id: os_freebsd.cpp,v 1.73 2009/01/14 02:39:00 sxzzsf Exp $"; - -const char *os_XXXX_c_cvsid="$Id: os_freebsd.cpp,v 1.73 2009/01/14 02:39:00 sxzzsf Exp $" \ +#include "dev_interface.h" +#include "dev_ata_cmd_set.h" + +#define USBDEV "/dev/usb" + +#define CONTROLLER_UNKNOWN 0x00 +#define CONTROLLER_ATA 0x01 +#define CONTROLLER_SCSI 0x02 +#define CONTROLLER_3WARE 0x03 // set by -d option, but converted to one of three types below +#define CONTROLLER_3WARE_678K 0x04 // NOT set by guess_device_type() +#define CONTROLLER_3WARE_9000_CHAR 0x05 // set by guess_device_type() +#define CONTROLLER_3WARE_678K_CHAR 0x06 // set by guess_device_type() +#define CONTROLLER_MARVELL_SATA 0x07 // SATA drives behind Marvell controllers +#define CONTROLLER_SAT 0x08 // SATA device behind a SCSI ATA Translation (SAT) layer +#define CONTROLLER_HPT 0x09 // SATA drives behind HighPoint Raid controllers +#define CONTROLLER_CCISS 0x10 // CCISS controller +#define CONTROLLER_PARSEDEV 0x11 // "smartctl -r ataioctl,2 ..." output parser pseudo-device +#define CONTROLLER_USBCYPRESS 0x12 // ATA device behind Cypress USB bridge +#define CONTROLLER_ARECA 0x13 // Areca controller + +static __unused const char *filenameandversion="$Id$"; + +const char *os_XXXX_c_cvsid="$Id$" \ ATACMDS_H_CVSID CCISS_H_CVSID CONFIG_H_CVSID INT64_H_CVSID OS_FREEBSD_H_CVSID SCSICMDS_H_CVSID UTILITY_H_CVSID; extern smartmonctrl * con; @@ -58,139 +77,8 @@ extern smartmonctrl * con; struct freebsd_dev_channel *devicetable[FREEBSD_MAXDEV]; // forward declaration -static int parse_ata_chan_dev(const char * dev_name, struct freebsd_dev_channel *ch); - -// print examples for smartctl -void print_smartctl_examples(){ - printf("=================================================== SMARTCTL EXAMPLES =====\n\n"); -#ifdef HAVE_GETOPT_LONG - printf( - " 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" - " (Prints all SMART information for ATA disk on\n" - " third port of first 3ware RAID controller)\n" - ); -#else - printf( - " smartctl -a /dev/ad0 (Prints all SMART information)\n" - " smartctl -s on -o on -S on /dev/ad0 (Enables SMART on first disk)\n" - " smartctl -t long /dev/ad0 (Executes extended disk self-test)\n" - " smartctl -A -l selftest -q errorsonly /dev/ad0\n" - " (Prints Self-Test & Attribute errors)\n" - " smartctl -a -d 3ware,2 /dev/twa0\n" - " smartctl -a -d 3ware,2 /dev/twe0\n" - ); -#endif - return; -} - -// Like open(). Return positive integer handle, used by functions below only. -int deviceopen (const char* dev, __unused char* mode) { - struct freebsd_dev_channel *fdchan; - int parse_ok, i; - - // Search table for a free entry - for (i=0; i<FREEBSD_MAXDEV; i++) - if (!devicetable[i]) - break; - - // If no free entry found, return error. We have max allowed number - // of "file descriptors" already allocated. - if (i == FREEBSD_MAXDEV) { - errno = EMFILE; - return -1; - } - - fdchan = (struct freebsd_dev_channel *)calloc(1,sizeof(struct freebsd_dev_channel)); - if (fdchan == NULL) { - // errno already set by call to malloc() - return -1; - } - - parse_ok = parse_ata_chan_dev(dev,fdchan); - if (parse_ok == CONTROLLER_UNKNOWN) { - free(fdchan); - errno = ENOTTY; - return -1; // can't handle what we don't know - } - - if (parse_ok == CONTROLLER_ATA) { -#ifdef IOCATAREQUEST - if ((fdchan->device = open(dev,O_RDONLY))<0) { -#else - if ((fdchan->atacommand = open("/dev/ata",O_RDWR))<0) { -#endif - int myerror = errno; // preserve across free call - free(fdchan); - errno = myerror; - return -1; - } - } - - if (parse_ok == CONTROLLER_3WARE_678K_CHAR) { - char buf[512]; - sprintf(buf,"/dev/twe%d",fdchan->device); -#ifdef IOCATAREQUEST - if ((fdchan->device = open(buf,O_RDWR))<0) { -#else - if ((fdchan->atacommand = open(buf,O_RDWR))<0) { -#endif - int myerror = errno; // preserve across free call - free(fdchan); - errno = myerror; - return -1; - } - } - - if (parse_ok == CONTROLLER_3WARE_9000_CHAR) { - char buf[512]; - sprintf(buf,"/dev/twa%d",fdchan->device); -#ifdef IOCATAREQUEST - if ((fdchan->device = open(buf,O_RDWR))<0) { -#else - if ((fdchan->atacommand = open(buf,O_RDWR))<0) { -#endif - int myerror = errno; // preserve across free call - free(fdchan); - errno = myerror; - return -1; - } - } - - if (parse_ok == CONTROLLER_HPT) { - if ((fdchan->device = open(dev,O_RDWR))<0) { - int myerror = errno; // preserve across free call - free(fdchan); - errno = myerror; - return -1; - } - } - - if (parse_ok == CONTROLLER_CCISS) { - if ((fdchan->device = open(dev,O_RDWR))<0) { - int myerror = errno; // preserve across free call - free(fdchan); - errno = myerror; - return -1; - } - } +// static int parse_ata_chan_dev(const char * dev_name, struct freebsd_dev_channel *ch); - if (parse_ok == CONTROLLER_SCSI) { - // this is really a NO-OP, as the parse takes care - // of filling in correct details - } - - // return pointer to "file descriptor" table entry, properly offset. - devicetable[i]=fdchan; - return i+FREEBSD_FDOFFSET; -} // Returns 1 if device not available/open/found else 0. Also shifts fd into valid range. static int isnotopen(int *fd, struct freebsd_dev_channel** fdchan) { @@ -206,39 +94,6 @@ static int isnotopen(int *fd, struct freebsd_dev_channel** fdchan) { return 0; } -// Like close(). Acts on handles returned by above function. -int deviceclose (int fd) { - struct freebsd_dev_channel *fdchan; - int failed = 0; - - // check for valid file descriptor - if (isnotopen(&fd, &fdchan)) - return -1; - - - // did we allocate a SCSI device name? - if (fdchan->devname) - free(fdchan->devname); - - // close device, if open - if (fdchan->device) - failed=close(fdchan->device); -#ifndef IOCATAREQUEST - if (fdchan->atacommand) - failed=close(fdchan->atacommand); -#endif - - // if close succeeded, then remove from device list - // Eduard, should we also remove it from list if close() fails? I'm - // not sure. Here I only remove it from list if close() worked. - if (!failed) { - free(fdchan); - devicetable[fd]=NULL; - } - - return failed; -} - #define NO_RETURN 0 #define BAD_SMART 1 #define NO_DISK_3WARE 2 @@ -269,584 +124,806 @@ void printwarning(int msgNo, const char* extra) { return; } -// Interface to ATA devices. See os_linux.c -int marvell_command_interface(__unused int fd, __unused smart_command_set command, __unused int select, __unused char *data) { - return -1; -} +// Interface to ATA devices behind 3ware escalade RAID controller cards. See os_linux.c -int highpoint_command_interface(int fd, smart_command_set command, int select, char *data) { - int ids[2]; - struct freebsd_dev_channel* fbcon; - 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; +#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) ) - // check that "file descriptor" is valid - if (isnotopen(&fd, &fbcon)) - return -1; - // get internal deviceid - ids[0] = con->hpt_data[0] - 1; - ids[1] = con->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 (con->hpt_data[2]==1) { - param.ctrl_code = HPT_IOCTL_GET_CHANNEL_INFO; - param.out_size = sizeof(HPT_CHANNEL_INFO); - } - if (ioctl(fbcon->device, HPT_DO_IOCONTROL, ¶m)!=0 || - info.devices[con->hpt_data[2]-1]==0) { - return -1; - } - // perform smart action - memset(buff, 0, 512 + 2 * sizeof(HPT_PASS_THROUGH_HEADER)); - pide_pt_hdr = (PHPT_PASS_THROUGH_HEADER)buff; +#ifndef ATA_DEVICE +#define ATA_DEVICE "/dev/ata" +#endif - pide_pt_hdr->lbamid = 0x4f; - pide_pt_hdr->lbahigh = 0xc2; - pide_pt_hdr->command = ATA_SMART_CMD; - pide_pt_hdr->id = info.devices[con->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)); +// global variable holding byte count of allocated memory +long long bytes; - 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; +/* + * dev_legacy.cpp + * + * Home page of code is: http://smartmontools.sourceforge.net + * + * Copyright (C) 2008 Christian Franke <smartmontools-support@lists.sourceforge.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * You should have received a copy of the GNU General Public License + * (for example COPYING); If not, see <http://www.gnu.org/licenses/>. + * + */ - if ((ioctl(fbcon->device, 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]; - sprintf(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; -} - -int areca_command_interface(__unused int fd, __unused int disknum, __unused smart_command_set command, __unused int select, __unused char *data) { - return -1; -} - -int ata_command_interface(int fd, smart_command_set command, int select, char *data) { -#if !defined(ATAREQUEST) && !defined(IOCATAREQUEST) - // sorry, but without ATAng, we can't do anything here - printwarning(BAD_KERNEL,NULL); - errno = ENOSYS; - return -1; -#else - struct freebsd_dev_channel* con; - int retval, copydata=0; -#ifdef IOCATAREQUEST - struct ata_ioc_request request; -#else - struct ata_cmd iocmd; -#endif - unsigned char buff[512]; - // check that "file descriptor" is valid - if (isnotopen(&fd,&con)) - return -1; +const char * dev_freebsd_cpp_cvsid = "$Id$" + DEV_INTERFACE_H_CVSID; - bzero(buff,512); +extern smartmonctrl * con; // con->reportscsiioctl -#ifdef IOCATAREQUEST - bzero(&request,sizeof(struct ata_ioc_request)); -#else - bzero(&iocmd,sizeof(struct ata_cmd)); -#endif - bzero(buff,512); +///////////////////////////////////////////////////////////////////////////// -#ifndef IOCATAREQUEST - iocmd.cmd=ATAREQUEST; - iocmd.channel=con->channel; - iocmd.device=con->device; -#define request iocmd.u.request +#ifdef HAVE_ATA_IDENTIFY_IS_CACHED +int ata_identify_is_cached(int fd); #endif - request.u.ata.command=ATA_SMART_CMD; - request.timeout=600; - switch (command){ - case READ_VALUES: - request.u.ata.feature=ATA_SMART_READ_VALUES; - request.u.ata.lba=0xc24f<<8; - request.flags=ATA_CMD_READ; - request.data=(char *)buff; - request.count=512; - copydata=1; - break; - case READ_THRESHOLDS: - request.u.ata.feature=ATA_SMART_READ_THRESHOLDS; - request.u.ata.count=1; - request.u.ata.lba=1|(0xc24f<<8); - request.flags=ATA_CMD_READ; - request.data=(char *)buff; - request.count=512; - copydata=1; - break; - case READ_LOG: - request.u.ata.feature=ATA_SMART_READ_LOG_SECTOR; - request.u.ata.lba=select|(0xc24f<<8); - request.u.ata.count=1; - request.flags=ATA_CMD_READ; - request.data=(char *)buff; - request.count=512; - copydata=1; - break; - case IDENTIFY: - request.u.ata.command=ATA_IDENTIFY_DEVICE; - request.flags=ATA_CMD_READ; - request.data=(char *)buff; - request.count=512; - copydata=1; - break; - case PIDENTIFY: - request.u.ata.command=ATA_IDENTIFY_PACKET_DEVICE; - request.flags=ATA_CMD_READ; - request.data=(char *)buff; - request.count=512; - copydata=1; - break; - case ENABLE: - request.u.ata.feature=ATA_SMART_ENABLE; - request.u.ata.lba=0xc24f<<8; - request.flags=ATA_CMD_CONTROL; - break; - case DISABLE: - request.u.ata.feature=ATA_SMART_DISABLE; - request.u.ata.lba=0xc24f<<8; - request.flags=ATA_CMD_CONTROL; - break; - case AUTO_OFFLINE: - // NOTE: According to ATAPI 4 and UP, this command is obsolete - request.u.ata.feature=ATA_SMART_AUTO_OFFLINE; - request.u.ata.lba=0xc24f<<8; - request.u.ata.count=select; - request.flags=ATA_CMD_CONTROL; - break; - case AUTOSAVE: - request.u.ata.feature=ATA_SMART_AUTOSAVE; - request.u.ata.lba=0xc24f<<8; - request.u.ata.count=select; - request.flags=ATA_CMD_CONTROL; - break; - case IMMEDIATE_OFFLINE: - request.u.ata.feature=ATA_SMART_IMMEDIATE_OFFLINE; - request.u.ata.lba = select|(0xc24f<<8); // put test in sector - request.flags=ATA_CMD_CONTROL; - break; - case STATUS_CHECK: // same command, no HDIO in FreeBSD - case STATUS: - // this command only says if SMART is working. It could be - // replaced with STATUS_CHECK below. - request.u.ata.feature=ATA_SMART_STATUS; - request.u.ata.lba=0xc24f<<8; - request.flags=ATA_CMD_CONTROL; - break; - case CHECK_POWER_MODE: - request.u.ata.command=ATA_CHECK_POWER_MODE; - request.u.ata.feature=0; - request.flags=ATA_CMD_CONTROL; - break; - case WRITE_LOG: - memcpy(buff, data, 512); - request.u.ata.feature=ATA_SMART_WRITE_LOG_SECTOR; - request.u.ata.lba=select|(0xc24f<<8); - request.u.ata.count=1; - request.flags=ATA_CMD_WRITE; - request.data=(char *)buff; - request.count=512; - break; - default: - pout("Unrecognized command %d in ata_command_interface()\n" - "Please contact " PACKAGE_BUGREPORT "\n", command); - errno=ENOSYS; - 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; - -#ifdef IOCATAREQUEST - if ((retval=ioctl(con->device, IOCATAREQUEST, &request)) || request.error) -#else - if ((retval=ioctl(con->atacommand, IOCATA, &iocmd)) || request.error) -#endif - return -1; +///////////////////////////////////////////////////////////////////////////// -#if __FreeBSD_version < 502000 - printwarning(NO_RETURN,NULL); -#endif +namespace os_freebsd { // No need to publish anything, name provided for Doxygen - high = (request.u.ata.lba >> 16) & 0xff; - low = (request.u.ata.lba >> 8) & 0xff; - - // 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]; - sprintf(buf,"CMD=0x%02x\nFR =0x%02x\nNS =0x%02x\nSC =0x%02x\nCL =0x%02x\nCH =0x%02x\nRETURN =0x%04x\n", - (int)request.u.ata.command, - (int)request.u.ata.feature, - (int)request.u.ata.count, - (int)((request.u.ata.lba) & 0xff), - (int)((request.u.ata.lba>>8) & 0xff), - (int)((request.u.ata.lba>>16) & 0xff), - (int)request.error); - printwarning(BAD_SMART,buf); - return 0; - } +///////////////////////////////////////////////////////////////////////////// +/// Implement shared open/close routines with old functions. -#ifdef IOCATAREQUEST - if ((retval=ioctl(con->device, IOCATAREQUEST, &request)) || request.error) -#else - if ((retval=ioctl(con->atacommand, IOCATA, &iocmd)) || request.error) -#endif - { - return -1; - } - // - if (command == CHECK_POWER_MODE) { - data[0] = request.u.ata.count & 0xff; - return 0; - } - if (copydata) - memcpy(data, buff, 512); - - return 0; -#endif -} +class freebsd_smart_device +: virtual public /*implements*/ smart_device +{ +public: + explicit freebsd_smart_device(const char * mode) + : smart_device(never_called), + m_fd(-1), m_mode(mode) { } + virtual ~freebsd_smart_device() throw(); -// Interface to SCSI devices. See os_linux.c -int do_normal_scsi_cmnd_io(int fd, struct scsi_cmnd_io * iop, int report) -{ - struct freebsd_dev_channel* con = NULL; - struct cam_device* cam_dev = NULL; - union ccb *ccb; - - - if (report > 0) { - unsigned int k; - const unsigned char * ucp = iop->cmnd; - const char * np; + virtual bool is_open() const; - np = scsi_get_opcode_name(ucp[0]); - pout(" [%s: ", np ? np : "<unknown opcode>"); - for (k = 0; k < iop->cmnd_len; ++k) - pout("%02x ", ucp[k]); - if ((report > 1) && - (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) { - int trunc = (iop->dxfer_len > 256) ? 1 : 0; + virtual bool open(); - 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("]"); - } + virtual bool close(); - // check that "file descriptor" is valid - if (isnotopen(&fd,&con)) - return -ENOTTY; +protected: + /// Return filedesc for derived classes. + int get_fd() const + { return m_fd; } +private: + int m_fd; ///< filedesc, -1 if not open. + const char * m_mode; ///< Mode string for deviceopen(). +}; - if (!(cam_dev = cam_open_spec_device(con->devname,con->unitnum,O_RDWR,NULL))) { - warnx("%s",cam_errbuf); - return -EIO; - } - if (!(ccb = cam_getccb(cam_dev))) { - warnx("error allocating ccb"); - return -ENOMEM; - } +freebsd_smart_device::~freebsd_smart_device() throw() +{ + if (m_fd >= 0) + os_freebsd::freebsd_smart_device::close(); +} - // clear out structure, except for header that was filled in for us - bzero(&(&ccb->ccb_h)[1], - sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); +// migration from the old_style +unsigned char m_controller_type; +unsigned char m_controller_port; - cam_fill_csio(&ccb->csio, - /*retrires*/ 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, - /* timout (converted to seconds) */ iop->timeout*1000); - memcpy(ccb->csio.cdb_io.cdb_bytes,iop->cmnd,iop->cmnd_len); +// 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" + " (Prints all SMART information for ATA disk on\n" + " third port of first 3ware RAID controller)\n" + ; - if (cam_send_ccb(cam_dev,ccb) < 0) { - warn("error sending SCSI ccb"); - #if __FreeBSD_version > 500000 - cam_error_print(cam_dev,ccb,CAM_ESF_ALL,CAM_EPF_ALL,stderr); - #endif - cam_freeccb(ccb); - return -EIO; - } +bool freebsd_smart_device::is_open() const +{ + return (m_fd >= 0); +} - if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { - #if __FreeBSD_version > 500000 - cam_error_print(cam_dev,ccb,CAM_ESF_ALL,CAM_EPF_ALL,stderr); - #endif - cam_freeccb(ccb); - return -EIO; - } - if (iop->sensep) { - memcpy(iop->sensep,&(ccb->csio.sense_data),sizeof(struct scsi_sense_data)); - iop->resp_sense_len = sizeof(struct scsi_sense_data); +static int hpt_hba(const char* name) { + int i=0; + const char *hpt_node[]={"hptmv", "hptmv6", "hptrr", "hptiop", "hptmviop", "hpt32xx", "rr2320", + "rr232x", "rr2310", "rr2310_00", "rr2300", "rr2340", "rr1740", NULL}; + while (hpt_node[i]) { + if (!strncmp(name, hpt_node[i], strlen(hpt_node[i]))) + return 1; + i++; } + return 0; +} - iop->scsi_status = ccb->csio.scsi_status; +static int get_tw_channel_unit (const char* name, int* unit, int* dev) { + const char *p; - cam_freeccb(ccb); - - if (cam_dev) - cam_close_device(cam_dev); + /* device node sanity check */ + for (p = name + 3; *p; p++) + if (*p < '0' || *p > '9') + return -1; + if (strlen(name) > 4 && *(name + 3) == '0') + return -1; - if (report > 0) { - int trunc; + if (dev != NULL) + *dev=atoi(name + 3); - pout(" status=0\n"); - trunc = (iop->dxfer_len > 256) ? 1 : 0; - - pout(" Incoming data, len=%d%s:\n", (int)iop->dxfer_len, - (trunc ? " [only first 256 bytes shown]" : "")); - dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); - } + /* no need for unit number */ + if (unit != NULL) + *unit=0; return 0; } -/* Check and call the right interface. Maybe when the do_generic_scsi_cmd_io interface is better - we can take off this crude way of calling the right interface */ -int do_scsi_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report) -{ -struct freebsd_dev_channel *fdchan; - switch(con->controller_type) - { - case CONTROLLER_CCISS: -#ifdef HAVE_DEV_CISS_CISSIO_H - // check that "file descriptor" is valid - if (isnotopen(&dev_fd,&fdchan)) - return -ENOTTY; - return cciss_io_interface(fdchan->device, con->controller_port-1, iop, report); +#ifndef IOCATAREQUEST +static int get_ata_channel_unit ( const char* name, int* unit, int* dev) { +#ifndef ATAREQUEST + *dev=0; + *unit=0; +return 0; #else - { - static int warned = 0; - if (!warned) { - pout("CCISS support is not available in this build of smartmontools,\n" - "/usr/src/sys/dev/ciss/cissio.h was not available at build time.\n\n"); - warned = 1; - } - } - return -ENOSYS; + // there is no direct correlation between name 'ad0, ad1, ...' and + // channel/unit number. So we need to iterate through the possible + // channels and check each unit to see if we match names + struct ata_cmd iocmd; + int fd,maxunit; + + bzero(&iocmd, sizeof(struct ata_cmd)); + + if ((fd = open(ATA_DEVICE, O_RDWR)) < 0) + return -errno; + + iocmd.cmd = ATAGMAXCHANNEL; + if (ioctl(fd, IOCATA, &iocmd) < 0) { + return -errno; + close(fd); + } + maxunit = iocmd.u.maxchan; + for (*unit = 0; *unit < maxunit; (*unit)++) { + iocmd.channel = *unit; + iocmd.device = -1; + iocmd.cmd = ATAGPARM; + if (ioctl(fd, IOCATA, &iocmd) < 0) { + close(fd); + return -errno; + } + if (iocmd.u.param.type[0] && !strcmp(name,iocmd.u.param.name[0])) { + *dev = 0; + break; + } + if (iocmd.u.param.type[1] && !strcmp(name,iocmd.u.param.name[1])) { + *dev = 1; + break; + } + } + close(fd); + if (*unit == maxunit) + return -1; + else + return 0; #endif - // not reached - break; - default: - return do_normal_scsi_cmnd_io(dev_fd, iop, report); - // not reached - break; - } } +#endif -// Interface to ATA devices behind 3ware escalade RAID controller cards. See os_linux.c +// Guess device type (ata or scsi) based on device name (FreeBSD +// specific) SCSI device name in FreeBSD can be sd, sr, scd, st, nst, +// osst, nosst and sg. +static const char * fbsd_dev_prefix = _PATH_DEV; +static const char * fbsd_dev_ata_disk_prefix = "ad"; +static const char * fbsd_dev_scsi_disk_plus = "da"; +static const char * fbsd_dev_scsi_pass = "pass"; +static const char * fbsd_dev_scsi_tape1 = "sa"; +static const char * fbsd_dev_scsi_tape2 = "nsa"; +static const char * fbsd_dev_scsi_tape3 = "esa"; +static const char * fbsd_dev_twe_ctrl = "twe"; +static const char * fbsd_dev_twa_ctrl = "twa"; +static const char * fbsd_dev_cciss = "ciss"; -#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) ) +int parse_ata_chan_dev(const char * dev_name, struct freebsd_dev_channel *chan) { + int len; + int dev_prefix_len = strlen(fbsd_dev_prefix); + + // if dev_name null, or string length zero + if (!dev_name || !(len = strlen(dev_name))) + return CONTROLLER_UNKNOWN; + + // Remove the leading /dev/... if it's there + if (!strncmp(fbsd_dev_prefix, dev_name, dev_prefix_len)) { + if (len <= dev_prefix_len) + // if nothing else in the string, unrecognized + return CONTROLLER_UNKNOWN; + // else advance pointer to following characters + dev_name += dev_prefix_len; + } + // form /dev/ad* or ad* + if (!strncmp(fbsd_dev_ata_disk_prefix, dev_name, + strlen(fbsd_dev_ata_disk_prefix))) { +#ifndef IOCATAREQUEST + if (chan != NULL) { + if (get_ata_channel_unit(dev_name,&(chan->channel),&(chan->device))<0) { + return CONTROLLER_UNKNOWN; + } + } +#endif + return CONTROLLER_ATA; + } -int escalade_command_interface(int fd, int disknum, int escalade_type, smart_command_set command, int select, char *data) { - // to hold true file descriptor - struct freebsd_dev_channel* con; + // form /dev/pass* or pass* + if (!strncmp(fbsd_dev_scsi_pass, dev_name, + strlen(fbsd_dev_scsi_pass))) + goto handlescsi; - // return value and buffer for ioctl() - int ioctlreturn, readdata=0; - struct twe_usercommand* cmd_twe = NULL; - TW_OSLI_IOCTL_NO_DATA_BUF* cmd_twa = NULL; - TWE_Command_ATA* ata = NULL; + // form /dev/da* or da* + if (!strncmp(fbsd_dev_scsi_disk_plus, dev_name, + strlen(fbsd_dev_scsi_disk_plus))) + goto handlescsi; - // Used by both the SCSI and char interfaces - char ioctl_buffer[TW_IOCTL_BUFFER_SIZE]; + // form /dev/sa* or sa* + if (!strncmp(fbsd_dev_scsi_tape1, dev_name, + strlen(fbsd_dev_scsi_tape1))) + goto handlescsi; - if (disknum < 0) { - printwarning(NO_DISK_3WARE,NULL); - return -1; + // form /dev/nsa* or nsa* + if (!strncmp(fbsd_dev_scsi_tape2, dev_name, + strlen(fbsd_dev_scsi_tape2))) + goto handlescsi; + + // form /dev/esa* or esa* + if (!strncmp(fbsd_dev_scsi_tape3, dev_name, + strlen(fbsd_dev_scsi_tape3))) + goto handlescsi; + + if (!strncmp(fbsd_dev_twa_ctrl,dev_name, + strlen(fbsd_dev_twa_ctrl))) { + if (chan != NULL) { + if (get_tw_channel_unit(dev_name,&(chan->channel),&(chan->device))<0) { + return CONTROLLER_UNKNOWN; + } + } + else if (get_tw_channel_unit(dev_name,NULL,NULL)<0) { + return CONTROLLER_UNKNOWN; + } + return CONTROLLER_3WARE_9000_CHAR; } - // check that "file descriptor" is valid - if (isnotopen(&fd,&con)) - return -1; + if (!strncmp(fbsd_dev_twe_ctrl,dev_name, + strlen(fbsd_dev_twe_ctrl))) { + if (chan != NULL) { + if (get_tw_channel_unit(dev_name,&(chan->channel),&(chan->device))<0) { + return CONTROLLER_UNKNOWN; + } + } + else if (get_tw_channel_unit(dev_name,NULL,NULL)<0) { + return CONTROLLER_UNKNOWN; + } + return CONTROLLER_3WARE_678K_CHAR; + } - memset(ioctl_buffer, 0, TW_IOCTL_BUFFER_SIZE); + if (hpt_hba(dev_name)) { + return CONTROLLER_HPT; + } - if (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 = 512; - ata = (TWE_Command_ATA*)&cmd_twa->cmd_pkt.command.cmd_pkt_7k; - } else if (escalade_type==CONTROLLER_3WARE_678K_CHAR) { - cmd_twe = (struct twe_usercommand*)ioctl_buffer; - ata = &cmd_twe->tu_command.ata; - } else { - pout("Unrecognized escalade_type %d in freebsd_3ware_command_interface(disk %d)\n" - "Please contact " PACKAGE_BUGREPORT "\n", escalade_type, disknum); - errno=ENOSYS; - return -1; + // form /dev/ciss* + if (!strncmp(fbsd_dev_cciss, dev_name, + strlen(fbsd_dev_cciss))) + return CONTROLLER_CCISS; + + // we failed to recognize any of the forms + return CONTROLLER_UNKNOWN; + + handlescsi: + if (chan != NULL) { + if (!(chan->devname = (char *)calloc(1,DEV_IDLEN+1))) + return CONTROLLER_UNKNOWN; + + if (cam_get_device(dev_name,chan->devname,DEV_IDLEN,&(chan->unitnum)) == -1) + return CONTROLLER_UNKNOWN; } + return CONTROLLER_SCSI; + +} - ata->opcode = TWE_OP_ATA_PASSTHROUGH; - // Same for (almost) all commands - but some reset below - ata->request_id = 0xFF; - ata->unit = disknum; - ata->status = 0; - ata->flags = 0x1; - ata->drive_head = 0x0; - ata->sector_num = 0; +bool freebsd_smart_device::open() +{ + + const char *dev = get_dev_name(); + struct freebsd_dev_channel *fdchan; + int parse_ok, i; - // All SMART commands use this CL/CH signature. These are magic - // values from the ATA specifications. - ata->cylinder_lo = 0x4F; - ata->cylinder_hi = 0xC2; + // Search table for a free entry + for (i=0; i<FREEBSD_MAXDEV; i++) + if (!devicetable[i]) + break; - // SMART ATA COMMAND REGISTER value - ata->command = ATA_SMART_CMD; + // If no free entry found, return error. We have max allowed number + // of "file descriptors" already allocated. + if (i == FREEBSD_MAXDEV) { + errno = EMFILE; + return false; + } + + fdchan = (struct freebsd_dev_channel *)calloc(1,sizeof(struct freebsd_dev_channel)); + if (fdchan == NULL) { + // errno already set by call to malloc() + return false; + } + + parse_ok = parse_ata_chan_dev(dev,fdchan); - // 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 - if (command == READ_VALUES || - command == READ_THRESHOLDS || - command == READ_LOG || - command == IDENTIFY || - command == WRITE_LOG ) { - readdata=1; - if (escalade_type==CONTROLLER_3WARE_678K_CHAR) { - cmd_twe->tu_data = data; - cmd_twe->tu_size = 512; + if (parse_ok == CONTROLLER_UNKNOWN) { + free(fdchan); + errno = ENOTTY; + return false; // can't handle what we don't know + } + + if (parse_ok == CONTROLLER_ATA) { +#ifdef IOCATAREQUEST + if ((fdchan->device = ::open(dev,O_RDONLY))<0) { +#else + if ((fdchan->atacommand = ::open("/dev/ata",O_RDWR))<0) { +#endif + int myerror = errno; // preserve across free call + free(fdchan); + errno = myerror; + return false; } - ata->sgl_offset = 0x5; + } + + if (parse_ok == CONTROLLER_3WARE_678K_CHAR) { + char buf[512]; + sprintf(buf,"/dev/twe%d",fdchan->device); +#ifdef IOCATAREQUEST + if ((fdchan->device = ::open(buf,O_RDWR))<0) { +#else + if ((fdchan->atacommand = ::open(buf,O_RDWR))<0) { +#endif + int myerror = errno; // preserve across free call + free(fdchan); + errno = myerror; + return false; + } + } + + if (parse_ok == CONTROLLER_3WARE_9000_CHAR) { + char buf[512]; + sprintf(buf,"/dev/twa%d",fdchan->device); +#ifdef IOCATAREQUEST + if ((fdchan->device = ::open(buf,O_RDWR))<0) { +#else + if ((fdchan->atacommand = ::open(buf,O_RDWR))<0) { +#endif + int myerror = errno; // preserve across free call + free(fdchan); + errno = myerror; + return false; + } + } + + if (parse_ok == CONTROLLER_HPT) { + if ((fdchan->device = ::open(dev,O_RDWR))<0) { + int myerror = errno; // preserve across free call + free(fdchan); + errno = myerror; + return false; + } + } + + if (parse_ok == CONTROLLER_CCISS) { + if ((fdchan->device = ::open(dev,O_RDWR))<0) { + int myerror = errno; // preserve across free call + free(fdchan); + errno = myerror; + return false; + } + } + + if (parse_ok == CONTROLLER_SCSI) { + // this is really a NO-OP, as the parse takes care + // of filling in correct details + } + + // return pointer to "file descriptor" table entry, properly offset. + devicetable[i]=fdchan; + m_fd = i+FREEBSD_FDOFFSET; + // endofold + if (m_fd < 0) { + set_err((errno==ENOENT || errno==ENOTDIR) ? ENODEV : errno); + return false; + } + return true; +} + +bool freebsd_smart_device::close() +{ + int fd = m_fd; m_fd = -1; + struct freebsd_dev_channel *fdchan; + int failed = 0; + + // check for valid file descriptor + if (isnotopen(&fd, &fdchan)) + return false; + + + // did we allocate a SCSI device name? + if (fdchan->devname) + free(fdchan->devname); + + // close device, if open + if (fdchan->device) + failed=::close(fdchan->device); +#ifndef IOCATAREQUEST + if (fdchan->atacommand) + failed=::close(fdchan->atacommand); +#endif + + // if close succeeded, then remove from device list + // Eduard, should we also remove it from list if close() fails? I'm + // not sure. Here I only remove it from list if close() worked. + if (!failed) { + free(fdchan); + devicetable[fd]=NULL; + } + + return failed; +} + +///////////////////////////////////////////////////////////////////////////// +/// Implement standard ATA support with old functions + +class freebsd_ata_device +: public /*implements*/ ata_device_with_command_set, + public /*extends*/ freebsd_smart_device +{ +public: + freebsd_ata_device(smart_interface * intf, const char * dev_name, const char * req_type); + +#ifdef HAVE_ATA_IDENTIFY_IS_CACHED + virtual bool ata_identify_is_cached() const; +#endif + +protected: + virtual int ata_command_interface(smart_command_set command, int select, char * data); +}; + +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("ATA") +{ +} + +int freebsd_ata_device::ata_command_interface(smart_command_set command, int select, char * data) +{ + int fd=get_fd(); + #if !defined(ATAREQUEST) && !defined(IOCATAREQUEST) + // sorry, but without ATAng, we can't do anything here + printwarning(BAD_KERNEL,NULL); + errno = ENOSYS; + return -1; + #else + struct freebsd_dev_channel* con; + int retval, copydata=0; + #ifdef IOCATAREQUEST + struct ata_ioc_request request; + #else + struct ata_cmd iocmd; + #endif + unsigned char buff[512]; + + // check that "file descriptor" is valid + if (isnotopen(&fd,&con)) + return -1; + + bzero(buff,512); + + #ifdef IOCATAREQUEST + bzero(&request,sizeof(struct ata_ioc_request)); + #else + bzero(&iocmd,sizeof(struct ata_cmd)); + #endif + bzero(buff,512); + + #ifndef IOCATAREQUEST + iocmd.cmd=ATAREQUEST; + iocmd.channel=con->channel; + iocmd.device=con->device; + #define request iocmd.u.request + #endif + + request.u.ata.command=ATA_SMART_CMD; + request.timeout=600; + switch (command){ + case READ_VALUES: + request.u.ata.feature=ATA_SMART_READ_VALUES; + request.u.ata.lba=0xc24f<<8; + request.flags=ATA_CMD_READ; + request.data=(char *)buff; + request.count=512; + copydata=1; + break; + case READ_THRESHOLDS: + request.u.ata.feature=ATA_SMART_READ_THRESHOLDS; + request.u.ata.count=1; + request.u.ata.lba=1|(0xc24f<<8); + request.flags=ATA_CMD_READ; + request.data=(char *)buff; + request.count=512; + copydata=1; + break; + case READ_LOG: + request.u.ata.feature=ATA_SMART_READ_LOG_SECTOR; + request.u.ata.lba=select|(0xc24f<<8); + request.u.ata.count=1; + request.flags=ATA_CMD_READ; + request.data=(char *)buff; + request.count=512; + copydata=1; + break; + case IDENTIFY: + request.u.ata.command=ATA_IDENTIFY_DEVICE; + request.flags=ATA_CMD_READ; + request.data=(char *)buff; + request.count=512; + copydata=1; + break; + case PIDENTIFY: + request.u.ata.command=ATA_IDENTIFY_PACKET_DEVICE; + request.flags=ATA_CMD_READ; + request.data=(char *)buff; + request.count=512; + copydata=1; + break; + case ENABLE: + request.u.ata.feature=ATA_SMART_ENABLE; + request.u.ata.lba=0xc24f<<8; + request.flags=ATA_CMD_CONTROL; + break; + case DISABLE: + request.u.ata.feature=ATA_SMART_DISABLE; + request.u.ata.lba=0xc24f<<8; + request.flags=ATA_CMD_CONTROL; + break; + case AUTO_OFFLINE: + // NOTE: According to ATAPI 4 and UP, this command is obsolete + request.u.ata.feature=ATA_SMART_AUTO_OFFLINE; + request.u.ata.lba=0xc24f<<8; + request.u.ata.count=select; + request.flags=ATA_CMD_CONTROL; + break; + case AUTOSAVE: + request.u.ata.feature=ATA_SMART_AUTOSAVE; + request.u.ata.lba=0xc24f<<8; + request.u.ata.count=select; + request.flags=ATA_CMD_CONTROL; + break; + case IMMEDIATE_OFFLINE: + request.u.ata.feature=ATA_SMART_IMMEDIATE_OFFLINE; + request.u.ata.lba = select|(0xc24f<<8); // put test in sector + request.flags=ATA_CMD_CONTROL; + break; + case STATUS_CHECK: // same command, no HDIO in FreeBSD + case STATUS: + // this command only says if SMART is working. It could be + // replaced with STATUS_CHECK below. + request.u.ata.feature=ATA_SMART_STATUS; + request.u.ata.lba=0xc24f<<8; + request.flags=ATA_CMD_CONTROL; + break; + case CHECK_POWER_MODE: + request.u.ata.command=ATA_CHECK_POWER_MODE; + request.u.ata.feature=0; + request.flags=ATA_CMD_CONTROL; + break; + case WRITE_LOG: + memcpy(buff, data, 512); + request.u.ata.feature=ATA_SMART_WRITE_LOG_SECTOR; + request.u.ata.lba=select|(0xc24f<<8); + request.u.ata.count=1; + request.flags=ATA_CMD_WRITE; + request.data=(char *)buff; + request.count=512; + break; + default: + pout("Unrecognized command %d in ata_command_interface()\n" + "Please contact " PACKAGE_BUGREPORT "\n", command); + errno=ENOSYS; + 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; + + #ifdef IOCATAREQUEST + if ((retval=ioctl(con->device, IOCATAREQUEST, &request)) || request.error) + #else + if ((retval=ioctl(con->atacommand, IOCATA, &iocmd)) || request.error) + #endif + return -1; + + #if __FreeBSD_version < 502000 + printwarning(NO_RETURN,NULL); + #endif + + high = (request.u.ata.lba >> 16) & 0xff; + low = (request.u.ata.lba >> 8) & 0xff; + + // 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]; + sprintf(buf,"CMD=0x%02x\nFR =0x%02x\nNS =0x%02x\nSC =0x%02x\nCL =0x%02x\nCH =0x%02x\nRETURN =0x%04x\n", + (int)request.u.ata.command, + (int)request.u.ata.feature, + (int)request.u.ata.count, + (int)((request.u.ata.lba) & 0xff), + (int)((request.u.ata.lba>>8) & 0xff), + (int)((request.u.ata.lba>>16) & 0xff), + (int)request.error); + printwarning(BAD_SMART,buf); + return 0; + } + + #ifdef IOCATAREQUEST + if ((retval=ioctl(con->device, IOCATAREQUEST, &request)) || request.error) + #else + if ((retval=ioctl(con->atacommand, IOCATA, &iocmd)) || request.error) + #endif + { + return -1; + } + // + if (command == CHECK_POWER_MODE) { + data[0] = request.u.ata.count & 0xff; + return 0; + } + if (copydata) + memcpy(data, buff, 512); + + return 0; + #endif +} + +#ifdef HAVE_ATA_IDENTIFY_IS_CACHED +bool freebsd_ata_device::ata_identify_is_cached() const +{ + return !!::ata_identify_is_cached(get_fd()); +} +#endif + + +///////////////////////////////////////////////////////////////////////////// +/// Implement AMCC/3ware RAID support with old functions + +class freebsd_escalade_device +: public /*implements*/ ata_device_with_command_set, + public /*extends*/ freebsd_smart_device +{ +public: + freebsd_escalade_device(smart_interface * intf, const char * dev_name, + int escalade_type, int disknum); + +protected: + virtual int ata_command_interface(smart_command_set command, int select, char * data); + +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( + escalade_type==CONTROLLER_3WARE_9000_CHAR ? "ATA_3WARE_9000" : + escalade_type==CONTROLLER_3WARE_678K_CHAR ? "ATA_3WARE_678K" : + /* CONTROLLER_3WARE_678K */ "ATA" ), + m_escalade_type(escalade_type), m_disknum(disknum) +{ + set_info().info_name = strprintf("%s [3ware_disk_%02d]", dev_name, disknum); +} + +int freebsd_escalade_device::ata_command_interface(smart_command_set command, int select, char * data) +{ + // to hold true file descriptor + int fd = get_fd(); + struct freebsd_dev_channel* con; + + // return value and buffer for ioctl() + int ioctlreturn, readdata=0; + 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 -1; + } + + // check that "file descriptor" is valid + if (isnotopen(&fd,&con)) + return -1; + + 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 = 512; + 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 { + pout("Unrecognized escalade_type %d in freebsd_3ware_command_interface(disk %d)\n" + "Please contact " PACKAGE_BUGREPORT "\n", m_escalade_type, m_disknum); + errno=ENOSYS; + return -1; + } + + 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->drive_head = 0x0; + ata->sector_num = 0; + + // All SMART commands use this CL/CH signature. These are magic + // values from the ATA specifications. + ata->cylinder_lo = 0x4F; + ata->cylinder_hi = 0xC2; + + // SMART ATA COMMAND REGISTER value + ata->command = ATA_SMART_CMD; + + // 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 + if (command == READ_VALUES || + command == READ_THRESHOLDS || + command == READ_LOG || + command == IDENTIFY || + command == WRITE_LOG ) { + readdata=1; + if (m_escalade_type==CONTROLLER_3WARE_678K_CHAR) { + cmd_twe->tu_data = data; + cmd_twe->tu_size = 512; + } + ata->sgl_offset = 0x5; ata->size = 0x5; ata->param = 0xD; ata->sector_count = 0x1; @@ -901,7 +978,7 @@ int escalade_command_interface(int fd, int disknum, int escalade_type, smart_com break; case PIDENTIFY: // 3WARE controller can NOT have packet device internally - pout("WARNING - NO DEVICE FOUND ON 3WARE CONTROLLER (disk %d)\n", disknum); + pout("WARNING - NO DEVICE FOUND ON 3WARE CONTROLLER (disk %d)\n", m_disknum); errno=ENODEV; return -1; case ENABLE: @@ -936,13 +1013,13 @@ int escalade_command_interface(int fd, int disknum, int escalade_type, smart_com break; default: pout("Unrecognized command %d in freebsd_3ware_command_interface(disk %d)\n" - "Please contact " PACKAGE_BUGREPORT "\n", command, disknum); + "Please contact " PACKAGE_BUGREPORT "\n", command, m_disknum); errno=ENOSYS; return -1; } // Now send the command down through an ioctl() - if (escalade_type==CONTROLLER_3WARE_9000_CHAR) { + if (m_escalade_type==CONTROLLER_3WARE_9000_CHAR) { #ifdef IOCATAREQUEST ioctlreturn=ioctl(con->device,TW_OSL_IOCTL_FIRMWARE_PASS_THROUGH,cmd_twa); #else @@ -983,7 +1060,7 @@ int escalade_command_interface(int fd, int disknum, int escalade_type, smart_com // If this is a read data command, copy data to output buffer if (readdata) { - if (escalade_type==CONTROLLER_3WARE_9000_CHAR) + if (m_escalade_type==CONTROLLER_3WARE_9000_CHAR) memcpy(data, cmd_twa->pdata, 512); } @@ -1022,215 +1099,625 @@ int escalade_command_interface(int fd, int disknum, int escalade_type, smart_com return 0; } -static int get_tw_channel_unit (const char* name, int* unit, int* dev) { - const char *p; - /* device node sanity check */ - for (p = name + 3; *p; p++) - if (*p < '0' || *p > '9') +///////////////////////////////////////////////////////////////////////////// +/// 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); + +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("ATA") +{ + 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]); +} + +int freebsd_highpoint_device::ata_command_interface(smart_command_set command, int select, char * data) +{ + int fd=get_fd(); + int ids[2]; + struct freebsd_dev_channel* fbcon; + 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; + + // check that "file descriptor" is valid + if (isnotopen(&fd, &fbcon)) return -1; - if (strlen(name) > 4 && *(name + 3) == '0') + + // 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(fbcon->device, HPT_DO_IOCONTROL, ¶m)!=0 || + info.devices[m_hpt_data[2]-1]==0) { return -1; + } - if (dev != NULL) - *dev=atoi(name + 3); + // perform smart action + memset(buff, 0, 512 + 2 * sizeof(HPT_PASS_THROUGH_HEADER)); + pide_pt_hdr = (PHPT_PASS_THROUGH_HEADER)buff; - /* no need for unit number */ - if (unit != NULL) - *unit=0; + 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(fbcon->device, 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]; + sprintf(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; } -static int hpt_hba(const char* name) { - int i=0; - const char *hpt_node[]={"hptmv", "hptmv6", "hptrr", "hptiop", "hptmviop", "hpt32xx", "rr2320", - "rr232x", "rr2310", "rr2310_00", "rr2300", "rr2340", "rr1740", NULL}; - while (hpt_node[i]) { - if (!strncmp(name, hpt_node[i], strlen(hpt_node[i]))) - return 1; - i++; + +///////////////////////////////////////////////////////////////////////////// +/// Implement standard SCSI support with old functions + +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(); + + virtual bool scsi_pass_through(scsi_cmnd_io * iop); +}; + +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("SCSI") +{ +} + +// Interface to SCSI devices. See os_linux.c +int do_normal_scsi_cmnd_io(int fd, struct scsi_cmnd_io * iop, int report) +{ + struct freebsd_dev_channel* con = NULL; + struct cam_device* cam_dev = NULL; + union ccb *ccb; + + + if (report > 0) { + unsigned int k; + const unsigned char * ucp = iop->cmnd; + const char * np; + + np = scsi_get_opcode_name(ucp[0]); + pout(" [%s: ", np ? np : "<unknown opcode>"); + for (k = 0; k < iop->cmnd_len; ++k) + pout("%02x ", ucp[k]); + if ((report > 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("]"); + } + + // check that "file descriptor" is valid + if (isnotopen(&fd,&con)) + return -ENOTTY; + + + if (!(cam_dev = cam_open_spec_device(con->devname,con->unitnum,O_RDWR,NULL))) { + warnx("%s",cam_errbuf); + return -EIO; + } + + if (!(ccb = cam_getccb(cam_dev))) { + warnx("error allocating ccb"); + return -ENOMEM; + } + + // clear out structure, except for header that was filled in for us + bzero(&(&ccb->ccb_h)[1], + sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); + + cam_fill_csio(&ccb->csio, + /*retrires*/ 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, + /* timout (converted to seconds) */ iop->timeout*1000); + memcpy(ccb->csio.cdb_io.cdb_bytes,iop->cmnd,iop->cmnd_len); + + if (cam_send_ccb(cam_dev,ccb) < 0) { + warn("error sending SCSI ccb"); + #if __FreeBSD_version > 500000 + cam_error_print(cam_dev,ccb,CAM_ESF_ALL,CAM_EPF_ALL,stderr); + #endif + cam_freeccb(ccb); + return -EIO; + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + #if __FreeBSD_version > 500000 + cam_error_print(cam_dev,ccb,CAM_ESF_ALL,CAM_EPF_ALL,stderr); + #endif + cam_freeccb(ccb); + return -EIO; + } + + if (iop->sensep) { + memcpy(iop->sensep,&(ccb->csio.sense_data),sizeof(struct scsi_sense_data)); + iop->resp_sense_len = sizeof(struct scsi_sense_data); + } + + iop->scsi_status = ccb->csio.scsi_status; + + cam_freeccb(ccb); + + if (cam_dev) + cam_close_device(cam_dev); + + if (report > 0) { + int trunc; + + pout(" status=0\n"); + trunc = (iop->dxfer_len > 256) ? 1 : 0; + + pout(" Incoming data, len=%d%s:\n", (int)iop->dxfer_len, + (trunc ? " [only first 256 bytes shown]" : "")); + dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); + } + return 0; +} + + +/* Check and call the right interface. Maybe when the do_generic_scsi_cmd_io interface is better + we can take off this crude way of calling the right interface */ +int do_scsi_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report) +{ +struct freebsd_dev_channel *fdchan; + switch(m_controller_type) + { + case CONTROLLER_CCISS: +#ifdef HAVE_DEV_CISS_CISSIO_H + // check that "file descriptor" is valid + if (isnotopen(&dev_fd,&fdchan)) + return -ENOTTY; + return cciss_io_interface(fdchan->device, m_controller_port-1, iop, report); +#else + { + static int warned = 0; + if (!warned) { + pout("CCISS support is not available in this build of smartmontools,\n" + "/usr/src/sys/dev/ciss/cissio.h was not available at build time.\n\n"); + warned = 1; + } + } + return -ENOSYS; +#endif + // not reached + break; + default: + return do_normal_scsi_cmnd_io(dev_fd, iop, report); + // not reached + break; + } +} + +bool freebsd_scsi_device::scsi_pass_through(scsi_cmnd_io * iop) +{ + unsigned char oldtype = m_controller_type, oldport = m_controller_port; + m_controller_type = CONTROLLER_SCSI; m_controller_port = 0; + int status = do_scsi_cmnd_io(get_fd(), iop, con->reportscsiioctl); + m_controller_type = oldtype; m_controller_port = oldport; + if (status < 0) { + set_err(-status); + return false; + } + 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); + +private: + unsigned char m_disknum; ///< Disk number. +}; + + +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("SCSI"), + 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) +{ + // See os_linux.cpp + unsigned char oldtype = m_controller_type, oldport = m_controller_port; + m_controller_type = CONTROLLER_CCISS; m_controller_port = m_disknum+1; + int status = do_scsi_cmnd_io(get_fd(), iop, con->reportscsiioctl); + m_controller_type = oldtype; m_controller_port = oldport; + if (status < 0) { + set_err(-status); + return false; } - return 0; + return true; } -#ifndef ATA_DEVICE -#define ATA_DEVICE "/dev/ata" -#endif -#ifndef IOCATAREQUEST -static int get_ata_channel_unit ( const char* name, int* unit, int* dev) { -#ifndef ATAREQUEST - *dev=0; - *unit=0; -return 0; -#else - // there is no direct correlation between name 'ad0, ad1, ...' and - // channel/unit number. So we need to iterate through the possible - // channels and check each unit to see if we match names - struct ata_cmd iocmd; - int fd,maxunit; - - bzero(&iocmd, sizeof(struct ata_cmd)); +///////////////////////////////////////////////////////////////////////////// +/// SCSI open with autodetection support - if ((fd = open(ATA_DEVICE, O_RDWR)) < 0) - return -errno; - - iocmd.cmd = ATAGMAXCHANNEL; - if (ioctl(fd, IOCATA, &iocmd) < 0) { - return -errno; - close(fd); - } - maxunit = iocmd.u.maxchan; - for (*unit = 0; *unit < maxunit; (*unit)++) { - iocmd.channel = *unit; - iocmd.device = -1; - iocmd.cmd = ATAGPARM; - if (ioctl(fd, IOCATA, &iocmd) < 0) { - close(fd); - return -errno; - } - if (iocmd.u.param.type[0] && !strcmp(name,iocmd.u.param.name[0])) { - *dev = 0; - break; - } - if (iocmd.u.param.type[1] && !strcmp(name,iocmd.u.param.name[1])) { - *dev = 1; - break; +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; } } - close(fd); - if (*unit == maxunit) - return -1; - else - return 0; -#endif -} -#endif -// Guess device type (ata or scsi) based on device name (FreeBSD -// specific) SCSI device name in FreeBSD can be sd, sr, scd, st, nst, -// osst, nosst and sg. -static const char * fbsd_dev_prefix = _PATH_DEV; -static const char * fbsd_dev_ata_disk_prefix = "ad"; -static const char * fbsd_dev_scsi_disk_plus = "da"; -static const char * fbsd_dev_scsi_pass = "pass"; -static const char * fbsd_dev_scsi_tape1 = "sa"; -static const char * fbsd_dev_scsi_tape2 = "nsa"; -static const char * fbsd_dev_scsi_tape3 = "esa"; -static const char * fbsd_dev_twe_ctrl = "twe"; -static const char * fbsd_dev_twa_ctrl = "twa"; -static const char * fbsd_dev_cciss = "ciss"; + 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 + smart_device * newdev = 0; + try { + // 3ware ? + if (!memcmp(req_buff + 8, "3ware", 5) || !memcmp(req_buff + 8, "AMCC", 4)) { + close(); +#if defined(_WIN32) || defined(__CYGWIN__) + set_err(EINVAL, "AMCC/3ware controller, please try changing device to %s,N", get_dev_name()); +#else + set_err(EINVAL, "AMCC/3ware controller, please try adding '-d 3ware,N',\n" + "you may need to replace %s with /dev/twaN or /dev/tweN", get_dev_name()); +#endif + return this; + } -static int parse_ata_chan_dev(const char * dev_name, struct freebsd_dev_channel *chan) { - int len; - int dev_prefix_len = strlen(fbsd_dev_prefix); - - // if dev_name null, or string length zero - if (!dev_name || !(len = strlen(dev_name))) - return CONTROLLER_UNKNOWN; - - // Remove the leading /dev/... if it's there - if (!strncmp(fbsd_dev_prefix, dev_name, dev_prefix_len)) { - if (len <= dev_prefix_len) - // if nothing else in the string, unrecognized - return CONTROLLER_UNKNOWN; - // else advance pointer to following characters - dev_name += dev_prefix_len; + // SAT or USB ? + newdev = smi()->autodetect_sat_device(this, req_buff, len); + if (newdev) + // NOTE: 'this' is now owned by '*newdev' + return newdev; } - // form /dev/ad* or ad* - if (!strncmp(fbsd_dev_ata_disk_prefix, dev_name, - strlen(fbsd_dev_ata_disk_prefix))) { -#ifndef IOCATAREQUEST - if (chan != NULL) { - if (get_ata_channel_unit(dev_name,&(chan->channel),&(chan->device))<0) { - return CONTROLLER_UNKNOWN; - } - } -#endif - return CONTROLLER_ATA; + catch (...) { + // Cleanup if exception occurs after newdev was allocated + delete newdev; + throw; } - // form /dev/pass* or pass* - if (!strncmp(fbsd_dev_scsi_pass, dev_name, - strlen(fbsd_dev_scsi_pass))) - goto handlescsi; + // Nothing special found + return this; +} - // form /dev/da* or da* - if (!strncmp(fbsd_dev_scsi_disk_plus, dev_name, - strlen(fbsd_dev_scsi_disk_plus))) - goto handlescsi; - // form /dev/sa* or sa* - if (!strncmp(fbsd_dev_scsi_tape1, dev_name, - strlen(fbsd_dev_scsi_tape1))) - goto handlescsi; +///////////////////////////////////////////////////////////////////////////// +/// Implement platform interface with old functions. - // form /dev/nsa* or nsa* - if (!strncmp(fbsd_dev_scsi_tape2, dev_name, - strlen(fbsd_dev_scsi_tape2))) - goto handlescsi; +class freebsd_smart_interface +: public /*implements*/ smart_interface +{ +public: + virtual const char * get_os_version_str(); - // form /dev/esa* or esa* - if (!strncmp(fbsd_dev_scsi_tape3, dev_name, - strlen(fbsd_dev_scsi_tape3))) - goto handlescsi; - - if (!strncmp(fbsd_dev_twa_ctrl,dev_name, - strlen(fbsd_dev_twa_ctrl))) { - if (chan != NULL) { - if (get_tw_channel_unit(dev_name,&(chan->channel),&(chan->device))<0) { - return CONTROLLER_UNKNOWN; - } - } - else if (get_tw_channel_unit(dev_name,NULL,NULL)<0) { - return CONTROLLER_UNKNOWN; - } - return CONTROLLER_3WARE_9000_CHAR; - } + virtual const char * get_app_examples(const char * appname); - if (!strncmp(fbsd_dev_twe_ctrl,dev_name, - strlen(fbsd_dev_twe_ctrl))) { - if (chan != NULL) { - if (get_tw_channel_unit(dev_name,&(chan->channel),&(chan->device))<0) { - return CONTROLLER_UNKNOWN; - } - } - else if (get_tw_channel_unit(dev_name,NULL,NULL)<0) { - return CONTROLLER_UNKNOWN; - } - return CONTROLLER_3WARE_678K_CHAR; - } + virtual bool scan_smart_devices(smart_device_list & devlist, const char * type, + const char * pattern = 0); - if (hpt_hba(dev_name)) { - return CONTROLLER_HPT; - } +protected: + virtual ata_device * get_ata_device(const char * name, const char * type); - // form /dev/ciss* - if (!strncmp(fbsd_dev_cciss, dev_name, - strlen(fbsd_dev_cciss))) - return CONTROLLER_CCISS; + virtual scsi_device * get_scsi_device(const char * name, const char * type); - // we failed to recognize any of the forms - return CONTROLLER_UNKNOWN; + virtual smart_device * autodetect_smart_device(const char * name); - handlescsi: - if (chan != NULL) { - if (!(chan->devname = (char *)calloc(1,DEV_IDLEN+1))) - return CONTROLLER_UNKNOWN; - - if (cam_get_device(dev_name,chan->devname,DEV_IDLEN,&(chan->unitnum)) == -1) - return CONTROLLER_UNKNOWN; - } - return CONTROLLER_SCSI; - + virtual smart_device * get_custom_smart_device(const char * name, const char * type); + + virtual const char * get_valid_custom_dev_types_str(); +}; + + +////////////////////////////////////////////////////////////////////// +char sysname[256]; +const char * freebsd_smart_interface::get_os_version_str() +{ + struct utsname osname; + uname(&osname); + snprintf(sysname, sizeof(sysname),"%s %s %s",osname.sysname, osname.release, + osname.machine); + return sysname; } -int guess_device_type (const char* dev_name) { - return parse_ata_chan_dev(dev_name,NULL); +const char * freebsd_smart_interface::get_app_examples(const char * appname) +{ + if (!strcmp(appname, "smartctl")) + return smartctl_examples; + return 0; +} + +ata_device * freebsd_smart_interface::get_ata_device(const char * name, const char * type) +{ + return new freebsd_ata_device(this, name, type); +} + +scsi_device * freebsd_smart_interface::get_scsi_device(const char * name, const char * type) +{ + return new freebsd_scsi_device(this, name, type); +} + +static int +cam_getumassno(char * devname) { + union ccb ccb; + int bufsize, fd; + unsigned int i; + int error = -1; + char devstring[256]; + + if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) { + warn("couldn't open %s", XPT_DEVICE); + return(1); + } + bzero(&ccb, 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; + bufsize = sizeof(struct dev_match_result) * 100; + ccb.cdm.match_buf_len = bufsize; + ccb.cdm.matches = (struct dev_match_result *)malloc(bufsize); + if (ccb.cdm.matches == NULL) { + warnx("can't malloc memory for matches"); + close(fd); + return(1); + } + ccb.cdm.num_matches = 0; + /* + * We fetch all nodes, since we display most of them in the default + * case, and all in the verbose case. + */ + 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 100 nodes in the EDT. + */ + + do { + if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { + warn("error sending CAMIOCOMMAND ioctl"); + error = -1; + break; + } + if ((ccb.ccb_h.status != CAM_REQ_CMP) + || ((ccb.cdm.status != CAM_DEV_MATCH_LAST) + && (ccb.cdm.status != CAM_DEV_MATCH_MORE))) { + warnx("got CAM error %#x, CDM error %d\n", + ccb.ccb_h.status, ccb.cdm.status); + error = -1; + break; + } + + struct bus_match_result *bus_result = 0; + for (i = 0; i < ccb.cdm.num_matches; i++) { + switch (ccb.cdm.matches[i].type) { + case DEV_MATCH_BUS: { + // struct bus_match_result *bus_result; + bus_result = + &ccb.cdm.matches[i].result.bus_result; + break; + } + case DEV_MATCH_DEVICE: { + /* we are not interested in device name */ + break; + } + case DEV_MATCH_PERIPH: { + struct periph_match_result *periph_result; + + periph_result = + &ccb.cdm.matches[i].result.periph_result; + + snprintf(devstring,sizeof(devstring),"%s%d",periph_result->periph_name,periph_result->unit_number); + if(strcmp(devstring,devname)==0){ /* found our device */ + if(strcmp(bus_result->dev_name,"umass-sim")) { + close(fd); + return -1; /* non usb device found, giving up */ + } + /* return bus number */ + return bus_result->unit_number; + } + break; + } + + default: + fprintf(stdout, "WARN: unknown match type\n"); + break; + } + } + + } while ((ccb.ccb_h.status == CAM_REQ_CMP) + && (ccb.cdm.status == CAM_DEV_MATCH_MORE)); + close(fd); + free(ccb.cdm.matches); + return(error); /* no device found */ } -// global variable holding byte count of allocated memory -extern long long bytes; // we are using CAM subsystem XPT enumerator to found all SCSI devices on system // despite of it's names @@ -1343,7 +1830,7 @@ int get_dev_names_scsi(char*** names) { // skip_device = 1; changed = 1; } else if (ccb.cdm.matches[i].type == DEV_MATCH_PERIPH && skip_device == 0) { - /* One device may be populated as many peripherals (pass0 & da 0 fo rexample). + /* One device may be populated as many peripherals (pass0 & da0 for example). * We are searching for latest name */ periph_result = &ccb.cdm.matches[i].result.periph_result; @@ -1477,11 +1964,262 @@ end: return n; } -int make_device_names (char*** devlist, const char* name) { - if (!strcmp(name,"SCSI")) - return get_dev_names_scsi(devlist); - else if (!strcmp(name,"ATA")) - return get_dev_names_ata(devlist); - else + + +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; + } + + // 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; + } + } + + char * * scsinames = 0; int numscsi = 0; + if (!type || !strcmp(type, "scsi")) { + numscsi = get_dev_names_scsi(&scsinames); + if (numscsi < 0) { + set_err(ENOMEM); + 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.add(atadev); + } + + for (i = 0; i < numscsi; i++) { + scsi_device * scsidev = get_scsi_device(scsinames[i], type); + if (scsidev) + devlist.add(scsidev); + } + return true; +} + + +static char done[USB_MAX_DEVICES]; +// static unsigned short vendor_id = 0, product_id = 0, version = 0; + +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; +} + + + + +static int usbdevlist(int busno,unsigned short & vendor_id, + unsigned short & product_id, unsigned short & version) +{ + int i, f, a, rc; + char buf[50]; + int ncont; + + for (ncont = 0, i = 0; i < 10; i++) { + snprintf(buf, sizeof(buf), "%s%d", USBDEV, i); + 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; +} + +// Get USB bridge ID for "/dev/daX" +static bool get_usb_id(const char * path, unsigned short & vendor_id, + unsigned short & product_id, unsigned short & version) +{ + // Only "/dev/daX" supported + if (!(!strncmp(path, "/dev/da", 7) && !strchr(path + 7, '/'))) + return false; + int bus = cam_getumassno((char *)path+5); + + if (bus == -1) + return false; + + usbdevlist(bus,vendor_id, + product_id, version); + + return true; +} + + +smart_device * freebsd_smart_interface::autodetect_smart_device(const char * name) +{ + int guess = parse_ata_chan_dev(name,NULL); + unsigned short vendor_id = 0, product_id = 0, version = 0; + + switch (guess) { + case CONTROLLER_ATA : + return new freebsd_ata_device(this, name, ""); + case CONTROLLER_SCSI: + // Try to detect possible USB->(S)ATA bridge + if (get_usb_id(name, vendor_id, product_id, version)) { + const char * usbtype = get_usb_dev_type_by_id(vendor_id, product_id, version); + if (!usbtype) + return 0; + // Return SAT/USB device for this type + // (Note: freebsd_scsi_device::autodetect_open() will not be called in this case) + return get_sat_device(usbtype, new freebsd_scsi_device(this, name, "")); + } + // non usb device, handle as normal scsi + return new freebsd_scsi_device(this, name, ""); + case CONTROLLER_CCISS: + // device - cciss, but no ID known + set_err(EINVAL, "Option -d cciss,N requires N to be a non-negative integer"); return 0; + } + + + // TODO: Test autodetect device here + return 0; +} + + +smart_device * freebsd_smart_interface::get_custom_smart_device(const char * name, const char * 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)) { + set_err(EINVAL, "Option -d 3ware,N requires N to be a non-negative integer"); + return 0; + } + if (!(0 <= disknum && disknum <= 15)) { + set_err(EINVAL, "Option -d 3ware,N (N=%d) must have 0 <= N <= 15", disknum); + return 0; + } + int contr = parse_ata_chan_dev(name,NULL); + if (contr != CONTROLLER_3WARE_9000_CHAR && contr != CONTROLLER_3WARE_678K_CHAR) + contr = CONTROLLER_3WARE_678K; + 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 <= 8)) { + 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 <= 15)) { + set_err(EINVAL, "Option -d cciss,N (N=%d) must have 0 <= N <= 15", disknum); + return 0; + } + return new freebsd_cciss_device(this, name, disknum); + } + + return 0; +} + +const char * freebsd_smart_interface::get_valid_custom_dev_types_str() +{ + return "marvell, 3ware,N, hpt,L/M/N, cciss,N"; +} + + +} // 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); }