/*
 * SPDX-FileCopyrightText: 2023 CERN (home.cern)
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

/**
 *	@file   eeprom.c
 * 	@brief  Library part that takes care of EEPROM serial number
 * 	@author Tomasz Włostowski
 * 	@author Juan David Gonzalez Cobas
 *	@date	October 17th 2018
 */


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>

#include <libusb-1.0/libusb.h>
#include <ftdi.h>

#include "liboattnusb.h"
#include "liboattnusb-i.h"

static void eeprom_backup(unsigned char *eeprom_raw, int sz)
{
	static char fname[64];
	FILE *f;
	struct timeval tv;

	gettimeofday(&tv, NULL);
	snprintf(fname, sizeof(fname),
		"/tmp/eeprom_dump-%10ld%06ld.bin", tv.tv_sec, tv.tv_usec);
	if ((f = fopen(fname, "wb")) != NULL) {
		fprintf(stderr, "backing up EEPROM into %s\n", fname);
		fwrite(eeprom_raw, sz, 1, f);
		fclose(f);
	}
}

int oau_factory_program_eeprom(unsigned int bus, unsigned int dev, char *serial)
{
	int ret;
	int b, d;
	char devbus[16];
	static uint8_t eeprom_raw[128];
	static char ser[64];
	struct ftdi_context *ctx;

	if ((ctx = ftdi_new()) == NULL) {
		fprintf(stderr, "cannot init libftdi, exiting\n");
		return -ENOMEM;
	}

	snprintf(devbus, sizeof(devbus), "d:%03u/%03u", bus, dev);
	ret = ftdi_usb_open_string(ctx, devbus);
	if (ret != 0) {
		fprintf(stderr, "cannot open usb device %s\n", devbus);
		return -ENODEV;
	}
	b = libusb_get_bus_number(libusb_get_device(ctx->usb_dev));
	d = libusb_get_port_number(libusb_get_device(ctx->usb_dev));

	// Read and backup any existing contents
	ret = ftdi_read_eeprom(ctx);
	if (ret != 0) {
		fprintf(stderr, "could not read eeprom of %03x:%03x\n", b, d);
		return -ENOENT;
	}
	ret = ftdi_get_eeprom_buf(ctx, eeprom_raw, sizeof(eeprom_raw));
	if (ret != 0) {
		fprintf(stderr, "could not read raw eeprom of %03x:%03x\n", b, d);
		return -ENOENT;
	}
	eeprom_backup(eeprom_raw, sizeof(eeprom_raw));

	// Ignore all existing data and start from scratch, setting all necessary fields.
	// Erasing is also necessary to properly detect the size of the attached EEPROM.
	ret = ftdi_eeprom_initdefaults(ctx, NULL, NULL, NULL);
	if (ret != 0) {
		fprintf(stderr, "could not init eeprom of %03x:%03x\n", b, d);
		return -EINVAL;
	}
	ret = ftdi_erase_eeprom(ctx);
	if (ret != 0) {
		fprintf(stderr, "could not erase eeprom of %03x:%03x\n", b, d);
		return -EIO;
	}
	snprintf(ser, sizeof(ser), serial);
	ret = 0;
	ret = ret || ftdi_eeprom_set_strings(ctx, OATTNUSB_MANUFACTURER, OATTNUSB_PRODUCT, ser);
	ret = ret || ftdi_set_eeprom_value(ctx, VENDOR_ID, OATTNUSB_VENDOR_ID);
	ret = ret || ftdi_set_eeprom_value(ctx, PRODUCT_ID, OATTNUSB_DEVICE_ID);
	ret = ret || ftdi_set_eeprom_value(ctx, CHANNEL_A_TYPE, CHANNEL_IS_FIFO);
	ret = ret || ftdi_set_eeprom_value(ctx, CHANNEL_B_TYPE, CHANNEL_IS_FIFO);
	ret = ret || ftdi_set_eeprom_value(ctx, CHANNEL_A_DRIVER, DRIVER_VCP);
	ret = ret || ftdi_set_eeprom_value(ctx, CHANNEL_B_DRIVER, DRIVER_VCP);

	// Try to rebuild the binary buffer, back it up and check that it is valid
	ret = ret || (ftdi_eeprom_build(ctx) < 0);
	eeprom_backup(eeprom_raw, sizeof(eeprom_raw));
	ret = ret || ftdi_eeprom_decode(ctx, 1);

	if (ret != 0) {
		fprintf(stderr, "error building new EEPROM, cowardly refusing to write it\n");
		return -EINVAL;
	}

	ret = ftdi_write_eeprom(ctx);
	if (ret == 0) {
		fprintf(stderr, "successful EEPROM write, resetting USB device\n");
		libusb_reset_device(ctx->usb_dev);
	} else {
		fprintf(stderr, "EEPROM write error, ftdi_write_eeprom returned %d\n", ret);
		return -EIO;
	}

	ftdi_free(ctx);

	return 0;
}
