/* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
 * openUBMC is licensed under 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 <stdio.h>

#include "sml_errcodes.h"
#include "sml.h"
#include "sml_common.h"
#include "sml_public.h"
#include "sml_physical_drive.h"
#include "histore/tool_lib.h"
#include "histore/error_code.h"
#include "hs_misc.h"
#include "hs_pd_sas_log.h"

/*
 * Description : 初始化SML_PD_LOG_DATA_S数据
 */
static void init_pd_log_info(SML_PD_LOG_DATA_S *p_pd_log)
{
    p_pd_log->result = SML_SUCCESS;
    p_pd_log->scsi_status_code = SCSI_STATUS_SUCCESS;
    p_pd_log->data_length = 0;
    p_pd_log->data = NULL;
}

/*
 * Description : 组装获取HSSD IO信息的SCSI信令。该命令获取硬盘的读写数据量统计数据, 返回0x40字节数据，每项8字节
 */
void component_hssd_io_info_scsi_command(
    struct cmd_scsi_passthrough *scsi_passthru_cmd, guint16 device_id, guint16 page_length)
{
    if (scsi_passthru_cmd == NULL) {
        debug_log(DLOG_ERROR, "%s, The scsi_passthru_cmd is null.", __FUNCTION__);
        return;
    }

    struct multi_disk_location disk_location;
    (void)memset_s(&disk_location, sizeof(disk_location), 0, sizeof(disk_location));
    disk_location.did = device_id;
    disk_location.flag = DISK_LOC_DID;
    (void)memcpy_s(&scsi_passthru_cmd->loc, sizeof(scsi_passthru_cmd->loc), &disk_location, sizeof(disk_location));

    // 0，1,2,3,4,5,6,7,8,9 huawei scsi协议字段
    scsi_passthru_cmd->lun = 0;
    scsi_passthru_cmd->cdb_len = SCSI_CDB_LENGTH10;
    scsi_passthru_cmd->cdb[0] = 0xC0;   // operation Code(C0h)
    scsi_passthru_cmd->cdb[1] = 0x07;   // SubFunCode(07h)
    scsi_passthru_cmd->cdb[2] = 0x00;   // 2: page_length高字节位
    scsi_passthru_cmd->cdb[3] = 0x40;   // 3: page_length低字节位
    scsi_passthru_cmd->cdb[4] = 0x00;   // 4: 后6字节为Reserved
    scsi_passthru_cmd->cdb[5] = 0x00;
    scsi_passthru_cmd->cdb[6] = 0x00;
    scsi_passthru_cmd->cdb[7] = 0x00;
    scsi_passthru_cmd->cdb[8] = page_length & 0xFF;
    scsi_passthru_cmd->cdb[9] = 0x00;

    return;
}

 /*
 * Description : 通过SCSI passthru发送命令读取HSSD IO信息
 */
