Add support for handling capabilities requests

Change-Id: Id5aa197312c88b0c448dc085d8477ed67da24724
diff --git a/driver_library/include/ethosu.hpp b/driver_library/include/ethosu.hpp
index 70d0701..d3f7421 100644
--- a/driver_library/include/ethosu.hpp
+++ b/driver_library/include/ethosu.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020 Arm Limited. All rights reserved.
+ * Copyright (c) 2020-2021 Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: Apache-2.0
  *
@@ -19,10 +19,25 @@
 #pragma once
 
 #include <algorithm>
+#include <iostream>
 #include <memory>
 #include <string>
 #include <vector>
 
+/*
+ *The following undef are necessary to avoid clash with macros in GNU C Library
+ * if removed the following warning/error are produced:
+ *
+ *  In the GNU C Library, "major" ("minor") is defined
+ *  by <sys/sysmacros.h>. For historical compatibility, it is
+ *  currently defined by <sys/types.h> as well, but we plan to
+ *  remove this soon. To use "major" ("minor"), include <sys/sysmacros.h>
+ *  directly. If you did not intend to use a system-defined macro
+ *  "major" ("minor"), you should undefine it after including <sys/types.h>.
+ */
+#undef major
+#undef minor
+
 namespace EthosU {
 
 class Exception : public std::exception {
@@ -35,12 +50,95 @@
     std::string msg;
 };
 
+/**
+ * Sematic Version : major.minor.patch
+ */
+class SemanticVersion {
+public:
+    SemanticVersion(uint32_t _major = 0, uint32_t _minor = 0, uint32_t _patch = 0) :
+        major(_major), minor(_minor), patch(_patch){};
+
+    bool operator==(const SemanticVersion &other);
+    bool operator<(const SemanticVersion &other);
+    bool operator<=(const SemanticVersion &other);
+    bool operator!=(const SemanticVersion &other);
+    bool operator>(const SemanticVersion &other);
+    bool operator>=(const SemanticVersion &other);
+
+    uint32_t major;
+    uint32_t minor;
+    uint32_t patch;
+};
+
+std::ostream &operator<<(std::ostream &out, const SemanticVersion &v);
+
+/*
+ * Hardware Identifier
+ * @versionStatus:             Version status
+ * @version:                   Version revision
+ * @product:                   Product revision
+ * @architecture:              Architecture revison
+ */
+struct HardwareId {
+public:
+    HardwareId(uint32_t _versionStatus,
+               const SemanticVersion &_version,
+               const SemanticVersion &_product,
+               const SemanticVersion &_architecture) :
+        versionStatus(_versionStatus),
+        version(_version), product(_product), architecture(_architecture) {}
+
+    uint32_t versionStatus;
+    SemanticVersion version;
+    SemanticVersion product;
+    SemanticVersion architecture;
+};
+
+/*
+ * Hardware Configuration
+ * @macsPerClockCycle:         MACs per clock cycle
+ * @cmdStreamVersion:          NPU command stream version
+ * @shramSize:                 SHRAM size
+ * @customDma:                 Custom DMA enabled
+ */
+struct HardwareConfiguration {
+public:
+    HardwareConfiguration(uint32_t _macsPerClockCycle,
+                          uint32_t _cmdStreamVersion,
+                          uint32_t _shramSize,
+                          bool _customDma) :
+        macsPerClockCycle(_macsPerClockCycle),
+        cmdStreamVersion(_cmdStreamVersion), shramSize(_shramSize), customDma(_customDma) {}
+
+    uint32_t macsPerClockCycle;
+    uint32_t cmdStreamVersion;
+    uint32_t shramSize;
+    bool customDma;
+};
+
+/**
+ * Device capabilities
+ * @hwId:                      Hardware
+ * @driver:                    Driver revision
+ * @hwCfg                      Hardware configuration
+ */
+class Capabilities {
+public:
+    Capabilities(const HardwareId &_hwId, const HardwareConfiguration &_hwCfg, const SemanticVersion &_driver) :
+        hwId(_hwId), hwCfg(_hwCfg), driver(_driver) {}
+
+    HardwareId hwId;
+    HardwareConfiguration hwCfg;
+    SemanticVersion driver;
+};
+
 class Device {
 public:
     Device(const char *device = "/dev/ethosu0");
     virtual ~Device();
 
     int ioctl(unsigned long cmd, void *data = nullptr);
+    Capabilities capabilities();
 
 private:
     int fd;
diff --git a/driver_library/src/ethosu.cpp b/driver_library/src/ethosu.cpp
index 1768271..ea1f7f4 100644
--- a/driver_library/src/ethosu.cpp
+++ b/driver_library/src/ethosu.cpp
@@ -112,6 +112,43 @@
 }
 
 /****************************************************************************
+ * Semantic Version
+ ****************************************************************************/
+
+bool SemanticVersion::operator==(const SemanticVersion &other) {
+    return other.major == major && other.minor == minor && other.patch == patch;
+}
+
+bool SemanticVersion::operator<(const SemanticVersion &other) {
+    if (other.major > major)
+        return true;
+    if (other.minor > minor)
+        return true;
+    return other.patch > patch;
+}
+
+bool SemanticVersion::operator<=(const SemanticVersion &other) {
+    return *this < other || *this == other;
+}
+
+bool SemanticVersion::operator!=(const SemanticVersion &other) {
+    return !(*this == other);
+}
+
+bool SemanticVersion::operator>(const SemanticVersion &other) {
+    return !(*this <= other);
+}
+
+bool SemanticVersion::operator>=(const SemanticVersion &other) {
+    return !(*this < other);
+}
+
+ostream &operator<<(ostream &out, const SemanticVersion &v) {
+    return out << "{ major=" << unsigned(v.major) << ", minor=" << unsigned(v.minor) << ", patch=" << unsigned(v.patch)
+               << " }";
+}
+
+/****************************************************************************
  * Device
  ****************************************************************************/
 
@@ -130,6 +167,23 @@
     return eioctl(fd, cmd, data);
 }
 
