diff --git a/sbin/camcontrol/Makefile b/sbin/camcontrol/Makefile --- 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.h b/sbin/camcontrol/camcontrol.h --- a/sbin/camcontrol/camcontrol.h +++ b/sbin/camcontrol/camcontrol.h @@ -88,6 +88,9 @@ 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/camcontrol.8 b/sbin/camcontrol/camcontrol.8 --- 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 @@ .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 --- a/sbin/camcontrol/camcontrol.c +++ b/sbin/camcontrol/camcontrol.c @@ -111,6 +111,7 @@ CAM_CMD_POWER_MODE, CAM_CMD_DEVTYPE, CAM_CMD_AMA, + CAM_CMD_DEPOP, } cam_cmd; typedef enum { @@ -228,6 +229,7 @@ {"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 @@ " 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 @@ "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 @@ "-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 @@ 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/depop.c b/sbin/camcontrol/depop.c new file mode 100644 --- /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 +__FBSDID("$FreeBSD$"); + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#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); +}