/*
 * This work is part of the White Rabbit project
 *
 * Copyright (C) 2012, 2013 CERN (www.cern.ch)
 * Author: Grzegorz Daniluk <grzegorz.daniluk@cern.ch>
 * Author: Alessandro Rubini <rubini@gnudd.com>
 *
 * Released according to the GNU GPL, version 2 or any later version.
 */
#include <wrc.h>
#include <w1.h>
#include <storage.h>

#include "types.h"
#include "i2c.h"
#include "onewire.h"
#include "endpoint.h"
#include <sdb.h>

#define SDBFS_BIG_ENDIAN
#include <libsdbfs.h>
#include <flash.h>

/*
 * This source file is a drop-in replacement of the legacy one: it manages
 * both i2c and w1 devices even if the interface is the old i2c-based one
 */
#define SDB_VENDOR	0x46696c6544617461LL /* "FileData" */
#define SDB_DEV_INIT	0x77722d69 /* wr-i (nit) */
#define SDB_DEV_MAC	0x6d61632d /* mac- (address) */
#define SDB_DEV_SFP	0x7366702d /* sfp- (database) */
#define SDB_DEV_CALIB	0x63616c69 /* cali (bration) */

/* constants for scanning I2C EEPROMs */
#define EEPROM_START_ADR 0
#define EEPROM_STOP_ADR  127

/* Functions for Flash access */
static int sdb_flash_read(struct sdbfs *fs, int offset, void *buf, int count)
{
	return flash_read(offset, buf, count);
}

static int sdb_flash_write(struct sdbfs *fs, int offset, void *buf, int count)
{
	return flash_write(offset, buf, count);
}

static int sdb_flash_erase(struct sdbfs *fs, int offset, int count)
{
	return flash_erase(offset, count);
}

/* The methods for W1 access */
static int sdb_w1_read(struct sdbfs *fs, int offset, void *buf, int count)
{
	return w1_read_eeprom_bus(fs->drvdata, offset, buf, count);
}

static int sdb_w1_write(struct sdbfs *fs, int offset, void *buf, int count)
{
	return w1_write_eeprom_bus(fs->drvdata, offset, buf, count);
}

static int sdb_w1_erase(struct sdbfs *fs, int offset, int count)
{
	return w1_erase_eeprom_bus(fs->drvdata, offset, count);
}

/*
 * I2C code.
 * The functions in ./eeprom.c (legacy) are replicated here with the sdb
 * calling convention. So we miss the low-level and high-level layer splitting
 */
struct i2c_params {
	int ifnum;
	int addr;
};

static struct i2c_params i2c_params;

static int sdb_i2c_read(struct sdbfs *fs, int offset, void *buf, int count)
{
	int i;
	struct i2c_params *p = fs->drvdata;
	unsigned char *cb = buf;

	mi2c_start(p->ifnum);
	if (mi2c_put_byte(p->ifnum, p->addr << 1) < 0) {
		mi2c_stop(p->ifnum);
		return -1;
	}
	mi2c_put_byte(p->ifnum, (offset >> 8) & 0xff);
	mi2c_put_byte(p->ifnum, offset & 0xff);
	mi2c_repeat_start(p->ifnum);
	mi2c_put_byte(p->ifnum, (p->addr << 1) | 1);
	for (i = 0; i < count - 1; ++i) {
		mi2c_get_byte(p->ifnum, cb, 0);
		cb++;
	}
	mi2c_get_byte(p->ifnum, cb, 1);
	cb++;
	mi2c_stop(p->ifnum);

	return count;
}

static int sdb_i2c_write(struct sdbfs *fs, int offset, void *buf, int count)
{
	int i, busy;
	struct i2c_params *p = fs->drvdata;
	unsigned char *cb = buf;

	for (i = 0; i < count; i++) {
		mi2c_start(p->ifnum);

		if (mi2c_put_byte(p->ifnum, p->addr << 1) < 0) {
			mi2c_stop(p->ifnum);
			return -1;
		}
		mi2c_put_byte(p->ifnum, (offset >> 8) & 0xff);
		mi2c_put_byte(p->ifnum, offset & 0xff);
		mi2c_put_byte(p->ifnum, *cb++);
		offset++;
		mi2c_stop(p->ifnum);

		do {		/* wait until the chip becomes ready */
			mi2c_start(p->ifnum);
			busy = mi2c_put_byte(p->ifnum, p->addr << 1);
			mi2c_stop(p->ifnum);
		} while (busy);

	}
	return count;
}

