/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
 *
 * this file licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 * http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 *
 */

#include "hs_drive_upgrade.h"
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "hs_misc.h"
#include "sml_errcodes.h"
#include "sml_pd.h"
#include "histore/lib_api.h"
#include "histore/tool_lib.h"

/*
 * Description: 判断当前系统是否为大端序
 * Return: TRUE表示大端序，FALSE表示小端序
 */
LOCAL gboolean is_big_endian(void)
{
    guint32 test_value = 0x12345678;
    guint8 *ptr = (guint8 *)&test_value;
    
    // 如果第一个字节是0x12，说明是大端序
    return (ptr[0] == 0x12);
}

/*
 * Description: 将32位整数转换为24位大端序字节数组
 * Parameters:
 *   value: 要转换的32位整数
 *   buffer: 输出的3字节缓冲区
 */
LOCAL void convert_to_24bit_big_endian(guint32 value, guint8 *buffer)
{
    if (buffer == NULL) {
        return;
    }

    if (is_big_endian()) {
        // 大端系统：直接从高位到低位复制（跳过最高字节）
        guint8 *ptr = (guint8 *)&value;
        buffer[0] = ptr[1]; // 第二高字节
        buffer[1] = ptr[2]; // 第二低字节
        buffer[2] = ptr[3]; // 最低字节
    } else {
        // 小端系统：使用位操作转换为大端序
        buffer[0] = (value >> 16) & 0xFF; // 高字节
        buffer[1] = (value >> 8) & 0xFF;  // 中字节
        buffer[2] = value & 0xFF;         // 低字节
    }
}

/* SCSI Write Buffer CDB structure for firmware download */
/*
 * Description: Fill SCSI Write Buffer CDB for firmware download
 * Note: Use scsi_update_pthru_cdb from lib_api.h instead of defining a new structure
 */
LOCAL void fill_write_buffer_cdb_download(struct scsi_update_pthru_cdb *cdb,
                                          guint32 buffer_offset, guint32 data_len)
{
    if (cdb == NULL) {
        return;
    }

    (void)memset_s(cdb, sizeof(struct scsi_update_pthru_cdb), 0, sizeof(struct scsi_update_pthru_cdb));

    cdb->opcode = SCSI_WRITE_BUFFER_OPCODE;
    /* Use mode 0x0E (download and save) */
    cdb->model = SCSI_WRITE_BUFFER_MODE_DOWNLOAD_MICRO;
    cdb->buffer_id = 0; // Default buffer ID

    /* Set buffer offset (24-bit big-endian) - 根据系统字节序处理 */
    convert_to_24bit_big_endian(buffer_offset, cdb->buff_offset);

    /* Set parameter list length (24-bit big-endian) - 根据系统字节序处理 */
    convert_to_24bit_big_endian(data_len, cdb->transfer_length);

    cdb->control = 0; // Default control byte
}

/*
 * Description: Send SCSI write command (wrapper for ADM_RAID_WRITE)
 * This function uses ADM_RAID_WRITE opcode internally for SCSI passthrough write operations
 */
gint32 send_scsi_passthru_cmd_write(guint32 ctrl_id, struct cmd_scsi_passthrough *scsi_passthru_cmd,
                                    gpointer data, guint32 data_length)
{
    if (scsi_passthru_cmd == NULL) {
        return SML_ERR_NULL_DATA;
    }

    struct lib_cmd_param lib_param;
    (void)memset_s(&lib_param, sizeof(lib_param), 0, sizeof(lib_param));

    lib_param.opcode = ADM_RAID_WRITE; // Use WRITE opcode for firmware download
    lib_param.subopcode = ADM_CMD_SCSI_PASSTHROUGH;
    lib_param.card_id = ctrl_id;
    lib_param.channel = PCIE_PHYSICAL_FUCTION0;
    lib_param.timeout_ms = PD_UPGRADE_TIMEOUT_MS; // 10 minutes timeout for drive upgrade
    lib_param.param_len = (guint16)sizeof(struct cmd_scsi_passthrough);
    lib_param.p_param = scsi_passthru_cmd;

    if (data != NULL && data_length > 0) {
        lib_param.data = data;
        lib_param.data_len = data_length;
    }
    return process_histore_cmd(&lib_param);
}

