// SPDX-License-Identifier: MIT
/*
 * Copyright 2025 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 */
#include "ras.h"
#include "ras_umc.h"
#include "ras_umc_v12_0.h"

#define MAX_ECC_NUM_PER_RETIREMENT  16

/* bad page timestamp format
 * yy[31:27] mm[26:23] day[22:17] hh[16:12] mm[11:6] ss[5:0]
 */
#define EEPROM_TIMESTAMP_MINUTE  6
#define EEPROM_TIMESTAMP_HOUR    12
#define EEPROM_TIMESTAMP_DAY     17
#define EEPROM_TIMESTAMP_MONTH   23
#define EEPROM_TIMESTAMP_YEAR    27

static uint64_t ras_umc_get_eeprom_timestamp(struct ras_core_context *ras_core)
{
	struct ras_time tm = {0};
	uint64_t utc_timestamp = 0;
	uint64_t eeprom_timestamp = 0;

	utc_timestamp = ras_core_get_utc_second_timestamp(ras_core);
	if (!utc_timestamp)
		return utc_timestamp;

	ras_core_convert_timestamp_to_time(ras_core, utc_timestamp, &tm);

	/* the year range is 2000 ~ 2031, set the year if not in the range */
	if (tm.tm_year < 2000)
		tm.tm_year = 2000;
	if (tm.tm_year > 2031)
		tm.tm_year = 2031;

	tm.tm_year -= 2000;

	eeprom_timestamp = tm.tm_sec + (tm.tm_min << EEPROM_TIMESTAMP_MINUTE)
				+ (tm.tm_hour << EEPROM_TIMESTAMP_HOUR)
				+ (tm.tm_mday << EEPROM_TIMESTAMP_DAY)
				+ (tm.tm_mon << EEPROM_TIMESTAMP_MONTH)
				+ (tm.tm_year << EEPROM_TIMESTAMP_YEAR);
	eeprom_timestamp &= 0xffffffff;

	return eeprom_timestamp;
}

static const struct ras_umc_ip_func *ras_umc_get_ip_func(
				struct ras_core_context *ras_core, uint32_t ip_version)
{
	switch (ip_version) {
	case IP_VERSION(12, 0, 0):
	case IP_VERSION(12, 5, 0):
		return &ras_umc_func_v12_0;
	default:
		RAS_DEV_ERR(ras_core->dev,
			"UMC ip version(0x%x) is not supported!\n", ip_version);
		break;
	}

	return NULL;
}

int ras_umc_psp_ma2pa(struct ras_core_context *ras_core,
		struct umc_mca_addr *in, struct umc_phy_addr *out,
		uint32_t nps)
{
	struct ras_ta_query_address_input addr_in;
	struct ras_ta_query_address_output addr_out;
	int ret;

	if (!in)
		return -EINVAL;

	memset(&addr_in, 0, sizeof(addr_in));
	memset(&addr_out, 0, sizeof(addr_out));

	addr_in.ma.err_addr = in->err_addr;
	addr_in.ma.ch_inst = in->ch_inst;
	addr_in.ma.umc_inst = in->umc_inst;
	addr_in.ma.node_inst = in->node_inst;
	addr_in.ma.socket_id = in->socket_id;

	addr_in.addr_type = RAS_TA_MCA_TO_PA;

	ret = ras_psp_query_address(ras_core, &addr_in, &addr_out);
	if (ret) {
		RAS_DEV_WARN(ras_core->dev,
			"Failed to query RAS physical address for 0x%llx, ret:%d",
			in->err_addr, ret);
		return -EREMOTEIO;
	}

	if (out) {
		out->pa = addr_out.pa.pa;
		out->bank = addr_out.pa.bank;
		out->channel_idx = addr_out.pa.channel_idx;
	}

	return 0;
}

static int ras_umc_log_ecc(struct ras_core_context *ras_core,
		unsigned long idx, void *data)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	int ret;

	mutex_lock(&ras_umc->tree_lock);
	ret = radix_tree_insert(&ras_umc->root, idx, data);
	if (!ret)
		radix_tree_tag_set(&ras_umc->root, idx, UMC_ECC_NEW_DETECTED_TAG);
	mutex_unlock(&ras_umc->tree_lock);

	return ret;
}