static int sdb_i2c_erase(struct sdbfs *fs, int offset, int count)
{
	int i, busy;
	struct i2c_params *p = fs->drvdata;

	for (i = 0; i < count; i++) {
		mi2c_start(p->ifnum);

		if (mi2c_put_byte(p->ifnum, p->addr << 1) < 0) {
			mi2c_stop(p->ifnum);
			return -1;
		}
		mi2c_put_byte(p->ifnum, (offset >> 8) & 0xff);
		mi2c_put_byte(p->ifnum, offset & 0xff);
		mi2c_put_byte(p->ifnum, 0xff);
		offset++;
		mi2c_stop(p->ifnum);

		do {		/* wait until the chip becomes ready */
			mi2c_start(p->ifnum);
			busy = mi2c_put_byte(p->ifnum, p->addr << 1);
			mi2c_stop(p->ifnum);
		} while (busy);

	}
	return count;
}

/*
 * A trivial dumper, just to show what's up in there
 */
static void storage_sdb_list(struct sdbfs *fs)
{
	struct sdb_device *d;
	int new = 1;
	while ((d = sdbfs_scan(fs, new)) != NULL) {
		d->sdb_component.product.record_type = '\0';
		pp_printf("file 0x%08x @ %4i, name %s\n",
			  (int)(d->sdb_component.product.device_id),
			  (int)(d->sdb_component.addr_first),
			  (char *)(d->sdb_component.product.name));
		new = 0;
	}
}
/* The sdb filesystem itself, build-time initialized for i2c */
static struct sdbfs wrc_sdb = {
	.name = "eeprom",
	.blocksize = 1, /* Not currently used */
	.drvdata = &i2c_params,
	.read = sdb_i2c_read,
	.write = sdb_i2c_write,
};

uint8_t has_eeprom = 0; /* modified at init time */

/*
 * Init: sets "int has_eeprom" above
 *
 * This is called by wrc_main, after initializing both w1 and i2c
 */
