diff options
Diffstat (limited to 'util')
-rw-r--r-- | util/bucts/Makefile | 21 | ||||
-rw-r--r-- | util/bucts/bucts.c | 440 | ||||
-rw-r--r-- | util/bucts/readme.md | 52 |
3 files changed, 513 insertions, 0 deletions
diff --git a/util/bucts/Makefile b/util/bucts/Makefile new file mode 100644 index 0000000000..3bc73ee3d6 --- /dev/null +++ b/util/bucts/Makefile @@ -0,0 +1,21 @@ +CC:=gcc +OBJ:=bucts.o +VERSION:=$(shell git describe) +CFLAGS+=-Wall + +ifeq ($(shell uname), FreeBSD) + CFLAGS += -I/usr/local/include + LDFLAGS += -L/usr/local/lib +endif + +all: bucts + +bucts: $(OBJ) + $(CC) -o $@ $(OBJ) $(LDFLAGS) -lpci + +%.o: %.c + $(CC) $(CFLAGS) -DVERSION='"$(VERSION)"' -c $< + +.PHONY: clean +clean: + rm -f bucts $(OBJ) diff --git a/util/bucts/bucts.c b/util/bucts/bucts.c new file mode 100644 index 0000000000..a4bca3039f --- /dev/null +++ b/util/bucts/bucts.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2011 Peter Stuge <peter@stuge.se> + * Copyright (C) 2017-2018 Arthur Heymans <arthur@aheymans.xyz> + * + * This program 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; version 2 of the License. + * + * This program 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. + */ + +#include <getopt.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/mman.h> +#if defined(__GLIBC__) +#include <sys/io.h> +#endif +#include <pci/pci.h> + +#if defined(__sun) && (defined(__i386) || defined(__amd64)) +#define MEM_DEV "/dev/xsvc" +#else +#define MEM_DEV "/dev/mem" +#endif + +#define BUC_OFFSET 0x3414 +#define GCS_OFFSET 0x3410 + +#define SPIBAR 0x3800 +#define FDOC 0xb0 +#define FDOD 0xb4 + +enum { + DESCRIPTOR_MAP_SECTION = 0, + COMPONENT_SECTION, + REGION_SECTION, + MASTER_SECTION, + PCHSTRAPS_SECTION, +}; + +enum { + BUCTS_UNSET = 0, + BUCTS_SET +}; + +static int fd_mem = -1; + +static void print_usage(const char *name) +{ + printf("usage: %s [-h?]\n", name); + printf("\n" + " -s | --set: Set the top swap bit to invert the bootblock region decoding\n" + " -u | --unset: Unset the top swap bit to have regular bootblock region decoding\n" + " -p | --print: print the current status of the hardware\n" + " -h | --help: print this help\n" + "\n"); +} + +static void *sys_physmap(unsigned long phys_addr, size_t len) +{ + void *virt_addr = + mmap(0, len, PROT_WRITE | PROT_READ, MAP_SHARED, fd_mem, + (off_t) phys_addr); + return virt_addr == MAP_FAILED ? NULL : virt_addr; +} + +static void physunmap(void *virt_addr, size_t len) +{ + if (!len) { + printf("Not unmapping zero size at %p\n", virt_addr); + return; + } + munmap(virt_addr, len); +} + +static void *physmap(const char *descr, unsigned long phys_addr, size_t len) +{ + void *virt_addr; + + if (!descr) + descr = "memory"; + + if (!len) { + printf("Not mapping %s, zero size at 0x%08lx.\n", descr, + phys_addr); + return NULL; + } + + if ((getpagesize() - 1) & len) + fprintf(stderr, "Unaligned size 0x%lx for %s at 0x%08lx!\n", + (unsigned long)len, descr, phys_addr); + + if ((getpagesize() - 1) & phys_addr) + fprintf(stderr, "Unaligned address 0x%08lx for %s!\n", + phys_addr, descr); + + virt_addr = sys_physmap(phys_addr, len); + if (!virt_addr) { + fprintf(stderr, "Error accessing 0x%lx bytes %s at 0x%08lx!\n", + (unsigned long)len, descr, phys_addr); + perror("mmap(" MEM_DEV ")"); + if (errno == EINVAL) { + fprintf(stderr, "\n"); + fprintf(stderr, + "In Linux this error can be caused by the CONFIG_NONPROMISC_DEVMEM (<2.6.27),\n"); + fprintf(stderr, + "CONFIG_STRICT_DEVMEM (>=2.6.27) and CONFIG_X86_PAT kernel options.\n"); + fprintf(stderr, + "Please check if either is enabled in your kernel before reporting a failure.\n"); + fprintf(stderr, + "You can override CONFIG_X86_PAT at boot with the nopat kernel parameter but\n"); + fprintf(stderr, + "disabling the other option unfortunately requires a kernel recompile. Sorry!\n"); + } + } + + return virt_addr; +} + +static int get_platform_info(struct pci_dev *sb, int *has_var_bb, + uint32_t *rcba_addr) +{ + *has_var_bb = 0; + switch (sb->device_id) { + case 0x1c44: /* Z68 */ + case 0x1c46: /* P67 */ + case 0x1c47: /* UM67 */ + case 0x1c49: /* HM68 */ + case 0x1c4a: /* H67 */ + case 0x1c4b: /* HM67 */ + case 0x1c4c: /* Q65 */ + case 0x1c4d: /* QS67 */ + case 0x1c4e: /* Q67 */ + case 0x1c4f: /* QM67 */ + case 0x1c50: /* B65 */ + case 0x1c52: /* C202 */ + case 0x1c54: /* C204 */ + case 0x1c56: /* C026 */ + case 0x1c5c: /* H61 */ + case 0x1d40: /* C60x/X79 */ + case 0x1c41: /* C60x/X79 */ + case 0x1e44: /* Z77 */ + case 0x1e46: /* Z75 */ + case 0x1e47: /* Q77 */ + case 0x1e48: /* Q75 */ + case 0x1e49: /* B75 */ + case 0x1e4a: /* H77 */ + case 0x1e53: /* C216 */ + case 0x1e55: /* QM77 */ + case 0x1e56: /* QS77 */ + case 0x1e57: /* HM77 */ + case 0x1e58: /* UM77 */ + case 0x1e59: /* HM76 */ + case 0x1e5d: /* HM75 */ + case 0x1e5e: /* HM70 */ + case 0x1e5f: /* NM70 */ + case 0x3b02: /* P55 */ + case 0x3b03: /* PM55 */ + case 0x3b06: /* H55 */ + case 0x3b07: /* QM55 */ + case 0x3b08: /* H57 */ + case 0x3b09: /* HM55 */ + case 0x3b0a: /* Q57 */ + case 0x3b0b: /* HM57 */ + case 0x3b0d: /* PCH 3400 Mobile SFF */ + case 0x3b0e: /* B55 */ + case 0x3b0f: /* QS57 */ + case 0x3b12: /* PCH 3400 */ + case 0x3b14: /* PCH 3420 */ + case 0x3b16: /* PCH 3450 */ + case 0x3b1e: /* B55 */ + case 0x8c40: /* Lynx Point */ + case 0x8c41: /* Lynx Point Mobile Eng. Sample */ + case 0x8c42: /* Lynx Point Desktop Eng. Sample */ + case 0x8c43: /* Lynx Point */ + case 0x8c44: /* Z87 */ + case 0x8c45: /* Lynx Point */ + *has_var_bb = 1; + /* fallthrough */ + case 0x2640: /* ICH6/ICH6R */ + case 0x2641: /* ICH6-M */ + case 0x2642: /* ICH6W/ICH6RW */ + case 0x2670: /* 631xESB/632xESB/3100 */ + case 0x27b0: /* ICH7DH */ + case 0x27b8: /* ICH7/ICH7R */ + case 0x27b9: /* ICH7M */ + case 0x27bd: /* ICH7MDH */ + case 0x27bc: /* NM10 */ + case 0x2810: /* ICH8/ICH8R */ + case 0x2811: /* ICH8M-E */ + case 0x2812: /* ICH8DH */ + case 0x2814: /* ICH8DO */ + case 0x2815: /* ICH8M */ + case 0x2910: /* ICH9 Eng. Sample */ + case 0x2912: /* ICH9DH */ + case 0x2914: /* ICH9DO */ + case 0x2916: /* ICH9R */ + case 0x2917: /* ICH9M-E */ + case 0x2918: /* ICH9 */ + case 0x2919: /* ICH9M */ + case 0x3a10: /* ICH10R Eng. Sample */ + case 0x3a14: /* ICH10D0 */ + case 0x3a16: /* ICH10R */ + case 0x3a18: /* ICH10 */ + case 0x3a1a: /* ICH10D */ + case 0x3a1e: /* ICH10 Eng. Sample*/ + case 0x3b00: /* PCH 3400 Desktop */ + case 0x3b01: /* PCH 3400 Mobile */ + *rcba_addr = pci_read_long(sb, 0xf0) & ~1; + return 0; + default: + fprintf(stderr, "Unsupported LPC bridge. Sorry.\n"); + return 1; + } +} + +int print_status(uint32_t rcba_addr, int has_var_bb) +{ + int ret = 0; + volatile uint8_t *rcba; + int bild, ts, ts_size = 64 << 10; + rcba = physmap("RCBA", rcba_addr, 0x4000); + if (!rcba) + return 1; + + bild = rcba[GCS_OFFSET] & 1; + ts = rcba[BUC_OFFSET] & 1; + int spi_is_bootdevice = + ((*(uint16_t *)(rcba + GCS_OFFSET) >> 10) & 3) == 3; + + if (has_var_bb && spi_is_bootdevice) { + *(uint32_t *)(rcba + SPIBAR + FDOC) = + (PCHSTRAPS_SECTION << 12) | 0; + uint32_t pchstrap0 = *(uint32_t *)(rcba + SPIBAR + FDOD); + switch (pchstrap0 >> 29) { + case 0: + ts_size = 64 << 10; + break; + case 1: + ts_size = 128 << 10; + break; + case 2: + ts_size = 256 << 10; + break; + default: + printf("Unknown BIOS Boot-Block size (BBBS)\n"); + ret = 1; + goto out; + } + } + + printf("\nSystem status\n=============\n"); + printf("BILD (BIOS Interface Lock-Down) : %s\n", bild + ? "set, changing top swap not possible" : "unset, changing top swap is possible"); + printf("Top Swap : %s\n", ts ? "Enabled" : "Disabled"); + printf("BIOS Boot-block Size (BBDS) : %dK\n", ts_size >> 10); + if (ts) { + printf("Ranges swapped : 0x%llx-0x%08llx <-> 0x%08llx-0x%08llx", + ((4ULL << 30) - ts_size), + (4ULL << 30) - 1, + (4ULL << 30) - 2 * ts_size, + (4ULL << 30) - ts_size - 1); + } + +out: + physunmap((void *)rcba, 0x4000); + return ret; +} + +static int set_bucts(uint32_t rcba_addr, int bucts_state) +{ + int ret = 0; + volatile uint8_t *rcba; + int bild; + rcba = physmap("RCBA", rcba_addr, 0x4000); + if (!rcba) + return 1; + + bild = rcba[GCS_OFFSET] & 1; + if (bild) { + printf("BIOS Interface Lock-Down set, Changing Top Swap not possible.\n"); + ret = 1; + goto out; + } + + + rcba[BUC_OFFSET] = bucts_state & 1; + if ((rcba[BUC_OFFSET] & 1) != bucts_state) { + fprintf(stderr, "Hu?! setting the top swap bit failed!\n"); + ret = 1; + } else { + printf("Setting top swap bit succeeded.\n"); + ret = 0; + } + +out: + physunmap((void *)rcba, 0x4000); + return ret; +} + +int main(int argc, char *argv[], const char *envp[]) +{ + struct pci_access *pacc; + struct pci_dev *dev, *sb = NULL; +#if defined(__FreeBSD__) + int io_fd; +#endif + int opt, option_index = 0; + int print_state = 0; + int change_state = 0; + int bucts_state; + + static struct option long_options[] = { + {"unset", 0, 0, 'u'}, + {"set", 0, 0, 's'}, + {"print", 0, 0, 'p'}, + {"help", 0, 0, 'h'}, + {0, 0, 0, 0} + }; + + + printf("bucts utility version '" VERSION "'\n"); + + if (argv[1] == NULL) { + print_usage(argv[0]); + exit(1); + } + + while ((opt = getopt_long(argc, argv, "usph?:", + long_options, &option_index)) != EOF) { + switch (opt) { + case 'u': + if (change_state) { + printf("Invalid setting: multiple set/unset arguments\n"); + exit(1); + } + if (print_state) { + printf("Print and Set are mutually exclusive!\n"); + exit(1); + } + bucts_state = BUCTS_UNSET; + change_state = 1; + break; + case 's': + if (change_state) { + printf("Invalid setting: multiple set/unset arguments\n"); + exit(1); + } + if (print_state) { + printf("Print and Set are mutually exclusive!\n"); + exit(1); + } + bucts_state = BUCTS_SET; + change_state = 1; + break; + case 'p': + if (change_state) { + printf("Print and Set are mutually exclusive!\n"); + exit(1); + } + print_state = 1; + break; + case 'h': + print_usage(argv[0]); + exit(0); + case '?': + default: + print_usage(argv[0]); + exit(1); + } + } + + if (optind < argc) { + fprintf(stderr, "Error: Extra parameter found.\n"); + print_usage(argv[0]); + exit(1); + } + + +#if defined(__FreeBSD__) + if ((io_fd = open("/dev/io", O_RDWR)) < 0) { + perror("open(/dev/io)"); +#else + if (iopl(3)) { + perror("iopl"); +#endif + printf("You need to be root.\n"); + return 1; + } + + if ((fd_mem = open(MEM_DEV, O_RDWR | O_SYNC)) == -1) { + perror("Error: open(" MEM_DEV ")"); + return 1; + } + + pacc = pci_alloc(); + pci_init(pacc); + pci_scan_bus(pacc); + for (dev = pacc->devices; dev && !sb; dev = dev->next) { + pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_CLASS); + if (dev->vendor_id != 0x8086 || dev->device_class != 0x0601) + continue; + sb = dev; + } + if (!sb) { + fprintf(stderr, "Error: LPC bridge not found!\n"); + return 1; + } + + printf("Using LPC bridge %04x:%04x at %02x%02x:%02x.%02x\n", + sb->vendor_id, sb->device_id, sb->domain, sb->bus, sb->dev, + sb->func); + + int has_var_bb; + uint32_t rcba_addr; + + if (get_platform_info(sb, &has_var_bb, &rcba_addr)) + exit(1); + + if (print_state) + print_status(rcba_addr, has_var_bb); + if (change_state) { + set_bucts(rcba_addr, bucts_state); + print_status(rcba_addr, has_var_bb); + } + + close(fd_mem); + return 0; +} diff --git a/util/bucts/readme.md b/util/bucts/readme.md new file mode 100644 index 0000000000..1bf9beebc8 --- /dev/null +++ b/util/bucts/readme.md @@ -0,0 +1,52 @@ +# What is bucts? +Many Intel southbridges provide a mechanism called Back Up Control Top Swap (`BUC.TS`). +This functionality allows to have the southbridge fetch the reset vector or +the beginning of the bootblock at a 64K/128/256K offset from the usual top of flash. + +This can be useful in different ways: +- Have a backup bootblock in case of bootblock hacking; +- Some vendor BIOS only write protect their bootblock so this tool makes it + possible to circumvent this protection to allow flashing and booting coreboot. + +The BUC.TS status is stored in a nvram bit. To clear it one has to remove the RTC battery. + +# Operation Description +First compile bucts by running make: + + $ make + +Then you can view the current system settings: + + $ ./bucts --print + +To flip the decode address of the bootblock, by setting `BUC.TS` to 1: + + $ ./bucts --set + +To set the behavior the regular mapping, by setting `BUC.TS` to 0 + + $ ./bucts --unset + + +# Details +Example Bootblock size of 64KB (this is only configurable to be something else +like 128 or 256K on PCH Intel targets). + +<pre> + +-------------+ -> 0x200000 +-------------+ -> 0xFFFFFFFF +-------------+ -> 0xFFFFFFFF + | | | | | | + | bootblock_0 | | bootblock_0 | | bootblock_1 | + | | | | | | + +-------------+ -> 0x1F0000 +-------------+ -> 0xFFFF0000 +-------------+ -> 0xFFFF0000 + | | | | | | + | bootblock_1 | | bootblock_1 | | bootblock_0 | + | | | | | | + +-------------+ -> 0x1E0000 +-------------+ -> 0xFFFE0000 +-------------+ -> 0xFFFE0000 + | | | | | | + | | | | | | + Z Z Z Z Z Z + Z Z Z Z Z Z + | | | | | | + | | | | | | + FLASH (2M) Memory Map BUC.TS=0 Memory Map BUC.TS=1 +</pre> |