| /* |
| * 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"); |
| } |