void storage_init(int chosen_i2cif, int chosen_i2c_addr)
{
	uint32_t magic = 0;
	static unsigned entry_points_eeprom[] = {0, 64, 128, 256, 512, 1024};
	static unsigned entry_points_flash[] = {
				0x000000,	/* flash base */
				0x100,		/* second page in flash */
				0x200,		/* IPMI with MultiRecord */
				0x300,		/* IPMI with larger MultiRecord */
				0x170000,	/* after first FPGA bitstream */
				0x2e0000,	/* after MultiBoot bitstream */
				0x600000};	/* after SVEC AFPGA bitstream */
	int i, ret;

	/*
	 * 1. Check if there is SDBFS in the Flash.
	 */
	for (i = 0; i < ARRAY_SIZE(entry_points_flash); i++) {
		flash_read(entry_points_flash[i], (void *)&magic, sizeof(magic));
		if (magic == SDB_MAGIC)
			break;
	}
	if (magic == SDB_MAGIC) {
		pp_printf("sdbfs: found at %i in Flash\n",
				entry_points_flash[i]);
		wrc_sdb.drvdata = NULL;
		wrc_sdb.blocksize = FLASH_BLOCKSIZE;
		wrc_sdb.entrypoint = entry_points_flash[i];
		wrc_sdb.read = sdb_flash_read;
		wrc_sdb.write = sdb_flash_write;
		wrc_sdb.erase = sdb_flash_erase;
		goto found_exit;
	}


	/*
	 * 2. Look for w1 first: if there is no eeprom it fails fast
	 */
	for (i = 0; i < ARRAY_SIZE(entry_points_eeprom); i++) {
		ret = w1_read_eeprom_bus(&wrpc_w1_bus, entry_points_eeprom[i],
					 (void *)&magic, sizeof(magic));
		if (ret != sizeof(magic))
			break;
		if (magic == SDB_MAGIC)
			break;
	}
	if (magic == SDB_MAGIC) {
		pp_printf("sdbfs: found at %i in W1\n", entry_points_eeprom[i]);
		/* override default i2c settings with w1 ones */
		wrc_sdb.drvdata = &wrpc_w1_bus;
		wrc_sdb.blocksize = 1;
		wrc_sdb.entrypoint = entry_points_eeprom[i];
		wrc_sdb.read = sdb_w1_read;
		wrc_sdb.write = sdb_w1_write;
		wrc_sdb.erase = sdb_w1_erase;
		goto found_exit;
	}

	/*
	 * 3. If w1 failed, look for i2c: start from low offsets.
	 */
	i2c_params.ifnum = chosen_i2cif;
	i2c_params.addr = EEPROM_START_ADR;
	while (i2c_params.addr <= EEPROM_STOP_ADR) {
		/* First, we check if I2C EEPROM is there */
	        if (!mi2c_devprobe(i2c_params.ifnum, i2c_params.addr)) {
			i2c_params.addr++;
			continue;
		}
		/* While looking for the magic number, use sdb-based read function */
		for (i = 0; i < ARRAY_SIZE(entry_points_eeprom); i++) {
			sdb_i2c_read(&wrc_sdb, entry_points_eeprom[i], (void *)&magic,
				    sizeof(magic));
			if (magic == SDB_MAGIC)
				break;
		}
		if (magic == SDB_MAGIC) {
			pp_printf("sdbfs: found at %i in I2C(0x%2X)\n",
				entry_points_eeprom[i], i2c_params.addr);
			wrc_sdb.drvdata = &i2c_params;
			wrc_sdb.blocksize = 1;
			wrc_sdb.entrypoint = entry_points_eeprom[i];
			wrc_sdb.read = sdb_i2c_read;
			wrc_sdb.write = sdb_i2c_write;
			wrc_sdb.erase = sdb_i2c_erase;
			goto found_exit;
		}
		i2c_params.addr++;
	}

	if (i2c_params.addr == EEPROM_STOP_ADR) {
		pp_printf("No SDB filesystem in i2c eeprom\n");
		return;
	}

found_exit:
	/* found: register the filesystem */
	has_eeprom = 1;
	sdbfs_dev_create(&wrc_sdb);
	storage_sdb_list(&wrc_sdb);
	return;
}

/*
 * Reading/writing the MAC address used to be part of dev/onewire.c,
 * but is not onewire-specific.  What is w1-specific is the default
 * setting if no sdbfs is there, but CONFIG_SDB_STORAGE depends on
 * CONFIG_W1 anyways.
 */
int get_persistent_mac(uint8_t portnum, uint8_t *mac)
{
	int ret;
	int i, class;
	uint64_t rom;

	if (IS_HOST_PROCESS) {
		/* we don't have sdb working, so get the real eth address */
		get_mac_addr(mac);
		return 0;
	}

	if (sdbfs_open_id(&wrc_sdb, SDB_VENDOR, SDB_DEV_MAC) < 0)
		return -1;

	ret = sdbfs_fread(&wrc_sdb, 0, mac, 6);
	sdbfs_close(&wrc_sdb);

	if (ret < 0)
		pp_printf("%s: SDB error\n", __func__);
	if (mac[0] == 0xff ||
	    (mac[0] | mac[1] | mac[2] | mac[3] | mac[4] | mac[5]) == 0) {
		pp_printf("%s: SDB file is empty\n", __func__);
		ret = -1;
	}
	if (ret < 0) {
		pp_printf("%s: Using W1 serial number\n", __func__);
		for (i = 0; i < W1_MAX_DEVICES; i++) {
			class = w1_class(wrpc_w1_bus.devs + i);
			if (class != 0x28 && class != 0x42)
				continue;
			rom = wrpc_w1_bus.devs[i].rom;
			mac[0] = 0x22;
			mac[1] = 0x33;
			mac[2] = rom >> 32;
			mac[3] = rom >> 24;
			mac[4] = rom >> 16;
			mac[5] = rom >> 8;
			ret = 0;
		}
	}
	if (ret < 0) {
		pp_printf("%s: failure\n", __func__);
		return -1;
	}
	return 0;
}