+Capabilities Device::capabilities() {
+    ethosu_uapi_device_capabilities uapi;
+    (void)eioctl(fd, ETHOSU_IOCTL_CAPABILITIES_REQ, static_cast<void *>(&uapi));
+
+    Capabilities capabilities(
+        HardwareId(uapi.hw_id.version_status,
+                   SemanticVersion(uapi.hw_id.version_major, uapi.hw_id.version_minor),
+                   SemanticVersion(uapi.hw_id.product_major),
+                   SemanticVersion(uapi.hw_id.arch_major_rev, uapi.hw_id.arch_minor_rev, uapi.hw_id.arch_patch_rev)),
+        HardwareConfiguration(uapi.hw_cfg.macs_per_cc,
+                              uapi.hw_cfg.cmd_stream_version,
+                              uapi.hw_cfg.shram_size,
+                              bool(uapi.hw_cfg.custom_dma)),
+        SemanticVersion(uapi.driver_major_rev, uapi.driver_minor_rev, uapi.driver_patch_rev));
+    return capabilities;
+}
+
 /****************************************************************************
  * Buffer
  ****************************************************************************/
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
diff --git a/utils/inference_runner/inference_runner.cpp b/utils/inference_runner/inference_runner.cpp
index 7ceec71..17969da 100644
--- a/utils/inference_runner/inference_runner.cpp
+++ b/utils/inference_runner/inference_runner.cpp
@@ -23,6 +23,7 @@
 #include <iomanip>
 #include <iostream>
 #include <list>
+#include <stdio.h>
 #include <string>
 #include <unistd.h>
 
@@ -209,6 +210,20 @@
         cout << "Send version request" << endl;
         device.ioctl(ETHOSU_IOCTL_VERSION_REQ);
 
+        cout << "Send capabilities request" << endl;
+        Capabilities capabilities = device.capabilities();
+
+        cout << "Capabilities:" << endl
+             << "\tversion_status:" << unsigned(capabilities.hwId.versionStatus) << endl
+             << "\tversion:" << capabilities.hwId.version << endl
+             << "\tproduct:" << capabilities.hwId.product << endl
+             << "\tarchitecture:" << capabilities.hwId.architecture << endl
+             << "\tdriver:" << capabilities.driver << endl
+             << "\tmacs_per_cc:" << unsigned(capabilities.hwCfg.macsPerClockCycle) << endl
+             << "\tcmd_stream_version:" << unsigned(capabilities.hwCfg.cmdStreamVersion) << endl
+             << "\tshram_size:" << unsigned(capabilities.hwCfg.shramSize) << endl
+             << "\tcustom_dma:" << std::boolalpha << capabilities.hwCfg.customDma << endl;
+
         /* Create network */
         cout << "Create network" << endl;
         shared_ptr<Buffer> networkBuffer = allocAndFill(device, networkArg);