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