int set_persistent_mac(uint8_t portnum, uint8_t *mac)
{
	int ret;

	ret = sdbfs_open_id(&wrc_sdb, SDB_VENDOR, SDB_DEV_MAC);
	if (ret >= 0) {
		sdbfs_ferase(&wrc_sdb, 0, wrc_sdb.f_len);
		ret = sdbfs_fwrite(&wrc_sdb, 0, mac, 6);
	}
	sdbfs_close(&wrc_sdb);

	if (ret < 0) {
		pp_printf("%s: SDB error, can't save\n", __func__);
		return -1;
	}
	return 0;
}

/*
 * The SFP section is placed somewhere inside EEPROM (W1 or I2C), using sdbfs.
 *
 * Initially we have a count of SFP records
 *
 * For each sfp we have
 *
 * - part number (16 bytes)
 * - alpha (4 bytes)
 * - deltaTx (4 bytes)
 * - delta Rx (4 bytes)
 * - checksum (1 byte)  (low order 8 bits of the sum of all bytes)
 *
 * the total is 29 bytes for each sfp (ugly, but we are byte-oriented anyways
 */


/* Erase SFB database in the memory */
int32_t storage_sfpdb_erase(void)
{
	int ret;

	if (sdbfs_open_id(&wrc_sdb, SDB_VENDOR, SDB_DEV_SFP) < 0)
		return -1;
	ret = sdbfs_ferase(&wrc_sdb, 0, wrc_sdb.f_len);
	if (ret == wrc_sdb.f_len)
		ret = 1;
	sdbfs_close(&wrc_sdb);
	return ret == 1 ? 0 : -1;
}

/* Dummy check if sfp information is correct by verifying it doesn't have
 * 0xff bytes */
static int sfp_valid(struct s_sfpinfo *sfp)
{
	int i;

	for (i = 0; i < SFP_PN_LEN; ++i) {
		if (sfp->pn[i] == 0xff)
			return 0;
	}
	return 1;
}

static int sfp_entry(struct s_sfpinfo *sfp, uint8_t oper, uint8_t pos)
{
	static uint8_t sfpcount = 0;
	struct s_sfpinfo tempsfp;
	int ret = -1;
	uint8_t i, chksum = 0;
	uint8_t *ptr;
	int sdb_offset;

	if (pos >= SFPS_MAX)
		return EE_RET_POSERR;	/* position outside the range */

	if (sdbfs_open_id(&wrc_sdb, SDB_VENDOR, SDB_DEV_SFP) < 0)
		return -1;

	/* Read how many SFPs are in the database, but only in the first
	 * call */
	if (!pos) {
		sfpcount = 0;
		sdb_offset = sizeof(sfpcount);
		while (sdbfs_fread(&wrc_sdb, sdb_offset, &tempsfp,
					sizeof(tempsfp)) == sizeof(tempsfp)) {
			if (!sfp_valid(&tempsfp))
				break;
			sfpcount++;
			sdb_offset = sizeof(sfpcount) + sfpcount * sizeof(tempsfp);
		}
	}

	if ((oper == SFP_ADD) && (sfpcount == SFPS_MAX)) {
		/* no more space to add new SFPs */
		ret = EE_RET_DBFULL;
		goto out;
	}

	if (!pos && (oper == SFP_GET) && sfpcount == 0) {
		/* no SFPs in the database */
		ret = 0;
		goto out;
	}

	if (oper == SFP_GET) {
		sdb_offset = sizeof(sfpcount) + pos * sizeof(*sfp);
		if (sdbfs_fread(&wrc_sdb, sdb_offset, sfp, sizeof(*sfp))
				!= sizeof(*sfp))
			goto out;

		ptr = (uint8_t *)sfp;
		/* read sizeof() - 1 because we don't include checksum */
		for (i = 0; i < sizeof(struct s_sfpinfo) - 1; ++i)
			chksum = chksum + *(ptr++);
		if (chksum != sfp->chksum) {
			pp_printf("sfp: corrupted checksum\n");
			goto out;
		}
	}
	if (oper == SFP_ADD) {
		/* count checksum */
		ptr = (uint8_t *)sfp;
		/* use sizeof() - 1 because we don't include checksum */
		for (i = 0; i < sizeof(struct s_sfpinfo) - 1; ++i)
			chksum = chksum + *(ptr++);
		sfp->chksum = chksum;
		/* add SFP at the end of DB */
		sdb_offset = sizeof(sfpcount) + sfpcount * sizeof(*sfp);
		if (sdbfs_fwrite(&wrc_sdb, sdb_offset, sfp, sizeof(*sfp))
				!= sizeof(*sfp)) {
			goto out;
		}
		sfpcount++;
	}
	ret = sfpcount;
out:
	sdbfs_close(&wrc_sdb);
	return ret;
}

