blob: 18dc6c72d0a5c8909a002bbb548c6ca1efebfc1e [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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; either version 2 of
* the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <gbb_header.h>
#include <stdio.h>
#include <vboot_api.h>
#include <vboot/screens.h>
#include "base/algorithm.h"
#include "base/cbfs/cbfs.h"
#include "base/graphics.h"
#include "drivers/video/display.h"
#include "vboot/util/gbb.h"
/*
* This is the base used to specify the size and the coordinate of the image.
* For example, height = 40 means 4.0% of the canvas (=drawing area) height.
*/
#define VB_SCALE 1000
#define VB_SCALE_HALF (VB_SCALE / 2) /* 50.0% */
/* Height of the text image per line relative to the canvas size */
#define VB_TEXT_HEIGHT 40 /* 4.0% */
/* Indicate width or height is automatically set based on the other value */
#define VB_SIZE_AUTO 0
/* Height of the icons relative to the canvas size */
#define VB_ICON_HEIGHT 160
/* Vertical position and size of the dividers */
#define VB_DIVIDER_WIDTH 800 /* 80.0% */
#define VB_DIVIDER_V_OFFSET 160 /* 16.0% */
/* Width of the language name */
#define VB_LANGUAGE_WIDTH 100
#define RETURN_ON_ERROR(function_call) do { \
VbError_t rv = (function_call); \
if (rv) \
return rv; \
} while (0)
static char initialized = 0;
static uint32_t locale_count;
static char *supported_locales[256];
static VbError_t draw_image(const char *image_name,
int32_t x, int32_t y, int32_t width, int32_t height,
char pivot)
{
uint8_t *image;
size_t size;
VbError_t rv = VBERROR_SUCCESS;
image = cbfs_get_file_content(CBFS_DEFAULT_MEDIA, image_name,
CBFS_TYPE_RAW, &size);
if (!image)
return VBERROR_NO_IMAGE_PRESENT;
struct scale pos = {
.x = { .n = x, .d = VB_SCALE, },
.y = { .n = y, .d = VB_SCALE, },
};
struct scale dim = {
.x = { .n = width, .d = VB_SCALE, },
.y = { .n = height, .d = VB_SCALE, },
};
rv = draw_bitmap(image, size, &pos, pivot, &dim);
if (rv)
rv = VBERROR_UNKNOWN;
free(image);
return rv;
}
static int draw_image_locale(const char *image_name, uint32_t locale,
int32_t x, int32_t y,
int32_t width, int32_t height, char pivot)
{
char str[256];
snprintf(str, sizeof(str), "locale/%s/%s",
supported_locales[locale], image_name);
return draw_image(str, x, y, width, height, pivot);
}
static VbError_t get_image_size(const char *image_name,
int32_t *width, int32_t *height)
{
uint8_t *image;
size_t size;
VbError_t rv;
image = cbfs_get_file_content(CBFS_DEFAULT_MEDIA, image_name,
CBFS_TYPE_RAW, &size);
if (!image)
return VBERROR_NO_IMAGE_PRESENT;
struct scale dim = {
.x = { .n = *width, .d = VB_SCALE, },
.y = { .n = *height, .d = VB_SCALE, },
};
rv = get_bitmap_dimension(image, size, &dim);
free(image);
if (rv)
return VBERROR_UNKNOWN;
*width = dim.x.n * VB_SCALE / dim.x.d;
*height = dim.y.n * VB_SCALE / dim.y.d;
return VBERROR_SUCCESS;
}
static VbError_t get_image_size_locale(const char *image_name, uint32_t locale,
int32_t *width, int32_t *height)
{
char str[256];
snprintf(str, sizeof(str), "locale/%s/%s",
supported_locales[locale], image_name);
return get_image_size(str, width, height);
}
static int draw_icon(const char *image_name)
{
return draw_image(image_name,
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_ICON_HEIGHT,
PIVOT_H_CENTER|PIVOT_V_BOTTOM);
}
static int draw_text(const char *text, int32_t x, int32_t y,
int32_t height, char pivot)
{
int32_t w, h;
char str[256];
while (*text) {
sprintf(str, "font/idx%03d_%02x.bmp", *text, *text);
w = 0;
h = height;
RETURN_ON_ERROR(get_image_size(str, &w, &h));
RETURN_ON_ERROR(draw_image(str,
x, y, VB_SIZE_AUTO, height, pivot));
x += w;
text++;
}
return VBERROR_SUCCESS;
}
static int get_text_width(const char *text, int32_t *width, int32_t *height)
{
int32_t w, h;
char str[256];
while (*text) {
sprintf(str, "font/idx%03d_%02x.bmp", *text, *text);
w = 0;
h = *height;
RETURN_ON_ERROR(get_image_size(str, &w, &h));
*width += w;
text++;
}
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_footer(uint32_t locale)
{
int32_t x, y, w1, h1, w2, h2;
/*
* Draw help URL line: 'For help visit http://.../'. It consits of
* two parts: 'For help visit', which is locale dependent, and a URL.
* Since the widths vary, we need to get the widths first then calculate
* the horizontal positions of the images.
*/
w1 = 0;
h1 = VB_TEXT_HEIGHT;
RETURN_ON_ERROR(get_image_size_locale("for_help_left.bmp", locale,
&w1, &h1));
w2 = 0;
h2 = VB_TEXT_HEIGHT;
RETURN_ON_ERROR(get_image_size("Url.bmp", &w2, &h2));
/* Calculate horizontal position to centralize the combined images */
x = (VB_SCALE - w1 - w2) / 2;
y = VB_SCALE - VB_DIVIDER_V_OFFSET;
RETURN_ON_ERROR(draw_image_locale("for_help_left.bmp", locale,
x, y, VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_LEFT|PIVOT_V_TOP));
x += w1;
RETURN_ON_ERROR(draw_image("Url.bmp",
x, y, VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_LEFT|PIVOT_V_TOP));
/*
* Draw model line: 'Model XYZ'. It consists of two parts: 'Model',
* which is locale dependent, and 'XYZ', a model name. Model name
* consists of individual font images: 'X' 'Y' 'Z'.
*/
const char *hwid = gbb_read_hwid(NULL);
if (!hwid)
hwid = "NOT FOUND";
w1 = 0;
h1 = VB_TEXT_HEIGHT;
RETURN_ON_ERROR(get_image_size_locale("model.bmp", locale, &w1, &h1));
w2 = 0;
h2 = VB_TEXT_HEIGHT;
RETURN_ON_ERROR(get_text_width(hwid, &w2, &h2));
/* Calculate horizontal position to centralize the combined images */
x = (VB_SCALE - w1 - w2) / 2;
y += VB_TEXT_HEIGHT;
RETURN_ON_ERROR(draw_image_locale("model.bmp", locale,
x, y, VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_LEFT|PIVOT_V_TOP));
x += w1;
RETURN_ON_ERROR(draw_text(hwid, x, y, VB_TEXT_HEIGHT,
PIVOT_H_LEFT|PIVOT_V_TOP));
return VBERROR_SUCCESS;
}
/*
* Draws the language section at the top right corner. The language text image
* is placed in the middle surrounded by arrows on each side.
*/
static VbError_t vboot_draw_language(uint32_t locale)
{
int32_t w, h, x;
/* This width is used to calculate the position of language.bmp */
w = 0;
h = VB_TEXT_HEIGHT;
RETURN_ON_ERROR(get_image_size("arrow_right.bmp", &w, &h));
/*
* Right arrow starts from the right edge of the divider, which is
* positioned horizontally in the center.
*/
x = VB_SCALE_HALF + VB_DIVIDER_WIDTH / 2;
RETURN_ON_ERROR(draw_image("arrow_right.bmp",
x, VB_DIVIDER_V_OFFSET, VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_RIGHT|PIVOT_V_BOTTOM));
/* Since widths of language.bmp vary, we have to use the center pivot */
x -= (w + VB_LANGUAGE_WIDTH / 2);
RETURN_ON_ERROR(draw_image_locale("language.bmp", locale,
x, VB_DIVIDER_V_OFFSET, VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_CENTER|PIVOT_V_BOTTOM));
/* Left arrow starts from the right edge of language.bmp */
x -= VB_LANGUAGE_WIDTH / 2;
RETURN_ON_ERROR(draw_image("arrow_left.bmp",
x, VB_DIVIDER_V_OFFSET, VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_RIGHT|PIVOT_V_BOTTOM));
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_base_screen(uint32_t locale)
{
const struct rgb_color white = { 0xff, 0xff, 0xff };
if (clear_screen(&white))
return VBERROR_UNKNOWN;
RETURN_ON_ERROR(draw_image("chrome_logo.bmp",
(VB_SCALE - VB_DIVIDER_WIDTH)/2,
/* '-10' to keep the logo lifted up from the divider */
VB_DIVIDER_V_OFFSET - 10,
VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_LEFT|PIVOT_V_BOTTOM));
RETURN_ON_ERROR(vboot_draw_language(locale));
RETURN_ON_ERROR(draw_image("divider_top.bmp",
VB_SCALE_HALF, VB_DIVIDER_V_OFFSET,
VB_DIVIDER_WIDTH, VB_SIZE_AUTO,
PIVOT_H_CENTER|PIVOT_V_TOP));
RETURN_ON_ERROR(draw_image("divider_btm.bmp",
VB_SCALE_HALF, VB_SCALE - VB_DIVIDER_V_OFFSET,
VB_DIVIDER_WIDTH, VB_SIZE_AUTO,
PIVOT_H_CENTER|PIVOT_V_BOTTOM));
RETURN_ON_ERROR(vboot_draw_footer(locale));
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_blank(uint32_t locale)
{
video_console_clear();
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_developer_warning(uint32_t locale)
{
RETURN_ON_ERROR(vboot_draw_base_screen(locale));
RETURN_ON_ERROR(draw_icon("VerificationOff.bmp"));
RETURN_ON_ERROR(draw_image_locale("verif_off.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_CENTER|PIVOT_V_TOP));
RETURN_ON_ERROR(draw_image_locale("devmode.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF + VB_TEXT_HEIGHT,
VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_CENTER|PIVOT_V_TOP));
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_recovery_remove(uint32_t locale)
{
RETURN_ON_ERROR(vboot_draw_base_screen(locale));
RETURN_ON_ERROR(draw_image_locale("remove.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF - VB_ICON_HEIGHT,
VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_CENTER|PIVOT_V_BOTTOM));
RETURN_ON_ERROR(draw_image("RemoveDevices.bmp",
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_ICON_HEIGHT * 2,
PIVOT_H_CENTER|PIVOT_V_CENTER));
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_recovery_no_good(uint32_t locale)
{
RETURN_ON_ERROR(vboot_draw_base_screen(locale));
RETURN_ON_ERROR(draw_image_locale("yuck.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_CENTER|PIVOT_V_BOTTOM));
/*
* TODO: We need a mechanism to let boards customize these. For example,
* some boards have only USB.
*/
RETURN_ON_ERROR(draw_image("BadSD.bmp",
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_ICON_HEIGHT,
PIVOT_H_RIGHT|PIVOT_V_TOP));
RETURN_ON_ERROR(draw_image("BadUSB.bmp",
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_ICON_HEIGHT,
PIVOT_H_LEFT|PIVOT_V_TOP));
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_recovery_insert(uint32_t locale)
{
RETURN_ON_ERROR(vboot_draw_base_screen(locale));
RETURN_ON_ERROR(draw_icon("Warning.bmp"));
RETURN_ON_ERROR(draw_image_locale("insert.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_CENTER|PIVOT_V_TOP));
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_recovery_to_dev(uint32_t locale)
{
RETURN_ON_ERROR(vboot_draw_base_screen(locale));
RETURN_ON_ERROR(draw_image_locale("todev.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_TEXT_HEIGHT * 4,
PIVOT_H_CENTER|PIVOT_V_CENTER));
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_developer_to_norm(uint32_t locale)
{
RETURN_ON_ERROR(vboot_draw_base_screen(locale));
RETURN_ON_ERROR(draw_icon("VerificationOff.bmp"));
RETURN_ON_ERROR(draw_image_locale("verif_off.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_CENTER|PIVOT_V_TOP));
RETURN_ON_ERROR(draw_image_locale("tonorm.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF + VB_TEXT_HEIGHT,
VB_SIZE_AUTO, VB_TEXT_HEIGHT * 3,
PIVOT_H_CENTER|PIVOT_V_TOP));
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_wait(uint32_t locale)
{
RETURN_ON_ERROR(vboot_draw_base_screen(locale));
RETURN_ON_ERROR(draw_image_locale("update.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_TEXT_HEIGHT * 3,
PIVOT_H_CENTER|PIVOT_V_CENTER));
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_to_norm_confirmed(uint32_t locale)
{
RETURN_ON_ERROR(vboot_draw_base_screen(locale));
RETURN_ON_ERROR(draw_icon("VerificationOn.bmp"));
RETURN_ON_ERROR(draw_image_locale("verif_on.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_CENTER|PIVOT_V_TOP));
RETURN_ON_ERROR(draw_image_locale("reboot_erase.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF + VB_TEXT_HEIGHT,
VB_SIZE_AUTO, VB_TEXT_HEIGHT,
PIVOT_H_CENTER|PIVOT_V_TOP));
return VBERROR_SUCCESS;
}
static VbError_t vboot_draw_os_broken(uint32_t locale)
{
RETURN_ON_ERROR(vboot_draw_base_screen(locale));
RETURN_ON_ERROR(draw_image_locale("os_broken.bmp", locale,
VB_SCALE_HALF, VB_SCALE_HALF,
VB_SIZE_AUTO, VB_TEXT_HEIGHT * 2,
PIVOT_H_CENTER|PIVOT_V_CENTER));
return VBERROR_SUCCESS;
}
/* we may export this in the future for the board customization */
struct vboot_screen_descriptor {
uint32_t id; /* VB_SCREEN_* */
VbError_t (*draw)(uint32_t locale); /* draw function */
const char *mesg; /* fallback message */
};
static const struct vboot_screen_descriptor vboot_screens[] = {
{
.id = VB_SCREEN_BLANK,
.draw = vboot_draw_blank,
.mesg = NULL,
},
{
.id = VB_SCREEN_DEVELOPER_WARNING,
.draw = vboot_draw_developer_warning,
.mesg = "OS verification is OFF\n"
"Press SPACE to re-enable.\n",
},
{
.id = VB_SCREEN_RECOVERY_REMOVE,
.draw = vboot_draw_recovery_remove,
.mesg = "Please remove all external devices to begin recovery\n",
},
{
.id = VB_SCREEN_RECOVERY_NO_GOOD,
.draw = vboot_draw_recovery_no_good,
.mesg = "The device you inserted does not contain Chrome OS.\n",
},
{
.id = VB_SCREEN_RECOVERY_INSERT,
.draw = vboot_draw_recovery_insert,
.mesg = "Chrome OS is missing or damaged.\n"
"Please insert a recovery USB stick or SD card.\n",
},
{
.id = VB_SCREEN_RECOVERY_TO_DEV,
.draw = vboot_draw_recovery_to_dev,
.mesg = "To turn OS verificaion OFF, press ENTER.\n"
"Your system will reboot and local data will be cleared.\n"
"To go back, press ESC.\n",
},
{
.id = VB_SCREEN_DEVELOPER_TO_NORM,
.draw = vboot_draw_developer_to_norm,
.mesg = "OS verification is OFF\n"
"Press ENTER to confirm you wish to turn OS verification on.\n"
"Your system will reboot and local data will be cleared.\n"
"To go back, press ESC.\n",
},
{
.id = VB_SCREEN_WAIT,
.draw = vboot_draw_wait,
.mesg = "Your system is applying a critical update.\n"
"Please do not turn off.\n",
},
{
.id = VB_SCREEN_TO_NORM_CONFIRMED,
.draw = vboot_draw_to_norm_confirmed,
.mesg = "OS verification is ON\n"
"Your system will reboot and local data will be cleared.\n",
},
{
.id = VB_SCREEN_OS_BROKEN,
.draw = vboot_draw_os_broken,
.mesg = "Chrome OS may be broken.\n"
"Remove media and initiate recovery.\n",
},
};
static const struct vboot_screen_descriptor *get_screen_descriptor(uint32_t id)
{
int i;
for (i = 0; i < ARRAY_SIZE(vboot_screens); i++) {
if (vboot_screens[i].id == id)
return &vboot_screens[i];
}
return NULL;
}
static void print_fallback_message(const struct vboot_screen_descriptor *desc)
{
const struct rgb_color white = { 0xff, 0xff, 0xff };
if (desc->mesg)
graphics_print_single_text_block(desc->mesg, &white, 0, 15,
VIDEO_PRINTF_ALIGN_CENTER);
else
clear_screen(&white);
}
static VbError_t draw_screen(uint32_t screen_type, uint32_t locale)
{
VbError_t rv = VBERROR_UNKNOWN;
const struct vboot_screen_descriptor *desc;
desc = get_screen_descriptor(screen_type);
if (!desc) {
printf("Not a valid screen type: 0x%x\n", screen_type);
return VBERROR_INVALID_SCREEN_INDEX;
}
if (locale >= locale_count) {
printf("Unsupported locale (%d)\n", locale);
print_fallback_message(desc);
return VBERROR_INVALID_PARAMETER;
}
/* if no drawing function is registered, fallback msg will be printed */
if (desc->draw)
rv = desc->draw(locale);
if (rv)
print_fallback_message(desc);
return rv;
}
static void vboot_init_locale(void)
{
char *locales, *loc_start, *loc;
size_t size;
locale_count = 0;
/* Load locale list from cbfs */
locales = cbfs_get_file_content(CBFS_DEFAULT_MEDIA, "locales",
CBFS_TYPE_RAW, &size);
if (!locales || !size) {
printf("%s: locale list not found", __func__);
return;
}
/* Copy the file and null-terminate it */
loc_start = malloc(size + 1);
if (!loc_start) {
printf("%s: out of memory\n", __func__);
free(locales);
return;
}
memcpy(loc_start, locales, size);
loc_start[size] = '\0';
/* Parse the list */
printf("%s: Supported locales:", __func__);
loc = loc_start;
while (loc - loc_start < size
&& locale_count < ARRAY_SIZE(supported_locales)) {
char *lang = strsep(&loc, "\n");
if (!lang || !strlen(lang))
break;
printf(" %s,", lang);
supported_locales[locale_count] = lang;
locale_count++;
}
free(locales);
printf(" (%d locales)\n", locale_count);
}
static VbError_t vboot_init_screen(void)
{
vboot_init_locale();
if (graphics_init())
return VBERROR_UNKNOWN;
initialized = 1;
return VBERROR_SUCCESS;
}
int vboot_draw_screen(uint32_t screen, uint32_t locale)
{
static uint32_t current_screen = VB_SCREEN_BLANK;
static uint32_t current_locale = 0;
printf("%s: screen=0x%x locale=%d\n", __func__, screen, locale);
if (!initialized) {
if (vboot_init_screen())
return VBERROR_UNKNOWN;
}
/* If requested screen is the same as the current one, we're done. */
if (screen == current_screen && locale == current_locale)
return VBERROR_SUCCESS;
/* If the screen is blank, turn off the backlight; else turn it on. */
backlight_update(VB_SCREEN_BLANK == screen ? 0 : 1);
/* TODO: draw only locale dependent part if current_screen == screen */
RETURN_ON_ERROR(draw_screen(screen, locale));
current_screen = screen;
current_locale = locale;
return VBERROR_SUCCESS;
}
int vboot_get_locale_count(void)
{
if (!initialized) {
if (vboot_init_screen())
return VBERROR_UNKNOWN;
}
return locale_count;
}