| /* |
| * Copyright (c) 2020, Mediatek Inc. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <getopt.h> |
| #include <errno.h> |
| |
| #include "options.h" |
| #include "ufs.h" |
| #include "unipro.h" |
| #include "ufs_ffu.h" |
| #include "ufs_rpmb.h" |
| #include "ufs_hmr.h" |
| |
| static int verify_and_set_idn(struct tool_options *options); |
| static int verify_read(struct tool_options *options); |
| static int verify_write(struct tool_options *options); |
| static int verify_and_set_flag_operation(int opr_type, |
| struct tool_options *options); |
| static int verify_and_set_device_path(struct tool_options *options); |
| static int verify_and_set_index(struct tool_options *options); |
| static int verify_and_set_selector(struct tool_options *options); |
| static int verify_target(struct tool_options *options, int target); |
| static int verify_and_set_ffu_chunk_size(struct tool_options *options); |
| static int verify_length(struct tool_options *options); |
| static int verify_offset(struct tool_options *options); |
| static int verify_and_set_start_addr(struct tool_options *options); |
| static int verify_and_set_num_block(struct tool_options *options); |
| static int verify_lun(struct tool_options *options); |
| static int verify_and_set_key_path(struct tool_options *options); |
| static int verify_region(struct tool_options *options); |
| static int verify_and_set_hmr_method(struct tool_options *options); |
| static int verify_and_set_hmr_unit(struct tool_options *options); |
| static int verify_sg_struct(struct tool_options *options); |
| |
| #define MAX_ADDRESS 0xFFFF |
| |
| |
| int init_options(int opt_cnt, char *opt_arr[], struct tool_options *options) |
| { |
| int rc = -EINVAL; |
| int curr_opt = 0; |
| int opt = 0; |
| |
| static struct option long_opts[] = { |
| {"peer", no_argument, NULL, 'u'}, /* UFS device */ |
| {"local", no_argument, NULL, 'l'}, /* UFS host*/ |
| {NULL, 0, NULL, 0} |
| }; |
| static char *short_opts = "t:p:w:i:s:O:L:n:k:m:d:x:y:g:rocea"; |
| |
| while (-1 != |
| (curr_opt = getopt_long(opt_cnt, opt_arr, short_opts, |
| long_opts, &opt))) { |
| switch (curr_opt) { |
| case 'a': |
| rc = verify_read(options); |
| if (!rc) |
| options->opr = READ_ALL; |
| break; |
| case 't': |
| rc = verify_and_set_idn(options); |
| break; |
| case 'r': |
| rc = verify_read(options); |
| if (!rc) |
| options->opr = READ; |
| break; |
| case 'w': |
| rc = verify_write(options); |
| if (!rc) |
| options->opr = WRITE; |
| break; |
| case 'c': |
| rc = verify_and_set_flag_operation(CLEAR_FLAG, |
| options); |
| break; |
| case 'e': |
| rc = verify_and_set_flag_operation(SET_FLAG, options); |
| break; |
| case 'o': |
| rc = verify_and_set_flag_operation(TOGGLE_FLAG, |
| options); |
| break; |
| case 'p': |
| rc = verify_and_set_device_path(options); |
| break; |
| case 'i': |
| rc = verify_and_set_index(options); |
| break; |
| case 's': |
| if (options->config_type_inx == FFU_TYPE) |
| rc = verify_and_set_ffu_chunk_size(options); |
| else if (options->config_type_inx == RPMB_CMD_TYPE) |
| rc = verify_and_set_start_addr(options); |
| else |
| rc = verify_and_set_selector(options); |
| break; |
| case 'u': |
| rc = verify_target(options, DME_PEER); |
| break; |
| case 'l': |
| rc = verify_target(options, DME_LOCAL); |
| break; |
| case 'd': |
| rc = verify_lun(options); |
| break; |
| case 'L': |
| rc = verify_length(options); |
| break; |
| case 'O': |
| rc = verify_offset(options); |
| break; |
| case 'n': |
| rc = verify_and_set_num_block(options); |
| break; |
| case 'k': |
| rc = verify_and_set_key_path(options); |
| break; |
| case 'm': |
| rc = verify_region(options); |
| break; |
| case 'x': |
| rc = verify_and_set_hmr_method(options); |
| break; |
| case 'y': |
| rc = verify_and_set_hmr_unit(options); |
| break; |
| case 'g': |
| rc = verify_sg_struct(options); |
| break; |
| default: |
| rc = -EINVAL; |
| break; |
| } |
| if (rc) |
| break; |
| } |
| |
| if (!rc) |
| rc = verify_arg_and_set_default(options); |
| |
| return rc; |
| } |
| |
| static int verify_target(struct tool_options *options, int target) |
| { |
| if (options->target != INVALID) { |
| print_error("duplicated operate target."); |
| goto out; |
| } |
| |
| options->target = target; |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int verify_and_set_index(struct tool_options *options) |
| { |
| int index = INVALID; |
| |
| if (options->index != INVALID) { |
| print_error("duplicated index"); |
| goto out; |
| } |
| |
| /* In case atoi returned 0 . Check that is real 0 and not error |
| * arguments . Also check that the value is in correct range |
| */ |
| if (strstr(optarg, "0x") || strstr(optarg, "0X")) |
| index = (int)strtol(optarg, NULL, 0); |
| else |
| index = atoi(optarg); |
| |
| if ((index == 0 && strcmp(optarg, "0")) || index < 0) { |
| print_error("Invalid argument for index"); |
| goto out; |
| } |
| |
| options->index = index; |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int verify_and_set_ffu_chunk_size(struct tool_options *options) |
| { |
| int chunk_size_kb = atoi(optarg); |
| |
| if (!chunk_size_kb) { |
| print_error("Invalid chunk_size %d ", chunk_size_kb); |
| goto out; |
| } |
| options->size = chunk_size_kb * 1024; |
| if ((options->size > MAX_IOCTL_BUF_SIZE) || |
| (options->size % ALIGNMENT_CHUNK_SIZE)) { |
| print_error("The chunk should be multiple value of 4k, between 4k and %dk", |
| MAX_IOCTL_BUF_SIZE / 1024); |
| goto out; |
| } |
| return OK; |
| out: |
| return ERROR; |
| } |
| |
| static int verify_and_set_selector(struct tool_options *options) |
| { |
| int selector = INVALID; |
| |
| if (options->selector != INVALID) { |
| print_error("duplicated selector"); |
| goto out; |
| } |
| |
| /* In case atoi returned 0 . Check that is real 0 and not error |
| * arguments . Also check that the value is in correct range |
| */ |
| selector = atoi(optarg); |
| if ((selector == 0 && strcmp(optarg, "0")) || selector < 0) { |
| print_error("Invalid argument for selector"); |
| goto out; |
| } |
| |
| options->selector = selector; |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int verify_and_set_idn(struct tool_options *options) |
| { |
| int idn = INVALID; |
| |
| if (options->idn != INVALID) { |
| print_error("duplicated type option"); |
| goto out; |
| } |
| |
| /* In case atoi returned 0. Check that is real 0 and not error |
| * arguments. Also check that the value is in correct range |
| */ |
| idn = atoi(optarg); |
| if ((idn == 0 && strcmp(optarg, "0")) || idn < 0) { |
| print_error("Invalid argument for idn"); |
| goto out; |
| } |
| |
| switch (options->config_type_inx) { |
| case DESC_TYPE: |
| if (idn > QUERY_DESC_IDN_MAX) { |
| print_error("Invalid descriptor idn %d", idn); |
| goto out; |
| } |
| break; |
| case ATTR_TYPE: |
| if (idn >= QUERY_ATTR_IDN_MAX) { |
| print_error("Invalid attr idn %d", idn); |
| goto out; |
| } |
| break; |
| case FLAG_TYPE: |
| if (idn > QUERY_FLAG_IDN_MAX) { |
| print_error("Invalid flag idn %d", idn); |
| goto out; |
| } |
| break; |
| case UIC_TYPE: |
| if (idn >= MAX_UNIPRO_IDN) { |
| print_error("Invalid UIC idn %d", idn); |
| goto out; |
| } |
| break; |
| case FFU_TYPE: |
| if (idn >= UFS_FFU_MAX) { |
| print_error("Invalid ffu cmd %d", idn); |
| goto out; |
| } |
| break; |
| case RPMB_CMD_TYPE: |
| if (idn >= RPMB_CMD_MAX) { |
| print_error("Invalid rpmb cmd %d", idn); |
| goto out; |
| } |
| break; |
| default: |
| print_error("Invalid UFS configuration type %d", idn); |
| goto out; |
| } |
| |
| options->idn = idn; |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int verify_length(struct tool_options *options) |
| { |
| int len = INVALID; |
| |
| if (options->len != INVALID) { |
| print_error("duplicated length option"); |
| goto out; |
| } |
| |
| /* In case atoi returned 0. Check that is real 0 and not error |
| * arguments. Also check that the value is in correct range |
| */ |
| len = atoi(optarg); |
| if (len == 0 || len < 0 || len > BLOCK_SIZE) { |
| print_error("Invalid argument for length. The value should be between 1 to %dB", |
| BLOCK_SIZE); |
| goto out; |
| } |
| |
| options->len = len; |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int verify_offset(struct tool_options *options) |
| { |
| int offset = INVALID; |
| |
| if (options->offset != INVALID) { |
| print_error("duplicated offset option"); |
| goto out; |
| } |
| if (strstr(optarg, "0x") || strstr(optarg, "0X")) |
| offset = (int)strtol(optarg, NULL, 0); |
| else |
| offset = atoi(optarg); |
| if ((offset == 0 && strcmp(optarg, "0")) || offset < 0) { |
| print_error("Invalid argument for offset"); |
| goto out; |
| } |
| |
| options->offset = offset; |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int verify_and_set_start_addr(struct tool_options *options) |
| { |
| int start_block = 0; |
| |
| if (options->config_type_inx != RPMB_CMD_TYPE) { |
| print_error("start block address using only for rpmb cmd"); |
| goto out; |
| } |
| |
| start_block = atoi(optarg); |
| if ((start_block == 0 && strcmp(optarg, "0")) || |
| start_block < 0 || start_block > MAX_ADDRESS) { |
| print_error("Invalid start address"); |
| goto out; |
| } else |
| options->start_block = start_block; |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int verify_and_set_num_block(struct tool_options *options) |
| { |
| int num_block = 0; |
| |
| if (options->config_type_inx != RPMB_CMD_TYPE) { |
| print_error("num_block using only for rpmb cmd"); |
| goto out; |
| } |
| |
| num_block = atoi(optarg); |
| if ((num_block == 0 && strcmp(optarg, "0")) || num_block < 0) { |
| print_error("Invalid numbers of block"); |
| goto out; |
| } else |
| options->num_block = num_block; |
| return OK; |
| |
| out: |
| return ERROR; |
| |
| } |
| |
| static int verify_and_set_key_path(struct tool_options *options) |
| { |
| if (options->config_type_inx != RPMB_CMD_TYPE) { |
| print_error("key path using only for rpmb cmd"); |
| goto out; |
| } |
| |
| if (options->keypath[0] != '\0') { |
| print_error("Duplicate Key path"); |
| goto out; |
| } |
| |
| if ((optarg == NULL) || (optarg[0] == 0)) { |
| print_error("Key path missed"); |
| goto out; |
| } |
| |
| if (strlen(optarg) >= PATH_MAX) { |
| print_error("Key path is too long"); |
| goto out; |
| } |
| |
| strcpy(options->keypath, optarg); |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int verify_sg_struct(struct tool_options *options) |
| { |
| int8_t sg_type; |
| |
| if ((0 == (sg_type = atoi(optarg)) && 0 != (strcmp(optarg, "0"))) || |
| ((sg_type != SG3_TYPE) && (sg_type != SG4_TYPE))) { |
| print_error("Invalid SG struct"); |
| return ERROR; |
| } else |
| options->sg_type = sg_type; |
| return OK; |
| } |
| |
| static int verify_lun(struct tool_options *options) |
| { |
| int8_t lun = 0; |
| |
| lun = atoi(optarg); |
| if ((lun == 0 && strcmp(optarg, "0")) || lun < 0) { |
| print_error("Invalid lun"); |
| return ERROR; |
| } |
| options->lun = lun; |
| return OK; |
| } |
| |
| static int verify_region(struct tool_options *options) |
| { |
| int8_t region = 0; |
| |
| region = atoi(optarg); |
| if ((region == 0 && strcmp(optarg, "0")) || region < 0 |
| || region > 3) { |
| print_error("Invalid RPMB region"); |
| return ERROR; |
| } |
| options->region = region; |
| return OK; |
| } |
| |
| static int verify_rpmb_arg(struct tool_options *options) |
| { |
| int ret = OK; |
| |
| if (options->region == INVALID) |
| options->region = 0; |
| |
| switch (options->idn) { |
| case AUTHENTICATION_KEY: |
| if (options->keypath[0] == 0) { |
| print_error("Key path is missed"); |
| ret = ERROR; |
| } |
| break; |
| case READ_RPMB: |
| if (!options->data) { |
| print_error("Output data file missed"); |
| ret = ERROR; |
| } |
| break; |
| case READ_SEC_RPMB_CONF_BLOCK: |
| if (!options->data) { |
| print_error("Output data file missed"); |
| ret = ERROR; |
| } |
| if (options->lun == INVALID) { |
| print_error("LUN parameter is missed"); |
| ret = ERROR; |
| } |
| |
| if (options->region > 0) { |
| print_error("Config block exist only in region 0"); |
| ret = ERROR; |
| } |
| break; |
| case WRITE_RPMB: |
| if (!options->data) { |
| print_error("Input data file missed"); |
| ret = ERROR; |
| } |
| if (options->keypath[0] == 0) { |
| print_error("Key path is missed"); |
| ret = ERROR; |
| } |
| if (options->num_block == INVALID) |
| options->num_block = 1; |
| if (options->start_block == INVALID) |
| options->start_block = 0; |
| break; |
| case WRITE_SEC_RPMB_CONF_BLOCK: |
| if (!options->data) { |
| print_error("Input data file missed"); |
| ret = ERROR; |
| } |
| if (options->keypath[0] == 0) { |
| print_error("Key path is missed"); |
| ret = ERROR; |
| } |
| if (options->region > 0) { |
| print_error("Config block exist only in region 0"); |
| ret = ERROR; |
| } |
| break; |
| case READ_WRITE_COUNTER: |
| break; |
| default: |
| print_error("Unsupported RPMB cmd %d", |
| options->idn); |
| break; |
| } |
| return ret; |
| } |
| |
| static int verify_and_set_hmr_method(struct tool_options *options) |
| { |
| int ret = ERROR; |
| long result; |
| |
| if (options->hmr_method != INVALID) { |
| print_error("Duplicated hmr method option"); |
| goto out; |
| } |
| |
| ret = str_to_long(optarg, 10, &result); |
| if (ret == ERROR) { |
| print_error("Invalid argument for hmr method: not convertable"); |
| goto out; |
| } |
| |
| if (result < HMR_METHOD_FORCE || result >= HMR_METHOD_MAX) { |
| print_error("Invalid argument for hmr method: out of range"); |
| ret = ERROR; |
| goto out; |
| } |
| |
| options->hmr_method = result; |
| |
| out: |
| return ret; |
| } |
| |
| static int verify_and_set_hmr_unit(struct tool_options *options) |
| { |
| int ret = ERROR; |
| long result; |
| |
| if (options->hmr_unit != INVALID) { |
| print_error("Duplicated hmr unit option"); |
| goto out; |
| } |
| |
| ret = str_to_long(optarg, 10, &result); |
| if (ret == ERROR) { |
| print_error("Invalid argument for hmr unit: not convertable"); |
| goto out; |
| } |
| |
| if (result < HMR_UNIT_MIN || result >= HMR_UNIT_MAX) { |
| print_error("Invalid argument for hmr unit: out of range"); |
| ret = ERROR; |
| goto out; |
| } |
| |
| options->hmr_unit = result; |
| |
| out: |
| return ret; |
| } |
| |
| |
| // TODO: remove arg verification! |
| int verify_arg_and_set_default(struct tool_options *options) |
| { |
| if (options->path[0] == '\0') { |
| print_error("Missing device path type"); |
| goto out; |
| } |
| |
| if (options->opr == INVALID) |
| options->opr = READ; |
| |
| if (options->opr == WRITE && !options->data) { |
| print_error("Data missed for the write operation"); |
| goto out; |
| } |
| if (options->config_type_inx != ERR_HIST_TYPE && |
| options->config_type_inx != VENDOR_BUFFER_TYPE && |
| options->config_type_inx != HMR_TYPE && |
| options->opr != READ_ALL && |
| options->idn == INVALID) { |
| print_error("The type idn is missed"); |
| goto out; |
| } |
| |
| if (options->config_type_inx == DESC_TYPE && |
| options->idn == QUERY_DESC_IDN_STRING && |
| options->index == INVALID) { |
| print_error("The index is missed"); |
| goto out; |
| } |
| |
| if (options->config_type_inx == UIC_TYPE) { |
| if (options->idn == INVALID) { |
| /* |
| * As for the Unipro attributes access, should always |
| * specify idn. |
| */ |
| print_error("idn of Unipro attributes is missed"); |
| goto out; |
| } |
| |
| if (options->opr == WRITE && options->target != DME_PEER && |
| options->target != DME_LOCAL) { |
| /* |
| * As for Unipro attributes write, should |
| * specify accessing target. |
| */ |
| print_error("accessing target is missed"); |
| goto out; |
| } |
| |
| if (options->index == INVALID && |
| (options->opr == READ || options->opr == WRITE)) { |
| print_error("ID of Unipro attributes is missed"); |
| goto out; |
| } |
| } |
| |
| if (options->index == INVALID) |
| options->index = 0; |
| |
| if (options->selector == INVALID) |
| options->selector = 0; |
| |
| if (options->config_type_inx == FFU_TYPE) { |
| if (options->size == INVALID) |
| options->size = MAX_IOCTL_BUF_SIZE; |
| if (options->idn == INVALID) |
| /*Default operation*/ |
| options->idn = UFS_FFU; |
| if ((options->idn != UFS_CHECK_FFU_STATUS) && |
| (options->data == NULL)) { |
| print_error("The FW file name is missing"); |
| goto out; |
| } |
| } |
| |
| if ((options->config_type_inx == VENDOR_BUFFER_TYPE) && |
| (options->len == INVALID)) |
| options->len = BLOCK_SIZE; |
| |
| if (options->config_type_inx == RPMB_CMD_TYPE) { |
| if (verify_rpmb_arg(options)) |
| goto out; |
| } |
| |
| if (options->config_type_inx == HMR_TYPE) { |
| if (options->hmr_method == INVALID) |
| options->hmr_method = HMR_METHOD_SELECTIVE; |
| |
| if (options->hmr_unit == INVALID) |
| options->hmr_unit = HMR_UNIT_MIN; |
| } |
| |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int verify_and_set_device_path(struct tool_options *options) |
| { |
| if (options->path[0] != '\0') { |
| print_error("Duplicate Device path %d", options->path[0]); |
| goto out; |
| } |
| |
| if (optarg[0] == 0) { |
| print_error("Device path missed"); |
| goto out; |
| } |
| |
| if (strlen(optarg) >= PATH_MAX) { |
| print_error("Device path is too long"); |
| goto out; |
| } |
| |
| // strcpy(options->path, optarg); |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int verify_read(struct tool_options *options) |
| { |
| if (options->opr != INVALID) { |
| print_error("duplicated operation option(read)"); |
| goto out; |
| } |
| |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int verify_write(struct tool_options *options) |
| { |
| errno = 0; |
| |
| if (options->opr != INVALID) { |
| print_error("duplicated operation option(write)"); |
| goto out; |
| } |
| |
| if (optarg[0] == 0) { |
| print_error("Data is missed"); |
| goto out; |
| } |
| |
| if (options->config_type_inx == DESC_TYPE) { |
| int arg_len = strlen(optarg); |
| int str_desc_max_len = QUERY_DESC_STRING_MAX_SIZE/2 - 2; |
| |
| if (options->idn != QUERY_DESC_IDN_CONFIGURAION && |
| options->idn != QUERY_DESC_IDN_STRING) { |
| print_error("write unavailable for descriptor = %d", |
| options->idn); |
| goto out; |
| } |
| |
| if (arg_len > str_desc_max_len) { |
| print_error("Input data is too big"); |
| goto out; |
| } |
| |
| options->data = (char *)malloc(QUERY_DESC_STRING_MAX_SIZE); |
| if (!options->data) { |
| print_error("Memory Allocation problem"); |
| goto out; |
| } |
| |
| strcpy(options->data, optarg); |
| } |
| |
| if (options->config_type_inx == FLAG_TYPE) { |
| print_error("Please use 'c', 'e', or 'o' for flag operations"); |
| goto out; |
| } |
| |
| if (options->config_type_inx == ATTR_TYPE || |
| options->config_type_inx == UIC_TYPE) { |
| options->data = (__u32 *)calloc(1, sizeof(__u32)); |
| if (!options->data) { |
| print_error("Memory Allocation problem"); |
| goto out; |
| } |
| |
| // *(__u32 *)options->data = strtol(optarg, &endptr, 16); |
| |
| if (errno != 0) { |
| print_error("Wrong data"); |
| goto out; |
| } |
| } |
| if (options->config_type_inx == FFU_TYPE || |
| options->config_type_inx == VENDOR_BUFFER_TYPE || |
| options->config_type_inx == RPMB_CMD_TYPE) { |
| int len = strlen(optarg) + 1; |
| |
| if (len >= PATH_MAX) { |
| print_error("Input file path is too long"); |
| goto out; |
| } |
| options->data = (char *)calloc(1, len); |
| if (options->data == NULL) { |
| print_error("Memory Allocation problem"); |
| goto out; |
| } else |
| strcpy(options->data, optarg); |
| } |
| |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |
| static int |
| verify_and_set_flag_operation(int opr_type, struct tool_options *options) |
| { |
| if (options->opr != INVALID) { |
| print_error("duplicated operation option"); |
| goto out; |
| } |
| |
| if (options->config_type_inx != FLAG_TYPE) { |
| print_error("-c | -o | -e operation only for the flag type"); |
| goto out; |
| } |
| |
| if (opr_type < CLEAR_FLAG || opr_type > TOGGLE_FLAG) { |
| print_error("Incorrect operation for the flag type"); |
| goto out; |
| } |
| |
| options->opr = opr_type; |
| return OK; |
| |
| out: |
| return ERROR; |
| } |
| |