blob: a619cb38ad85c2ca76181d24403959a66dddd2ab [file] [log] [blame]
/*
* 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;
}