Create device for Ethos-U kernel driver

When the Ethos-U kernel driver is probed it creates a /dev/ethosu<nr>
device node, which user space use ioctl to communicate with. When
the driver is removed the Ethos-U resources must live on until all
open file handles have been closed.

The rpmsg device can be removed for a number of reasons, for example
if the firmware is stopped or the remoteproc driver is removed. To
allow the remove to complete without waiting for all file handles to
close, a new 'struct device' is created by the kernel driver. This
device will be used to memory allocations and will live on until the
last file handle has been closed.

Change-Id: I790d8219960f25fe64f58c11a865eb65c7b08974
diff --git a/kernel/ethosu_device.c b/kernel/ethosu_device.c
index e99f8c0..96bd8b4 100644
--- a/kernel/ethosu_device.c
+++ b/kernel/ethosu_device.c
@@ -33,7 +33,6 @@
 #include "ethosu_network_info.h"
 #include "uapi/ethosu.h"
 
-#include <linux/dma-mapping.h>
 #include <linux/errno.h>
 #include <linux/fs.h>
 #include <linux/io.h>
@@ -43,6 +42,19 @@
 #include <linux/uaccess.h>
 
 /****************************************************************************
+ * Defines
+ ****************************************************************************/
+
+#define MINOR_BASE      0 /* Minor version starts at 0 */
+#define MINOR_COUNT    64 /* Allocate minor versions */
+
+/****************************************************************************
+ * Variables
+ ****************************************************************************/
+
+static DECLARE_BITMAP(minors, MINOR_COUNT);
+
+/****************************************************************************
  * Functions
  ****************************************************************************/
 
