aboutsummaryrefslogtreecommitdiff
path: root/lib/sg_pt_freebsd.c
diff options
context:
space:
mode:
authorDouglas Gilbert <dgilbert@interlog.com>2017-11-15 06:21:21 +0000
committerDouglas Gilbert <dgilbert@interlog.com>2017-11-15 06:21:21 +0000
commitcad489950e9fc29fe8786ddee2e163831abb3322 (patch)
treef5263f4d234ec6894c61a8561a4a77c06068c7bc /lib/sg_pt_freebsd.c
parent098b15e48f02ac4b501dc383456be94b25c1266b (diff)
downloadsg3_utils-cad489950e9fc29fe8786ddee2e163831abb3322.tar.gz
rename sg_write_atomic to sg_write_x; sg_inq: decode NVMe identify for Linux+FreeBSD
git-svn-id: https://svn.bingwo.ca/repos/sg3_utils/trunk@731 6180dd3e-e324-4e3e-922d-17de1ae2f315
Diffstat (limited to 'lib/sg_pt_freebsd.c')
-rw-r--r--lib/sg_pt_freebsd.c348
1 files changed, 314 insertions, 34 deletions
diff --git a/lib/sg_pt_freebsd.c b/lib/sg_pt_freebsd.c
index 2828c34a..dc4f3c78 100644
--- a/lib/sg_pt_freebsd.c
+++ b/lib/sg_pt_freebsd.c
@@ -5,7 +5,7 @@
* license that can be found in the BSD_LICENSE file.
*/
-/* sg_pt_freebsd version 1.16 20171030 */
+/* sg_pt_freebsd version 1.18 20171114 */
#include <stdio.h>
#include <stdlib.h>
@@ -14,6 +14,8 @@
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
+#include <limits.h>
+#include <libgen.h> /* for basename */
#include <fcntl.h>
#include <errno.h>
#include <err.h>
@@ -29,6 +31,7 @@
#include "sg_pt.h"
#include "sg_lib.h"
+#include "freebsd_nvme_ioctl.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
@@ -40,9 +43,14 @@
struct freebsd_dev_channel {
- char* devname; // the SCSI device name
- int unitnum; // the SCSI unit number
- struct cam_device* cam_dev;
+ int unitnum; // the SCSI unit number
+ bool is_nvme;
+ bool is_char;
+ uint32_t nsid;
+ uint32_t nv_ctrlid;
+ int dev_fd; // for NVMe, use -1 to indicate not provided
+ char* devname; // the device name
+ struct cam_device* cam_dev;
};
// Private table of open devices: guaranteed zero on startup since
@@ -67,12 +75,16 @@ struct sg_pt_freebsd_scsi {
int in_err;
int os_err;
int transport_err;
+ int dev_han; // -1 if not provided
+ uint32_t result; // NVMe result from completion
};
struct sg_pt_base {
struct sg_pt_freebsd_scsi impl;
};
+static const uint32_t broadcast_nsid = 0xffffffff;
+
#if defined(__GNUC__) || defined(__clang__)
static int pr2ws(const char * fmt, ...)
__attribute__ ((format (printf, 1, 2)));
@@ -105,15 +117,22 @@ scsi_pt_open_device(const char * device_name, bool read_only, int verbose)
}
/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed
- * together. The 'flags' argument is ignored in FreeBSD.
+ * together. The 'flags' is only used on NVMe devices. It is ignored on
+ * SCSI and ATA devices in FreeBSD.
* Returns >= 0 if successful, otherwise returns negated errno. */
int
-scsi_pt_open_flags(const char * device_name,
- int flags __attribute__ ((unused)), int verbose)
+scsi_pt_open_flags(const char * device_name, int oflags, int verbose)
{
- struct freebsd_dev_channel *fdchan;
+ bool is_char, is_block, possible_nvme;
+ char tmp;
+ int k, err, dev_fd, ret;
+ uint32_t nsid, nv_ctrlid;
+ ssize_t s;
+ struct freebsd_dev_channel *fdchan = NULL;
struct cam_device* cam_dev;
- int k;
+ struct stat a_stat;
+ char b[PATH_MAX];
+ char full_path[64];
// Search table for a free entry
for (k = 0; k < FREEBSD_MAXDEV; k++)
@@ -125,68 +144,184 @@ scsi_pt_open_flags(const char * device_name,
if (k == FREEBSD_MAXDEV) {
if (verbose)
pr2ws("too many open file descriptors (%d)\n", FREEBSD_MAXDEV);
- errno = EMFILE;
- return -1;
+ ret = -EMFILE;
+ goto err_out;
+ }
+ if (stat(device_name, &a_stat) < 0) {
+ err = errno;
+ pr2ws("%s: unable to stat(%s): %s\n", __func__, device_name,
+ strerror(err));
+ ret = -err;
+ goto err_out;
+ }
+ is_block = S_ISBLK(a_stat.st_mode);
+ is_char = S_ISCHR(a_stat.st_mode);
+ if (! (is_block || is_char)) {
+ if (verbose)
+ pr2ws("%s: %s is not char nor block device\n", __func__,
+ device_name);
+ ret = -ENODEV;
+ goto err_out;
+ }
+ s = readlink(device_name, b, sizeof(b));
+ if (s <= 0) {
+ strncpy(b, device_name, PATH_MAX - 1);
+ b[PATH_MAX - 1] = '\0';
+ }
+
+ /* Some code borrowed from smartmontools, Christian Franke */
+ nsid = broadcast_nsid;
+ nv_ctrlid = broadcast_nsid;
+ possible_nvme = false;
+ while (true) { /* dummy loop, so can 'break' out */
+ if(sscanf(b, NVME_CTRLR_PREFIX"%u%c", &nv_ctrlid, &tmp) == 1) {
+ if(nv_ctrlid == broadcast_nsid)
+ break;
+ } else if (sscanf(b, NVME_CTRLR_PREFIX"%d"NVME_NS_PREFIX"%d%c",
+ &nv_ctrlid, &nsid, &tmp) == 2) {
+ if((nv_ctrlid == broadcast_nsid) || (nsid == broadcast_nsid))
+ break;
+ } else
+ break;
+ possible_nvme = true;
+ break;
}
fdchan = (struct freebsd_dev_channel *)
calloc(1,sizeof(struct freebsd_dev_channel));
if (fdchan == NULL) {
// errno already set by call to calloc()
- return -1;
+ ret = -ENOMEM;
+ goto err_out;
+ }
+ fdchan->dev_fd = -1;
+ if (! (fdchan->devname = (char *)calloc(1, DEV_IDLEN+1))) {
+ ret = -ENOMEM;
+ goto err_out;
}
- if (! (fdchan->devname = (char *)calloc(1, DEV_IDLEN+1)))
- return -1;
+ if (possible_nvme) {
+ // we should always open controller, not namespace device
+ snprintf(fdchan->devname, DEV_IDLEN, NVME_CTRLR_PREFIX"%d",
+ nv_ctrlid);
+ dev_fd = open(fdchan->devname, oflags);
+ if (dev_fd < 0) {
+ err = errno;
+ if (verbose)
+ pr2ws("%s: open(%s) failed: %s (errno=%d), try SCSI/ATA\n",
+ __func__, full_path, strerror(err), err);
+ goto scsi_ata_try;
+ }
+ fdchan->is_nvme = true;
+ fdchan->is_char = is_char;
+ fdchan->nsid = (broadcast_nsid == nsid) ? 0 : nsid;
+ fdchan->nv_ctrlid = nv_ctrlid;
+ fdchan->dev_fd = dev_fd;
+ devicetable[k] = fdchan;
+ return k + FREEBSD_FDOFFSET;
+ }
+scsi_ata_try:
+ fdchan->is_char = is_char;
if (cam_get_device(device_name, fdchan->devname, DEV_IDLEN,
&(fdchan->unitnum)) == -1) {
if (verbose)
pr2ws("bad device name structure\n");
errno = EINVAL;
- return -1;
+ ret = -errno;
+ goto err_out;
}
+ if (verbose > 4)
+ pr2ws("%s: cam_get_device, f->devname: %s, f->unitnum=%d\n", __func__,
+ fdchan->devname, fdchan->unitnum);
if (! (cam_dev = cam_open_spec_device(fdchan->devname,
fdchan->unitnum, O_RDWR, NULL))) {
if (verbose)
pr2ws("cam_open_spec_device: %s\n", cam_errbuf);
- errno = EPERM; /* permissions or no CAM */
- return -1;
+ errno = EPERM; /* permissions or not CAM device (NVMe ?) */
+ ret = -errno;
+ goto err_out;
}
fdchan->cam_dev = cam_dev;
// return pointer to "file descriptor" table entry, properly offset.
devicetable[k] = fdchan;
return k + FREEBSD_FDOFFSET;
+
+err_out: /* ret should be negative value (negated errno) */
+ if (fdchan) {
+ if (fdchan->devname)
+ free(fdchan->devname);
+ free(fdchan);
+ fdchan = NULL;
+ }
+ return ret;
}
/* Returns 0 if successful. If error in Unix returns negated errno. */
int
-scsi_pt_close_device(int device_fd)
+scsi_pt_close_device(int device_han)
{
struct freebsd_dev_channel *fdchan;
- int fd = device_fd - FREEBSD_FDOFFSET;
+ int han = device_han - FREEBSD_FDOFFSET;
- if ((fd < 0) || (fd >= FREEBSD_MAXDEV)) {
+ if ((han < 0) || (han >= FREEBSD_MAXDEV)) {
errno = ENODEV;
- return -1;
+ return -errno;
}
- fdchan = devicetable[fd];
+ fdchan = devicetable[han];
if (NULL == fdchan) {
errno = ENODEV;
- return -1;
+ return -errno;
}
if (fdchan->devname)
free(fdchan->devname);
if (fdchan->cam_dev)
cam_close_device(fdchan->cam_dev);
+ if (fdchan->is_nvme) {
+ if (fdchan->dev_fd >= 0)
+ close(fdchan->dev_fd);
+ }
free(fdchan);
- devicetable[fd] = NULL;
+ devicetable[han] = NULL;
+ errno = 0;
return 0;
}
+/* Assumes dev_fd is an "open" file handle associated with some device.
+ * Returns 1 if SCSI generic pass-though device, returns 2 if secondary
+ * SCSI pass-through device (in Linux a bsg device); returns 3 is char
+ * NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes
+ * NSID), or 0 if something else (e.g. ATA block device) or dev_fd < 0.
+ * If error, returns negated errno (operating system) value. */
+int
+check_pt_file_handle(int device_han, const char * device_name, int verbose)
+{
+ struct freebsd_dev_channel *fdchan;
+ int han = device_han - FREEBSD_FDOFFSET;
+
+ if ((han < 0) || (han >= FREEBSD_MAXDEV)) {
+ errno = ENODEV;
+ return -errno;
+ }
+ fdchan = devicetable[han];
+ if (NULL == fdchan) {
+ errno = ENODEV;
+ return -errno;
+ }
+ if (fdchan->is_nvme)
+ return 4 - (int)fdchan->is_char;
+ else if (fdchan->cam_dev)
+ return 2 - (int)fdchan->is_char;
+ else {
+ if (device_name) { }
+ if (verbose) { }
+ return 0;
+ }
+}
+
struct sg_pt_base *
-construct_scsi_pt_obj()
+construct_scsi_pt_obj_with_fd(int dev_han, int verbose)
{
struct sg_pt_freebsd_scsi * ptp;
@@ -201,10 +336,19 @@ construct_scsi_pt_obj()
if (ptp) {
memset(ptp, 0, sizeof(struct sg_pt_freebsd_scsi));
ptp->dxfer_dir = CAM_DIR_NONE;
- }
+ ptp->dev_han = (dev_han < 0) ? -1 : dev_han;
+ } else if (verbose)
+ pr2ws("%s: calloc() out of memory\n", __func__);
return (struct sg_pt_base *)ptp;
}
+
+struct sg_pt_base *
+construct_scsi_pt_obj()
+{
+ return construct_scsi_pt_obj_with_fd(-1, 0);
+}
+
void
destruct_scsi_pt_obj(struct sg_pt_base * vp)
{
@@ -220,16 +364,44 @@ destruct_scsi_pt_obj(struct sg_pt_base * vp)
void
clear_scsi_pt_obj(struct sg_pt_base * vp)
{
+ int dev_han;
struct sg_pt_freebsd_scsi * ptp = &vp->impl;
if (ptp) {
if (ptp->ccb)
cam_freeccb(ptp->ccb);
+ dev_han = ptp->dev_han;
memset(ptp, 0, sizeof(struct sg_pt_freebsd_scsi));
ptp->dxfer_dir = CAM_DIR_NONE;
+ ptp->dev_han = dev_han;
}
}
+/* Forget any previous dev_han and install the one given. May attempt to
+ * find file type (e.g. if pass-though) from OS so there could be an error.
+ * Returns 0 for success or the the same value as get_scsi_pt_os_err()
+ * will return. dev_han should be >= 0 for a valid file handle or -1 . */
+int set_pt_file_handle(struct sg_pt_base * vp, int dev_han, int verbose)
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (ptp)
+ ptp->dev_han = dev_han;
+ ptp->os_err = 0;
+ if (verbose) { }
+ return 0;
+
+}
+
+/* Valid file handles (which is the return value) are >= 0 . Returns -1
+ * if there is no valid file handle. */
+int get_pt_file_handle(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ return ptp ? ptp->dev_han : -1;
+}
+
void
set_scsi_pt_cdb(struct sg_pt_base * vp, const unsigned char * cdb, int cdb_len)
{
@@ -330,13 +502,13 @@ set_scsi_pt_flags(struct sg_pt_base * objp, int flags)
* Clears os_err field prior to active call (whose result may set it
* again). */
int
-do_scsi_pt(struct sg_pt_base * vp, int device_fd, int time_secs, int verbose)
+do_scsi_pt(struct sg_pt_base * vp, int dev_han, int time_secs, int verbose)
{
- int fd = device_fd - FREEBSD_FDOFFSET;
+ int n, len, timout_ms;
+ int han;
struct sg_pt_freebsd_scsi * ptp = &vp->impl;
struct freebsd_dev_channel *fdchan;
union ccb *ccb;
- int len, timout_ms;
ptp->os_err = 0;
if (ptp->in_err) {
@@ -344,25 +516,87 @@ do_scsi_pt(struct sg_pt_base * vp, int device_fd, int time_secs, int verbose)
pr2ws("Replicated or unused set_scsi_pt...\n");
return SCSI_PT_DO_BAD_PARAMS;
}
+ if (dev_han < 0) {
+ if (ptp->dev_han < 0) {
+ if (verbose)
+ pr2ws("%s: No device file handle given\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ } else {
+ if (ptp->dev_han >= 0) {
+ if (dev_han != ptp->dev_han) {
+ if (verbose)
+ pr2ws("%s: file handle given to create and this "
+ "differ\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ } else
+ ptp->dev_han = dev_han;
+ }
+ han = ptp->dev_han - FREEBSD_FDOFFSET;
+
if (NULL == ptp->cdb) {
if (verbose)
pr2ws("No command (cdb) given\n");
return SCSI_PT_DO_BAD_PARAMS;
}
- if ((fd < 0) || (fd >= FREEBSD_MAXDEV)) {
+ if ((han < 0) || (han >= FREEBSD_MAXDEV)) {
if (verbose)
- pr2ws("Bad file descriptor\n");
+ pr2ws("Bad file handle\n");
ptp->os_err = ENODEV;
return -ptp->os_err;
}
- fdchan = devicetable[fd];
+ fdchan = devicetable[han];
if (NULL == fdchan) {
if (verbose)
pr2ws("File descriptor closed??\n");
ptp->os_err = ENODEV;
return -ptp->os_err;
}
+ if (fdchan->is_nvme) {
+ int err;
+ struct nvme_pt_command npc;
+
+ if (fdchan->dev_fd < 0) {
+ if (verbose)
+ pr2ws("%s: is_nvme is true but dev_fd<0, inconsistent\n",
+ __func__);
+ ptp->os_err = EINVAL;
+ return -ptp->os_err;
+ }
+ memset(&npc, 0, sizeof(npc));
+ n = ptp->cdb_len;
+ len = (int)sizeof(npc.cmd);
+ n = (len < n) ? len : n;
+ if (n < 8) {
+ if (verbose)
+ pr2ws("%s: cdb_len=%d too short\n", __func__, n);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ memcpy(&npc.cmd, ptp->cdb, ptp->cdb_len);
+ npc.buf = ptp->dxferp;
+ npc.len = ptp->dxfer_len;
+ npc.is_read = (CAM_DIR_IN == ptp->dxfer_dir);
+ if ((0 == npc.is_read) && (CAM_DIR_OUT == ptp->dxfer_dir))
+ npc.len = 0; /* don't want write by accident */
+ err = ioctl(fdchan->dev_fd, NVME_PASSTHROUGH_CMD, &npc);
+ if (err < 0) {
+ ptp->os_err = errno;
+ if (verbose > 3)
+ pr2ws("%s: ioctl(NVME_PASSTHROUGH_CMD) failed: %s "
+ "(errno=%d)\n", __func__, strerror(ptp->os_err),
+ ptp->os_err);
+ return -ptp->os_err;
+ }
+ ptp->result = npc.cpl.cdw0;
+ if (ptp->sense_len > 0) {
+ n = (int)sizeof(npc.cpl);
+ n = ptp->sense_len < n ? ptp->sense_len : n;
+ memcpy(ptp->sense, &npc.cpl, n);
+ }
+ return 0;
+ }
if (NULL == fdchan->cam_dev) {
if (verbose)
pr2ws("No open CAM device\n");
@@ -464,7 +698,18 @@ get_scsi_pt_status_response(const struct sg_pt_base * vp)
{
const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
- return ptp->scsi_status;
+ if (ptp) {
+ int han = ptp->dev_han - FREEBSD_FDOFFSET;
+ struct freebsd_dev_channel *fdchan;
+
+ if ((han < 0) || (han >= FREEBSD_MAXDEV))
+ return -1;
+ fdchan = devicetable[han];
+ if (NULL == fdchan)
+ return -1;
+ return fdchan->is_nvme ? (int)ptp->result : ptp->scsi_status;
+ }
+ return -1;
}
int
@@ -533,10 +778,45 @@ pt_device_is_nvme(const struct sg_pt_base * vp)
{
const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
- if (ptp) { ; } /* suppress warning */
+ if (ptp && (ptp->dev_han >= 0)) {
+ int han = ptp->dev_han - FREEBSD_FDOFFSET;
+ struct freebsd_dev_channel *fdchan;
+
+ if ((han < 0) || (han >= FREEBSD_MAXDEV)) {
+ errno = ENODEV;
+ return false;
+ }
+ fdchan = devicetable[han];
+ if (NULL == fdchan) {
+ errno = ENODEV;
+ return false;
+ }
+ return fdchan->is_nvme ;
+ }
return false;
}
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'objp', then its NSID is returned (values range from 0x1 to
+ * 0xffffffe). Otherwise 0 is returned. */
+uint32_t
+get_pt_nvme_nsid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (ptp && (ptp->dev_han >= 0)) {
+ int han = ptp->dev_han - FREEBSD_FDOFFSET;
+ struct freebsd_dev_channel *fdchan;
+
+ if ((han < 0) || (han >= FREEBSD_MAXDEV))
+ return 0;
+ fdchan = devicetable[han];
+ if (NULL == fdchan)
+ return 0;
+ return fdchan->nsid ;
+ }
+ return 0;
+}
char *
get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b)