/* $NetBSD: scsictl.c,v 1.23 2004/01/05 23:23:33 jmmv 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 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 #ifndef lint __RCSID("$NetBSD: scsictl.c,v 1.23 2004/01/05 23:23:33 jmmv Exp $"); #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "extern.h" struct command { const char *cmd_name; const char *arg_names; void (*cmd_func) __P((int, char *[])); }; int main __P((int, char *[])); void usage __P((void)); int fd; /* file descriptor for device */ const char *dvname; /* device name */ char dvname_store[MAXPATHLEN]; /* for opendisk(3) */ const char *cmdname; /* command user issued */ const char *argnames; /* helpstring: expected arguments */ struct scsi_addr dvaddr; /* SCSI device's address */ void device_format __P((int, char *[])); void device_identify __P((int, char *[])); void device_reassign __P((int, char *[])); void device_release __P((int, char *[])); void device_reserve __P((int, char *[])); void device_reset __P((int, char *[])); void device_debug __P((int, char *[])); void device_prevent __P((int, char *[])); void device_allow __P((int, char *[])); void device_start __P((int, char *[])); void device_stop __P((int, char *[])); void device_tur __P((int, char *[])); void device_getcache __P((int, char *[])); void device_setcache __P((int, char *[])); void device_flushcache __P((int, char *[])); struct command device_commands[] = { { "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 }, { NULL, NULL, NULL }, }; void bus_reset __P((int, char *[])); void bus_scan __P((int, char *[])); void bus_detach __P((int, char *[])); struct command bus_commands[] = { { "reset", "", bus_reset }, { "scan", "target lun", bus_scan }, { "detach", "target lun", bus_detach }, { NULL, NULL, NULL }, }; int main(argc, argv) 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); argnames = commands[i].arg_names; (*commands[i].cmd_func)(argc, argv); exit(0); } void usage() { 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_format: * * Format a direct access device. */ void device_format(argc, argv) 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 scsipi_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 scsipi_mode_header header; struct scsi_blk_desc blk_desc; struct page_disk_format format_page; } mode_page; struct { struct scsipi_mode_header header; struct scsi_blk_desc 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 = sense.flags & SSD_KEY) == 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: %hd\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_blk_desc); /* * 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, 0); /* * Poll device for completion of Format */ if (immediate) { i = 0; printf("formatting."); fflush(stdout); do { scsireq_t req; struct scsipi_test_unit_ready tcmd; memset(&tcmd, 0, sizeof(cmd)); tcmd.opcode = 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, SENSEBUFLEN); if (sense.sense_key_spec_1 == SSD_SCS_VALID) { j = (sense.sense_key_spec_2 << 8) | (sense.sense_key_spec_3); if (j >= complete[i]) { printf(".%d0%%.", ++i); fflush(stdout); } } sleep(10); } while ((sense.flags & SSD_KEY) == SKEY_NOT_READY); printf(".100%%..done.\n"); } return; } /* * device_identify: * * Display the identity of the device, including it's SCSI bus, * target, lun, and it's vendor/product/revision information. */ void device_identify(argc, argv) 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. */ void device_reassign(argc, argv) 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 drevice */ #ifndef SCSI_RELEASE #define SCSI_RELEASE 0x17 #endif void device_release(argc, argv) int argc; char *argv[]; { struct scsipi_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 drevice */ #ifndef SCSI_RESERVE #define SCSI_RESERVE 0x16 #endif void device_reserve(argc, argv) int argc; char *argv[]; { struct scsipi_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. */ void device_reset(argc, argv) 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. */ void device_debug(argc, argv) 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. */ void device_getcache(argc, argv) int argc; char *argv[]; { struct { struct scsipi_mode_header header; struct scsi_blk_desc 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. */ void device_setcache(argc, argv) int argc; char *argv[]; { struct { struct scsipi_mode_header header; struct scsi_blk_desc blk_desc; struct page_caching caching_params; } data; int dlen; u_int8_t flags, byte2; if (argc > 2 || argc == 0) usage(); 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 drevice */ #ifndef SCSI_FLUSHCACHE #define SCSI_FLUSHCACHE 0x35 #endif void device_flushcache(argc, argv) int argc; char *argv[]; { struct scsipi_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_prevent: * * Issue a prevent to a SCSI device. */ void device_prevent(argc, argv) int argc; char *argv[]; { struct scsipi_prevent cmd; /* No arguments. */ if (argc != 0) usage(); memset(&cmd, 0, sizeof(cmd)); cmd.opcode = PREVENT_ALLOW; cmd.how = PR_PREVENT; scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0); return; } /* * device_allow: * * Issue a stop to a SCSI device. */ void device_allow(argc, argv) int argc; char *argv[]; { struct scsipi_prevent cmd; /* No arguments. */ if (argc != 0) usage(); memset(&cmd, 0, sizeof(cmd)); cmd.opcode = PREVENT_ALLOW; cmd.how = PR_ALLOW; scsi_command(fd, &cmd, sizeof(cmd), NULL, 0, 10000, 0); return; } /* * device_start: * * Issue a start to a SCSI device. */ void device_start(argc, argv) 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, 10000, 0); return; } /* * device_stop: * * Issue a stop to a SCSI device. */ void device_stop(argc, argv) 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, 10000, 0); return; } /* * device_tur: * * Issue a TEST UNIT READY to a SCSI drevice */ void device_tur(argc, argv) int argc; char *argv[]; { struct scsipi_test_unit_ready cmd; /* No arguments. */ if (argc != 0) usage(); memset(&cmd, 0, sizeof(cmd)); cmd.opcode = 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. */ void bus_reset(argc, argv) 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. */ void bus_scan(argc, argv) 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. */ void bus_detach(argc, argv) 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; }