int ras_umc_clear_logged_ecc(struct ras_core_context *ras_core)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	uint64_t buf[8] = {0};
	void  **slot;
	void *data;
	void *iter = buf;

	mutex_lock(&ras_umc->tree_lock);
	radix_tree_for_each_slot(slot, &ras_umc->root, iter, 0) {
		data = ras_radix_tree_delete_iter(&ras_umc->root, iter);
		kfree(data);
	}
	mutex_unlock(&ras_umc->tree_lock);

	return 0;
}

int ras_umc_convert_record_to_nps_pages(struct ras_core_context *ras_core,
		struct eeprom_umc_record *record, uint32_t nps,
		uint64_t *page_pfn, uint32_t max_pages)
{
	int count = 0;
	struct ras_umc *ras_umc = &ras_core->ras_umc;

	if (!page_pfn || !max_pages)
		return -EINVAL;

	if (ras_umc->ip_func && ras_umc->ip_func->eeprom_record_to_nps_pages)
		count = ras_umc->ip_func->eeprom_record_to_nps_pages(ras_core,
					record, nps, page_pfn, max_pages);

	return count;
}

static void ras_umc_reserve_eeprom_record(struct ras_core_context *ras_core,
				struct eeprom_umc_record *record)
{
	uint64_t page_pfn[16];
	int count = 0, i;

	memset(page_pfn, 0, sizeof(page_pfn));
	count = ras_umc_convert_record_to_nps_pages(ras_core,
					record, record->cur_nps, page_pfn, ARRAY_SIZE(page_pfn));
	if (count <= 0) {
		RAS_DEV_ERR(ras_core->dev,
			"Fail to convert error address! count:%d\n", count);
		return;
	}

	/* Reserve memory */
	for (i = 0; i < count; i++)
		ras_core_event_notify(ras_core,
			RAS_EVENT_ID__RESERVE_BAD_PAGE, &page_pfn[i]);
}

/* When gpu reset is ongoing, ecc logging operations will be pended.
 *
 * The pending list is bounded by RAS_UMC_PENDING_ECC_MAX so that an ECC
 * storm or repeated UMC error injection cannot make this list (and the
 * kernel allocations behind it) grow without bound. Once the limit is
 * reached, additional events are dropped and counted in
 * pending_ecc_dropped, with a rate-limited warning emitted.
 */
int ras_umc_log_bad_bank_pending(struct ras_core_context *ras_core, struct ras_bank_ecc *bank)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	struct ras_bank_ecc_node *ecc_node;

	mutex_lock(&ras_umc->pending_ecc_lock);
	if (ras_umc->pending_ecc_count >= RAS_UMC_PENDING_ECC_MAX) {
		ras_umc->pending_ecc_dropped++;
		mutex_unlock(&ras_umc->pending_ecc_lock);
		RAS_DEV_WARN_RATELIMITED(ras_core->dev,
			"pending ECC list full (%u), dropping bad bank event (total dropped:%u)\n",
			RAS_UMC_PENDING_ECC_MAX, ras_umc->pending_ecc_dropped);
		return -ENOSPC;
	}
	mutex_unlock(&ras_umc->pending_ecc_lock);

	ecc_node = kzalloc_obj(*ecc_node);
	if (!ecc_node)
		return -ENOMEM;

	memcpy(&ecc_node->ecc, bank, sizeof(ecc_node->ecc));

	mutex_lock(&ras_umc->pending_ecc_lock);
	/* re-check under the lock to honor the cap across concurrent callers */
	if (ras_umc->pending_ecc_count >= RAS_UMC_PENDING_ECC_MAX) {
		ras_umc->pending_ecc_dropped++;
		mutex_unlock(&ras_umc->pending_ecc_lock);
		kfree(ecc_node);
		return -ENOSPC;
	}
	list_add_tail(&ecc_node->node, &ras_umc->pending_ecc_list);
	ras_umc->pending_ecc_count++;
	mutex_unlock(&ras_umc->pending_ecc_lock);

	return 0;
}

/* After gpu reset is complete, re-log the pending error banks.
 */
