#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: scsictl.c,v 1.44 2025/12/26 09:50:08 nia Exp $");
#endif
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/scsiio.h>
#include <err.h>
#include <errno.h>
#include <endian.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <util.h>
#include <dev/scsipi/scsi_spc.h>
#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsi_disk.h>
#include <dev/scsipi/scsipiconf.h>
#include "extern.h"
struct command {
const char *cmd_name;
const char *arg_names;
void (*cmd_func)(int, char *[]);
};
__dead static void usage(void);
static int fd;
const char *dvname;
static char dvname_store[MAXPATHLEN];
static const char *cmdname;
static struct scsi_addr dvaddr;
static void device_defects(int, char *[]);
static void device_format(int, char *[]);
static void device_identify(int, char *[]);
static void device_reassign(int, char *[]);
static void device_release(int, char *[]);
static void device_reserve(int, char *[]);
static void device_reset(int, char *[]);
static void device_debug(int, char *[]);
static void device_prevent(int, char *[]);
static void device_allow(int, char *[]);
static void device_start(int, char *[]);
static void device_stop(int, char *[]);
static void device_tur(int, char *[]);
static void device_getcache(int, char *[]);
static void device_setcache(int, char *[]);
static void device_flushcache(int, char *[]);
static void device_setspeed(int, char *[]);
static void device_getrealloc(int, char *[]);
static void device_setrealloc(int, char *[]);
static void device_reportluns(int, char *[]);
static struct command device_commands[] = {
{ "defects", "[primary] [grown] [block|byte|physical]",
device_defects },
{ "format", "[blocksize [immediate]]", device_format },
{ "identify", "[vpd]", device_identify },
{ "reassign", "blkno [blkno [...]]", device_reassign },
{ "release", "", device_release },
{ "reserve", "", device_reserve },
{ "reset", "", device_reset },
{ "debug", "level", device_debug },
{ "prevent", "", device_prevent },
{ "allow", "", device_allow },
{ "start", "", device_start },
{ "stop", "", device_stop },
{ "tur", "", device_tur },
{ "getcache", "", device_getcache },
{ "setcache", "none|r|w|rw [save]", device_setcache },
{ "flushcache", "", device_flushcache },
{ "setspeed", "[speed]", device_setspeed },
{ "getrealloc", "", device_getrealloc },
{ "setrealloc", "none|r|w|rw [save]", device_setrealloc },
{ "reportluns", "normal|wellknown|all|#", device_reportluns },
{ NULL, NULL, NULL },
};
static void bus_reset(int, char *[]);
static void bus_scan(int, char *[]);
static void bus_detach(int, char *[]);
static struct command bus_commands[] = {
{ "reset", "", bus_reset },
{ "scan", "target lun", bus_scan },
{ "detach", "target lun", bus_detach },
{ NULL, NULL, NULL },
};
int
main(int argc, char *argv[])
{
struct command *commands;
int i;
if (argc < 3)
usage();
dvname = argv[1];
cmdname = argv[2];
argv += 3;
argc -= 3;
fd = opendisk(dvname, O_RDWR, dvname_store, sizeof(dvname_store), 0);
if (fd == -1) {
if (errno == ENOENT) {
fd = opendisk(dvname, O_RDWR, dvname_store,
sizeof(dvname_store), 1);
if (fd == -1)
err(1, "%s", dvname);
} else
err(1, "%s", dvname);
}
dvname = dvname_store;
if (ioctl(fd, SCIOCIDENTIFY, &dvaddr) < 0)
commands = bus_commands;
else
commands = device_commands;
for (i = 0; commands[i].cmd_name != NULL; i++)
if (strcmp(cmdname, commands[i].cmd_name) == 0)
break;
if (commands[i].cmd_name == NULL)
errx(1, "unknown %s command: %s",
commands == bus_commands ? "bus" : "device", cmdname);
(*commands[i].cmd_func)(argc, argv);
exit(0);
}
static void
usage(void)
{
int i;
fprintf(stderr, "usage: %s device command [arg [...]]\n",
getprogname());
fprintf(stderr, " Commands pertaining to scsi devices:\n");
for (i = 0; device_commands[i].cmd_name != NULL; i++)
fprintf(stderr, "\t%s %s\n", device_commands[i].cmd_name,
device_commands[i].arg_names);
fprintf(stderr, " Commands pertaining to scsi busses:\n");
for (i = 0; bus_commands[i].cmd_name != NULL; i++)
fprintf(stderr, "\t%s %s\n", bus_commands[i].cmd_name,
bus_commands[i].arg_names);
fprintf(stderr, " Use `any' or `all' to wildcard target or lun\n");
exit(1);
}
static void print_bf_dd(union scsi_defect_descriptor *);
static void print_bfif_dd(union scsi_defect_descriptor *);
static void print_psf_dd(union scsi_defect_descriptor *);
static void
device_defects(int argc, char *argv[])
{
struct scsi_read_defect_data cmd;
struct scsi_read_defect_data_data *data;
size_t dlen;
int i, dlfmt = -1;
int defects;
char msg[256];
void (*pfunc)(union scsi_defect_descriptor *);
#define RDD_P_G_MASK 0x18
#define RDD_DLF_MASK 0x7
dlen = USHRT_MAX;
data = malloc(dlen);
if (data == NULL)
errx(1, "unable to allocate defect list");
memset(data, 0, dlen);
memset(&cmd, 0, sizeof(cmd));
defects = 0;
pfunc = NULL;
for (i = 0; i < argc; i++) {
if (strncmp("primary", argv[i], 7) == 0) {
cmd.flags |= RDD_PRIMARY;
continue;
}
if (strncmp("grown", argv[i], 5) == 0) {
cmd.flags |= RDD_GROWN;
continue;
}
break;
}
if ((cmd.flags & (RDD_PRIMARY|RDD_GROWN)) == 0)
cmd.flags |= (RDD_PRIMARY|RDD_GROWN);
if (i < argc) {
if (strncmp("block", argv[i], 5) == 0) {
cmd.flags |= RDD_BF;
dlfmt = RDD_BF;
}
else if (strncmp("byte", argv[i], 4) == 0) {
cmd.flags |= RDD_BFIF;
dlfmt = RDD_BFIF;
}
else if (strncmp("physical", argv[i], 4) == 0) {
cmd.flags |= RDD_PSF;
dlfmt = RDD_PSF;
}
else {
usage();
}
}
if (dlfmt < 0) {
cmd.flags |= RDD_PSF;
dlfmt = RDD_PSF;
}
cmd.opcode = SCSI_READ_DEFECT_DATA;
_lto2b(dlen, &cmd.length[0]);
scsi_command(fd, &cmd, sizeof(cmd), data, dlen, 30000, SCCMD_READ);
msg[0] = '\0';
if ((data->flags & RDD_DLF_MASK) != dlfmt) {
strcpy(msg, "\n\tnotice:"
"requested defect list format not supported by device\n\n");
dlfmt = (data->flags & RDD_DLF_MASK);
}
if (data->flags & RDD_PRIMARY)
strcat(msg, "primary");
if (data->flags & RDD_GROWN) {
if (data->flags & RDD_PRIMARY)
strcat(msg, " and ");
strcat(msg, "grown");
}
strcat(msg, " defects");
if ((data->flags & RDD_P_G_MASK) == 0)
strcat(msg, ": none reported\n");
printf("%s: scsibus%d target %d lun %d %s",
dvname, dvaddr.addr.scsi.scbus, dvaddr.addr.scsi.target,
dvaddr.addr.scsi.lun, msg);
if ((data->flags & RDD_P_G_MASK) == 0)
return;
switch (dlfmt) {
case RDD_BF:
defects = _2btol(data->length) /
sizeof(struct scsi_defect_descriptor_bf);
pfunc = print_bf_dd;
strcpy(msg, "block address\n"
"-------------\n");
break;
case RDD_BFIF:
defects = _2btol(data->length) /
sizeof(struct scsi_defect_descriptor_bfif);
pfunc = print_bfif_dd;
strcpy(msg, " bytes from\n"
"cylinder head index\n"
"-------- ---- ----------\n");
break;
case RDD_PSF:
defects = _2btol(data->length) /
sizeof(struct scsi_defect_descriptor_psf);
pfunc = print_psf_dd;
strcpy(msg, "cylinder head sector\n"
"-------- ---- ----------\n");
break;
}
if (defects == 0) {
printf(": none\n");
return;
}
printf(": %d\n", defects);
printf("%s", msg);
for (i = 0 ; i < defects; i++) {
pfunc(&data->defect_descriptor[i]);
}
free(data);
return;
}
static void
print_bf_dd(union scsi_defect_descriptor *dd)
{
u_int32_t block;
block = _4btol(dd->bf.block_address);
printf("%13u\n", block);
}
#define DEFECTIVE_TRACK 0xffffffff
static void
print_bfif_dd(union scsi_defect_descriptor *dd)
{
u_int32_t cylinder;
u_int32_t head;
u_int32_t bytes_from_index;
cylinder = _3btol(dd->bfif.cylinder);
head = dd->bfif.head;
bytes_from_index = _4btol(dd->bfif.bytes_from_index);
printf("%8u %4u ", cylinder, head);
if (bytes_from_index == DEFECTIVE_TRACK)
printf("entire track defective\n");
else
printf("%10u\n", bytes_from_index);
}
static void
print_psf_dd(union scsi_defect_descriptor *dd)
{
u_int32_t cylinder;
u_int32_t head;
u_int32_t sector;
cylinder = _3btol(dd->psf.cylinder);
head = dd->psf.head;
sector = _4btol(dd->psf.sector);
printf("%8u %4u ", cylinder, head);
if (sector == DEFECTIVE_TRACK)
printf("entire track defective\n");
else
printf("%10u\n", sector);
}
static void
device_format(int argc, char *argv[])
{
u_int32_t blksize;
int i, j, immediate;
#define PC (65536/10)
static int complete[] = {
PC*1, PC*2, PC*3, PC*4, PC*5, PC*6, PC*7, PC*8, PC*9, 65536
};
char *cp, buffer[64];
struct scsi_sense_data sense;
struct scsi_format_unit cmd;
struct {
struct scsi_format_unit_defect_list_header header;
} dfl;
struct {
struct scsi_mode_parameter_header_6 header;
struct scsi_general_block_descriptor blk_desc;
struct page_disk_format format_page;
} mode_page;
struct {
struct scsi_mode_parameter_header_6 header;
struct scsi_general_block_descriptor blk_desc;
} data_select;
if (argc > 2)
usage();
for (i = 0; i < 8; i++) {
scsi_request_sense(fd, &sense, sizeof (sense));
if ((j = SSD_SENSE_KEY(sense.flags)) == SKEY_NO_SENSE)
break;
}
if (j != SKEY_NO_SENSE) {
cp = scsi_decode_sense((const unsigned char *) &sense, 2,
buffer, sizeof (buffer));
errx(1, "failed to clean Unit Attention: %s", cp);
}
scsi_mode_sense(fd, 0x03, 0x00, &mode_page, sizeof(mode_page));
j = (mode_page.format_page.bytes_s[0] << 8) |
(mode_page.format_page.bytes_s[1]);
if (j != DEV_BSIZE)
printf("current disk sector size: %d\n", j);
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_FORMAT_UNIT;
memcpy(cmd.interleave, mode_page.format_page.interleave,
sizeof(cmd.interleave));
if (argc > 0) {
blksize = strtoul(argv[0], &cp, 10);
if (*cp != '\0')
errx(1, "invalid block size: %s", argv[0]);
memset(&data_select, 0, sizeof(data_select));
data_select.header.blk_desc_len =
sizeof(struct scsi_general_block_descriptor);
_lto4b(blksize, &data_select.blk_desc.reserved);
scsi_mode_select(fd, 0, &data_select, sizeof(data_select));
cmd.flags = SFU_CMPLST | SFU_FMTDATA;
}
memset(&dfl, 0, sizeof(dfl));
if (argc > 1 && strncmp(argv[1], "imm", 3) == 0) {
dfl.header.flags = DLH_IMMED;
immediate = 1;
} else {
immediate = 0;
}
scsi_command(fd, &cmd, sizeof(cmd), &dfl, sizeof(dfl),
8 * 60 * 60 * 1000, SCCMD_WRITE);
if (immediate) {
i = 0;
printf("formatting.");
fflush(stdout);
do {
scsireq_t req;
struct scsi_test_unit_ready tcmd;
memset(&tcmd, 0, sizeof(tcmd));
tcmd.opcode = SCSI_TEST_UNIT_READY;
memset(&req, 0, sizeof(req));
memcpy(req.cmd, &tcmd, 6);
req.cmdlen = 6;
req.timeout = 10000;
req.senselen = SENSEBUFLEN;
if (ioctl(fd, SCIOCCOMMAND, &req) == -1) {
err(1, "SCIOCCOMMAND");
}
if (req.retsts == SCCMD_OK) {
break;
} else if (req.retsts == SCCMD_TIMEOUT) {
fprintf(stderr, "%s: SCSI command timed out",
dvname);
break;
} else if (req.retsts == SCCMD_BUSY) {
fprintf(stderr, "%s: device is busy",
dvname);
break;
} else if (req.retsts != SCCMD_SENSE) {
fprintf(stderr,
"%s: device had unknown status %x", dvname,
req.retsts);
break;
}
memcpy(&sense, req.sense, sizeof(sense));
if (sense.sks.sks_bytes[0] & SSD_SKSV) {
j = (sense.sks.sks_bytes[1] << 8) |
(sense.sks.sks_bytes[2]);
if (j >= complete[i]) {
printf(".%d0%%.", ++i);
fflush(stdout);
}
}
sleep(10);
} while (SSD_SENSE_KEY(sense.flags) == SKEY_NOT_READY);
printf(".100%%..done.\n");
}
return;
}
static void
print_designator(const char *pre, struct scsipi_inquiry_evpd_device_id *did)
{
char buf[252 * 4 + 1];
unsigned assoc, proto, code, type;
static const char *typestr[] = {
"vendor",
"t10",
"eui64",
"naa",
"target port",
"port group",
"lun group",
"md5",
"scsi",
"res9",
"res10",
"res11",
"res12",
"res13",
"res14",
"res15"
};
static const char *assocstr[] = {
"lun",
"port",
"target",
"reserved"
};
static const char *protostr[] = {
"fibre channel",
"obsolete",
"ssa",
"ieee1394",
"rdma",
"iSCSI",
"SAS"
};
const unsigned maxproto = __arraycount(protostr) - 1;
const unsigned isbinary =
__SHIFTOUT(SINQ_DEVICE_ID_CODESET_BINARY, SINQ_DEVICE_ID_CODESET);
unsigned k;
assoc = __SHIFTOUT(did->flags, SINQ_DEVICE_ID_ASSOCIATION);
proto = __SHIFTOUT(did->pc, SINQ_DEVICE_ID_PROTOCOL);
code = __SHIFTOUT(did->pc, SINQ_DEVICE_ID_CODESET);
type = __SHIFTOUT(did->flags, SINQ_DEVICE_ID_TYPE);
printf("%s%s", pre, assocstr[assoc]);
if (did->flags & SINQ_DEVICE_ID_PIV) {
if (proto > maxproto)
printf(" proto%u", proto);
else
printf(" %s", protostr[proto]);
}
printf(" %s: ", typestr[type]);
if (code == isbinary) {
for (k = 0; k < did->designator_length; k++) {
printf("%02x", did->designator[k]);
}
printf("\n");
} else {
scsi_strvis(buf, sizeof(buf), (char *)did->designator,
did->designator_length);
printf("%s\n", buf);
}
}
static void
device_identify(int argc, char *argv[])
{
struct scsipi_inquiry_data inqbuf;
struct {
struct scsipi_inquiry_evpd_header h;
uint8_t d[255 - sizeof(struct scsipi_inquiry_evpd_header)];
} evpdbuf;
struct scsipi_inquiry cmd;
unsigned len, rlen;
struct scsipi_inquiry_evpd_serial *ser;
struct scsipi_inquiry_evpd_device_id *did;
int has_serial;
int has_device_id;
bool getvpd = false;
int i;
char vendor[(sizeof(inqbuf.vendor) * 4) + 1],
product[(sizeof(inqbuf.product) * 4) + 1],
revision[(sizeof(inqbuf.revision) * 4) + 1],
ident[252 * 4 + 1];
for (i = 0; i < argc; i++) {
if (strncmp("vpd", argv[i], 3) == 0) {
getvpd = true;
continue;
}
usage();
}
memset(&cmd, 0, sizeof(cmd));
memset(&inqbuf, 0, sizeof(inqbuf));
cmd.opcode = INQUIRY;
cmd.length = sizeof(inqbuf);
scsi_command(fd, &cmd, sizeof(cmd), &inqbuf, sizeof(inqbuf),
10000, SCCMD_READ);
scsi_strvis(vendor, sizeof(vendor), inqbuf.vendor,
sizeof(inqbuf.vendor));
scsi_strvis(product, sizeof(product), inqbuf.product,
sizeof(inqbuf.product));
scsi_strvis(revision, sizeof(revision), inqbuf.revision,
sizeof(inqbuf.revision));
printf("%s: scsibus%d target %d lun %d <%s, %s, %s>\n",
dvname, dvaddr.addr.scsi.scbus, dvaddr.addr.scsi.target,
dvaddr.addr.scsi.lun, vendor, product, revision);
if (!getvpd)
return;
cmd.byte2 |= SINQ_EVPD;
cmd.pagecode = SINQ_VPD_PAGES;
scsi_command(fd, &cmd, sizeof(cmd), &evpdbuf, sizeof(evpdbuf),
10000, SCCMD_READ);
len = be16dec(evpdbuf.h.length);
if (len > sizeof(evpdbuf.d))
len = 0;
has_serial = memchr(evpdbuf.d, SINQ_VPD_SERIAL, len) != NULL;
has_device_id = memchr(evpdbuf.d, SINQ_VPD_DEVICE_ID, len) != NULL;
if (has_serial) {
cmd.byte2 |= SINQ_EVPD;
cmd.pagecode = SINQ_VPD_SERIAL;
scsi_command(fd, &cmd, sizeof(cmd), &evpdbuf, sizeof(evpdbuf),
10000, SCCMD_READ);
len = be16dec(evpdbuf.h.length);
if (len > sizeof(evpdbuf.d))
len = 0;
ser = (struct scsipi_inquiry_evpd_serial *)&evpdbuf.d;
scsi_strvis(ident, sizeof(ident), (char *)ser->serial_number,
len);
printf("VPD Serial:\n");
printf("\t%s\n", ident);
}
if (has_device_id) {
cmd.byte2 |= SINQ_EVPD;
cmd.pagecode = SINQ_VPD_DEVICE_ID;
scsi_command(fd, &cmd, sizeof(cmd), &evpdbuf, sizeof(evpdbuf),
10000, SCCMD_READ);
len = be16dec(evpdbuf.h.length);
if (len > sizeof(evpdbuf.d))
len = 0;
printf("VPD Device IDs:\n");
for (unsigned off = 0; off < len - sizeof(*did); off += rlen) {
void *p = &evpdbuf.d[off];
did = (struct scsipi_inquiry_evpd_device_id *)p;
rlen = sizeof(*did) + did->designator_length - 1;
if (off + rlen > len)
break;
print_designator("\t", did);
}
}
return;
}
static void
device_reassign(int argc, char *argv[])
{
struct scsi_reassign_blocks cmd;
struct scsi_reassign_blocks_data *data;
size_t dlen;
u_int32_t blkno;
int i;
char *cp;
if (argc < 1)
usage();
dlen = sizeof(struct scsi_reassign_blocks_data) + ((argc - 1) * 4);
data = malloc(dlen);
if (data == NULL)
errx(1, "unable to allocate defect descriptor");
memset(data, 0, dlen);
cmd.opcode = SCSI_REASSIGN_BLOCKS;
cmd.byte2 = 0;
cmd.unused[0] = 0;
cmd.unused[1] = 0;
cmd.unused[2] = 0;
cmd.control = 0;
_lto2b(argc * 4, data->length);
for (i = 0; i < argc; i++) {
blkno = strtoul(argv[i], &cp, 10);
if (*cp != '\0')
errx(1, "invalid block number: %s", argv[i]);
_lto4b(blkno, data->defect_descriptor[i].dlbaddr);
}
scsi_command(fd, &cmd, sizeof(cmd), data, dlen, 30000, SCCMD_WRITE);
free(data);
return;
}
#ifndef SCSI_RELEASE
#define SCSI_RELEASE 0x17
#endif
static void
device_release(int argc, char *argv[])
{
struct scsi_test_unit_ready cmd;
if (argc != 0)
usage();
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_RELEASE;
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0);
return;
}
#ifndef SCSI_RESERVE
#define SCSI_RESERVE 0x16
#endif
static void
device_reserve(int argc, char *argv[])
{
struct scsi_test_unit_ready cmd;
if (argc != 0)
usage();
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_RESERVE;
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0);
return;
}
static void
device_reset(int argc, char *argv[])
{
if (argc != 0)
usage();
if (ioctl(fd, SCIOCRESET, NULL) != 0)
err(1, "SCIOCRESET");
return;
}
static void
device_debug(int argc, char *argv[])
{
int lvl;
if (argc < 1)
usage();
lvl = atoi(argv[0]);
if (ioctl(fd, SCIOCDEBUG, &lvl) != 0)
err(1, "SCIOCDEBUG");
return;
}
static void
device_getcache(int argc, char *argv[])
{
struct {
struct scsi_mode_parameter_header_6 header;
struct scsi_general_block_descriptor blk_desc;
struct page_caching caching_params;
} data;
if (argc != 0)
usage();
scsi_mode_sense(fd, 0x08, 0x00, &data, sizeof(data));
if ((data.caching_params.flags & (CACHING_RCD|CACHING_WCE)) ==
CACHING_RCD)
printf("%s: no caches enabled\n", dvname);
else {
printf("%s: read cache %senabled\n", dvname,
(data.caching_params.flags & CACHING_RCD) ? "not " : "");
printf("%s: write-back cache %senabled\n", dvname,
(data.caching_params.flags & CACHING_WCE) ? "" : "not ");
}
printf("%s: caching parameters are %ssavable\n", dvname,
(data.caching_params.pg_code & PGCODE_PS) ? "" : "not ");
}
static void
device_setcache(int argc, char *argv[])
{
struct {
struct scsi_mode_parameter_header_6 header;
struct scsi_general_block_descriptor blk_desc;
struct page_caching caching_params;
} data;
int dlen;
u_int8_t flags, byte2;
if (argc > 2 || argc == 0)
usage();
flags = 0;
byte2 = 0;
if (strcmp(argv[0], "none") == 0)
flags = CACHING_RCD;
else if (strcmp(argv[0], "r") == 0)
flags = 0;
else if (strcmp(argv[0], "w") == 0)
flags = CACHING_RCD|CACHING_WCE;
else if (strcmp(argv[0], "rw") == 0)
flags = CACHING_WCE;
else
usage();
if (argc == 2) {
if (strcmp(argv[1], "save") == 0)
byte2 = SMS_SP;
else
usage();
}
scsi_mode_sense(fd, 0x08, 0x00, &data, sizeof(data));
data.caching_params.pg_code &= PGCODE_MASK;
data.caching_params.flags =
(data.caching_params.flags & ~(CACHING_RCD|CACHING_WCE)) | flags;
data.caching_params.cache_segment_size[0] = 0;
data.caching_params.cache_segment_size[1] = 0;
data.header.data_length = 0;
dlen = sizeof(data.header) + sizeof(data.blk_desc) + 2 +
data.caching_params.pg_length;
scsi_mode_select(fd, byte2, &data, dlen);
}
#ifndef SCSI_FLUSHCACHE
#define SCSI_FLUSHCACHE 0x35
#endif
static void
device_flushcache(int argc, char *argv[])
{
struct scsi_test_unit_ready cmd;
if (argc != 0)
usage();
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_FLUSHCACHE;
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0);
return;
}
static void
device_setspeed(int argc, char *argv[])
{
u_char cmd[11];
u_char pd[28];
u_int32_t speed;
if (argc != 1)
usage();
speed = atoi(argv[0]) * 177;
memset(&pd, 0, sizeof(pd));
if (speed == 0)
pd[0] = 4;
pd[8] = 0xff;
pd[9] = 0xff;
pd[10] = 0xff;
pd[11] = 0xff;
pd[12] = pd[20] = (speed >> 24) & 0xff;
pd[13] = pd[21] = (speed >> 16) & 0xff;
pd[14] = pd[22] = (speed >> 8) & 0xff;
pd[15] = pd[23] = speed & 0xff;
pd[18] = pd[26] = 1000 >> 8;
pd[19] = pd[27] = 1000 & 0xff;
memset(&cmd, 0, sizeof(cmd));
cmd[0] = 0xb6;
cmd[10] = sizeof(pd);
scsi_command(fd, &cmd, sizeof(cmd), pd, sizeof(pd), 10000, SCCMD_WRITE);
return;
}
static void
device_reportluns(int argc, char *argv[])
{
struct scsi_report_luns cmd;
struct {
struct scsi_report_luns_header header;
struct scsi_report_luns_lun desc[1];
} *data;
u_int32_t dlen, len;
u_int64_t lun;
size_t count, idx;
unsigned long sel;
char *endp;
int i;
dlen = USHRT_MAX;
data = malloc(dlen);
if (data == NULL)
errx(1, "unable to allocate lun report");
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_REPORT_LUNS;
cmd.selectreport = SELECTREPORT_NORMAL;
for (i = 0; i < argc; i++) {
if (strcmp("normal", argv[i]) == 0) {
cmd.selectreport = SELECTREPORT_NORMAL;
continue;
}
if (strcmp("wellknown", argv[i]) == 0) {
cmd.selectreport = SELECTREPORT_WELLKNOWN;
continue;
}
if (strcmp("all", argv[i]) == 0) {
cmd.selectreport = SELECTREPORT_ALL;
continue;
}
sel = strtoul(argv[i], &endp, 0);
if (*endp != '\0' || sel > 255)
errx(1, "Unknown select report '%s'", argv[i]);
cmd.selectreport = sel;
}
_lto4b(dlen, &cmd.alloclen[0]);
cmd.control = 0x00;
scsi_command(fd, &cmd, sizeof(cmd), data, dlen, 30000, SCCMD_READ);
len = _4btol(data->header.length);
if (len > dlen) {
printf("%s: report truncated %" PRIu32 "to %" PRIu32 "\n",
dvname, len, dlen);
len = dlen;
}
count = len / sizeof(data->desc[0]);
for (idx = 0; idx < count; idx++) {
lun = _8btol(data->desc[idx].lun);
lun = (lun & 0xffff000000000000ull) >> 48
| (lun & 0x0000ffff00000000ull) >> 16
| (lun & 0x00000000ffff0000ull) << 16
| (lun & 0x000000000000ffffull) << 48;
printf("%s: lun %" PRIu64 "\n", dvname, lun);
}
free(data);
}
static void
device_getrealloc(int argc, char *argv[])
{
struct {
struct scsi_mode_parameter_header_6 header;
struct scsi_general_block_descriptor blk_desc;
struct page_err_recov err_recov_params;
} data;
u_int8_t flags;
if (argc != 0)
usage();
scsi_mode_sense(fd, 0x01, 0x00, &data, sizeof(data));
flags = data.err_recov_params.flags;
if ((flags & (ERR_RECOV_ARRE | ERR_RECOV_AWRE)) == 0)
printf("%s: no automatic reallocation enabled\n", dvname);
else {
printf("%s: automatic read reallocation %senabled\n", dvname,
(flags & ERR_RECOV_ARRE) ? "" : "not ");
printf("%s: automatic write reallocation %senabled\n", dvname,
(flags & ERR_RECOV_AWRE) ? "" : "not ");
}
printf("%s: error recovery parameters are %ssavable\n", dvname,
(data.err_recov_params.pg_code & PGCODE_PS) ? "" : "not ");
}
static void
device_setrealloc(int argc, char *argv[])
{
struct {
struct scsi_mode_parameter_header_6 header;
struct scsi_general_block_descriptor blk_desc;
struct page_err_recov err_recov_params;
} data;
int dlen;
u_int8_t flags, byte2;
if (argc > 2 || argc == 0)
usage();
flags = 0;
byte2 = 0;
if (strcmp(argv[0], "none") == 0)
flags = 0;
else if (strcmp(argv[0], "r") == 0)
flags = ERR_RECOV_ARRE;
else if (strcmp(argv[0], "w") == 0)
flags = ERR_RECOV_AWRE;
else if (strcmp(argv[0], "rw") == 0)
flags = ERR_RECOV_ARRE | ERR_RECOV_AWRE;
else
usage();
if (argc == 2) {
if (strcmp(argv[1], "save") == 0)
byte2 = SMS_SP;
else
usage();
}
scsi_mode_sense(fd, 0x01, 0x00, &data, sizeof(data));
data.err_recov_params.pg_code &= PGCODE_MASK;
data.err_recov_params.flags &= ~(ERR_RECOV_ARRE | ERR_RECOV_AWRE);
data.err_recov_params.flags |= flags;
data.header.data_length = 0;
dlen = sizeof(data.header) + sizeof(data.blk_desc) + 2 +
data.err_recov_params.pg_length;
scsi_mode_select(fd, byte2, &data, dlen);
}
static void
device_prevent(int argc, char *argv[])
{
struct scsi_prevent_allow_medium_removal cmd;
if (argc != 0)
usage();
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL;
cmd.how = SPAMR_PREVENT_DT;
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0);
return;
}
static void
device_allow(int argc, char *argv[])
{
struct scsi_prevent_allow_medium_removal cmd;
if (argc != 0)
usage();
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL;
cmd.how = SPAMR_ALLOW;
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0);
return;
}
static void
device_start(int argc, char *argv[])
{
struct scsipi_start_stop cmd;
if (argc != 0)
usage();
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = START_STOP;
cmd.how = SSS_START;
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 30000, 0);
return;
}
static void
device_stop(int argc, char *argv[])
{
struct scsipi_start_stop cmd;
if (argc != 0)
usage();
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = START_STOP;
cmd.how = SSS_STOP;
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 30000, 0);
return;
}
static void
device_tur(int argc, char *argv[])
{
struct scsi_test_unit_ready cmd;
if (argc != 0)
usage();
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_TEST_UNIT_READY;
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0);
return;
}
static void
bus_reset(int argc, char *argv[])
{
if (argc != 0)
usage();
if (ioctl(fd, SCBUSIORESET, NULL) != 0)
err(1, "SCBUSIORESET");
return;
}
static void
bus_scan(int argc, char *argv[])
{
struct scbusioscan_args args;
char *cp;
if (argc != 2)
usage();
if (strcmp(argv[0], "any") == 0 || strcmp(argv[0], "all") == 0)
args.sa_target = -1;
else {
args.sa_target = strtol(argv[0], &cp, 10);
if (*cp != '\0' || args.sa_target < 0)
errx(1, "invalid target: %s", argv[0]);
}
if (strcmp(argv[1], "any") == 0 || strcmp(argv[1], "all") == 0)
args.sa_lun = -1;
else {
args.sa_lun = strtol(argv[1], &cp, 10);
if (*cp != '\0' || args.sa_lun < 0)
errx(1, "invalid lun: %s", argv[1]);
}
if (ioctl(fd, SCBUSIOSCAN, &args) != 0)
err(1, "SCBUSIOSCAN");
return;
}
static void
bus_detach(int argc, char *argv[])
{
struct scbusiodetach_args args;
char *cp;
if (argc != 2)
usage();
if (strcmp(argv[0], "any") == 0 || strcmp(argv[0], "all") == 0)
args.sa_target = -1;
else {
args.sa_target = strtol(argv[0], &cp, 10);
if (*cp != '\0' || args.sa_target < 0)
errx(1, "invalid target: %s", argv[0]);
}
if (strcmp(argv[1], "any") == 0 || strcmp(argv[1], "all") == 0)
args.sa_lun = -1;
else {
args.sa_lun = strtol(argv[1], &cp, 10);
if (*cp != '\0' || args.sa_lun < 0)
errx(1, "invalid lun: %s", argv[1]);
}
if (ioctl(fd, SCBUSIODETACH, &args) != 0)
err(1, "SCBUSIODETACH");
return;
}