/******************************************************************************
 *
 * Copyright (C) 2019 Xilinx, Inc.  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.
 *
 ******************************************************************************/
/*****************************************************************************/
/**
 * @file: fpgautil.c
 * Simple command line tool to load fpga via overlay or through sysfs interface
 * and read fpga configuration using Xilinx Zynq/ZynqMP fpga manager
 * Author: Appana Durga Kedareswara Rao <appanad@xilinx.com>
 * Author: Nava kishore Manne <navam@xilinx.com>
 */
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <getopt.h>
#include <poll.h>
#include <ctype.h>
#include <libgen.h>
#include <time.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/stat.h>

#define OVERLAY      1
#define FPGA_SYSFS   2
#define READBACK     3
#define ENCRYPTION_USERKEY_EN     (0x00000008U)

int fpga_getplatform()
{
        char fpstr[100];
        FILE *fptr;
        char *zynqmpstr = "Xilinx ZynqMP FPGA Manager";

        if ((fptr = fopen("/sys/class/fpga_manager/fpga0/name", "r")) == NULL)
        {
                printf("Error! opening file");
                // Program exits if file pointer returns NULL.
                exit(1);
         }

        // reads text until newline
        fscanf(fptr,"%[^\n]", fpstr);
        fclose(fptr);

        if (!strcmp(zynqmpstr, fpstr))
                return 1;
        else
                return 0;

}

void print_usage(char *prg)
{
	int iszynqmp = fpga_getplatform();

	fprintf(stderr, "\n%s: FPGA Utility for Loading/reading PL Configuration\n\n", prg);
	fprintf(stderr, "Usage:	%s -b <bin file path> -o <dtbo file path>\n\r", prg);
	fprintf(stderr, "\n");
	fprintf(stderr, "Options: -b <binfile>		(Bin file path)\n");
	fprintf(stderr, "         -o <dtbofile>		(DTBO file path)\n");
	fprintf(stderr, "         -f <flags>		Optional: <Bitstream type flags>\n");
	fprintf(stderr, "				   f := <Full | Partial > \n");

	if (iszynqmp)
	{
		fprintf(stderr, "				Default: <Full>\n");
		fprintf(stderr, "	  -s <secure flags>	Optional: <Secure flags>\n");
		fprintf(stderr, "				   s := <AuthDDR | AuthOCM | EnUsrKey | EnDevKey | AuthEnUsrKeyDDR | AuthEnUsrKeyOCM | AuthEnDevKeyDDR | AuthEnDevKeyOCM>\n");
		fprintf(stderr, "	  -k <AesKey>		Optional: <AES User Key>\n");
		fprintf(stderr, "	  -r <Readback> 	Optional: <file name>\n");
		fprintf(stderr, "				Default: By default Read back contents will be stored in readback.bin file\n");
		fprintf(stderr, "	  -t			Optional: <Readback Type>\n");
		fprintf(stderr, "				   0 - Configuration Register readback\n");
		fprintf(stderr, "				   1 - Configuration Data Frames readback\n");
		fprintf(stderr, "				Default: 0 (Configuration register readback)\n");
		fprintf(stderr, "	  -R 			Optional: Remove overlay from a live tree\n");
	}

	fprintf(stderr, " \n");
	fprintf(stderr, "Examples:\n");
	fprintf(stderr, "(Load Full bitstream using Overlay)\n");
	fprintf(stderr, "%s -b top.bit.bin -o can.dtbo\n", prg);
	fprintf(stderr, "(Load Partial bitstream through the sysfs interface)\n");
	fprintf(stderr, "%s -b top.bit.bin -f Partial \n", prg);
	if (iszynqmp)
	{
		fprintf(stderr, "(Load Authenticated bitstream through the sysfs interface)\n");
		fprintf(stderr, "%s -b top.bit.bin -f Full -s AuthDDR \n", prg);
		fprintf(stderr, "(Load Parital Encrypted Userkey bitstream using Overlay)\n");
		fprintf(stderr, "%s -b top.bit.bin -o pl.dtbo -f Partial -s EnUsrKey -k <32byte key value>\n", prg);
		fprintf(stderr, "(Read PL Configuration Registers)\n");
		fprintf(stderr, "%s -b top.bit.bin -r\n", prg);
	}
	fprintf(stderr, " \n");
}

int gettime(struct timeval  t0, struct timeval t1)
{
        return ((t1.tv_sec - t0.tv_sec) * 1000.0f + (t1.tv_usec -t0.tv_usec) / 1000.0f);
}

