diff --git a/lib/libcam/Makefile b/lib/libcam/Makefile --- a/lib/libcam/Makefile +++ b/lib/libcam/Makefile @@ -5,8 +5,8 @@ LIB= cam SHLIBDIR?= /lib SRCS= camlib.c scsi_cmdparse.c scsi_all.c scsi_da.c scsi_sa.c cam.c \ - ata_all.c nvme_all.c smp_all.c -INCS= camlib.h + ata_all.c nvme_all.c smp_all.c scsi_wrap.c +INCS= camlib.h scsi_wrap.h LIBADD= sbuf diff --git a/lib/libcam/scsi_wrap.h b/lib/libcam/scsi_wrap.h new file mode 100644 --- /dev/null +++ b/lib/libcam/scsi_wrap.h @@ -0,0 +1,38 @@ +/*- + * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, 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 DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _CAM_SCSI_WRAP_H +#define _CAM_SCSI_WRAP_H + +void *scsi_wrap_get_physical_element_status(struct cam_device *device, + int task_attr, int retry_count, int timeout, uint8_t report_type, + uint32_t start_element); +void *scsi_wrap_inquiry(struct cam_device *device, uint32_t page, uint32_t length); +struct scsi_vpd_block_device_characteristics *scsi_wrap_vpd_block_device_characteristics( + struct cam_device *device); + +#endif /* _CAM_SCSI_WRAP_H */ diff --git a/lib/libcam/scsi_wrap.c b/lib/libcam/scsi_wrap.c new file mode 100644 --- /dev/null +++ b/lib/libcam/scsi_wrap.c @@ -0,0 +1,181 @@ +/*- + * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, 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 DAMAGE. + */ + +/* + * Wrapper functions to make requests and get answers w/o managing the + * details. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "camlib.h" +#include "scsi_wrap.h" + +void * +scsi_wrap_get_physical_element_status(struct cam_device *device, int task_attr, int retry_count, + int timeout, uint8_t report_type, uint32_t start_element) +{ + uint32_t allocation_length; + union ccb *ccb = NULL; + struct scsi_get_physical_element_hdr *hdr = NULL; + uint32_t dtors; + uint32_t reported; + + ccb = cam_getccb(device); + if (ccb == NULL) { + warnx("Can't allocate ccb"); + return (NULL); + } + + /* + * Do the request up to twice. Once to get the length and once to get + * the data. We'll guess that 4096 is enough to almost always long + * enough since that's 127 entries and most drives have < 20 heads. If + * by chance it's not, then we'll loop once as we'll then know the + * proper length. + */ + allocation_length = MAX(sizeof(*hdr), 4096); +again: + free(hdr); + hdr = calloc(allocation_length, 1); + if (hdr == NULL) { + warnx("Can't allocate memory for physical element list"); + return (NULL); + } + + scsi_get_physical_element_status(&ccb->csio, + retry_count, + NULL, + task_attr, + (uint8_t *)hdr, + allocation_length, + report_type, + start_element, + 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"); + goto errout; + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + cam_error_print(device, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + goto errout; + } + + dtors = scsi_4btoul(hdr->num_descriptors); + reported = scsi_4btoul(hdr->num_returned); + if (dtors != 0 && dtors != reported) { + /* + * Get all the data... in the future we may need to step through + * a long list, but so far all drives I've found fit into one + * response. A 4k transfer can do 128 heads and current designs + * have 16. + */ + allocation_length = dtors * sizeof(struct scsi_get_physical_element_descriptor) + + sizeof(*hdr); + goto again; + } + cam_freeccb(ccb); + return (hdr); +errout: + cam_freeccb(ccb); + free(hdr); + return (NULL); +} + +void * +scsi_wrap_inquiry(struct cam_device *device, uint32_t page, uint32_t length) +{ + union ccb *ccb; + uint8_t *buf; + + ccb = cam_getccb(device); + + if (ccb == NULL) + return (NULL); + + buf = malloc(length); + + if (buf == NULL) { + cam_freeccb(ccb); + return (NULL); + } + + scsi_inquiry(&ccb->csio, + /*retries*/ 0, + /*cbfcnp*/ NULL, + /* tag_action */ MSG_SIMPLE_Q_TAG, + /* inq_buf */ (u_int8_t *)buf, + /* inq_len */ length, + /* evpd */ 1, + /* page_code */ page, + /* sense_len */ SSD_FULL_SIZE, + /* timeout */ 5000); + + /* Disable freezing the device queue */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + // ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; + + if (cam_send_ccb(device, ccb) < 0) { + warn("error sending INQUIRY command"); + cam_freeccb(ccb); + free(buf); + return (NULL); + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + free(buf); + buf = NULL; + } + cam_freeccb(ccb); + return (buf); +} + +struct scsi_vpd_block_device_characteristics * +scsi_wrap_vpd_block_device_characteristics(struct cam_device *device) +{ + + return ((struct scsi_vpd_block_device_characteristics *)scsi_wrap_inquiry( + device, SVPD_BDC, sizeof(struct scsi_vpd_block_device_characteristics))); +} diff --git a/sys/cam/scsi/scsi_all.h b/sys/cam/scsi/scsi_all.h --- a/sys/cam/scsi/scsi_all.h +++ b/sys/cam/scsi/scsi_all.h @@ -2158,6 +2158,9 @@ #define REPORT_PRIORITY 0x0E #define REPORT_TIMESTAMP 0x0F #define MANAGEMENT_PROTOCOL_IN 0x10 +#define GET_PHYSICAL_ELEMENT_STATUS 0x17 +#define REMOVE_ELEMENT_AND_TRUNCATE 0x18 +#define RESTORE_ELEMENTS_AND_REBUILD 0x19 /* Maintenance Out Service Action Codes */ #define SET_IDENTIFY_INFORMATION 0x06 #define SET_TARGET_PORT_GROUPS 0x0A @@ -2847,6 +2850,8 @@ uint8_t depopulation_time[4]; uint8_t reserved2[48]; }; +_Static_assert(sizeof(struct scsi_vpd_block_device_characteristics) == 64, + "scsi_vpd_block_characteristics wrong size"); #define SBDC_IS_PRESENT(bdc, length, field) \ ((length >= offsetof(struct scsi_vpd_block_device_characteristics, \ @@ -3064,6 +3069,70 @@ struct scsi_report_luns_lundata luns[0]; }; +/* + * GET PHYSICAL ELEMENT STATUS (GPES) from SBC-4 (r21 or later) + * REMOVE ELEMENT AND TRUNCATE (RET) from SBC-4 (r21 or later) + * RESTORE ELEMENT AND REBUILD (RER) from SBC-4 (r21 or later) + * + * Queries drives that support it for the status of feach of their physical + * storage elements (which typically map to heads, but aren't required to). + * These elements can be selective removed (at a reduced capacity) or restored + * to service. + */ +struct scsi_get_physical_element_status +{ + uint8_t opcode; + uint8_t service_action; + uint8_t rsvd[4]; + uint8_t starting_element[4]; + uint8_t allocation_length[4]; + uint8_t report_type; +#define SCSI_GPES_FILTER_ALL 0x00 +#define SCSI_GPES_FILTER_EXEPTION 0x40 +#define SCSI_GPES_REPORT_TYPE_PHYS 0x00 +#define SCSI_GEPS_REPORT_TYPE_STORAGE 0x01 + uint8_t control; +}; +_Static_assert(sizeof(struct scsi_get_physical_element_status) == 16, + "scsi_get_physical_element_status wrong size"); + +struct scsi_get_physical_element_hdr +{ + uint8_t num_descriptors[4]; + uint8_t num_returned[4]; + uint8_t id_depop[4]; + uint8_t rsvd[20]; +}; +_Static_assert(sizeof(struct scsi_get_physical_element_hdr) == 32, + "scsi_get_physical_element_hdr wrong size"); + +struct scsi_get_physical_element_descriptor +{ + uint8_t rsvd1[4]; + uint8_t element_identifier[4]; + uint8_t rsvd2[5]; + uint8_t ralwd; + uint8_t physical_element_type; +#define GPED_TYPE_STORAGE 0x1 + uint8_t physical_element_health; + uint8_t capacity[8]; + uint8_t rsvd3[8]; +}; +_Static_assert(sizeof(struct scsi_get_physical_element_descriptor) == 32, + "scsi_get_physical_element_descriptor wrong size"); + +struct scsi_remove_element_and_truncate +{ + uint8_t opcode; + uint8_t service_action; + uint8_t requested_capacity[8]; + uint8_t element_identifier[4]; + uint8_t rsvd; + uint8_t control; +}; +_Static_assert(sizeof(struct scsi_remove_element_and_truncate) == 16, + "scsi_remove_element_and_truncate wrong size"); + struct scsi_target_group { uint8_t opcode; @@ -4160,6 +4229,24 @@ uint16_t param_list_length, uint8_t sense_len, uint32_t timeout); +void scsi_get_physical_element_status(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, uint8_t *data_ptr, + uint16_t allocation_length, uint8_t report_type, + uint32_t starting_element, + uint8_t sense_len, uint32_t timeout); + +void scsi_remove_element_and_truncate(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, + uint64_t requested_capacity, uint32_t element_id, + uint8_t sense_len, uint32_t timeout); + +void scsi_restore_elements_and_rebuild(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, + uint8_t sense_len, uint32_t timeout); + void scsi_read_buffer(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb*), uint8_t tag_action, int mode, diff --git a/sys/cam/scsi/scsi_all.c b/sys/cam/scsi/scsi_all.c --- a/sys/cam/scsi/scsi_all.c +++ b/sys/cam/scsi/scsi_all.c @@ -8751,6 +8751,88 @@ timeout); } +void +scsi_get_physical_element_status(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, uint8_t *data_ptr, + uint16_t allocation_length, uint8_t report_type, + uint32_t starting_element, + uint8_t sense_len, uint32_t timeout) +{ + struct scsi_get_physical_element_status *scsi_cmd; + + scsi_cmd = (struct scsi_get_physical_element_status *)&csio->cdb_io.cdb_bytes; + memset(scsi_cmd, 0, sizeof(*scsi_cmd)); + scsi_cmd->opcode = SERVICE_ACTION_IN; + scsi_cmd->service_action = GET_PHYSICAL_ELEMENT_STATUS; + scsi_ulto4b(starting_element, scsi_cmd->starting_element); + scsi_ulto4b(allocation_length, scsi_cmd->allocation_length); + + cam_fill_csio(csio, + retries, + cbfcnp, + /*flags*/ CAM_DIR_IN, + tag_action, + data_ptr, + allocation_length, + sense_len, + sizeof(*scsi_cmd), + timeout); +} + +void +scsi_remove_element_and_truncate(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, + uint64_t requested_capacity, uint32_t element_id, + uint8_t sense_len, uint32_t timeout) +{ + struct scsi_remove_element_and_truncate *scsi_cmd; + + scsi_cmd = (struct scsi_remove_element_and_truncate *)&csio->cdb_io.cdb_bytes; + memset(scsi_cmd, 0, sizeof(*scsi_cmd)); + scsi_cmd->opcode = SERVICE_ACTION_IN; + scsi_cmd->service_action = REMOVE_ELEMENT_AND_TRUNCATE; + scsi_u64to8b(requested_capacity, scsi_cmd->requested_capacity); + scsi_ulto4b(element_id, scsi_cmd->element_identifier); + + cam_fill_csio(csio, + retries, + cbfcnp, + /*flags*/ CAM_DIR_OUT, + tag_action, + NULL, + 0, + sense_len, + sizeof(*scsi_cmd), + timeout); +} + +void +scsi_restore_elements_and_rebuild(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, + uint8_t sense_len, uint32_t timeout) +{ + struct scsi_service_action_in *scsi_cmd; + + scsi_cmd = (struct scsi_service_action_in *)&csio->cdb_io.cdb_bytes; + memset(scsi_cmd, 0, sizeof(*scsi_cmd)); + scsi_cmd->opcode = SERVICE_ACTION_IN; + scsi_cmd->service_action = RESTORE_ELEMENTS_AND_REBUILD; + + cam_fill_csio(csio, + retries, + cbfcnp, + /*flags*/ CAM_DIR_OUT, + tag_action, + NULL, + 0, + sense_len, + sizeof(*scsi_cmd), + timeout); +} + void scsi_read_buffer(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb*),