@@ -54,7 +66,7 @@
 			       u32 src)
 {
 	struct ethosu_device *edev = dev_get_drvdata(&rpdev->dev);
-	struct device *dev = &rpdev->dev;
+	struct device *dev = &edev->dev;
 	struct ethosu_core_rpmsg *rpmsg = data;
 	int length = len - sizeof(rpmsg->header);
 	int ret;
@@ -64,7 +76,7 @@
 		 rpmsg->header.magic, rpmsg->header.type, rpmsg->header.msg_id);
 
 	switch (rpmsg->header.type) {
-	case ETHOSU_CORE_MSG_ERR: {
+	case ETHOSU_CORE_MSG_ERR:
 		if (length != sizeof(rpmsg->error)) {
 			dev_warn(dev,
 				 "Msg: Error message of incorrect size. size=%u, expected=%zu", length,
@@ -76,8 +88,9 @@
 		rpmsg->error.msg[sizeof(rpmsg->error.msg) - 1] = '\0';
 		dev_warn(dev, "Msg: Error. type=%u, msg=\"%s\"",
 			 rpmsg->error.type, rpmsg->error.msg);
+
+		rproc_report_crash(rproc_get_by_child(dev), RPROC_FATAL_ERROR);
 		break;
-	}
 	case ETHOSU_CORE_MSG_PING:
 		dev_info(dev, "Msg: Ping");
 		ret = ethosu_mailbox_pong(&edev->mailbox);
@@ -205,7 +218,7 @@
 	struct ethosu_device *edev = container_of(cdev, struct ethosu_device,
 						  cdev);
 	struct rpmsg_device *rpdev = edev->rpdev;
-	struct device *dev = &rpdev->dev;
+	struct device *dev = &edev->dev;
 
 	dev_info(dev, "Device open. file=0x%pK", file);
 
@@ -219,8 +232,8 @@
 			 unsigned long arg)
 {
 	struct rpmsg_device *rpdev = file->private_data;
-	struct device *dev = &rpdev->dev;
-	struct ethosu_device *edev = dev_get_drvdata(dev);
+	struct ethosu_device *edev = dev_get_drvdata(&rpdev->dev);
+	struct device *dev = &edev->dev;
 	void __user *udata = (void __user *)arg;
 	int ret = -EINVAL;
 
@@ -325,6 +338,58 @@
 #endif
 };
 
+static void ethosu_dev_release(struct device *dev)
+{
+	struct ethosu_device *edev = dev_get_drvdata(dev);
+
+	dev_info(dev, "%s", __FUNCTION__);
+
+	clear_bit(MINOR(edev->cdev.dev), minors);
+
+	ethosu_mailbox_deinit(&edev->mailbox);
+	device_destroy(edev->class, edev->cdev.dev);
+	kfree(edev);
+}
+
+static int ethosu_device_register(struct device *dev,
+				  struct device *parent,
+				  void *drvdata,
+				  dev_t devt)
+{
+	struct rproc *rproc = rproc_get_by_child(parent);
+	int ret;
+
+	dev->parent = parent;
+	dev->release = ethosu_dev_release;
+	dev_set_drvdata(dev, drvdata);
+
+	ret = dev_set_name(dev, "ethosu%d", MINOR(devt));
+	if (ret) {
+		dev_err(parent, "Failed to set device name. ret=%d", ret);
+
+		return ret;
+	}
+
+	/* Inherit DMA settings for rproc device */
+	ret = of_reserved_mem_device_init_by_idx(dev,
+						 rproc->dev.parent->of_node, 0);
+	if (ret) {
+		dev_err(parent, "Failed to initialize reserved memory. ret=%d",
+			ret);
+
+		return ret;
+	}
+
+	ret = device_register(dev);
+	if (ret) {
+		dev_err(parent, "Failed to register device. ret=%d", ret);
+
+		return ret;
+	}
+
+	return 0;
+}
+
 int ethosu_dev_init(struct rpmsg_device *rpdev,
 		    struct class *class,
 		    dev_t devt)
@@ -332,51 +397,76 @@
 	struct device *dev = &rpdev->dev;
 	struct ethosu_device *edev;
 	struct device *sysdev;
+	int minor;
 	int ret;
 
 	dev_info(dev, "%s", __FUNCTION__);
 
+	/* Reserve minor number for device node */
+	minor = find_first_zero_bit(minors, MINOR_COUNT);
+	if (minor >= MINOR_COUNT) {
+		dev_err(dev, "No more minor numbers.");
+
+		return -ENOMEM;
+	}
+
+	devt = MKDEV(MAJOR(devt), minor);
+
 	/* Allocate and create Ethos-U device */
-	edev = devm_kzalloc(dev, sizeof(*edev), GFP_KERNEL);
+	edev = kzalloc(sizeof(*edev), GFP_KERNEL);
 	if (!edev)
 		return -ENOMEM;
 
-	dev_set_drvdata(dev, edev);
+	dev_set_drvdata(&rpdev->dev, edev);
 
 	edev->rpdev = rpdev;
 	edev->class = class;
-	edev->devt = devt;
+
+	/* Create device object */
+	ret = ethosu_device_register(&edev->dev, &rpdev->dev, edev,
+				     devt);
+	if (ret)
+		goto free_edev;
+
+	/* Continue with new device */
+	dev = &edev->dev;
 
 	/* Create RPMsg endpoint */
 	edev->ept = ethosu_create_ept(rpdev);
-	if (IS_ERR(edev->ept))
-		return PTR_ERR(edev->ept);
+	if (IS_ERR(edev->ept)) {
+		ret = PTR_ERR(edev->ept);
+		goto device_unregister;
+	}
 
 	ret = ethosu_mailbox_init(&edev->mailbox, dev, edev->ept);
 	if (ret)
-		return ret;
+		goto device_unregister;
 
 	/* Create device node */
 	cdev_init(&edev->cdev, &fops);
 	edev->cdev.owner = THIS_MODULE;
 
-	ret = cdev_add(&edev->cdev, edev->devt, 1);
+	cdev_set_parent(&edev->cdev, &dev->kobj);
+
+	ret = cdev_add(&edev->cdev, devt, 1);
 	if (ret) {
 		dev_err(dev, "Failed to add character device.");
 		goto deinit_mailbox;
 	}
 
-	sysdev = device_create(edev->class, NULL, edev->devt, rpdev,
-			       "ethosu%d", MINOR(edev->devt));
+	sysdev = device_create(edev->class, NULL, devt, rpdev,
+			       "ethosu%d", MINOR(devt));
 	if (IS_ERR(sysdev)) {
 		dev_err(dev, "Failed to create device.");
 		ret = PTR_ERR(sysdev);
 		goto del_cdev;
 	}
 
+	set_bit(minor, minors);
+
 	dev_info(dev,
 		 "Created Arm Ethos-U device. name=%s, major=%d, minor=%d",
-		 dev_name(sysdev), MAJOR(edev->devt), MINOR(edev->devt));
+		 dev_name(sysdev), MAJOR(devt), MINOR(devt));
 
 	ethosu_mailbox_ping(&edev->mailbox);
 
@@ -388,6 +478,12 @@
 deinit_mailbox:
 	ethosu_mailbox_deinit(&edev->mailbox);
 
+device_unregister:
+	device_unregister(dev);
+
+free_edev:
+	kfree(edev);
+
 	return ret;
 }
 
@@ -398,8 +494,8 @@
 
 	dev_info(dev, "%s", __FUNCTION__);
 
-	ethosu_mailbox_deinit(&edev->mailbox);
+	ethosu_mailbox_fail(&edev->mailbox);
 	rpmsg_destroy_ept(edev->ept);
-	device_destroy(edev->class, edev->cdev.dev);
 	cdev_del(&edev->cdev);
+	device_unregister(&edev->dev);
 }