/*
 * Copyright (c) 2015-2020, NVIDIA CORPORATION.  All rights reserved.
 *
 * 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 AUTHORS OR COPYRIGHT HOLDERS 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 <nvgpu/bios.h>
#include <nvgpu/io.h>
#include <nvgpu/gk20a.h>
#include <nvgpu/string.h>
#include <nvgpu/soc.h>
#include <nvgpu/static_analysis.h>
#include <nvgpu/timers.h>

#include "bios_sw_gv100.h"
#include "bios_sw_tu104.h"

#if defined(CONFIG_NVGPU_NEXT) && defined(CONFIG_NVGPU_NON_FUSA)
#include "nvgpu_next_gpuid.h"
#endif

static void nvgpu_bios_parse_bit(struct gk20a *g, u32 offset);

int nvgpu_bios_devinit(struct gk20a *g,
		 struct nvgpu_bios *bios)
{
	if (bios == NULL) {
		return 0;
	}

	if (bios->devinit_bios != NULL) {
		return bios->devinit_bios(g);
	} else {
		return 0;
	}
}

int nvgpu_bios_preos_wait_for_halt(struct gk20a *g,
		 struct nvgpu_bios *bios)
{
	if (bios == NULL) {
		return 0;
	}

	if (bios->preos_wait_for_halt != NULL) {
		return bios->preos_wait_for_halt(g);
	} else {
		return 0;
	}
}

bool nvgpu_bios_check_dgpu(struct gk20a *g, u32 ver)
{
	bool is_supported;

	switch (ver) {

	case NVGPU_GPUID_GV100:
	case NVGPU_GPUID_TU104:
#if defined(CONFIG_NVGPU_NEXT) && defined(CONFIG_NVGPU_NON_FUSA)
	case NVGPU_NEXT_DGPU_GPUID:
#endif
		is_supported = true;
		break;

	default:
		is_supported = false;
		break;
	}

	return is_supported;
}

u32  nvgpu_bios_get_vbios_version(struct gk20a *g)
{
	u32 ver = nvgpu_safe_add_u32(g->params.gpu_arch, g->params.gpu_impl);
	u32 vbios_version;

	switch (ver) {

	case NVGPU_GPUID_GV100:
	case NVGPU_GPUID_TU104:
		if (nvgpu_platform_is_silicon(g)) {
			vbios_version = g->bios->vbios_version;
		} else {
			vbios_version = 0;
		}
		break;

	default:
		vbios_version = 0;
		break;
	}

	return vbios_version;
}

u8  nvgpu_bios_get_vbios_oem_version(struct gk20a *g)
{
	u32 ver = nvgpu_safe_add_u32(g->params.gpu_arch, g->params.gpu_impl);
	u8 vbios_oem_version;

	switch (ver) {

	case NVGPU_GPUID_GV100:
	case NVGPU_GPUID_TU104:
		if (nvgpu_platform_is_silicon(g)) {
			vbios_oem_version = g->bios->vbios_oem_version;
		} else {
			vbios_oem_version = 0;
		}
		break;

	default:
		vbios_oem_version = 0;
		break;
	}

	return vbios_oem_version;
}

struct bit_token *nvgpu_bios_get_bit_token(struct gk20a *g, u8 token_id)
{
	struct bit_token *token = NULL;

	switch (token_id) {
	case NVGPU_BIOS_CLOCK_TOKEN:
		token = g->bios->clock_token;
		break;

	case NVGPU_BIOS_PERF_TOKEN:
		token = g->bios->perf_token;
		break;

	case NVGPU_BIOS_VIRT_TOKEN:
		token = g->bios->virt_token;
		break;

	default:
		token = NULL;
		break;
	}
	return token;
}

int nvgpu_bios_sw_init(struct gk20a *g)
{
	u32 ver = nvgpu_safe_add_u32(g->params.gpu_arch, g->params.gpu_impl);
	int err = 0;

#ifdef CONFIG_NVGPU_SIM
	if (nvgpu_is_enabled(g, NVGPU_IS_FMODEL)) {
		goto done;
	}
#endif

	if (nvgpu_bios_check_dgpu(g, ver) == false) {
		goto done;
	}

	if (g->bios != NULL) {
		/* skip alloc/reinit for unrailgate sequence */
		nvgpu_pmu_dbg(g, "skip bios init for unrailgate sequence");
		goto done;
	}

	g->bios = (struct nvgpu_bios *)
		nvgpu_kzalloc(g, sizeof(struct nvgpu_bios));
	if (g->bios == NULL) {
		err = -ENOMEM;
		goto done;
	}

	switch (ver) {
#ifdef CONFIG_NVGPU_DGPU
	case NVGPU_GPUID_GV100:
		nvgpu_gv100_bios_sw_init(g, g->bios);
		break;

	case NVGPU_GPUID_TU104:
		nvgpu_tu104_bios_sw_init(g, g->bios);
		break;

#if defined(CONFIG_NVGPU_NEXT) && defined(CONFIG_NVGPU_NON_FUSA)
	case NVGPU_NEXT_DGPU_GPUID:
		/*
		 * TODO
		 * After IFR region removal from bios image this can
		 * be replaced with nvgpu_tu104_bios_sw_init.
		 */
		err = tu104_bios_verify_devinit(g);
		break;
#endif
#endif
	default:
		goto clean_bios;
	}

	if ((g->bios)->init != NULL) {
		err = g->bios->init(g);
		if (err != 0) {
			nvgpu_falcon_sw_free(g, FALCON_ID_FECS);
			goto clean_bios;
		}
	}
