/* * This file is mostly copied from libzbc. * * Copyright (C) 2009-2014, HGST, Inc. All rights reserved. * * This software is distributed under the terms of the BSD 2-clause license, * "as is," without technical support, and WITHOUT ANY WARRANTY, without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. You should have received a copy of the BSD 2-clause license along * with libzbc. If not, see . * * Authors: Damien Le Moal (damien.lemoal@hgst.com) * Christophe Louargant (christophe.louargant@hgst.com) * * Integrated into f2fs-tools by: * Jaegeuk Kim (jaegeuk@kernel.org) */ #include #include "zbc.h" static struct zbc_sg_cmd_s { char *cdb_cmd_name; int cdb_opcode; int cdb_sa; size_t cdb_length; int dir; } zbc_sg_cmd_list[ZBC_SG_CMD_NUM] = { /* ZBC_SG_TEST_UNIT_READY */ { "TEST UNIT READY", ZBC_SG_TEST_UNIT_READY_CDB_OPCODE, 0, ZBC_SG_TEST_UNIT_READY_CDB_LENGTH, SG_DXFER_NONE }, /* ZBC_SG_INQUIRY */ { "INQUIRY", ZBC_SG_INQUIRY_CDB_OPCODE, 0, ZBC_SG_INQUIRY_CDB_LENGTH, SG_DXFER_FROM_DEV }, /* ZBC_SG_READ_CAPACITY */ { "READ CAPACITY 16", ZBC_SG_READ_CAPACITY_CDB_OPCODE, ZBC_SG_READ_CAPACITY_CDB_SA, ZBC_SG_READ_CAPACITY_CDB_LENGTH, SG_DXFER_FROM_DEV }, /* ZBC_SG_READ */ { "READ 16", ZBC_SG_READ_CDB_OPCODE, 0, ZBC_SG_READ_CDB_LENGTH, SG_DXFER_FROM_DEV }, /* ZBC_SG_WRITE */ { "WRITE 16", ZBC_SG_WRITE_CDB_OPCODE, 0, ZBC_SG_WRITE_CDB_LENGTH, SG_DXFER_TO_DEV }, /* ZBC_SG_SYNC_CACHE */ { "SYNCHRONIZE CACHE 16", ZBC_SG_SYNC_CACHE_CDB_OPCODE, 0, ZBC_SG_SYNC_CACHE_CDB_LENGTH, SG_DXFER_NONE }, /* ZBC_SG_REPORT_ZONES */ { "REPORT ZONES", ZBC_SG_REPORT_ZONES_CDB_OPCODE, ZBC_SG_REPORT_ZONES_CDB_SA, ZBC_SG_REPORT_ZONES_CDB_LENGTH, SG_DXFER_FROM_DEV }, /* ZBC_SG_OPEN_ZONE */ { "OPEN ZONE", ZBC_SG_OPEN_ZONE_CDB_OPCODE, ZBC_SG_OPEN_ZONE_CDB_SA, ZBC_SG_OPEN_ZONE_CDB_LENGTH, SG_DXFER_NONE }, /* ZBC_SG_CLOSE_ZONE */ { "CLOSE ZONE", ZBC_SG_CLOSE_ZONE_CDB_OPCODE, ZBC_SG_CLOSE_ZONE_CDB_SA, ZBC_SG_CLOSE_ZONE_CDB_LENGTH, SG_DXFER_NONE }, /* ZBC_SG_FINISH_ZONE */ { "FINISH ZONE", ZBC_SG_FINISH_ZONE_CDB_OPCODE, ZBC_SG_FINISH_ZONE_CDB_SA, ZBC_SG_FINISH_ZONE_CDB_LENGTH, SG_DXFER_NONE }, /* ZBC_SG_RESET_WRITE_POINTER */ { "RESET WRITE POINTER", ZBC_SG_RESET_WRITE_POINTER_CDB_OPCODE, ZBC_SG_RESET_WRITE_POINTER_CDB_SA, ZBC_SG_RESET_WRITE_POINTER_CDB_LENGTH, SG_DXFER_NONE }, /* ZBC_SG_SET_ZONES */ { "SET ZONES", ZBC_SG_SET_ZONES_CDB_OPCODE, ZBC_SG_SET_ZONES_CDB_SA, ZBC_SG_SET_ZONES_CDB_LENGTH, SG_DXFER_NONE }, /* ZBC_SG_SET_WRITE_POINTER */ { "SET WRITE POINTER", ZBC_SG_SET_WRITE_POINTER_CDB_OPCODE, ZBC_SG_SET_WRITE_POINTER_CDB_SA, ZBC_SG_SET_WRITE_POINTER_CDB_LENGTH, SG_DXFER_NONE }, /* ZBC_SG_ATA12 */ { "ATA 12", ZBC_SG_ATA12_CDB_OPCODE, 0, ZBC_SG_ATA12_CDB_LENGTH, 0 }, /* ZBC_SG_ATA16 */ { "ATA 16", ZBC_SG_ATA16_CDB_OPCODE, 0, ZBC_SG_ATA16_CDB_LENGTH, 0 } }; static void zbc_sg_cmd_set_bytes(uint8_t *cmd, void *buf, int bytes) { uint8_t *v = (uint8_t *) buf; int i; for (i = 0; i < bytes; i++) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ /* The least significant byte is stored last */ cmd[bytes - i - 1] = v[i]; #else /* The most significant byte is stored first */ cmd[i] = v[i]; #endif } return; } static void zbc_sg_cmd_get_bytes(uint8_t *val, union converter *conv, int bytes) { uint8_t *v = (uint8_t *) val; int i; memset(conv, 0, sizeof(union converter)); for(i = 0; i < bytes; i++) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ conv->val_buf[bytes - i - 1] = v[i]; #else conv->val_buf[i] = v[i]; #endif } return; } static inline void zbc_sg_cmd_set_int64(uint8_t *buf, uint64_t val) { zbc_sg_cmd_set_bytes(buf, &val, 8); return; } static inline void zbc_sg_cmd_set_int32(uint8_t *buf, uint32_t val) { zbc_sg_cmd_set_bytes(buf, &val, 4); return; } static inline uint32_t zbc_sg_cmd_get_int32(uint8_t *buf) { union converter conv; zbc_sg_cmd_get_bytes(buf, &conv, 4); return conv.val32; } static inline uint64_t zbc_sg_cmd_get_int64(uint8_t *buf) { union converter conv; zbc_sg_cmd_get_bytes(buf, &conv, 8); return( conv.val64 ); } static void zbc_sg_cmd_destroy(zbc_sg_cmd_t *cmd) { /* Free the command */ if (!cmd) return; if (cmd->out_buf && cmd->out_buf_needfree) { free(cmd->out_buf); cmd->out_buf = NULL; cmd->out_bufsz = 0; } memset(cmd, 0, sizeof(*cmd)); return; } static int zbc_sg_cmd_init(zbc_sg_cmd_t *cmd, int cmd_code, uint8_t *out_buf, size_t out_bufsz) { int ret = 0; if ((!cmd) || (cmd_code < 0) || (cmd_code >= ZBC_SG_CMD_NUM) ) { ERR_MSG("Invalid command specified\n"); return -EINVAL; } /* Set command */ memset(cmd, 0, sizeof(zbc_sg_cmd_t)); cmd->code = cmd_code; cmd->cdb_sz = zbc_sg_cmd_list[cmd_code].cdb_length; cmd->cdb_opcode = zbc_sg_cmd_list[cmd_code].cdb_opcode; cmd->cdb_sa = zbc_sg_cmd_list[cmd_code].cdb_sa; /* Set output buffer */ if (out_buf) { /* Set specified buffer */ if (!out_bufsz) { ERR_MSG("Invalid 0 output buffer size\n"); ret = -EINVAL; goto out; } cmd->out_buf = out_buf; cmd->out_bufsz = out_bufsz; } else if (out_bufsz) { /* Allocate a buffer */ ret = posix_memalign((void **)&cmd->out_buf, sysconf(_SC_PAGESIZE), out_bufsz); if ( ret != 0 ) { ERR_MSG("No memory for output buffer (%zu B)\n", out_bufsz); ret = -ENOMEM; goto out; } memset(cmd->out_buf, 0, out_bufsz); cmd->out_bufsz = out_bufsz; cmd->out_buf_needfree = 1; } /* OK: setup SGIO header */ memset(&cmd->io_hdr, 0, sizeof(sg_io_hdr_t)); cmd->io_hdr.interface_id = 'S'; cmd->io_hdr.timeout = 20000; cmd->io_hdr.flags = 0; //SG_FLAG_DIRECT_IO; cmd->io_hdr.cmd_len = cmd->cdb_sz; cmd->io_hdr.cmdp = &cmd->cdb[0]; cmd->io_hdr.dxfer_direction = zbc_sg_cmd_list[cmd_code].dir; cmd->io_hdr.dxfer_len = cmd->out_bufsz; cmd->io_hdr.dxferp = cmd->out_buf; cmd->io_hdr.mx_sb_len = ZBC_SG_SENSE_MAX_LENGTH; cmd->io_hdr.sbp = cmd->sense_buf; out: if (ret != 0) zbc_sg_cmd_destroy(cmd); return ret; } static char *zbc_sg_cmd_name(zbc_sg_cmd_t *cmd) { char *name; if ((cmd->code >= 0) && (cmd->code < ZBC_SG_CMD_NUM)) { name = zbc_sg_cmd_list[cmd->code].cdb_cmd_name; } else { name = "(UNKNOWN COMMAND)"; } return name; } static void zbc_sg_set_sense(struct f2fs_configuration *c, uint8_t *sense_buf) { if (sense_buf == NULL) { c->zbd_errno.sk = 0x00; c->zbd_errno.asc_ascq = 0x0000; } else { if ((sense_buf[0] & 0x7F) == 0x72 || (sense_buf[0] & 0x7F) == 0x73) { /* store sense key, ASC/ASCQ */ c->zbd_errno.sk = sense_buf[1] & 0x0F; c->zbd_errno.asc_ascq = ((int)sense_buf[2] << 8) | (int)sense_buf[3]; } else if ((sense_buf[0] & 0x7F) == 0x70 || (sense_buf[0] & 0x7F) == 0x71) { /* store sense key, ASC/ASCQ */ c->zbd_errno.sk = sense_buf[2] & 0x0F; c->zbd_errno.asc_ascq = ((int)sense_buf[12] << 8) | (int)sense_buf[13]; } } return; } static int zbc_sg_cmd_exec(struct f2fs_configuration *c, zbc_sg_cmd_t *cmd) { int ret; /* Send the SG_IO command */ ret = ioctl(c->fd, SG_IO, &cmd->io_hdr); if (ret) { ERR_MSG("SG_IO ioctl failed (%s)\n", strerror(errno)); goto out; } /* Reset errno */ zbc_sg_set_sense(c, NULL); DBG(1, "Command %s done: status 0x%02x (0x%02x), host status 0x%04x, driver status 0x%04x (flags 0x%04x)\n", zbc_sg_cmd_name(cmd), (unsigned int)cmd->io_hdr.status, (unsigned int)cmd->io_hdr.masked_status, (unsigned int)cmd->io_hdr.host_status, (unsigned int)zbc_sg_cmd_driver_status(cmd), (unsigned int)zbc_sg_cmd_driver_flags(cmd)); /* Check status */ if (((cmd->code == ZBC_SG_ATA12) || (cmd->code == ZBC_SG_ATA16)) && (cmd->cdb[2] & (1 << 5)) ) { /* ATA command status */ if (cmd->io_hdr.status != ZBC_SG_CHECK_CONDITION) { zbc_sg_set_sense(c, cmd->sense_buf); ret = -EIO; goto out; } if ((zbc_sg_cmd_driver_status(cmd) == ZBC_SG_DRIVER_SENSE) && (cmd->io_hdr.sb_len_wr > 21) && (cmd->sense_buf[21] != 0x50) ) { zbc_sg_set_sense(c, cmd->sense_buf); ret = -EIO; goto out; } cmd->io_hdr.status = 0; } if (cmd->io_hdr.status || (cmd->io_hdr.host_status != ZBC_SG_DID_OK) || (zbc_sg_cmd_driver_status(cmd) && (zbc_sg_cmd_driver_status(cmd) != ZBC_SG_DRIVER_SENSE)) ) { ERR_MSG("Command %s failed with status 0x%02x (0x%02x), host status 0x%04x, driver status 0x%04x (flags 0x%04x)\n", zbc_sg_cmd_name(cmd), (unsigned int)cmd->io_hdr.status, (unsigned int)cmd->io_hdr.masked_status, (unsigned int)cmd->io_hdr.host_status, (unsigned int)zbc_sg_cmd_driver_status(cmd), (unsigned int)zbc_sg_cmd_driver_flags(cmd)); zbc_sg_set_sense(c, cmd->sense_buf); ret = -EIO; goto out; } if (cmd->io_hdr.resid) { ERR_MSG("Transfer missing %d B of data\n", cmd->io_hdr.resid); cmd->out_bufsz -= cmd->io_hdr.resid; } out: return ret; } #define ZBC_SCSI_REPORT_ZONES_BUFSZ 524288 int zbc_scsi_report_zones(struct f2fs_configuration *c) { zbc_sg_cmd_t cmd; uint8_t *buf; zbc_zone_t *z, *zones = NULL; int i, buf_nz, ret; size_t bufsz; uint32_t idx = 0, nr_zones = 0; uint64_t next_lba = 0; int phase = 0; next: bufsz = ZBC_ZONE_DESCRIPTOR_OFFSET; if (phase) { if (c->nr_zones - idx == 0) return 0; bufsz += (size_t)(c->nr_zones - idx) * ZBC_ZONE_DESCRIPTOR_LENGTH; if (bufsz > ZBC_SCSI_REPORT_ZONES_BUFSZ) bufsz = ZBC_SCSI_REPORT_ZONES_BUFSZ; } /* For in kernel ATA translation: align to 512 B */ bufsz = (bufsz + 511) & ~511; /* Allocate and intialize report zones command */ ret = zbc_sg_cmd_init(&cmd, ZBC_SG_REPORT_ZONES, NULL, bufsz); if (ret) { ERR_MSG("zbc_sg_cmd_init failed\n"); return ret; } /* Fill command CDB: * +=============================================================================+ * | Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | * |Byte | | | | | | | | | * |=====+==========================+============================================| * | 0 | Operation Code (95h) | * |-----+-----------------------------------------------------------------------| * | 1 | Reserved | Service Action (00h) | * |-----+-----------------------------------------------------------------------| * | 2 | (MSB) | * |- - -+--- Zone Start LBA ---| * | 9 | (LSB) | * |-----+-----------------------------------------------------------------------| * | 10 | (MSB) | * |- - -+--- Allocation Length ---| * | 13 | (LSB) | * |-----+-----------------------------------------------------------------------| * | 14 |Partial |Reserved| Reporting Options | * |-----+-----------------------------------------------------------------------| * | 15 | Control | * +=============================================================================+ */ cmd.cdb[0] = ZBC_SG_REPORT_ZONES_CDB_OPCODE; cmd.cdb[1] = ZBC_SG_REPORT_ZONES_CDB_SA; zbc_sg_cmd_set_int64(&cmd.cdb[2], next_lba); zbc_sg_cmd_set_int32(&cmd.cdb[10], (unsigned int) bufsz); cmd.cdb[14] = 0; /* Send the SG_IO command */ ret = zbc_sg_cmd_exec(c, &cmd); if (ret != 0) goto out; if (cmd.out_bufsz < ZBC_ZONE_DESCRIPTOR_OFFSET) { ERR_MSG("Not enough data received (need at least %d B, got %zu B)\n", ZBC_ZONE_DESCRIPTOR_OFFSET, cmd.out_bufsz); ret = -EIO; goto out; } /* Process output: * +=============================================================================+ * | Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | * |Byte | | | | | | | | | * |=====+=======================================================================| * | 0 | (MSB) | * |- - -+--- Zone List Length (n - 64) ---| * | 3 | (LSB) | * |-----+-----------------------------------------------------------------------| * | 4 | Reserved | Same | * |-----+-----------------------------------------------------------------------| * | 5 | | * |- - -+--- Reserved ---| * | 7 | | * |-----+-----------------------------------------------------------------------| * | 8 | (MSB) | * |- - -+--- Maximum LBA ---| * | 15 | (LSB) | * |-----+-----------------------------------------------------------------------| * | 16 | (MSB) | * |- - -+--- Reserved ---| * | 63 | (LSB) | * |=====+=======================================================================| * | | Vendor-Specific Parameters | * |=====+=======================================================================| * | 64 | (MSB) | * |- - -+--- Zone Descriptor [first] ---| * | 127 | (LSB) | * |-----+-----------------------------------------------------------------------| * | . | * | . | * | . | * |-----+-----------------------------------------------------------------------| * |n-63 | | * |- - -+--- Zone Descriptor [last] ---| * | n | | * +=============================================================================+ */ /* Get number of zones in result */ buf = (uint8_t *)cmd.out_buf; nr_zones = zbc_sg_cmd_get_int32(buf) / ZBC_ZONE_DESCRIPTOR_LENGTH; /* read # of zones and then get all the zone info */ if (phase == 0) { c->nr_zones = nr_zones; c->nr_conventional = 0; zbc_sg_cmd_destroy(&cmd); phase++; goto next; } if (nr_zones > c->nr_zones - idx) nr_zones = c->nr_zones - idx; buf_nz = (cmd.out_bufsz - ZBC_ZONE_DESCRIPTOR_OFFSET) / ZBC_ZONE_DESCRIPTOR_LENGTH; if (nr_zones > buf_nz) nr_zones = buf_nz; if (!nr_zones) { ERR_MSG("No more zones\n"); goto out; } /* Allocate zone array */ zones = (zbc_zone_t *)malloc(sizeof(zbc_zone_t) * nr_zones); if (!zones) { ERR_MSG("No memory\n"); goto out; } memset(zones, 0, sizeof(zbc_zone_t) * nr_zones); /* Get zone descriptors: * +=============================================================================+ * | Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | * |Byte | | | | | | | | | * |=====+=======================================================================| * | 0 | Reserved | Zone type | * |-----+-----------------------------------------------------------------------| * | 1 | Zone condition | Reserved |non-seq | Reset | * |-----+-----------------------------------------------------------------------| * | 2 | | * |- - -+--- Reserved ---| * | 7 | | * |-----+-----------------------------------------------------------------------| * | 8 | (MSB) | * |- - -+--- Zone Length ---| * | 15 | (LSB) | * |-----+-----------------------------------------------------------------------| * | 16 | (MSB) | * |- - -+--- Zone Start LBA ---| * | 23 | (LSB) | * |-----+-----------------------------------------------------------------------| * | 24 | (MSB) | * |- - -+--- Write Pointer LBA ---| * | 31 | (LSB) | * |-----+-----------------------------------------------------------------------| * | 32 | | * |- - -+--- Reserved ---| * | 63 | | * +=============================================================================+ */ buf += ZBC_ZONE_DESCRIPTOR_OFFSET; for(i = 0; i < nr_zones; i++) { zones[i].zbz_type = buf[0] & 0x0f; zones[i].zbz_condition = (buf[1] >> 4) & 0x0f; zones[i].zbz_length = zbc_sg_cmd_get_int64(&buf[8]); zones[i].zbz_start = zbc_sg_cmd_get_int64(&buf[16]); zones[i].zbz_write_pointer = zbc_sg_cmd_get_int64(&buf[24]); zones[i].zbz_flags = buf[1] & 0x03; buf += ZBC_ZONE_DESCRIPTOR_LENGTH; } for (i = 0; i < nr_zones; i++) { z = &zones[i]; if ( zbc_zone_conventional(z) ) { c->nr_conventional++; DBG(1, "Zone %05d: type 0x%x (%s), cond 0x%x (%s), LBA %llu, %llu sectors, wp N/A\n", i + idx, zbc_zone_type(z), zbc_zone_type_str(zbc_zone_type(z)), zbc_zone_condition(z), zbc_zone_condition_str(zbc_zone_condition(z)), zbc_zone_start_lba(z), zbc_zone_length(z)); } else { DBG(1, "Zone %05d: type 0x%x (%s), cond 0x%x (%s), need_reset %d, non_seq %d, LBA %llu, %llu sectors, wp %llu\n", i + idx, zbc_zone_type(z), zbc_zone_type_str(zbc_zone_type(z)), zbc_zone_condition(z), zbc_zone_condition_str(zbc_zone_condition(z)), zbc_zone_need_reset(z), zbc_zone_non_seq(z), zbc_zone_start_lba(z), zbc_zone_length(z), zbc_zone_wp_lba(z)); } } idx += nr_zones; next_lba = zones[nr_zones - 1].zbz_start + zones[nr_zones - 1].zbz_length; c->zone_sectors = zones[nr_zones - 1].zbz_length; phase++; zbc_sg_cmd_destroy(&cmd); free(zones); goto next; out: zbc_sg_cmd_destroy(&cmd); return ret; }