Add support for handling capabilities requests

Change-Id: Id5aa197312c88b0c448dc085d8477ed67da24724
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);