done:
	return err;

clean_bios:
	nvgpu_kfree(g, g->bios);
	g->bios = NULL;
	return err;

}

void nvgpu_bios_sw_deinit(struct gk20a *g, struct nvgpu_bios *bios)
{
	if (bios == NULL) {
		return;
	} else {
		nvgpu_kfree(g, bios);
	}
}

static u16 nvgpu_bios_rdu16(struct gk20a *g, u32 offset)
{
	u32 val;

	nvgpu_assert(offset < U32_MAX);
	/*
	 * cast up to u32 because cast to u16 promotes into an int and
	 * causes a CERT-C INT31 violation
	 */
	val = nvgpu_safe_add_u32(U32(g->bios->data[offset+1U]) << U32(8),
		U32(g->bios->data[offset]));
	return nvgpu_safe_cast_u32_to_u16(val);
}

static u32 nvgpu_bios_rdu32(struct gk20a *g, u32 offset)
{
	u32 val;

	nvgpu_assert(offset <= (U32_MAX - 3U));
	val = nvgpu_safe_add_u32(U32(g->bios->data[offset+3U]) << U32(24),
		  U32(g->bios->data[offset+2U]) << U32(16));
	val = nvgpu_safe_add_u32(val, U32(g->bios->data[offset+1U]) << U32(8));
	val = nvgpu_safe_add_u32(val, U32(g->bios->data[offset]));
	return val;
}

