From 36d0fa44e240623ba1336aa427bad3210d952120 Mon Sep 17 00:00:00 2001 From: "Kenneth D. Merry" Date: Wed, 3 May 2017 20:57:52 +0000 Subject: [PATCH] Add the ability to rescan or reset devices specified by peripheral name and unit number in camcontrol(8). Previously camcontrol(8) only supported rescanning or resetting devices specified by bus:target:lun. This is because for rescanning at least, you don't have a peripheral name and unit number (e.g. da4) for devices that don't exist yet. That is still the case after this change, but in other cases, when the device does exist in the CAM EDT (Existing Device Table), we do a careful lookup of the bus/target/lun if the user supplies a peripheral name and unit number to find the bus:target:lun and then issue the requested reset or rescan. The lookup is done without actually opening the device in question, since a rescan is often done to make a device go away after it has been pulled. (This is especially true for busses/controllers, like parallel SCSI controllers, that don't automatically detect changes in topology.) Opening a device that is no longer there to determine the bus/target/lun might result in error recovery actions when the user really just wanted to make the device go away. sbin/camcontrol/camcontrol.c: In dorescan_or_reset(), if the use hasn't specified a numeric argument, assume he has specified a device. Lookup the pass(4) instance for that device using the transport layer CAMGETPASSTHRU ioctl. If that is successful, we can use the returned bus:target:lun to rescan or reset the device. Under the hood, resetting a device using XPT_RESET_DEV is actually sent via the pass(4) device anyway. But this provides a way for the user to specify devices in a more convenient way, and can work on device rescans when the device is going away, assuming it still exists in the EDT. sbin/camcontrol/camcontrol.8: Update the man page for the rescan and reset subcommands to reflect that you can now use a device name and unit number with them. Sponsored by: Spectra Logic MFC after: 3 days --- sbin/camcontrol/camcontrol.8 | 18 ++++-- sbin/camcontrol/camcontrol.c | 107 +++++++++++++++++++++++++++++++++-- 2 files changed, 115 insertions(+), 10 deletions(-) diff --git a/sbin/camcontrol/camcontrol.8 b/sbin/camcontrol/camcontrol.8 index ff4c6baa3fe9..a4a98befed36 100644 --- a/sbin/camcontrol/camcontrol.8 +++ b/sbin/camcontrol/camcontrol.8 @@ -102,10 +102,10 @@ .Op device id .Nm .Ic rescan -.Aq all | bus Ns Op :target:lun +.Aq all | device id | bus Ns Op :target:lun .Nm .Ic reset -.Aq all | bus Ns Op :target:lun +.Aq all | device id | bus Ns Op :target:lun .Nm .Ic defects .Op device id @@ -578,12 +578,20 @@ start bit cleared and the load/eject bit set. .It Ic rescan Tell the kernel to scan all buses in the system (with the .Ar all -argument), the given bus (XPT_SCAN_BUS), or bus:target:lun +argument), the given bus (XPT_SCAN_BUS), bus:target:lun or device (XPT_SCAN_LUN) for new devices or devices that have gone away. The user may specify a scan of all buses, a single bus, or a lun. Scanning all luns on a target is not supported. +.Pp +If a device is specified by peripheral name and unit number, for instance +da4, it may only be rescanned if that device currently exists in the CAM EDT +(Existing Device Table). +If the device is no longer there (see +.Nm +devlist ), +you must use the bus:target:lun form to rescan it. .It Ic reprobe Tell the kernel to refresh the information about the device and notify the upper layer, @@ -593,8 +601,8 @@ the disk size visible to the rest of the system. .It Ic reset Tell the kernel to reset all buses in the system (with the .Ar all -argument) or the given bus (XPT_RESET_BUS) by issuing a SCSI bus -reset for that bus, or to reset the given bus:target:lun +argument), the given bus (XPT_RESET_BUS) by issuing a SCSI bus +reset for that bus, or to reset the given bus:target:lun or device (XPT_RESET_DEV), typically by issuing a BUS DEVICE RESET message after connecting to that device. Note that this can have a destructive impact diff --git a/sbin/camcontrol/camcontrol.c b/sbin/camcontrol/camcontrol.c index 10f2778a9323..3de48507c8f3 100644 --- a/sbin/camcontrol/camcontrol.c +++ b/sbin/camcontrol/camcontrol.c @@ -3131,12 +3131,107 @@ dorescan_or_reset(int argc, char **argv, int rescan) tstr++; if (strncasecmp(tstr, "all", strlen("all")) == 0) arglist |= CAM_ARG_BUS; - else { + else if (isdigit(*tstr)) { rv = parse_btl(argv[optind], &bus, &target, &lun, &arglist); if (rv != 1 && rv != 3) { warnx(must, rescan? "rescan" : "reset"); return(1); } + } else { + char name[30]; + int unit; + int fd = -1; + union ccb ccb; + + /* + * Note that resetting or rescanning a device used to + * require a bus or bus:target:lun. This is because the + * device in question may not exist and you're trying to + * get the controller to rescan to find it. It may also be + * because the device is hung / unresponsive, and opening + * an unresponsive device is not desireable. + * + * It can be more convenient to reference a device by + * peripheral name and unit number, though, and it is + * possible to get the bus:target:lun for devices that + * currently exist in the EDT. So this can work for + * devices that we want to reset, or devices that exist + * that we want to rescan, but not devices that do not + * exist yet. + * + * So, we are careful here to look up the bus/target/lun + * for the device the user wants to operate on, specified + * by peripheral instance (e.g. da0, pass32) without + * actually opening that device. The process is similar to + * what cam_lookup_pass() does, except that we don't + * actually open the passthrough driver instance in the end. + */ + + if (cam_get_device(tstr, name, sizeof(name), &unit) == -1) { + warnx("%s", cam_errbuf); + error = 1; + goto bailout; + } + + if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) { + warn("Unable to open %s", XPT_DEVICE); + error = 1; + goto bailout; + } + + bzero(&ccb, sizeof(ccb)); + + /* + * The function code isn't strictly necessary for the + * GETPASSTHRU ioctl. + */ + ccb.ccb_h.func_code = XPT_GDEVLIST; + + /* + * These two are necessary for the GETPASSTHRU ioctl to + * work. + */ + strlcpy(ccb.cgdl.periph_name, name, + sizeof(ccb.cgdl.periph_name)); + ccb.cgdl.unit_number = unit; + + /* + * Attempt to get the passthrough device. This ioctl will + * fail if the device name is null, if the device doesn't + * exist, or if the passthrough driver isn't in the kernel. + */ + if (ioctl(fd, CAMGETPASSTHRU, &ccb) == -1) { + warn("Unable to find bus:target:lun for device %s%d", + name, unit); + error = 1; + close(fd); + goto bailout; + } + if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + const struct cam_status_entry *entry; + + entry = cam_fetch_status_entry(ccb.ccb_h.status); + warnx("Unable to find bus:target_lun for device %s%d, " + "CAM status: %s (%#x)", name, unit, + entry ? entry->status_text : "Unknown", + ccb.ccb_h.status); + error = 1; + close(fd); + goto bailout; + } + + /* + * The kernel fills in the bus/target/lun. We don't + * need the passthrough device name and unit number since + * we aren't going to open it. + */ + bus = ccb.ccb_h.path_id; + target = ccb.ccb_h.target_id; + lun = ccb.ccb_h.target_lun; + + arglist |= CAM_ARG_BUS | CAM_ARG_TARGET | CAM_ARG_LUN; + + close(fd); } if ((arglist & CAM_ARG_BUS) @@ -3146,6 +3241,8 @@ dorescan_or_reset(int argc, char **argv, int rescan) else error = rescan_or_reset_bus(bus, rescan); +bailout: + return(error); } @@ -8916,8 +9013,8 @@ usage(int printlong) " camcontrol eject [dev_id][generic args]\n" " camcontrol reprobe [dev_id][generic args]\n" #endif /* MINIMALISTIC */ -" camcontrol rescan \n" -" camcontrol reset \n" +" camcontrol rescan \n" +" camcontrol reset \n" #ifndef MINIMALISTIC " camcontrol defects [dev_id][generic args] <-f format> [-P][-G]\n" " [-q][-s][-S offset][-X]\n" @@ -8996,8 +9093,8 @@ usage(int printlong) "load send a Start Unit command to the device with the load bit set\n" "eject send a Stop Unit command to the device with the eject bit set\n" "reprobe update capacity information of the given device\n" -"rescan rescan all buses, the given bus, or bus:target:lun\n" -"reset reset all buses, the given bus, or bus:target:lun\n" +"rescan rescan all buses, the given bus, bus:target:lun or device\n" +"reset reset all buses, the given bus, bus:target:lun or device\n" "defects read the defect list of the specified device\n" "modepage display or edit (-e) the given mode page\n" "cmd send the given SCSI command, may need -i or -o as well\n"