Official read only mirror of the smartmontools project SVN https://www.smartmontools.org/browser
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
smartmontools/smartmontools/os_freebsd.cpp

2537 lines
73 KiB

/*
* 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$" \
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;