int nvgpu_bios_parse_rom(struct gk20a *g)
{
	u32 offset = 0;
	u8 last = 0;
	bool found = false;
	unsigned int i;

	while (last == 0U) {
		struct pci_exp_rom pci_rom;
		struct pci_data_struct pci_data;
		struct pci_ext_data_struct pci_ext_data;

		nvgpu_memcpy((u8 *)&pci_rom, (u8 *)(g->bios->data + offset),
				sizeof(struct pci_exp_rom));
		nvgpu_log_fn(g, "pci rom sig %04x ptr %04x block %x",
				pci_rom.sig, pci_rom.pci_data_struct_ptr,
				pci_rom.size_of_block);

		if (pci_rom.sig != PCI_EXP_ROM_SIG &&
		    pci_rom.sig != PCI_EXP_ROM_SIG_NV) {
			nvgpu_err(g, "invalid VBIOS signature");
			return -EINVAL;
		}

		nvgpu_memcpy((u8 *)&pci_data, (u8 *)(g->bios->data + offset +
				pci_rom.pci_data_struct_ptr),
				sizeof(struct pci_data_struct));
		nvgpu_log_fn(g, "pci data sig %08x len %d image len %x type %x last %d max %08x",
				pci_data.sig, pci_data.pci_data_struct_len,
				pci_data.image_len, pci_data.code_type,
				pci_data.last_image,
				pci_data.max_runtime_image_len);

		/* Get Base ROM Size */
		if (pci_data.code_type ==
				PCI_DATA_STRUCTURE_CODE_TYPE_VBIOS_BASE) {
			g->bios->base_rom_size = nvgpu_safe_mult_u32(
						(u32)pci_data.image_len,
						PCI_ROM_IMAGE_BLOCK_SIZE);
			nvgpu_log_fn(g, "Base ROM Size: %x",
						g->bios->base_rom_size);
		}

		/* Get Expansion ROM offset:
		 * In the UEFI case, the expansion ROM where the Perf tables
		 * are located is not necessarily immediately after the base
		 * VBIOS image. Some VBIOS images uses a "private image" layout,
		 * where the order of the images is the VBIOS base block,
		 * the UEFI ROM, the expansion ROM, and then the cert. So we
		 * need to add the UEFI ROM size to offsets within the
		 * expansion ROM.
		 */
		if (pci_data.code_type ==
				PCI_DATA_STRUCTURE_CODE_TYPE_VBIOS_UEFI) {
			u32 ext_offset;

			ext_offset = nvgpu_safe_add_u32(offset,
					  pci_rom.pci_data_struct_ptr);
			ext_offset = nvgpu_safe_add_u32(ext_offset,
					  pci_data.pci_data_struct_len);
			ext_offset = nvgpu_safe_add_u32(ext_offset,
					  0xfU) & ~0xfU;
			nvgpu_memcpy((u8 *)&pci_ext_data, (u8 *)(g->bios->data +
					ext_offset),
					sizeof(struct pci_ext_data_struct));
			nvgpu_log_fn(g, "pci ext data sig %08x rev %x len %x sub_image_len %x priv_last %d flags %x",
					pci_ext_data.sig,
					pci_ext_data.nv_pci_data_ext_rev,
					pci_ext_data.nv_pci_data_ext_len,
					pci_ext_data.sub_image_len,
					pci_ext_data.priv_last_image,
					pci_ext_data.flags);

			nvgpu_log_fn(g, "expansion rom offset %x",
				nvgpu_safe_mult_u32((u32)pci_data.image_len,
						PCI_ROM_IMAGE_BLOCK_SIZE));
			g->bios->expansion_rom_offset = nvgpu_safe_mult_u32(
					(u32)pci_data.image_len,
						PCI_ROM_IMAGE_BLOCK_SIZE);
			offset = nvgpu_safe_add_u32(offset,
					nvgpu_safe_mult_u32(
						(u32)pci_ext_data.sub_image_len,
						PCI_ROM_IMAGE_BLOCK_SIZE));
			last = pci_ext_data.priv_last_image;
		} else {
			offset = nvgpu_safe_add_u32(offset,
					nvgpu_safe_mult_u32(
					(u32)pci_data.image_len,
						PCI_ROM_IMAGE_BLOCK_SIZE));
			last = pci_data.last_image;
		}
	}

	nvgpu_log_info(g, "read bios");
	for (i = 0; i < nvgpu_safe_sub_u64(g->bios->size, 6ULL); i++) {
		if (nvgpu_bios_rdu16(g, i) == BIT_HEADER_ID &&
		    nvgpu_bios_rdu32(g, i+2U) ==  BIT_HEADER_SIGNATURE) {
			nvgpu_bios_parse_bit(g, i);
			found = true;
		}
	}

	if (!found) {
		return -EINVAL;
	} else {
		return 0;
	}
}

