/* hdparm.c - command to get/set ATA disk parameters. */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 2009 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GRUB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GRUB. If not, see . */ #include #include #include #include #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); static const struct grub_arg_option options[] = { {"apm", 'B', 0, N_("Set Advanced Power Management\n" "(1=low, ..., 254=high, 255=off)."), 0, ARG_TYPE_INT}, {"power", 'C', 0, N_("Display power mode."), 0, ARG_TYPE_NONE}, {"security-freeze", 'F', 0, N_("Freeze ATA security settings until reset."), 0, ARG_TYPE_NONE}, {"health", 'H', 0, N_("Display SMART health status."), 0, ARG_TYPE_NONE}, {"aam", 'M', 0, N_("Set Automatic Acoustic Management\n" "(0=off, 128=quiet, ..., 254=fast)."), 0, ARG_TYPE_INT}, {"standby-timeout", 'S', 0, N_("Set standby timeout\n" "(0=off, 1=5s, 2=10s, ..., 240=20m, 241=30m, ...)."), 0, ARG_TYPE_INT}, {"standby", 'y', 0, N_("Set drive to standby mode."), 0, ARG_TYPE_NONE}, {"sleep", 'Y', 0, N_("Set drive to sleep mode."), 0, ARG_TYPE_NONE}, {"identify", 'i', 0, N_("Print drive identity and settings."), 0, ARG_TYPE_NONE}, {"dumpid", 'I', 0, N_("Show raw contents of ATA IDENTIFY sector."), 0, ARG_TYPE_NONE}, {"smart", -1, 0, N_("Disable/enable SMART (0/1)."), 0, ARG_TYPE_INT}, {"quiet", 'q', 0, N_("Do not print messages."), 0, ARG_TYPE_NONE}, {0, 0, 0, 0, 0, 0} }; enum grub_ata_smart_commands { GRUB_ATA_FEAT_SMART_ENABLE = 0xd8, GRUB_ATA_FEAT_SMART_DISABLE = 0xd9, GRUB_ATA_FEAT_SMART_STATUS = 0xda, }; static int quiet = 0; static grub_err_t grub_hdparm_do_ata_cmd (grub_ata_t ata, grub_uint8_t cmd, grub_uint8_t features, grub_uint8_t sectors, void * buffer, int size) { struct grub_disk_ata_pass_through_parms apt; grub_memset (&apt, 0, sizeof (apt)); apt.taskfile.cmd = cmd; apt.taskfile.features = features; apt.taskfile.sectors = sectors; apt.taskfile.disk = 0xE0; apt.buffer = buffer; apt.size = size; if (ata->dev->readwrite (ata, &apt, 0)) return grub_errno; return GRUB_ERR_NONE; } static int grub_hdparm_do_check_powermode_cmd (grub_ata_t ata) { struct grub_disk_ata_pass_through_parms apt; grub_memset (&apt, 0, sizeof (apt)); apt.taskfile.cmd = GRUB_ATA_CMD_CHECK_POWER_MODE; apt.taskfile.disk = 0xE0; if (ata->dev->readwrite (ata, &apt, 0)) return -1; return apt.taskfile.sectors; } static int grub_hdparm_do_smart_cmd (grub_ata_t ata, grub_uint8_t features) { struct grub_disk_ata_pass_through_parms apt; grub_memset (&apt, 0, sizeof (apt)); apt.taskfile.cmd = GRUB_ATA_CMD_SMART; apt.taskfile.features = features; apt.taskfile.lba_mid = 0x4f; apt.taskfile.lba_high = 0xc2; apt.taskfile.disk = 0xE0; if (ata->dev->readwrite (ata, &apt, 0)) return -1; if (features == GRUB_ATA_FEAT_SMART_STATUS) { if ( apt.taskfile.lba_mid == 0x4f && apt.taskfile.lba_high == 0xc2) return 0; /* Good SMART status. */ else if ( apt.taskfile.lba_mid == 0xf4 && apt.taskfile.lba_high == 0x2c) return 1; /* Bad SMART status. */ else return -1; } return 0; } static grub_err_t grub_hdparm_simple_cmd (const char * msg, grub_ata_t ata, grub_uint8_t cmd) { if (! quiet && msg) grub_printf ("%s", msg); grub_err_t err = grub_hdparm_do_ata_cmd (ata, cmd, 0, 0, NULL, 0); if (! quiet && msg) grub_printf ("%s\n", ! err ? "" : ": not supported"); return err; } static grub_err_t grub_hdparm_set_val_cmd (const char * msg, int val, grub_ata_t ata, grub_uint8_t cmd, grub_uint8_t features, grub_uint8_t sectors) { if (! quiet && msg && *msg) { if (val >= 0) grub_printf ("Set %s to %d", msg, val); else grub_printf ("Disable %s", msg); } grub_err_t err = grub_hdparm_do_ata_cmd (ata, cmd, features, sectors, NULL, 0); if (! quiet && msg) grub_printf ("%s\n", ! err ? "" : ": not supported"); return err; } static const char * le16_to_char (grub_uint16_t *dest, const grub_uint16_t * src16, unsigned bytes) { unsigned i; for (i = 0; i < bytes / 2; i++) dest[i] = grub_swap_bytes16 (src16[i]); dest[i] = 0; return (char *) dest; } static void grub_hdparm_print_identify (const grub_uint16_t * idw) { /* Print identity strings. */ grub_uint16_t tmp[21]; grub_printf ("Model: \"%.40s\"\n", le16_to_char (tmp, &idw[27], 40)); grub_printf ("Firmware: \"%.8s\"\n", le16_to_char (tmp, &idw[23], 8)); grub_printf ("Serial: \"%.20s\"\n", le16_to_char (tmp, &idw[10], 20)); /* Print AAM, APM and SMART settings. */ grub_uint16_t features1 = grub_le_to_cpu16 (idw[82]); grub_uint16_t features2 = grub_le_to_cpu16 (idw[83]); grub_uint16_t enabled1 = grub_le_to_cpu16 (idw[85]); grub_uint16_t enabled2 = grub_le_to_cpu16 (idw[86]); grub_printf ("Automatic Acoustic Management: "); if (features2 & 0x0200) { if (enabled2 & 0x0200) { grub_uint16_t aam = grub_le_to_cpu16 (idw[94]); grub_printf ("%u (128=quiet, ..., 254=fast, recommended=%u)\n", aam & 0xff, (aam >> 8) & 0xff); } else grub_printf ("disabled\n"); } else grub_printf ("not supported\n"); grub_printf ("Advanced Power Management: "); if (features2 & 0x0008) { if (enabled2 & 0x0008) grub_printf ("%u (1=low, ..., 254=high)\n", grub_le_to_cpu16 (idw[91]) & 0xff); else grub_printf ("disabled\n"); } else grub_printf ("not supported\n"); grub_printf ("SMART Feature Set: "); if (features1 & 0x0001) grub_printf ("%sabled\n", (enabled1 & 0x0001 ? "en" : "dis")); else grub_printf ("not supported\n"); /* Print security settings. */ grub_uint16_t security = grub_le_to_cpu16 (idw[128]); grub_printf ("ATA Security: "); if (security & 0x0001) grub_printf ("%s, %s, %s, %s\n", (security & 0x0002 ? "ENABLED" : "disabled"), (security & 0x0004 ? "**LOCKED**" : "not locked"), (security & 0x0008 ? "frozen" : "NOT FROZEN"), (security & 0x0010 ? "COUNT EXPIRED" : "count not expired")); else grub_printf ("not supported\n"); } static void grub_hdparm_print_standby_tout (int timeout) { if (timeout == 0) grub_printf ("off"); else if (timeout <= 252 || timeout == 255) { int h = 0, m = 0 , s = 0; if (timeout == 255) { m = 21; s = 15; } else if (timeout == 252) m = 21; else if (timeout <= 240) { s = timeout * 5; m = s / 60; s %= 60; } else { m = (timeout - 240) * 30; h = m / 60; m %= 60; } grub_printf ("%02d:%02d:%02d", h, m, s); } else grub_printf ("invalid or vendor-specific"); } static int get_int_arg (const struct grub_arg_list *state) { return (state->set ? (int)grub_strtoul (state->arg, 0, 0) : -1); } static grub_err_t grub_cmd_hdparm (grub_extcmd_context_t ctxt, int argc, char **args) { struct grub_arg_list *state = ctxt->state; struct grub_ata *ata; const char *diskname; /* Check command line. */ if (argc != 1) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); if (args[0][0] == '(') { grub_size_t len = grub_strlen (args[0]); if (args[0][len - 1] == ')') args[0][len - 1] = 0; diskname = &args[0][1]; } else diskname = &args[0][0]; int i = 0; int apm = get_int_arg (&state[i++]); int power = state[i++].set; int sec_freeze = state[i++].set; int health = state[i++].set; int aam = get_int_arg (&state[i++]); int standby_tout = get_int_arg (&state[i++]); int standby_now = state[i++].set; int sleep_now = state[i++].set; int ident = state[i++].set; int dumpid = state[i++].set; int enable_smart = get_int_arg (&state[i++]); quiet = state[i++].set; /* Open disk. */ grub_disk_t disk = grub_disk_open (diskname); if (! disk) return grub_errno; switch (disk->dev->id) { case GRUB_DISK_DEVICE_ATA_ID: ata = disk->data; break; case GRUB_DISK_DEVICE_SCSI_ID: if (((disk->id >> GRUB_SCSI_ID_SUBSYSTEM_SHIFT) & 0xFF) == GRUB_SCSI_SUBSYSTEM_PATA || (((disk->id >> GRUB_SCSI_ID_SUBSYSTEM_SHIFT) & 0xFF) == GRUB_SCSI_SUBSYSTEM_AHCI)) { ata = ((struct grub_scsi *) disk->data)->data; break; } default: return grub_error (GRUB_ERR_IO, "not an ATA device"); } /* Change settings. */ if (aam >= 0) grub_hdparm_set_val_cmd ("Automatic Acoustic Management", (aam ? aam : -1), ata, GRUB_ATA_CMD_SET_FEATURES, (aam ? 0x42 : 0xc2), aam); if (apm >= 0) grub_hdparm_set_val_cmd ("Advanced Power Management", (apm != 255 ? apm : -1), ata, GRUB_ATA_CMD_SET_FEATURES, (apm != 255 ? 0x05 : 0x85), (apm != 255 ? apm : 0)); if (standby_tout >= 0) { if (! quiet) { grub_printf ("Set standby timeout to %d (", standby_tout); grub_hdparm_print_standby_tout (standby_tout); grub_printf (")"); } /* The IDLE cmd sets disk to idle mode and configures standby timer. */ grub_hdparm_set_val_cmd ("", -1, ata, GRUB_ATA_CMD_IDLE, 0, standby_tout); } if (enable_smart >= 0) { if (! quiet) grub_printf ("%sable SMART operations", (enable_smart ? "En" : "Dis")); int err = grub_hdparm_do_smart_cmd (ata, (enable_smart ? GRUB_ATA_FEAT_SMART_ENABLE : GRUB_ATA_FEAT_SMART_DISABLE)); if (! quiet) grub_printf ("%s\n", err ? ": not supported" : ""); } if (sec_freeze) grub_hdparm_simple_cmd ("Freeze security settings", ata, GRUB_ATA_CMD_SECURITY_FREEZE_LOCK); /* Print/dump IDENTIFY. */ if (ident || dumpid) { grub_uint16_t buf[GRUB_DISK_SECTOR_SIZE / 2]; if (grub_hdparm_do_ata_cmd (ata, GRUB_ATA_CMD_IDENTIFY_DEVICE, 0, 0, buf, sizeof (buf))) grub_printf ("Cannot read ATA IDENTIFY data\n"); else { if (ident) grub_hdparm_print_identify (buf); if (dumpid) hexdump (0, (char *) buf, sizeof (buf)); } } /* Check power mode. */ if (power) { grub_printf ("Disk power mode is: "); int mode = grub_hdparm_do_check_powermode_cmd (ata); if (mode < 0) grub_printf ("unknown\n"); else grub_printf ("%s (0x%02x)\n", (mode == 0xff ? "active/idle" : mode == 0x80 ? "idle" : mode == 0x00 ? "standby" : "unknown"), mode); } /* Check health. */ int status = 0; if (health) { if (! quiet) grub_printf ("SMART status is: "); int err = grub_hdparm_do_smart_cmd (ata, GRUB_ATA_FEAT_SMART_STATUS); if (! quiet) grub_printf ("%s\n", (err < 0 ? "unknown" : err == 0 ? "OK" : "*BAD*")); status = (err > 0); } /* Change power mode. */ if (standby_now) grub_hdparm_simple_cmd ("Set disk to standby mode", ata, GRUB_ATA_CMD_STANDBY_IMMEDIATE); if (sleep_now) grub_hdparm_simple_cmd ("Set disk to sleep mode", ata, GRUB_ATA_CMD_SLEEP); grub_disk_close (disk); grub_errno = GRUB_ERR_NONE; return status; } static grub_extcmd_t cmd; GRUB_MOD_INIT(hdparm) { cmd = grub_register_extcmd ("hdparm", grub_cmd_hdparm, 0, N_("[OPTIONS] DISK"), N_("Get/set ATA disk parameters."), options); } GRUB_MOD_FINI(hdparm) { grub_unregister_extcmd (cmd); }