/* 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 <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#ifndef HAVE_MACOS
#include <linux/fs.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/in.h>
#include "vboot_common.h"
#include "vboot_nvstorage.h"
#include "host_common.h"
#include "crossystem.h"
#include "crossystem_arch.h"
#define MOSYS_PATH "/usr/sbin/mosys"
/* Base name for firmware FDT files */
#define FDT_BASE_PATH "/proc/device-tree/firmware/chromeos"
/* Path to compatible FDT entry */
#define FDT_COMPATIBLE_PATH "/proc/device-tree/compatible"
/* Path to the chromeos_arm platform device */
#define PLATFORM_DEV_PATH "/sys/devices/platform/chromeos_arm"
/* Device for NVCTX write */
#define NVCTX_PATH "/dev/mmcblk%d"
/* Base name for GPIO files */
#define GPIO_BASE_PATH "/sys/class/gpio"
#define GPIO_EXPORT_PATH GPIO_BASE_PATH "/export"
/* Name of NvStorage type property */
#define FDT_NVSTORAGE_TYPE_PROP "nonvolatile-context-storage"
/* Errors */
#define E_FAIL -1
#define E_FILEOP -2
#define E_MEM -3
/* Common constants */
#define FNAME_SIZE 80
#define SECTOR_SIZE 512
#define MAX_NMMCBLK 9
typedef struct PlatformFamily {
const char* compatible_string; /* Last string in FDT compatible entry */
const char* platform_string; /* String to return */
} PlatformFamily;
/* Array of platform family names, terminated with a NULL entry */
const PlatformFamily platform_family_array[] = {
{"nvidia,tegra124", "Tegra5"},
{"nvidia,tegra250", "Tegra2"},
{"nvidia,tegra20", "Tegra2"},
{"ti,omap4", "OMAP4"},
{"ti,omap3", "OMAP3"},
{"samsung,exynos4210", "EXYNOS4"},
{"samsung,exynos5250", "EXYNOS5"},
{"samsung,exynos5420", "EXYNOS5"},
{"qcom,ipq8064", "IPQ8064"},
/* Terminate with NULL entry */
{NULL, NULL}
};
static int FindEmmcDev(void) {
int mmcblk;
unsigned value;
char filename[FNAME_SIZE];
for (mmcblk = 0; mmcblk < MAX_NMMCBLK; mmcblk++) {
/* Get first non-removable mmc block device */
snprintf(filename, sizeof(filename), "/sys/block/mmcblk%d/removable",
mmcblk);
if (ReadFileInt(filename, &value) < 0)
continue;
if (value == 0)
return mmcblk;
}
/* eMMC not found */
return E_FAIL;
}
static int ReadFdtValue(const char *property, int *value) {
char filename[FNAME_SIZE];
FILE *file;
int data = 0;
snprintf(filename, sizeof(filename), FDT_BASE_PATH "/%s", property);
file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Unable to open FDT property %s\n", property);
return E_FILEOP;
}
if (fread(&data, 1, sizeof(data), file) != sizeof(data)) {
fprintf(stderr, "Unable to read FDT property %s\n", property);
return E_FILEOP;
}
fclose(file);
if (value)
*value = ntohl(data); /* FDT is network byte order */
return 0;
}
static int ReadFdtInt(const char *property) {
int value = 0;
if (ReadFdtValue(property, &value))
return E_FAIL;
return value;
}
static void GetFdtPropertyPath(const char *property, char *path, size_t size) {
if (property[0] == '/')
StrCopy(path, property, size);
else
snprintf(path, size, FDT_BASE_PATH "/%s", property);
}
static int FdtPropertyExist(const char *property) {
char filename[FNAME_SIZE];
struct stat file_status;
GetFdtPropertyPath(property, filename, sizeof(filename));
if (!stat(filename, &file_status))
return 1; // It exists!
else
return 0; // It does not exist or some error happened.
}
static int ReadFdtBlock(const char *property, void **block, size_t *size) {
char filename[FNAME_SIZE];
FILE *file;
size_t property_size;
char *data;
if (!block)
return E_FAIL;
GetFdtPropertyPath(property, filename, sizeof(filename));
file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Unable to open FDT property %s\n", property);
return E_FILEOP;
}
fseek(file, 0, SEEK_END);
property_size = ftell(file);
rewind(file);
data = malloc(property_size +1);
if (!data) {
fclose(file);
return E_MEM;
}
data[property_size] = 0;
if (1 != fread(data, property_size, 1, file)) {
fprintf(stderr, "Unable to read from property %s\n", property);
fclose(file);
free(data);
return E_FILEOP;
}
fclose(file);
*block = data;
if (size)
*size = property_size;
return 0;
}
static char * ReadFdtString(const char *property) {
void *str = NULL;
/* Do not need property size */
ReadFdtBlock(property, &str, 0);
return (char *)str;
}
static char * ReadFdtPlatformFamily(void) {
char *compat = NULL;
char *s;
const PlatformFamily* p;
size_t size = 0;
int slen;
if(ReadFdtBlock(FDT_COMPATIBLE_PATH, (void **)&compat, &size))
return NULL;
if (size > 0)
compat[size-1] = 0;
/* Check each null separated string in compatible against the family array */
s = compat;
while ((s-compat) < size) {
slen = strlen(s);
for (p = platform_family_array; p->compatible_string; p++) {
if (!strcmp(s, p->compatible_string)) {
free(compat);
return strdup(p->platform_string);
}
}
s += slen + 1;
}
/* No recognized 'compatible' entry found */
free(compat);
return NULL;
}
static int VbGetPlatformGpioStatus(const char* name) {
char gpio_name[FNAME_SIZE];
unsigned value;
snprintf(gpio_name, sizeof(gpio_name), "%s/%s/value",
PLATFORM_DEV_PATH, name);
if (ReadFileInt(gpio_name, &value) < 0)
return -1;
return (int)value;
}
static int VbGetGpioStatus(unsigned gpio_number) {
char gpio_name[FNAME_SIZE];
unsigned value;
snprintf(gpio_name, sizeof(gpio_name), "%s/gpio%d/value",
GPIO_BASE_PATH, gpio_number);
if (ReadFileInt(gpio_name, &value) < 0) {
/* Try exporting the GPIO */
FILE* f = fopen(GPIO_EXPORT_PATH, "wt");
if (!f)
return -1;
fprintf(f, "%d", gpio_number);
fclose(f);
/* Try re-reading the GPIO value */
if (ReadFileInt(gpio_name, &value) < 0)
return -1;
}
return (int)value;
}
static int VbGetVarGpio(const char* name) {
int gpio_num;
void *pp = NULL;
int *prop;
size_t proplen = 0;
int ret = 0;
/* TODO: This should at some point in the future use the phandle
* to find the gpio chip and thus the base number. Assume 0 now,
* which isn't 100% future-proof (i.e. if one of the switches gets
* moved to an offchip gpio controller.
*/
ret = ReadFdtBlock(name, &pp, &proplen);
if (ret || !pp || proplen != 12) {
ret = 2;
goto out;
}
prop = pp;
gpio_num = ntohl(prop[1]);
/*
* TODO(chrome-os-partner:11296): Use gpio_num == 0 to denote non-exist
* GPIO for now, at the risk that one day we might actually want to read
* from a GPIO port 0. We should figure out how to represent "non-exist"
* properly.
*/
if (gpio_num)
ret = VbGetGpioStatus(gpio_num);
else
ret = -1;
out:
if (pp)
free(pp);
return ret;
}
static int ExecuteMosys(char * const argv[], char *buf, size_t bufsize) {
int status, mosys_to_crossystem[2];
pid_t pid;
ssize_t n;
if (pipe(mosys_to_crossystem) < 0) {
VBDEBUG(("pipe() error\n"));
return -1;
}
if ((pid = fork()) < 0) {
VBDEBUG(("fork() error\n"));
close(mosys_to_crossystem[0]);
close(mosys_to_crossystem[1]);
return -1;
} else if (!pid) { /* Child */
close(mosys_to_crossystem[0]);
/* Redirect pipe's write-end to mosys' stdout */
if (STDOUT_FILENO != mosys_to_crossystem[1]) {
if (dup2(mosys_to_crossystem[1], STDOUT_FILENO) != STDOUT_FILENO) {
VBDEBUG(("stdout dup2() failed (mosys)\n"));
close(mosys_to_crossystem[1]);
exit(1);
}
}
/* Execute mosys */
execv(MOSYS_PATH, argv);
/* We shouldn't be here; exit now! */
VBDEBUG(("execv() of mosys failed\n"));
close(mosys_to_crossystem[1]);
exit(1);
} else { /* Parent */
close(mosys_to_crossystem[1]);
if (bufsize) {
bufsize--; /* Reserve 1 byte for '\0' */
while ((n = read(mosys_to_crossystem[0], buf, bufsize)) > 0) {
buf += n;
bufsize -= n;
}
*buf = '\0';
} else {
n = 0;
}
close(mosys_to_crossystem[0]);
if (n < 0)
VBDEBUG(("read() error while reading output from mosys\n"));
if (waitpid(pid, &status, 0) < 0 || status) {
VBDEBUG(("waitpid() or mosys error\n"));
fprintf(stderr, "waitpid() or mosys error\n");
return -1;
}
if (n < 0)
return -1;
}
return 0;
}
static int VbReadNvStorage_mosys(VbNvContext* vnc) {
char hexstring[VBNV_BLOCK_SIZE * 2 + 32]; /* Reserve extra 32 bytes */
char * const argv[] = {
MOSYS_PATH, "nvram", "vboot", "read", NULL
};
char hexdigit[3];
int i;
if (ExecuteMosys(argv, hexstring, sizeof(hexstring)))
return -1;
hexdigit[2] = '\0';
for (i = 0; i < VBNV_BLOCK_SIZE; i++) {
hexdigit[0] = hexstring[i * 2];
hexdigit[1] = hexstring[i * 2 + 1];
vnc->raw[i] = strtol(hexdigit, NULL, 16);
}
return 0;
}
static int VbWriteNvStorage_mosys(VbNvContext* vnc) {
char hexstring[VBNV_BLOCK_SIZE * 2 + 1];
char * const argv[] = {
MOSYS_PATH, "nvram", "vboot", "write", hexstring, NULL
};
int i;
for (i = 0; i < VBNV_BLOCK_SIZE; i++)
snprintf(hexstring + i * 2, 3, "%02x", vnc->raw[i]);
hexstring[sizeof(hexstring) - 1] = '\0';
if (ExecuteMosys(argv, NULL, 0))
return -1;
return 0;
}
static int VbReadNvStorage_disk(VbNvContext* vnc) {
int nvctx_fd = -1;
uint8_t sector[SECTOR_SIZE];
int rv = -1;
char nvctx_path[FNAME_SIZE];
int emmc_dev;
int lba = ReadFdtInt("nonvolatile-context-lba");
int offset = ReadFdtInt("nonvolatile-context-offset");
int size = ReadFdtInt("nonvolatile-context-size");
emmc_dev = FindEmmcDev();
if (emmc_dev < 0)
return E_FAIL;
snprintf(nvctx_path, sizeof(nvctx_path), NVCTX_PATH, emmc_dev);
if (size != sizeof(vnc->raw) || (size + offset > SECTOR_SIZE))
return E_FAIL;
nvctx_fd = open(nvctx_path, O_RDONLY);
if (nvctx_fd == -1) {
fprintf(stderr, "%s: failed to open %s\n", __FUNCTION__, nvctx_path);
goto out;
}
lseek(nvctx_fd, lba * SECTOR_SIZE, SEEK_SET);
rv = read(nvctx_fd, sector, SECTOR_SIZE);
if (size <= 0) {
fprintf(stderr, "%s: failed to read nvctx from device %s\n",
__FUNCTION__, nvctx_path);
goto out;
}
Memcpy(vnc->raw, sector+offset, size);
rv = 0;
out:
if (nvctx_fd > 0)
close(nvctx_fd);
return rv;
}
static int VbWriteNvStorage_disk(VbNvContext* vnc) {
int nvctx_fd = -1;
uint8_t sector[SECTOR_SIZE];
int rv = -1;
char nvctx_path[FNAME_SIZE];
int emmc_dev;
int lba = ReadFdtInt("nonvolatile-context-lba");
int offset = ReadFdtInt("nonvolatile-context-offset");
int size = ReadFdtInt("nonvolatile-context-size");
emmc_dev = FindEmmcDev();
if (emmc_dev < 0)
return E_FAIL;
snprintf(nvctx_path, sizeof(nvctx_path), NVCTX_PATH, emmc_dev);
if (size != sizeof(vnc->raw) || (size + offset > SECTOR_SIZE))
return E_FAIL;
do {
nvctx_fd = open(nvctx_path, O_RDWR);
if (nvctx_fd == -1) {
fprintf(stderr, "%s: failed to open %s\n", __FUNCTION__, nvctx_path);
break;
}
lseek(nvctx_fd, lba * SECTOR_SIZE, SEEK_SET);
rv = read(nvctx_fd, sector, SECTOR_SIZE);
if (rv <= 0) {
fprintf(stderr, "%s: failed to read nvctx from device %s\n",
__FUNCTION__, nvctx_path);
break;
}
Memcpy(sector+offset, vnc->raw, size);
lseek(nvctx_fd, lba * SECTOR_SIZE, SEEK_SET);
rv = write(nvctx_fd, sector, SECTOR_SIZE);
if (rv <= 0) {
fprintf(stderr, "%s: failed to write nvctx to device %s\n",
__FUNCTION__, nvctx_path);
break;
}
#ifndef HAVE_MACOS
/* Must flush buffer cache here to make sure it goes to disk */
rv = ioctl(nvctx_fd, BLKFLSBUF, 0);
if (rv < 0) {
fprintf(stderr, "%s: failed to flush nvctx to device %s\n",
__FUNCTION__, nvctx_path);
break;
}
#endif
rv = 0;
} while (0);
if (nvctx_fd > 0)
close(nvctx_fd);
return rv;
}
int VbReadNvStorage(VbNvContext* vnc) {
/* Default to disk for older firmware which does not provide storage type */
char *media;
if (!FdtPropertyExist(FDT_NVSTORAGE_TYPE_PROP))
return VbReadNvStorage_disk(vnc);
media = ReadFdtString(FDT_NVSTORAGE_TYPE_PROP);
if (!strcmp(media, "disk"))
return VbReadNvStorage_disk(vnc);
if (!strcmp(media, "mkbp") || !strcmp(media, "flash"))
return VbReadNvStorage_mosys(vnc);
return -1;
}
int VbWriteNvStorage(VbNvContext* vnc) {
/* Default to disk for older firmware which does not provide storage type */
char *media;
if (!FdtPropertyExist(FDT_NVSTORAGE_TYPE_PROP))
return VbWriteNvStorage_disk(vnc);
media = ReadFdtString(FDT_NVSTORAGE_TYPE_PROP);
if (!strcmp(media, "disk"))
return VbWriteNvStorage_disk(vnc);
if (!strcmp(media, "mkbp") || !strcmp(media, "flash"))
return VbWriteNvStorage_mosys(vnc);
return -1;
}
VbSharedDataHeader *VbSharedDataRead(void) {
void *block = NULL;
size_t size = 0;
if (ReadFdtBlock("vboot-shared-data", &block, &size))
return NULL;
VbSharedDataHeader *p = (VbSharedDataHeader *)block;
if (p->magic != VB_SHARED_DATA_MAGIC) {
fprintf(stderr, "%s: failed to validate magic in "
"VbSharedDataHeader (%x != %x)\n",
__FUNCTION__, p->magic, VB_SHARED_DATA_MAGIC);
return NULL;
}
return (VbSharedDataHeader *)block;
}
int VbGetArchPropertyInt(const char* name) {
if (!strcasecmp(name, "fmap_base")) {
return ReadFdtInt("fmap-offset");
} else 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))
return VbGetSystemPropertyInt("devsw_boot");
return VbGetVarGpio("developer-switch");
} else if (!strcasecmp(name, "recoverysw_cur")) {
int value;
value = VbGetPlatformGpioStatus("recovery");
if (value != -1)
return value;
return VbGetVarGpio("recovery-switch");
} else if (!strcasecmp(name, "wpsw_cur")) {
int value;
/* Try finding the GPIO through the chromeos_arm platform device first. */
value = VbGetPlatformGpioStatus("write-protect");
if (value != -1)
return value;
return VbGetVarGpio("write-protect-switch");
} else if (!strcasecmp(name, "recoverysw_ec_boot"))
/* TODO: read correct value using ectool */
return 0;
else
return -1;
}
const char* VbGetArchPropertyString(const char* name, char* dest,
size_t size) {
char *str = NULL;
char *rv = NULL;
char *prop = NULL;
if (!strcasecmp(name,"arch"))
return StrCopy(dest, "arm", size);
/* Properties from fdt */
if (!strcasecmp(name, "ro_fwid"))
prop = "readonly-firmware-version";
else if (!strcasecmp(name, "hwid"))
prop = "hardware-id";
else if (!strcasecmp(name, "fwid"))
prop = "firmware-version";
else if (!strcasecmp(name, "mainfw_type"))
prop = "firmware-type";
else if (!strcasecmp(name, "ecfw_act"))
prop = "active-ec-firmware";
else if (!strcasecmp(name, "ddr_type"))
prop = "ddr-type";
if (prop)
str = ReadFdtString(prop);
if (!strcasecmp(name, "platform_family"))
str = ReadFdtPlatformFamily();
if (str) {
rv = StrCopy(dest, str, size);
free(str);
return rv;
}
return NULL;
}
int VbSetArchPropertyInt(const char* name, int value) {
/* All is handled in arch independent fashion */
return -1;
}
int VbSetArchPropertyString(const char* name, const char* value) {
/* All is handled in arch independent fashion */
return -1;
}
int VbArchInit(void)
{
return 0;
}