git: 53cce2e744c1 - main - camcontrol: depop command

Warner Losh imp at FreeBSD.org
Mon Sep 20 22:39:34 UTC 2021


The branch main has been updated by imp:

URL: https://cgit.FreeBSD.org/src/commit/?id=53cce2e744c1086bd5c6aa18bff4daac42468538

commit 53cce2e744c1086bd5c6aa18bff4daac42468538
Author:     Warner Losh <imp at FreeBSD.org>
AuthorDate: 2021-09-17 22:30:06 +0000
Commit:     Warner Losh <imp at FreeBSD.org>
CommitDate: 2021-09-20 22:27:59 +0000

    camcontrol: depop command
    
    Implement and document the new depop command. This command manages drive elements
    for drives that support it. Storage elements are typically heads. Element status
    can be discovered. Elements may be removed or restored. And the status of any
    current depop operation can be assessed.
    
    depop -d elm will remove element elm and truncate available capacity.
    depop -l will list the current drive elements and their current status.
    depop -r elm will try to restore all retired elements and rebuild capacity.
    
    Changing storage elements may reinitialize the drive. This operation will lose
    data and may take hours to complete. Use the drive provided timeout for
    operations by default.
    
    Reviewed by:            gbe (manpages)
    Sponsored by:           Netflix
    Differential Revision:  https://reviews.freebsd.org/D29018
---
 sbin/camcontrol/Makefile     |   2 +-
 sbin/camcontrol/camcontrol.8 |  61 +++++++++
 sbin/camcontrol/camcontrol.c |  15 +++
 sbin/camcontrol/camcontrol.h |   3 +
 sbin/camcontrol/depop.c      | 297 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 377 insertions(+), 1 deletion(-)

diff --git a/sbin/camcontrol/Makefile b/sbin/camcontrol/Makefile
index 3ed8b92372f8..958f37714662 100644
--- a/sbin/camcontrol/Makefile
+++ b/sbin/camcontrol/Makefile
@@ -5,7 +5,7 @@
 PACKAGE=runtime
 PROG=	camcontrol
 SRCS=	camcontrol.c util.c
-SRCS+=	attrib.c epc.c fwdownload.c modeedit.c persist.c progress.c timestamp.c zone.c
+SRCS+=	attrib.c depop.c epc.c fwdownload.c modeedit.c persist.c progress.c timestamp.c zone.c
 .if ${MK_NVME} != "no"
 .PATH:	${SRCTOP}/sbin/nvmecontrol
 CFLAGS+= -I${SRCTOP}/sbin/nvmecontrol -DWITH_NVME
diff --git a/sbin/camcontrol/camcontrol.8 b/sbin/camcontrol/camcontrol.8
index 9c128131d817..7790d047a255 100644
--- a/sbin/camcontrol/camcontrol.8
+++ b/sbin/camcontrol/camcontrol.8
@@ -366,6 +366,13 @@
 .Ic devtype
 .Op device id
 .Nm
+.Ic depop
+.Op device id
+.Op generic args
+.Ao Fl l | Fl d | Fl r Ac
+.Op Fl e Ar elem
+.Op Fl c Ar capacity
+.Nm
 .Ic help
 .Sh DESCRIPTION
 The
@@ -2594,6 +2601,60 @@ Device type is unknown
 .It illegal
 A programming error occurred
 .El
+.It Ic depop
+Commands necessary to support the depopulation (depop) of defective elements of a device
+(typically heads for hard drives) or setting capacity point (typically used on
+flash drives).
+Issues either GET PHYSICAL ELEMENT STATUS, REMOVE ELEMENT AND TRUNCATE, or RESTORE
+ELEMENT AND REBUILD command to manage storage elements of a drive.
+Removal or restoration of elements may take up to a day to complete.
+One of the
+.Fl d ,
+.Fl l ,
+or
+.Fl r
+options must be specified.
+These options are mutually exclusive.
+Only SCSI drives are supported.
+Changing the storage elements of a storage drive may result in the loss of all
+data on that storage drive.
+The drive may need to reinitialize after
+.Fl d
+or
+.Fl r
+commands.
+The data on the drive is inaccessible until one of these commands complete.
+Once one of these commands start, the drive is format corrupt until the
+operation successfully completes.
+While format corrupt, no read or write I/O is possible to the drive.
+If the drive power cycles, it will remain format corrupt and the operation
+must be restarted.
+TEST UNIT READY or
+.Dq camcontrol tur
+can monitor an in-progress depop operation.
+.Bl -tag -width 6n
+.It Fl c Ar capacity
+Specify the desired capacity point for the drive.
+Valid only for the
+.Fl d
+flag.
+.It Fl d
+Remove the physical element from service or set the capacity point specified by the
+.Fl e
+or
+.Fl c
+flags.
+The drive's capacity may be reduced by this operation.
+.It Fl e Ar element
+Specify the physical element to remove from service.
+Valid only for the
+.Fl d
+flag.
+.It Fl l
+Report the current status of the physical elements of a drive.
+.It Fl r
+Restore all the eligible physical elements to service.
+.El
 .It Ic help
 Print out verbose usage information.
 .El