gint32 send_read_hssd_io_info_scsi_command(
    guint32 ctrl_id, guint16 device_id, guint8 **pbuf, guint32 *buf_size, guint32 *scsi_status)
{
    gint32 ret = SML_SUCCESS;
    const guint16 page_length = 64;

    if (pbuf == NULL) {
        return SML_ERR_NULL_DATA;
    }

    struct cmd_scsi_passthrough *scsi_passthru_cmd =
        (struct cmd_scsi_passthrough *)g_malloc0(sizeof(struct cmd_scsi_passthrough));
    if (scsi_passthru_cmd == NULL) {
        debug_log(DLOG_ERROR, "%s, Can not alloc memory.", __FUNCTION__);
        return SML_ERR_CANNOT_ALLOC_MEM;
    }

    component_hssd_io_info_scsi_command(scsi_passthru_cmd, device_id, page_length);

    guint8 *data = (guint8 *)g_malloc0(page_length);
    if (data == NULL) {
        debug_log(DLOG_ERROR, "%s, Can not alloc memory.", __FUNCTION__);
        g_free(scsi_passthru_cmd);
        return SML_ERR_NULL_DATA;
    }

    if ((ret = send_scsi_passthru_cmd(ctrl_id, scsi_passthru_cmd, data, page_length)) != SML_SUCCESS) {
        debug_log(DLOG_ERROR, "Get io info data failed, ctrl_id[%u], device_id[%u], return 0x%04x",
            ctrl_id, device_id, ret);
        
        if (scsi_status != NULL) {
            *scsi_status = ret;
        }

        g_free(data);
        g_free(scsi_passthru_cmd);
        return ret;
    }

    *pbuf = (guint8 *)g_malloc0(page_length);
    if (*pbuf == NULL) {
        debug_log(DLOG_ERROR, "Cannot alloc memory for response data buf.");
        g_free(data);
        g_free(scsi_passthru_cmd);
        return SML_ERR_CANNOT_ALLOC_MEM;
    }

    errno_t safe_ret = memcpy_s(*pbuf, page_length, data, page_length);
    if (safe_ret != EOK) {
        debug_log(DLOG_ERROR, "%s, The memcpy data to buf failed, return %d.", __FUNCTION__, safe_ret);
        g_free(*pbuf);
        *pbuf = NULL;
        g_free(data);
        g_free(scsi_passthru_cmd);
        return SML_ERR_SEC_FUNC_FAILED;
    }
    
    *buf_size = page_length;
    g_free(data);
    g_free(scsi_passthru_cmd);

    return SML_SUCCESS;
}

 /*
 * Description : 发送scsi协议，读写硬盘读写时延统计信息
 * Note: 函数内部申请内存，成功时外部进行释放，失败时内部释放
 */
LOCAL gint32 send_scsi_read_defect_command10(guint32 ctrl_id, guint16 device_id, guint8 *data,
    guint8 log_type, guint32 *scsi_status)
{
    struct cmd_scsi_passthrough scsi_passthru_cmd = { 0 };
    struct multi_disk_location disk_location = { 0 };
    disk_location.did = device_id;
    disk_location.flag = DISK_LOC_DID;
    (void)memcpy_s(&scsi_passthru_cmd.loc, sizeof(scsi_passthru_cmd.loc), &disk_location, sizeof(disk_location));

    scsi_passthru_cmd.lun = 0;
    scsi_passthru_cmd.cdb_len = SCSI_CDB_LENGTH10;
    scsi_passthru_cmd.cdb[0] = 0xC0;        // cdb[0]:operation code;       0xC0
    scsi_passthru_cmd.cdb[1] = 0x7E;        // cdb[1]:sub operaion code;    0x7E
    scsi_passthru_cmd.cdb[2] = 0xF4;        // cdb[2]:transfer type;   0xF4: 获取IO时延档位分布信息
    scsi_passthru_cmd.cdb[3] = log_type;    // cdb[3]:log type;        0x00: 读的时延统计; 0x01: 写的时延统计;
    scsi_passthru_cmd.cdb[4] = 0x01;        // cdb[4]:version;         0x00: 统计计数32bit，大约半小时发生翻转
                                            // cdb[4]:version;         0x01: 统计计数64bit，不会翻转
    // Byte5-6: Reserved
    scsi_passthru_cmd.cdb[7] = 0x03;        // cdb[7]:transfer length(固定长度0x0310): 高8位;
    scsi_passthru_cmd.cdb[8] = 0x10;        // cdb[8]:transfer length(固定长度0x0310): 低8位;
    // Byte9: Reserved

    if (scsi_status != NULL) {
        *scsi_status = SML_SUCCESS;
    }
    gint32 ret = send_scsi_passthru_cmd(ctrl_id, &scsi_passthru_cmd, data, ATA_RW_LATENCY_DATA_LENGTH);
    if (ret != SML_SUCCESS) {
        sleep(1);   // 等待1s后重试
        ret = send_scsi_passthru_cmd(ctrl_id, &scsi_passthru_cmd, data, ATA_RW_LATENCY_DATA_LENGTH);
        if (ret != SML_SUCCESS) {
            debug_log(DLOG_ERROR, "%s: SSD SAS Device get %s latency log fail, DeviceId = %u, CtrlId = %u",
                __FUNCTION__, (log_type == 0 ? "read" : "write"), device_id, SML_CTRL_ID_VALID_BIT(ctrl_id));

            if (scsi_status != NULL) {
                *scsi_status = ret;
            }
            return SML_ERR_CTRL_TOO_BUSY_TO_RESP_OOB;
        }
    }

    return SML_SUCCESS;
}

 /*
 * Description : 通过SCSI passthru获取SAS Glist
 */