static void nvgpu_bios_parse_biosdata(struct gk20a *g, u32 offset)
{
	struct biosdata bios_data;

	nvgpu_memcpy((u8 *)&bios_data, &g->bios->data[offset],
			sizeof(bios_data));
	nvgpu_log_fn(g, "bios version %x, oem version %x",
			bios_data.version,
			bios_data.oem_version);

	g->bios->vbios_version = bios_data.version;
	g->bios->vbios_oem_version = bios_data.oem_version;
}

static void nvgpu_bios_parse_nvinit_ptrs(struct gk20a *g, u32 offset)
{
	struct nvinit_ptrs init_ptrs;

	nvgpu_memcpy((u8 *)&init_ptrs, &g->bios->data[offset],
		sizeof(init_ptrs));
	nvgpu_log_fn(g, "devinit ptr %x size %d", init_ptrs.devinit_tables_ptr,
			init_ptrs.devinit_tables_size);
	nvgpu_log_fn(g, "bootscripts ptr %x size %d", init_ptrs.bootscripts_ptr,
			init_ptrs.bootscripts_size);

	g->bios->devinit_tables = &g->bios->data[init_ptrs.devinit_tables_ptr];
	g->bios->devinit_tables_size = init_ptrs.devinit_tables_size;
	g->bios->bootscripts = &g->bios->data[init_ptrs.bootscripts_ptr];
	g->bios->bootscripts_size = init_ptrs.bootscripts_size;
	g->bios->condition_table_ptr = init_ptrs.condition_table_ptr;
	g->bios->nvlink_config_data_offset = init_ptrs.nvlink_config_data_ptr;
}
static void nvgpu_bios_parse_memory_ptrs(struct gk20a *g, u16 offset, u8 version)
{
	struct memory_ptrs_v1 v1;
	struct memory_ptrs_v2 v2;

	switch (version) {
	case MEMORY_PTRS_V1:
		nvgpu_memcpy((u8 *)&v1, &g->bios->data[offset], sizeof(v1));
		g->bios->mem_strap_data_count = v1.mem_strap_data_count;
		g->bios->mem_strap_xlat_tbl_ptr = v1.mem_strap_xlat_tbl_ptr;
		break;
	case MEMORY_PTRS_V2:
		nvgpu_memcpy((u8 *)&v2, &g->bios->data[offset], sizeof(v2));
		g->bios->mem_strap_data_count = v2.mem_strap_data_count;
		g->bios->mem_strap_xlat_tbl_ptr = v2.mem_strap_xlat_tbl_ptr;
		break;
	default:
		nvgpu_err(g, "unknown vbios memory table version %x", version);
		break;
	}

	return;
}

static void nvgpu_bios_parse_bios_board_id_ptrs(struct gk20a *g, u16 offset)
{
	struct bios_board_id board;

	nvgpu_memcpy((u8 *)&board, &g->bios->data[offset], sizeof(board));
	g->bios->vbios_board_id = board.board_id;
}

static void nvgpu_bios_parse_devinit_appinfo(struct gk20a *g, u32 dmem_offset)
{
	struct devinit_engine_interface interface;

	nvgpu_memcpy((u8 *)&interface, &g->bios->devinit.dmem[dmem_offset],
		sizeof(interface));
	nvgpu_log_fn(g, "devinit version %x tables phys %x script phys %x size %d",
			interface.version,
			interface.tables_phys_base,
			interface.script_phys_base,
			interface.script_size);

	if (interface.version != 1U) {
		return;
	}
	g->bios->devinit_tables_phys_base = interface.tables_phys_base;
	g->bios->devinit_script_phys_base = interface.script_phys_base;
}