static int storage_update_sfp(struct s_sfpinfo *sfp)
{
	int sfpcount = 1;
	int temp;
	int8_t i;
	struct s_sfpinfo sfp_db[SFPS_MAX];
	struct s_sfpinfo *dbsfp;

	/* copy entries from flash to the memory, update entry if matched */
	for (i = 0; i < sfpcount; ++i) {
		dbsfp = &sfp_db[i];
		sfpcount = sfp_entry(dbsfp, SFP_GET, i);
		if (sfpcount <= 0)
			return sfpcount;
		if (!strncmp(dbsfp->pn, sfp->pn, 16)) {
			/* update matched entry */
			dbsfp->dTx = sfp->dTx;
			dbsfp->dRx = sfp->dRx;
			dbsfp->alpha = sfp->alpha;
		}
	}

	/* erase entire database */
	if (storage_sfpdb_erase() == EE_RET_I2CERR) {
			pp_printf("Could not erase DB\n");
			return -1;
		}

	/* add all SFPs */
	for (i = 0; i < sfpcount; ++i) {
		dbsfp = &sfp_db[i];
		temp = sfp_entry(dbsfp, SFP_ADD, 0);
		if (temp < 0) {
			/* if error, return it */
			return temp;
		}
	}
	return i;
}

int storage_get_sfp(struct s_sfpinfo *sfp, uint8_t oper, uint8_t pos)
{
	struct s_sfpinfo tmp_sfp;

	if (oper == SFP_GET) {
		/* Get SFP entry */
		return sfp_entry(sfp, SFP_GET, pos);
	}

	/* storage_match_sfp replaces content of parameter, so do the copy
	 * first */
	tmp_sfp = *sfp;
	if (!storage_match_sfp(&tmp_sfp)) { /* add a new sfp entry */
		pp_printf("Adding new SFP entry\n");
		return sfp_entry(sfp, SFP_ADD, 0);
	}

	pp_printf("Update existing SFP entry\n");
	return storage_update_sfp(sfp);
}

int storage_match_sfp(struct s_sfpinfo *sfp)
{
	uint8_t sfpcount = 1;
	int8_t i;
	struct s_sfpinfo dbsfp;

	for (i = 0; i < sfpcount; ++i) {
		sfpcount = sfp_entry(&dbsfp, SFP_GET, i);
		if (sfpcount <= 0)
			return sfpcount;
		if (!strncmp(dbsfp.pn, sfp->pn, 16)) {
			sfp->dTx = dbsfp.dTx;
			sfp->dRx = dbsfp.dRx;
			sfp->alpha = dbsfp.alpha;
			return 1;
		}
	}

	return 0;
}

/*
 * Phase transition ("calibration" file)
 */
#define VALIDITY_BIT 0x80000000
int storage_phtrans(uint32_t *valp, uint8_t write)
{
	int ret = -1;
	uint32_t value;

	if (sdbfs_open_id(&wrc_sdb, SDB_VENDOR, SDB_DEV_CALIB) < 0)
		return -1;
	if (write) {
		sdbfs_ferase(&wrc_sdb, 0, wrc_sdb.f_len);
		value = *valp | VALIDITY_BIT;
		if (sdbfs_fwrite(&wrc_sdb, 0, &value, sizeof(value))
		    != sizeof(value))
			goto out;
		ret = 1;
	} else {
		if (sdbfs_fread(&wrc_sdb, 0, &value, sizeof(value))
		    != sizeof(value))
			goto out;
		*valp = value & ~VALIDITY_BIT;
		ret = (value & VALIDITY_BIT) != 0;
	}
out:
	sdbfs_close(&wrc_sdb);
	return ret;
}

