Implement a new camcontrol function, 'camcontrol format'.

libcam/Makefile:	Add scsi_da.c to libcam for the new
			scsi_format_unit() function.

camcontrol.8:		Update the man page for the new format
			functionality, and take out the examples section
			describing how to do it with 'camcontrol cmd'.

camcontrol.c:		New format functionality.  Note that unlike the
			rest of the camcontrol subcommands, this one is
			interactive by default.  Because of the potential
			destructiveness of the format command, I thought
			it necessary to get confirmation from the user
			before spamming a disk.  You can disable the
			interactive behavior, and the status meter with
			command line arguments.

scsi_da.c:		Add the new scsi_format_unit() cdb building
			function and use #ifdef _KERNEL to make this file
			compile in both the kernel and userland.  The
			format unit function is currently only defined in
			the non-kernel case, because nothing in the kernel
			is using it.  If that changes, it should be
			un-ifdefed and compiled in both cases.

scsi_da.h:		New function declaration, CDB structure and format
			data structures.

Thanks to Nick Hibma for providing some valuable input on these changes.
This commit is contained in:
Kenneth D. Merry 2000-05-21 23:57:52 +00:00
parent 1c23847582
commit 96a93c63a8
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=60767
5 changed files with 508 additions and 32 deletions

View File

@ -3,7 +3,7 @@
MAINTAINER=ken@FreeBSD.ORG
LIB= cam
SRCS= camlib.c scsi_cmdparse.c scsi_all.c scsi_sa.c cam.c
SRCS= camlib.c scsi_cmdparse.c scsi_all.c scsi_da.c scsi_sa.c cam.c
INCS= camlib.h
MAN3= cam.3 cam_cdbparse.3

View File

@ -1,5 +1,5 @@
.\"
.\" Copyright (c) 1998, 1999 Kenneth D. Merry.
.\" Copyright (c) 1998, 1999, 2000 Kenneth D. Merry.
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
@ -128,6 +128,13 @@ negotiate
.Op Fl W Ar bus_width
.Op Fl v
.Nm camcontrol
format
.Op device id
.Op generic args
.Op Fl q
.Op Fl w
.Op Fl y
.Nm camcontrol
help
.Sh DESCRIPTION
.Nm camcontrol
@ -172,7 +179,7 @@ and
.Fl u
arguments will
.Em not
override a specified bus:target or bus:target:lun, howevever.
override a specified bus:target or bus:target:lun, however.
.Pp
Most of the
.Nm camcontrol
@ -333,7 +340,7 @@ function requires the
argument to specify the CDB. Other arguments are optional, depending on
the command type. The command and data specification syntax is documented
in
.Xr cam 3 .
.Xr cam_cdbparse 3 .
NOTE: If the CDB specified causes data to be transfered to or from the
SCSI device in question, you MUST specify either
.Fl i
@ -510,6 +517,70 @@ device until a command has been sent to the device. The
.Fl a
switch above will automatically send a Test Unit Ready to the device so
negotiation parameters will take effect.
.It format
Issue the
.Tn SCSI
FORMAT UNIT command to the named device.
.Pp
.Em WARNING! WARNING! WARNING!
.Pp
Low level formatting a disk will destroy ALL data on the disk. Use
extreme caution when issuing this command. Many users low-level format
disks that do not really need to be low-level formatted. There are
relatively few scenarios that call for low-level formatting a disk.
One reason for
low-level formatting a disk is to initialize the disk after changing
its physical sector size. Another reason for low-level formatting a disk
is to revive the disk if you are getting "medium format corrupted" errors
from the disk in response to read and write requests.
.Pp
Some disks take longer than others to format. Users should specify a
timeout long enough to allow the format to complete. The default format
timeout is 3 hours, which should be long enough for most disks. Some hard
disks will complete a format operation in a very short period of time
(on the order of 5 minutes or less). This is often because the drive
doesn't really support the FORMAT UNIT command -- it just accepts the
command, waits a few minutes and then returns it.
.Pp
The
.Sq format
subcommand takes several arguments that modify its default behavior. The
.Fl q
and
.Fl y
arguments can be useful for scripts.
.Pp
.Bl -tag -width 123456
.It Fl q
Be quiet, don't print any status messages. This option will not disable
the questions, however. To disable questions, use the
.Fl y
argument, below.
.It Fl w
Issue a non-immediate format command. By default,
.Nm camcontrol
issues the FORMAT UNIT command with the immediate bit set. This tells the
device to immediately return the format command, before the format has
actually completed. Then,
.Nm camcontrol
gathers
.Tn SCSI
sense information from the device every second to determine how far along
in the format process it is. If the
.Fl w
argument is specified,
.Nm camcontrol
will issue a non-immediate format command, and will be unable to print any
information to let the user know what percentage of the disk has been
formatted.
.It Fl y
Don't ask any questions. By default,
.Nm camcontrol
will ask the user if he/she really wants to format the disk in question,
and also if the default format command timeout is acceptable. The user
will not be asked about the timeout if a timeout is specified on the
command line.
.El
.It help
Print out verbose usage information.
.El
@ -611,33 +682,6 @@ camcontrol negotiate -n da -u 3 -R 20.000 -O 15 -a
.Pp
Negotiate a sync rate of 20MHz and an offset of 15 with da3. Then send a
Test Unit Ready command to make the settings take effect.
.Pp
.Bd -literal -offset indent
camcontrol cmd -n da -u 3 -v -t 7200 -c "4 0 0 0 0 0"
.Ed
.Pp
Send the FORMAT UNIT (0x04) command to da3. This will low-level format the
disk. Print sense information if the command fails, and set the timeout to
two hours (or 7200 seconds).
.Pp
.Em WARNING! WARNING! WARNING!
.Pp
Low level formatting a disk will destroy ALL data on the disk. Use
extreme caution when issuing this command. Many users low-level format
disks that do not really need to be low-level formatted. There are
relatively few scenarios that call for low-level formatting a disk.
One reason for
low-level formatting a disk is if you want to change the physical sector
size of the disk. Another reason for low-level formatting a disk is to
revive the disk if you are getting "medium format corrupted" errors from the
disk in response to read and write requests.
.Pp
Some disks take longer than others to format. Users should specify a
timeout long enough to allow the format to complete. Some hard disks
will complete a format operation in a very short period of time (on the
order of 5 minutes or less). This is often because the drive doesn't
really support the FORMAT UNIT command -- it just accepts the command,
waits a few minutes and then returns it.
.Sh SEE ALSO
.Xr cam 3 ,
.Xr cam_cdbparse 3 ,

