blob: 57a5bc8bce049b34a544261af19bd3465b83faf3 [file] [log] [blame]
// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// NOTE: this file is translated from reboot_mode.py
//
// A script to manipulate ChromeOS CMOS reboot Field.
//
// A few bits in a byte in CMOS (called RDB, short for 'Reboot Data Byte'
// hereafter) are dedicated to information exchange between Linux and BIOS.
//
// The CMOS contents are available through /dev/nvrom. The location of RDB is
// reported by BIOS through ACPI. The few bits in RDB operated on by this script
// are defined in the all_mode_fields dictionary below along with their
// functions.
//
// When invoked without parameters this script prints values of the fields. When
// invoked with any of the bit field names as parameter(s), the script sets the
// bit(s) as required.
//
// Additional parameters allow to specify alternative ACPI and NVRAM files for
// testing.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/param.h>
#include <stdint.h>
#include <errno.h>
#include <stdarg.h>
#include <getopt.h>
#include <string>
#include <algorithm>
#include <vector>
#include <map>
#define DEFAULT_ACPI_FILE "/sys/devices/platform/chromeos_acpi/CHNV"
#define DEFAULT_NVRAM_FILE "/dev/nvram"
///////////////////////////////////////////////////////////////////////
// Utilities for quick python-C translation
using std::string;
// Works like throwing an exception - directly exit here.
static void RebootModeError(const char *message, ...) {
va_list args;
va_start(args, message);
vfprintf(stderr, message, args);
fprintf(stderr, "\n");
va_end(args);
exit(1);
}
// return a string which is upper case of input parameter.
static string str_to_upper(const string str) {
string newstr = str;
std::transform(str.begin(), str.end(), newstr.begin(), toupper);
return newstr;
}
///////////////////////////////////////////////////////////////////////
typedef std::map<string, uint8_t> RDB_BitField;
struct RebootModeConfig {
// file path
string acpi_file;
string nvram_file;
// Offset of RDB in NVRAM
int rdb_offset;
// NVRAM contents read on startup
uint8_t rdb_value;
// A dictionary of fields to be updated as requested by the command line
// parameters
RDB_BitField fields_to_set;
// All bitfields in RDB this script provides access to. Field names prepended
// by `--' become this script's command line options
RDB_BitField all_mode_fields;
// write constructure here to ease python translation
RebootModeConfig() {
acpi_file = DEFAULT_ACPI_FILE;
nvram_file = DEFAULT_NVRAM_FILE;
rdb_offset = 0;
all_mode_fields["recovery"] = 0x80;
all_mode_fields["debug_reset"] = 0x40;
all_mode_fields["try_firmware_b"] = 0x20;
}
} cfg; // this is a special global object to ease python translation.
// Process bit field name command line parameter.
//
// Verifies that the value is in range (0 or 1) and adds the appropriate
// element to fields_to_set dictionary. Should the same field specified in the
// command line more than once, only the last value will be used.
//
// Raises:
// RebootModeError in case the parameter value is out of range.
void OptionHandler(const char *opt_str, const char *value_str) {
const char *key = opt_str;
int value = atoi(value_str);
if (!value && *value_str != '0')
RebootModeError("--%s needs numeric argument value", key);
if (value < 0 || value > 1)
RebootModeError("--%s should be either 0 or 1", key);
if (cfg.all_mode_fields.find(key) == cfg.all_mode_fields.end())
RebootModeError("INTERNAL ERROR: UNKNOWN FIELD: %s", key);
cfg.fields_to_set[key] = value;
}
void GetRDBOffset() {
const char *acpi_file = cfg.acpi_file.c_str();
FILE *f = fopen(acpi_file, "rb");
if (!f) {
perror(acpi_file);
RebootModeError("Cannot read acpi_file: %s", acpi_file);
}
char buffer[PATH_MAX]; // this should be large enough
if (fgets(buffer, sizeof(buffer), f) == NULL) {
perror(acpi_file);
RebootModeError("Trying to read %s but failed.", acpi_file);
}
cfg.rdb_offset = atoi(buffer);
if (!cfg.rdb_offset && *buffer != '0') { // consider this as error
RebootModeError("%s contents are corrupted", acpi_file);
}
}
void ReadNvram() {
FILE *f = fopen(cfg.nvram_file.c_str(), "rb");
if (!f) {
if (errno == 2)
RebootModeError("%s does not exist. Is nvram module installed?",
cfg.nvram_file.c_str());
if (errno == 13)
RebootModeError("Access to %s denied. Are you root?",
cfg.nvram_file.c_str());
// all other error
perror(cfg.nvram_file.c_str());
}
if (fseek(f, cfg.rdb_offset, SEEK_SET) == -1 ||
fread(&cfg.rdb_value, 1, 1, f) != 1) {
perror(cfg.nvram_file.c_str());
RebootModeError("Failed reading mode byte from %s", cfg.nvram_file.c_str());
}
fclose(f);
}
void PrintCurrentMode() {
printf("Current reboot mode settings:\n");
for (RDB_BitField::iterator i = cfg.all_mode_fields.begin();
i != cfg.all_mode_fields.end();
++i) {
printf("%-15s: %d\n", i->first.c_str(),
(cfg.rdb_value & i->second) ? 1 : 0);
}
}
void SetNewMode() {
uint8_t new_mode = 0;
uint8_t updated_bits_mask = 0;
for (RDB_BitField::iterator i = cfg.fields_to_set.begin();
i != cfg.fields_to_set.end();
++i) {
string opt = i->first;
uint8_t value = i->second;
assert(cfg.all_mode_fields.find(opt) != cfg.all_mode_fields.end());
uint8_t mask = cfg.all_mode_fields[opt];
if (value)
new_mode |= mask;
updated_bits_mask |= mask;
}
if ((cfg.rdb_value & updated_bits_mask) == new_mode) {
printf("No update required\n");
return;
}
FILE *f = fopen(cfg.nvram_file.c_str(), "rb+");
fseek(f, cfg.rdb_offset, SEEK_SET);
new_mode |= (cfg.rdb_value & ~updated_bits_mask);
if (fwrite(&new_mode, 1, 1, f) != 1) {
RebootModeError("Failed writing mode byte to %s", cfg.nvram_file.c_str());
}
fclose(f);
}
const char *__name__;
void usage_help_exit(int err) {
printf("Usage: %s [options]\n"
"\n"
"Options:\n"
" -h, --help\t\tshow this help message and exit\n"
" --acpi_file=ACPI_FILE\n"
" --nvram_file=NVRAM_FILE\n"
, __name__);
// list all possible fields
for (RDB_BitField::iterator i = cfg.all_mode_fields.begin();
i != cfg.all_mode_fields.end();
++i) {
printf(" --%s=%s\n",
i->first.c_str(),
str_to_upper(i->first).c_str());
}
exit(err);
}
int main(int argc, char *argv[]) {
__name__ = argv[0];
struct option optv = {0};
std::vector<struct option> longopts;
int max_field_index = 0;
optv.has_arg = 0;
optv.val = 0;
optv.name = "help"; // value = 0
longopts.push_back(optv);
optv.has_arg = 1;
optv.val++;
optv.name = "acpi_file"; // value = 1
longopts.push_back(optv);
optv.val++;
optv.name = "nvram_file"; // value = 2
longopts.push_back(optv);
// add all known RDB fields
for (RDB_BitField::iterator i = cfg.all_mode_fields.begin();
i != cfg.all_mode_fields.end();
++i) {
optv.name = i->first.c_str();
optv.val++;
longopts.push_back(optv);
}
max_field_index = optv.val;
// add a zero for closing options
optv.name = NULL;
optv.val = optv.has_arg = 0;
longopts.push_back(optv);
int optc;
while ((optc = getopt_long(argc, argv, "h", &longopts.front(), NULL)) != -1) {
switch (optc) {
case 1:
cfg.acpi_file = optarg;
break;
case 2:
cfg.nvram_file = optarg;
break;
default:
// RDB fields?
// printf("optc: %d %s %s\n", optc, longopts[optc].name, optarg);
if (optc > 0 && optc <= max_field_index)
OptionHandler(longopts[optc].name, optarg);
else
usage_help_exit(1);
break;
}
}
// currently no other non-dashed arguments allowed.
if (optind != argc)
usage_help_exit(1);
GetRDBOffset();
ReadNvram();
if (!cfg.fields_to_set.empty()) {
SetNewMode();
} else {
PrintCurrentMode();
}
return 0;
}