int ras_umc_log_pending_bad_bank(struct ras_core_context *ras_core)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	struct ras_bank_ecc_node *ecc_node, *tmp;

	mutex_lock(&ras_umc->pending_ecc_lock);
	list_for_each_entry_safe(ecc_node,
		tmp, &ras_umc->pending_ecc_list, node){
		if (!ras_umc_log_bad_bank(ras_core, &ecc_node->ecc)) {
			list_del(&ecc_node->node);
			kfree(ecc_node);
			if (ras_umc->pending_ecc_count)
				ras_umc->pending_ecc_count--;
		}
	}
	if (ras_umc->pending_ecc_dropped) {
		RAS_DEV_WARN(ras_core->dev,
			"%u pending ECC bad-bank events were dropped during GPU reset\n",
			ras_umc->pending_ecc_dropped);
		ras_umc->pending_ecc_dropped = 0;
	}
	mutex_unlock(&ras_umc->pending_ecc_lock);

	return 0;
}

int ras_umc_log_bad_bank(struct ras_core_context *ras_core, struct ras_bank_ecc *bank)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	struct eeprom_umc_record umc_rec;
	struct eeprom_umc_record *err_rec;
	int ret;

	memset(&umc_rec, 0, sizeof(umc_rec));

	mutex_lock(&ras_umc->bank_log_lock);
	ret = ras_umc->ip_func->bank_to_eeprom_record(ras_core, bank, &umc_rec);
	if (ret)
		goto out;

	err_rec = kzalloc_obj(*err_rec);
	if (!err_rec) {
		ret = -ENOMEM;
		goto out;
	}

	memcpy(err_rec, &umc_rec, sizeof(umc_rec));
	ret = ras_umc_log_ecc(ras_core, err_rec->cur_nps_retired_row_pfn, err_rec);
	if (ret) {
		if (ret == -EEXIST) {
			RAS_DEV_INFO(ras_core->dev, "The bad pages have been logged before.\n");
			ret = 0;
		}

		kfree(err_rec);
		goto out;
	}

	ras_umc_reserve_eeprom_record(ras_core, err_rec);

	ret = ras_core_event_notify(ras_core,
			RAS_EVENT_ID__BAD_PAGE_DETECTED, NULL);

out:
	mutex_unlock(&ras_umc->bank_log_lock);
	return ret;
}

int ras_umc_ma2pa(struct ras_core_context *ras_core,
	struct umc_mca_addr *addr_in, struct umc_phy_addr *addr_out,
	uint32_t nps)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	int ret;

	if (ras_psp_check_supported_cmd(ras_core, RAS_TA_CMD_ID__QUERY_ADDRESS)) {
		ret = ras_umc_psp_ma2pa(ras_core, addr_in, addr_out, nps);
	} else {
		if (ras_umc->ip_func && ras_umc->ip_func->ma2pa) {
			ret = ras_umc->ip_func->ma2pa(ras_core, addr_in, addr_out, nps);
		} else {
			RAS_DEV_ERR(ras_core->dev, "ma2pa is not supported!\n");
			ret = -EOPNOTSUPP;
		}
	}

	return ret;
}

static int ras_umc_pa2ma(struct ras_core_context *ras_core, uint64_t pa,
	uint64_t *mca, uint32_t nps)
{
	struct ras_ta_query_address_input addr_in;
	struct ras_ta_query_address_output addr_out;
	int ret;

	if (!ras_psp_check_supported_cmd(ras_core, RAS_TA_CMD_ID__QUERY_ADDRESS))
		return -EOPNOTSUPP;

	memset(&addr_in, 0, sizeof(addr_in));
	memset(&addr_out, 0, sizeof(addr_out));
	/* nps: the pa belongs to, always NPS1 for legacy eeprom data */
	addr_in.pa.pa = pa | ((uint64_t)nps << UMC_PA_NPS_SHIFT);
	addr_in.addr_type = RAS_TA_PA_TO_MCA;
	ret = ras_psp_query_address(ras_core, &addr_in, &addr_out);
	if (ret) {
		RAS_DEV_WARN_RATELIMITED(ras_core->dev,
			"Failed to query RAS MCA address for 0x%llx, ret:%d\n", pa, ret);

		return -EREMOTEIO;
	}

	*mca = addr_out.ma.err_addr;
	return 0;
}

