From cad489950e9fc29fe8786ddee2e163831abb3322 Mon Sep 17 00:00:00 2001 From: Douglas Gilbert Date: Wed, 15 Nov 2017 06:21:21 +0000 Subject: 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 --- lib/sg_pt_freebsd.c | 348 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 314 insertions(+), 34 deletions(-) (limited to 'lib/sg_pt_freebsd.c') 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 #include @@ -14,6 +14,8 @@ #include #include #include +#include +#include /* for basename */ #include #include #include @@ -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) -- cgit v1.2.3