You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
909 lines
26 KiB
909 lines
26 KiB
/* 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 <linux/nvram.h> |
|
#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 <unistd.h> |
|
|
|
#include "crossystem.h" |
|
#include "crossystem_arch.h" |
|
#include "host_common.h" |
|
#include "utility.h" |
|
#include "vboot_common.h" |
|
#include "vboot_nvstorage.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 |
|
#define CHSW_WP_BOOT 0x00000200 |
|
/* 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_DEV 2 |
|
#define GPIO_SIGNAL_TYPE_WP 3 |
|
|
|
/* 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_FMAP_PATH ACPI_BASE_PATH "/FMAP" |
|
#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 PlatformFamily { |
|
unsigned int vendor; /* Vendor id value */ |
|
unsigned int device; /* Device id value */ |
|
const char* platform_string; /* String to return */ |
|
} PlatformFamily; |
|
|
|
/* Array of platform family names, terminated with a NULL entry */ |
|
const PlatformFamily platform_family_array[] = { |
|
{0x8086, 0xA010, "PineTrail"}, |
|
{0x8086, 0x3406, "Westmere"}, |
|
{0x8086, 0x0104, "SandyBridge"}, /* mobile */ |
|
{0x8086, 0x0100, "SandyBridge"}, /* desktop */ |
|
{0x8086, 0x0154, "IvyBridge"}, /* mobile */ |
|
{0x8086, 0x0150, "IvyBridge"}, /* desktop */ |
|
{0x8086, 0x0a04, "Haswell"}, /* ult */ |
|
{0x8086, 0x0c04, "Haswell"}, /* mobile */ |
|
{0x8086, 0x0f00, "BayTrail"}, /* mobile */ |
|
{0x8086, 0x1604, "Broadwell"}, /* ult */ |
|
/* Terminate with NULL entry */ |
|
{0, 0, 0} |
|
}; |
|
|
|
static void VbFixCmosChecksum(FILE* file) { |
|
int fd = fileno(file); |
|
ioctl(fd, NVRAM_SETCKS); |
|
} |
|
|
|
|
|
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 VbReadNvStorage(VbNvContext* vnc) { |
|
unsigned offs, blksz; |
|
|
|
/* 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 (VBNV_BLOCK_SIZE > blksz) |
|
return -1; /* NV storage block is too small */ |
|
|
|
if (0 != VbCmosRead(offs, VBNV_BLOCK_SIZE, vnc->raw)) |
|
return -1; |
|
|
|
return 0; |
|
} |
|
|
|
|
|
int VbWriteNvStorage(VbNvContext* vnc) { |
|
unsigned offs, blksz; |
|
|
|
if (!vnc->raw_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 (VBNV_BLOCK_SIZE > blksz) |
|
return -1; /* NV storage block is too small */ |
|
|
|
if (0 != VbCmosWrite(offs, VBNV_BLOCK_SIZE, vnc->raw)) |
|
return -1; |
|
|
|
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; |
|
|
|
rv = stat(filename, &fs); |
|
if (rv || !S_ISREG(fs.st_mode)) |
|
break; |
|
|
|
f = fopen(filename, "r"); |
|
if (!f) |
|
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; |
|
|
|
i += 2; /* skip the second character and the following space */ |
|
} |
|
|
|
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_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 int 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 VBNV_RECOVERY_NOT_REQUESTED; |
|
case BINF0_RECOVERY_BUTTON: |
|
return VBNV_RECOVERY_RO_MANUAL; |
|
case BINF0_RECOVERY_DEV_SCREEN_KEY: |
|
return VBNV_RECOVERY_RW_DEV_SCREEN; |
|
case BINF0_RECOVERY_RW_FW_BAD: |
|
return VBNV_RECOVERY_RO_INVALID_RW; |
|
case BINF0_RECOVERY_NO_OS: |
|
return VBNV_RECOVERY_RW_NO_OS; |
|
case BINF0_RECOVERY_BAD_OS: |
|
return VBNV_RECOVERY_RW_INVALID_OS; |
|
case BINF0_RECOVERY_OS_INITIATED: |
|
return VBNV_RECOVERY_LEGACY; |
|
default: |
|
/* Other values don't map cleanly to firmware type. */ |
|
return -1; |
|
} |
|
} |
|
|
|
/* Determine the platform family and return it in the dest string. |
|
* This uses the PCI Bus 0, Device 0, Function 0 vendor and device id values |
|
* taken from sysfs to determine the platform family. This assumes there will |
|
* be a unique pair of values here for any given platform. |
|
*/ |
|
static char* ReadPlatformFamilyString(char* dest, int size) { |
|
FILE* f; |
|
const PlatformFamily* p; |
|
unsigned int v = 0xFFFF; |
|
unsigned int d = 0xFFFF; |
|
|
|
f = fopen(PCI_VENDOR_ID_PATH, "rt"); |
|
if (!f) |
|
return NULL; |
|
if(fscanf(f, "0x%4x", &v) != 1) |
|
return NULL; |
|
fclose(f); |
|
|
|
f = fopen(PCI_DEVICE_ID_PATH, "rt"); |
|
if (!f) |
|
return NULL; |
|
if(fscanf(f, "0x%4x", &d) != 1) |
|
return NULL; |
|
fclose(f); |
|
|
|
for (p = platform_family_array; p->vendor; p++) { |
|
if((v == p->vendor) && (d == p->device)) |
|
return StrCopy(dest, p->platform_string, size); |
|
} |
|
|
|
/* No recognized platform family was found */ |
|
return NULL; |
|
} |
|
|
|
/* 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; |
|
|
|
dir = opendir(GPIO_BASE_PATH); |
|
if (!dir) { |
|
return 0; |
|
} |
|
|
|
while(0 != (ent = readdir(dir))) { |
|
if (1 == sscanf(ent->d_name, "gpiochip%u", 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, *offset); |
|
if (ReadFileString(chiplabel, sizeof(chiplabel), filename)) { |
|
if (!strncasecmp(chiplabel, name, strlen(name))) |
|
match++; |
|
} |
|
} |
|
} |
|
|
|
closedir(dir); |
|
return (1 == match); |
|
} |
|
|
|
/* 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) { |
|
DIR *dir; |
|
struct dirent *ent; |
|
unsigned expected_uid; |
|
int match = 0; |
|
|
|
/* Obtain relative GPIO number. */ |
|
if (*gpio_num >= 0x2000) { |
|
*gpio_num -= 0x2000; |
|
expected_uid = 3; |
|
} else if (*gpio_num >= 0x1000) { |
|
*gpio_num -= 0x1000; |
|
expected_uid = 2; |
|
} else if (*gpio_num >= 0x0000) { |
|
*gpio_num -= 0x0000; |
|
expected_uid = 1; |
|
} else { |
|
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 (expected_uid == uid_value) { |
|
match++; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
closedir(dir); |
|
return (1 == match); |
|
} |
|
|
|
|
|
struct GpioChipset { |
|
const char *name; |
|
int (*ChipOffsetAndGpioNumber)(unsigned *gpio_num, unsigned *chip_offset, |
|
const char *name); |
|
}; |
|
|
|
static const struct GpioChipset chipsets_supported[] = { |
|
{ "NM10", FindGpioChipOffset }, |
|
{ "CougarPoint", FindGpioChipOffset }, |
|
{ "PantherPoint", FindGpioChipOffset }, |
|
{ "LynxPoint", FindGpioChipOffset }, |
|
{ "PCH-LP", FindGpioChipOffset }, |
|
{ "INT3437:00", FindGpioChipOffsetByLabel }, |
|
{ "BayTrail", BayTrailFindGpioChipOffset }, |
|
{ 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; |
|
|
|
/* Values from ACPI */ |
|
if (!strcasecmp(name,"fmap_base")) { |
|
unsigned fmap_base; |
|
if (ReadFileInt(ACPI_FMAP_PATH, &fmap_base) < 0) |
|
return -1; |
|
else |
|
value = (int)fmap_base; |
|
} |
|
|
|
/* Switch positions */ |
|
if (!strcasecmp(name,"devsw_cur")) { |
|
/* Systems with virtual developer switches return at-boot value */ |
|
int flags = VbGetSystemPropertyInt("vdat_flags"); |
|
if ((flags != -1) && (flags & VBSD_HONOR_VIRT_DEV_SWITCH)) |
|
value = VbGetSystemPropertyInt("devsw_boot"); |
|
else |
|
value = ReadGpio(GPIO_SIGNAL_TYPE_DEV); |
|
} else if (!strcasecmp(name,"recoverysw_cur")) { |
|
value = ReadGpio(GPIO_SIGNAL_TYPE_RECOVERY); |
|
} else if (!strcasecmp(name,"wpsw_cur")) { |
|
value = ReadGpio(GPIO_SIGNAL_TYPE_WP); |
|
if (-1 != value && FwidStartsWith("Mario.")) |
|
value = 1 - value; /* Mario reports this backwards */ |
|
} else if (!strcasecmp(name,"recoverysw_ec_boot")) { |
|
value = ReadFileBit(ACPI_CHSW_PATH, CHSW_RECOVERY_EC_BOOT); |
|
} |
|
|
|
/* 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); |
|
} else if (!strcasecmp(name,"wpsw_boot")) { |
|
value = ReadFileBit(ACPI_CHSW_PATH, CHSW_WP_BOOT); |
|
if (-1 != value && FwidStartsWith("Mario.")) |
|
value = 1 - value; /* Mario reports this backwards */ |
|
} |
|
} |
|
|
|
/* Saved memory is at a fixed location for all H2C BIOS. If the CHSW |
|
* path exists in sysfs, it's a H2C BIOS. */ |
|
if (!strcasecmp(name,"savedmem_base")) { |
|
unsigned savedmem_base; |
|
if (ReadFileInt(ACPI_CHSW_PATH, &savedmem_base) < 0) |
|
return -1; |
|
else |
|
return 0x00F00000; |
|
} else if (!strcasecmp(name,"savedmem_size")) { |
|
unsigned savedmem_size; |
|
if (ReadFileInt(ACPI_CHSW_PATH, &savedmem_size) < 0) |
|
return -1; |
|
else |
|
return 0x00100000; |
|
} |
|
|
|
/* 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 = VbGetNvStorage(VBNV_RECOVERY_REQUEST); |
|
if (-1 == value) |
|
value = VbGetCmosRebootField(CMOSRF_RECOVERY); |
|
} else if (!strcasecmp(name,"dbg_reset")) { |
|
value = VbGetNvStorage(VBNV_DEBUG_RESET_MODE); |
|
if (-1 == value) |
|
value = VbGetCmosRebootField(CMOSRF_DEBUG_RESET); |
|
} else if (!strcasecmp(name,"fwb_tries")) { |
|
value = VbGetNvStorage(VBNV_TRY_B_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 != VbGetNvStorage(VBNV_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; |
|
} |
|
} else if (!strcasecmp(name,"platform_family")) { |
|
return ReadPlatformFamilyString(dest, size); |
|
} |
|
|
|
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 == VbSetNvStorage(VBNV_RECOVERY_REQUEST, value)) |
|
return 0; |
|
return VbSetCmosRebootField(CMOSRF_RECOVERY, value); |
|
} else if (!strcasecmp(name,"dbg_reset")) { |
|
if (0 == VbSetNvStorage(VBNV_DEBUG_RESET_MODE, value)) |
|
return 0; |
|
return VbSetCmosRebootField(CMOSRF_DEBUG_RESET, value); |
|
} else if (!strcasecmp(name,"fwb_tries")) { |
|
if (0 == VbSetNvStorage(VBNV_TRY_B_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 != VbGetNvStorage(VBNV_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; |
|
}
|
|
|