static int __ras_umc_eeprom_rec2nps_addr(struct ras_core_context *ras_core,
	struct eeprom_umc_record *record, uint64_t *pa,
	uint32_t nps, uint32_t die_id)
{
	struct device_system_info dev_info = {0};
	struct umc_mca_addr addr_in;
	struct umc_phy_addr addr_out;
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	int ret;

	memset(&addr_in, 0, sizeof(addr_in));
	memset(&addr_out, 0, sizeof(addr_out));

	ras_core_get_device_system_info(ras_core, &dev_info);

	addr_in.err_addr = record->address;
	addr_in.ch_inst = record->mem_channel;
	addr_in.umc_inst = record->mcumc_id;
	addr_in.node_inst = die_id;
	addr_in.socket_id = dev_info.socket_id;

	ret = ras_umc_ma2pa(ras_core, &addr_in, &addr_out, nps);
	if (ret)
		return ret;

	if (ras_umc->ip_func && ras_umc->ip_func->nps_pa_to_row_pa) {
		*pa = ras_umc->ip_func->nps_pa_to_row_pa(ras_core, addr_out.pa,
			nps, false);
	} else {
		RAS_DEV_ERR(ras_core->dev, "nps_pa_to_row_pa is not supported!\n");
		return -EOPNOTSUPP;
	}

	return ret;
}

static int ras_umc_eeprom_rec2nps_addr(struct ras_core_context *ras_core,
		struct eeprom_umc_record *record, uint64_t *pa, uint32_t nps)
{
	return __ras_umc_eeprom_rec2nps_addr(ras_core, record, pa, nps,
			UMC_INV_AID_NODE);
}

/* For legacy eeprom data format, the scope of channel index is
 * limited to umc instance, and die id is not stored, have to
 * get it from PA
 */
static int ras_umc_eeprom_rec2nps_addr_legacy(struct ras_core_context *ras_core,
		struct eeprom_umc_record *record, uint64_t *pa, uint32_t nps)
{
	uint32_t die_id;

	/* the die id is derived from an NPS1-mode PA(legacy-format EEPROMs
	 * only ever existed on NPS1 systems)
	 */
	if (ras_core->ras_umc.ip_func && ras_core->ras_umc.ip_func->get_die_id) {
		die_id = ras_core->ras_umc.ip_func->get_die_id(record->address,
			RAS_PFN_TO_ADDR(EEPROM_RECORD_UMC_ADDR_PFN(record)));
	} else {
		RAS_DEV_ERR(ras_core->dev, "get_die_id is not supported!\n");
		return -EOPNOTSUPP;
	}

	return __ras_umc_eeprom_rec2nps_addr(ras_core, record, pa, nps, die_id);
}

static int ras_umc_eeprom_rec2nps_rec(struct ras_core_context *ras_core,
	struct eeprom_umc_record *record, uint32_t nps)
{
	uint64_t ch_idx_v2, pa = 0;
	uint32_t save_nps;
	int ret = 0;

	save_nps = EEPROM_RECORD_UMC_NPS_MODE(record);
	/* eeprom v2 has no stored nps, always convert if the flag is set */
	ch_idx_v2 = record->retired_row_pfn & UMC_CHANNEL_IDX_V2;
	record->cur_nps = nps;

	if (save_nps || ch_idx_v2) {
		if ((nps == save_nps) && !ras_fw_eeprom_supported(ras_core)) {
			record->cur_nps_retired_row_pfn =
				EEPROM_RECORD_UMC_ADDR_PFN(record);
		} else {
			ret = ras_umc_eeprom_rec2nps_addr(ras_core, record, &pa, nps);
			if (!ret)
				record->cur_nps_retired_row_pfn = RAS_ADDR_TO_PFN(pa);
		}
	} else {
		/* for specific old eeprom data, mca address is not stored(0 is
		 * default value), calc it from pa(it's nps1 in this case, other
		 * nps modes are introduced later)
		 */
		if (record->address == 0) {
			ret = ras_umc_pa2ma(ras_core,
				RAS_PFN_TO_ADDR(EEPROM_RECORD_UMC_ADDR_PFN(record)),
				&record->address, UMC_MEMORY_PARTITION_MODE_NPS1);
			if (ret)
				return ret;
		}

		/* old eeprom data format, the scope of channel index is
		 * limited to umc instance
		 */
		ret = ras_umc_eeprom_rec2nps_addr_legacy(ras_core, record, &pa, nps);
		if (!ret)
			record->cur_nps_retired_row_pfn = RAS_ADDR_TO_PFN(pa);
	}

	return ret;
}

