+ dev_req_buffer_size = (u32 *)(dev_req_da + dev_req->count);
+ memcpy(dev_req->data, req->data, req->size_in);
+
+ buffer = kmalloc_array(req->count, sizeof(*buffer), GFP_KERNEL);
+ for (i = 0; i < req->count; i++) {
+ buffer[i].fd = fd[i];
+ ret = apu_device_memory_map(apu, &buffer[i]);
+ if (ret)
+ goto err_free_memory;
+ dev_req_da[i] = buffer[i].iova;
+ dev_req_buffer_size[i] = buffer_size[i];
+ }
+
+ ret = _apu_send_request(apu, rpdev, dev_req, size);
+
+err_free_memory:
+ for (i--; i >= 0; i--)
+ apu_device_memory_unmap(apu, &buffer[i]);
+
+ req->result = dev_req->result;
+ req->size_in = dev_req->size_in;
+ req->size_out = dev_req->size_out;
+ memcpy(req->data, dev_req->data, dev_req->size_in + dev_req->size_out +
+ sizeof(u32) * req->count);
+
+ kfree(buffer);
+ kfree(dev_req);
+
+ return ret;
+}
+
+
+static long rpmsg_eptdev_ioctl(struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct rpmsg_apu *apu = fp->private_data;
+ struct apu_request apu_req;
+ struct apu_request *apu_req_full;
+ void __user *argp = (void __user *)arg;
+ int len;
+ int ret;
+
+ switch (cmd) {
+ case APU_SEND_REQ_IOCTL:
+ /* Get the header */
+ if (copy_from_user(&apu_req, argp,
+ sizeof(apu_req)))
+ return -EFAULT;
+
+ len = sizeof(*apu_req_full) + apu_req.size_in +
+ apu_req.size_out + apu_req.count * sizeof(u32) * 2;
+ apu_req_full = kzalloc(len, GFP_KERNEL);
+ if (!apu_req_full)
+ return -ENOMEM;
+
+ /* Get the whole request */
+ if (copy_from_user(apu_req_full, argp, len)) {
+ kfree(apu_req_full);
+ return -EFAULT;
+ }
+
+ ret = apu_send_request(apu, apu_req_full);
+ if (ret) {
+ kfree(apu_req_full);
+ return ret;
+ }
+
+ if (copy_to_user(argp, apu_req_full, sizeof(apu_req) +
+ sizeof(u32) * apu_req_full->count +
+ apu_req_full->size_in + apu_req_full->size_out))
+ ret = -EFAULT;
+
+ kfree(apu_req_full);
+ return ret;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rpmsg_eptdev_open(struct inode *inode, struct file *filp)
+{
+ struct rpmsg_apu *apu = cdev_to_apu(inode->i_cdev);
+
+ get_device(&apu->dev);
+ filp->private_data = apu;
+
+ return 0;
+}
+
+static int rpmsg_eptdev_release(struct inode *inode, struct file *filp)
+{
+ struct rpmsg_apu *apu = cdev_to_apu(inode->i_cdev);
+
+ put_device(&apu->dev);
+
+ return 0;
+}
+
+static const struct file_operations rpmsg_eptdev_fops = {
+ .owner = THIS_MODULE,
+ .open = rpmsg_eptdev_open,
+ .release = rpmsg_eptdev_release,
+ .unlocked_ioctl = rpmsg_eptdev_ioctl,
+ .compat_ioctl = rpmsg_eptdev_ioctl,
+};
+
+static void iova_domain_release(struct kref *ref)
+{
+ put_iova_domain(&apu_iovad->iovad);
+ kfree(apu_iovad);
+ apu_iovad = NULL;
+}
+
+static struct fw_rsc_iova *apu_find_rcs_iova(struct rpmsg_apu *apu)
+{
+ struct rproc *rproc = apu->rproc;
+ struct resource_table *table;
+ struct fw_rsc_iova *rsc;
+ int i;
+
+ table = rproc->table_ptr;
+ for (i = 0; i < table->num; i++) {
+ int offset = table->offset[i];
+ struct fw_rsc_hdr *hdr = (void *)table + offset;
+
+ switch (hdr->type) {
+ case RSC_VENDOR_IOVA:
+ rsc = (void *)hdr + sizeof(*hdr);
+ return rsc;
+ break;
+ default:
+ continue;
+ }
+ }
+
+ return NULL;
+}
+
+static int apu_reserve_iova(struct rpmsg_apu *apu, struct iova_domain *iovad)
+{
+ struct rproc *rproc = apu->rproc;
+ struct resource_table *table;
+ struct fw_rsc_carveout *rsc;
+ int i;
+
+ table = rproc->table_ptr;
+ for (i = 0; i < table->num; i++) {
+ int offset = table->offset[i];
+ struct fw_rsc_hdr *hdr = (void *)table + offset;
+
+ if (hdr->type == RSC_CARVEOUT) {
+ struct iova *iova;
+
+ rsc = (void *)hdr + sizeof(*hdr);
+ iova = reserve_iova(iovad, PHYS_PFN(rsc->da),
+ PHYS_PFN(rsc->da + rsc->len));
+ if (!iova) {
+ dev_err(&apu->dev, "failed to reserve iova\n");
+ return -ENOMEM;
+ }
+ dev_dbg(&apu->dev, "Reserve: %x - %x\n",
+ rsc->da, rsc->da + rsc->len);
+ }
+ }
+
+ return 0;
+}
+
+static int apu_init_iovad(struct rpmsg_apu *apu)
+{
+ struct fw_rsc_iova *rsc;
+
+ if (!apu->rproc->table_ptr) {
+ dev_err(&apu->dev,
+ "No resource_table: has the firmware been loaded ?\n");
+ return -ENODEV;
+ }
+
+ rsc = apu_find_rcs_iova(apu);
+ if (!rsc) {
+ dev_err(&apu->dev, "No iova range defined in resource_table\n");
+ return -ENOMEM;
+ }
+
+ if (!apu_iovad) {
+ apu_iovad = kzalloc(sizeof(*apu_iovad), GFP_KERNEL);
+ if (!apu_iovad)
+ return -ENOMEM;
+
+ init_iova_domain(&apu_iovad->iovad, PAGE_SIZE,
+ PHYS_PFN(rsc->da));
+ apu_reserve_iova(apu, &apu_iovad->iovad);
+ kref_init(&apu_iovad->refcount);
+ } else
+ kref_get(&apu_iovad->refcount);
+
+ apu->iovad = &apu_iovad->iovad;
+ apu->iova_limit_pfn = PHYS_PFN(rsc->da + rsc->len) - 1;
+
+ return 0;
+}
+
+static struct rproc *apu_get_rproc(struct rpmsg_device *rpdev)
+{
+ /*
+ * To work, the APU RPMsg driver need to get the rproc device.
+ * Currently, we only use virtio so we could use that to find the
+ * remoteproc parent.
+ */
+ if (!rpdev->dev.parent && rpdev->dev.parent->bus) {
+ dev_err(&rpdev->dev, "invalid rpmsg device\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (strcmp(rpdev->dev.parent->bus->name, "virtio")) {
+ dev_err(&rpdev->dev, "unsupported bus\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ return vdev_to_rproc(dev_to_virtio(rpdev->dev.parent));
+}
+
+static void rpmsg_apu_release_device(struct device *dev)
+{
+ struct rpmsg_apu *apu = dev_to_apu(dev);
+
+ ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
+ ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
+ cdev_del(&apu->cdev);
+ kfree(apu);
+}
+
+static int apu_rpmsg_probe(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_apu *apu;
+ struct device *dev;
+ int ret;
+
+ apu = devm_kzalloc(&rpdev->dev, sizeof(*apu), GFP_KERNEL);
+ if (!apu)
+ return -ENOMEM;
+ apu->rpdev = rpdev;
+
+ apu->rproc = apu_get_rproc(rpdev);
+ if (IS_ERR_OR_NULL(apu->rproc))
+ return PTR_ERR(apu->rproc);
+
+ dev = &apu->dev;
+ device_initialize(dev);
+ dev->parent = &rpdev->dev;
+
+ cdev_init(&apu->cdev, &rpmsg_eptdev_fops);
+ apu->cdev.owner = THIS_MODULE;
+
+ ret = ida_simple_get(&rpmsg_minor_ida, 0, APU_DEV_MAX, GFP_KERNEL);
+ if (ret < 0)
+ goto free_apu;
+ dev->devt = MKDEV(MAJOR(rpmsg_major), ret);
+
+ ret = ida_simple_get(&rpmsg_ctrl_ida, 0, 0, GFP_KERNEL);
+ if (ret < 0)
+ goto free_minor_ida;
+ dev->id = ret;
+ dev_set_name(&apu->dev, "apu%d", ret);
+
+ ret = cdev_add(&apu->cdev, dev->devt, 1);
+ if (ret)
+ goto free_ctrl_ida;
+
+ /* We can now rely on the release function for cleanup */
+ dev->release = rpmsg_apu_release_device;
+
+ ret = device_add(dev);
+ if (ret) {
+ dev_err(&rpdev->dev, "device_add failed: %d\n", ret);
+ put_device(dev);
+ }
+
+ /* Make device dma capable by inheriting from parent's capabilities */
+ set_dma_ops(&rpdev->dev, get_dma_ops(apu->rproc->dev.parent));
+
+ ret = dma_coerce_mask_and_coherent(&rpdev->dev,
+ dma_get_mask(apu->rproc->dev.parent));
+ if (ret)
+ goto err_put_device;
+
+ rpdev->dev.iommu_group = apu->rproc->dev.parent->iommu_group;
+
+ ret = apu_init_iovad(apu);
+
+ dev_set_drvdata(&rpdev->dev, apu);
+
+ return ret;
+
+err_put_device:
+ put_device(dev);
+free_ctrl_ida:
+ ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
+free_minor_ida:
+ ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
+free_apu:
+ put_device(dev);
+ kfree(apu);
+
+ return ret;
+}
+
+static void apu_rpmsg_remove(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_apu *apu = dev_get_drvdata(&rpdev->dev);
+
+ if (apu_iovad)
+ kref_put(&apu_iovad->refcount, iova_domain_release);
+
+ device_del(&apu->dev);
+ put_device(&apu->dev);
+ kfree(apu);
+}
+
+static const struct rpmsg_device_id apu_rpmsg_match[] = {
+ { APU_RPMSG_SERVICE_MT8183 },
+ {}
+};
+
+static struct rpmsg_driver apu_rpmsg_driver = {
+ .probe = apu_rpmsg_probe,
+ .remove = apu_rpmsg_remove,
+ .callback = apu_rpmsg_callback,
+ .id_table = apu_rpmsg_match,
+ .drv = {
+ .name = "apu_rpmsg",
+ },
+};
+
+static int __init apu_rpmsg_init(void)
+{
+ int ret;
+
+ ret = alloc_chrdev_region(&rpmsg_major, 0, APU_DEV_MAX, "apu");
+ if (ret < 0) {
+ pr_err("apu: failed to allocate char dev region\n");
+ return ret;
+ }
+
+ return register_rpmsg_driver(&apu_rpmsg_driver);
+}
+arch_initcall(apu_rpmsg_init);
+
+static void __exit apu_rpmsg_exit(void)
+{
+ unregister_rpmsg_driver(&apu_rpmsg_driver);
+}
+module_exit(apu_rpmsg_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("APU RPMSG driver");
diff --git a/drivers/rpmsg/apu_rpmsg.h b/drivers/rpmsg/apu_rpmsg.h
new file mode 100644
index 000000000000..54b5b7880750
--- /dev/null
+++ b/drivers/rpmsg/apu_rpmsg.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright 2020 BayLibre SAS
+ */
+
+#ifndef __APU_RPMSG_H__
+#define __APU_RPMSG_H__
+
+/*
+ * Firmware request, must be aligned with the one defined in firmware.
+ * @id: Request id, used in the case of reply, to find the pending request
+ * @cmd: The command id to execute in the firmware
+ * @result: The result of the command executed on the firmware
+ * @size: The size of the data available in this request
+ * @count: The number of shared buffer
+ * @data: Contains the data attached with the request if size is greater than
+ * zero, and the addresses of shared buffers if count is greater than
+ * zero. Both the data and the shared buffer could be read and write
+ * by the APU.
+ */
+struct apu_dev_request {
+ u16 id;
+ u16 cmd;
+ u16 result;
+ u16 size_in;
+ u16 size_out;
+ u16 count;
+ u8 data[0];
+} __packed;
+
+#define APU_RPMSG_SERVICE_MT8183 "rpmsg-mt8183-apu0"
+#define APU_CTRL_SRC 1
+#define APU_CTRL_DST 1
+
+/* Vendor specific resource table entry */
+#define RSC_VENDOR_IOVA 128
+
+/*
+ * Firmware IOVA resource table entry
+ * Define a range of virtual device address that could mapped using the IOMMU.
+ * @da: Start virtual device address
+ * @len: Length of the virtual device address
+ * @name: name of the resource
+ */
+struct fw_rsc_iova {
+ u32 da;
+ u32 len;
+ u32 reserved;
+ u8 name[32];
+} __packed;
+
+#endif /* __APU_RPMSG_H__ */
diff --git a/include/uapi/linux/apu_rpmsg.h b/include/uapi/linux/apu_rpmsg.h
new file mode 100644
index 000000000000..81c9e4af9a94
--- /dev/null
+++ b/include/uapi/linux/apu_rpmsg.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2020 BayLibre
+ */
+
+#ifndef _UAPI_RPMSG_APU_H_
+#define _UAPI_RPMSG_APU_H_
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/*
+ * Structure containing the APU request from userspace application
+ * @cmd: The id of the command to execute on the APU
+ * @result: The result of the command executed on the APU
+ * @size: The size of the data available in this request
+ * @count: The number of shared buffer
+ * @data: Contains the data attached with the request if size is greater than
+ * zero, and the files descriptors of shared buffers if count is greater
+ * than zero. Both the data and the shared buffer could be read and write
+ * by the APU.
+ */
+struct apu_request {
+ __u16 cmd;
+ __u16 result;
+ __u16 size_in;
+ __u16 size_out;
+ __u16 count;
+ __u16 reserved;
+ __u8 data[0];
+};
+
+/* Send synchronous request to an APU */
+#define APU_SEND_REQ_IOCTL _IOWR(0xb7, 0x2, struct apu_request)
+
+#endif
--
2.26.2