blob: 6ec2fc7260c60939005039bb8798fdc983e46498 [file] [log] [blame]
/*
* 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 <http://opensource.org/licenses/BSD-2-Clause>.
*
* 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 <f2fs_fs.h>
#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;
}