text: /*
   * A GEM style device manager for PCIe based OpenCL accelerators.
   *
   * Copyright (C) 2018 Xilinx, Inc. All rights reserved.
   *
   * Authors: Chien-Wei Lan <chienwei@xilinx.com>
   *
   * This software is licensed under the terms of the GNU General Public
   * License version 2, as published by the Free Software Foundation, and
   * may be copied, distributed, and modified under those terms.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   */
  
  #include <linux/hwmon.h>
  #include <linux/hwmon-sysfs.h>
  #include <linux/vmalloc.h>
  #include "../xocl_drv.h"
  #include "mgmt-ioctl.h"
  
  /* Registers are defined in pg150-ultrascale-memory-ip.pdf:
   * AXI4-Lite Slave Control/Status Register Map
   */
  #define XLNX_DNA_MEMORY_MAP_MAGIC_IS_DEFINED                        (0x3E4D7732)
  #define XLNX_DNA_MAJOR_MINOR_VERSION_REGISTER_OFFSET                0x00          //  RO
  #define XLNX_DNA_REVISION_REGISTER_OFFSET                           0x04          //  RO
  #define XLNX_DNA_CAPABILITY_REGISTER_OFFSET                         0x08          //  RO
  //#define XLNX_DNA_SCRATCHPAD_REGISTER_OFFSET                         (0x0C)          //  RO (31-1) + RW (0)
  #define XLNX_DNA_STATUS_REGISTER_OFFSET                             0x10            //  RO
  #define XLNX_DNA_FSM_DNA_WORD_WRITE_COUNT_REGISTER_OFFSET           (0x14)          //  RO
  #define XLNX_DNA_FSM_CERTIFICATE_WORD_WRITE_COUNT_REGISTER_OFFSET   (0x18)          //  RO
  #define XLNX_DNA_TIMEOUT_REGISTER_OFFSET                            (0x1C)          //  RO
  #define XLNX_DNA_MESSAGE_START_AXI_ONLY_REGISTER_OFFSET             (0x20)          //  RO (31-1) + RW (0)
  #define XLNX_DNA_READBACK_REGISTER_2_OFFSET                         0x40            //  RO XLNX_DNA_BOARD_DNA_95_64
  #define XLNX_DNA_READBACK_REGISTER_1_OFFSET                         0x44            //  RO XLNX_DNA_BOARD_DNA_63_32
  #define XLNX_DNA_READBACK_REGISTER_0_OFFSET                         0x48            //  RO XLNX_DNA_BOARD_DNA_31_0
  #define XLNX_DNA_DATA_AXI_ONLY_REGISTER_OFFSET                      (0x80)          //  WO
  #define XLNX_DNA_CERTIFICATE_DATA_AXI_ONLY_REGISTER_OFFSET          (0xC0)          //  WO - 512 bit aligned.
  #define XLNX_DNA_MAX_ADDRESS_WORDS                                  (0xC4)
  
  #define DEV2XDEV(d) xocl_get_xdev(to_platform_device(d))
  
  #define XLNX_DNA_INVALID_CAPABILITY_MASK                            0xFFFFFFEE
  #define XLNX_DNA_PRIVILEGED(xlnx_dna)				    ((xlnx_dna)->base != NULL)
  
  
  #define XLNX_DNA_DEFAULT_EXPIRE_SECS 	1
  
  #define XLNX_DNA_MAX_RES		1
  
  struct xocl_xlnx_dna {
  	void __iomem		*base;
  	struct device		*xlnx_dna_dev;
  	struct mutex		xlnx_dna_lock;
  	u64			cache_expire_secs;
  	struct xcl_dna		cache;
  	ktime_t			cache_expires;
  };
  
  
  enum dna_prop {
  	DNA_RAW,
  	STATUS,
  	CAP,
  	VER,
  	REVERSION,
  };
  
  static void set_xlnx_dna_data(struct xocl_xlnx_dna *xlnx_dna, struct xcl_dna *dna_status)
  {
  	memcpy(&xlnx_dna->cache, dna_status, sizeof(struct xcl_dna));
  	xlnx_dna->cache_expires = ktime_add(ktime_get_boottime(),
  		ktime_set(xlnx_dna->cache_expire_secs, 0));
  }
  
  static void xlnx_dna_read_from_peer(struct platform_device *pdev)
  {
  	struct xocl_xlnx_dna *xlnx_dna = platform_get_drvdata(pdev);
  	struct mailbox_subdev_peer subdev_peer = {0};
  	struct xcl_dna dna_status = {0};
  	size_t resp_len = sizeof(struct xcl_dna);
  	size_t data_len = sizeof(struct mailbox_subdev_peer);
  	struct mailbox_req *mb_req = NULL;
  	size_t reqlen = sizeof(struct mailbox_req) + data_len;
  	xdev_handle_t xdev = xocl_get_xdev(pdev);
  
  	mb_req = vmalloc(reqlen);
  	if (!mb_req)
  		return;
  
  	mb_req->req = MAILBOX_REQ_PEER_DATA;
  	subdev_peer.size = resp_len;
  	subdev_peer.kind = DNA;
  	subdev_peer.entries = 1;
  
  	memcpy(mb_req->data, &subdev_peer, data_len);
  
  	(void) xocl_peer_request(xdev,
  		mb_req, reqlen, &dna_status, &resp_len, NULL, NULL);
  	set_xlnx_dna_data(xlnx_dna, &dna_status);
  
  	vfree(mb_req);
  }
  
  static void get_xlnx_dna_data(struct platform_device *pdev)
  {
  	struct xocl_xlnx_dna *xlnx_dna = platform_get_drvdata(pdev);
  	ktime_t now = ktime_get_boottime();
  
  	if (ktime_compare(now, xlnx_dna->cache_expires) > 0)
  		xlnx_dna_read_from_peer(pdev);
  }
  
  static void xlnx_dna_get_prop(struct device *dev, enum dna_prop prop, void *val)
  {
  	struct xocl_xlnx_dna *xlnx_dna = dev_get_drvdata(dev);
  
  	BUG_ON(!xlnx_dna);
  
  	if (XLNX_DNA_PRIVILEGED(xlnx_dna)) {
  		switch (prop) {
  		case DNA_RAW:
  			*((u32 *)val+2) = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_READBACK_REGISTER_2_OFFSET);
  			*((u32 *)val+1) = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_READBACK_REGISTER_1_OFFSET);
  			*(u32 *)val     = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_READBACK_REGISTER_0_OFFSET);
  			break;
  		case STATUS:
  			*(u32 *)val = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_STATUS_REGISTER_OFFSET);
  			break;
  		case CAP:
  			*(u32 *)val = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_CAPABILITY_REGISTER_OFFSET);
  			break;
  		case VER:
  			*(u32 *)val = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_MAJOR_MINOR_VERSION_REGISTER_OFFSET);
  			break;
  		case REVERSION:
  			*(u32 *)val = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_REVISION_REGISTER_OFFSET);
  			break;
  		default:
  			break;
  		}
  	} else {
  
  		get_xlnx_dna_data(to_platform_device(dev));
  
  		switch (prop) {
  		case DNA_RAW:
  			memcpy(val, xlnx_dna->cache.dna, sizeof(u32)*4);
  			break;
  		case STATUS:
  			*(u32 *)val = xlnx_dna->cache.status;
  			break;
  		case CAP:
  			*(u32 *)val = xlnx_dna->cache.capability;
  			break;
  		case VER:
  			*(u32 *)val = xlnx_dna->cache.dna_version;
  			break;
  		case REVERSION:
  			*(u32 *)val = xlnx_dna->cache.revision;
  			break;
  		default:
  			break;
  		}
  	}
  }
  
  static ssize_t status_show(struct device *dev, struct device_attribute *attr,
  	char *buf)
  {
  	u32 status;
  
  	xlnx_dna_get_prop(dev, STATUS, &status);
  	return sprintf(buf, "0x%x\n", status);
  }
  static DEVICE_ATTR_RO(status);
  
  
  static ssize_t dna_show(struct device *dev, struct device_attribute *attr,
  	char *buf)
  {
  	uint32_t dna[4];
  
  	xlnx_dna_get_prop(dev, DNA_RAW, dna);
  	return sprintf(buf, "%08x%08x%08x\n", dna[2], dna[1], dna[0]);
  }
  static DEVICE_ATTR_RO(dna);
  
  
  static ssize_t capability_show(struct device *dev, struct device_attribute *attr,
  	char *buf)
  {
  	u32 capability;
  
  	xlnx_dna_get_prop(dev, CAP, &capability);
  	return sprintf(buf, "0x%x\n", capability);
  }
  static DEVICE_ATTR_RO(capability);
  
  
  static ssize_t dna_version_show(struct device *dev, struct device_attribute *attr,
  	char *buf)
  {
  	u32 version;
  
  	xlnx_dna_get_prop(dev, VER, &version);
  
  	return sprintf(buf, "%d.%d\n", version>>16, version&0xffff);
  }
  static DEVICE_ATTR_RO(dna_version);
  
  static ssize_t revision_show(struct device *dev, struct device_attribute *attr,
  	char *buf)
  {
  
  	u32 revision;
  
  	xlnx_dna_get_prop(dev, REVERSION, &revision);
  	return sprintf(buf, "%d\n", revision);
  }
  static DEVICE_ATTR_RO(revision);
  
  static struct attribute *xlnx_dna_attributes[] = {
  	&dev_attr_status.attr,
  	&dev_attr_dna.attr,
  	&dev_attr_capability.attr,
  	&dev_attr_dna_version.attr,
  	&dev_attr_revision.attr,
  	NULL
  };
  
  static const struct attribute_group xlnx_dna_attrgroup = {
  	.attrs = xlnx_dna_attributes,
  };
  
  static uint32_t dna_status(struct platform_device *pdev)
  {
  	struct xocl_xlnx_dna *xlnx_dna = platform_get_drvdata(pdev);
  	struct device *dev  = &pdev->dev;
  	uint32_t status = 0;
  	uint8_t retries = 10;
  	bool rsa4096done = false;
  
  	if (!xlnx_dna)
  		return status;
  
  	while (!rsa4096done && retries) {
  		xlnx_dna_get_prop(dev, STATUS, &status);
  		if (status>>8 & 0x1) {
  			rsa4096done = true;
  			break;
  		}
  		msleep(1);
  		retries--;
  	}
  
  	if (retries == 0)
  		return -EBUSY;
  
  	xlnx_dna_get_prop(dev, STATUS, &status);
  
  	return status;
  }
  
  static uint32_t dna_capability(struct platform_device *pdev)
  {
  	struct xocl_xlnx_dna *xlnx_dna = platform_get_drvdata(pdev);
  	struct device *dev  = &pdev->dev;
  	u32 capability = 0;
  
  	if (!xlnx_dna)
  		return capability;
  
  	xlnx_dna_get_prop(dev, CAP, &capability);
  
  	return capability;
  }
  
  static void dna_write_cert(struct platform_device *pdev, const uint32_t *cert, uint32_t len)
  {
  	struct xocl_xlnx_dna *xlnx_dna = platform_get_drvdata(pdev);
  	struct device *dev  = &pdev->dev;
  	int i, j, k;
  	u32 status = 0, words;
  	uint8_t retries = 100;
  	bool sha256done = false;
  	uint32_t convert;
  	uint32_t sign_start, message_words = (len-512)>>2;
  
  	sign_start = message_words;
  	if (!xlnx_dna)
  		return;
  
  	if (!XLNX_DNA_PRIVILEGED(xlnx_dna))
  		return;
  
  	xocl_dr_reg_write32(DEV2XDEV(dev), 0x1, xlnx_dna->base+XLNX_DNA_MESSAGE_START_AXI_ONLY_REGISTER_OFFSET);
  	status = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_STATUS_REGISTER_OFFSET);
  	xocl_info(&pdev->dev, "Start: status %08x", status);
  
  	for (i = 0; i < message_words; i += 16) {
  
  		retries = 100;
  		sha256done = false;
  
  		while (!sha256done && retries) {
  			status = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_STATUS_REGISTER_OFFSET);
  			if (!(status>>4 & 0x1)) {
  				sha256done = true;
  				break;
  			}
  			msleep(10);
  			retries--;
  		}
  		for (j = 0; j < 16; ++j) {
  			convert = (*(cert+i+j)>>24 & 0xff) | (*(cert+i+j)>>8 & 0xff00) | (*(cert+i+j)<<8 & 0xff0000) | ((*(cert+i+j) & 0xff)<<24);
  			xocl_dr_reg_write32(DEV2XDEV(dev), convert, xlnx_dna->base+XLNX_DNA_DATA_AXI_ONLY_REGISTER_OFFSET+j*4);
  		}
  	}
  	retries = 100;
  	sha256done = false;
  	while (!sha256done && retries) {
  		status = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_STATUS_REGISTER_OFFSET);
  		if (!(status>>4 & 0x1)) {
  			sha256done = true;
  			break;
  		}
  		msleep(10);
  		retries--;
  	}
  
  	status = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_STATUS_REGISTER_OFFSET);
  	words  = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_FSM_DNA_WORD_WRITE_COUNT_REGISTER_OFFSET);
  	xocl_info(&pdev->dev, "Message: status %08x dna words %d", status, words);
  
  	for (k = 0; k < 128; k += 16) {
  		for (i = 0; i < 16; i++) {
  			j = k+i+sign_start;
  			convert = (*(cert+j)>>24 & 0xff) | (*(cert+j)>>8 & 0xff00) | (*(cert+j)<<8 & 0xff0000) | ((*(cert+j) & 0xff)<<24);
  			xocl_dr_reg_write32(DEV2XDEV(dev), convert, xlnx_dna->base+XLNX_DNA_CERTIFICATE_DATA_AXI_ONLY_REGISTER_OFFSET+i*4);
  		}
  	}
  
  	status = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_STATUS_REGISTER_OFFSET);
  	words  = xocl_dr_reg_read32(DEV2XDEV(dev), xlnx_dna->base+XLNX_DNA_FSM_CERTIFICATE_WORD_WRITE_COUNT_REGISTER_OFFSET);
  	xocl_info(&pdev->dev, "Signature: status %08x certificate words %d", status, words);
  }
  
  static void dna_get_data(struct platform_device *pdev, void *buf)
  {
  	struct xocl_xlnx_dna *xlnx_dna = platform_get_drvdata(pdev);
  	struct xcl_dna dna_status = {0};
  
  	if (!XLNX_DNA_PRIVILEGED(xlnx_dna))
  		return;
  
  	xlnx_dna_get_prop(&pdev->dev, STATUS, &dna_status.status);
  	xlnx_dna_get_prop(&pdev->dev, CAP, &dna_status.capability);
  	xlnx_dna_get_prop(&pdev->dev, DNA_RAW, dna_status.dna);
  	xlnx_dna_get_prop(&pdev->dev, VER, &dna_status.dna_version);
  	xlnx_dna_get_prop(&pdev->dev, REVERSION, &dna_status.revision);
  
  	memcpy(buf, &dna_status, sizeof(struct xcl_dna));
  }
  
  static struct xocl_dna_funcs dna_ops = {
  	.status = dna_status,
  	.capability = dna_capability,
  	.write_cert = dna_write_cert,
  	.get_data = dna_get_data,
  };
  
  
  static void mgmt_sysfs_destroy_xlnx_dna(struct platform_device *pdev)
  {
  	struct xocl_xlnx_dna *xlnx_dna;
  
  	xlnx_dna = platform_get_drvdata(pdev);
  
  	sysfs_remove_group(&pdev->dev.kobj, &xlnx_dna_attrgroup);
  
  }
  
  static int mgmt_sysfs_create_xlnx_dna(struct platform_device *pdev)
  {
  	struct xocl_xlnx_dna *xlnx_dna;
  	struct xocl_dev_core *core;
  	int err;
  
  	xlnx_dna = platform_get_drvdata(pdev);
  	core = XDEV(xocl_get_xdev(pdev));
  
  	err = sysfs_create_group(&pdev->dev.kobj, &xlnx_dna_attrgroup);
  	if (err) {
  		xocl_err(&pdev->dev, "create pw group failed: 0x%x", err);
  		goto create_grp_failed;
  	}
  
  	return 0;
  
  create_grp_failed:
  	return err;
  }
  
  static int xlnx_dna_probe(struct platform_device *pdev)
  {
  	struct xocl_xlnx_dna *xlnx_dna;
  	struct resource *res;
  	int i, err;
  	uint32_t capability;
  
  	xlnx_dna = devm_kzalloc(&pdev->dev, sizeof(*xlnx_dna), GFP_KERNEL);
  	if (!xlnx_dna)
  		return -ENOMEM;
  
  
  	for (i = 0; i < XLNX_DNA_MAX_RES; ++i) {
  		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  		if (!res)
  			break;
  
  		xocl_info(&pdev->dev, "IO start: 0x%llx, end: 0x%llx",
  			res->start, res->end);
  
  		xlnx_dna->base = ioremap_nocache(res->start, res->end - res->start + 1);
  		if (!xlnx_dna->base) {
  			err = -EIO;
  			xocl_err(&pdev->dev, "Map iomem failed");
  			goto failed;
  		}
  	}
  	platform_set_drvdata(pdev, xlnx_dna);
  
  	capability = dna_capability(pdev);
  	if (capability & XLNX_DNA_INVALID_CAPABILITY_MASK) {
  		xocl_err(&pdev->dev, "DNA IP not detected");
  		err = -EINVAL;
  		goto create_xlnx_dna_failed;
  	}
  
  	err = mgmt_sysfs_create_xlnx_dna(pdev);
  	if (err)
  		goto create_xlnx_dna_failed;
  
  	xlnx_dna->cache_expire_secs = XLNX_DNA_DEFAULT_EXPIRE_SECS;
  
  	return 0;
  
  create_xlnx_dna_failed:
  	platform_set_drvdata(pdev, NULL);
  failed:
  	return err;
  }
  
  
  static int xlnx_dna_remove(struct platform_device *pdev)
  {
  	struct xocl_xlnx_dna	*xlnx_dna;
  
  	xlnx_dna = platform_get_drvdata(pdev);
  	if (!xlnx_dna) {
  		xocl_err(&pdev->dev, "driver data is NULL");
  		return -EINVAL;
  	}
  
  	mgmt_sysfs_destroy_xlnx_dna(pdev);
  
  	if (xlnx_dna->base)
  		iounmap(xlnx_dna->base);
  
  	platform_set_drvdata(pdev, NULL);
  	devm_kfree(&pdev->dev, xlnx_dna);
  
  	return 0;
  }
  
  struct xocl_drv_private dna_priv = {
  	.ops = &dna_ops,
  };
  
  struct platform_device_id xlnx_dna_id_table[] = {
  	{ XOCL_DEVNAME(XOCL_DNA), (kernel_ulong_t)&dna_priv },
  	{ },
  };
  
  static struct platform_driver	xlnx_dna_driver = {
  	.probe		= xlnx_dna_probe,
  	.remove		= xlnx_dna_remove,
  	.driver		= {
  		.name = XOCL_DEVNAME(XOCL_DNA),
  	},
  	.id_table = xlnx_dna_id_table,
  };
  
  int __init xocl_init_dna(void)
  {
  	return platform_driver_register(&xlnx_dna_driver);
  }
  
  void xocl_fini_dna(void)
  {
  	platform_driver_unregister(&xlnx_dna_driver);
  }
  