xref: /openbsd-src/sbin/atactl/atactl.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: atactl.c,v 1.8 2001/07/07 18:26:09 deraadt Exp $	*/
2 /*	$NetBSD: atactl.c,v 1.4 1999/02/24 18:49:14 jwise Exp $	*/
3 
4 /*-
5  * Copyright (c) 1998 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Ken Hornstein.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *	This product includes software developed by the NetBSD
22  *	Foundation, Inc. and its contributors.
23  * 4. Neither the name of The NetBSD Foundation nor the names of its
24  *    contributors may be used to endorse or promote products derived
25  *    from this software without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 /*
41  * atactl(8) - a program to control ATA devices.
42  */
43 
44 #include <sys/param.h>
45 #include <sys/ioctl.h>
46 #include <err.h>
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include <util.h>
54 
55 #include <dev/ata/atareg.h>
56 #include <dev/ic/wdcreg.h>
57 #include <sys/ataio.h>
58 
59 struct command {
60 	const char *cmd_name;
61 	void (*cmd_func) __P((int, char *[]));
62 };
63 
64 struct bitinfo {
65 	u_int bitmask;
66 	const char *string;
67 };
68 
69 int	main __P((int, char *[]));
70 void	usage __P((void));
71 void	ata_command __P((struct atareq *));
72 void	print_bitinfo __P((const char *, u_int, struct bitinfo *));
73 
74 int	fd;				/* file descriptor for device */
75 const	char *dvname;			/* device name */
76 char	dvname_store[MAXPATHLEN];	/* for opendisk(3) */
77 const	char *cmdname;			/* command user issued */
78 
79 extern const char *__progname;		/* from crt0.o */
80 
81 void	device_identify __P((int, char *[]));
82 void	device_setidle __P((int, char *[]));
83 void	device_idle __P((int, char *[]));
84 void	device_checkpower __P((int, char *[]));
85 void	device_acoustic __P((int, char *[]));
86 void	device_apm __P((int, char *[]));
87 void	device_feature __P((int, char *[]));
88 void	device_smart __P((int, char *[]));
89 
90 struct command commands[] = {
91 	{ "identify",		device_identify },
92 	{ "setidle",		device_setidle },
93 	{ "setstandby",		device_setidle },
94 	{ "idle",		device_idle },
95 	{ "standby",		device_idle },
96 	{ "sleep",		device_idle },
97 	{ "checkpower",		device_checkpower },
98 	{ "acousticdisable",	device_feature },
99 	{ "acousticset",	device_acoustic },
100 	{ "apmdisable",		device_feature },
101 	{ "apmset",		device_apm },
102 	{ "poddisable",		device_feature },
103 	{ "podenable",		device_feature },
104 	{ "puisdisable",	device_feature },
105 	{ "puisenable",		device_feature },
106 	{ "puisspinup",		device_feature },
107 	{ "readaheaddisable",	device_feature },
108 	{ "readaheadenable",	device_feature },
109 	{ "smartenable", 	device_smart },
110 	{ "smartdisable", 	device_smart },
111 	{ "smartstatus", 	device_smart },
112 	{ "writecachedisable",	device_feature },
113 	{ "writecacheenable",	device_feature },
114 	{ NULL,		NULL },
115 };
116 
117 /*
118  * Tables containing bitmasks used for error reporting and
119  * device identification.
120  */
121 
122 struct bitinfo ata_caps[] = {
123 	{ ATA_CAP_STBY, "ATA standby timer values" },
124 	{ WDC_CAP_IORDY, "IORDY operation" },
125 	{ WDC_CAP_IORDY_DSBL, "IORDY disabling" },
126 	{ NULL, NULL },
127 };
128 
129 struct bitinfo ata_vers[] = {
130 	{ WDC_VER_ATA1,	 "ATA-1" },
131 	{ WDC_VER_ATA2,	 "ATA-2" },
132 	{ WDC_VER_ATA3,	 "ATA-3" },
133 	{ WDC_VER_ATA4,	 "ATA-4" },
134 	{ WDC_VER_ATA5,	 "ATA-5" },
135 	{ WDC_VER_ATA6,	 "ATA-6" },
136 	{ WDC_VER_ATA7,	 "ATA-7" },
137 	{ WDC_VER_ATA8,	 "ATA-8" },
138 	{ WDC_VER_ATA9,	 "ATA-9" },
139 	{ WDC_VER_ATA10, "ATA-10" },
140 	{ WDC_VER_ATA11, "ATA-11" },
141 	{ WDC_VER_ATA12, "ATA-12" },
142 	{ WDC_VER_ATA13, "ATA-13" },
143 	{ WDC_VER_ATA14, "ATA-14" },
144 	{ NULL, NULL },
145 };
146 
147 struct bitinfo ata_cmd_set1[] = {
148 	{ WDC_CMD1_NOP, "NOP command" },
149 	{ WDC_CMD1_RB, "READ BUFFER command" },
150 	{ WDC_CMD1_WB, "WRITE BUFFER command" },
151 	{ WDC_CMD1_HPA, "Host Protected Area feature set" },
152 	{ WDC_CMD1_DVRST, "DEVICE RESET command" },
153 	{ WDC_CMD1_SRV, "SERVICE interrupt" },
154 	{ WDC_CMD1_RLSE, "release interrupt" },
155 	{ WDC_CMD1_AHEAD, "read look-ahead" },
156 	{ WDC_CMD1_CACHE, "write cache" },
157 	{ WDC_CMD1_PKT, "PACKET command feature set" },
158 	{ WDC_CMD1_PM, "Power Management feature set" },
159 	{ WDC_CMD1_REMOV, "Removable Media feature set" },
160 	{ WDC_CMD1_SEC, "Security Mode feature set" },
161 	{ WDC_CMD1_SMART, "SMART feature set" },
162 	{ NULL, NULL },
163 };
164 
165 struct bitinfo ata_cmd_set2[] = {
166 	{ ATAPI_CMD2_FCE, "Flush Cache Ext command" },
167 	{ ATAPI_CMD2_FC, "Flush Cache command" },
168 	{ ATAPI_CMD2_DCO, "Device Configuration Overlay feature set" },
169 	{ ATAPI_CMD2_48AD, "48bit address feature set" },
170 	{ ATAPI_CMD2_AAM, "Automatic Acoustic Management feature set" },
171 	{ ATAPI_CMD2_SM, "Set Max security extension commands" },
172 	{ ATAPI_CMD2_SF, "Set Features subcommand required" },
173 	{ ATAPI_CMD2_PUIS, "Power-up in standby feature set" },
174 	{ WDC_CMD2_RMSN, "Removable Media Status Notification feature set" },
175 	{ ATA_CMD2_APM, "Advanced Power Management feature set" },
176 	{ ATA_CMD2_CFA, "CFA feature set" },
177 	{ ATA_CMD2_RWQ, "READ/WRITE DMA QUEUED commands" },
178 	{ WDC_CMD2_DM, "DOWNLOAD MICROCODE command" },
179 	{ NULL, NULL },
180 };
181 
182 struct bitinfo ata_cmd_ext[] = {
183 	{ ATAPI_CMDE_MSER, "Media serial number" },
184 	{ ATAPI_CMDE_TEST, "SMART self-test" },
185 	{ ATAPI_CMDE_SLOG, "SMART error logging" },
186 	{ NULL, NULL },
187 };
188 
189 int
190 main(argc, argv)
191 	int argc;
192 	char *argv[];
193 {
194 	int i;
195 
196 	dvname = argv[1];
197 	if (argc == 2) {
198 		cmdname = "identify";
199 		argv += 2;
200 		argc -= 2;
201 	} else if (argc < 3) {
202 		usage();
203 	} else {
204 		/* Skip program name, get and skip device name and command. */
205 
206 		cmdname = argv[2];
207 		argv += 3;
208 		argc -= 3;
209 	}
210 
211 	/*
212 	 * Open the device
213 	 */
214 	fd = opendisk(dvname, O_RDWR, dvname_store, sizeof(dvname_store), 0);
215 	if (fd == -1) {
216 		if (errno == ENOENT) {
217 			/*
218 			 * Device doesn't exist.  Probably trying to open
219 			 * a device which doesn't use disk semantics for
220 			 * device name.  Try again, specifying "cooked",
221 			 * which leaves off the "r" in front of the device's
222 			 * name.
223 			 */
224 			fd = opendisk(dvname, O_RDWR, dvname_store,
225 			    sizeof(dvname_store), 1);
226 			if (fd == -1)
227 				err(1, "%s", dvname);
228 		} else
229 			err(1, "%s", dvname);
230 	}
231 
232 	/*
233 	 * Point the dvname at the actual device name that opendisk() opened.
234 	 */
235 	dvname = dvname_store;
236 
237 	/* Look up and call the command. */
238 	for (i = 0; commands[i].cmd_name != NULL; i++)
239 		if (strcmp(cmdname, commands[i].cmd_name) == 0)
240 			break;
241 	if (commands[i].cmd_name == NULL)
242 		errx(1, "unknown command: %s\n", cmdname);
243 
244 	(*commands[i].cmd_func)(argc, argv);
245 
246 	return (0);
247 }
248 
249 void
250 usage()
251 {
252 
253 	fprintf(stderr, "usage: %s device command [arg [...]]\n",
254 	    __progname);
255 	exit(1);
256 }
257 
258 /*
259  * Wrapper that calls ATAIOCCOMMAND and checks for errors
260  */
261 
262 void
263 ata_command(req)
264 	struct atareq *req;
265 {
266 	int error;
267 
268 	error = ioctl(fd, ATAIOCCOMMAND, req);
269 
270 	if (error == -1)
271 		err(1, "ATAIOCCOMMAND failed");
272 
273 	switch (req->retsts) {
274 
275 	case ATACMD_OK:
276 		return;
277 	case ATACMD_TIMEOUT:
278 		fprintf(stderr, "ATA command timed out\n");
279 		exit(1);
280 	case ATACMD_DF:
281 		fprintf(stderr, "ATA device returned a Device Fault\n");
282 		exit(1);
283 	case ATACMD_ERROR:
284 		if (req->error & WDCE_ABRT)
285 			fprintf(stderr, "ATA device returned Aborted "
286 			    "Command\n");
287 		else
288 			fprintf(stderr, "ATA device returned error register "
289 			    "%0x\n", req->error);
290 		exit(1);
291 	default:
292 		fprintf(stderr, "ATAIOCCOMMAND returned unknown result code "
293 		    "%d\n", req->retsts);
294 		exit(1);
295 	}
296 }
297 
298 /*
299  * Print out strings associated with particular bitmasks
300  */
301 
302 void
303 print_bitinfo(f, bits, binfo)
304 	const char *f;
305 	u_int bits;
306 	struct bitinfo *binfo;
307 {
308 
309 	for (; binfo->bitmask != NULL; binfo++)
310 		if (bits & binfo->bitmask)
311 			printf(f, binfo->string);
312 }
313 
314 /*
315  * DEVICE COMMANDS
316  */
317 
318 /*
319  * device_identify:
320  *
321  *	Display the identity of the device
322  */
323 void
324 device_identify(argc, argv)
325 	int argc;
326 	char *argv[];
327 {
328 	struct ataparams *inqbuf;
329 	struct atareq req;
330 	unsigned char inbuf[DEV_BSIZE];
331 #if BYTE_ORDER == LITTLE_ENDIAN
332 	int i;
333 	u_int16_t *p;
334 #endif
335 
336 	/* No arguments. */
337 	if (argc != 0)
338 		goto usage;
339 
340 	memset(&inbuf, 0, sizeof(inbuf));
341 	memset(&req, 0, sizeof(req));
342 
343 	inqbuf = (struct ataparams *) inbuf;
344 
345 	req.flags = ATACMD_READ;
346 	req.command = WDCC_IDENTIFY;
347 	req.databuf = (caddr_t) inbuf;
348 	req.datalen = sizeof(inbuf);
349 	req.timeout = 1000;
350 
351 	ata_command(&req);
352 
353 #if BYTE_ORDER == LITTLE_ENDIAN
354 	/*
355 	 * On little endian machines, we need to shuffle the string
356 	 * byte order.  However, we don't have to do this for NEC or
357 	 * Mitsumi ATAPI devices
358 	 */
359 
360 	if (!((inqbuf->atap_config & WDC_CFG_ATAPI_MASK) == WDC_CFG_ATAPI &&
361 	      ((inqbuf->atap_model[0] == 'N' &&
362 		  inqbuf->atap_model[1] == 'E') ||
363 	       (inqbuf->atap_model[0] == 'F' &&
364 		  inqbuf->atap_model[1] == 'X')))) {
365 		for (i = 0 ; i < sizeof(inqbuf->atap_model); i += 2) {
366 			p = (u_short *) (inqbuf->atap_model + i);
367 			*p = ntohs(*p);
368 		}
369 		for (i = 0 ; i < sizeof(inqbuf->atap_serial); i += 2) {
370 			p = (u_short *) (inqbuf->atap_serial + i);
371 			*p = ntohs(*p);
372 		}
373 		for (i = 0 ; i < sizeof(inqbuf->atap_revision); i += 2) {
374 			p = (u_short *) (inqbuf->atap_revision + i);
375 			*p = ntohs(*p);
376 		}
377 	}
378 #endif
379 
380 	/*
381 	 * Strip blanks off of the info strings.  Yuck, I wish this was
382 	 * cleaner.
383 	 */
384 
385 	if (inqbuf->atap_model[sizeof(inqbuf->atap_model) - 1] == ' ') {
386 		inqbuf->atap_model[sizeof(inqbuf->atap_model) - 1] = '\0';
387 		while (inqbuf->atap_model[strlen(inqbuf->atap_model) - 1] == ' ')
388 			inqbuf->atap_model[strlen(inqbuf->atap_model) - 1] = '\0';
389 	}
390 
391 	if (inqbuf->atap_revision[sizeof(inqbuf->atap_revision) - 1] == ' ') {
392 		inqbuf->atap_revision[sizeof(inqbuf->atap_revision) - 1] = '\0';
393 		while (inqbuf->atap_revision[strlen(inqbuf->atap_revision) - 1] == ' ')
394 			inqbuf->atap_revision[strlen(inqbuf->atap_revision) - 1] = '\0';
395 	}
396 
397 	if (inqbuf->atap_serial[sizeof(inqbuf->atap_serial) - 1] == ' ') {
398 		inqbuf->atap_serial[sizeof(inqbuf->atap_serial) - 1] = '\0';
399 		while (inqbuf->atap_serial[strlen(inqbuf->atap_serial) - 1] == ' ')
400 			inqbuf->atap_serial[strlen(inqbuf->atap_serial) - 1] = '\0';
401 	}
402 
403 	printf("Model: %.*s, Rev: %.*s, Serial #: %.*s\n",
404 	    (int) sizeof(inqbuf->atap_model), inqbuf->atap_model,
405 	    (int) sizeof(inqbuf->atap_revision), inqbuf->atap_revision,
406 	    (int) sizeof(inqbuf->atap_serial), inqbuf->atap_serial);
407 
408 	printf("Device type: %s, %s\n", inqbuf->atap_config & WDC_CFG_ATAPI ?
409 	       "ATAPI" : "ATA", inqbuf->atap_config & ATA_CFG_FIXED ? "fixed" :
410 	       "removable");
411 
412 	if ((inqbuf->atap_config & WDC_CFG_ATAPI_MASK) == 0)
413 		printf("Cylinders: %d, heads: %d, sec/track: %d, total "
414 		    "sectors: %d\n", inqbuf->atap_cylinders,
415 		    inqbuf->atap_heads, inqbuf->atap_sectors,
416 		    (inqbuf->atap_capacity[1] << 16) |
417 		    inqbuf->atap_capacity[0]);
418 
419 	if (inqbuf->atap_queuedepth & WDC_QUEUE_DEPTH_MASK)
420 		printf("Device supports command queue depth of %d\n",
421 		    inqbuf->atap_queuedepth & 0xf);
422 
423 	printf("Device capabilities:\n");
424 	print_bitinfo("\t%s\n", inqbuf->atap_capabilities1, ata_caps);
425 
426 	if (inqbuf->atap_ata_major != 0 && inqbuf->atap_ata_major != 0xffff) {
427 		printf("Device supports following standards:\n");
428 		print_bitinfo("%s ", inqbuf->atap_ata_major, ata_vers);
429 		printf("\n");
430 	}
431 
432 	if (inqbuf->atap_cmd_set1 != 0 && inqbuf->atap_cmd_set1 != 0xffff &&
433 	    inqbuf->atap_cmd_set2 != 0 && inqbuf->atap_cmd_set2 != 0xffff) {
434 		printf("Device supports the following command sets:\n");
435 		print_bitinfo("\t%s\n", inqbuf->atap_cmd_set1, ata_cmd_set1);
436 		print_bitinfo("\t%s\n", inqbuf->atap_cmd_set2, ata_cmd_set2);
437 		print_bitinfo("\t%s\n", inqbuf->atap_cmd_ext, ata_cmd_ext);
438 	}
439 
440 	if (inqbuf->atap_cmd_def != 0 && inqbuf->atap_cmd_def != 0xffff) {
441 		printf("Device has enabled the following command sets/features:\n");
442 		print_bitinfo("\t%s\n", inqbuf->atap_cmd1_en, ata_cmd_set1);
443 		print_bitinfo("\t%s\n", inqbuf->atap_cmd2_en, ata_cmd_set2);
444 #if 0
445 		print_bitinfo("\t%s\n", inqbuf->atap_cmd_set1 &
446 		    (WDC_CMD1_SRV | WDC_CMD1_RLSE | WDC_CMD1_AHEAD |
447 		    WDC_CMD1_CACHE | WDC_CMD1_SEC | WDC_CMD1_SMART),
448 		    ata_cmd_set1);
449 		print_bitinfo("\t%s\n", inqbuf->atap_cmd_set2 &
450 		    (WDC_CMD2_RMSN | ATA_CMD2_APM | ATAPI_CMD2_PUIS |
451 		    ATAPI_CMD2_AAM | ATAPI_CMD2_48AD |
452 		    ATAPI_CMD2_DCO), ata_cmd_set2);
453 #endif
454 	}
455 
456 	return;
457 
458 usage:
459 	fprintf(stderr, "usage: %s device %s\n", __progname, cmdname);
460 	exit(1);
461 }
462 
463 /*
464  * device idle:
465  *
466  * issue the IDLE IMMEDIATE command to the drive
467  */
468 
469 void
470 device_idle(argc, argv)
471 	int argc;
472 	char *argv[];
473 {
474 	struct atareq req;
475 
476 	/* No arguments. */
477 	if (argc != 0)
478 		goto usage;
479 
480 	memset(&req, 0, sizeof(req));
481 
482 	if (strcmp(cmdname, "idle") == 0)
483 		req.command = WDCC_IDLE_IMMED;
484 	else if (strcmp(cmdname, "standby") == 0)
485 		req.command = WDCC_STANDBY_IMMED;
486 	else
487 		req.command = WDCC_SLEEP;
488 
489 	req.timeout = 1000;
490 
491 	ata_command(&req);
492 
493 	return;
494 usage:
495 	fprintf(stderr, "usage: %s device %s\n", __progname, cmdname);
496 	exit(1);
497 }
498 
499 /*
500  * SMART.
501  *
502  * issue the SMART ENABLE/DISABLE/STATUS commands to the drive
503  */
504 
505 void
506 device_smart(argc, argv)
507 	int argc;
508 	char *argv[];
509 {
510 	struct atareq req;
511 
512 	/* No arguments. */
513 	if (argc != 0)
514 		goto usage;
515 
516 	memset(&req, 0, sizeof(req));
517 
518 	req.command = ATAPI_SMART;
519 	req.cylinder = 0xC24F; /* Cylinders is mapped to LBA Mid/Low */
520 	/* XXX: I assume cylinders is correctly mapped w.r.t.
521 	 * endianness? */
522 
523 	if (strcmp(cmdname, "smartenable") == 0)
524 		req.features = ATAPI_SMART_EN;
525 	else if (strcmp(cmdname, "smartdisable") == 0)
526 		req.features = ATAPI_SMART_DS;
527 	else if (strcmp(cmdname, "smartstatus") == 0)
528 		req.features = ATAPI_SMART_STATUS;
529 	else
530 		goto usage;
531 
532 	req.timeout = 1000;
533 
534 	ata_command(&req);
535 
536 	if (strcmp(cmdname, "smartstatus") == 0) {
537 		if (req.cylinder == 0xC24F)
538 			printf("No SMART threshold exceeded\n");
539 		else if (req.cylinder == 0x2CF4) {
540 			fprintf(stderr,"SMART threshold exceeded!\n");
541 			exit(2);
542 		} else {
543 			fprintf(stderr, "Unknown response %02x!\n",
544 			    req.cylinder);
545 			exit(1);
546 		}
547 	}
548 
549 	return;
550 usage:
551 	fprintf(stderr, "usage: %s device %s\n", __progname, cmdname);
552 	exit(1);
553 }
554 
555 /*
556  * Set the automatic acoustic managmement on the disk.
557  */
558 void
559 device_acoustic(argc, argv)
560 	int argc;
561 	char *argv[];
562 {
563 	unsigned long acoustic;
564 	struct atareq req;
565 	char *end;
566 
567 	/* Only one argument */
568 	if (argc != 1)
569 		goto usage;
570 
571 	acoustic = strtoul(argv[0], &end, 0);
572 
573 	if (*end != '\0') {
574 		fprintf(stderr, "Invalid acoustic management value: \"%s\""
575 		    "(valid values range from 0 to 126)\n", argv[0]);
576 		exit(1);
577 	}
578 
579 	if (acoustic > 126) {
580 		fprintf(stderr, "Automatic acoustic management has a "
581 		    "maximum value of 126\n");
582 		exit(1);
583 	}
584 
585 	memset(&req, 0, sizeof(req));
586 
587 	req.sec_count = acoustic + 0x80;
588 
589 	req.command = SET_FEATURES ;
590 	req.features = WDSF_AAM_EN ;
591 	req.timeout = 1000;
592 
593 	ata_command(&req);
594 
595 	return;
596 
597 usage:
598 	fprintf(stderr, "usage; %s device %s acoustic-management-value\n",
599 	    __progname, cmdname);
600 	exit(1);
601 }
602 
603 /*
604  * Set the advanced power managmement on the disk. Power management
605  * levels are translated from user-range 0-253 to ATAPI levels 1-0xFD
606  * to keep a uniform interface to the user.
607  */
608 void
609 device_apm(argc, argv)
610 	int argc;
611 	char *argv[];
612 {
613 	unsigned long power;
614 	struct atareq req;
615 	char *end;
616 
617 	/* Only one argument */
618 	if (argc != 1)
619 		goto usage;
620 
621 	power = strtoul(argv[0], &end, 0);
622 
623 	if (*end != '\0') {
624 		fprintf(stderr, "Invalid advanced power management value: "
625 		    "\"%s\" (valid values range from 0 to 253)\n",
626 		    argv[0]);
627 		exit(1);
628 	}
629 
630 	if (power > 253) {
631 		fprintf(stderr, "Advanced power management has a "
632 		    "maximum value of 253\n");
633 		exit(1);
634 	}
635 
636 	memset(&req, 0, sizeof(req));
637 
638 	req.sec_count = power + 0x01;
639 
640 	req.command = SET_FEATURES ;
641 	req.features = WDSF_APM_EN ;
642 	req.timeout = 1000;
643 
644 	ata_command(&req);
645 
646 	return;
647 
648 usage:
649 	fprintf(stderr, "usage; %s device %s power-management-level\n",
650 	    __progname, cmdname);
651 	exit(1);
652 }
653 
654 /*
655  * En/disable features (the automatic acoustic managmement, Advanced Power
656  * Management) on the disk.
657  */
658 void
659 device_feature(argc, argv)
660 	int argc;
661 	char *argv[];
662 {
663 	struct atareq req;
664 
665 	/* No argument */
666 	if (argc != 0)
667 		goto usage;
668 
669 	memset(&req, 0, sizeof(req));
670 
671 	req.command = SET_FEATURES ;
672 
673 	if (strcmp(cmdname, "acousticdisable") == 0)
674 		req.features = WDSF_AAM_DS;
675 	else if (strcmp(cmdname, "readaheadenable") == 0)
676 		req.features = WDSF_READAHEAD_EN;
677 	else if (strcmp(cmdname, "readaheaddisable") == 0)
678 		req.features = WDSF_READAHEAD_DS;
679 	else if (strcmp(cmdname, "writecacheenable") == 0)
680 		req.features = WDSF_EN_WR_CACHE;
681 	else if (strcmp(cmdname, "writecachedisable") == 0)
682 		req.features = WDSF_WRITE_CACHE_DS;
683 	else if (strcmp(cmdname, "apmdisable") == 0)
684 		req.features = WDSF_APM_DS;
685 	else if (strcmp(cmdname, "puisenable") == 0)
686 		req.features = WDSF_PUIS_EN;
687 	else if (strcmp(cmdname, "puisdisable") == 0)
688 		req.features = WDSF_PUIS_DS;
689 	else if (strcmp(cmdname, "puisspinup") == 0)
690 		req.features = WDSF_PUIS_SPINUP;
691 	else
692 		goto usage;
693 
694 	req.timeout = 1000;
695 
696 	ata_command(&req);
697 
698 	return;
699 
700 usage:
701 	fprintf(stderr, "usage; %s device %s\n", __progname,
702 	    cmdname);
703 	exit(1);
704 }
705 
706 /*
707  * Set the idle timer on the disk.  Set it for either idle mode or
708  * standby mode, depending on how we were invoked.
709  */
710 
711 void
712 device_setidle(argc, argv)
713 	int argc;
714 	char *argv[];
715 {
716 	unsigned long idle;
717 	struct atareq req;
718 	char *end;
719 
720 	/* Only one argument */
721 	if (argc != 1)
722 		goto usage;
723 
724 	idle = strtoul(argv[0], &end, 0);
725 
726 	if (*end != '\0') {
727 		fprintf(stderr, "Invalid idle time: \"%s\"\n", argv[0]);
728 		exit(1);
729 	}
730 
731 	if (idle > 19800) {
732 		fprintf(stderr, "Idle time has a maximum value of 5.5 "
733 		    "hours\n");
734 		exit(1);
735 	}
736 
737 	if (idle != 0 && idle < 5) {
738 		fprintf(stderr, "Idle timer must be at least 5 seconds\n");
739 		exit(1);
740 	}
741 
742 	memset(&req, 0, sizeof(req));
743 
744 	if (idle <= 240*5)
745 		req.sec_count = idle / 5;
746 	else
747 		req.sec_count = idle / (30*60) + 240;
748 
749 	req.command = cmdname[3] == 's' ? WDCC_STANDBY : WDCC_IDLE;
750 	req.timeout = 1000;
751 
752 	ata_command(&req);
753 
754 	return;
755 
756 usage:
757 	fprintf(stderr, "usage; %s device %s idle-time\n", __progname,
758 	    cmdname);
759 	exit(1);
760 }
761 
762 /*
763  * Query the device for the current power mode
764  */
765 
766 void
767 device_checkpower(argc, argv)
768 	int argc;
769 	char *argv[];
770 {
771 	struct atareq req;
772 
773 	/* No arguments. */
774 	if (argc != 0)
775 		goto usage;
776 
777 	memset(&req, 0, sizeof(req));
778 
779 	req.command = WDCC_CHECK_PWR;
780 	req.timeout = 1000;
781 	req.flags = ATACMD_READREG;
782 
783 	ata_command(&req);
784 
785 	printf("Current power status: ");
786 
787 	switch (req.sec_count) {
788 	case 0x00:
789 		printf("Standby mode\n");
790 		break;
791 	case 0x80:
792 		printf("Idle mode\n");
793 		break;
794 	case 0xff:
795 		printf("Active mode\n");
796 		break;
797 	default:
798 		printf("Unknown power code (%02x)\n", req.sec_count);
799 	}
800 
801 	return;
802 usage:
803 	fprintf(stderr, "usage: %s device %s\n", __progname, cmdname);
804 	exit(1);
805 }
806