View File

@ -132,6 +132,7 @@ struct camcontrol_opts option_table[] = {
{"negotiate", CAM_ARG_RATE, negotiate_opts},
{"rate", CAM_ARG_RATE, negotiate_opts},
{"debug", CAM_ARG_DEBUG, "ITSc"},
{"format", CAM_ARG_FORMAT, "qwy"},
{"help", CAM_ARG_USAGE, NULL},
{"-?", CAM_ARG_USAGE, NULL},
{"-h", CAM_ARG_USAGE, NULL},
@ -181,6 +182,8 @@ static int get_print_cts(struct cam_device *device, int user_settings,
int quiet, struct ccb_trans_settings *cts);
static int ratecontrol(struct cam_device *device, int retry_count,
int timeout, int argc, char **argv, char *combinedopt);
static int scsiformat(struct cam_device *device, int argc, char **argv,
char *combinedopt, int retry_count, int timeout);
camcontrol_optret
getoption(char *arg, cam_argmask *argnum, char **subopt)
@ -2652,6 +2655,311 @@ ratecontrol(struct cam_device *device, int retry_count, int timeout,
return(retval);
}
static int
scsiformat(struct cam_device *device, int argc, char **argv,
char *combinedopt, int retry_count, int timeout)
{
union ccb *ccb;
int c;
int ycount = 0, quiet = 0;
int error = 0, response = 0, retval = 0;
int use_timeout = 10800 * 1000;
int immediate = 1;
struct format_defect_list_header fh;
u_int8_t *data_ptr = NULL;
u_int32_t dxfer_len = 0;
u_int8_t byte2 = 0;
int num_warnings = 0;
ccb = cam_getccb(device);
if (ccb == NULL) {
warnx("scsiformat: error allocating ccb");
return(1);
}
bzero(&(&ccb->ccb_h)[1],
sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
while ((c = getopt(argc, argv, combinedopt)) != -1) {
switch(c) {
case 'q':
quiet++;
break;
case 'w':
immediate = 0;
break;
case 'y':
ycount++;
break;
}
}
if (quiet == 0) {
fprintf(stdout, "You are about to REMOVE ALL DATA from the "
"following device:\n");
error = scsidoinquiry(device, argc, argv, combinedopt,
retry_count, timeout);
if (error != 0) {
warnx("scsiformat: error sending inquiry");
goto scsiformat_bailout;
}
}
if (ycount == 0) {
do {
char str[1024];
fprintf(stdout, "Are you SURE you want to do "
"this? (yes/no) ");
if (fgets(str, sizeof(str), stdin) != NULL) {
if (strncasecmp(str, "yes", 3) == 0)
response = 1;
else if (strncasecmp(str, "no", 2) == 0)
response = -1;
else {
fprintf(stdout, "Please answer"
" \"yes\" or \"no\"\n");
}
}
} while (response == 0);
if (response == -1) {
error = 1;
goto scsiformat_bailout;
}
}
if (timeout != 0)
use_timeout = timeout;
if (quiet == 0) {
fprintf(stdout, "Current format timeout is %d seconds\n",
use_timeout / 1000);
}
/*
* If the user hasn't disabled questions and didn't specify a
* timeout on the command line, ask them if they want the current
* timeout.
*/
if ((ycount == 0)
&& (timeout == 0)) {
char str[1024];
int new_timeout = 0;
fprintf(stdout, "Enter new timeout in seconds or press\n"
"return to keep the current timeout [%d] ",
use_timeout / 1000);
if (fgets(str, sizeof(str), stdin) != NULL) {
if (str[0] != '\0')
new_timeout = atoi(str);
}
if (new_timeout != 0) {
use_timeout = new_timeout * 1000;
fprintf(stdout, "Using new timeout value %d\n",
use_timeout / 1000);
}
}
/*
* Keep this outside the if block below to silence any unused
* variable warnings.
*/
bzero(&fh, sizeof(fh));
/*
* If we're in immediate mode, we've got to include the format
* header
*/
if (immediate != 0) {
fh.byte2 = FU_DLH_IMMED;
data_ptr = (u_int8_t *)&fh;
dxfer_len = sizeof(fh);
byte2 = FU_FMT_DATA;
} else if (quiet == 0) {
fprintf(stdout, "Formatting...");
fflush(stdout);
}
scsi_format_unit(&ccb->csio,
/* retries */ retry_count,
/* cbfcnp */ NULL,
/* tag_action */ MSG_SIMPLE_Q_TAG,
/* byte2 */ byte2,
/* ileave */ 0,
/* data_ptr */ data_ptr,
/* dxfer_len */ dxfer_len,
/* sense_len */ SSD_FULL_SIZE,
/* timeout */ use_timeout);
/* Disable freezing the device queue */
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
if (arglist & CAM_ARG_ERR_RECOVER)
ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
if (((retval = cam_send_ccb(device, ccb)) < 0)
|| ((immediate == 0)
&& ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP))) {
char *errstr = "error sending format command";
if (retval < 0)
warn(errstr);
else
warnx(errstr);
if (arglist & CAM_ARG_VERBOSE) {
if ((ccb->ccb_h.status & CAM_STATUS_MASK) ==
CAM_SCSI_STATUS_ERROR)
scsi_sense_print(device, &ccb->csio, stderr);
else
fprintf(stderr, "CAM status is %#x\n",
ccb->ccb_h.status);
}
error = 1;
goto scsiformat_bailout;
}
/*
* If we ran in non-immediate mode, we already checked for errors
* above and printed out any necessary information. If we're in
* immediate mode, we need to loop through and get status
* information periodically.
*/
if (immediate == 0) {
if (quiet == 0) {
fprintf(stdout, "Format Complete\n");
}
goto scsiformat_bailout;
}
do {
cam_status status;
bzero(&(&ccb->ccb_h)[1],
sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
/*
* There's really no need to do error recovery or
* retries here, since we're just going to sit in a
* loop and wait for the device to finish formatting.
*/
scsi_test_unit_ready(&ccb->csio,
/* retries */ 0,
/* cbfcnp */ NULL,
/* tag_action */ MSG_SIMPLE_Q_TAG,
/* sense_len */ SSD_FULL_SIZE,
/* timeout */ 5000);
/* Disable freezing the device queue */
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
retval = cam_send_ccb(device, ccb);
/*
* If we get an error from the ioctl, bail out. SCSI
* errors are expected.
*/
if (retval < 0) {
warn("error sending CAMIOCOMMAND ioctl");
if (arglist & CAM_ARG_VERBOSE) {
if ((ccb->ccb_h.status & CAM_STATUS_MASK) ==
CAM_SCSI_STATUS_ERROR)
scsi_sense_print(device, &ccb->csio,
stderr);
else
fprintf(stderr, "CAM status is %#x\n",
ccb->ccb_h.status);
}
error = 1;
goto scsiformat_bailout;
}
status = ccb->ccb_h.status & CAM_STATUS_MASK;
if ((status != CAM_REQ_CMP)
&& (status == CAM_SCSI_STATUS_ERROR)) {
struct scsi_sense_data *sense;
int error_code, sense_key, asc, ascq;
sense = &ccb->csio.sense_data;
scsi_extract_sense(sense, &error_code, &sense_key,
&asc, &ascq);
/*
* According to the SCSI-2 and SCSI-3 specs, a
* drive that is in the middle of a format should
* return NOT READY with an ASC of "logical unit
* not ready, format in progress". The sense key
* specific bytes will then be a progress indicator.
*/
if ((sense_key == SSD_KEY_NOT_READY)
&& (asc == 0x04) && (ascq == 0x04)) {
if ((sense->extra_len >= 10)
&& ((sense->sense_key_spec[0] &
SSD_SCS_VALID) != 0)
&& (quiet == 0)) {
int val;
u_int64_t percentage;
val = scsi_2btoul(
&sense->sense_key_spec[1]);
percentage = 10000 * val;
fprintf(stdout,
"\rFormatting: %qd.%02qd %% "
"(%d/%d) done",
percentage / (0x10000 * 100),
(percentage / 0x10000) % 100,
val, 0x10000);
fflush(stdout);
} else if ((quiet == 0)
&& (++num_warnings <= 1)) {
warnx("Unexpected SCSI Sense Key "
"Specific value returned "
"during format:");
scsi_sense_print(device, &ccb->csio,
stderr);
warnx("Unable to print status "
"information, but format will "
"proceed.");
warnx("will exit when format is "
"complete");
}
sleep(1);
} else {
warnx("Unexpected SCSI error during format");
scsi_sense_print(device, &ccb->csio, stderr);
error = 1;
goto scsiformat_bailout;
}
} else if (status != CAM_REQ_CMP) {
warnx("Unexpected CAM status %#x", status);
error = 1;
goto scsiformat_bailout;
}
} while((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP);
if (quiet == 0)
fprintf(stdout, "\nFormat Complete\n");
scsiformat_bailout:
cam_freeccb(ccb);
return(error);
}
void
usage(int verbose)
{
@ -2677,6 +2985,7 @@ usage(int verbose)
" [-D <enable|disable>][-O offset][-q]\n"
" [-R syncrate][-v][-T <enable|disable>]\n"
" [-U][-W bus_width]\n"
" camcontrol format [dev_id][generic args][-q][-w][-y]\n"
" camcontrol help\n");
if (!verbose)
return;
@ -2697,6 +3006,7 @@ usage(int verbose)
"debug turn debugging on/off for a bus, target, or lun, or all devices\n"
"tags report or set the number of transaction slots for a device\n"
"negotiate report or set device negotiation parameters\n"
"format send the SCSI FORMAT UNIT command to the named device\n"
"help this message\n"
"Device Identifiers:\n"
"bus:target specify the bus and target, lun defaults to 0\n"
@ -2745,7 +3055,11 @@ usage(int verbose)
"-T <arg> \"enable\" or \"disable\" tagged queueing\n"
"-U report/set user negotiation settings\n"
"-W bus_width set the bus width in bits (8, 16 or 32)\n"
"-v also print a Path Inquiry CCB for the controller\n",
"-v also print a Path Inquiry CCB for the controller\n"
"format arguments:\n"
"-q be quiet, don't print status messages\n"
"-w don't send immediate format command\n"
"-y don't ask any questions\n",
DEFAULT_DEVICE, DEFAULT_UNIT);
}
@ -3005,6 +3319,10 @@ main(int argc, char **argv)
error = ratecontrol(cam_dev, retry_count, timeout,
argc, argv, combinedopt);
break;
case CAM_ARG_FORMAT:
error = scsiformat(cam_dev, argc, argv,
combinedopt, retry_count, timeout);
break;
case CAM_ARG_USAGE:
usage(1);
break;

View File

@ -28,12 +28,18 @@
* $FreeBSD$
*/
#ifdef _KERNEL
#include "opt_hw_wdog.h"
#endif /* _KERNEL */
#include <sys/param.h>
#ifdef _KERNEL
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/bio.h>
#endif /* _KERNEL */
#include <sys/devicestat.h>
#include <sys/conf.h>
#include <sys/disk.h>
@ -46,6 +52,11 @@
#include <vm/vm.h>
#include <vm/pmap.h>
#ifndef _KERNEL
#include <stdio.h>
#include <string.h>
#endif /* _KERNEL */
#include <cam/cam.h>
#include <cam/cam_ccb.h>
#include <cam/cam_extend.h>
@ -54,6 +65,11 @@
#include <cam/scsi/scsi_message.h>
#ifndef _KERNEL
#include <cam/scsi/scsi_da.h>
#endif /* !_KERNEL */
#ifdef _KERNEL
typedef enum {
DA_STATE_PROBE,
DA_STATE_NORMAL
@ -1548,3 +1564,38 @@ dashutdown(void * arg, int howto)
}
}
#else /* !_KERNEL */
/*
* XXX This is only left out of the kernel build to silence warnings. If,
* for some reason this function is used in the kernel, the ifdefs should
* be moved so it is included both in the kernel and userland.
*/
void
scsi_format_unit(struct ccb_scsiio *csio, u_int32_t retries,
void (*cbfcnp)(struct cam_periph *, union ccb *),
u_int8_t tag_action, u_int8_t byte2, u_int16_t ileave,
u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len,
u_int32_t timeout)
{
struct scsi_format_unit *scsi_cmd;
scsi_cmd = (struct scsi_format_unit *)&csio->cdb_io.cdb_bytes;
scsi_cmd->opcode = FORMAT_UNIT;
scsi_cmd->byte2 = byte2;
scsi_ulto2b(ileave, scsi_cmd->interleave);
cam_fill_csio(csio,
retries,
cbfcnp,
/*flags*/ (dxfer_len > 0) ? CAM_DIR_OUT : CAM_DIR_NONE,
tag_action,
data_ptr,
dxfer_len,
sense_len,
sizeof(*scsi_cmd),
timeout);
}
#endif /* _KERNEL */

