blob: 7085eaaa8a530564c5793be5c870003c6e3f4201 [file] [log] [blame]
/*
<:copyright-BRCM:2014:DUAL/GPL:standard
Copyright (c) 2014 Broadcom Corporation
All Rights Reserved
Unless you and Broadcom execute a separate written software license
agreement governing use of this software, this software is licensed
to you under the terms of the GNU General Public License version 2
(the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
with the following added to such license:
As a special exception, the copyright holders of this software give
you permission to link this software with independent modules, and
to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent
module, the terms and conditions of the license of that module.
An independent module is a module which is not derived from this
software. The special exception does not apply to any modifications
of the software.
Not withstanding the above, under no circumstances may you combine
this software in any way with any other Broadcom software provided
under a license other than the GPL, without Broadcom's express prior
written consent.
:>
*/
#include <bcmtr_interface.h>
#include <bcmcli.h>
#include <libgen.h>
#include "bcmolt_api.h"
#include "bcm_dev_log.h"
#include "bcmolt_image_transfer.h"
#include "bcmolt_image_transfer_user.h"
static bcmuser_mftp_block_size_get mftp_block_size_get;
static bcmuser_mftp_crc_get mftp_crc_get_cb;
static bcmuser_mftp_image_read mftp_read_data_cb;
static bcmuser_mftp_notification_rx mftp_notify_user_cb;
static mftp_context mftp_context_db[BCMTR_MAX_OLTS];
static bcmos_errno mftp_ind_rx_cb_register(bcmolt_devid device);
static bcmos_errno mftp_ind_rx_cb_unregister(bcmolt_devid device);
/* ========================================================================== */
static bcmolt_image_transfer_status mftp_err_to_status(bcmos_errno err)
{
bcmolt_image_transfer_status status;
switch (err)
{
case BCM_ERR_OK:
status = BCMOLT_IMAGE_TRANSFER_STATUS_SUCCESS;
break;
case BCM_ERR_NOMEM:
status = BCMOLT_IMAGE_TRANSFER_STATUS_MEMORY_ALLOCATION_FAILURE;
break;
case BCM_ERR_INCOMPLETE_TERMINATION:
status = BCMOLT_IMAGE_TRANSFER_STATUS_PREMATURE_TERMINATION;
break;
case BCM_ERR_CHECKSUM:
status = BCMOLT_IMAGE_TRANSFER_STATUS_CRC_ERROR;
break;
case BCM_ERR_OVERFLOW:
status = BCMOLT_IMAGE_TRANSFER_STATUS_INTERNAL_ERROR;
break;
case BCM_ERR_OUT_OF_SYNC:
status = BCMOLT_IMAGE_TRANSFER_STATUS_BLOCK_OUT_OF_SYNC;
break;
case BCM_ERR_STATE:
status = BCMOLT_IMAGE_TRANSFER_STATUS_INVALID_STATE;
break;
case BCM_ERR_IMAGE_TYPE:
status = BCMOLT_IMAGE_TRANSFER_STATUS_UNSUPPORTED_FILE_TYPE;
break;
default:
status = BCMOLT_IMAGE_TRANSFER_STATUS_INTERNAL_ERROR;
break;
}
return status;
}
/* ========================================================================== */
const char *bcmolt_user_mftp_status_to_str(bcmolt_image_transfer_status status)
{
static const char *mftp_status_to_str_table[BCMOLT_IMAGE_TRANSFER_STATUS__NUM_OF] =
{
[BCMOLT_IMAGE_TRANSFER_STATUS_SUCCESS] = "Succeeded",
[BCMOLT_IMAGE_TRANSFER_STATUS_MEMORY_ALLOCATION_FAILURE] = "ERROR: Memory allocation",
[BCMOLT_IMAGE_TRANSFER_STATUS_UNSUPPORTED_FILE_TYPE] = "ERROR: Invalid image type",
[BCMOLT_IMAGE_TRANSFER_STATUS_CRC_ERROR] = "ERROR: Checksum",
[BCMOLT_IMAGE_TRANSFER_STATUS_BLOCK_OUT_OF_SYNC] = "ERROR: Block out-of-sync",
[BCMOLT_IMAGE_TRANSFER_STATUS_INTERNAL_ERROR] = "ERROR: Buffer overflow",
[BCMOLT_IMAGE_TRANSFER_STATUS_INVALID_STATE] = "ERROR: Invalid state",
[BCMOLT_IMAGE_TRANSFER_STATUS_PREMATURE_TERMINATION] = "ERROR: Premature termination",
[BCMOLT_IMAGE_TRANSFER_STATUS_ACK_TIMEOUT] = "ERROR: ACK timeout"
};
return (status >= BCMOLT_IMAGE_TRANSFER_STATUS__NUM_OF) ? "<unknown>" : mftp_status_to_str_table[status];
}
/* ========================================================================== */
static inline mftp_context *mftp_context_get(bcmolt_devid device)
{
return &mftp_context_db[device];
}
/* ========================================================================== */
/** convert device ID to module ID */
static inline bcmos_module_id mftp_device_id_to_module_id(bcmolt_devid device)
{
return (BCMOS_MODULE_ID_USER_APPL_IMAGE_TRANSFER0 + device);
}
/* ========================================================================== */
/** examines the ACK response. */
static bcmolt_image_transfer_status mftp_check_params(mftp_context *context,
bcmolt_device_image_transfer_complete_data *ack_data)
{
bcmolt_image_transfer_status status = BCMOLT_IMAGE_TRANSFER_STATUS_SUCCESS;
if (context->block_num != ack_data->block_number)
{
BCM_LOG(DEBUG, context->log_id, "ACK block # mismatch: exp=%u ack=%u\n", context->block_num, ack_data->block_number);
status = BCMOLT_IMAGE_TRANSFER_STATUS_BLOCK_OUT_OF_SYNC;
}
else if (context->image_type != ack_data->image_type)
{
BCM_LOG(DEBUG, context->log_id, "ACK image type mismatch: exp=%u ack=%u\n", context->image_type, ack_data->image_type);
status = BCMOLT_IMAGE_TRANSFER_STATUS_UNSUPPORTED_FILE_TYPE;
}
else if (ack_data->status != BCMOLT_IMAGE_TRANSFER_STATUS_SUCCESS)
{
BCM_LOG(DEBUG, context->log_id, "OLT returned an error: %s\n", bcmolt_user_mftp_status_to_str(ack_data->status));
status = ack_data->status;
}
return status;
}
/* ========================================================================== */
static void mftp_terminate(bcmolt_devid device, bcmolt_device_image_type image_type,
uint32_t block_num, bcmolt_image_transfer_status status)
{
mftp_context *context = mftp_context_get(device);
bcmos_errno rc = mftp_ind_rx_cb_unregister(device);
if (rc != BCM_ERR_OK)
{
BCM_LOG(ERROR, context->log_id, "Failed to un-register IND callback\n");
}
mftp_notify_user_cb(device, image_type, block_num, status);
context->status = MFTP_STATUS_DISABLED;
}
/* ========================================================================== */
/** Sends the image transfer start operation to the OLT. */
static bcmos_errno mftp_send_wrq(bcmolt_devid device, bcmolt_device_image_type image_type,
uint32_t image_size, uint32_t crc32, bcmolt_str_64 *image_name)
{
bcmolt_device_image_transfer_start oper;
bcmolt_device_key key = { };
BCMOLT_OPER_INIT(&oper, device, image_transfer_start, key);
BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_start, image_type, image_type);
BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_start, image_size, image_size);
BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_start, crc32, crc32);
BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_start, image_name, *image_name);
return bcmolt_oper_submit(device, &oper.hdr);
}
/* ========================================================================== */
/** Sends a data block to the OLT. */
static bcmos_errno mftp_send_data(bcmolt_devid device, uint8_t *buf, uint32_t buf_size,
uint32_t block_number, bcmos_bool more_data)
{
bcmolt_device_key key = {.reserved = 0};
bcmolt_device_image_transfer_data oper = {};
bcmolt_u8_list_u16_hex data;
data.len = buf_size;
data.val = buf;
/* Builds a transport message for BCMOLT_DEVICE_OPER_ID_IMAGE_TRANSFER_DATA */
BCMOLT_OPER_INIT(&oper, device, image_transfer_data, key);
oper.hdr.hdr.type = BCMOLT_MSG_TYPE_SET;
BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_data, block_number, block_number);
BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_data, more_data, more_data);
BCMOLT_OPER_PROP_SET(&oper, device, image_transfer_data, data, data);
return bcmolt_oper_submit(device, &oper.hdr);
}
/* ========================================================================== */
/* handler for the OPERATION device.image_transfer_start. */
bcmos_errno bcmolt_user_mftp_start(bcmolt_devid device, bcmolt_device_image_type image_type)
{
mftp_context *context = mftp_context_get(device);
uint32_t image_size;
uint32_t crc32;
const char *path_name;
char file_name[MFTP_MAX_PATH_NAME_LEN];
char *base_name;
bcmolt_str_64 image_name;
bcmos_errno rc = BCM_ERR_OK;
if (context->status != MFTP_STATUS_DISABLED)
{
return BCM_ERR_IN_PROGRESS;
}
rc = bcmuser_image_transfer_file_size_get(device, image_type, &image_size);
if (rc != BCM_ERR_OK)
{
BCM_LOG(ERROR, context->log_id, "Failed to get the image size\n");
return rc;
}
path_name = bcmuser_device_image_name_get(image_type);
if (path_name == NULL)
{
BCM_LOG(ERROR, context->log_id, "File not found\n");
return BCM_ERR_PARM;
}
if (strlen(path_name) >= sizeof(file_name))
{
BCM_LOG(ERROR, context->log_id, "Path name too long\n");
return BCM_ERR_PARM;
}
(void)strcpy(file_name, path_name); /* to make lint happy. */
base_name = basename(file_name);
if (strlen(base_name) >= sizeof(image_name))
{
BCM_LOG(ERROR, context->log_id, "File name too long\n");
return BCM_ERR_PARM;
}
(void)strcpy(image_name.str, base_name);
context->status = MFTP_STATUS_WAITING_ACK;
context->block_num = 0;
context->image_type = image_type;
crc32 = mftp_crc_get_cb(device, image_type);
BCM_LOG(DEBUG, context->log_id, "Image size=%u crc=0x%x\n", image_size, crc32);
rc = mftp_ind_rx_cb_register(device);
if (rc != BCM_ERR_OK)
{
BCM_LOG(ERROR, context->log_id, "Failed to register IND callback\n");
BUG();
}
rc = mftp_send_wrq(device, image_type, image_size, crc32, &image_name);
if (BCM_ERR_OK != rc)
{
BCM_LOG(DEBUG, context->log_id, "WRQ OPER failed. %s\n", bcmos_strerror(rc));
(void)mftp_ind_rx_cb_unregister(device);
context->status = MFTP_STATUS_DISABLED;
}
else
{
bcmos_timer_start(&context->timer, MFTP_ACK_TIMEOUT_US);
}
return rc;
}
/* ========================================================================== */
static void mftp_process_last_ack(bcmolt_devid device, bcmolt_device_image_transfer_complete_data *ack_data)
{
bcmolt_image_transfer_status status;
mftp_context *context = mftp_context_get(device);
status = mftp_check_params(context, ack_data);
mftp_terminate(device, ack_data->image_type, ack_data->block_number, status);
}
/* ========================================================================== */
/* reads a data block using customer-provided callback and sends it to the embedded. */
static void mftp_process_ack(bcmolt_devid device, bcmolt_device_image_transfer_complete_data *ack_data)
{
uint32_t offset = 0; /* file-position*/
uint32_t bytes_read = 0;
bcmos_bool more_data;
bcmos_errno rc = BCM_ERR_OK;
bcmolt_image_transfer_status status;
mftp_context *context = mftp_context_get(device);
status = mftp_check_params(context, ack_data);
if (status != BCMOLT_IMAGE_TRANSFER_STATUS_SUCCESS)
{
mftp_terminate(device, context->image_type, ack_data->block_number, status);
return;
}
offset = (context->block_num * context->block_size);
context->block_num++;
/* CALL CUSTOMER-PROVIDED CALLBACK */
bytes_read = mftp_read_data_cb(device, context->image_type, offset,
context->buf, context->block_size); /* bcmuser_mftp_read_data() */
more_data = !(bytes_read < context->block_size);
rc = mftp_send_data(device, context->buf, bytes_read, context->block_num, more_data);
if (BCM_ERR_OK != rc)
{
BCM_LOG(DEBUG, context->log_id, "DATA OPER failed. Block %u. %s\n", context->block_num, bcmos_strerror(rc));
mftp_terminate(device, ack_data->image_type, ack_data->block_number, mftp_err_to_status(rc));
return;
}
BCM_LOG(DEBUG, context->log_id, "Sent block#=%u buf_size=%u more=%u\n", context->block_num, bytes_read, more_data);
context->status = (more_data) ? MFTP_STATUS_WAITING_ACK : MFTP_STATUS_WAITING_LAST_ACK;
bcmos_timer_start(&context->timer, MFTP_ACK_TIMEOUT_US);
}
/* ========================================================================== */
/* ACK indication callback */
static void mftp_process_ind(bcmolt_devid olt, bcmolt_msg *msg)
{
mftp_context *context = mftp_context_get(olt);
bcmolt_device_image_transfer_complete *ack;
bcmolt_device_image_transfer_complete_data *ack_data;
ack = (bcmolt_device_image_transfer_complete *)msg;
ack_data = &ack->data;
BCM_LOG(DEBUG, context->log_id, "## ACK: obj=%u, group=%u, image=%u, block=%u, status=%u\n",
msg->obj_type, msg->group, ack_data->image_type, ack_data->block_number, ack_data->status);
switch (context->status)
{
case MFTP_STATUS_WAITING_ACK:
bcmos_timer_stop(&context->timer);
mftp_process_ack(olt, ack_data);
break;
case MFTP_STATUS_WAITING_LAST_ACK:
bcmos_timer_stop(&context->timer);
mftp_process_last_ack(olt, ack_data);
break;
default:
/* This could happen when the START operation fails. */
BCM_LOG(DEBUG, context->log_id, "Unexpected ACK\n");
break;
}
bcmolt_msg_free(msg);
}
/* ========================================================================== */
static bcmos_errno mftp_ind_rx_cb_register(bcmolt_devid device)
{
bcmtr_handler_parm tparm = {
.group = BCMOLT_MGT_GROUP_AUTO,
.object = BCMOLT_OBJ_ID_DEVICE,
.subgroup = BCMOLT_DEVICE_AUTO_ID_IMAGE_TRANSFER_COMPLETE,
.app_cb = mftp_process_ind,
.flags = BCMOLT_AUTO_FLAGS_DISPATCH,
};
mftp_context *context = mftp_context_get(device);
uint8_t inst;
bcmos_errno rc = BCM_ERR_OK;
tparm.module = context->module_id;
for (inst = 0; (inst < BCMTR_MAX_INSTANCES) && (rc == BCM_ERR_OK); inst++)
{
tparm.instance = inst;
rc = bcmtr_msg_handler_unregister(device, &tparm);
rc = (rc == BCM_ERR_OK) ? bcmtr_msg_handler_register(device, &tparm) : rc;
}
return rc;
}
/* ========================================================================== */
static bcmos_errno mftp_ind_rx_cb_unregister(bcmolt_devid device)
{
bcmtr_handler_parm tparm = {
.group = BCMOLT_MGT_GROUP_AUTO,
.object = BCMOLT_OBJ_ID_DEVICE,
.subgroup = BCMOLT_DEVICE_AUTO_ID_IMAGE_TRANSFER_COMPLETE,
};
uint8_t inst;
bcmos_errno rc = BCM_ERR_OK;
for (inst = 0; (inst < BCMTR_MAX_INSTANCES) && (rc == BCM_ERR_OK); inst++)
{
tparm.instance = inst;
rc = bcmtr_msg_handler_unregister(device, &tparm);
}
return rc;
}
/* ========================================================================== */
/* ACK response timeout handler */
static bcmos_timer_rc mftp_ack_timeout_handler(bcmos_timer *timer, long data)
{
bcmolt_devid device = (bcmolt_devid)data;
mftp_context *context = mftp_context_get(device);
if ((context->status == MFTP_STATUS_WAITING_ACK) || (context->status == MFTP_STATUS_WAITING_LAST_ACK))
{
mftp_terminate(device, context->image_type, context->block_num, BCMOLT_IMAGE_TRANSFER_STATUS_ACK_TIMEOUT);
}
else
{
BCM_LOG(INFO, context->log_id, "timer in irrelevant state\n");
}
return BCMOS_TIMER_STOP;
}
/* ========================================================================== */
static bcmos_errno mftp_init_timers(bcmolt_devid device)
{
mftp_context *context = mftp_context_get(device);
bcmos_timer_parm timer_params = {};
bcmos_errno rc = BCM_ERR_OK;
/* If timer was already created, then we don't need to re-create it */
if ((context->timer.flags & BCMOS_TIMER_FLAG_VALID) != 0)
return BCM_ERR_OK;
timer_params.name = context->name;
timer_params.owner = context->module_id;
timer_params.periodic = BCMOS_FALSE;
timer_params.handler = mftp_ack_timeout_handler;
timer_params.data = (long)device;
rc = bcmos_timer_create(&context->timer, &timer_params);
if (rc != BCM_ERR_OK)
{
BCM_LOG(ERROR, context->log_id, "Timer creation failed. %s\n", bcmos_strerror(rc));
}
return rc;
}
/* ========================================================================== */
static bcmos_errno mftp_init_tasks(bcmolt_devid device)
{
mftp_context *context = mftp_context_get(device);
bcmos_task_parm task_params = {};
bcmos_errno rc = BCM_ERR_OK;
task_params.name = context->name;
task_params.priority = TASK_PRIORITY_USER_APPL_IMAGE_TRANSFER;
task_params.core = BCMOS_CPU_CORE_ANY; /* No CPU affinity */
task_params.init_handler = NULL;
task_params.data = (long)device;
rc = bcmos_task_create(&context->task, &task_params);
if (rc != BCM_ERR_OK)
{
BCM_LOG(ERROR, context->log_id, "Task creation failed. %s \n", bcmos_strerror(rc));
}
return rc;
}
/* ========================================================================== */
static bcmos_errno mftp_init_modules(bcmolt_devid device)
{
mftp_context *context = mftp_context_get(device);
bcmos_module_parm module_params = {};
bcmos_errno rc = BCM_ERR_OK;
context->module_id = mftp_device_id_to_module_id(device);
module_params.qparm.name = context->name;
module_params.qparm.size = BCMOS_MSG_POOL_DEFAULT_SIZE;
module_params.init = NULL;
rc = bcmos_module_create(context->module_id, &context->task, &module_params);
if (rc != BCM_ERR_OK)
{
BCM_LOG(ERROR, context->log_id, "Module creation failed. %s \n", bcmos_strerror(rc));
}
return rc;
}
/* ========================================================================== */
/* Initializes the MFTP parameters. */
void bcmolt_user_mftp_init(void)
{
mftp_context *context;
bcmolt_devid device;
bcmos_errno rc = BCM_ERR_OK;
/* The customer should provide these callback functions. */
mftp_read_data_cb = bcmuser_image_transfer_read_data;
mftp_notify_user_cb = bcmuser_image_transfer_notification_rx;
mftp_block_size_get = bcmuser_image_transfer_block_size_get;
mftp_crc_get_cb = bcmuser_image_transfer_crc_get;
/* initialize some parameters, i.e. the .status and .timer.
other parameters are (re)built upon START, so no need to init. */
for (device = 0; (device < BCMTR_MAX_OLTS) && (rc == BCM_ERR_OK); device++)
{
context = mftp_context_get(device);
context->status = MFTP_STATUS_DISABLED;
context->block_size = mftp_block_size_get();
context->buf = bcmos_calloc(context->block_size);
rc = (context->buf == NULL) ? BCM_ERR_NOMEM : rc;
snprintf(context->name, sizeof(context->name), "mftp%u", device);
rc = (rc == BCM_ERR_OK) ? mftp_init_tasks(device) : rc;
rc = (rc == BCM_ERR_OK) ? mftp_init_modules(device) : rc;
rc = (rc == BCM_ERR_OK) ? mftp_init_timers(device) : rc;
context->log_id = bcm_dev_log_id_register(context->name, DEV_LOG_LEVEL_INFO, DEV_LOG_ID_TYPE_BOTH);
}
}