1373 lines
31 KiB
C
1373 lines
31 KiB
C
/* $NetBSD: scsictl.c,v 1.40 2022/08/21 12:44:16 mlelstv Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 1998, 2002 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
|
|
* NASA Ames Research Center.
|
|
*
|
|
* 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
|
|
*/
|
|
|
|
/*
|
|
* scsictl(8) - a program to manipulate SCSI devices and busses.
|
|
*/
|
|
#include <sys/cdefs.h>
|
|
|
|
#ifndef lint
|
|
__RCSID("$NetBSD: scsictl.c,v 1.40 2022/08/21 12:44:16 mlelstv Exp $");
|
|
#endif
|
|
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/scsiio.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.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; /* file descriptor for device */
|
|
const char *dvname; /* device name */
|
|
static char dvname_store[MAXPATHLEN]; /* for opendisk(3) */
|
|
static const char *cmdname; /* command user issued */
|
|
static struct scsi_addr dvaddr; /* SCSI device's address */
|
|
|
|
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", "", 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;
|
|
|
|
/* Must have at least: device command */
|
|
if (argc < 3)
|
|
usage();
|
|
|
|
/* Skip program name, get and skip device name and command. */
|
|
dvname = argv[1];
|
|
cmdname = argv[2];
|
|
argv += 3;
|
|
argc -= 3;
|
|
|
|
/*
|
|
* Open the device and determine if it's a scsibus or an actual
|
|
* device. Devices respond to the SCIOCIDENTIFY ioctl.
|
|
*/
|
|
fd = opendisk(dvname, O_RDWR, dvname_store, sizeof(dvname_store), 0);
|
|
if (fd == -1) {
|
|
if (errno == ENOENT) {
|
|
/*
|
|
* Device doesn't exist. Probably trying to open
|
|
* a device which doesn't use disk semantics for
|
|
* device name. Try again, specifying "cooked",
|
|
* which leaves off the "r" in front of the device's
|
|
* name.
|
|
*/
|
|
fd = opendisk(dvname, O_RDWR, dvname_store,
|
|
sizeof(dvname_store), 1);
|
|
if (fd == -1)
|
|
err(1, "%s", dvname);
|
|
} else
|
|
err(1, "%s", dvname);
|
|
}
|
|
|
|
/*
|
|
* Point the dvname at the actual device name that opendisk() opened.
|
|
*/
|
|
dvname = dvname_store;
|
|
|
|
if (ioctl(fd, SCIOCIDENTIFY, &dvaddr) < 0)
|
|
commands = bus_commands;
|
|
else
|
|
commands = device_commands;
|
|
|
|
/* Look up and call the command. */
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* DEVICE COMMANDS
|
|
*/
|
|
|
|
/*
|
|
* device_read_defect:
|
|
*
|
|
* Read primary and/or growth defect list in physical or block
|
|
* format from a direct access device.
|
|
*
|
|
* XXX Does not handle very large defect lists. Needs SCSI3 12
|
|
* byte READ DEFECT DATA command.
|
|
*/
|
|
|
|
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; /* XXX - this may not be enough room
|
|
* for all of the defects.
|
|
*/
|
|
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;
|
|
|
|
/* determine which defect list(s) to read. */
|
|
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;
|
|
}
|
|
|
|
/* no defect list sepecified, assume both. */
|
|
if ((cmd.flags & (RDD_PRIMARY|RDD_GROWN)) == 0)
|
|
cmd.flags |= (RDD_PRIMARY|RDD_GROWN);
|
|
|
|
/* list format option. */
|
|
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();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* no list format specified; since block format not
|
|
* recommended use physical sector format as default.
|
|
*/
|
|
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';
|
|
|
|
/* is the defect list in the format asked for? */
|
|
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);
|
|
|
|
/* device did not return either defect list. */
|
|
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;
|
|
}
|
|
|
|
/* device did not return any defects. */
|
|
if (defects == 0) {
|
|
printf(": none\n");
|
|
return;
|
|
}
|
|
|
|
printf(": %d\n", defects);
|
|
|
|
/* print heading. */
|
|
printf("%s", msg);
|
|
|
|
/* print defect list. */
|
|
for (i = 0 ; i < defects; i++) {
|
|
pfunc(&data->defect_descriptor[i]);
|
|
}
|
|
|
|
free(data);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* print_bf_dd:
|
|
*
|
|
* Print a block format defect descriptor.
|
|
*/
|
|
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
|
|
|
|
/*
|
|
* print_bfif_dd:
|
|
*
|
|
* Print a bytes from index format defect descriptor.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* print_psf_dd:
|
|
*
|
|
* Print a physical sector format defect descriptor.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* device_format:
|
|
*
|
|
* Format a direct access device.
|
|
*/
|
|
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;
|
|
/* optional initialization pattern */
|
|
/* optional defect list */
|
|
} 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;
|
|
|
|
|
|
/* Blocksize is an optional argument. */
|
|
if (argc > 2)
|
|
usage();
|
|
|
|
/*
|
|
* Loop doing Request Sense to clear any pending Unit Attention.
|
|
*
|
|
* Multiple conditions may exist on the drive which are returned
|
|
* in priority order.
|
|
*/
|
|
for (i = 0; i < 8; i++) {
|
|
scsi_request_sense(fd, &sense, sizeof (sense));
|
|
if ((j = SSD_SENSE_KEY(sense.flags)) == SKEY_NO_SENSE)
|
|
break;
|
|
}
|
|
/*
|
|
* Make sure we cleared any pending Unit Attention
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* Get the DISK FORMAT mode page. SCSI-2 recommends specifying the
|
|
* interleave read from this page in the FORMAT UNIT command.
|
|
*/
|
|
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));
|
|
|
|
/*
|
|
* The blocksize on the device is only changed if the user
|
|
* specified a new blocksize. If not specified the blocksize
|
|
* used for the device will be the Default value in the device.
|
|
* We don't specify the number of blocks since the format
|
|
* command will always reformat the entire drive. Also by
|
|
* not specifying a block count the drive will reset the
|
|
* block count to the maximum available after the format
|
|
* completes if the blocksize was changed in the format.
|
|
* Finally, the new disk geometry will not but updated on
|
|
* the drive in permanent storage until _AFTER_ the format
|
|
* completes successfully.
|
|
*/
|
|
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);
|
|
/*
|
|
* blklen in desc is 3 bytes with a leading reserved byte
|
|
*/
|
|
_lto4b(blksize, &data_select.blk_desc.reserved);
|
|
|
|
/*
|
|
* Issue Mode Select to modify the device blocksize to be
|
|
* used on the Format. The modified device geometry will
|
|
* be stored as Current and Saved Page 3 parameters when
|
|
* the Format completes.
|
|
*/
|
|
scsi_mode_select(fd, 0, &data_select, sizeof(data_select));
|
|
|
|
/*
|
|
* Since user specified a specific block size make sure it
|
|
* gets stored in the device when the format completes.
|
|
*
|
|
* Also scrub the defect list back to the manufacturers
|
|
* original.
|
|
*/
|
|
cmd.flags = SFU_CMPLST | SFU_FMTDATA;
|
|
}
|
|
|
|
memset(&dfl, 0, sizeof(dfl));
|
|
|
|
if (argc > 1 && strncmp(argv[1], "imm", 3) == 0) {
|
|
/*
|
|
* Signal target for an immediate return from Format.
|
|
*
|
|
* We'll poll for completion status.
|
|
*/
|
|
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);
|
|
|
|
/*
|
|
* Poll device for completion of Format
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* device_identify:
|
|
*
|
|
* Display the identity of the device, including its SCSI bus,
|
|
* target, lun, and its vendor/product/revision information.
|
|
*/
|
|
static void
|
|
device_identify(int argc, char *argv[])
|
|
{
|
|
struct scsipi_inquiry_data inqbuf;
|
|
struct scsipi_inquiry cmd;
|
|
|
|
/* x4 in case every character is escaped, +1 for NUL. */
|
|
char vendor[(sizeof(inqbuf.vendor) * 4) + 1],
|
|
product[(sizeof(inqbuf.product) * 4) + 1],
|
|
revision[(sizeof(inqbuf.revision) * 4) + 1];
|
|
|
|
/* No arguments. */
|
|
if (argc != 0)
|
|
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);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* device_reassign:
|
|
*
|
|
* Reassign bad blocks on a direct access device.
|
|
*/
|
|
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;
|
|
|
|
/* We get a list of block numbers. */
|
|
if (argc < 1)
|
|
usage();
|
|
|
|
/*
|
|
* Allocate the reassign blocks descriptor. The 4 comes from the
|
|
* size of the block address in the defect descriptor.
|
|
*/
|
|
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;
|
|
|
|
/* Defect descriptor length. */
|
|
_lto2b(argc * 4, data->length);
|
|
|
|
/* Build the defect descriptor list. */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* device_release:
|
|
*
|
|
* Issue a RELEASE command to a SCSI device.
|
|
*/
|
|
#ifndef SCSI_RELEASE
|
|
#define SCSI_RELEASE 0x17
|
|
#endif
|
|
static void
|
|
device_release(int argc, char *argv[])
|
|
{
|
|
struct scsi_test_unit_ready cmd; /* close enough */
|
|
|
|
/* No arguments. */
|
|
if (argc != 0)
|
|
usage();
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
|
|
cmd.opcode = SCSI_RELEASE;
|
|
|
|
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* device_reserve:
|
|
*
|
|
* Issue a RESERVE command to a SCSI device.
|
|
*/
|
|
#ifndef SCSI_RESERVE
|
|
#define SCSI_RESERVE 0x16
|
|
#endif
|
|
static void
|
|
device_reserve(int argc, char *argv[])
|
|
{
|
|
struct scsi_test_unit_ready cmd; /* close enough */
|
|
|
|
/* No arguments. */
|
|
if (argc != 0)
|
|
usage();
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
|
|
cmd.opcode = SCSI_RESERVE;
|
|
|
|
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* device_reset:
|
|
*
|
|
* Issue a reset to a SCSI device.
|
|
*/
|
|
static void
|
|
device_reset(int argc, char *argv[])
|
|
{
|
|
|
|
/* No arguments. */
|
|
if (argc != 0)
|
|
usage();
|
|
|
|
if (ioctl(fd, SCIOCRESET, NULL) != 0)
|
|
err(1, "SCIOCRESET");
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* device_debug:
|
|
*
|
|
* Set debug level to a SCSI device.
|
|
* scsipi will print anything iff SCSIPI_DEBUG set in config.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* device_getcache:
|
|
*
|
|
* Get the caching parameters for a SCSI disk.
|
|
*/
|
|
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;
|
|
|
|
/* No arguments. */
|
|
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 ");
|
|
}
|
|
|
|
/*
|
|
* device_setcache:
|
|
*
|
|
* Set cache enables for a SCSI disk.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* device_flushcache:
|
|
*
|
|
* Issue a FLUSH CACHE command to a SCSI device.
|
|
*/
|
|
#ifndef SCSI_FLUSHCACHE
|
|
#define SCSI_FLUSHCACHE 0x35
|
|
#endif
|
|
static void
|
|
device_flushcache(int argc, char *argv[])
|
|
{
|
|
struct scsi_test_unit_ready cmd; /* close enough */
|
|
|
|
/* No arguments. */
|
|
if (argc != 0)
|
|
usage();
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
|
|
cmd.opcode = SCSI_FLUSHCACHE;
|
|
|
|
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* device_setspeed:
|
|
*
|
|
* Set rotation speed to a CD/DVD drive.
|
|
*/
|
|
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; /* restore drive defaults */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* device_reportluns:
|
|
*
|
|
* Report the known LUNs to which the initiator can send commands
|
|
*/
|
|
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; /* good for > 8000 LUNs */
|
|
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;
|
|
|
|
/* determine which report to read. */
|
|
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) {
|
|
/* XXX reallocate and retry */
|
|
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);
|
|
|
|
/*
|
|
* swizzle bits so that LUNs 0..255 are
|
|
* mapped to numbers 0..255
|
|
*/
|
|
lun = (lun & 0xffff000000000000ull) >> 48
|
|
| (lun & 0x0000ffff00000000ull) >> 16
|
|
| (lun & 0x00000000ffff0000ull) << 16
|
|
| (lun & 0x000000000000ffffull) << 48;
|
|
|
|
printf("%s: lun %" PRIu64 "\n", dvname, lun);
|
|
}
|
|
|
|
free(data);
|
|
}
|
|
|
|
/*
|
|
* device_getrealloc:
|
|
*
|
|
* Get the automatic reallocation parameters for a SCSI disk.
|
|
*/
|
|
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;
|
|
|
|
/* No arguments. */
|
|
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 ");
|
|
}
|
|
|
|
/*
|
|
* device_setrealloc:
|
|
*
|
|
* Set the automatic reallocation parameters for a SCSI disk.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* device_prevent:
|
|
*
|
|
* Issue a prevent to a SCSI device.
|
|
*/
|
|
static void
|
|
device_prevent(int argc, char *argv[])
|
|
{
|
|
struct scsi_prevent_allow_medium_removal cmd;
|
|
|
|
/* No arguments. */
|
|
if (argc != 0)
|
|
usage();
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
|
|
cmd.opcode = SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL;
|
|
cmd.how = SPAMR_PREVENT_DT; /* XXX SMAMR_PREVENT_ALL? */
|
|
|
|
scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* device_allow:
|
|
*
|
|
* Issue a stop to a SCSI device.
|
|
*/
|
|
static void
|
|
device_allow(int argc, char *argv[])
|
|
{
|
|
struct scsi_prevent_allow_medium_removal cmd;
|
|
|
|
/* No arguments. */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* device_start:
|
|
*
|
|
* Issue a start to a SCSI device.
|
|
*/
|
|
static void
|
|
device_start(int argc, char *argv[])
|
|
{
|
|
struct scsipi_start_stop cmd;
|
|
|
|
/* No arguments. */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* device_stop:
|
|
*
|
|
* Issue a stop to a SCSI device.
|
|
*/
|
|
static void
|
|
device_stop(int argc, char *argv[])
|
|
{
|
|
struct scsipi_start_stop cmd;
|
|
|
|
/* No arguments. */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* device_tur:
|
|
*
|
|
* Issue a TEST UNIT READY to a SCSI device.
|
|
*/
|
|
static void
|
|
device_tur(int argc, char *argv[])
|
|
{
|
|
struct scsi_test_unit_ready cmd;
|
|
|
|
/* No arguments. */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* BUS COMMANDS
|
|
*/
|
|
|
|
/*
|
|
* bus_reset:
|
|
*
|
|
* Issue a reset to a SCSI bus.
|
|
*/
|
|
static void
|
|
bus_reset(int argc, char *argv[])
|
|
{
|
|
|
|
/* No arguments. */
|
|
if (argc != 0)
|
|
usage();
|
|
|
|
if (ioctl(fd, SCBUSIORESET, NULL) != 0)
|
|
err(1, "SCBUSIORESET");
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* bus_scan:
|
|
*
|
|
* Rescan a SCSI bus for new devices.
|
|
*/
|
|
static void
|
|
bus_scan(int argc, char *argv[])
|
|
{
|
|
struct scbusioscan_args args;
|
|
char *cp;
|
|
|
|
/* Must have two args: target lun */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* bus_detach:
|
|
*
|
|
* detach SCSI devices from a bus.
|
|
*/
|
|
static void
|
|
bus_detach(int argc, char *argv[])
|
|
{
|
|
struct scbusiodetach_args args;
|
|
char *cp;
|
|
|
|
/* Must have two args: target lun */
|
|
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;
|
|
}
|