/*
 * Description: Send drive firmware upgrade file via SCSI Write Buffer command
 * Parameters:
 *   ctrl_id: Controller ID
 *   device_id: Device ID (did)
 *   file_data: Firmware data buffer
 *   data_len: Length of firmware data in this piece
 *   buffer_offset: Offset in firmware file
 * Return: RET_OK on success, error code otherwise
 */
gint32 sml_send_drive_fw_upgrade_file(guint32 ctrl_id, guint16 device_id,
    guchar *file_data, guint32 data_len, guint32 buffer_offset)
{
    struct cmd_scsi_passthrough scsi_passthru_cmd = {0};
    struct scsi_update_pthru_cdb write_buffer_cdb = {0};
    guint8 sense_data[ADM_SCSI_CDB_SENSE_MAX_LEN] = {0}; // Standard sense buffer size
    gint32 ret;

    if (file_data == NULL || data_len == 0) {
        debug_log(DLOG_ERROR, "[%s] Invalid parameters", __FUNCTION__);
        return SML_ERR_NULL_DATA;
    }

    /* Fill Write Buffer CDB */
    fill_write_buffer_cdb_download(&write_buffer_cdb, buffer_offset, data_len);

    /* Fill SCSI passthrough command structure */
    scsi_passthru_cmd.loc.did = device_id;
    scsi_passthru_cmd.loc.flag = DISK_LOC_DID; // DISK_LOC_DID: 使用device_id定位
    scsi_passthru_cmd.lun = 0; // Default LUN
    scsi_passthru_cmd.cdb_len = SCSI_CDB_LENGTH10;
    scsi_passthru_cmd.sense_buffer_len = sizeof(sense_data);
    scsi_passthru_cmd.sense_buffer = sense_data;

    /* Copy CDB to passthrough command */
    ret = memcpy_s(scsi_passthru_cmd.cdb, sizeof(scsi_passthru_cmd.cdb),
                   &write_buffer_cdb, sizeof(write_buffer_cdb));
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "[%s] memcpy_s CDB failed, ret=%d", __FUNCTION__, ret);
        return ret;
    }

    /* Send SCSI command via sml layer (uses ADM_RAID_WRITE opcode internally) */
    ret = send_scsi_passthru_cmd_write(ctrl_id, &scsi_passthru_cmd, (gpointer)file_data, data_len);
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "[%s] send_scsi_passthru_cmd_write failed, ret=0x%04x, device_id=%u, offset=%u, len=%u",
                  __FUNCTION__, ret, device_id, buffer_offset, data_len);
        return ret;
    }

    debug_log(DLOG_DEBUG, "[%s] Successfully sent firmware data, device_id=%u, offset=%u, len=%u",
              __FUNCTION__, device_id, buffer_offset, data_len);
    return RET_OK;
}

/*
 * Description: Activate drive firmware using ADM_CMD_ACTIVE_DISK_FW command
 * Parameters:
 *   ctrl_id: Controller ID
 *   device_id: Device ID (did)
 * Return: RET_OK on success, error code otherwise
 */
gint32 sml_active_drive_fw(guint32 ctrl_id, guint16 device_id)
{
    struct lib_cmd_param lib_param = {0};
    struct adm_disk_fw_active cmd_param = {0};
    gint32 ret;

    /* 填充激活命令参数 */
    cmd_param.loc.did = device_id;
    cmd_param.loc.flag = DISK_LOC_DID; // 使用device_id定位
    cmd_param.io_break = 0; // 0:中断IO业务

    /* 填充lib命令参数 */
    lib_param.opcode = ADM_RAID_SET;
    lib_param.subopcode = ADM_CMD_ACTIVE_DISK_FW;
    lib_param.card_id = ctrl_id;
    lib_param.channel = PCIE_PHYSICAL_FUCTION0;
    lib_param.timeout_ms = HISTORE_CMD_TIMEOUT_DEFAULT;
    lib_param.param_len = sizeof(struct adm_disk_fw_active);
    /* 将命令参数拷贝到lib_cmd_param的cmd_param数组中 */
    ret = memcpy_s(lib_param.cmd_param, sizeof(lib_param.cmd_param),
        &cmd_param, sizeof(cmd_param));
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "[%s] memcpy_s cmd_param failed, ret=%d", __FUNCTION__, ret);
        return ret;
    }
    lib_param.data = NULL;
    lib_param.data_len = 0;

    /* 发送命令 */
    ret = process_histore_cmd(&lib_param);
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "[%s] Activate drive firmware failed, ret=0x%04x, ctrl_id=%u, device_id=%u",
                  __FUNCTION__, ret, ctrl_id, device_id);
        return ret;
    }

    debug_log(DLOG_INFO, "[%s] Successfully activated drive firmware, ctrl_id=%u, device_id=%u",
        __FUNCTION__, ctrl_id, device_id);
    return RET_OK;
}

