Add support for handling capabilities requests

Change-Id: Id5aa197312c88b0c448dc085d8477ed67da24724
diff --git a/kernel/ethosu_core_interface.h b/kernel/ethosu_core_interface.h
index 4e7a864..ad8311c 100644
--- a/kernel/ethosu_core_interface.h
+++ b/kernel/ethosu_core_interface.h
@@ -55,6 +55,8 @@
 	ETHOSU_CORE_MSG_INFERENCE_RSP,
 	ETHOSU_CORE_MSG_VERSION_REQ,
 	ETHOSU_CORE_MSG_VERSION_RSP,
+	ETHOSU_CORE_MSG_CAPABILITIES_REQ,
+	ETHOSU_CORE_MSG_CAPABILITIES_RSP,
 	ETHOSU_CORE_MSG_MAX
 };
 
@@ -129,6 +131,34 @@
 };
 
 /**
+ * struct ethosu_core_capabilities_req - Message capabilities request
+ */
+struct ethosu_core_capabilities_req {
+	uint64_t user_arg;
+};
+
+/**
+ * struct ethosu_core_capabilities_rsp - Message capabilities response
+ */
+struct ethosu_core_msg_capabilities_rsp {
+	uint64_t user_arg;
+	uint32_t version_status;
+	uint32_t version_minor;
+	uint32_t version_major;
+	uint32_t product_major;
+	uint32_t arch_patch_rev;
+	uint32_t arch_minor_rev;
+	uint32_t arch_major_rev;
+	uint32_t driver_patch_rev;
+	uint32_t driver_minor_rev;
+	uint32_t driver_major_rev;
+	uint32_t macs_per_cc;
+	uint32_t cmd_stream_version;
+	uint32_t shram_size;
+	uint32_t custom_dma;
+};
+
+/**
  * enum ethosu_core_msg_err_type - Error types
  */
 enum ethosu_core_msg_err_type {
diff --git a/kernel/ethosu_device.c b/kernel/ethosu_device.c
index e82b4c0..7adc7a6 100644
--- a/kernel/ethosu_device.c
+++ b/kernel/ethosu_device.c
@@ -38,6 +38,7 @@
 #include <linux/io.h>
 #include <linux/of_reserved_mem.h>
 #include <linux/uaccess.h>
+#include <linux/slab.h>
 
 /****************************************************************************
  * Defines
@@ -47,6 +48,8 @@
 #define MINOR_COUNT    1 /* Allocate 1 minor version */
 #define DMA_ADDR_BITS 32 /* Number of address bits */
 
+#define CAPABILITIES_RESP_TIMEOUT_MS 50
+
 /****************************************************************************
  * Types
  ****************************************************************************/
@@ -55,6 +58,71 @@
  * Functions
  ****************************************************************************/
 
+static void ethosu_capabilities_destroy(struct kref *kref)
+{
+	struct ethosu_capabilities *cap =
+		container_of(kref, struct ethosu_capabilities, refcount);
+
+	list_del(&cap->list);
+
+	devm_kfree(cap->edev->dev, cap);
+}
+
+static int ethosu_capabilities_find(struct ethosu_capabilities *cap,
+				    struct list_head *capabilties_list)
+{
+	struct ethosu_capabilities *cur;
+
+	list_for_each_entry(cur, capabilties_list, list) {
+		if (cur == cap)
+			return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int ethosu_capability_rsp(struct ethosu_device *edev,
+				 struct ethosu_core_msg_capabilities_rsp *msg)
+{
+	struct ethosu_capabilities *cap;
+	struct ethosu_uapi_device_capabilities *capabilities;
+	int ret;
+
+	cap = (struct ethosu_capabilities *)msg->user_arg;
+	ret = ethosu_capabilities_find(cap, &edev->capabilities_list);
+	if (0 != ret) {
+		dev_warn(edev->dev,
+			 "Handle not found in capabilities list. handle=0x%p\n",
+			 cap);
+
+		/* NOTE: do not call complete or kref_put on invalid data! */
+		return ret;
+	}
+
+	capabilities = cap->capabilities;
+
+	capabilities->hw_id.version_status = msg->version_status;
+	capabilities->hw_id.version_minor = msg->version_minor;
+	capabilities->hw_id.version_major = msg->version_major;
+	capabilities->hw_id.product_major = msg->product_major;
+	capabilities->hw_id.arch_patch_rev = msg->arch_patch_rev;
+	capabilities->hw_id.arch_minor_rev = msg->arch_minor_rev;
+	capabilities->hw_id.arch_major_rev = msg->arch_major_rev;
+	capabilities->driver_patch_rev = msg->driver_patch_rev;
+	capabilities->driver_minor_rev = msg->driver_minor_rev;
+	capabilities->driver_major_rev = msg->driver_major_rev;
+	capabilities->hw_cfg.macs_per_cc = msg->macs_per_cc;
+	capabilities->hw_cfg.cmd_stream_version = msg->cmd_stream_version;
+	capabilities->hw_cfg.shram_size = msg->shram_size;
+	capabilities->hw_cfg.custom_dma = msg->custom_dma;
+
+	complete(&cap->done);
+
+	kref_put(&cap->refcount, ethosu_capabilities_destroy);
+
+	return 0;
+}
+
 /* Incoming messages */
 static int ethosu_handle_msg(struct ethosu_device *edev)
 {
@@ -62,9 +130,10 @@
 	struct ethosu_core_msg header;
 
 	union {
-		struct ethosu_core_msg_err       error;
-		struct ethosu_core_inference_rsp inf;
-		struct ethosu_core_msg_version   version;
+		struct ethosu_core_msg_err              error;
+		struct ethosu_core_inference_rsp        inf;
+		struct ethosu_core_msg_version          version;
+		struct ethosu_core_msg_capabilities_rsp capabilities;
 	} data;
 
 	/* Read message */
@@ -133,7 +202,35 @@
 		}
 
 		break;
+	case ETHOSU_CORE_MSG_CAPABILITIES_RSP:
+		if (header.length != sizeof(data.capabilities)) {
+			dev_warn(edev->dev,
+				 "Msg: Capabilities response of incorrect size. size=%u, expected=%zu\n", header.length,
+				 sizeof(data.capabilities));
+			ret = -EBADMSG;
+			break;
+		}
 
+		dev_info(edev->dev,
+			 "Msg: Capabilities response ua%llx vs%hhu v%hhu.%hhu p%hhu av%hhu.%hhu.%hhu dv%hhu.%hhu.%hhu mcc%hhu csv%hhu ss%hhu cd%hhu\n",
+			 data.capabilities.user_arg,
+			 data.capabilities.version_status,
+			 data.capabilities.version_major,
+			 data.capabilities.version_minor,
+			 data.capabilities.product_major,
+			 data.capabilities.arch_major_rev,
+			 data.capabilities.arch_minor_rev,
+			 data.capabilities.arch_patch_rev,
+			 data.capabilities.driver_major_rev,
+			 data.capabilities.driver_minor_rev,
+			 data.capabilities.driver_patch_rev,
+			 data.capabilities.macs_per_cc,
+			 data.capabilities.cmd_stream_version,
+			 data.capabilities.shram_size,
+			 data.capabilities.custom_dma);
+
+		ret = ethosu_capability_rsp(edev, &data.capabilities);
+		break;
 	default:
 		/* This should not happen due to version checks */
 		dev_warn(edev->dev, "Msg: Protocol error\n");
@@ -157,6 +254,64 @@
 	return nonseekable_open(inode, file);
 }
 
+static int ethosu_send_capabilities_request(struct ethosu_device *edev,
+					    void __user *udata)
+{
+	struct ethosu_uapi_device_capabilities uapi;
+	struct ethosu_capabilities *cap;
+	int ret;
+	int timeout;
+
+	cap = devm_kzalloc(edev->dev, sizeof(struct ethosu_capabilities),
+			   GFP_KERNEL);
+	if (!cap)
+		return -ENOMEM;
+
+	cap->edev = edev;
+	cap->capabilities = &uapi;
+	kref_init(&cap->refcount);
+	init_completion(&cap->done);
+	list_add(&cap->list, &edev->capabilities_list);
+
+	ret = ethosu_mailbox_capabilities_request(&edev->mailbox, cap);
+	if (0 != ret)
+		goto put_kref;
+
+	/*
+	 * Increase ref counter since we sent the pointer out to
+	 * response handler thread. That thread is responsible to
+	 * decrease the ref counter before exiting. So the memory
+	 * can be freed.
+	 *
+	 * NOTE: if no response is received back, the memory is leaked.
+	 */
+	kref_get(&cap->refcount);
+	/* Unlock the mutex before going to block on the condition */
+	mutex_unlock(&edev->mutex);
+	/* wait for response to arrive back */
+	timeout = wait_for_completion_timeout(&cap->done,
+					      msecs_to_jiffies(
+						      CAPABILITIES_RESP_TIMEOUT_MS));
+	/* take back the mutex before resuming to do anything */
+	ret = mutex_lock_interruptible(&edev->mutex);
+	if (0 != ret)
+		goto put_kref;
+
+	if (0 == timeout /* timed out*/) {
+		dev_warn(edev->dev,
+			 "Msg: Capabilities response lost - timeout\n");
+		ret = -EIO;
+		goto put_kref;
+	}
+
+	ret = copy_to_user(udata, &uapi, sizeof(uapi)) ? -EFAULT : 0;
+
+put_kref:
+	kref_put(&cap->refcount, ethosu_capabilities_destroy);
+
+	return ret;
+}
+
 static long ethosu_ioctl(struct file *file,
 			 unsigned int cmd,
 			 unsigned long arg)
@@ -176,6 +331,10 @@
 		dev_info(edev->dev, "Ioctl: Send version request\n");
 		ret = ethosu_mailbox_version_request(&edev->mailbox);
 		break;
+	case ETHOSU_IOCTL_CAPABILITIES_REQ:
+		dev_info(edev->dev, "Ioctl: Send capabilities request\n");
+		ret = ethosu_send_capabilities_request(edev, udata);
+		break;
 	case ETHOSU_IOCTL_PING: {
 		dev_info(edev->dev, "Ioctl: Send ping\n");
 		ret = ethosu_mailbox_ping(&edev->mailbox);
@@ -257,6 +416,7 @@
 	edev->dev = dev;
 	edev->class = class;
 	mutex_init(&edev->mutex);
+	INIT_LIST_HEAD(&edev->capabilities_list);
 	INIT_LIST_HEAD(&edev->inference_list);
 
 	ret = of_reserved_mem_device_init(edev->dev);
diff --git a/kernel/ethosu_device.h b/kernel/ethosu_device.h
index 4e4f59d..0722814 100644
--- a/kernel/ethosu_device.h
+++ b/kernel/ethosu_device.h
@@ -25,6 +25,7 @@
  * Includes
  ****************************************************************************/
 
+#include "uapi/ethosu.h"
 #include "ethosu_mailbox.h"
 
 #include <linux/device.h>
@@ -32,6 +33,7 @@
 #include <linux/io.h>
 #include <linux/mutex.h>
 #include <linux/workqueue.h>
+#include <linux/completion.h>
 
 /****************************************************************************
  * Types
@@ -47,9 +49,21 @@
 	dev_t                 devt;
 	struct mutex          mutex;
 	struct ethosu_mailbox mailbox;
+	struct list_head      capabilities_list;
 	struct list_head      inference_list;
 };
 
+/**
+ * struct ethosu_capabilities - Capabilities internal struct
+ */
+struct ethosu_capabilities {
+	struct ethosu_device                   *edev;
+	struct completion                      done;
+	struct kref                            refcount;
+	struct ethosu_uapi_device_capabilities *capabilities;
+	struct list_head                       list;
+};
+
 /****************************************************************************
  * Functions
  ****************************************************************************/
diff --git a/kernel/ethosu_mailbox.c b/kernel/ethosu_mailbox.c
index f61c181..158a7f5 100644
--- a/kernel/ethosu_mailbox.c
+++ b/kernel/ethosu_mailbox.c
@@ -213,6 +213,18 @@
 				      0);
 }
 
+int ethosu_mailbox_capabilities_request(struct ethosu_mailbox *mbox,
+					void *user_arg)
+{
+	struct ethosu_core_capabilities_req req = {
+		.user_arg = (ptrdiff_t)user_arg
+	};
+
+	return ethosu_queue_write_msg(mbox, ETHOSU_CORE_MSG_CAPABILITIES_REQ,
+				      &req,
+				      sizeof(req));
+}
+
 int ethosu_mailbox_inference(struct ethosu_mailbox *mbox,
 			     void *user_arg,
 			     uint32_t ifm_count,
diff --git a/kernel/ethosu_mailbox.h b/kernel/ethosu_mailbox.h
index 0bc5ffb..5cd5e62 100644
--- a/kernel/ethosu_mailbox.h
+++ b/kernel/ethosu_mailbox.h
@@ -114,6 +114,14 @@
 int ethosu_mailbox_version_request(struct ethosu_mailbox *mbox);
 
 /**
+ * ethosu_mailbox_capabilities_request() - Send capabilities request
+ *
+ * Return: 0 on success, else error code.
+ */
+int ethosu_mailbox_capabilities_request(struct ethosu_mailbox *mbox,
+					void *user_arg);
+
+/**
  * ethosu_mailbox_inference() - Send inference
  *
  * Return: 0 on success, else error code.
diff --git a/kernel/uapi/ethosu.h b/kernel/uapi/ethosu.h
index 1e53c91..3e4e7a3 100644
--- a/kernel/uapi/ethosu.h
+++ b/kernel/uapi/ethosu.h
@@ -44,6 +44,7 @@
 
 #define ETHOSU_IOCTL_PING               ETHOSU_IO(0x00)
 #define ETHOSU_IOCTL_VERSION_REQ        ETHOSU_IO(0x01)
+#define ETHOSU_IOCTL_CAPABILITIES_REQ   ETHOSU_IO(0x02)
 #define ETHOSU_IOCTL_BUFFER_CREATE      ETHOSU_IOR(0x10, \
 						   struct ethosu_uapi_buffer_create)
 #define ETHOSU_IOCTL_BUFFER_SET         ETHOSU_IOR(0x11, \
@@ -125,6 +126,56 @@
 };
 
 /**
+ * struct ethosu_uapi_device_hw_id - Device hardware identification
+ * @version_status:            Version status
+ * @version_minor:             Version minor
+ * @version_major:             Version major
+ * @product_major:             Product major
+ * @arch_patch_rev:            Architecture version patch
+ * @arch_minor_rev:            Architecture version minor
+ * @arch_major_rev:            Architecture version major
+ */
+struct ethosu_uapi_device_hw_id {
+	__u32 version_status;
+	__u32 version_minor;
+	__u32 version_major;
+	__u32 product_major;
+	__u32 arch_patch_rev;
+	__u32 arch_minor_rev;
+	__u32 arch_major_rev;
+};
+
+/**
+ * struct ethosu_uapi_device_hw_cfg - Device hardware configuration
+ * @macs_per_cc:               MACs per clock cycle
+ * @cmd_stream_version:        NPU command stream version
+ * @shram_size:                SHRAM size
+ * @custom_dma:                Custom DMA enabled
+ */
+struct ethosu_uapi_device_hw_cfg {
+	__u32 macs_per_cc;
+	__u32 cmd_stream_version;
+	__u32 shram_size;
+	__u32 custom_dma;
+};
+
+/**
+ * struct ethosu_uapi_capabilities - Device capabilities
+ * @hw_id:                     Hardware identification
+ * @hw_cfg:                    Hardware configuration
+ * @driver_patch_rev:          Driver version patch
+ * @driver_minor_rev:          Driver version minor
+ * @driver_major_rev:          Driver version major
+ */
+struct ethosu_uapi_device_capabilities {
+	struct ethosu_uapi_device_hw_id  hw_id;
+	struct ethosu_uapi_device_hw_cfg hw_cfg;
+	__u32                            driver_patch_rev;
+	__u32                            driver_minor_rev;
+	__u32                            driver_major_rev;
+};
+
+/**
  * struct ethosu_uapi_inference_create - Create network request
  * @ifm_count:		Number of IFM file descriptors
  * @ifm_fd:		IFM buffer file descriptors