| /* |
| * 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); |
| } |
| |