static int nvgpu_bios_parse_appinfo_table(struct gk20a *g, u32 offset)
{
	struct application_interface_table_hdr_v1 hdr;
	u32 i;

	nvgpu_memcpy((u8 *)&hdr, &g->bios->data[offset], sizeof(hdr));

	nvgpu_log_fn(g, "appInfoHdr ver %d size %d entrySize %d entryCount %d",
			hdr.version, hdr.header_size,
			hdr.entry_size, hdr.entry_count);

	if (hdr.version != 1U) {
		return 0;
	}

	offset = nvgpu_safe_add_u32(offset,
			nvgpu_safe_cast_u64_to_u32(sizeof(hdr)));
	for (i = 0U; i < hdr.entry_count; i++) {
		struct application_interface_entry_v1 entry;

		nvgpu_memcpy((u8 *)&entry, &g->bios->data[offset],
			sizeof(entry));

		nvgpu_log_fn(g, "appInfo id %d dmem_offset %d",
				entry.id, entry.dmem_offset);

		if (entry.id == APPINFO_ID_DEVINIT) {
			nvgpu_bios_parse_devinit_appinfo(g, entry.dmem_offset);
		}

		offset = nvgpu_safe_add_u32(offset, hdr.entry_size);
	}

	return 0;
}

static int nvgpu_bios_parse_falcon_ucode_desc(struct gk20a *g,
		struct nvgpu_bios_ucode *ucode, u32 offset)
{
	union falcon_ucode_desc udesc;
	struct falcon_ucode_desc_v2 desc;
	u8 version;
	u16 desc_size;
	int ret = 0;

	nvgpu_memcpy((u8 *)&udesc, &g->bios->data[offset], sizeof(udesc));

	if (FALCON_UCODE_IS_VERSION_AVAILABLE(udesc)) {
		version = FALCON_UCODE_GET_VERSION(udesc);
		desc_size = FALCON_UCODE_GET_DESC_SIZE(udesc);
	} else {
		size_t tmp_size = sizeof(udesc.v1);

		version = 1;
		nvgpu_assert(tmp_size <= (size_t)U16_MAX);
		desc_size = U16(tmp_size);
	}

	switch (version) {
	case 1:
		desc.stored_size = udesc.v1.hdr_size.stored_size;
		desc.uncompressed_size = udesc.v1.uncompressed_size;
		desc.virtual_entry = udesc.v1.virtual_entry;
		desc.interface_offset = udesc.v1.interface_offset;
		desc.imem_phys_base = udesc.v1.imem_phys_base;
		desc.imem_load_size = udesc.v1.imem_load_size;
		desc.imem_virt_base = udesc.v1.imem_virt_base;
		desc.imem_sec_base = udesc.v1.imem_sec_base;
		desc.imem_sec_size = udesc.v1.imem_sec_size;
		desc.dmem_offset = udesc.v1.dmem_offset;
		desc.dmem_phys_base = udesc.v1.dmem_phys_base;
		desc.dmem_load_size = udesc.v1.dmem_load_size;
		break;
	case 2:
		nvgpu_memcpy((u8 *)&desc, (u8 *)&udesc, sizeof(udesc.v2));
		break;
	default:
		nvgpu_log_info(g, "invalid version");
		ret = -EINVAL;
		break;
	}
	if (ret != 0) {
		return ret;
	}

	nvgpu_log_info(g, "falcon ucode desc version %x len %x", version, desc_size);

	nvgpu_log_info(g, "falcon ucode desc stored size %x uncompressed size %x",
			desc.stored_size, desc.uncompressed_size);
	nvgpu_log_info(g, "falcon ucode desc virtualEntry %x, interfaceOffset %x",
			desc.virtual_entry, desc.interface_offset);
	nvgpu_log_info(g, "falcon ucode IMEM phys base %x, load size %x virt base %x sec base %x sec size %x",
			desc.imem_phys_base, desc.imem_load_size,
			desc.imem_virt_base, desc.imem_sec_base,
			desc.imem_sec_size);
	nvgpu_log_info(g, "falcon ucode DMEM offset %x phys base %x, load size %x",
			desc.dmem_offset, desc.dmem_phys_base,
			desc.dmem_load_size);

