git: 327da43602cc - stable/13 - camcontrol: Force a rescan of the lun after firmware download.

From: Warner Losh <imp_at_FreeBSD.org>
Date: Wed, 09 Mar 2022 20:52:51 UTC
The branch stable/13 has been updated by imp:

URL: https://cgit.FreeBSD.org/src/commit/?id=327da43602cc40836d0858e22e200f5e39edae4b

commit 327da43602cc40836d0858e22e200f5e39edae4b
Author:     Warner Losh <imp@FreeBSD.org>
AuthorDate: 2022-02-22 17:34:36 +0000
Commit:     Warner Losh <imp@FreeBSD.org>
CommitDate: 2022-03-09 20:52:22 +0000

    camcontrol: Force a rescan of the lun after firmware download.
    
    After downloading the firmware to a device, it's inquiry data likely
    will change. Force a rescan of the target with the CAM_EXPECT_INQ_CHANGE
    flag to get it to record the new inqury data as being expected. This
    avoids the need for a 'camcontrol rescan' on the device which detaches
    and re-attaches the disk (da, ada) device. This brings fwdownload up to
    nvmecontrol's ability to do the same thing w/o changing the exposed
    nvme/nvd/nda device. We scan the target and not the LUN because dual
    actuator drives have multiple LUNs, but the firmware is global across
    many vendors' drives (and the so far theoretical ones that aren't won't
    be harmed by the rescan).
    
    Since the underlying struct disk is now preserved accross this
    operation, it's now possible to upgrade firmware of a root device w/o
    crashing the system.  On systems that are quite busy, the worst that
    happens is that certain operaions are reported cancelled when the new
    firmware is activated. These operations are retried with the normal CAM
    recovery mechanisms and will work on the retry. The only visible hiccup
    is the time that new firmware is flashing / initializing. One should not
    consider this operation completely risk free, however, since not all
    drives are well behaved after a firmware download.
    
    MFC After:              1 week
    Relnotes:               yes
    Sponsored by:           Netflix
    Feedback by:            mav
    Differential Revision:  https://reviews.freebsd.org/D34325
    
    (cherry picked from commit 9835900cb95bcd068774934961fb1419719d595b)
---
 sbin/camcontrol/fwdownload.c | 67 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/sbin/camcontrol/fwdownload.c b/sbin/camcontrol/fwdownload.c
index 87aef6d3c240..56ccaaac1e59 100644
--- a/sbin/camcontrol/fwdownload.c
+++ b/sbin/camcontrol/fwdownload.c
@@ -57,12 +57,15 @@ __FBSDID("$FreeBSD$");
 
 #include <err.h>
 #include <fcntl.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
+#include <cam/cam.h>
 #include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_pass.h>
 #include <cam/scsi/scsi_message.h>
 #include <camlib.h>
 
@@ -764,6 +767,67 @@ bailout:
 	return (retval);
 }
 
+/*
+ * After the firmware is downloaded, we know the sense data has changed (or is
+ * likely to change since it contains the firmware version).  Rescan the target
+ * with a flag to tell the kernel it's OK. This allows us to continnue using the
+ * old periph/disk in the kernel, which is less disruptive. We rescan the target
+ * because multilun devices usually update all the luns after the first firmware
+ * download.
+ */
+static int
+fw_rescan_lun(struct cam_device *dev, bool printerrors)
+{
+	union ccb ccb;
+	int fd;
+	target_id_t target;
+	uint32_t bus;
+
+	/* Can only send XPT_SCAN_TGT via /dev/xpt, not pass device in *dev */
+	if ((fd = open(XPT_DEVICE, O_RDWR)) < 0) {
+		warnx("error opening transport layer device %s\n",
+		    XPT_DEVICE);
+		warn("%s", XPT_DEVICE);
+		return (1);
+	}
+
+	/* Fill in the bus and target IDs as they don't seem to be in *dev */
+	bzero(&ccb, sizeof(union ccb));
+	ccb.ccb_h.func_code = XPT_GDEVLIST;
+	strlcpy(ccb.cgdl.periph_name, dev->device_name, sizeof(ccb.cgdl.periph_name));
+	ccb.cgdl.unit_number = dev->dev_unit_num;
+	if (cam_send_ccb(dev, &ccb) < 0) {
+		warn("send_ccb GDEVLIST failed\n");
+		close(fd);
+		return (1);
+	}
+	bus = ccb.ccb_h.path_id;
+	target = ccb.ccb_h.target_id;
+
+	/* Rescan the target */
+	bzero(&ccb, sizeof(union ccb));
+	ccb.ccb_h.func_code = XPT_SCAN_TGT;
+	ccb.ccb_h.path_id = bus;
+	ccb.ccb_h.target_id = target;
+	ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;
+	ccb.crcn.flags = CAM_EXPECT_INQ_CHANGE;
+	ccb.ccb_h.pinfo.priority = 5;	/* run this at a low priority */
+
+	if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0) {
+		warn("CAMIOCOMMAND XPT_SCAN_TGT ioctl failed");
+		close(fd);
+		return (1);
+	}
+	if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+		warn("Can't send rescan lun");
+		if (printerrors)
+			cam_error_print(dev, &ccb, CAM_ESF_ALL, CAM_EPF_ALL,
+			    stderr);
+		return (1);
+	}
+	return (0);
+}
+
 /* 
  * Download firmware stored in buf to cam_dev. If simulation mode
  * is enabled, only show what packet sizes would be sent to the 
@@ -919,6 +983,9 @@ bailout:
 	if (quiet == 0)
 		progress_complete(&progress, size - img_size);
 	cam_freeccb(ccb);
+	if (retval == 0 && !sim_mode) {
+		fw_rescan_lun(cam_dev, printerrors);
+	}
 	return (retval);
 }