static int ras_umc_get_new_records(struct ras_core_context *ras_core,
			struct eeprom_umc_record *records, u32 num)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	struct eeprom_umc_record *entries[MAX_ECC_NUM_PER_RETIREMENT];
	u32 entry_num = num < MAX_ECC_NUM_PER_RETIREMENT ? num : MAX_ECC_NUM_PER_RETIREMENT;
	int count = 0;
	int new_detected, i;

	mutex_lock(&ras_umc->tree_lock);
	new_detected = radix_tree_gang_lookup_tag(&ras_umc->root, (void **)entries,
			0, entry_num, UMC_ECC_NEW_DETECTED_TAG);
	for (i = 0; i < new_detected; i++) {
		if (!entries[i])
			continue;

		memcpy(&records[i], entries[i], sizeof(struct eeprom_umc_record));
		count++;
		radix_tree_tag_clear(&ras_umc->root,
				entries[i]->cur_nps_retired_row_pfn, UMC_ECC_NEW_DETECTED_TAG);
	}
	mutex_unlock(&ras_umc->tree_lock);

	return count;
}

static bool ras_umc_check_retired_record(struct ras_core_context *ras_core,
				struct eeprom_umc_record *record, bool from_eeprom)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	struct eeprom_store_record *data = &ras_umc->umc_err_data.rom_data;
	uint32_t nps = 0;
	int i, ret;

	if (from_eeprom) {
		nps = ras_umc->umc_err_data.umc_nps_mode;
		ret = ras_umc_eeprom_rec2nps_rec(ras_core, record, nps);
		if (ret)
			RAS_DEV_WARN_RATELIMITED(ras_core->dev,
				"Failed to adjust eeprom record, ret:%d", ret);

		return false;
	}

	for (i = 0; i < data->count; i++) {
		if ((data->bps[i].retired_row_pfn == record->retired_row_pfn) &&
		    (data->bps[i].cur_nps_retired_row_pfn == record->cur_nps_retired_row_pfn))
			return true;
	}

	return false;
}

/* alloc/realloc bps array */
static int ras_umc_realloc_err_data_space(struct ras_core_context *ras_core,
		struct eeprom_store_record *data, int pages)
{
	unsigned int old_space = data->count + data->space_left;
	unsigned int new_space = old_space + pages;
	unsigned int align_space = ALIGN(new_space, 512);
	void *bps = kzalloc(align_space * sizeof(*data->bps), GFP_KERNEL);

	if (!bps)
		return -ENOMEM;

	if (data->bps) {
		memcpy(bps, data->bps,
				data->count * sizeof(*data->bps));
		kfree(data->bps);
	}

	data->bps = bps;
	data->space_left += align_space - old_space;
	return 0;
}

static int ras_umc_update_eeprom_rom_data(struct ras_core_context *ras_core,
		struct eeprom_umc_record *bps)
{
	struct eeprom_store_record *data = &ras_core->ras_umc.umc_err_data.rom_data;

	if (!data->space_left &&
		ras_umc_realloc_err_data_space(ras_core, data, 256)) {
		return	-ENOMEM;
	}

	memcpy(&data->bps[data->count], bps, sizeof(*data->bps));
	data->count++;
	data->space_left--;
	return 0;
}

static int ras_umc_update_eeprom_ram_data(struct ras_core_context *ras_core,
				struct eeprom_umc_record *bps)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	struct eeprom_store_record *data = &ras_umc->umc_err_data.ram_data;
	uint64_t page_pfn[16];
	int count = 0, i, j;

	if (!data->space_left &&
		ras_umc_realloc_err_data_space(ras_core, data, 256)) {
		return	-ENOMEM;
	}

	memset(page_pfn, 0, sizeof(page_pfn));
	count = ras_umc_convert_record_to_nps_pages(ras_core,
					bps, bps->cur_nps, page_pfn, ARRAY_SIZE(page_pfn));
	if (count > 0) {
		for (j = 0; j < count; j++) {
			if (ras_core_check_address_sanity(ras_core,
				page_pfn[j] << AMDGPU_GPU_PAGE_SHIFT)) {

				for (i = 0; i < data->count; i++)
					if (page_pfn[j] == data->bps[i].cur_nps_retired_row_pfn)
						break;
				data->bps[data->count].cur_nps_retired_row_pfn = U64_MAX;
				data->count++;
				data->space_left--;
				continue;
			}

			bps->cur_nps_retired_row_pfn = page_pfn[j];
			memcpy(&data->bps[data->count], bps, sizeof(*data->bps));
			data->count++;
			data->space_left--;
			data->bad_page_num++;
		}
	} else {
		RAS_DEV_ERR(ras_core->dev, "Failed to convert record to nps pages!");
		return -EINVAL;
	}

	return 0;
}