gint32 get_sas_device_read_latency(guint32 ctrl_id, guint16 device_id, SML_PD_INFO_S *pd,
    SML_PD_LOG_DATA_S *read_latency)
{
    if (read_latency == NULL) {
        return SML_ERR_NULL_DATA;
    }

    init_pd_log_info(read_latency);

    guint8 *data = (guint8 *)g_malloc0(ATA_RW_LATENCY_DATA_LENGTH);
    if (data == NULL) {
        debug_log(DLOG_ERROR, "%s: g_malloc0 failed.", __FUNCTION__);
        return SML_ERR_CANNOT_ALLOC_MEM;
    }
    gint32 ret = send_scsi_read_defect_command10(ctrl_id, device_id, data, 0x00, &read_latency->scsi_status_code);
    if (ret != SML_SUCCESS) {
        debug_log(DLOG_ERROR, "%s: Send SCSI %d.%d get Read Latency Statistics command return 0x%x",
            __FUNCTION__, SML_CTRL_ID_VALID_BIT(ctrl_id), device_id, ret);
        goto EXIT;
    }
    read_latency->data_length = ATA_RW_LATENCY_DATA_LENGTH;
    read_latency->data = (guint8 *)g_malloc0(ATA_RW_LATENCY_DATA_LENGTH);
    if (read_latency->data == NULL) {
        ret = SML_ERR_CANNOT_ALLOC_MEM;
        goto EXIT;
    }

    (void)memcpy_s(read_latency->data, read_latency->data_length, data, read_latency->data_length);
    ret = SML_SUCCESS;
EXIT:
    g_free(data);
    data = NULL;
    return ret;
}

 /*
 * Description : 通过SCSI passthru获取SAS Glist
 */
gint32 get_sas_device_write_latency(guint32 ctrl_id, guint16 device_id, SML_PD_INFO_S *pd,
    SML_PD_LOG_DATA_S *write_latency)
{
    if (write_latency == NULL) {
        return SML_ERR_NULL_DATA;
    }

    init_pd_log_info(write_latency);

    guint8 *data = (guint8 *)g_malloc0(ATA_RW_LATENCY_DATA_LENGTH);
    if (data == NULL) {
        debug_log(DLOG_ERROR, "%s: g_malloc0 failed.", __FUNCTION__);
        return SML_ERR_CANNOT_ALLOC_MEM;
    }
    gint32 ret = send_scsi_read_defect_command10(ctrl_id, device_id, data, 0x01, &write_latency->scsi_status_code);
    if (ret != SML_SUCCESS) {
        debug_log(DLOG_ERROR, "%s: Send SCSI %d.%d get Write Latency Statistics command return 0x%x",
            __FUNCTION__, SML_CTRL_ID_VALID_BIT(ctrl_id), device_id, ret);
        goto EXIT;
    }
    write_latency->data_length = ATA_RW_LATENCY_DATA_LENGTH;
    write_latency->data = (guint8 *)g_malloc0(ATA_RW_LATENCY_DATA_LENGTH);
    if (write_latency->data == NULL) {
        ret = SML_ERR_CANNOT_ALLOC_MEM;
        goto EXIT;
    }

    (void)memcpy_s(write_latency->data, write_latency->data_length, data, write_latency->data_length);
    ret = SML_SUCCESS;
EXIT:
    g_free(data);
    data = NULL;
    return ret;
}