diff --git a/sbin/camcontrol/camcontrol.c b/sbin/camcontrol/camcontrol.c
index 69939715898e..e560eb349f79 100644
--- a/sbin/camcontrol/camcontrol.c
+++ b/sbin/camcontrol/camcontrol.c
@@ -111,6 +111,7 @@ typedef enum {
 	CAM_CMD_POWER_MODE,
 	CAM_CMD_DEVTYPE,
 	CAM_CMD_AMA,
+	CAM_CMD_DEPOP,
 } cam_cmd;
 
 typedef enum {
@@ -228,6 +229,7 @@ static struct camcontrol_opts option_table[] = {
 	{"zone", CAM_CMD_ZONE, CAM_ARG_NONE, "ac:l:No:P:"},
 	{"epc", CAM_CMD_EPC, CAM_ARG_NONE, "c:dDeHp:Pr:sS:T:"},
 	{"timestamp", CAM_CMD_TIMESTAMP, CAM_ARG_NONE, "f:mrsUT:"},
+	{"depop", CAM_CMD_DEPOP, CAM_ARG_NONE, "ac:de:ls"},
 	{"help", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
 	{"-?", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
 	{"-h", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
@@ -9946,6 +9948,7 @@ usage(int printlong)
 "        camcontrol timestamp  [dev_id][generic_args] <-r [-f format|-m|-U]>|\n"
 "                              <-s <-f format -T time | -U >>\n"
 "        camcontrol devtype    [dev_id]\n"
+"        camcontrol depop      [dev_id] [-d | -l | -r] [-e element] [-c capacity]\n"
 "        camcontrol mmcsdcmd   [dev_id] [[-c mmc_opcode] [-a mmc_arg]\n"
 "                                  [-f mmc_flags] [-l data_len]\n"
 "                                  [-W [-b data_byte]]] |\n"
@@ -9999,6 +10002,7 @@ usage(int printlong)
 "epc         send ATA Extended Power Conditions commands\n"
 "timestamp   report or set the device's timestamp\n"
 "devtype     report the type of device\n"
+"depop       manage drive storage elements\n"
 "mmcsdcmd    send the given MMC command, needs -c and -a as well\n"
 "help        this message\n"
 "Device Identifiers:\n"
@@ -10208,6 +10212,12 @@ usage(int printlong)
 "-f format         the format of the time string passed into strptime(3)\n"
 "-T time           the time value passed into strptime(3)\n"
 "-U                set the timestamp of the device to UTC time\n"
+"depop arguments:\n"
+"-d                remove an element from service\n"
+"-l                list status of all elements of drive\n"
+"-r                restore all elements to service\n"
+"-e elm            element to remove\n"
+"-c capacity       requested new capacity\n"
 "mmcsdcmd arguments:\n"
 "-c mmc_cmd        MMC command to send to the card\n"
 "-a mmc_arg        Argument for the MMC command\n"
@@ -10631,6 +10641,11 @@ main(int argc, char **argv)
 		    task_attr, retry_count, timeout,
 		    arglist & CAM_ARG_VERBOSE);
 		break;
+	case CAM_CMD_DEPOP:
+		error = depop(cam_dev, argc, argv, combinedopt,
+		    task_attr, retry_count, timeout,
+		    arglist & CAM_ARG_VERBOSE);
+		break;
 	case CAM_CMD_USAGE:
 		usage(1);
 		break;
diff --git a/sbin/camcontrol/camcontrol.h b/sbin/camcontrol/camcontrol.h
index 85f2e8658fe8..b84587df7a3e 100644
--- a/sbin/camcontrol/camcontrol.h
+++ b/sbin/camcontrol/camcontrol.h
@@ -88,6 +88,9 @@ int epc(struct cam_device *device, int argc, char **argv, char *combinedopt,
 int timestamp(struct cam_device *device, int argc, char **argv,
 	      char *combinedopt, int task_attr, int retry_count, int timeout,
 	      int verbosemode);
+int depop(struct cam_device *device, int argc, char **argv,
+	  char *combinedopt, int task_attr, int retry_count, int timeout,
+	  int verbosemode);
 void mode_sense(struct cam_device *device, int *cdb_len, int dbd, int llbaa,
 		int pc, int page, int subpage, int task_attr, int retry_count,
 		int timeout, uint8_t *data, int datalen);
diff --git a/sbin/camcontrol/depop.c b/sbin/camcontrol/depop.c
new file mode 100644
index 000000000000..3dbd2ba5358d
--- /dev/null
+++ b/sbin/camcontrol/depop.c
@@ -0,0 +1,297 @@
+/*-
+ * Copyright (c) 2021 Netflix, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ */
+/*
+ * SCSI disk depop (head depopulation) support
+ *
+ * The standard defines 'storage elements' as the generic way of referring to a
+ * disk drive head. Each storage element has an identifier and an active status.
+ * The health of an element can be querried. Active elements may be removed from
+ * service with a REMOVE ELEMENT AND TRUNCATE (RET) command. Inactive element
+ * may be returned to service with a RESTORE ELEMENTS AND REBUILD (RER)
+ * command. GET PHYSICAL ELEMENT STATUS (GPES) will return a list of elements,
+ * their health, whether they are in service, how much capacity the element is
+ * used for, etc.
+ *
+ * When a depop operation starts, the drive becomes format corrupt. No normal
+ * I/O can be done to the drive and a limited number of CDBs will
+ * succeed. Status can be obtained by either a TEST UNIT READY or a GPES
+ * command. A drive reset will not stop a depop operation, but a power cycle
+ * will. A failed depop operation will be reported when the next TEST UNIT READY
+ * is sent to the drive. Drives that are format corrupt after an interrupted
+ * operation need to have that operation repeated.
+ *
+ * 'depop' provides a wrapper around all these functions.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <cam/cam.h>
+#include <cam/cam_debug.h>
+#include <cam/cam_ccb.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_message.h>
+#include <camlib.h>
+#include <scsi_wrap.h>
+#include "camcontrol.h"
+
+enum depop_action {
+	DEPOP_NONE,
+	DEPOP_LIST,
+	DEPOP_RESTORE,
+	DEPOP_REMOVE,
+};
+
+static int
+depop_list(struct cam_device *device, int task_attr, int retry_count,
+    int timeout, int verbosemode __unused)
+{
+	int error = 0;
+	uint32_t dtors;
+	struct scsi_get_physical_element_hdr *hdr;
+	struct scsi_get_physical_element_descriptor *dtor_ptr;
+
+	hdr = scsi_wrap_get_physical_element_status(device, task_attr, retry_count, timeout,
+	    SCSI_GPES_FILTER_ALL | SCSI_GPES_REPORT_TYPE_PHYS, 1);
+	if (hdr == NULL)
+		errx(1, "scsi_wrap_get_physical_element_status returned an error");
+
+	/*
+	 * OK, we have the data, not report it out.
+	 */
+	dtor_ptr = (struct scsi_get_physical_element_descriptor *)(hdr + 1);
+	dtors = scsi_4btoul(hdr->num_descriptors);
+	printf("Elem ID    * Health Capacity\n");
+	for (uint32_t i = 0; i < dtors; i++) {
+		uint32_t id = scsi_4btoul(dtor_ptr[i].element_identifier);
+		uint8_t ralwd = dtor_ptr[i].ralwd;
+		uint8_t type = dtor_ptr[i].physical_element_type;
+		uint8_t health = dtor_ptr[i].physical_element_health;
+		uint64_t cap = scsi_8btou64(dtor_ptr[i].capacity);
+		if (type != GPED_TYPE_STORAGE)
+			printf("0x%08x -- type unknown %d\n", id, type);
+		else
+			printf("0x%08x %c 0x%02x   %jd\n", id, ralwd ? '*' : ' ', health, cap);
+	}
+	printf("* -- Element can be restored\n");
+
+	free(hdr);
+	return (error);
+}
+
+static int
+depop_remove(struct cam_device *device, int task_attr, int retry_count,
+    int timeout, int verbosemode __unused, uint32_t elem, uint64_t capacity)
+{
+	union ccb *ccb;
+	int error = 0;
+
+	ccb = cam_getccb(device);
+	if (ccb == NULL) {
+		warnx("Can't allocate ccb");
+		return (1);
+	}
+	scsi_remove_element_and_truncate(&ccb->csio,
+	    retry_count,
+	    NULL,
+	    task_attr,
+	    capacity,
+	    elem,
+	    SSD_FULL_SIZE,
+	    timeout);
+	/* Disable freezing the device queue */
+	ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+	if (cam_send_ccb(device, ccb) < 0) {
+		warn("error sending GET PHYSICAL ELEMENT STATUS command");
+		error = 1;
+		goto out;
+	}
+
+	if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+		cam_error_print(device, ccb, CAM_ESF_ALL,
+				CAM_EPF_ALL, stderr);
+		error = 1;
+	}
+
+out:
+	cam_freeccb(ccb);
+	return (error);
+}
+
+static int
+depop_restore(struct cam_device *device, int task_attr, int retry_count,
+    int timeout, int verbosemode __unused)
+{
+	union ccb *ccb;
+	int error = 0;
+
+	ccb = cam_getccb(device);
+	if (ccb == NULL) {
+		warnx("Can't allocate ccb");
+		return (1);
+	}
+	scsi_restore_elements_and_rebuild(&ccb->csio,
+	    retry_count,
+	    NULL,
+	    task_attr,
+	    SSD_FULL_SIZE,
+	    timeout);
+
+	/* Disable freezing the device queue */
+	ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+	if (cam_send_ccb(device, ccb) < 0) {
+		warn("error sending GET PHYSICAL ELEMENT STATUS command");
+		error = 1;
+		goto out;
+	}
+
+	if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+		cam_error_print(device, ccb, CAM_ESF_ALL,
+				CAM_EPF_ALL, stderr);
+		error = 1;
+	}
+
+out:
+	cam_freeccb(ccb);
+	return (error);
+}
+
+#define MUST_BE_NONE() \
+	if (action != DEPOP_NONE) { \
+		warnx("Use only one of -d, -l, or -r"); \
+		error = 1; \
+		goto bailout; \
+	}
+
+int
+depop(struct cam_device *device, int argc, char **argv, char *combinedopt,
+    int task_attr, int retry_count, int timeout, int verbosemode)
+{
+	int c;
+	int action = DEPOP_NONE;
+	char *endptr;
+	int error = 0;
+	uint32_t elem = 0;
+	uint64_t capacity = 0;
+
+	while ((c = getopt(argc, argv, combinedopt)) != -1) {
+		switch (c) {
+		case 'c':
+			capacity = strtoumax(optarg, &endptr, 0);
+			if (*endptr != '\0') {
+				warnx("Invalid capacity: %s", optarg);
+				error = 1;
+				goto bailout;
+			}
+			break;
+		case 'e':
+			elem = strtoul(optarg, &endptr, 0);
+			if (*endptr != '\0') {
+				warnx("Invalid element: %s", optarg);
+				error = 1;
+				goto bailout;
+			}
+			break;
+		case 'd':
+			MUST_BE_NONE();
+			action = DEPOP_REMOVE;
+			break;
+		case 'l':
+			MUST_BE_NONE();
+			action  = DEPOP_LIST;
+			break;
+		case 'r':
+			MUST_BE_NONE();
+			action  = DEPOP_RESTORE;
+			break;
+		default:
+			break;
+		}
+	}
+
+	/*
+	 * Compute a sane timeout if none given. 5 seconds for the list command
+	 * and whatever the block device characteristics VPD says for other
+	 * depop commands. If there's no value in that field, default to 1
+	 * day. Experience has shown that these operations take the better part
+	 * of a day to complete, so a 1 day timeout default seems appropriate.
+	 */
+	if (timeout == 0 && action != DEPOP_NONE) {
+		if (action == DEPOP_LIST) {
+			timeout = 5 * 1000;
+		} else {
+			struct scsi_vpd_block_device_characteristics *bdc;
+
+			timeout = 24 * 60 * 60 * 1000;	/* 1 day */
+			bdc = scsi_wrap_vpd_block_device_characteristics(device);
+			if (bdc != NULL) {
+				timeout = scsi_4btoul(bdc->depopulation_time);
+			}
+			free(bdc);
+		}
+	}
+
+	switch (action) {
+	case DEPOP_NONE:
+		warnx("Must specify one of -d, -l, or -r");
+		error = 1;
+		break;
+	case DEPOP_REMOVE:
+		if (elem == 0 && capacity == 0) {
+			warnx("Must specify at least one of -e and/or -c");
+			error = 1;
+			break;
+		}
+		error = depop_remove(device, task_attr, retry_count, timeout,
+		    verbosemode, elem, capacity);
+		break;
+	case DEPOP_RESTORE:
+		error = depop_restore(device, task_attr, retry_count, timeout,
+		    verbosemode);
+		break;
+	case DEPOP_LIST:
+		error = depop_list(device, task_attr, retry_count, timeout,
+		    verbosemode);
+		break;
+	}
+
+bailout:
+
+	return (error);
+}


More information about the dev-commits-src-main mailing list