]>
Commit | Line | Data |
---|---|---|
9ff516f3 | 1 | /* hdparm.c - command to get/set ATA disk parameters. */ |
2 | /* | |
3 | * GRUB -- GRand Unified Bootloader | |
4 | * Copyright (C) 2009 Free Software Foundation, Inc. | |
5 | * | |
6 | * GRUB is free software: you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation, either version 3 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * GRUB is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with GRUB. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
9ff516f3 | 20 | #include <grub/ata.h> |
c7336d91 | 21 | #include <grub/scsi.h> |
9ff516f3 | 22 | #include <grub/disk.h> |
23 | #include <grub/dl.h> | |
24 | #include <grub/misc.h> | |
9ff516f3 | 25 | #include <grub/mm.h> |
26 | #include <grub/lib/hexdump.h> | |
b1b797cb | 27 | #include <grub/extcmd.h> |
77a79592 | 28 | #include <grub/i18n.h> |
9ff516f3 | 29 | |
e745cf0c VS |
30 | GRUB_MOD_LICENSE ("GPLv3+"); |
31 | ||
9ff516f3 | 32 | static const struct grub_arg_option options[] = { |
77a79592 | 33 | {"apm", 'B', 0, N_("Set Advanced Power Management\n" |
34 | "(1=low, ..., 254=high, 255=off)."), | |
9ff516f3 | 35 | 0, ARG_TYPE_INT}, |
8f95d002 | 36 | {"power", 'C', 0, N_("Display power mode."), 0, ARG_TYPE_NONE}, |
77a79592 | 37 | {"security-freeze", 'F', 0, N_("Freeze ATA security settings until reset."), |
9ff516f3 | 38 | 0, ARG_TYPE_NONE}, |
8f95d002 | 39 | {"health", 'H', 0, N_("Display SMART health status."), 0, ARG_TYPE_NONE}, |
77a79592 | 40 | {"aam", 'M', 0, N_("Set Automatic Acoustic Management\n" |
41 | "(0=off, 128=quiet, ..., 254=fast)."), | |
9ff516f3 | 42 | 0, ARG_TYPE_INT}, |
77a79592 | 43 | {"standby-timeout", 'S', 0, N_("Set standby timeout\n" |
44 | "(0=off, 1=5s, 2=10s, ..., 240=20m, 241=30m, ...)."), | |
9ff516f3 | 45 | 0, ARG_TYPE_INT}, |
77a79592 | 46 | {"standby", 'y', 0, N_("Set drive to standby mode."), 0, ARG_TYPE_NONE}, |
47 | {"sleep", 'Y', 0, N_("Set drive to sleep mode."), 0, ARG_TYPE_NONE}, | |
48 | {"identify", 'i', 0, N_("Print drive identity and settings."), | |
9ff516f3 | 49 | 0, ARG_TYPE_NONE}, |
9c4b5c13 | 50 | {"dumpid", 'I', 0, N_("Show raw contents of ATA IDENTIFY sector."), |
9ff516f3 | 51 | 0, ARG_TYPE_NONE}, |
77a79592 | 52 | {"smart", -1, 0, N_("Disable/enable SMART (0/1)."), 0, ARG_TYPE_INT}, |
53 | {"quiet", 'q', 0, N_("Do not print messages."), 0, ARG_TYPE_NONE}, | |
9ff516f3 | 54 | {0, 0, 0, 0, 0, 0} |
55 | }; | |
56 | ||
57 | enum grub_ata_smart_commands | |
58 | { | |
59 | GRUB_ATA_FEAT_SMART_ENABLE = 0xd8, | |
60 | GRUB_ATA_FEAT_SMART_DISABLE = 0xd9, | |
61 | GRUB_ATA_FEAT_SMART_STATUS = 0xda, | |
62 | }; | |
63 | ||
64 | static int quiet = 0; | |
65 | ||
66 | static grub_err_t | |
c7336d91 | 67 | grub_hdparm_do_ata_cmd (grub_ata_t ata, grub_uint8_t cmd, |
9ff516f3 | 68 | grub_uint8_t features, grub_uint8_t sectors, |
69 | void * buffer, int size) | |
70 | { | |
71 | struct grub_disk_ata_pass_through_parms apt; | |
72 | grub_memset (&apt, 0, sizeof (apt)); | |
73 | ||
c7336d91 VS |
74 | apt.taskfile.cmd = cmd; |
75 | apt.taskfile.features = features; | |
76 | apt.taskfile.sectors = sectors; | |
d9675dbe VS |
77 | apt.taskfile.disk = 0xE0; |
78 | ||
9ff516f3 | 79 | apt.buffer = buffer; |
80 | apt.size = size; | |
81 | ||
0670a975 | 82 | if (ata->dev->readwrite (ata, &apt, 0)) |
9ff516f3 | 83 | return grub_errno; |
84 | ||
85 | return GRUB_ERR_NONE; | |
86 | } | |
87 | ||
88 | static int | |
c7336d91 | 89 | grub_hdparm_do_check_powermode_cmd (grub_ata_t ata) |
9ff516f3 | 90 | { |
91 | struct grub_disk_ata_pass_through_parms apt; | |
92 | grub_memset (&apt, 0, sizeof (apt)); | |
93 | ||
c7336d91 | 94 | apt.taskfile.cmd = GRUB_ATA_CMD_CHECK_POWER_MODE; |
d9675dbe | 95 | apt.taskfile.disk = 0xE0; |
9ff516f3 | 96 | |
0670a975 | 97 | if (ata->dev->readwrite (ata, &apt, 0)) |
9ff516f3 | 98 | return -1; |
99 | ||
c7336d91 | 100 | return apt.taskfile.sectors; |
9ff516f3 | 101 | } |
102 | ||
103 | static int | |
c7336d91 | 104 | grub_hdparm_do_smart_cmd (grub_ata_t ata, grub_uint8_t features) |
9ff516f3 | 105 | { |
106 | struct grub_disk_ata_pass_through_parms apt; | |
107 | grub_memset (&apt, 0, sizeof (apt)); | |
108 | ||
c7336d91 VS |
109 | apt.taskfile.cmd = GRUB_ATA_CMD_SMART; |
110 | apt.taskfile.features = features; | |
111 | apt.taskfile.lba_mid = 0x4f; | |
112 | apt.taskfile.lba_high = 0xc2; | |
d9675dbe | 113 | apt.taskfile.disk = 0xE0; |
9ff516f3 | 114 | |
0670a975 | 115 | if (ata->dev->readwrite (ata, &apt, 0)) |
9ff516f3 | 116 | return -1; |
117 | ||
118 | if (features == GRUB_ATA_FEAT_SMART_STATUS) | |
119 | { | |
c7336d91 VS |
120 | if ( apt.taskfile.lba_mid == 0x4f |
121 | && apt.taskfile.lba_high == 0xc2) | |
9ff516f3 | 122 | return 0; /* Good SMART status. */ |
c7336d91 VS |
123 | else if ( apt.taskfile.lba_mid == 0xf4 |
124 | && apt.taskfile.lba_high == 0x2c) | |
9ff516f3 | 125 | return 1; /* Bad SMART status. */ |
126 | else | |
127 | return -1; | |
128 | } | |
129 | return 0; | |
130 | } | |
131 | ||
132 | static grub_err_t | |
133 | grub_hdparm_simple_cmd (const char * msg, | |
c7336d91 | 134 | grub_ata_t ata, grub_uint8_t cmd) |
9ff516f3 | 135 | { |
136 | if (! quiet && msg) | |
137 | grub_printf ("%s", msg); | |
138 | ||
c7336d91 | 139 | grub_err_t err = grub_hdparm_do_ata_cmd (ata, cmd, 0, 0, NULL, 0); |
9ff516f3 | 140 | |
141 | if (! quiet && msg) | |
142 | grub_printf ("%s\n", ! err ? "" : ": not supported"); | |
143 | return err; | |
144 | } | |
145 | ||
146 | static grub_err_t | |
147 | grub_hdparm_set_val_cmd (const char * msg, int val, | |
c7336d91 | 148 | grub_ata_t ata, grub_uint8_t cmd, |
9ff516f3 | 149 | grub_uint8_t features, grub_uint8_t sectors) |
150 | { | |
151 | if (! quiet && msg && *msg) | |
152 | { | |
153 | if (val >= 0) | |
154 | grub_printf ("Set %s to %d", msg, val); | |
155 | else | |
156 | grub_printf ("Disable %s", msg); | |
157 | } | |
158 | ||
c7336d91 | 159 | grub_err_t err = grub_hdparm_do_ata_cmd (ata, cmd, features, sectors, |
9ff516f3 | 160 | NULL, 0); |
161 | ||
162 | if (! quiet && msg) | |
163 | grub_printf ("%s\n", ! err ? "" : ": not supported"); | |
164 | return err; | |
165 | } | |
166 | ||
167 | static const char * | |
61664420 | 168 | le16_to_char (grub_uint16_t *dest, const grub_uint16_t * src16, unsigned bytes) |
9ff516f3 | 169 | { |
9ff516f3 | 170 | unsigned i; |
171 | for (i = 0; i < bytes / 2; i++) | |
b7b4ab33 | 172 | dest[i] = grub_swap_bytes16 (src16[i]); |
61664420 VS |
173 | dest[i] = 0; |
174 | return (char *) dest; | |
9ff516f3 | 175 | } |
176 | ||
177 | static void | |
61664420 | 178 | grub_hdparm_print_identify (const grub_uint16_t * idw) |
9ff516f3 | 179 | { |
9ff516f3 | 180 | /* Print identity strings. */ |
61664420 | 181 | grub_uint16_t tmp[21]; |
9ff516f3 | 182 | grub_printf ("Model: \"%.40s\"\n", le16_to_char (tmp, &idw[27], 40)); |
183 | grub_printf ("Firmware: \"%.8s\"\n", le16_to_char (tmp, &idw[23], 8)); | |
184 | grub_printf ("Serial: \"%.20s\"\n", le16_to_char (tmp, &idw[10], 20)); | |
185 | ||
186 | /* Print AAM, APM and SMART settings. */ | |
187 | grub_uint16_t features1 = grub_le_to_cpu16 (idw[82]); | |
188 | grub_uint16_t features2 = grub_le_to_cpu16 (idw[83]); | |
189 | grub_uint16_t enabled1 = grub_le_to_cpu16 (idw[85]); | |
190 | grub_uint16_t enabled2 = grub_le_to_cpu16 (idw[86]); | |
191 | ||
192 | grub_printf ("Automatic Acoustic Management: "); | |
193 | if (features2 & 0x0200) | |
194 | { | |
195 | if (enabled2 & 0x0200) | |
196 | { | |
197 | grub_uint16_t aam = grub_le_to_cpu16 (idw[94]); | |
198 | grub_printf ("%u (128=quiet, ..., 254=fast, recommended=%u)\n", | |
199 | aam & 0xff, (aam >> 8) & 0xff); | |
200 | } | |
201 | else | |
202 | grub_printf ("disabled\n"); | |
203 | } | |
204 | else | |
205 | grub_printf ("not supported\n"); | |
206 | ||
207 | grub_printf ("Advanced Power Management: "); | |
208 | if (features2 & 0x0008) | |
209 | { | |
210 | if (enabled2 & 0x0008) | |
211 | grub_printf ("%u (1=low, ..., 254=high)\n", | |
212 | grub_le_to_cpu16 (idw[91]) & 0xff); | |
213 | else | |
214 | grub_printf ("disabled\n"); | |
215 | } | |
216 | else | |
217 | grub_printf ("not supported\n"); | |
218 | ||
219 | grub_printf ("SMART Feature Set: "); | |
220 | if (features1 & 0x0001) | |
221 | grub_printf ("%sabled\n", (enabled1 & 0x0001 ? "en" : "dis")); | |
222 | else | |
223 | grub_printf ("not supported\n"); | |
224 | ||
225 | /* Print security settings. */ | |
226 | grub_uint16_t security = grub_le_to_cpu16 (idw[128]); | |
227 | ||
228 | grub_printf ("ATA Security: "); | |
229 | if (security & 0x0001) | |
230 | grub_printf ("%s, %s, %s, %s\n", | |
231 | (security & 0x0002 ? "ENABLED" : "disabled"), | |
232 | (security & 0x0004 ? "**LOCKED**" : "not locked"), | |
233 | (security & 0x0008 ? "frozen" : "NOT FROZEN"), | |
234 | (security & 0x0010 ? "COUNT EXPIRED" : "count not expired")); | |
235 | else | |
236 | grub_printf ("not supported\n"); | |
237 | } | |
238 | ||
239 | static void | |
240 | grub_hdparm_print_standby_tout (int timeout) | |
241 | { | |
242 | if (timeout == 0) | |
243 | grub_printf ("off"); | |
244 | else if (timeout <= 252 || timeout == 255) | |
245 | { | |
246 | int h = 0, m = 0 , s = 0; | |
247 | if (timeout == 255) | |
248 | { | |
249 | m = 21; | |
250 | s = 15; | |
251 | } | |
252 | else if (timeout == 252) | |
253 | m = 21; | |
254 | else if (timeout <= 240) | |
255 | { | |
256 | s = timeout * 5; | |
257 | m = s / 60; | |
258 | s %= 60; | |
259 | } | |
260 | else | |
261 | { | |
262 | m = (timeout - 240) * 30; | |
263 | h = m / 60; | |
264 | m %= 60; | |
265 | } | |
266 | grub_printf ("%02d:%02d:%02d", h, m, s); | |
267 | } | |
268 | else | |
269 | grub_printf ("invalid or vendor-specific"); | |
270 | } | |
271 | ||
272 | static int get_int_arg (const struct grub_arg_list *state) | |
273 | { | |
274 | return (state->set ? (int)grub_strtoul (state->arg, 0, 0) : -1); | |
275 | } | |
276 | ||
9ff516f3 | 277 | static grub_err_t |
66747894 | 278 | grub_cmd_hdparm (grub_extcmd_context_t ctxt, int argc, char **args) |
9ff516f3 | 279 | { |
28be0e94 | 280 | struct grub_arg_list *state = ctxt->state; |
c7336d91 | 281 | struct grub_ata *ata; |
66747894 | 282 | const char *diskname; |
b1b797cb | 283 | |
9ff516f3 | 284 | /* Check command line. */ |
285 | if (argc != 1) | |
66747894 | 286 | return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); |
9ff516f3 | 287 | |
66747894 VS |
288 | if (args[0][0] == '(') |
289 | { | |
290 | grub_size_t len = grub_strlen (args[0]); | |
291 | if (args[0][len - 1] == ')') | |
292 | args[0][len - 1] = 0; | |
293 | diskname = &args[0][1]; | |
294 | } | |
295 | else | |
296 | diskname = &args[0][0]; | |
9ff516f3 | 297 | |
9ff516f3 | 298 | int i = 0; |
299 | int apm = get_int_arg (&state[i++]); | |
300 | int power = state[i++].set; | |
301 | int sec_freeze = state[i++].set; | |
302 | int health = state[i++].set; | |
303 | int aam = get_int_arg (&state[i++]); | |
304 | int standby_tout = get_int_arg (&state[i++]); | |
305 | int standby_now = state[i++].set; | |
306 | int sleep_now = state[i++].set; | |
307 | int ident = state[i++].set; | |
308 | int dumpid = state[i++].set; | |
309 | int enable_smart = get_int_arg (&state[i++]); | |
310 | quiet = state[i++].set; | |
311 | ||
312 | /* Open disk. */ | |
66747894 | 313 | grub_disk_t disk = grub_disk_open (diskname); |
9ff516f3 | 314 | if (! disk) |
315 | return grub_errno; | |
316 | ||
c7336d91 VS |
317 | switch (disk->dev->id) |
318 | { | |
319 | case GRUB_DISK_DEVICE_ATA_ID: | |
320 | ata = disk->data; | |
321 | break; | |
322 | case GRUB_DISK_DEVICE_SCSI_ID: | |
323 | if (((disk->id >> GRUB_SCSI_ID_SUBSYSTEM_SHIFT) & 0xFF) | |
324 | == GRUB_SCSI_SUBSYSTEM_PATA | |
325 | || (((disk->id >> GRUB_SCSI_ID_SUBSYSTEM_SHIFT) & 0xFF) | |
326 | == GRUB_SCSI_SUBSYSTEM_AHCI)) | |
327 | { | |
328 | ata = ((struct grub_scsi *) disk->data)->data; | |
329 | break; | |
330 | } | |
331 | default: | |
332 | return grub_error (GRUB_ERR_IO, "not an ATA device"); | |
333 | } | |
334 | ||
335 | ||
9ff516f3 | 336 | /* Change settings. */ |
337 | if (aam >= 0) | |
338 | grub_hdparm_set_val_cmd ("Automatic Acoustic Management", (aam ? aam : -1), | |
c7336d91 VS |
339 | ata, GRUB_ATA_CMD_SET_FEATURES, |
340 | (aam ? 0x42 : 0xc2), aam); | |
9ff516f3 | 341 | |
342 | if (apm >= 0) | |
343 | grub_hdparm_set_val_cmd ("Advanced Power Management", | |
c7336d91 VS |
344 | (apm != 255 ? apm : -1), ata, |
345 | GRUB_ATA_CMD_SET_FEATURES, | |
346 | (apm != 255 ? 0x05 : 0x85), | |
347 | (apm != 255 ? apm : 0)); | |
9ff516f3 | 348 | |
349 | if (standby_tout >= 0) | |
350 | { | |
351 | if (! quiet) | |
352 | { | |
353 | grub_printf ("Set standby timeout to %d (", standby_tout); | |
354 | grub_hdparm_print_standby_tout (standby_tout); | |
355 | grub_printf (")"); | |
356 | } | |
357 | /* The IDLE cmd sets disk to idle mode and configures standby timer. */ | |
c7336d91 | 358 | grub_hdparm_set_val_cmd ("", -1, ata, GRUB_ATA_CMD_IDLE, 0, standby_tout); |
9ff516f3 | 359 | } |
360 | ||
361 | if (enable_smart >= 0) | |
362 | { | |
363 | if (! quiet) | |
364 | grub_printf ("%sable SMART operations", (enable_smart ? "En" : "Dis")); | |
c7336d91 | 365 | int err = grub_hdparm_do_smart_cmd (ata, (enable_smart ? |
9ff516f3 | 366 | GRUB_ATA_FEAT_SMART_ENABLE : GRUB_ATA_FEAT_SMART_DISABLE)); |
367 | if (! quiet) | |
368 | grub_printf ("%s\n", err ? ": not supported" : ""); | |
369 | } | |
370 | ||
371 | if (sec_freeze) | |
c7336d91 | 372 | grub_hdparm_simple_cmd ("Freeze security settings", ata, |
9ff516f3 | 373 | GRUB_ATA_CMD_SECURITY_FREEZE_LOCK); |
374 | ||
375 | /* Print/dump IDENTIFY. */ | |
376 | if (ident || dumpid) | |
377 | { | |
61664420 | 378 | grub_uint16_t buf[GRUB_DISK_SECTOR_SIZE / 2]; |
c7336d91 | 379 | if (grub_hdparm_do_ata_cmd (ata, GRUB_ATA_CMD_IDENTIFY_DEVICE, |
9ff516f3 | 380 | 0, 0, buf, sizeof (buf))) |
381 | grub_printf ("Cannot read ATA IDENTIFY data\n"); | |
382 | else | |
383 | { | |
384 | if (ident) | |
385 | grub_hdparm_print_identify (buf); | |
386 | if (dumpid) | |
61664420 | 387 | hexdump (0, (char *) buf, sizeof (buf)); |
9ff516f3 | 388 | } |
389 | } | |
390 | ||
391 | /* Check power mode. */ | |
392 | if (power) | |
393 | { | |
394 | grub_printf ("Disk power mode is: "); | |
c7336d91 | 395 | int mode = grub_hdparm_do_check_powermode_cmd (ata); |
9ff516f3 | 396 | if (mode < 0) |
397 | grub_printf ("unknown\n"); | |
398 | else | |
399 | grub_printf ("%s (0x%02x)\n", | |
400 | (mode == 0xff ? "active/idle" : | |
401 | mode == 0x80 ? "idle" : | |
402 | mode == 0x00 ? "standby" : "unknown"), mode); | |
403 | } | |
404 | ||
405 | /* Check health. */ | |
406 | int status = 0; | |
407 | if (health) | |
408 | { | |
409 | if (! quiet) | |
410 | grub_printf ("SMART status is: "); | |
c7336d91 | 411 | int err = grub_hdparm_do_smart_cmd (ata, GRUB_ATA_FEAT_SMART_STATUS); |
9ff516f3 | 412 | if (! quiet) |
413 | grub_printf ("%s\n", (err < 0 ? "unknown" : | |
414 | err == 0 ? "OK" : "*BAD*")); | |
415 | status = (err > 0); | |
416 | } | |
417 | ||
418 | /* Change power mode. */ | |
419 | if (standby_now) | |
c7336d91 | 420 | grub_hdparm_simple_cmd ("Set disk to standby mode", ata, |
9ff516f3 | 421 | GRUB_ATA_CMD_STANDBY_IMMEDIATE); |
422 | ||
423 | if (sleep_now) | |
c7336d91 | 424 | grub_hdparm_simple_cmd ("Set disk to sleep mode", ata, |
9ff516f3 | 425 | GRUB_ATA_CMD_SLEEP); |
426 | ||
427 | grub_disk_close (disk); | |
428 | ||
429 | grub_errno = GRUB_ERR_NONE; | |
430 | return status; | |
431 | } | |
432 | ||
b1b797cb | 433 | static grub_extcmd_t cmd; |
9ff516f3 | 434 | |
435 | GRUB_MOD_INIT(hdparm) | |
436 | { | |
ed80f7d5 | 437 | cmd = grub_register_extcmd ("hdparm", grub_cmd_hdparm, 0, |
77a79592 | 438 | N_("[OPTIONS] DISK"), |
439 | N_("Get/set ATA disk parameters."), options); | |
9ff516f3 | 440 | } |
441 | ||
442 | GRUB_MOD_FINI(hdparm) | |
443 | { | |
b1b797cb | 444 | grub_unregister_extcmd (cmd); |
9ff516f3 | 445 | } |