	if (desc.stored_size != desc.uncompressed_size) {
		nvgpu_log_info(g, "does not match");
		return -EINVAL;
	}

	ucode->code_entry_point = desc.virtual_entry;
	ucode->bootloader = &g->bios->data[offset] + desc_size;
	ucode->bootloader_phys_base = desc.imem_phys_base;
	ucode->bootloader_size = nvgpu_safe_sub_u32(desc.imem_load_size,
						desc.imem_sec_size);
	ucode->ucode = ucode->bootloader + ucode->bootloader_size;
	ucode->phys_base = nvgpu_safe_add_u32(ucode->bootloader_phys_base,
					ucode->bootloader_size);
	ucode->size = desc.imem_sec_size;
	ucode->dmem = ucode->bootloader + desc.dmem_offset;
	ucode->dmem_phys_base = desc.dmem_phys_base;
	ucode->dmem_size = desc.dmem_load_size;

	offset = nvgpu_safe_add_u32(offset, U32(desc_size));
	offset = nvgpu_safe_add_u32(offset, desc.dmem_offset);
	offset = nvgpu_safe_add_u32(offset, desc.interface_offset);
	ret = nvgpu_bios_parse_appinfo_table(g, offset);

	return ret;
}

static int nvgpu_bios_parse_falcon_ucode_table(struct gk20a *g, u32 offset)
{
	struct falcon_ucode_table_hdr_v1 hdr;
	u32 i;

	nvgpu_memcpy((u8 *)&hdr, &g->bios->data[offset], sizeof(hdr));
	nvgpu_log_fn(g, "falcon ucode table ver %d size %d entrySize %d entryCount %d descVer %d descSize %d",
			hdr.version, hdr.header_size,
			hdr.entry_size, hdr.entry_count,
			hdr.desc_version, hdr.desc_size);

	if (hdr.version != 1U) {
		return -EINVAL;
	}

	offset = nvgpu_safe_add_u32(offset, hdr.header_size);

	for (i = 0U; i < hdr.entry_count; i++) {
		struct falcon_ucode_table_entry_v1 entry;

		nvgpu_memcpy((u8 *)&entry, &g->bios->data[offset],
			sizeof(entry));

		nvgpu_log_fn(g, "falcon ucode table entry appid %x targetId %x descPtr %x",
				entry.application_id, entry.target_id,
				entry.desc_ptr);

		if (entry.target_id == TARGET_ID_PMU &&
		    entry.application_id == APPLICATION_ID_DEVINIT) {
			int err;

			err = nvgpu_bios_parse_falcon_ucode_desc(g,
					&g->bios->devinit, entry.desc_ptr);
			if (err != 0) {
				err = nvgpu_bios_parse_falcon_ucode_desc(g,
					&g->bios->devinit,
					nvgpu_safe_add_u32(entry.desc_ptr,
						g->bios->expansion_rom_offset));
			}

			if (err != 0) {
				nvgpu_err(g,
					  "could not parse devinit ucode desc");
			}
		} else if (entry.target_id == TARGET_ID_PMU &&
		    entry.application_id == APPLICATION_ID_PRE_OS) {
			int err;

			err = nvgpu_bios_parse_falcon_ucode_desc(g,
					&g->bios->preos, entry.desc_ptr);
			if (err != 0) {
				err = nvgpu_bios_parse_falcon_ucode_desc(g,
					&g->bios->preos,
					nvgpu_safe_add_u32(entry.desc_ptr,
						g->bios->expansion_rom_offset));
			}

			if (err != 0) {
				nvgpu_err(g,
					  "could not parse preos ucode desc");
			}
		} else {
			nvgpu_log_info(g, "App_id: %u and target_id: %u"
					" combination not supported.",
					entry.application_id,
					entry.target_id);
		}

		offset = nvgpu_safe_add_u32(offset, hdr.entry_size);
	}

	return 0;
}