View File

@ -63,6 +63,25 @@ struct scsi_rezero_unit
u_int8_t control;
};
/*
* NOTE: The lower three bits of byte2 of the format CDB are the same as
* the lower three bits of byte2 of the read defect data CDB, below.
*/
struct scsi_format_unit
{
u_int8_t opcode;
u_int8_t byte2;
#define FU_FORMAT_MASK SRDD10_DLIST_FORMAT_MASK
#define FU_BLOCK_FORMAT SRDD10_BLOCK_FORMAT
#define FU_BFI_FORMAT SRDD10_BYTES_FROM_INDEX_FORMAT
#define FU_PHYS_FORMAT SRDD10_PHYSICAL_SECTOR_FORMAT
#define FU_CMPLST 0x08
#define FU_FMT_DATA 0x10
u_int8_t vendor_specific;
u_int8_t interleave[2];
u_int8_t control;
};
struct scsi_reassign_blocks
{
u_int8_t opcode;
@ -128,12 +147,40 @@ struct scsi_read_defect_data_12
* Opcodes
*/
#define REZERO_UNIT 0x01
#define FORMAT_UNIT 0x04
#define REASSIGN_BLOCKS 0x07
#define MODE_SELECT 0x15
#define MODE_SENSE 0x1a
#define READ_DEFECT_DATA_10 0x37
#define READ_DEFECT_DATA_12 0xb7
struct format_defect_list_header
{
u_int8_t reserved;
u_int8_t byte2;
#define FU_DLH_VS 0x01
#define FU_DLH_IMMED 0x02
#define FU_DLH_DSP 0x04
#define FU_DLH_IP 0x08
#define FU_DLH_STPF 0x10
#define FU_DLH_DCRT 0x20
#define FU_DLH_DPRY 0x40
#define FU_DLH_FOV 0x80
u_int8_t defect_list_length[2];
};
struct format_ipat_descriptor
{
u_int8_t byte1;
#define FU_INIT_NO_HDR 0x00
#define FU_INIT_LBA_MSB 0x40
#define FU_INIT_LBA_EACH 0x80
#define FU_INIT_SI 0x20
u_int8_t pattern_type;
#define FU_INIT_PAT_DEFAULT 0x00
#define FU_INIT_PAT_REPEAT 0x01
u_int8_t pat_length[2];
};
struct scsi_reassign_blocks_data
{
@ -318,4 +365,20 @@ struct scsi_da_rw_recovery_page {
u_int8_t recovery_time_limit[2];
};
__BEGIN_DECLS
/*
* XXX This is only left out of the kernel build to silence warnings. If,
* for some reason this function is used in the kernel, the ifdefs should
* be moved so it is included both in the kernel and userland.
*/
#ifndef _KERNEL
void scsi_format_unit(struct ccb_scsiio *csio, u_int32_t retries,
void (*cbfcnp)(struct cam_periph *, union ccb *),
u_int8_t tag_action, u_int8_t byte2, u_int16_t ileave,
u_int8_t *data_ptr, u_int32_t dxfer_len,
u_int8_t sense_len, u_int32_t timeout);
#endif /* !_KERNEL */
__END_DECLS
#endif /* _SCSI_SCSI_DA_H */