static void ras_umc_update_bad_pages(struct ras_core_context *ras_core)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	struct eeprom_store_record *data = &ras_umc->umc_err_data.ram_data;

	data->bad_page_num_old = data->bad_page_num;
}

/* it deal with vram only. */
static int ras_umc_add_bad_pages(struct ras_core_context *ras_core,
				 struct eeprom_umc_record *bps,
				 int pages, bool from_eeprom)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	struct ras_umc_err_data *data = &ras_umc->umc_err_data;
	int i, ret = 0;

	if (!bps || pages <= 0)
		return 0;

	mutex_lock(&ras_umc->umc_lock);
	for (i = 0; i < pages; i++) {
		if (ras_umc_check_retired_record(ras_core, &bps[i], from_eeprom))
			continue;

		ret = ras_umc_update_eeprom_rom_data(ras_core, &bps[i]);
		if (ret)
			goto out;

		if (data->last_retired_pfn == bps[i].cur_nps_retired_row_pfn)
			continue;

		data->last_retired_pfn = bps[i].cur_nps_retired_row_pfn;

		if (from_eeprom)
			ras_umc_reserve_eeprom_record(ras_core, &bps[i]);

		ret = ras_umc_update_eeprom_ram_data(ras_core, &bps[i]);
		if (ret)
			goto out;
	}
out:
	mutex_unlock(&ras_umc->umc_lock);

	return ret;
}

/*
 * read error record array in eeprom and reserve enough space for
 * storing new bad pages
 */
int ras_umc_load_bad_pages(struct ras_core_context *ras_core)
{
	struct eeprom_umc_record *bps;
	uint32_t ras_num_recs;
	int ret;

	if (ras_fw_eeprom_supported(ras_core)) {
		ras_num_recs = ras_fw_eeprom_get_record_count(ras_core);
		/* no bad page record, skip eeprom access */
		if (!ras_num_recs ||
		    ras_core->ras_fw_eeprom.record_threshold_config == DISABLE_RETIRE_PAGE)
			return 0;
	} else {
		ras_num_recs = ras_eeprom_get_record_count(ras_core);
		if (!ras_num_recs ||
		    ras_core->ras_eeprom.record_threshold_config == DISABLE_RETIRE_PAGE)
			return 0;
	}

	bps = kzalloc_objs(*bps, ras_num_recs);
	if (!bps)
		return -ENOMEM;

	if (ras_fw_eeprom_supported(ras_core))
		ret = ras_fw_eeprom_read_idx(ras_core, bps, 0, 0, ras_num_recs);
	else
		ret = ras_eeprom_read(ras_core, bps, ras_num_recs);
	if (ret) {
		RAS_DEV_ERR(ras_core->dev, "Failed to load EEPROM table records!");
	} else {
		ras_core->ras_umc.umc_err_data.last_retired_pfn = UMC_INV_MEM_PFN;
		ret = ras_umc_add_bad_pages(ras_core, bps, ras_num_recs, true);
		ras_umc_update_bad_pages(ras_core);
	}

	kfree(bps);
	return ret;
}

/*
 * write error record array to eeprom, the function should be
 * protected by recovery_lock
 * new_cnt: new added UE count, excluding reserved bad pages, can be NULL
 */
static int ras_umc_save_bad_pages(struct ras_core_context *ras_core)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	struct eeprom_store_record *data = &ras_umc->umc_err_data.rom_data;
	struct eeprom_store_record *ram_data = &ras_umc->umc_err_data.ram_data;
	uint32_t eeprom_record_num, logical_count = 0;
	int save_count;
	int ret = 0;

	if (!data->bps)
		return 0;

	if (ras_fw_eeprom_supported(ras_core))
		eeprom_record_num = ras_fw_eeprom_get_record_count(ras_core);
	else
		eeprom_record_num = ras_eeprom_get_record_count(ras_core);
	mutex_lock(&ras_umc->umc_lock);
	save_count = data->count - eeprom_record_num;
	logical_count = ram_data->bad_page_num - ram_data->bad_page_num_old;
	/* only new entries are saved */
	if (save_count > 0) {
		if (ras_fw_eeprom_supported(ras_core))
			ret = ras_fw_eeprom_append(ras_core, &data->bps[eeprom_record_num],
					save_count);
		else
			ret = ras_eeprom_append(ras_core, &data->bps[eeprom_record_num],
					save_count);
		if (ret) {
			RAS_DEV_ERR(ras_core->dev, "Failed to save EEPROM table data!");
			ret = -EIO;
			goto exit;
		}
		ras_umc_update_bad_pages(ras_core);
		RAS_DEV_INFO(ras_core->dev, "Saved %d pages to EEPROM table.\n", logical_count);
	}