static void nvgpu_bios_parse_falcon_data_v2(struct gk20a *g, u32 offset)
{
	struct falcon_data_v2 falcon_data;
	int err;

	nvgpu_memcpy((u8 *)&falcon_data, &g->bios->data[offset],
		sizeof(falcon_data));
	nvgpu_log_fn(g, "falcon ucode table ptr %x",
			falcon_data.falcon_ucode_table_ptr);
	err = nvgpu_bios_parse_falcon_ucode_table(g,
			falcon_data.falcon_ucode_table_ptr);
	if (err != 0) {
		err = nvgpu_bios_parse_falcon_ucode_table(g,
			nvgpu_safe_add_u32(falcon_data.falcon_ucode_table_ptr,
			g->bios->expansion_rom_offset));
	}

	if (err != 0) {
		nvgpu_err(g, "could not parse falcon ucode table");
	}
}

void *nvgpu_bios_get_perf_table_ptrs(struct gk20a *g,
		struct bit_token *ptoken, u8 table_id)
{
	u32 perf_table_id_offset = 0;
	u8 *perf_table_ptr = NULL;
	u8 data_size = 4;

	if (ptoken != NULL) {

		if (ptoken->token_id == TOKEN_ID_VIRT_PTRS) {
			perf_table_id_offset =
				*((u16 *)((uintptr_t)g->bios->data +
					  ptoken->data_ptr +
					  (U16(table_id) *
					   U16(PERF_PTRS_WIDTH_16))));
			data_size = PERF_PTRS_WIDTH_16;
		} else {
			perf_table_id_offset =
				*((u32 *)((uintptr_t)g->bios->data +
					  ptoken->data_ptr +
					  (U16(table_id) *
					   U16(PERF_PTRS_WIDTH))));
			data_size = PERF_PTRS_WIDTH;
		}
	} else {
		return (void *)perf_table_ptr;
	}

	if (table_id < (ptoken->data_size/data_size)) {

		nvgpu_log_info(g, "Perf_Tbl_ID-offset 0x%x Tbl_ID_Ptr-offset- 0x%x",
					(ptoken->data_ptr +
					(U16(table_id) * U16(data_size))),
					perf_table_id_offset);

		if (perf_table_id_offset != 0U) {
			/* check if perf_table_id_offset is beyond base rom */
			if (perf_table_id_offset > g->bios->base_rom_size) {
				u32 tmp_index = nvgpu_safe_add_u32(
						g->bios->expansion_rom_offset,
						perf_table_id_offset);
				perf_table_ptr = &g->bios->data[tmp_index];
			} else {
				perf_table_ptr =
					&g->bios->data[perf_table_id_offset];
			}
		} else {
			nvgpu_warn(g, "PERF TABLE ID %d is NULL",
					table_id);
		}
	} else {
		nvgpu_warn(g, "INVALID PERF TABLE ID - %d ", table_id);
	}

	return (void *)perf_table_ptr;
}

