| /* Copyright (c) 2012 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. |
| */ |
| |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #if !defined(__FreeBSD__) |
| #include <linux/nvram.h> |
| #include <linux/version.h> |
| #endif |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/utsname.h> |
| #include <unistd.h> |
| |
| #include "crossystem_arch.h" |
| #include "crossystem.h" |
| #include "crossystem_vbnv.h" |
| #include "host_common.h" |
| #include "vboot_struct.h" |
| |
| /* ACPI constants from Chrome OS Main Processor Firmware Spec */ |
| /* Boot reasons from BINF.0, from early H2C firmware */ |
| /* Unknown */ |
| #define BINF0_UNKNOWN 0 |
| /* Normal boot to Chrome OS */ |
| #define BINF0_NORMAL 1 |
| /* Developer mode boot (developer mode warning displayed) */ |
| #define BINF0_DEVELOPER 2 |
| /* Recovery initiated by user, using recovery button */ |
| #define BINF0_RECOVERY_BUTTON 3 |
| /* Recovery initiated by user pressing a key at developer mode warning |
| * screen */ |
| #define BINF0_RECOVERY_DEV_SCREEN_KEY 4 |
| /* Recovery caused by BIOS failed signature check (neither rewritable |
| * firmware was valid) */ |
| #define BINF0_RECOVERY_RW_FW_BAD 5 |
| /* Recovery caused by no OS kernel detected */ |
| #define BINF0_RECOVERY_NO_OS 6 |
| /* Recovery caused by OS kernel failed signature check */ |
| #define BINF0_RECOVERY_BAD_OS 7 |
| /* Recovery initiated by OS */ |
| #define BINF0_RECOVERY_OS_INITIATED 8 |
| /* OS-initiated S3 diagnostic path (debug mode boot) */ |
| #define BINF0_S3_DIAGNOSTIC_PATH 9 |
| /* S3 resume failed */ |
| #define BINF0_S3_RESUME_FAILED 10 |
| /* Recovery caused by TPM error */ |
| #define BINF0_RECOVERY_TPM_ERROR 11 |
| /* CHSW bitflags */ |
| #define CHSW_RECOVERY_BOOT 0x00000002 |
| #define CHSW_RECOVERY_EC_BOOT 0x00000004 |
| #define CHSW_DEV_BOOT 0x00000020 |
| /* CMOS reboot field bitflags */ |
| #define CMOSRF_RECOVERY 0x80 |
| #define CMOSRF_DEBUG_RESET 0x40 |
| #define CMOSRF_TRY_B 0x20 |
| /* GPIO signal types */ |
| #define GPIO_SIGNAL_TYPE_RECOVERY 1 |
| #define GPIO_SIGNAL_TYPE_DEPRECATED_DEV 2 /* Deprecated; see chromium:942901 */ |
| #define GPIO_SIGNAL_TYPE_WP 3 |
| #define GPIO_SIGNAL_TYPE_PHASE_ENFORCEMENT 4 |
| |
| /* Base name for ACPI files */ |
| #define ACPI_BASE_PATH "/sys/devices/platform/chromeos_acpi" |
| /* Paths for frequently used ACPI files */ |
| #define ACPI_BINF_PATH ACPI_BASE_PATH "/BINF" |
| #define ACPI_CHNV_PATH ACPI_BASE_PATH "/CHNV" |
| #define ACPI_CHSW_PATH ACPI_BASE_PATH "/CHSW" |
| #define ACPI_GPIO_PATH ACPI_BASE_PATH "/GPIO" |
| #define ACPI_VBNV_PATH ACPI_BASE_PATH "/VBNV" |
| #define ACPI_VDAT_PATH ACPI_BASE_PATH "/VDAT" |
| |
| /* Base name for GPIO files */ |
| #define GPIO_BASE_PATH "/sys/class/gpio" |
| #define GPIO_EXPORT_PATH GPIO_BASE_PATH "/export" |
| |
| /* Filename for NVRAM file */ |
| #define NVRAM_PATH "/dev/nvram" |
| |
| /* Filename for legacy firmware update tries */ |
| #define NEED_FWUPDATE_PATH "/mnt/stateful_partition/.need_firmware_update" |
| |
| /* Filenames for PCI Vendor and Device IDs */ |
| #define PCI_VENDOR_ID_PATH "/sys/bus/pci/devices/0000:00:00.0/vendor" |
| #define PCI_DEVICE_ID_PATH "/sys/bus/pci/devices/0000:00:00.0/device" |
| |
| typedef struct { |
| unsigned int base; |
| unsigned int uid; |
| } Basemapping; |
| |
| static void VbFixCmosChecksum(FILE* file) |
| { |
| #if !defined(__FreeBSD__) |
| int fd = fileno(file); |
| ioctl(fd, NVRAM_SETCKS); |
| #endif |
| } |
| |
| |
| static int VbCmosRead(unsigned offs, size_t size, void *ptr) |
| { |
| size_t res; |
| FILE* f; |
| |
| f = fopen(NVRAM_PATH, "rb"); |
| if (!f) |
| return -1; |
| |
| if (0 != fseek(f, offs, SEEK_SET)) { |
| fclose(f); |
| return -1; |
| } |
| |
| res = fread(ptr, size, 1, f); |
| if (1 != res && errno == EIO && ferror(f)) { |
| VbFixCmosChecksum(f); |
| res = fread(ptr, size, 1, f); |
| } |
| |
| fclose(f); |
| return (1 == res) ? 0 : -1; |
| } |
| |
| |
| static int VbCmosWrite(unsigned offs, size_t size, const void *ptr) |
| { |
| size_t res; |
| FILE* f; |
| |
| f = fopen(NVRAM_PATH, "w+b"); |
| if (!f) |
| return -1; |
| |
| if (0 != fseek(f, offs, SEEK_SET)) { |
| fclose(f); |
| return -1; |
| } |
| |
| res = fwrite(ptr, size, 1, f); |
| if (1 != res && errno == EIO && ferror(f)) { |
| VbFixCmosChecksum(f); |
| res = fwrite(ptr, size, 1, f); |
| } |
| |
| fclose(f); |
| return (1 == res) ? 0 : -1; |
| } |
| |
| |
| int vb2_read_nv_storage(struct vb2_context *ctx) |
| { |
| unsigned offs, blksz; |
| unsigned expectsz = vb2_nv_get_size(ctx); |
| |
| /* Get the byte offset from VBNV */ |
| if (ReadFileInt(ACPI_VBNV_PATH ".0", &offs) < 0) |
| return -1; |
| if (ReadFileInt(ACPI_VBNV_PATH ".1", &blksz) < 0) |
| return -1; |
| if (expectsz > blksz) |
| return -1; /* NV storage block is too small */ |
| |
| if (0 != VbCmosRead(offs, expectsz, ctx->nvdata)) |
| return -1; |
| |
| return 0; |
| } |
| |
| |
| int vb2_write_nv_storage(struct vb2_context *ctx) |
| { |
| unsigned offs, blksz; |
| unsigned expectsz = vb2_nv_get_size(ctx); |
| |
| if (!(ctx->flags & VB2_CONTEXT_NVDATA_CHANGED)) |
| return 0; /* Nothing changed, so no need to write */ |
| |
| /* Get the byte offset from VBNV */ |
| if (ReadFileInt(ACPI_VBNV_PATH ".0", &offs) < 0) |
| return -1; |
| if (ReadFileInt(ACPI_VBNV_PATH ".1", &blksz) < 0) |
| return -1; |
| if (expectsz > blksz) |
| return -1; /* NV storage block is too small */ |
| |
| if (0 != VbCmosWrite(offs, expectsz, ctx->nvdata)) |
| return -1; |
| |
| /* Also attempt to write using flashrom if using vboot2 */ |
| VbSharedDataHeader *sh = VbSharedDataRead(); |
| if (sh) { |
| if (sh->flags & VBSD_BOOT_FIRMWARE_VBOOT2) |
| vb2_write_nv_storage_flashrom(ctx); |
| free(sh); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Get buffer data from ACPI. |
| * |
| * Buffer data is expected to be represented by a file which is a text dump of |
| * the buffer, representing each byte by two hex numbers, space and newline |
| * separated. |
| * |
| * On success, stores the amount of data read in bytes to *buffer_size; on |
| * erros, sets *buffer_size=0. |
| * |
| * Input - ACPI file name to get data from. |
| * |
| * Output: a pointer to AcpiBuffer structure containing the binary |
| * representation of the data. The caller is responsible for |
| * deallocating the pointer, this will take care of both the structure |
| * and the buffer. Null in case of error. |
| */ |
| static uint8_t* VbGetBuffer(const char* filename, int* buffer_size) |
| { |
| FILE* f = NULL; |
| char* file_buffer = NULL; |
| uint8_t* output_buffer = NULL; |
| uint8_t* return_value = NULL; |
| |
| /* Assume error until proven otherwise */ |
| if (buffer_size) |
| *buffer_size = 0; |
| |
| do { |
| struct stat fs; |
| uint8_t* output_ptr; |
| int rv, i, real_size; |
| int parsed_size = 0; |
| |
| int fd = open(filename, O_RDONLY); |
| if (fd == -1) |
| break; |
| |
| rv = fstat(fd, &fs); |
| if (rv || !S_ISREG(fs.st_mode)) { |
| close(fd); |
| break; |
| } |
| |
| f = fdopen(fd, "r"); |
| if (!f) { |
| close(fd); |
| break; |
| } |
| |
| file_buffer = malloc(fs.st_size + 1); |
| if (!file_buffer) |
| break; |
| |
| real_size = fread(file_buffer, 1, fs.st_size, f); |
| if (!real_size) |
| break; |
| file_buffer[real_size] = '\0'; |
| |
| /* Each byte in the output will replace two characters and a |
| * space in the input, so the output size does not exceed input |
| * side/3 (a little less if account for newline characters). */ |
| output_buffer = malloc(real_size/3); |
| if (!output_buffer) |
| break; |
| output_ptr = output_buffer; |
| |
| /* process the file contents */ |
| for (i = 0; i < real_size; i++) { |
| char* base, *end; |
| |
| base = file_buffer + i; |
| |
| if (!isxdigit(*base)) |
| continue; |
| |
| output_ptr[parsed_size++] = |
| strtol(base, &end, 16) & 0xff; |
| |
| if ((end - base) != 2) |
| /* Input file format error */ |
| break; |
| |
| /* skip the second character and the following space */ |
| i += 2; |
| } |
| |
| if (i == real_size) { |
| /* all is well */ |
| return_value = output_buffer; |
| output_buffer = NULL; /* prevent it from deallocating */ |
| if (buffer_size) |
| *buffer_size = parsed_size; |
| } |
| } while(0); |
| |
| /* wrap up */ |
| if (f) |
| fclose(f); |
| |
| if (file_buffer) |
| free(file_buffer); |
| |
| if (output_buffer) |
| free(output_buffer); |
| |
| return return_value; |
| } |
| |
| |
| VbSharedDataHeader* VbSharedDataRead(void) |
| { |
| VbSharedDataHeader* sh; |
| int got_size = 0; |
| int expect_size; |
| |
| sh = (VbSharedDataHeader*)VbGetBuffer(ACPI_VDAT_PATH, &got_size); |
| if (!sh) |
| return NULL; |
| |
| /* Make sure the size is sufficient for the struct version we got. |
| * Check supported old versions first. */ |
| if (1 == sh->struct_version) |
| expect_size = VB_SHARED_DATA_HEADER_SIZE_V1; |
| else { |
| /* There'd better be enough data for the current header size. */ |
| expect_size = sizeof(VbSharedDataHeader); |
| } |
| |
| if (got_size < expect_size) { |
| free(sh); |
| return NULL; |
| } |
| if (sh->data_size > got_size) |
| sh->data_size = got_size; /* Truncated read */ |
| |
| return sh; |
| } |
| |
| |
| /* Read the CMOS reboot field in NVRAM. |
| * |
| * Returns 0 if the mask is clear in the field, 1 if set, or -1 if error. */ |
| static int VbGetCmosRebootField(uint8_t mask) |
| { |
| unsigned chnv; |
| uint8_t nvbyte; |
| |
| /* Get the byte offset from CHNV */ |
| if (ReadFileInt(ACPI_CHNV_PATH, &chnv) < 0) |
| return -1; |
| |
| if (0 != VbCmosRead(chnv, 1, &nvbyte)) |
| return -1; |
| |
| return (nvbyte & mask ? 1 : 0); |
| } |
| |
| |
| /* Write the CMOS reboot field in NVRAM. |
| * |
| * Sets (value=0) or clears (value!=0) the mask in the byte. |
| * |
| * Returns 0 if success, or -1 if error. */ |
| static int VbSetCmosRebootField(uint8_t mask, int value) |
| { |
| unsigned chnv; |
| uint8_t nvbyte; |
| |
| /* Get the byte offset from CHNV */ |
| if (ReadFileInt(ACPI_CHNV_PATH, &chnv) < 0) |
| return -1; |
| |
| if (0 != VbCmosRead(chnv, 1, &nvbyte)) |
| return -1; |
| |
| /* Set/clear the mask */ |
| if (value) |
| nvbyte |= mask; |
| else |
| nvbyte &= ~mask; |
| |
| /* Write the byte back */ |
| if (0 != VbCmosWrite(chnv, 1, &nvbyte)) |
| return -1; |
| |
| /* Success */ |
| return 0; |
| } |
| |
| |
| /* Read the active main firmware type into the destination buffer. |
| * Passed the destination and its size. Returns the destination, or |
| * NULL if error. */ |
| static const char* VbReadMainFwType(char* dest, int size) |
| { |
| unsigned value; |
| |
| /* Try reading type from BINF.3 */ |
| if (ReadFileInt(ACPI_BINF_PATH ".3", &value) == 0) { |
| switch(value) { |
| case BINF3_LEGACY: |
| return StrCopy(dest, "legacy", size); |
| case BINF3_NETBOOT: |
| return StrCopy(dest, "netboot", size); |
| case BINF3_RECOVERY: |
| return StrCopy(dest, "recovery", size); |
| case BINF3_NORMAL: |
| return StrCopy(dest, "normal", size); |
| case BINF3_DEVELOPER: |
| return StrCopy(dest, "developer", size); |
| default: |
| break; /* Fall through to legacy handling */ |
| } |
| } |
| |
| /* Fall back to BINF.0 for legacy systems like Mario. */ |
| if (ReadFileInt(ACPI_BINF_PATH ".0", &value) < 0) |
| /* Both BINF.0 and BINF.3 are missing, so this isn't Chrome OS |
| * firmware. */ |
| return StrCopy(dest, "nonchrome", size); |
| |
| switch(value) { |
| case BINF0_NORMAL: |
| return StrCopy(dest, "normal", size); |
| case BINF0_DEVELOPER: |
| return StrCopy(dest, "developer", size); |
| case BINF0_RECOVERY_BUTTON: |
| case BINF0_RECOVERY_DEV_SCREEN_KEY: |
| case BINF0_RECOVERY_RW_FW_BAD: |
| case BINF0_RECOVERY_NO_OS: |
| case BINF0_RECOVERY_BAD_OS: |
| case BINF0_RECOVERY_OS_INITIATED: |
| case BINF0_RECOVERY_TPM_ERROR: |
| /* Assorted flavors of recovery boot reason. */ |
| return StrCopy(dest, "recovery", size); |
| default: |
| /* Other values don't map cleanly to firmware type. */ |
| return NULL; |
| } |
| } |
| |
| |
| /* Read the recovery reason. Returns the reason code or -1 if error. */ |
| static vb2_error_t VbGetRecoveryReason(void) |
| { |
| unsigned value; |
| |
| /* Try reading type from BINF.4 */ |
| if (ReadFileInt(ACPI_BINF_PATH ".4", &value) == 0) |
| return value; |
| |
| /* Fall back to BINF.0 for legacy systems like Mario. */ |
| if (ReadFileInt(ACPI_BINF_PATH ".0", &value) < 0) |
| return -1; |
| switch(value) { |
| case BINF0_NORMAL: |
| case BINF0_DEVELOPER: |
| return VB2_RECOVERY_NOT_REQUESTED; |
| case BINF0_RECOVERY_BUTTON: |
| return VB2_RECOVERY_RO_MANUAL; |
| case BINF0_RECOVERY_RW_FW_BAD: |
| return VB2_RECOVERY_RO_INVALID_RW; |
| case BINF0_RECOVERY_NO_OS: |
| return VB2_RECOVERY_RW_NO_KERNEL; |
| case BINF0_RECOVERY_BAD_OS: |
| return VB2_RECOVERY_RW_INVALID_OS; |
| case BINF0_RECOVERY_OS_INITIATED: |
| return VB2_RECOVERY_LEGACY; |
| default: |
| /* Other values don't map cleanly to firmware type. */ |
| return -1; |
| } |
| } |
| |
| /* Physical GPIO number <N> may be accessed through /sys/class/gpio/gpio<M>/, |
| * but <N> and <M> may differ by some offset <O>. To determine that constant, |
| * we look for a directory named /sys/class/gpio/gpiochip<O>/. If there's not |
| * exactly one match for that, we're SOL. |
| */ |
| static int FindGpioChipOffset(unsigned *gpio_num, unsigned *offset, |
| const char *name) |
| { |
| DIR *dir; |
| struct dirent *ent; |
| int match = 0; |
| |
| dir = opendir(GPIO_BASE_PATH); |
| if (!dir) { |
| return 0; |
| } |
| |
| while(0 != (ent = readdir(dir))) { |
| if (1 == sscanf(ent->d_name, "gpiochip%u", offset)) { |
| match++; |
| } |
| } |
| |
| closedir(dir); |
| return (1 == match); |
| } |
| |
| /* Physical GPIO number <N> may be accessed through /sys/class/gpio/gpio<M>/, |
| * but <N> and <M> may differ by some offset <O>. To determine that constant, |
| * we look for a directory named /sys/class/gpio/gpiochip<O>/ and check for |
| * a 'label' file inside of it to find the expected the controller name. |
| */ |
| static int FindGpioChipOffsetByLabel(unsigned *gpio_num, unsigned *offset, |
| const char *name) |
| { |
| DIR *dir; |
| struct dirent *ent; |
| char filename[128]; |
| char chiplabel[128]; |
| int match = 0; |
| unsigned controller_offset = 0; |
| |
| dir = opendir(GPIO_BASE_PATH); |
| if (!dir) { |
| return 0; |
| } |
| |
| while(0 != (ent = readdir(dir))) { |
| if (1 == sscanf(ent->d_name, "gpiochip%u", |
| &controller_offset)) { |
| /* |
| * Read the file at gpiochip<O>/label to get the |
| * identifier for this bank of GPIOs. |
| */ |
| snprintf(filename, sizeof(filename), |
| "%s/gpiochip%u/label", |
| GPIO_BASE_PATH, controller_offset); |
| if (ReadFileString(chiplabel, sizeof(chiplabel), |
| filename)) { |
| if (!strncasecmp(chiplabel, name, |
| strlen(name))) { |
| /* |
| * Store offset when chip label is |
| * matched. |
| */ |
| *offset = controller_offset; |
| match++; |
| } |
| } |
| } |
| } |
| |
| closedir(dir); |
| return (1 == match); |
| } |
| |
| static int FindGpioChipOffsetByNumber(unsigned *gpio_num, unsigned *offset, |
| Basemapping *data) |
| { |
| DIR *dir; |
| struct dirent *ent; |
| int match = 0; |
| |
| /* Obtain relative GPIO number. |
| * The assumption here is the Basemapping |
| * table is arranged in decreasing order of |
| * base address and ends with 0. |
| * A UID with value 0 indicates an invalid range |
| * and causes an early return to avoid the directory |
| * opening code below. |
| */ |
| do { |
| if (*gpio_num >= data->base) { |
| *gpio_num -= data->base; |
| break; |
| } |
| data++; |
| } while(1); |
| |
| if (data->uid == 0) { |
| return 0; |
| } |
| |
| dir = opendir(GPIO_BASE_PATH); |
| if (!dir) { |
| return 0; |
| } |
| |
| while(0 != (ent = readdir(dir))) { |
| /* For every gpiochip entry determine uid. */ |
| if (1 == sscanf(ent->d_name, "gpiochip%u", offset)) { |
| char uid_file[128]; |
| unsigned uid_value; |
| snprintf(uid_file, sizeof(uid_file), |
| "%s/gpiochip%u/device/firmware_node/uid", |
| GPIO_BASE_PATH, *offset); |
| if (ReadFileInt(uid_file, &uid_value) < 0) |
| continue; |
| if (data->uid == uid_value) { |
| match++; |
| break; |
| } |
| } |
| } |
| |
| closedir(dir); |
| return (1 == match); |
| } |
| |
| |
| /* Braswell has 4 sets of GPIO banks. It is expected the firmware exposes each |
| * bank of gpios using a UID in ACPI. Furthermore the gpio number exposed is |
| * relative to the bank. e.g. gpio MF_ISH_GPIO_4 in the bank specified by UID 3 |
| * would be encoded as 0x10016. |
| * |
| * UID | Bank Offset |
| * ----+------------ |
| * 1 | 0x0000 |
| * 2 | 0x8000 |
| * 3 | 0x10000 |
| * 4 | 0x18000 |
| */ |
| static int BraswellFindGpioChipOffset(unsigned *gpio_num, unsigned *offset, |
| const char *name) |
| { |
| int ret; |
| struct utsname host; |
| unsigned int maj, min; |
| int gpe = 0; |
| static Basemapping data[]={ |
| {0x20000, 0}, |
| {0x18000, 4}, |
| {0x10000, 3}, |
| {0x08000, 2}, |
| {0x00000, 1}}; |
| |
| /* |
| * This quirk addresses b:143174998 and is required on kernels >= 4.16 |
| * when GPIO numbering has changed with an upstream commit: |
| * 03c4749dd6c7ff948a0ce59a44a1b97c015353c2 |
| * "gpio / ACPI: Drop unnecessary ACPI GPIO to Linux GPIO translation". |
| * With that change gpio ACPI/Linux kernel 1:1 mapping was introduced which |
| * made mismatch for gpio number and backward compatibility for user-space. |
| * Details on review commit review |
| * https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2153155 |
| */ |
| |
| /* |
| * Here we are addressing particular wpsw_cur pin which is connected to |
| * East Community GPIO chip (uid == 3, base == 0x10000). In this case there |
| * is only one gap between 11 and 15 (0..11 15..26). For now crosssystem |
| * is not checking pins in other gpio banks, but it is worth to mention that |
| * there are gaps as well. |
| */ |
| if (*gpio_num >= 0x10000 && *gpio_num < 0x18000) |
| gpe = 1; |
| |
| ret = FindGpioChipOffsetByNumber(gpio_num, offset, data); |
| if (!ret || !gpe) |
| return ret; |
| |
| if (uname(&host) == 0) { |
| if (sscanf(host.release, "%u.%u.", &maj, &min) == 2) { |
| #if !defined(__FreeBSD__) |
| if (KERNEL_VERSION(maj, min, 0) >= KERNEL_VERSION(4, 16, 0) && |
| *offset > 11) |
| *offset += 3; |
| #endif |
| } else { |
| printf("Couldn't retrieve kernel version!\n"); |
| ret = 0; |
| } |
| } else { |
| perror("uname"); |
| ret = 0; |
| } |
| |
| return ret; |
| } |
| |
| /* BayTrail has 3 sets of GPIO banks. It is expected the firmware exposes |
| * each bank of gpios using a UID in ACPI. Furthermore the gpio number exposed |
| * is relative to the bank. e.g. gpio 6 in the bank specified by UID 3 would |
| * be encoded as 0x2006. |
| * UID | Bank Offset |
| * ----+------------ |
| * 1 | 0x0000 |
| * 2 | 0x1000 |
| * 3 | 0x2000 |
| */ |
| static int BayTrailFindGpioChipOffset(unsigned *gpio_num, unsigned *offset, |
| const char *name) |
| { |
| static Basemapping data[]={ |
| {0x3000, 0}, |
| {0x2000, 3}, |
| {0x1000, 2}, |
| {0x0000, 1}}; |
| |
| return FindGpioChipOffsetByNumber(gpio_num, offset, data); |
| } |
| |
| struct GpioChipset { |
| const char *name; |
| int (*ChipOffsetAndGpioNumber)(unsigned *gpio_num, |
| unsigned *chip_offset, |
| const char *name); |
| }; |
| |
| static const struct GpioChipset chipsets_supported[] = { |
| { "AMD0030", FindGpioChipOffset }, |
| { "NM10", FindGpioChipOffset }, |
| { "CougarPoint", FindGpioChipOffset }, |
| { "PantherPoint", FindGpioChipOffset }, |
| { "LynxPoint", FindGpioChipOffset }, |
| { "PCH-LP", FindGpioChipOffset }, |
| { "INT3437:00", FindGpioChipOffsetByLabel }, |
| { "INT344B:00", FindGpioChipOffsetByLabel }, |
| /* INT3452 are for Apollolake */ |
| { "INT3452:00", FindGpioChipOffsetByLabel }, |
| { "INT3452:01", FindGpioChipOffsetByLabel }, |
| { "INT3452:02", FindGpioChipOffsetByLabel }, |
| { "INT3452:03", FindGpioChipOffsetByLabel }, |
| { "INT3455:00", FindGpioChipOffsetByLabel }, |
| { "INT34BB:00", FindGpioChipOffsetByLabel }, |
| { "INT34C8:00", FindGpioChipOffsetByLabel }, |
| { "INT34C5:00", FindGpioChipOffsetByLabel }, |
| /* INTC105x are for Alderlake */ |
| { "INTC1055:00", FindGpioChipOffsetByLabel }, |
| { "INTC1056:00", FindGpioChipOffsetByLabel }, |
| /* INT3453 are for GLK */ |
| { "INT3453:00", FindGpioChipOffsetByLabel }, |
| { "INT3453:01", FindGpioChipOffsetByLabel }, |
| { "INT3453:02", FindGpioChipOffsetByLabel }, |
| { "INT3453:03", FindGpioChipOffsetByLabel }, |
| { "BayTrail", BayTrailFindGpioChipOffset }, |
| { "Braswell", BraswellFindGpioChipOffset }, |
| { NULL }, |
| }; |
| |
| static const struct GpioChipset *FindChipset(const char *name) |
| { |
| const struct GpioChipset *chipset = &chipsets_supported[0]; |
| |
| while (chipset->name != NULL) { |
| if (!strcmp(name, chipset->name)) |
| return chipset; |
| chipset++; |
| } |
| return NULL; |
| } |
| |
| /* Read a GPIO of the specified signal type (see ACPI GPIO SignalType). |
| * |
| * Returns 1 if the signal is asserted, 0 if not asserted, or -1 if error. */ |
| static int ReadGpio(unsigned signal_type) |
| { |
| char name[128]; |
| int index = 0; |
| unsigned gpio_type; |
| unsigned active_high; |
| unsigned controller_num; |
| unsigned controller_offset = 0; |
| char controller_name[128]; |
| unsigned value; |
| const struct GpioChipset *chipset; |
| |
| /* Scan GPIO.* to find a matching signal type */ |
| for (index = 0; ; index++) { |
| snprintf(name, sizeof(name), "%s.%d/GPIO.0", ACPI_GPIO_PATH, |
| index); |
| if (ReadFileInt(name, &gpio_type) < 0) |
| return -1; /* Ran out of GPIOs before finding a match */ |
| if (gpio_type == signal_type) |
| break; |
| } |
| |
| /* Read attributes and controller info for the GPIO */ |
| snprintf(name, sizeof(name), "%s.%d/GPIO.1", ACPI_GPIO_PATH, index); |
| if (ReadFileInt(name, &active_high) < 0) |
| return -1; |
| snprintf(name, sizeof(name), "%s.%d/GPIO.2", ACPI_GPIO_PATH, index); |
| if (ReadFileInt(name, &controller_num) < 0) |
| return -1; |
| /* Do not attempt to read GPIO that is set to -1 in ACPI */ |
| if (controller_num == 0xFFFFFFFF) |
| return -1; |
| |
| /* Check for chipsets we recognize. */ |
| snprintf(name, sizeof(name), "%s.%d/GPIO.3", ACPI_GPIO_PATH, index); |
| if (!ReadFileString(controller_name, sizeof(controller_name), name)) |
| return -1; |
| chipset = FindChipset(controller_name); |
| if (chipset == NULL) |
| return -1; |
| |
| /* Modify GPIO number by driver's offset */ |
| if (!chipset->ChipOffsetAndGpioNumber(&controller_num, |
| &controller_offset, |
| chipset->name)) |
| return -1; |
| controller_offset += controller_num; |
| |
| /* Try reading the GPIO value */ |
| snprintf(name, sizeof(name), "%s/gpio%d/value", |
| GPIO_BASE_PATH, controller_offset); |
| if (ReadFileInt(name, &value) < 0) { |
| /* Try exporting the GPIO */ |
| FILE* f = fopen(GPIO_EXPORT_PATH, "wt"); |
| if (!f) |
| return -1; |
| fprintf(f, "%u", controller_offset); |
| fclose(f); |
| |
| /* Try re-reading the GPIO value */ |
| if (ReadFileInt(name, &value) < 0) |
| return -1; |
| } |
| |
| /* Normalize the value read from the kernel in case it is not always |
| * 1. */ |
| value = value ? 1 : 0; |
| |
| /* Compare the GPIO value with the active value and return 1 if |
| * match. */ |
| return (value == active_high ? 1 : 0); |
| } |
| |
| |
| int VbGetArchPropertyInt(const char* name) |
| { |
| int value = -1; |
| |
| /* Switch positions */ |
| if (!strcasecmp(name,"devsw_cur")) { |
| /* Systems with virtual developer switches return at-boot |
| * value */ |
| value = VbGetSystemPropertyInt("devsw_boot"); |
| } else if (!strcasecmp(name,"recoverysw_cur")) { |
| value = ReadGpio(GPIO_SIGNAL_TYPE_RECOVERY); |
| } else if (!strcasecmp(name,"wpsw_cur")) { |
| value = ReadGpio(GPIO_SIGNAL_TYPE_WP); |
| } else if (!strcasecmp(name,"recoverysw_ec_boot")) { |
| value = ReadFileBit(ACPI_CHSW_PATH, CHSW_RECOVERY_EC_BOOT); |
| } else if (!strcasecmp(name,"phase_enforcement")) { |
| value = ReadGpio(GPIO_SIGNAL_TYPE_PHASE_ENFORCEMENT); |
| } |
| |
| /* Fields for old systems which don't have VbSharedData */ |
| if (VbSharedDataVersion() < 2) { |
| if (!strcasecmp(name,"recovery_reason")) { |
| value = VbGetRecoveryReason(); |
| } else if (!strcasecmp(name,"devsw_boot")) { |
| value = ReadFileBit(ACPI_CHSW_PATH, CHSW_DEV_BOOT); |
| } else if (!strcasecmp(name,"recoverysw_boot")) { |
| value = ReadFileBit(ACPI_CHSW_PATH, CHSW_RECOVERY_BOOT); |
| } |
| } |
| |
| /* NV storage values. If unable to get from NV storage, fall back to |
| * the CMOS reboot field used by older BIOS (e.g. Mario). */ |
| if (!strcasecmp(name,"recovery_request")) { |
| value = vb2_get_nv_storage(VB2_NV_RECOVERY_REQUEST); |
| if (-1 == value) |
| value = VbGetCmosRebootField(CMOSRF_RECOVERY); |
| } else if (!strcasecmp(name,"dbg_reset")) { |
| value = vb2_get_nv_storage(VB2_NV_DEBUG_RESET_MODE); |
| if (-1 == value) |
| value = VbGetCmosRebootField(CMOSRF_DEBUG_RESET); |
| } else if (!strcasecmp(name,"fwb_tries")) { |
| value = vb2_get_nv_storage(VB2_NV_TRY_COUNT); |
| if (-1 == value) |
| value = VbGetCmosRebootField(CMOSRF_TRY_B); |
| } |
| |
| /* Firmware update tries is now stored in the kernel field. On |
| * older systems where it's not, it was stored in a file in the |
| * stateful partition. */ |
| if (!strcasecmp(name,"fwupdate_tries")) { |
| unsigned fwupdate_value; |
| if (-1 != vb2_get_nv_storage(VB2_NV_KERNEL_FIELD)) |
| return -1; /* NvStorage supported; fail through |
| * arch-specific implementation to normal |
| * implementation. */ |
| /* Read value from file; missing file means value=0. */ |
| if (ReadFileInt(NEED_FWUPDATE_PATH, &fwupdate_value) < 0) |
| value = 0; |
| else |
| value = (int)fwupdate_value; |
| } |
| |
| return value; |
| } |
| |
| |
| const char* VbGetArchPropertyString(const char* name, char* dest, |
| size_t size) |
| { |
| unsigned value; |
| |
| if (!strcasecmp(name,"arch")) { |
| return StrCopy(dest, "x86", size); |
| } else if (!strcasecmp(name,"hwid")) { |
| return ReadFileString(dest, size, ACPI_BASE_PATH "/HWID"); |
| } else if (!strcasecmp(name,"fwid")) { |
| return ReadFileString(dest, size, ACPI_BASE_PATH "/FWID"); |
| } else if (!strcasecmp(name,"ro_fwid")) { |
| return ReadFileString(dest, size, ACPI_BASE_PATH "/FRID"); |
| } else if (!strcasecmp(name,"mainfw_act")) { |
| if (ReadFileInt(ACPI_BINF_PATH ".1", &value) < 0) |
| return NULL; |
| switch(value) { |
| case 0: |
| return StrCopy(dest, "recovery", size); |
| case 1: |
| return StrCopy(dest, "A", size); |
| case 2: |
| return StrCopy(dest, "B", size); |
| default: |
| return NULL; |
| } |
| } else if (!strcasecmp(name,"mainfw_type")) { |
| return VbReadMainFwType(dest, size); |
| } else if (!strcasecmp(name,"ecfw_act")) { |
| if (ReadFileInt(ACPI_BINF_PATH ".2", &value) < 0) |
| return NULL; |
| switch(value) { |
| case 0: |
| return StrCopy(dest, "RO", size); |
| case 1: |
| return StrCopy(dest, "RW", size); |
| default: |
| return NULL; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| |
| int VbSetArchPropertyInt(const char* name, int value) |
| { |
| /* NV storage values. If unable to get from NV storage, fall back to |
| * the CMOS reboot field used by older BIOS. */ |
| if (!strcasecmp(name,"recovery_request")) { |
| if (0 == vb2_set_nv_storage(VB2_NV_RECOVERY_REQUEST, value)) |
| return 0; |
| return VbSetCmosRebootField(CMOSRF_RECOVERY, value); |
| } else if (!strcasecmp(name,"dbg_reset")) { |
| if (0 == vb2_set_nv_storage(VB2_NV_DEBUG_RESET_MODE, value)) |
| return 0; |
| return VbSetCmosRebootField(CMOSRF_DEBUG_RESET, value); |
| } else if (!strcasecmp(name,"fwb_tries")) { |
| if (0 == vb2_set_nv_storage(VB2_NV_TRY_COUNT, value)) |
| return 0; |
| return VbSetCmosRebootField(CMOSRF_TRY_B, value); |
| } |
| /* Firmware update tries is now stored in the kernel field. On |
| * older systems where it's not, it was stored in a file in the |
| * stateful partition. */ |
| else if (!strcasecmp(name,"fwupdate_tries")) { |
| if (-1 != vb2_get_nv_storage(VB2_NV_KERNEL_FIELD)) |
| return -1; /* NvStorage supported; fail through |
| * arch-specific implementation to normal |
| * implementation */ |
| |
| if (value) { |
| char buf[32]; |
| snprintf(buf, sizeof(buf), "%d", value); |
| return WriteFile(NEED_FWUPDATE_PATH, buf, strlen(buf)); |
| } else { |
| /* No update tries, so remove file if it exists. */ |
| unlink(NEED_FWUPDATE_PATH); |
| return 0; |
| } |
| } |
| |
| return -1; |
| } |
| |
| int VbSetArchPropertyString(const char* name, const char* value) |
| { |
| /* If there were settable architecture-dependent string properties, |
| * they'd be here. */ |
| return -1; |
| } |