/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2018 Netronome Systems, Inc.
 * All rights reserved.
 */

/* Parse the hwinfo table that the ARM firmware builds in the ARM scratch SRAM
 * after chip reset.
 *
 * Examples of the fields:
 *   me.count = 40
 *   me.mask = 0x7f_ffff_ffff
 *
 *   me.count is the total number of MEs on the system.
 *   me.mask is the bitmask of MEs that are available for application usage.
 *
 *   (ie, in this example, ME 39 has been reserved by boardconfig.)
 */

#include <stdio.h>
#include <time.h>

#include "nfp_cpp.h"
#include "nfp6000/nfp6000.h"
#include "nfp_resource.h"
#include "nfp_hwinfo.h"
#include "nfp_crc.h"

static int
nfp_hwinfo_is_updating(struct nfp_hwinfo *hwinfo)
{
	return hwinfo->version & NFP_HWINFO_VERSION_UPDATING;
}

static int
nfp_hwinfo_db_walk(struct nfp_hwinfo *hwinfo, uint32_t size)
{
	const char *key, *val, *end = hwinfo->data + size;

	for (key = hwinfo->data; *key && key < end;
	     key = val + strlen(val) + 1) {
		val = key + strlen(key) + 1;
		if (val >= end) {
			printf("Bad HWINFO - overflowing key\n");
			return -EINVAL;
		}

		if (val + strlen(val) + 1 > end) {
			printf("Bad HWINFO - overflowing value\n");
			return -EINVAL;
		}
	}
	return 0;
}

static int
nfp_hwinfo_db_validate(struct nfp_hwinfo *db, uint32_t len)
{
	uint32_t size, new_crc, *crc;

	size = db->size;
	if (size > len) {
		printf("Unsupported hwinfo size %u > %u\n", size, len);
		return -EINVAL;
	}

	size -= sizeof(uint32_t);
	new_crc = nfp_crc32_posix((char *)db, size);
	crc = (uint32_t *)(db->start + size);
	if (new_crc != *crc) {
		printf("Corrupt hwinfo table (CRC mismatch)\n");
		printf("\tcalculated 0x%x, expected 0x%x\n", new_crc, *crc);
		return -EINVAL;
	}

	return nfp_hwinfo_db_walk(db, size);
}

static struct nfp_hwinfo *
nfp_hwinfo_try_fetch(struct nfp_cpp *cpp, size_t *cpp_size)
{
	struct nfp_hwinfo *header;
	void *res;
	uint64_t cpp_addr;
	uint32_t cpp_id;
	int err;
	uint8_t *db;

	res = nfp_resource_acquire(cpp, NFP_RESOURCE_NFP_HWINFO);
	if (res) {
		cpp_id = nfp_resource_cpp_id(res);
		cpp_addr = nfp_resource_address(res);
		*cpp_size = nfp_resource_size(res);

		nfp_resource_release(res);

		if (*cpp_size < HWINFO_SIZE_MIN)
			return NULL;
	} else {
		return NULL;
	}

	db = malloc(*cpp_size + 1);
	if (!db)
		return NULL;

	err = nfp_cpp_read(cpp, cpp_id, cpp_addr, db, *cpp_size);
	if (err != (int)*cpp_size)
		goto exit_free;

	header = (void *)db;
	printf("NFP HWINFO header: %08x\n", *(uint32_t *)header);
	if (nfp_hwinfo_is_updating(header))
		goto exit_free;

	if (header->version != NFP_HWINFO_VERSION_2) {
		printf("Unknown HWInfo version: 0x%08x\n",
			header->version);
		goto exit_free;
	}

	/* NULL-terminate for safety */
	db[*cpp_size] = '\0';

	return (void *)db;
exit_free:
	free(db);
	return NULL;
}

static struct nfp_hwinfo *
nfp_hwinfo_fetch(struct nfp_cpp *cpp, size_t *hwdb_size)
{
	struct timespec wait;
	struct nfp_hwinfo *db;
	int count;

	wait.tv_sec = 0;
	wait.tv_nsec = 10000000;
	count = 0;

	for (;;) {
		db = nfp_hwinfo_try_fetch(cpp, hwdb_size);
		if (db)
			return db;

		nanosleep(&wait, NULL);
		if (count++ > 200) {
			printf("NFP access error\n");
			return NULL;
		}
	}
}

struct nfp_hwinfo *
nfp_hwinfo_read(struct nfp_cpp *cpp)
{
	struct nfp_hwinfo *db;
	size_t hwdb_size = 0;
	int err;

	db = nfp_hwinfo_fetch(cpp, &hwdb_size);
	if (!db)
		return NULL;

	err = nfp_hwinfo_db_validate(db, hwdb_size);
	if (err) {
		free(db);
		return NULL;
	}
	return db;
}

/*
 * nfp_hwinfo_lookup() - Find a value in the HWInfo table by name
 * @hwinfo:	NFP HWinfo table
 * @lookup:	HWInfo name to search for
 *
 * Return: Value of the HWInfo name, or NULL
 */
const char *
nfp_hwinfo_lookup(struct nfp_hwinfo *hwinfo, const char *lookup)
{
	const char *key, *val, *end;

	if (!hwinfo || !lookup)
		return NULL;

	end = hwinfo->data + hwinfo->size - sizeof(uint32_t);

	for (key = hwinfo->data; *key && key < end;
	     key = val + strlen(val) + 1) {
		val = key + strlen(key) + 1;

		if (strcmp(key, lookup) == 0)
			return val;
	}

	return NULL;
}
