blob: 67239f2ce9874882bc8b742cc9deaca435b4e5bc [file] [log] [blame]
/******************************************************************************
*
* Copyright 2011, Cypress Semiconductor Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the Cypress Semiconductor Corporation, nor the names
* of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Cypress Semiconductor Corporation ''AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Cypress Semiconductor Corporation
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of Cypress Semiconductor Corporation.
*
*******************************************************************************
*
* Authors:
* Dudley Du <dudl@cypress.com>
* Usage:
* Used to update Cypress Trackpad device firmware image from .iic or
* .bin files.
* e.g., update Cypress Trackpad device with new "new_image.iic" file:
* $ sudo ./cyapa_fw_update new_image.iic -f
* Version:
* 1.0.0 2011/07/26 Initial release of cyapa_fw_update utility tool.
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <libgen.h>
#include <linux/limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "cyapa.h"
#ifdef DBG
#define prt_info(fmt, ...) printf(fmt, ##__VA_ARGS__)
#define prt_warn(fmt, ...) printf("WARNING: " fmt, ##__VA_ARGS__)
#define prt_err(fmt, ...) printf("ERROR: " fmt, ##__VA_ARGS__)
#define prt_dbg(fmt, ...) printf("DEBUG: %d: " fmt, __LINE__, ##__VA_ARGS__)
#define DBG_DUMP_DATA_BLOCK 1
#else
#define prt_info(fmt, ...) printf(fmt, ##__VA_ARGS__)
#define prt_warn(fmt, ...) printf("WARNING: " fmt, ##__VA_ARGS__)
#define prt_err(fmt, ...) printf("ERROR: " fmt, ##__VA_ARGS__)
#define prt_dbg(fmt, ...)
#define DBG_DUMP_DATA_BLOCK 0
#endif
#define CYAPA_FW_UPDATE_VER "1.0.0"
#define DEFAULT_PROGRAM_NAME "cyapa_fw_update"
#define DEFAULT_FW_BAK_FOLDER "/tmp/cypress"
#define DEFAULT_FW_BAK_IMAGE_NAME "/tmp/cypress/cyapa_bak_firmware.bin"
#define FILE_TYPE_IIC 0
#define FILE_TYPE_BIN 1
#define CYAPA_FW_OFFSET_START 0x0780
#define CYAPA_FW_CHECKSUM_END 0x07FF
#define CYAPA_FW_OFFSET_END 0x7FFF
#define CYAPA_FW_SIZE (CYAPA_FW_OFFSET_END - \
CYAPA_FW_OFFSET_START + 1)
#define CYAPA_BAK_READ_BLOCK_LEN 16
#define CYAPA_FW_BLOCK_LEN 64
#define CYAPA_FW_BLOCK_COUNT (CYAPA_FW_SIZE / CYAPA_FW_BLOCK_LEN)
#define CYAPA_FW_START_BLOCK (CYAPA_FW_OFFSET_START / CYAPA_FW_BLOCK_LEN)
#define CYAPA_IIC_CMD_OP_NONE 0
#define CYAPA_IIC_CMD_OP_READ 1
#define CYAPA_IIC_CMD_OP_WRITE 2
#define CYAPA_IIC_CMD_OP_DELAY 3
#define CYAPA_IIC_CMD_OP_tries_WRITE 4
/*
* CYAPA firmware starts at an absolute offset.
* We remove this offset when converting between firmware address and binary
* file locations.
*/
#define CYAPA_FW_TO_BIN(x) ((x) - CYAPA_FW_OFFSET_START)
#define CYAPA_BIN_TO_FW(x) ((x) + CYAPA_FW_OFFSET_START)
#define CYAPA_IDAC_0_START 0x7900
#define CYAPA_IDAC_0_END 0x797F
#define CYAPA_IDAC_0_SIZE (CYAPA_IDAC_0_END - CYAPA_IDAC_0_START + 1)
#define CYAPA_IDAC_0_DEF 0x08
#define CYAPA_IDAC_1_START 0x7A00
#define CYAPA_IDAC_1_END 0x7A7F
#define CYAPA_IDAC_1_SIZE (CYAPA_IDAC_1_END - CYAPA_IDAC_1_START + 1)
#define CYAPA_IDAC_1_DEF 0x08
#define CYAPA_IDAC_2_START 0x7B00
#define CYAPA_IDAC_2_END 0x7BFF
#define CYAPA_IDAC_2_SIZE (CYAPA_IDAC_2_END - CYAPA_IDAC_2_START + 1)
#define CYAPA_IDAC_2_DEF 0x90
struct args {
/* the value should be FILE_TYPE_IIC or FILE_TYPE_BIN. */
int file_type;
bool backup_fw;
bool force;
bool convert;
const char *new_fw_image;
const char *bak_fw_image;
int fd_dev;
int fd_new_fw;
int fd_bak_fw;
};
/*
* command format:
* write data:
* byte 0 : operation. CYAPA_IIC_CMD_OP_WRITE
* byte 1 : length of behind real command data, from byte 2 to byte n.q
* byte 2 - byte n : real command as show in .iic file.
* byte 2 : I2C address, 0x67 for trackpad.
* byte 3 : I2C registers offset where byte 4 -byte n should be written.
* usually 0x00 for trackpad.
* byte 4 - byte n : command data to be written to trackpad.
* byte 4 : index, 0x00, 0x10, 0x20, 0x30, 0x40.
*
* read status:
* byte 0 : operation. CYAPA_IIC_CMD_OP_READ
* byte 1 : read length.
*
* delay:
* byte 0 : operation. CYAPA_IIC_CMD_OP_DELAY
* byte 1 - byte 4 : delay milliseconds (int).
*/
struct cmds_update_block {
int valid_cmds;
unsigned char cmds[8][24];
};
/* Global buffer for storing firmware image */
unsigned char fw_buf[CYAPA_FW_SIZE];
#if DBG_DUMP_DATA_BLOCK
void cyapa_dump_data_block(const char *str, unsigned char *buf,
unsigned int offset, int length)
{
char strbuf[2048];
unsigned int rest_len = sizeof(strbuf);
char *p = strbuf;
int i, len;
memset(strbuf, 0, sizeof(strbuf));
len = snprintf(p, rest_len, "%s: offset 0x%04X, %d bytes:",
str, offset, length);
rest_len -= len;
p += len;
for (i = 0; i < length && rest_len; i++, p += len, rest_len -= len) {
len = snprintf(p, rest_len, " %02X",
*(unsigned char *)(buf + i));
}
prt_info("%s\n", strbuf);
}
void cyapa_dump_delay_time(const char *str, int delay)
{
prt_info("%s: delay [%d] ms\n", str, delay);
}
#else
void cyapa_dump_data_block(const char *str, unsigned char *buf,
unsigned int offset, int length) {}
void cyapa_dump_delay_time(const char *str, int delay) {}
#endif
void show_usage(char *name)
{
printf("Usage: %s <new-firmware-image> [options]\n",
name ? name : DEFAULT_PROGRAM_NAME);
printf("Options:\n");
printf("\t -b, --backup\n");
printf("\t Backup current firmware before updating"
" to new firmware.\n");
printf("\t If -o option is not set, the default back up"
" path is \"/tmp/cypress\".\n");
printf("\t If -o option is set, use the path specified in"
"-o <path>.\n");
printf("\t -c, --convert <iic-path>\n");
printf("\t Convert new-firmware-image (.iic) to a .bin file.\n");
printf("\t Does not actually write firmware to the device.\n");
printf("\t Use --output to specify the destination path.\n");
printf("\t -o, --output <path>\n");
printf("\t Specifies full path of the output file for where to\n");
printf("\t back up a copy of the current trackpad firmware.\n");
printf("\t Only valid when option -b is set and\n");
printf("\t filename must have a \'bin\' extension.\n");
printf("\t By default, without -o option, the back up path is:\n");
printf("\t %s\n", DEFAULT_FW_BAK_IMAGE_NAME);
printf("\t If the requested update fails, this backed up copy\n");
printf("\t will be rewritten to the trackpad.\n");
printf("\t -f, --force\n");
printf("\t Force new firmware to be updated to trackpad device\n");
printf("\t and suppress any prompt information.\n");
printf("\t -v, --version\n");
printf("\t Print version information and exit.\n");
printf("\t -h, --help\n");
printf("\t Show this help information.\n");
printf("NOTE:\n");
printf(" This program must be executed as root\n");
printf(" cyapa_fw_update program release version: %s\n\n",
CYAPA_FW_UPDATE_VER);
}
void show_version_info(char *name)
{
printf("Cypress utility: %s %s\n\n", name, CYAPA_FW_UPDATE_VER);
printf("Copyright (C) 2011 Cypress Semiconductor Corporation.\n");
printf("License BSD 2-Clause License or later.\n");
printf("This is free software: you are free to change and"
" redistribute it.\n");
printf("There is NO WARRANTY, to the extent permitted by law.\n\n");
}
static struct option options[] = {
{"backup", no_argument, NULL, 'b'},
{"convert", no_argument, NULL, 'c'},
{"force", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"output", required_argument, NULL, 'o'},
{"version", no_argument, NULL, 'v'},
{0, 0, 0, 0}
};
bool ends_with(const char* name, const char* ext)
{
size_t nlen = strlen(name);
size_t elen = strlen(ext);
return (nlen >= elen && !strcmp(&name[nlen-elen], ext));
}
/**
* return value:
* 0 - parse and get valid input parameters.
* 1 - show program usage information.
* 2 - show program version information.
* < 0 - failed to parse and check input parameters.
*/
int check_input_args(int argc, char **argv, struct args *args)
{
int c;
while (1) {
int index = 0;
c = getopt_long(argc, argv, "bcfho:v", options, &index);
if (c == -1)
break;
switch (c) {
case 'b':
args->backup_fw = true;
break;
case 'c':
args->convert = true;
break;
case 'f':
args->force = true;
break;
case 'o':
args->bak_fw_image = optarg;
break;
case 'v':
return 2;
default:
return 1;
}
}
/* The last option should be the path of a new firmware image */
if (optind < argc)
args->new_fw_image = argv[optind++];
if (args->new_fw_image) {
if (ends_with(args->new_fw_image, ".iic"))
args->file_type = FILE_TYPE_IIC;
else if (ends_with(args->new_fw_image, ".bin"))
args->file_type = FILE_TYPE_BIN;
else
return 1;
}
if (args->backup_fw && !args->bak_fw_image) {
int fd;
args->bak_fw_image = DEFAULT_FW_BAK_IMAGE_NAME;
mkdir(DEFAULT_FW_BAK_FOLDER, 0777);
remove(args->bak_fw_image);
fd = creat(args->bak_fw_image, O_CREAT |
O_TRUNC | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
if (fd < 0)
return 1;
close(fd);
}
if (args->new_fw_image)
prt_info("Update new firmware image from: %s\n",
args->new_fw_image);
if (args->backup_fw && args->backup_fw)
prt_info("Backup firmware image from trackpad device to: %s\n",
args->bak_fw_image);
return 0;
}
#define MAX_PROGRESS_LENGTH 110
#define DEFAULT_TERM_WIDTH 80
int get_terminal_width(void)
{
int ret;
struct winsize sz;
ret = ioctl(0, TIOCGWINSZ, &sz);
if (ret < 0)
return DEFAULT_TERM_WIDTH;
else
return (sz.ws_col > MAX_PROGRESS_LENGTH) ?
MAX_PROGRESS_LENGTH :
((sz.ws_col < 20) ? DEFAULT_TERM_WIDTH : sz.ws_col);
}
/**
* Output progress format:
* |----------------------------- | ddd% |
*/
#define SHOW_PROGRESS_INIT 0
#define SHOW_PROGRESS_CONT 1
#define SHOW_PROGRESS_EXIT 2
void show_progress(int percent, int state, char *str)
{
int i;
int term_width;
int progress_count;
char buf[MAX_PROGRESS_LENGTH];
static int last_str_len;
#if DBG_DUMP_DATA_BLOCK
/* when enable dumping data, disable show progress. */
return;
#endif
if (state == SHOW_PROGRESS_INIT) {
prt_info("\r");
if (str != NULL)
prt_info("%s", str);
last_str_len = 0;
return;
} else if (state == SHOW_PROGRESS_EXIT) {
prt_info("\n");
if (str != NULL)
prt_info("%s", str);
last_str_len = 0;
return;
}
if (state != SHOW_PROGRESS_CONT)
return;
fflush(stdout);
for (i = 0; i < last_str_len; i++)
prt_info("\b");
percent = (percent < 0) ? 0 : ((percent > 100) ? 100 : percent);
term_width = get_terminal_width();
progress_count = percent * (term_width - 10) / 100;
memset(buf, ' ', term_width);
buf[0] = '|';
memset(&buf[1], '-', progress_count);
buf[term_width - 9] = '|';
sprintf(&buf[term_width - 7], "%3d", percent);
buf[term_width - 4] = '%';
buf[term_width - 2] = '|';
buf[term_width - 1] = '\0';
prt_info("%s", buf);
last_str_len = strlen(buf);
fflush(stdout);
}
/*
* sleep "msec" milliseconds.
*/
void msleep(unsigned int msec)
{
struct timespec ts;
int rc;
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec - (ts.tv_sec * 1000)) * 1000000;
do {
rc = nanosleep(&ts, &ts);
} while (rc == -1 && errno == EINTR);
}
#define TIMER_START 0
#define TIMER_STOP 1
void calculate_duration_time(int start_stop, const char *str)
{
static struct timeval last;
struct timeval tv;
struct timeval duration;
gettimeofday(&tv, NULL);
if (start_stop == TIMER_START) {
last = tv;
return;
}
if (start_stop != TIMER_STOP)
return;
timersub(&tv, &last, &duration);
prt_info("%s: %ld.%06ld seconds\n", str ?: "Duration time",
duration.tv_sec, duration.tv_usec);
last = tv;
}
unsigned char cyapa_calculate_checksum(unsigned char *buf, int count)
{
int i;
unsigned char checksum = 0;
for (i = 0; i < count; i++)
checksum += buf[i];
return checksum;
}
int cyapa_read_reg(int fd, unsigned int offset, int bytes, unsigned char *buf)
{
int ret;
int tries = 3;
do {
ret = (int)lseek(fd, (off_t)offset, SEEK_SET);
if (ret < 0)
continue;
ret = (int)read(fd, buf, (ssize_t)bytes);
if (ret == bytes)
break;
} while (tries--);
if (tries < 0)
return -1;
return ret;
}
int cyapa_write_reg(int fd, unsigned char *buf, unsigned int offset, int bytes)
{
int ret;
int tries = 3;
do {
ret = (int)lseek(fd, (off_t)offset, SEEK_SET);
if (ret < 0)
continue;
ret = (int)write(fd, buf, (ssize_t)bytes);
if (ret == bytes)
break;
} while (tries--);
if (tries < 0)
return -1;
return ret;
}
int open_trackpad_dev(struct args *args)
{
char dev_name[64];
sprintf(dev_name, "/dev/%s", CYAPA_MISC_NAME);
args->fd_dev = open(dev_name, O_RDWR);
if (args->fd_dev < 0) {
prt_err("Cannot open device %s:\n", dev_name);
perror(NULL);
return -EFAULT;
}
if (lseek(args->fd_dev, 0, SEEK_SET) < 0) {
prt_err("Cannot access device %s\n", dev_name);
perror(NULL);
close(args->fd_dev);
return -errno;
}
return 0;
}
int open_new_fw_image(struct args *args)
{
args->fd_new_fw = open(args->new_fw_image, O_RDONLY);
if (args->fd_new_fw < 0) {
prt_err("Cannot open firmware image %s\n", args->new_fw_image);
perror(NULL);
return -EFAULT;
}
return 0;
}
int open_bak_fw_image(struct args *args)
{
args->fd_bak_fw = open(args->bak_fw_image,
O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (args->fd_bak_fw < 0) {
prt_err("Cannot open backup file %s\n", args->bak_fw_image);
perror(NULL);
return -EFAULT;
}
return 0;
}
int cyapa_fw_image_check(struct args *args)
{
int ret;
int tries = 3;
int ret_bytes;
unsigned int offset = 0;
unsigned char buf[64];
int fd = args->fd_new_fw;
const char iic_identify[] = "00 00 FF 38 00 01 02 03 04 05 06 07";
ret_bytes = 0;
while ((ret_bytes != 64) && (tries-- > 0)) {
ret = (int)lseek(fd, (off_t)offset, SEEK_SET);
if (ret != (int)offset)
continue;
memset(buf, 0, sizeof(buf));
ret_bytes = (int)read(fd, buf, 64);
}
if (tries < 0)
return -1;
if (args->file_type == FILE_TYPE_BIN) {
if (buf[0x28] != 0xC0 || buf[0x29] != 0xC1 || buf[0x2A] != 0xC2)
return -2;
} else {
if (strncmp(iic_identify, (const char *)&buf[5],
strlen(iic_identify)))
return -3;
}
/* reset file cursor to the start of the image file. */
tries = 3;
do {
if (lseek(args->fd_new_fw, (off_t)offset, SEEK_SET) < 0)
continue;
else
break;
} while (tries--);
if (tries < 0)
return -4;
return 0;
}
int cyapa_get_trackpad_run_mode(int fd,
struct cyapa_trackpad_run_mode *run_mode)
{
struct cyapa_misc_ioctl_data ioctl_data;
memset(&ioctl_data, 0, sizeof(struct cyapa_misc_ioctl_data));
ioctl_data.buf = (__u8 *)run_mode;
ioctl_data.len = sizeof(struct cyapa_trackpad_run_mode);
if (ioctl(fd, CYAPA_GET_TRACKPAD_RUN_MODE, &ioctl_data) < 0) {
prt_dbg("Failed to get trackpad run mode state, %d\n", -errno);
return -errno;
}
prt_dbg("GET: run_mode = %d, bootloader_state = %d\n",
run_mode->run_mode, run_mode->bootloader_state);
return 0;
}
int cyapa_set_trackpad_run_mode(int fd,
struct cyapa_trackpad_run_mode *run_mode)
{
struct cyapa_misc_ioctl_data ioctl_data;
memset(&ioctl_data, 0, sizeof(struct cyapa_misc_ioctl_data));
ioctl_data.buf = (__u8 *)run_mode;
ioctl_data.len = sizeof(struct cyapa_trackpad_run_mode);
if (ioctl(fd, CYAYA_SEND_MODE_SWITCH_CMD, &ioctl_data) < 0) {
prt_dbg("failed to set trackpad device run mode state, %d\n",
--errno);
return -errno;
}
prt_dbg("SET: run_mode = %d, bootloader_state = %d\n",
run_mode->run_mode, run_mode->bootloader_state);
return 0;
}
void cyapa_recovery_bootload_header(struct args *args)
{
unsigned char cmd[] = {0x00, 0xFF, 0x3C,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x07, 0x80};
cyapa_write_reg(args->fd_dev, cmd, 0, sizeof(cmd));
msleep(10);
}
int cyapa_set_bootloader_idle_mode(struct args *args)
{
int tries = 3;
int fd = args->fd_dev;
struct cyapa_trackpad_run_mode run_mode;
while (tries-- > 0) {
memset(&run_mode, 0, sizeof(struct cyapa_trackpad_run_mode));
if (cyapa_get_trackpad_run_mode(fd, &run_mode) < 0)
continue;
if ((run_mode.bootloader_state == CYAPA_BOOTLOADER_IDLE_STATE)
&& (run_mode.run_mode == CYAPA_BOOTLOADER_MODE))
break; /* already in correct state. */
if (run_mode.run_mode == CYAPA_OPERATIONAL_MODE) {
run_mode.rev_cmd = CYAPA_CMD_APP_TO_IDLE;
if (cyapa_set_trackpad_run_mode(fd, &run_mode) < 0)
continue;
msleep(300);
} else if ((run_mode.run_mode == CYAPA_BOOTLOADER_MODE)
&& (run_mode.bootloader_state ==
CYAPA_BOOTLOADER_ACTIVE_STATE)) {
run_mode.rev_cmd = CYAPA_CMD_ACTIVE_TO_IDLE;
if (cyapa_set_trackpad_run_mode(fd, &run_mode) < 0)
continue;
msleep(300);
} else {
/*
* unknown trackpad states.
* try to recovery bootloader header registers.
*/
cyapa_recovery_bootload_header(args);
continue;
}
}
if (tries < 0)
return -1;
return 0;
}
int cyapa_set_bootloader_active_mode(struct args *args)
{
int tries = 3;
int fd = args->fd_dev;
struct cyapa_trackpad_run_mode run_mode;
while (tries-- > 0) {
memset(&run_mode, 0, sizeof(struct cyapa_trackpad_run_mode));
if (cyapa_get_trackpad_run_mode(fd, &run_mode) < 0)
continue;
if ((run_mode.bootloader_state == CYAPA_BOOTLOADER_ACTIVE_STATE)
&& (run_mode.run_mode == CYAPA_BOOTLOADER_MODE))
break; /* already in correct state. */
if (run_mode.run_mode == CYAPA_OPERATIONAL_MODE) {
if (cyapa_set_bootloader_idle_mode(args) < 0)
continue;
} else if ((run_mode.run_mode == CYAPA_BOOTLOADER_MODE)
&& (run_mode.bootloader_state ==
CYAPA_BOOTLOADER_IDLE_STATE)) {
run_mode.rev_cmd = CYAPA_CMD_IDLE_TO_ACTIVE;
if (cyapa_set_trackpad_run_mode(fd, &run_mode) < 0)
continue;
msleep(12000);
break;
} else {
/*
* unknown trackpad states.
* try to recovery bootloader header registers.
*/
cyapa_recovery_bootload_header(args);
continue;
}
}
if (tries < 0)
return -1;
return 0;
}
int cyapa_set_app_operational_mode(struct args *args)
{
int tries = 3;
int fd = args->fd_dev;
struct cyapa_trackpad_run_mode run_mode;
while (tries-- > 0) {
memset(&run_mode, 0, sizeof(struct cyapa_trackpad_run_mode));
if (cyapa_get_trackpad_run_mode(fd, &run_mode) < 0)
continue;
if (run_mode.run_mode == CYAPA_OPERATIONAL_MODE)
break; /* already in correct state. */
if ((run_mode.run_mode == CYAPA_BOOTLOADER_MODE)
&& (run_mode.bootloader_state ==
CYAPA_BOOTLOADER_IDLE_STATE)) {
run_mode.rev_cmd = CYAPA_CMD_IDLE_TO_APP;
if (cyapa_set_trackpad_run_mode(fd, &run_mode) < 0)
continue;
msleep(300);
} else if ((run_mode.run_mode == CYAPA_BOOTLOADER_MODE)
&& (run_mode.bootloader_state ==
CYAPA_BOOTLOADER_ACTIVE_STATE)) {
run_mode.rev_cmd = CYAPA_CMD_ACTIVE_TO_IDLE;
if (cyapa_set_trackpad_run_mode(fd, &run_mode) < 0)
continue;
msleep(300);
} else {
/*
* unknown trackpad states.
* try to recovery bootloader header registers.
*/
cyapa_recovery_bootload_header(args);
continue;
}
}
if (tries < 0)
return -1;
return 0;
}
int cyapa_get_firmware_version(struct args *args,
struct cyapa_firmware_ver *fw_version)
{
int ret = 0;
int tries = 3;
int fd = args->fd_dev;
struct cyapa_misc_ioctl_data ioctl_data;
struct cyapa_trackpad_run_mode run_mode;
unsigned char buf[32];
do {
memset(&run_mode, 0, sizeof(struct cyapa_trackpad_run_mode));
ret = cyapa_get_trackpad_run_mode(fd, &run_mode);
if (ret == 0)
break;
cyapa_recovery_bootload_header(args);
} while (tries--);
if (ret < 0)
goto error;
if (run_mode.run_mode == CYAPA_OPERATIONAL_MODE) {
memset(&ioctl_data, 0, sizeof(struct cyapa_misc_ioctl_data));
ioctl_data.buf = (__u8 *)fw_version;
ioctl_data.len = sizeof(struct cyapa_firmware_ver);
if (ioctl(args->fd_dev,
CYAPA_GET_FIRMWARE_VER, &ioctl_data) < 0)
goto error;
return 0;
}
/*
* firmware working in bootload mode.
* try to get firmware version from bootload header.
*/
memset(buf, 0, sizeof(buf));
ret = cyapa_read_reg(fd, 0, 16, buf);
if ((ret < 0) || ((buf[0x01] & 0x10) != 0x10))
goto error;
/* have valid bootload head and firmware version. */
if ((buf[0x0D] == 0xC0) && (buf[0x0E] == 0xC1) && (buf[0x0F] == 0xC2)) {
fw_version->major_ver = buf[0x0B];
fw_version->minor_ver = buf[0x0C];
} else
goto error;
return 0;
error:
fw_version->major_ver = 0;
fw_version->minor_ver = 0;
prt_warn("Unknown trackpad device firmware version.\n");
return ret;
}
int cyapa_get_protocol_version(struct args *args)
{
struct cyapa_misc_ioctl_data ioctl_data;
struct cyapa_protocol_ver protocol_gen;
memset(&ioctl_data, 0, sizeof(struct cyapa_misc_ioctl_data));
ioctl_data.buf = (__u8 *)&protocol_gen;
ioctl_data.len = sizeof(struct cyapa_protocol_ver);
if (ioctl(args->fd_dev, CYAPA_GET_PROTOCOL_VER, &ioctl_data) < 0)
return 0;
return (int)protocol_gen.protocol_gen;
}
/*
* Read a block of firmware bytes from trackpad and store them in the supplied
* buffer.
*/
int cyapa_read_fw_block(int fd, unsigned short offset, unsigned char *buf,
int len)
{
int tries = 3;
unsigned char cmd_str[] = {0x00, 0xFF, 0x3C,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
unsigned char read_cmd[sizeof(cmd_str) + 2];
unsigned char read_buf[len];
unsigned char verify_buf[len];
if (len > 16) {
prt_err("Can't read more then 16 bytes firmware each time.\n");
return -1;
}
memcpy(read_cmd, cmd_str, sizeof(cmd_str));
/* big endian for the value of offset when sending command. */
read_cmd[sizeof(cmd_str)] = offset >> 8;
read_cmd[sizeof(cmd_str) + 1] = offset;
cyapa_dump_data_block("TP: read cmd", read_cmd, 0, sizeof(read_cmd));
/*
* since trackpad doesn't protect i2c data with a CRC,
* verify data by reading it twice.
*/
while (tries-- > 0) {
cyapa_write_reg(fd, read_cmd, 0, sizeof(read_cmd));
msleep(1);
cyapa_read_reg(fd, 16, len, read_buf);
cyapa_write_reg(fd, read_cmd, 0, sizeof(read_cmd));
msleep(1);
cyapa_read_reg(fd, 16, len, verify_buf);
if (!memcmp(read_buf, verify_buf, len))
break;
}
if (tries < 0)
return -2;
memcpy(buf, read_buf, len);
cyapa_dump_data_block("TP: read data", buf, offset, len);
return len;
}
/*
* Some values read from the trackpad are volatile and must be restored to
* defaults before being used to write back as firmware.
*
* Assumes: sizeof buffer >= CYAPA_FW_SIZE
*/
void cyapa_fixup_fw_buffer(unsigned char* buf)
{
/*
* Set IDAC regions in the image to a default pattern.
* The values read from the device are dynamically generated at runtime
* during calibration, and should be ignored. Setting these regions
* to the pattern will ensure the resulting fw backup image has the
* correct checksum.
*/
memset(&buf[CYAPA_FW_TO_BIN(CYAPA_IDAC_0_START)], CYAPA_IDAC_0_DEF,
CYAPA_IDAC_0_SIZE);
memset(&buf[CYAPA_FW_TO_BIN(CYAPA_IDAC_1_START)], CYAPA_IDAC_1_DEF,
CYAPA_IDAC_1_SIZE);
memset(&buf[CYAPA_FW_TO_BIN(CYAPA_IDAC_2_START)], CYAPA_IDAC_2_DEF,
CYAPA_IDAC_2_SIZE);
}
int cyapa_backup_fw_from_trackpad(struct args *args)
{
int i;
int ret;
int offset;
int len;
int total;
ret = cyapa_set_bootloader_idle_mode(args);
if (ret < 0) {
prt_err("Failed to reset trackpad device, unable switch to "
"firmware bootloader idle state, %d.\n",
ret);
return -1;
}
calculate_duration_time(TIMER_START, NULL);
show_progress(0, SHOW_PROGRESS_INIT,
"Backup firmware from trackpad device in progress:\n");
show_progress(0, SHOW_PROGRESS_CONT, NULL);
total = CYAPA_FW_SIZE;
offset = CYAPA_FW_OFFSET_START;
len = CYAPA_BAK_READ_BLOCK_LEN;
for (i = 0; i < total; i += len) {
ret = cyapa_read_fw_block(args->fd_dev, offset + i,
&fw_buf[i], len);
if (ret < 0) {
prt_err("Failed to read firmware image, %d.\n", ret);
return -2;
}
show_progress(100 * (i+1) / total, SHOW_PROGRESS_CONT, NULL);
}
cyapa_fixup_fw_buffer(fw_buf);
/* store read data to output file. */
ret = write(args->fd_bak_fw, fw_buf, total);
if (ret != total) {
prt_err("Failed to write backup firmware image.\n");
perror(NULL);
return -3;
}
show_progress(0, SHOW_PROGRESS_EXIT,
"Backup firmware from trackpad device done.\n");
calculate_duration_time(TIMER_STOP,
"Backup firmware form trackpad device duration time");
return total;
}
/*
* routines for updating trackpad firmware from .iic firmware image file.
*/
int string_to_hex(const char *str)
{
int i, j;
int len = strlen(str);
char c = 0;
int d = 0;
int hex = 0;
for (j = 0, i = len - 1; i >= 0; j++, i--) {
c = tolower(*(str + i));
if ((c >= '0') && (c <= '9'))
d = c - '0';
else
d = c - 'a' + 10;
hex += (d << 4 * j);
}
return hex;
}
int cyapa_iic_cmd_to_data(char *iic_buf, int *cmd_op,
unsigned char *outbuf, int *cmd_data_len)
{
int i = 0;
int j = 0;
char *p = NULL;
char *tmpp = NULL;
char hexstr[4];
char buf[128];
memset(buf, 0, sizeof(buf));
strcpy(buf, iic_buf);
p = buf;
/* skip white-space if exists for the head of the string. */
while (*p == ' ')
p++;
/* delay specific time command. */
if (*p == '[' && !strncmp((p + 1), "delay=", 6)) {
*cmd_op = CYAPA_IIC_CMD_OP_DELAY;
tmpp = strrchr(p, ']');
*tmpp = '\0';
p = strchr(p, '=') + 1;
*(int *)outbuf = atoi(p);
*cmd_data_len = sizeof(int);
return 0;
}
if (*p == 'r')
*cmd_op = CYAPA_IIC_CMD_OP_READ;
else if (*p == 'w')
*cmd_op = CYAPA_IIC_CMD_OP_WRITE;
else {
/*
* this iis command line doesn't contain valid command data.
* skip this command line.
*/
*cmd_op = CYAPA_IIC_CMD_OP_NONE;
*cmd_data_len = 0;
return 0;
}
/* skip white-space. */
while (*(++p) == ' ')
;
/* parse command data. */
while (*p != 'p') {
if (*p == ' ') {
p++;
continue;
}
i = 0;
memset(hexstr, 0, sizeof(hexstr));
while (*p != ' ')
hexstr[i++] = *(p++);
if (!strcmp(hexstr, "x"))
j++;
else
outbuf[j++] = string_to_hex(hexstr);
}
*cmd_data_len = j;
return 0;
}
int cyapa_send_update_cmds(struct args *args,
struct cmds_update_block *iic_cmds)
{
int i;
int ret = 0;
int tries = 3;
int fd = args->fd_dev;
int cmd_op;
unsigned int offset;
int cmd_len;
unsigned char delay = 100;
unsigned char *cmd_buf = NULL;
unsigned char status[32];
if (iic_cmds->valid_cmds <= 0)
return 0;
do {
for (i = 0; i < iic_cmds->valid_cmds; i++) {
/* byte 0: cmd_op, read/write/delay*/
cmd_op = iic_cmds->cmds[i][0];
if (cmd_op == CYAPA_IIC_CMD_OP_WRITE) {
/*
* command format for read/write:
* byte 0: cmd_op, read/write;
* byte 1: total cmd len;
* byte 2: i2c device addr;
* byte 3: i2c register map offset;
* byte 4 - ... : cmd data.
*/
cmd_len = iic_cmds->cmds[i][1] - 4;
offset = iic_cmds->cmds[i][3];
cmd_buf = &iic_cmds->cmds[i][4];
cyapa_dump_data_block("CYAPA_IIC_CMD_OP_WRITE",
cmd_buf, offset, cmd_len);
ret = cyapa_write_reg(fd, cmd_buf,
offset, cmd_len);
if (ret < 0)
break;
} else if (cmd_op == CYAPA_IIC_CMD_OP_DELAY) {
/*
* byte 0: cmd_op, delay;
* byte 1: total cmd len;
* byte 2 - byte 5: time in milliseconds, int.
*/
delay = iic_cmds->cmds[i][2];
cyapa_dump_delay_time("CYAPA_IIC_CMD_OP_DELAY",
delay);
msleep(delay);
} else if (cmd_op == CYAPA_IIC_CMD_OP_READ) {
/*
* byte 0: cmd_op, read;
* byte 1: total cmd len;
* byte 2: i2c device addr.
* byte 3 - ... : total bytes number indicates
* how many bytes should be read.
*/
memset(status, 0, sizeof(status));
cmd_len = iic_cmds->cmds[i][1] - 3;
ret = cyapa_read_reg(fd, 0, cmd_len, status);
cyapa_dump_data_block("CYAPA_IIC_CMD_OP_READ",
status, 0, cmd_len);
if ((ret < 0) || (status[2] != 0x20))
break; /* tries to write this block. */
}
}
if (i == iic_cmds->valid_cmds)
break;
} while (tries--);
if (tries < 0)
return -1;
return 0;
}
int is_bootloader_terminal_cmd(struct cmds_update_block *iic_cmds)
{
int i;
unsigned char terminal_bootloader_cmd[] = {0x00, 0xFF, 0x3B,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
if (iic_cmds->valid_cmds != 1)
return 0;
for (i = 0; i < sizeof(terminal_bootloader_cmd); i++) {
if (iic_cmds->cmds[0][i+4] != terminal_bootloader_cmd[i])
break;
}
return (i == sizeof(terminal_bootloader_cmd)) ? 1 : 0;
}
#define IIC_CHECKSUM_START_BLOCK 0x001E
#define IIC_CHECKSUM_END_BLOCK 0x001F
#define IIC_APP_START_BLOCK 0x0020
#define IIC_APP_END_BLOCK 0x01FF
int cyapa_update_fw_image_from_iic(struct args *args)
{
int ret;
int block_index;
int total_blocks;
int fd = args->fd_new_fw;
char iic_buf[128];
int cmd_op;
unsigned char cmd_buf[CYAPA_FW_BLOCK_LEN];
int cmd_data_len;
FILE *fp = fdopen(fd, "r");
char *retp = NULL;
struct cmds_update_block iic_cmds;
if (fp == NULL) {
fp = fopen(args->new_fw_image, "r");
if (fp == NULL) {
prt_err("Failed to open new firmware image file, %d\n",
-errno);
ret = -errno;
goto error;
}
}
calculate_duration_time(TIMER_START, NULL);
total_blocks = (IIC_APP_END_BLOCK - IIC_APP_START_BLOCK + 1) +
(IIC_CHECKSUM_END_BLOCK - IIC_CHECKSUM_START_BLOCK + 1);
show_progress(0, SHOW_PROGRESS_INIT,
"Update trackpad firmware from .iic file in progress:\n");
show_progress(0, SHOW_PROGRESS_CONT, NULL);
/* updating firmware. */
memset(&iic_cmds, 0, sizeof(struct cmds_update_block));
while (1) {
/* read a command line from .iic file. */
memset(iic_buf, 0, sizeof(iic_buf));
retp = fgets(iic_buf, sizeof(iic_buf), fp);
/* TODO(djkurtz): Why forcing errno to 0?!? */
errno = 0;
if (retp == NULL) {
if (errno == 0) {
show_progress(0, SHOW_PROGRESS_EXIT,
"Update firmware from .iic"
" file done.\n");
calculate_duration_time(TIMER_STOP,
"Update firmware from .iic file"
" duration time");
ret = 0;
break;
} else {
prt_err("Failed to read next command line"
" from .iic file, %d.\n", -errno);
ret = -errno;
goto error;
}
}
/* convert .iic command line to i2c data. */
memset(cmd_buf, 0, sizeof(cmd_buf));
cyapa_iic_cmd_to_data(iic_buf, &cmd_op, cmd_buf, &cmd_data_len);
if (cmd_op == CYAPA_IIC_CMD_OP_NONE)
continue;
iic_cmds.cmds[iic_cmds.valid_cmds][0] = (unsigned char)cmd_op;
iic_cmds.cmds[iic_cmds.valid_cmds][1] =
(unsigned char)(cmd_data_len + 2);
memcpy(&iic_cmds.cmds[iic_cmds.valid_cmds][2],
cmd_buf, (cmd_data_len + 2));
iic_cmds.valid_cmds++;
/* update new firmware block data to trackpad device. */
if ((cmd_op == CYAPA_IIC_CMD_OP_READ)
|| (is_bootloader_terminal_cmd(&iic_cmds))) {
ret = cyapa_send_update_cmds(args, &iic_cmds);
if (ret < 0) {
prt_err("Failed to write new firmware's .iic"
" block data, %d.\n", ret);
goto error;
}
/* show new firmware update progress. */
if (iic_cmds.valid_cmds > 5) {
block_index = iic_cmds.cmds[0][15] << 8 |
iic_cmds.cmds[0][16];
if (block_index == IIC_CHECKSUM_START_BLOCK)
block_index = IIC_APP_END_BLOCK + 1;
else if (block_index == IIC_CHECKSUM_END_BLOCK)
block_index = IIC_APP_END_BLOCK + 2;
show_progress(((block_index -
IIC_APP_START_BLOCK + 1) * 100 /
total_blocks),
SHOW_PROGRESS_CONT,
NULL);
}
memset(&iic_cmds, 0, sizeof(struct cmds_update_block));
}
}
error:
fclose(fp);
return ret;
}
/*
* routines for updating trackpad firmware from .bin firmware image file.
*/
int cyapa_read_bin_fw_image(struct args *args,
unsigned short offset, unsigned char *buf, int len)
{
int ret = 0;
int read_bytes = 0;
int fd = args->fd_new_fw;
unsigned short read_offset;
int tries = 3;
if (args->file_type != FILE_TYPE_BIN)
return -1;
read_offset = offset - CYAPA_FW_OFFSET_START;
while ((read_bytes != len) && (tries-- > 0)) {
ret = (int)lseek(fd, (off_t)read_offset, SEEK_SET);
if (ret != (int)read_offset)
continue;
read_bytes = (int)read(fd, buf, (ssize_t)len);
}
if (tries < 0)
return -2;
return 0;
}
int cyapa_write_fw_image_block(struct args *args,
unsigned short offset, unsigned char *buf, int len)
{
int ret = 0;
int tries = 3;
int fd = args->fd_dev;
unsigned char status[3];
unsigned char cmd_buf[32];
unsigned char block_offset;
unsigned char block_buf[78];
unsigned char *p;
int left_len;
int cmd_len;
unsigned short block_index;
memset(block_buf, 0, sizeof(block_buf));
/* set write command and security key bytes. */
block_buf[0] = 0xFF;
block_buf[1] = 0x39;
block_buf[2] = 0x00;
block_buf[3] = 0x01;
block_buf[4] = 0x02;
block_buf[5] = 0x03;
block_buf[6] = 0x04;
block_buf[7] = 0x05;
block_buf[8] = 0x06;
block_buf[9] = 0x07;
/* block index is sent big endian. */
block_index = (unsigned short)(offset / CYAPA_FW_BLOCK_LEN);
block_buf[10] = block_index >> 8;
block_buf[11] = block_index;
memcpy(&block_buf[12], buf, len);
/* checksum for block data and whole command. */
block_buf[76] = cyapa_calculate_checksum(&block_buf[12], len);
block_buf[77] = cyapa_calculate_checksum(&block_buf[0], 77);
do {
p = block_buf;
left_len = sizeof(block_buf);
block_offset = 0;
while (left_len > 0) {
cmd_len = (left_len >= 16) ? 16 : left_len;
memset(cmd_buf, 0, sizeof(cmd_buf));
cmd_buf[0] = block_offset;
memcpy(&cmd_buf[1], p, cmd_len);
p += cmd_len;
block_offset += cmd_len;
left_len -= cmd_len;
/* write block data to trackpad device. */
cyapa_dump_data_block("CYAPA_WRITE_BLOCK_DATA",
cmd_buf, offset, (cmd_len + 1));
if (cyapa_write_reg(fd, cmd_buf, 0, (cmd_len + 1)) < 0)
break;
}
/* wait write command finished by trackpad device. */
msleep(100);
cyapa_dump_delay_time("CYAPA_WRITE_BLOCK_DELAY", 100);
if (left_len > 0) /* write failed, tries again. */
continue;
/* reset device pointer to offset 0. */
memset(cmd_buf, 0, sizeof(cmd_buf));
if (cyapa_write_reg(fd, cmd_buf, 0, 0) < 0)
prt_warn("Failed to reset trackpad device pointer.\n");
/* check block write command result status. */
memset(status, 0, sizeof(status));
ret = cyapa_read_reg(fd, 0, 3, status);
cyapa_dump_data_block("CYAPA_WRITE_STATUS_STATUS",
status, 0, 3);
if ((ret < 0) || (status[2] != 0x20))
continue;
/* block data written successfully. */
break;
} while (tries--);
if (tries < 0)
return -1;
return 0;
}
int cyapa_update_part_fw_image(struct args *args,
unsigned short start, unsigned end, int *blocks_written)
{
int blocks;
int ret = 0;
int total_blocks;
unsigned short offset;
unsigned char buf[CYAPA_FW_BLOCK_LEN];
total_blocks = (CYAPA_FW_OFFSET_END - CYAPA_FW_OFFSET_START + 1) /
CYAPA_FW_BLOCK_LEN;
blocks = 0;
offset = start + blocks * CYAPA_FW_BLOCK_LEN;
while ((offset + CYAPA_FW_BLOCK_LEN - 1) <= end) {
/* read data block from firmware image file. */
memset(buf, 0, sizeof(buf));
ret = cyapa_read_bin_fw_image(args, offset, buf,
CYAPA_FW_BLOCK_LEN);
if (ret < 0) {
prt_err("Failed to read %d bytes block data at 0x%04x"
" from .bin firmware image file, %d\n",
CYAPA_FW_BLOCK_LEN, offset, ret);
return ret;
}
/* write firmware data block to trackpad device. */
ret = cyapa_write_fw_image_block(args, offset,
buf, CYAPA_FW_BLOCK_LEN);
if (ret < 0) {
prt_err("Failed to write an image block data"
" to trackpad device, %d\n", ret);
return ret;
}
/* update pointers. */
blocks++;
offset = start + blocks * CYAPA_FW_BLOCK_LEN;
++(*blocks_written);
show_progress(((*blocks_written) * 100 / total_blocks),
SHOW_PROGRESS_CONT, NULL);
}
return blocks;
}
int cyapa_update_fw_image_from_bin(struct args *args)
{
int ret;
int blocks_written = 0;
calculate_duration_time(TIMER_START, NULL);
show_progress(0, SHOW_PROGRESS_INIT,
"Update trackpad firmware from .bin file in progress:\n");
show_progress(0, SHOW_PROGRESS_CONT, NULL);
/* switch firmware working state to bootloader active state. */
ret = cyapa_set_bootloader_active_mode(args);
if (ret < 0) {
prt_err("Failed switching firmware working state"
" to bootloader active state.\n");
goto error;
}
/*
* update firmware image to trackpad device.
* by default, should burn checksum blocks in the last step.
*/
ret = cyapa_update_part_fw_image(args, (CYAPA_FW_CHECKSUM_END + 1),
CYAPA_FW_OFFSET_END, &blocks_written);
if (ret < 0) {
prt_err("Failed to write firmware image blocks to"
" trackpad device.\n");
goto error;
}
ret = cyapa_update_part_fw_image(args, CYAPA_FW_OFFSET_START,
CYAPA_FW_CHECKSUM_END, &blocks_written);
if (ret < 0) {
prt_err("Failed to write checksum data blocks"
" to trackpad device.\n");
goto error;
}
show_progress(0, SHOW_PROGRESS_EXIT,
"Update firmware from .bin image file done.\n");
calculate_duration_time(TIMER_STOP,
"Update firmware from .bin image file duration time");
error:
/* set firmware back to bootloader idle mode working state. */
if (cyapa_set_bootloader_idle_mode(args) < 0) {
prt_warn("Failed to switch firmware working state"
" to bootloader idle state.\n");
}
return ret;
}
int cyapa_update_firmware(struct args *args)
{
int ret;
if (args->file_type == FILE_TYPE_IIC)
ret = cyapa_update_fw_image_from_iic(args);
else
ret = cyapa_update_fw_image_from_bin(args);
return ret;
}
int check_run_as_root(char *name)
{
const char *program;
if (getuid() == 0)
return 0;
program = basename(name) ?: DEFAULT_PROGRAM_NAME;
prt_warn("\"%s\" must be run as root.\n", program);
prt_warn("e.g.:\n");
prt_warn(" sudo %s new-firmware [options]\n", program);
prt_warn("Please run \"%s -h\" for more information.\n", program);
return -1;
}
static bool prompt(const char *intro, const char *Q, const char *Y,
const char *N)
{
int c;
prt_warn("%s", intro);
do {
prt_info("%s", Q);
c = getchar();
if (toupper(c) == 'Y') {
prt_info("%s", Y);
break;
} else {
prt_info("%s", N);
return false;
}
} while (c != EOF);
return true;
}
static bool warn_backup_fail(void)
{
return prompt("Failed to open firmware backup file.\n",
"Continue update without backup? <Y/N>: ",
"Continuing firmware update...\n",
"Canceling firmware update.\n");
}
static size_t line_to_buf(char *line, unsigned char *buf, size_t len)
{
size_t cnt = 0;
unsigned char b;
char *next;
do {
errno = 0;
b = strtoul(line, &next, 16);
if (line == next)
break;
line = next;
buf[cnt++] = b;
} while (cnt < len);
return cnt;
}
const unsigned char wr_cmd[] = { 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07 };
/*
* Process a 'w' line from an iic file.
*
* 5 'w' lines, when taken together, form a 78-byte write fw block command,
* with a 64-byte payload.
*/
static void proc_iic_w_line(const unsigned char *buf, size_t len)
{
static unsigned char cmd[14 + CYAPA_FW_BLOCK_LEN] = { 0 };
size_t offset;
size_t cmd_len;
ssize_t block;
unsigned char csum;
offset = buf[0];
cmd_len = offset + len - 1;
if (cmd_len > sizeof(cmd) || len == 0)
return;
memcpy(&cmd[offset], &buf[1], len - 1);
/* Start processing only if command is a full 78 bytes. */
if (cmd_len != sizeof(cmd))
return;
if (memcmp(cmd, wr_cmd, sizeof(wr_cmd)))
return;
/* Penultimate byte is crc of payload */
csum = cyapa_calculate_checksum(&cmd[12], CYAPA_FW_BLOCK_LEN);
if (csum != cmd[sizeof(cmd)-2])
return;
/* Last byte is crc of entire command */
csum = cyapa_calculate_checksum(cmd, sizeof(cmd)-1);
if (csum != cmd[sizeof(cmd)-1])
return;
block = ((cmd[10] << 8) | cmd[11]) - CYAPA_FW_START_BLOCK;
if (block >= 0 && block < CYAPA_FW_BLOCK_COUNT)
memcpy(&fw_buf[block * CYAPA_FW_BLOCK_LEN], &cmd[12],
CYAPA_FW_BLOCK_LEN);
}
/*
* An iic file consists of a sequence of commands, each on a single text line.
* The commands are 'r', 'w', or '[delay=X]'.
*
* Each write command has the following format:
* w <addr> <line_offset>
*/
static int convert_iic(struct args *args)
{
int ret;
FILE *f_iic;
FILE *f_bin;
char line[256];
unsigned char buf[20];
size_t n;
if (!args->new_fw_image || !args->bak_fw_image)
return -EINVAL;
f_iic = fopen(args->new_fw_image, "r");
if (f_iic == NULL) {
perror(NULL);
return -EIO;
}
while (!feof(f_iic)) {
char *r = fgets(line, sizeof(line), f_iic);
if (r == NULL)
break;
if (ferror(f_iic)) {
perror(NULL);
ret = -1;
goto close_iic;
}
/* Ignore lines that aren't part of a write command */
if (strncmp(line, "w 67 00 ", 8))
continue;
/* Extract remaining hex-encoded bytes on line into buf */
n = line_to_buf(&line[8], buf, sizeof(buf));
/* Ignore "w 67 00 p" */
if (n == 0)
continue;
proc_iic_w_line(buf, n);
}
f_bin = fopen(args->bak_fw_image, "wb");
if (f_bin == NULL) {
perror(NULL);
ret = -EIO;
goto close_iic;
}
printf("Writing %d bytes\n", CYAPA_FW_SIZE);
ret = fwrite(fw_buf, 1, CYAPA_FW_SIZE, f_bin);
if (ret != CYAPA_FW_SIZE) {
perror(NULL);
ret = -EIO;
goto close_bin;
}
ret = 0;
close_bin:
fclose(f_bin);
close_iic:
fclose(f_iic);
return ret;
}
int main(int argc, char **argv)
{
int err_code = 0;
int fd_temp = 0;
struct args args = { 0 };
struct cyapa_firmware_ver fw_version;
int old_protocol = -1;
int new_protocol = -1;
/* only root priority user can execute this program. */
if (check_run_as_root(argv[0]) < 0)
exit(-1);
/* parse input parameters. */
err_code = check_input_args(argc, argv, &args);
if (err_code == 2) {
show_version_info(basename(argv[0]));
exit(0);
} else if (err_code == 1) {
show_usage(basename(argv[0]));
exit(0);
}
if (args.convert)
return convert_iic(&args);
/* open device and firmware image files. */
if (open_trackpad_dev(&args)) {
prt_err("unable to open trackpad device.\n");
exit(-3);
}
if (args.new_fw_image && open_new_fw_image(&args)) {
prt_err("unable to open new firmware file.\n");
exit(-4);
}
if (args.backup_fw && open_bak_fw_image(&args) && !warn_backup_fail()) {
prt_err("unable to open backup file.\n");
exit(-5);
}
if (args.new_fw_image && cyapa_fw_image_check(&args) < 0) {
prt_err("New firmware image file \"%s\" is invalid.\n",
args.new_fw_image);
prt_info("Please specify a valid new trackpad"
" firmware image file.\n");
exit(-6);
}
/* show firmware version before firmware updated. */
memset(&fw_version, 0, sizeof(struct cyapa_firmware_ver));
if (cyapa_get_firmware_version(&args, &fw_version) < 0) {
err_code = -6;
goto error;
}
old_protocol = cyapa_get_protocol_version(&args);
prt_info("Firmware version before updated is: <%02d.%02d>,"
" protocol version is: GEN%d.\n\n",
fw_version.major_ver, fw_version.minor_ver, old_protocol);
/* backup firmware image for trackpad device. */
if (args.backup_fw) {
if (cyapa_backup_fw_from_trackpad(&args) < 0) {
if (warn_backup_fail())
goto error;
} else {
prt_info("Backup firmware image from trackpad device"
" to \"%s\" successfully.\n\n",
args.bak_fw_image);
}
}
/* If a new firmware file was provided, do update . */
if (args.new_fw_image) {
/* reset firmware working state to bootloader idle state. */
if (cyapa_set_bootloader_idle_mode(&args) < 0) {
prt_err("Failed to reset and switch firmware working"
" state to bootloader idle state.\n");
err_code = -8;
goto error;
}
if (cyapa_update_firmware(&args) < 0) {
prt_err("Update new firmware image %s failed.\n",
args.new_fw_image);
err_code = -9;
if (args.backup_fw) {
prt_info("Tries to recovery trackpad device to"
" previously backed up firmware image.\n");
fd_temp = args.fd_new_fw;
args.fd_new_fw = args.fd_bak_fw;
args.file_type = FILE_TYPE_BIN;
if (cyapa_update_firmware(&args) < 0) {
prt_err("Trackpad firmware recovery failed.\n");
err_code = -5;
} else {
err_code = 0;
prt_info("Successfully restored backed up"
" trackpad firmware\n");
}
args.fd_new_fw = fd_temp;
}
if (err_code < 0)
goto error;
}
prt_info("Successfully updated trackpad to new firmware image %s\n\n",
args.new_fw_image);
}
error:
/* reset trackpad device to operational mode. */
if (cyapa_set_app_operational_mode(&args) < 0) {
prt_info("Failed to reset trackpad device to"
" operational mode.\n");
prt_info("Need to reboot system to recover trackpad device.\n");
}
/* show firmware version after firmware updated. */
new_protocol = cyapa_get_protocol_version(&args);
memset(&fw_version, 0, sizeof(struct cyapa_firmware_ver));
if ((cyapa_get_firmware_version(&args, &fw_version) >= 0)
&& (err_code == 0))
prt_info("Firmware version after update is: v%02d.%02d,"
" new protocol is: GEN%d\n",
fw_version.major_ver,
fw_version.minor_ver,
new_protocol);
if (args.fd_dev > 0)
close(args.fd_dev);
if (args.fd_new_fw > 0)
close(args.fd_new_fw);
if (args.fd_bak_fw > 0)
close(args.fd_bak_fw);
return err_code;
}