blob: 949243847e9fd1c59d7abcdbc6c811c78f1ecf7a [file] [log] [blame]
/*
* Amiga NVRAM tool
*
* (C) Copyright 2009 Geert Uytterhoeven
*
* This file is subject to the terms and conditions of the GNU General Public
* License.
*/
#include <fcntl.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define NVRAM_FILE "/sys/devices/platform/rtc-rp5c01/nvram"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
static const char *program_name;
static const char *opt_nvram = NVRAM_FILE;
static _Bool opt_checksum;
static _Bool opt_quiet;
static char *opt_field;
struct nvram {
union {
uint32_t words[3];
uint8_t bytes[12];
struct {
uint32_t amiga;
uint32_t amix;
uint32_t shared;
} bits;
};
uint8_t checksum;
} __attribute__ ((packed));
static struct nvram nvram, backup;
static const struct nvram_field {
const char *name;
unsigned int addr;
unsigned int len;
} nvram_fields[] = {
{ "amiga", 0, 32 },
{ "amiga.amnesia", 0, 1 },
{ "amiga.scsi_timeout", 1, 1 },
{ "amiga.scsi_luns", 2, 1 },
{ "amix", 32, 32 },
{ "shared", 64, 32 },
{ "shared.amnesia", 64, 1 },
{ "shared.scsi_host_id", 65, 3 },
{ "shared.scsi_sync_xfer", 68, 1 },
{ "shared.scsi_fast_sync", 69, 1 },
{ "shared.scsi_tag_queues", 70, 1 },
{ "shared.ide_extra_wait_addr", 71, 1 },
};
static void msg(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
static void msg(const char *fmt, ...)
{
va_list ap;
if (!opt_quiet) {
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
}
static int nvram_read(void)
{
int file;
ssize_t n;
file = open(opt_nvram, O_RDONLY);
if (file < 0) {
perror("Cannot open NVRAM for reading");
return -1;
}
n = read(file, &nvram, sizeof(nvram));
if (n < 0) {
perror("Cannot read NVRAM");
goto failed;
}
if (n < sizeof(nvram.bytes)) {
fprintf(stderr, "Could read only %zu NVRAM bytes\n", n);
goto failed;
}
if (n < sizeof(nvram)) {
fprintf(stderr,
"Warning: Could not read checksum, assuming zero\n");
nvram.checksum = 0;
}
close(file);
memcpy(&backup, &nvram, sizeof(nvram));
return 0;
failed:
close(file);
return -1;
}
static int nvram_write(void)
{
int file;
ssize_t n;
if (!memcmp(&nvram, &backup, sizeof(nvram)))
return 0;
msg("Saving NVRAM\n");
file = open(opt_nvram, O_WRONLY);
if (file < 0) {
perror("Cannot open NVRAM for writing");
return -1;
}
n = write(file, &nvram, sizeof(nvram));
if (n < 0) {
perror("Cannot write NVRAM");
goto failed;
}
if (n < sizeof(nvram)) {
fprintf(stderr, "Could write only %zu NVRAM bytes\n", n);
goto failed;
}
close(file);
return 0;
failed:
close(file);
return -1;
}
static const uint8_t table[16] = {
0x00, 0x57, 0xae, 0xf9, 0x0b, 0x5c, 0xa5, 0xf2,
0x16, 0x41, 0xb8, 0x7f, 0x1d, 0x4a, 0xb3, 0xe4
};
static uint8_t checksum_nibble(uint8_t sum, uint8_t nibble)
{
uint8_t index = (sum >> 4) ^ nibble;
sum = (sum << 4) ^ table[index];
return sum;
}
static uint8_t nvram_checksum(void)
{
unsigned int i;
uint8_t sum = 0xff;
for (i = 0; i < sizeof(nvram.bytes); i++) {
sum = checksum_nibble(sum, nvram.bytes[i] >> 4);
sum = checksum_nibble(sum, nvram.bytes[i] & 0x0f);
}
return sum;
}
uint32_t be32_to_cpu(uint32_t x)
{
uint8_t bytes[4];
*(uint32_t *)bytes = x;
return bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3];
}
static uint32_t nvram_get_field(const struct nvram_field *field)
{
unsigned int word = field->addr / 32;
unsigned int offset = field->addr % 32;
unsigned int len = field->len;
uint32_t val = be32_to_cpu(nvram.words[word]);
if (len == 32)
return val;
val >>= 32 - offset - len;
val &= (1 << len) - 1;
return val;
}
static void nvram_set_field(const struct nvram_field *field, uint32_t val)
{
unsigned int word = field->addr / 32;
unsigned int offset = field->addr % 32;
unsigned int len = field->len;
uint32_t mask, shift, t;
if (len == 32) {
nvram.words[word] = be32_to_cpu(val);
return;
}
mask = (1 << len) - 1;
val &= mask;
shift = 32 - offset - len;
val <<= shift;
mask <<= shift;
t = be32_to_cpu(nvram.words[word]);
nvram.words[word] = be32_to_cpu((t & ~mask) | val);
}
static const struct nvram_field *nvram_find_field(const char *name)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(nvram_fields); i++)
if (!strcmp(nvram_fields[i].name, name))
return &nvram_fields[i];
return NULL;
}
static void nvram_dump(void)
{
unsigned int i;
unsigned int max_name_len = 0;
uint8_t sum;
for (i = 0; i < ARRAY_SIZE(nvram_fields); i++) {
size_t len = strlen(nvram_fields[i].name);
if (len > max_name_len)
max_name_len = len;
}
for (i = 0; i < ARRAY_SIZE(nvram_fields); i++) {
const struct nvram_field *field = &nvram_fields[i];
uint32_t val = nvram_get_field(field);
if (field->len == 32)
printf("%-*s = 0x%08x\n", max_name_len, field->name,
val);
else
printf("%-*s = %u\n", max_name_len, field->name, val);
}
printf("%-*s = 0x%02x ", max_name_len, "checksum", nvram.checksum);
sum = nvram_checksum();
if (sum == nvram.checksum)
puts("[correct]");
else
printf("[not correct, should be 0x%02x]\n", sum);
}
static void usage(void) __attribute__ ((noreturn));
static void usage(void)
{
fprintf(stderr,
"\n"
"%s: [options] [<field>[=<value>]]\n\n"
"Valid options are:\n"
" -c, --checksum Recalculate checksum\n"
" (auto-enabled when a field is modified)\n"
" -f, --file file Specify NVRAM file\n"
" (default: %s)\n"
" -h, --help Display this usage information\n"
" -q, --quiet Enable quiet mode\n"
"\n",
program_name, opt_nvram);
exit(1);
}
int main(int argc, char *argv[])
{
const char *slash = strrchr(argv[0], '/');
program_name = slash ? slash + 1 : argv[0];
int error;
while (argc > 1) {
if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
usage();
else if (!strcmp(argv[1], "-c") ||
!strcmp(argv[1], "--checksum")) {
opt_checksum = 1;
argv++;
argc--;
} else if (!strcmp(argv[1], "-f") ||
!strcmp(argv[1], "--file")) {
if (argc <= 2)
usage();
opt_nvram = argv[2];
argv += 2;
argc -= 2;
} else if (!strcmp(argv[1], "-q") ||
!strcmp(argv[1], "--quiet")) {
opt_quiet = 1;
argv++;
argc--;
} else if (!opt_field) {
opt_field = argv[1];
argv++;
argc--;
} else
usage();
}
error = nvram_read();
if (error)
exit(-1);
if (opt_field) {
uint32_t val;
char *equal = strchr(opt_field, '=');
if (equal)
*equal = '\0';
const struct nvram_field *field = nvram_find_field(opt_field);
if (!field) {
fprintf(stderr, "Cannot find NVRAM field %s\n",
opt_field);
exit(-1);
}
if (!equal) {
val = nvram_get_field(field);
if (field->len == 32)
printf("%s = 0x%08x\n", field->name, val);
else
printf("%s = %u\n", field->name, val);
} else {
val = strtoul(equal+1, NULL, 0);
nvram_set_field(field, val);
opt_checksum = 1;
}
} else if (!opt_quiet)
nvram_dump();
if (opt_checksum) {
uint8_t sum = nvram_checksum();
if (sum != nvram.checksum) {
msg("Correcting checksum\n");
nvram.checksum = sum;
}
}
error = nvram_write();
if (error)
exit(-1);
exit(0);
}