blob: 956d82be7e45cfbc411041d31d9320a9c0ea51b3 [file] [log] [blame]
/*
* Copyright (c) 2020, Mediatek Inc. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma GCC diagnostic ignored "-Wall"
#pragma GCC diagnostic ignored "-Wextra"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <endian.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <errno.h>
#include "ufs.h"
#include "ufs_cmds.h"
#include "options.h"
#include "ioctl.h"
#include "ufs_ffu.h"
#include "scsi_bsg_util.h"
#define DEVICE_VERSION_OFFSET 0x1E
#define FFU_STATUS_ATTR 0x14
enum ffu_status_type {
NO_INFORMATION,
SUCCESSFUL_MICROCODE_UPDATE,
MICROCODE_CORRUPTION_ERROR,
INTERNAL_ERROR,
MICROCODE_VERSION_MISMATCH,
GENERAL_ERROR = 0xFF
};
extern int do_query_rq(int fd, struct ufs_bsg_request *bsg_req,
struct ufs_bsg_reply *bsg_rsp, __u8 query_req_func,
__u8 opcode, __u8 idn, __u8 index, __u8 sel,
__u16 req_buf_len, __u16 res_buf_len, __u8 *data_buf);
extern struct desc_field_offset device_desc_field_name[];
/* Get sense key string or NULL if not available */
static const char *
ffu_status_string(enum ffu_status_type status)
{
switch (status) {
case NO_INFORMATION:
return "NO INFORMATION";
break;
case SUCCESSFUL_MICROCODE_UPDATE:
return "SUCCESSFUL MICROCODE UPDATE";
break;
case INTERNAL_ERROR:
return "INTERNAL ERROR";
break;
case MICROCODE_CORRUPTION_ERROR:
return "MICROCODE CORRUPTION ERROR";
break;
case GENERAL_ERROR:
return "GENERAL ERROR";
break;
default:
return "UNSUPPORTED STATUS";
break;
}
return 0;
}
static int flash_ffu(int fd, struct tool_options *opt)
{
int rc = INVALID;
int input_fd = INVALID;
off_t file_size;
__u8 *p_data = NULL;
uint32_t chunk_size = opt->size;
uint32_t buf_offset = 0;
uint32_t write_buf_count;
input_fd = open(opt->data, O_RDONLY | O_SYNC);
if (input_fd < 0) {
perror("Input file open");
goto out;
}
file_size = lseek(input_fd, 0, SEEK_END);
/* The FFU file shall be aligned to 4k */
if ((file_size <= 0) || (file_size % ALIGNMENT_CHUNK_SIZE)) {
print_error("Wrong FFU file");
goto out;
}
lseek(input_fd, 0, SEEK_SET);
p_data = calloc(file_size, sizeof(__u8));
if (!p_data) {
print_error("Cannot allocate FFU size %d", file_size);
goto out;
}
if (read(input_fd, (char *)p_data, file_size) !=
file_size) {
print_error("Read FFU is failed");
goto out;
}
while (file_size > 0) {
if (file_size > chunk_size)
write_buf_count = chunk_size;
else
write_buf_count = file_size;
rc = write_buffer(fd, p_data + buf_offset, BUFFER_FFU_MODE, 0,
buf_offset, write_buf_count, opt->sg_type);
if (rc) {
print_error("Write error %d:", rc);
goto out;
}
buf_offset = buf_offset + write_buf_count;
file_size = file_size - write_buf_count;
}
sync();
printf("\nFFU was written to the device, reboot and check status\n");
out:
if (input_fd != INVALID)
close(input_fd);
if (p_data)
free(p_data);
return rc;
}
static int check_ffu_status(int fd, struct tool_options *opt)
{
int rc = ERROR;
__u8 dev_desc[QUERY_DESC_DEVICE_MAX_SIZE] = {0};
struct ufs_bsg_request bsg_req = {0};
struct ufs_bsg_reply bsg_rsp = {0};
__u32 attr_value;
__u16 *ufs_feature_support;
struct desc_field_offset *tmp = &device_desc_field_name[DEVICE_VERSION_OFFSET];
rc = do_query_rq(fd, &bsg_req, &bsg_rsp,
UPIU_QUERY_FUNC_STANDARD_READ_REQUEST,
UPIU_QUERY_OPCODE_READ_ATTR, FFU_STATUS_ATTR,
0, 0, 0, 0, 0);
if (rc) {
print_warn("cannot read bDeviceFFUStatus attribute status");
goto out;
}
else {
attr_value = be32toh(bsg_rsp.upiu_rsp.qr.value);
printf("%-20s := 0x%02x (%s)\n", "bDeviceFFUStatus",
attr_value,
ffu_status_string((enum ffu_status_type)attr_value));
}
rc = do_device_desc(fd, (__u8 *)&dev_desc);
if (rc != OK)
print_error("Could not read device descriptor in order to "
"read device version\n");
else {
ufs_feature_support = (__u16 *)&dev_desc[tmp->offset];
printf("%s = 0x%x\n", tmp->name, *ufs_feature_support);
}
out:
return rc;
}
int do_ffu(struct tool_options *opt)
{
int rc = INVALID;
int fd = INVALID;
fd = open(opt->path, O_RDWR | O_SYNC);
if (fd < 0) {
perror("open");
exit(1);
}
switch (opt->idn) {
case UFS_FFU:
rc = flash_ffu(fd, opt);
break;
case UFS_CHECK_FFU_STATUS:
rc = check_ffu_status(fd, opt);
break;
default:
print_error("Unsupported FFU type operation");
break;
}
close(fd);
return rc;
}
void ffu_help(char *tool_name)
{
printf("\n FFU command usage:\n");
printf("\n\t%s ffu [-t] <ffu cmd idn> [-p] <path to device> \n",
tool_name);
printf("\n\t-t\t FFU cmd idn\n");
printf("\t\t\t %-3d: %-25s\n",
UFS_FFU,
"FFU, flash FFU");
printf("\t\t\t %-3d: %-25s\n",
UFS_CHECK_FFU_STATUS,
"Check FFU status (check FFU status attribute and display FW version)");
printf("\n\t-s\t Max chunk size in KB alignment to 4KB, "
"which FFU file will be split (optional)\n");
printf("\n\t-w\t path to FFU file\n");
printf("\n\t-g\t sg struct ver - 0: SG_IO_VER4 (default), 1: SG_IO_VER3\n");
printf("\n\t-p\t bsg device path for FFU, ufs-bsg for Check FFU status\n");
}