int fpga_state()
{
	FILE *fptr;
	char buf[10], *state;
	
	system("cat /sys/class/fpga_manager/fpga0/state >> state.txt");
	state = "operating";
	fptr = fopen("state.txt", "r");
	if (fptr) {
		fgets(buf, 10, fptr);
		fclose(fptr);
		system("rm state.txt");
		if (strcmp(buf, state) == 0)
			return 0;
		else
			return 1;
	}
	
	return 1;
}

struct fpgaflag {
        char *flag;
        unsigned int value;
};

static struct fpgaflag flagdump[] = {
        {.flag = "Full",		.value = 0x0},
        {.flag = "Partial",		.value = 0x1},
        {.flag = "AuthDDR",		.value = 0x2},
        {.flag = "AuthOCM",		.value = 0x4},
        {.flag = "EnUsrKey",		.value = 0x8},
        {.flag = "EnDevKey",		.value = 0x10},
        {.flag = "AuthEnUsrKeyDDR",	.value = 0xA},
        {.flag = "AuthEnUsrKeyOCM",	.value = 0xC},
        {.flag = "AuthEnDevKeyDDR",	.value = 0x12},
        {.flag = "AuthEnDevKeyOCM",	.value = 0x14},
        {}
};

static int cmd_flags(int argc, const char *name)
{
	int valid_flag = 0;
	int flag = 0;
	struct fpgaflag *p = flagdump;

	while (p->flag) {
		if (!strcmp(name, p->flag)) {
			flag = p->value;
			break;
		}
		p++;
	}

	return flag;
}

static int isvalid_flags(int argc, const char *name, bool is_secure)
{
	int valid_flag = 0;
	int count = 0;
	struct fpgaflag *p;

	if (!is_secure)
		p = flagdump;
	else
		p = &flagdump[2];

	while (p->flag) {
		if (!strcmp(name, p->flag))
			return 0;
		else if ((!is_secure) && (++count == 2))
			return 1;
		p++;
	}

	return 1;
}