exit:
	mutex_unlock(&ras_umc->umc_lock);
	return ret;
}

int ras_umc_handle_bad_pages(struct ras_core_context *ras_core, void *data)
{
	struct eeprom_umc_record *records;
	int count, ret;

	records = kzalloc_objs(*records, MAX_ECC_NUM_PER_RETIREMENT);
	if (!records)
		return -ENOMEM;

	count = ras_umc_get_new_records(ras_core, records,
					MAX_ECC_NUM_PER_RETIREMENT);
	if (count <= 0) {
		ret = -ENODATA;
		goto out;
	}

	ret = ras_umc_add_bad_pages(ras_core, records, count, false);
	if (ret) {
		RAS_DEV_ERR(ras_core->dev, "Failed to add ras bad page!\n");
		ret = -EINVAL;
		goto out;
	}

	ret = ras_umc_save_bad_pages(ras_core);
	if (ret) {
		RAS_DEV_ERR(ras_core->dev, "Failed to save ras bad page\n");
		ret = -EINVAL;
		goto out;
	}

	ret = 0;

out:
	kfree(records);
	return ret;
}

int ras_umc_sw_init(struct ras_core_context *ras_core)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;

	memset(ras_umc, 0, sizeof(*ras_umc));

	INIT_LIST_HEAD(&ras_umc->pending_ecc_list);

	INIT_RADIX_TREE(&ras_umc->root, GFP_KERNEL);

	mutex_init(&ras_umc->tree_lock);
	mutex_init(&ras_umc->pending_ecc_lock);
	mutex_init(&ras_umc->umc_lock);
	mutex_init(&ras_umc->bank_log_lock);

	return 0;
}

int ras_umc_sw_fini(struct ras_core_context *ras_core)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	struct ras_umc_err_data *umc_err_data = &ras_umc->umc_err_data;
	struct ras_bank_ecc_node *ecc_node, *tmp;

	mutex_destroy(&ras_umc->umc_lock);
	mutex_destroy(&ras_umc->bank_log_lock);

	if (umc_err_data->rom_data.bps) {
		umc_err_data->rom_data.count = 0;
		kfree(umc_err_data->rom_data.bps);
		umc_err_data->rom_data.bps = NULL;
		umc_err_data->rom_data.space_left = 0;
	}

	if (umc_err_data->ram_data.bps) {
		umc_err_data->ram_data.count = 0;
		kfree(umc_err_data->ram_data.bps);
		umc_err_data->ram_data.bps = NULL;
		umc_err_data->ram_data.space_left = 0;
	}

	ras_umc_clear_logged_ecc(ras_core);

	mutex_lock(&ras_umc->pending_ecc_lock);
	list_for_each_entry_safe(ecc_node,
		tmp, &ras_umc->pending_ecc_list, node){
		list_del(&ecc_node->node);
		kfree(ecc_node);
	}
	ras_umc->pending_ecc_count = 0;
	ras_umc->pending_ecc_dropped = 0;
	mutex_unlock(&ras_umc->pending_ecc_lock);

	mutex_destroy(&ras_umc->tree_lock);
	mutex_destroy(&ras_umc->pending_ecc_lock);

	return 0;
}

int ras_umc_hw_init(struct ras_core_context *ras_core)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	uint32_t nps;

	nps = ras_core_get_curr_nps_mode(ras_core);

	if (!nps || (nps >= UMC_MEMORY_PARTITION_MODE_UNKNOWN)) {
		RAS_DEV_ERR(ras_core->dev, "Invalid memory NPS mode: %u!\n", nps);
		return -ENODATA;
	}

	ras_umc->umc_err_data.umc_nps_mode = nps;

	ras_umc->umc_vram_type = ras_core->config->umc_cfg.umc_vram_type;
	ras_umc->num_umc = ras_core->config->umc_cfg.num_umc;
	if (!ras_umc->umc_vram_type) {
		RAS_DEV_ERR(ras_core->dev, "Invalid UMC VRAM Type: %u!\n",
			ras_umc->umc_vram_type);
		return -ENODATA;
	}

	ras_umc->umc_ip_version = ras_core->config->umc_ip_version;
	ras_umc->ip_func = ras_umc_get_ip_func(ras_core, ras_umc->umc_ip_version);
	if (!ras_umc->ip_func)
		return -EINVAL;

	return 0;
}