/*
 * Description: Allocate page-aligned memory for firmware data
 * Note: getpagesize() is POSIX, declared in <unistd.h> (already included above).
 */
LOCAL void *get_pagesize_memalign(void)
{
    void *addr = NULL;
    gint32 ret;

    ret = posix_memalign(&addr, (size_t)(long)getpagesize(), (size_t)DRIVE_ADM_MAX_DATA_SIZE);
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "[%s] posix_memalign failed, ret=%d", __FUNCTION__, ret);
        return NULL;
    }

    ret = memset_s(addr, DRIVE_ADM_MAX_DATA_SIZE, 0, DRIVE_ADM_MAX_DATA_SIZE);
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "[%s] memset_s failed, ret=%d", __FUNCTION__, ret);
        free(addr);
        return NULL;
    }

    return addr;
}

/*
 * Description: Get firmware file size
 */
LOCAL gint32 get_file_size(const gchar *file_path, size_t *file_size)
{
    struct stat statinfo = {0};

    if (file_path == NULL || file_size == NULL) {
        debug_log(DLOG_ERROR, "[%s] Invalid parameters", __FUNCTION__);
        return SML_ERR_NULL_DATA;
    }

    if (stat(file_path, &statinfo) < 0) {
        debug_log(DLOG_ERROR, "[%s] stat failed for %s, errno=%d", __FUNCTION__, file_path, errno);
        return -errno;
    }

    if (statinfo.st_size <= 0 || (unsigned long)statinfo.st_size > (unsigned long)DRIVE_UPGRADE_MAX_FILE_SIZE) {
        debug_log(DLOG_ERROR, "[%s] Invalid file size: %ld", __FUNCTION__, (long)statinfo.st_size);
        return SML_ERR_INVALID_PARAMETER;
    }

    *file_size = (size_t)statinfo.st_size;
    return RET_OK;
}

/*
 * Description: Read firmware file data from specified offset (reuse opened FILE*)
 */
LOCAL gint32 read_file_data_part(FILE *fp, guchar *file_data, guint32 offset, guint32 read_len)
{
    size_t actual;

    if (fp == NULL || file_data == NULL) {
        debug_log(DLOG_ERROR, "[%s] Invalid parameters", __FUNCTION__);
        return SML_ERR_NULL_DATA;
    }
    if (read_len == 0 || offset >= 0xFFFFFFFF || read_len >= 0xFFFFFFFF) {
        debug_log(DLOG_ERROR, "[%s] Invalid offset=%u or read_len=%u", __FUNCTION__, offset, read_len);
        return SML_ERR_INVALID_PARAMETER;
    }

    if (fseek(fp, (long)offset, SEEK_SET) != 0) {
        debug_log(DLOG_ERROR, "[%s] fseek failed, offset=%u", __FUNCTION__, offset);
        return -EIO;
    }

    actual = fread(file_data, 1, (size_t)read_len, fp);
    if (actual != (size_t)read_len) {
        debug_log(DLOG_ERROR, "[%s] fread failed, actual=%zu, expected=%u", __FUNCTION__, actual, read_len);
        return -EIO;
    }

    return RET_OK;
}

/*
 * Description: Read and send one piece of firmware file
 */