int main(int argc, char **argv)
{
	int ret;
	int iszynqmp = fpga_getplatform();
	char *binfile = NULL, *overlay = NULL, *AesKey = NULL, *flag = NULL, *partial_overlay = NULL;
	char *Module[100] = {0};
	int opt, flags = 0, flow = 0,rm_overlay = 0, readback_type = 0, sflags = 0;
	char command[2048], *token, *tmp, *tmp1, *tmp2 , *tmp3;
	const char *folder, *filename = "readback", *name;
	struct stat sb;
	double time;
        struct timeval t1, t0;

	if (argc == 1) {
		print_usage(basename(argv[0]));
		return 1;
	}

	while ((opt = getopt(argc, argv, "o:b:f:s:p:k:rt::Rh?:")) != -1) {
		switch (opt) {
		case 'o':
			overlay = optarg;
			flow = OVERLAY;
			break;
		case 'b':
			binfile = optarg;
			if (!(flow == OVERLAY))
				flow = FPGA_SYSFS;
			break;
		case 'f':
			if (flow == OVERLAY) {
				name = argv[6];
				flags = cmd_flags(argc, name);
			} else if (flow == FPGA_SYSFS) {
				name = argv[4];
				flags = cmd_flags(argc, name);
			}

			ret = isvalid_flags(argc, name, false);
			if (ret) {
				printf("Error: Invalid arugments :%s\n", strerror(1));
				print_usage(basename(argv[0]));
				return -EINVAL;
                        }

			flags += sflags;
			break;
		case 's':
			if (flow == OVERLAY) {
				name = argv[8];
				sflags = cmd_flags(argc, name);
			} else if (flow == FPGA_SYSFS) {
				name = argv[6];
				sflags = cmd_flags(argc, name);
			}

			ret = isvalid_flags(argc, name, true);
			if (ret) {
				printf("Error: Invalid arugments :%s\n", strerror(1));
				print_usage(basename(argv[0]));
				return -EINVAL;
			}

			flags += sflags;
			break;
		case 'p':
			partial_overlay = optarg;
			break;
		case 'k':
			AesKey = optarg;
			break;	 
		case 't':
			if (optarg == NULL && argv[4] != NULL)
				readback_type = atoi(argv[4]);
			break;
		case 'r':
			if (optarg == NULL && argv[2] != NULL)
				filename = argv[2];
			flow = READBACK;
			break;
		case 'R':
			rm_overlay = 1;
			break;
		case '?':
		case 'h':
		default:
			print_usage(basename(argv[0]));
			return 1;
			break;
		}
	}

	system("mkdir -p /lib/firmware");

	if (rm_overlay) {
		folder = "/configfs/device-tree/overlays/full";
		if (((stat(folder, &sb) == 0) && S_ISDIR(sb.st_mode))) {
			system("rmdir /configfs/device-tree/overlays/full");
		}
		return 0;
	}

	if (flow == OVERLAY) {

		folder = "/configfs/device-tree/overlays/full";
		if (((stat(folder, &sb) == 0) && S_ISDIR(sb.st_mode))) {
			printf("Error: Overlay already exists in the live tree\n\r");
			return 1;
		}

		if (argc < 5) {
			printf("\n\r");
			printf("%s: For more information run %s -h\n", strerror(22), basename(argv[0]));
			return 1;
		}
		
		folder = "/configfs/device-tree/";
		if (((stat(folder, &sb) == 0) && S_ISDIR(sb.st_mode))) {
		} else {
			system("mkdir /configfs");
			system("mount -t configfs configfs /configfs");
		}
		snprintf(command, sizeof(command), "cp %s /lib/firmware", binfile);
		system(command);
		snprintf(command, sizeof(command), "cp %s /lib/firmware", overlay);
		system(command);
		tmp = strdup(overlay);
		while((token = strsep(&tmp, "/"))) {
			tmp1 = token;
		}
		snprintf(command, sizeof(command), "echo %x > /sys/class/fpga_manager/fpga0/flags", flags);
		system(command);
		if (ENCRYPTION_USERKEY_EN & flags) {
			snprintf(command, sizeof(command), "echo %s > /sys/class/fpga_manager/fpga0/key", AesKey);
                	system(command);
		}

		if (!(flags & 1)) {
			system("cd /configfs/device-tree/overlays/ && mkdir full");
			snprintf(command, sizeof(command), "echo -n %s > /configfs/device-tree/overlays/full/path", tmp1);
			gettimeofday(&t0, NULL);
			system(command);
			if (partial_overlay) {
				snprintf(command, sizeof(command), "cp %s /lib/firmware", partial_overlay);
				system(command);
				tmp2 = strdup(partial_overlay);
				while((token = strsep(&tmp2, "/"))) {
					tmp3 = token;
				}
				system("cd /configfs/device-tree/overlays/ && mkdir full1");
				snprintf(command, sizeof(command), "echo -n %s > /configfs/device-tree/overlays/full1/path", tmp3);
				system(command);
			}
		} else {
			system("cd /configfs/device-tree/overlays/ && mkdir full1");
			snprintf(command, sizeof(command), "echo -n %s > /configfs/device-tree/overlays/full1/path", tmp1);
			gettimeofday(&t0, NULL);
			system(command);
		}
		gettimeofday(&t1, NULL);
		time = gettime(t0, t1);

		/* Delete Bin file and DTBO file*/
		snprintf(command, sizeof(command), "rm /lib/firmware/%s", tmp1);
		system(command);
		tmp = strdup(binfile);
		while((token = strsep(&tmp, "/"))) {
			tmp1 = token;
		}
		snprintf(command, sizeof(command), "rm /lib/firmware/%s", tmp1);
		system(command);
	} else if (flow == FPGA_SYSFS) {
		if (argc < 3) {
			printf("%s: For more information run %s -h\n", strerror(22), basename(argv[0]));
			return 1;
		}
		snprintf(command, sizeof(command), "cp %s /lib/firmware", binfile);
		system(command);
		snprintf(command, sizeof(command), "echo %x > /sys/class/fpga_manager/fpga0/flags", flags);
		system(command);
	        if (ENCRYPTION_USERKEY_EN & flags) {
                        snprintf(command, sizeof(command), "echo %s > /sys/class/fpga_manager/fpga0/key", AesKey);
                        system(command);
                }	
		tmp = strdup(binfile);
		while((token = strsep(&tmp, "/"))) {
			tmp1 = token;
		}
		snprintf(command, sizeof(command), "echo %s > /sys/class/fpga_manager/fpga0/firmware", tmp1);
		gettimeofday(&t0, NULL);
		system(command);
		gettimeofday(&t1, NULL);
		time = gettime(t0, t1);
		if (!fpga_state()) {
			printf("Time taken to load BIN is %f Milli Seconds\n\r", time);
			printf("BIN FILE loaded through FPGA manager successfully\n\r");
		} else {
			printf("BIN FILE loading through FPGA manager failed\n\r");
		}
		snprintf(command, sizeof(command), "rm /lib/firmware/%s", tmp1);
		system(command);
	} else if (flow == READBACK) {
		if (readback_type > 1) {
			printf("Invalid arugments :%s\n", strerror(1));
			printf("For more information run %s -h\n", basename(argv[0]));
			return -EINVAL;	
		}
		snprintf(command, sizeof(command), "echo %x > /sys/module/zynqmp_fpga/parameters/readback_type", readback_type);
		system(command);
		snprintf(command, sizeof(command), "cat /sys/kernel/debug/fpga/fpga0/image >> %s.bin", filename);
		system(command);
		printf("Readback contents are stored in the file %s.bin\n\r", filename);
	}

	return 0;
}