int ras_umc_hw_fini(struct ras_core_context *ras_core)
{
	return 0;
}

int ras_umc_clean_badpage_data(struct ras_core_context *ras_core)
{
	struct ras_umc_err_data *data = &ras_core->ras_umc.umc_err_data;

	mutex_lock(&ras_core->ras_umc.umc_lock);

	kfree(data->rom_data.bps);
	kfree(data->ram_data.bps);

	memset(data, 0, sizeof(*data));
	mutex_unlock(&ras_core->ras_umc.umc_lock);

	return 0;
}

int ras_umc_fill_eeprom_record(struct ras_core_context *ras_core,
		uint64_t err_addr, uint32_t umc_inst, struct umc_phy_addr *cur_nps_addr,
		enum umc_memory_partition_mode cur_nps, struct eeprom_umc_record *record)
{
	struct eeprom_umc_record *err_rec = record;

	/* Set bad page pfn and nps mode */
	EEPROM_RECORD_SETUP_UMC_ADDR_AND_NPS(err_rec,
			RAS_ADDR_TO_PFN(cur_nps_addr->pa), cur_nps);

	err_rec->address = err_addr;
	err_rec->ts = ras_umc_get_eeprom_timestamp(ras_core);
	err_rec->err_type = RAS_EEPROM_ERR_NON_RECOVERABLE;
	err_rec->cu = 0;
	err_rec->mem_channel = cur_nps_addr->channel_idx;
	err_rec->mcumc_id = umc_inst;
	err_rec->cur_nps_retired_row_pfn = RAS_ADDR_TO_PFN(cur_nps_addr->pa);
	err_rec->cur_nps_bank = cur_nps_addr->bank;
	err_rec->cur_nps = cur_nps;
	return 0;
}

int ras_umc_get_saved_eeprom_count(struct ras_core_context *ras_core)
{
	struct ras_umc_err_data *err_data = &ras_core->ras_umc.umc_err_data;

	return err_data->rom_data.count;
}

int ras_umc_get_badpage_count(struct ras_core_context *ras_core)
{
	struct eeprom_store_record *data = &ras_core->ras_umc.umc_err_data.ram_data;

	return data->count;
}

int ras_umc_get_badpage_record(struct ras_core_context *ras_core, uint32_t index, void *record)
{
	struct eeprom_store_record *data = &ras_core->ras_umc.umc_err_data.ram_data;

	if (index >= data->count)
		return -EINVAL;

	memcpy(record, &data->bps[index], sizeof(struct eeprom_umc_record));
	return 0;
}

bool ras_umc_check_retired_addr(struct ras_core_context *ras_core, uint64_t addr)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	struct eeprom_store_record *data = &ras_umc->umc_err_data.ram_data;
	uint64_t page_pfn = RAS_ADDR_TO_PFN(addr);
	int i, ret = false;

	mutex_lock(&ras_umc->umc_lock);
	for (i = 0; i < data->count; i++) {
		if (data->bps[i].cur_nps_retired_row_pfn == page_pfn) {
			ret = true;
			break;
		}
	}
	mutex_unlock(&ras_umc->umc_lock);

	return ret;
}

int ras_umc_translate_soc_pa_and_bank(struct ras_core_context *ras_core,
	uint64_t *soc_pa, struct umc_bank_addr *bank_addr, bool bank_to_pa)
{
	struct ras_umc *ras_umc = &ras_core->ras_umc;
	int ret = 0;

	if (bank_to_pa)
		ret = ras_umc->ip_func->bank_to_soc_pa(ras_core, *bank_addr, soc_pa);
	else
		ret = ras_umc->ip_func->soc_pa_to_bank(ras_core, *soc_pa, bank_addr);

	return ret;
}

uint32_t ras_umc_bit_wise_xor(uint32_t val)
{
	uint32_t result = 0;
	int i;

	for (i = 0; i < 32; i++)
		result = result ^ ((val >> i) & 0x1);

	return result;
}