LOCAL gint32 send_one_upgrade_piece(guint32 ctrl_id, guint16 device_id, FILE *fp, guchar *buf,
    guint32 piece_index, guint32 pieces_num, size_t file_size)
{
    guint32 pieces_offset;
    guint32 current_piece_len;
    gint32 ret;

    ret = memset_s(buf, DRIVE_ADM_MAX_DATA_SIZE, 0, DRIVE_ADM_MAX_DATA_SIZE);
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "[%s] memset_s failed, ret=%d", __FUNCTION__, ret);
        return ret;
    }

    pieces_offset = DRIVE_ADM_MAX_DATA_SIZE * piece_index;
    current_piece_len = ((piece_index == (pieces_num - 1) && (file_size % DRIVE_ADM_MAX_DATA_SIZE)) ?
        (guint32)(file_size % DRIVE_ADM_MAX_DATA_SIZE) : DRIVE_ADM_MAX_DATA_SIZE);

    ret = read_file_data_part(fp, buf, pieces_offset, current_piece_len);
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "[%s] read_file_data_part failed, ret=%d, piece=%u", __FUNCTION__, ret, piece_index);
        return ret;
    }

    ret = sml_send_drive_fw_upgrade_file(ctrl_id, device_id, buf, current_piece_len, pieces_offset);
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "[%s] sml_send_drive_fw_upgrade_file failed, piece=%u, ret=0x%x",
            __FUNCTION__, piece_index, ret);
        return ret;
    }
    return RET_OK;
}

/*
 * Description: Transfer drive firmware file by path (read file, split, send each chunk in histore)
 * Parameters:
 *   ctrl_id: Controller ID
 *   device_id: Device ID (did)
 *   file_path: Firmware file path
 * Return: RET_OK on success
 */
gint32 histore_send_drive_upgrade_file(guint32 ctrl_id, guint16 device_id, const gchar *file_path)
{
    FILE *fp = NULL;
    guchar *buf = NULL;
    size_t file_size = 0;
    guint32 pieces_num;
    gint32 ret;

    if (file_path == NULL || file_path[0] == '\0') {
        debug_log(DLOG_ERROR, "[%s] Invalid file_path", __FUNCTION__);
        return SML_ERR_NULL_DATA;
    }

    ret = get_file_size(file_path, &file_size);
    if (ret != RET_OK) {
        return ret;
    }

    buf = (guchar *)get_pagesize_memalign();
    if (buf == NULL) {
        return SML_ERR_CANNOT_ALLOC_MEM;
    }

    gchar *resolved_path = realpath(file_path, NULL);
    if (resolved_path == NULL) {
        goto out;
    }
    fp = fopen(resolved_path, "rb");
    if (fp == NULL) {
        debug_log(DLOG_ERROR, "[%s] fopen failed for %s, errno=%d", __FUNCTION__, resolved_path, errno);
        ret = -errno;
        goto out;
    }

    pieces_num = (guint32)((file_size + DRIVE_ADM_MAX_DATA_SIZE - 1) / DRIVE_ADM_MAX_DATA_SIZE);
    debug_log(DLOG_INFO, "[%s] Start upgrade, file=%s, size=%zu, pieces=%u",
        __FUNCTION__, resolved_path, file_size, pieces_num);

    for (guint32 i = 0; i < pieces_num; i++) {
        ret = send_one_upgrade_piece(ctrl_id, device_id, fp, buf, i, pieces_num, file_size);
        if (ret != RET_OK) {
            break;
        }
    }

    (void)fclose(fp);
    fp = NULL;
out:
    if (buf != NULL) {
        free(buf);
    }
    if (fp != NULL) {
        (void)fclose(fp);
    }
    if (resolved_path != NULL) {
        free(resolved_path);
    }
    return ret;
}

/*
* Description : 激活硬盘升级
* Parameters:
*   ctrl_id: 控制器ID
*   active_param: 硬盘固件激活参数结构体(SML_PD_FW_ACTIVE_S)
*/
gint32 histore_active_drive_upgrade(guint32 ctrl_id, gpointer active_param)
{
    if (active_param == NULL) {
        debug_log(DLOG_ERROR, "[%s] Invalid parameter", __FUNCTION__);
        return SML_ERR_NULL_DATA;
    }

    // 从结构体中提取参数
    SML_PD_FW_ACTIVE_S *pd = (SML_PD_FW_ACTIVE_S *)active_param;
    
    return sml_active_drive_fw(ctrl_id, pd->i_device_id);
}