static void nvgpu_bios_parse_bit(struct gk20a *g, u32 offset)
{
	struct bios_bit bit;
	struct bit_token token;
	u32 i;

	nvgpu_log_fn(g, " ");
	nvgpu_memcpy((u8 *)&bit, &g->bios->data[offset], sizeof(bit));

	nvgpu_log_info(g, "BIT header: %04x %08x", bit.id, bit.signature);
	nvgpu_log_info(g, "tokens: %d entries * %d bytes",
			bit.token_entries, bit.token_size);

	offset = nvgpu_safe_add_u32(offset, bit.header_size);
	for (i = 0U; i < bit.token_entries; i++) {
		nvgpu_memcpy((u8 *)&token, &g->bios->data[offset],
			sizeof(token));

		nvgpu_log_info(g, "BIT token id %d ptr %d size %d ver %d",
				token.token_id, token.data_ptr,
				token.data_size, token.data_version);

		switch (token.token_id) {
		case TOKEN_ID_BIOSDATA:
			nvgpu_bios_parse_biosdata(g, token.data_ptr);
			break;
		case TOKEN_ID_NVINIT_PTRS:
			nvgpu_bios_parse_nvinit_ptrs(g, token.data_ptr);
			break;
		case TOKEN_ID_FALCON_DATA:
			if (token.data_version == 2U) {
				nvgpu_bios_parse_falcon_data_v2(g,
						token.data_ptr);
			}
			break;
		case TOKEN_ID_PERF_PTRS:
			g->bios->perf_token =
				(struct bit_token *)
					((uintptr_t)g->bios->data + offset);
			break;
		case TOKEN_ID_CLOCK_PTRS:
			g->bios->clock_token =
				(struct bit_token *)
					((uintptr_t)g->bios->data + offset);
			break;
		case TOKEN_ID_VIRT_PTRS:
			g->bios->virt_token =
				(struct bit_token *)
					((uintptr_t)g->bios->data + offset);
			break;
		case TOKEN_ID_MEMORY_PTRS:
			nvgpu_bios_parse_memory_ptrs(g, token.data_ptr,
				token.data_version);
			break;
		case TOKEN_ID_BIOS_BOARD_ID_PTRS:
			nvgpu_bios_parse_bios_board_id_ptrs(g,
				token.data_ptr);
			break;
		default:
			nvgpu_log_info(g, "Token id %d not supported",
							token.token_id);
			break;
		}

		offset = nvgpu_safe_add_u32(offset, bit.token_size);
	}
	nvgpu_log_fn(g, "done");
}

static u32 nvgpu_bios_readbyte_impl(struct gk20a *g, u32 offset)
{
	return g->bios->data[offset];
}

u8 nvgpu_bios_read_u8(struct gk20a *g, u32 offset)
{
	u32 val = nvgpu_bios_readbyte_impl(g, offset);

	return nvgpu_safe_cast_u32_to_u8(val);
}

s8 nvgpu_bios_read_s8(struct gk20a *g, u32 offset)
{
	u32 val;

	val = nvgpu_bios_readbyte_impl(g, offset);
	val = ((val & 0x80U) != 0U) ? (val | ~0xffU) : val;

	return nvgpu_safe_cast_u32_to_s8(val);
}

u16 nvgpu_bios_read_u16(struct gk20a *g, u32 offset)
{
	u32 val;

	nvgpu_assert(offset < U32_MAX);
	/*
	 * cast up to u32 because cast to u16 promotes into an int and
	 * causes a CERT-C INT31 violation
	 */
	val = U32(nvgpu_bios_readbyte_impl(g, offset) |
		(U32(nvgpu_bios_readbyte_impl(g, offset+1U)) << 8U));

	return nvgpu_safe_cast_u32_to_u16(val);
}

u32 nvgpu_bios_read_u32(struct gk20a *g, u32 offset)
{
	u32 val;

	nvgpu_assert(offset < (U32_MAX - 3U));
	val = U32(nvgpu_bios_readbyte_impl(g, offset) |
		(nvgpu_bios_readbyte_impl(g, offset+1U) << 8U) |
		(nvgpu_bios_readbyte_impl(g, offset+2U) << 16U) |
		(nvgpu_bios_readbyte_impl(g, offset+3U) << 24U));

	return val;
}

#ifdef CONFIG_NVGPU_DGPU
bool nvgpu_bios_wait_for_init_done(struct gk20a *g)
{
	struct nvgpu_timeout timeout;
	int err;

	err = nvgpu_timeout_init(g, &timeout,
		NVGPU_BIOS_DEVINIT_VERIFY_TIMEOUT_MS, NVGPU_TIMER_CPU_TIMER);
	if (err != 0) {
		return false;
	}

	/* Wait till vbios is completed */
	do {
		if (g->bios_is_init == true) {
			return true;
		}
		nvgpu_msleep(NVGPU_BIOS_DEVINIT_VERIFY_COMPLETION_MS);
	} while (nvgpu_timeout_expired(&timeout) == 0);

	if (g->bios_is_init == true) {
		return true;
	} else {
		return false;
	}
}
#endif