/*
 * The init script area consist of 2-byte size field and a set of
 * shell commands separated with '\n' character.
 *
 * -------------------
 * | bytes used (2B) |
 * ------------------------------------------------
 * | shell commands separated with '\n'.....      |
 * |                                              |
 * |                                              |
 * ------------------------------------------------
 */

int storage_init_erase(void)
{
	int ret;

	if (sdbfs_open_id(&wrc_sdb, SDB_VENDOR, SDB_DEV_INIT) < 0)
		return -1;
	ret = sdbfs_ferase(&wrc_sdb, 0, wrc_sdb.f_len);
	if (ret == wrc_sdb.f_len)
		ret = 1;
	sdbfs_close(&wrc_sdb);
	return ret == 1 ? 0 : -1;
}

/*
 * Appends a new shell command at the end of boot script
 */
int storage_init_add(const char *args[])
{
	int len, i;
	uint8_t separator = ' ';
	uint16_t used, readback;
	int ret = -1;
	uint8_t byte;

	if (sdbfs_open_id(&wrc_sdb, SDB_VENDOR, SDB_DEV_INIT) < 0)
		return -1;

	/* check how many bytes we already have there */
	used = 0;
	while (sdbfs_fread(&wrc_sdb, sizeof(used)+used, &byte, 1) == 1) {
		if (byte == 0xff)
			break;
		used++;
	}

	if (used > 256 /* 0xffff or wrong */)
		used = 0;

	i = 1; /* args[0] is "add" */
	while (args[i] != NULL) {
		len = strlen(args[i]);
		if (sdbfs_fwrite(&wrc_sdb, sizeof(used) + used,
				 (void *)args[i], len) != len)
			goto out;
		used += len;
		if (args[i+1] != NULL)	/* next one is another word of the same command */
			separator = ' ';
		else			/* no more words, end command with '\n' */
			separator = '\n';
		if (sdbfs_fwrite(&wrc_sdb, sizeof(used) + used,
				&separator, sizeof(separator))
		    != sizeof(separator))
			goto out;
		++used;
		++i;
	}
	/* and finally update the size of the script */
	if (sdbfs_fwrite(&wrc_sdb, 0, &used, sizeof(used)) != sizeof(used))
		goto out;

	if (sdbfs_fread(&wrc_sdb, 0, &readback, sizeof(readback))
	    != sizeof(readback))
		goto out;

	ret = 0;
out:
	sdbfs_close(&wrc_sdb);
	return ret;
}

int storage_init_show(void)
{
	int ret = -1;
	uint16_t used;
	uint8_t byte;

	if (sdbfs_open_id(&wrc_sdb, SDB_VENDOR, SDB_DEV_INIT) < 0)
		return -1;

	used = 0;
	do {
		if (sdbfs_fread(&wrc_sdb, sizeof(used) + used, &byte, 1) != 1)
			goto out;
		if (byte != 0xff) {
			pp_printf("%c", byte);
			used++;
		}
	} while (byte != 0xff);

	if (used == 0)
		pp_printf("Empty init script...\n");
	ret = 0;
out:
	sdbfs_close(&wrc_sdb);
	return ret;
}

int storage_init_readcmd(uint8_t *buf, uint8_t bufsize, uint8_t next)
{
	int i = 0, ret = -1;
	uint16_t used;
	static uint16_t ptr;

	if (sdbfs_open_id(&wrc_sdb, SDB_VENDOR, SDB_DEV_INIT) < 0)
		return -1;

	if (next == 0)
		ptr = sizeof(used);
	do {
		if (ptr - sizeof(used) > bufsize)
			goto out;
		if (sdbfs_fread(&wrc_sdb, (ptr++),
				&buf[i], sizeof(char)) != sizeof(char))
			goto out;
		if (buf[i] == 0xff)
			break;
	} while (buf[i++] != '\n');
	ret = i;
out:
	sdbfs_close(&wrc_sdb);
	return ret;
}
