diff --git a/Documentation/devicetree/bindings/soc/qcom/guest_shm.txt b/Documentation/devicetree/bindings/soc/qcom/guest_shm.txt new file mode 100644 index 000000000000..9491344c7b9f --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/guest_shm.txt @@ -0,0 +1,19 @@ +QVM Guest Shared Memory + +guest_shm is a device that enables linux as a guest operating system +to allocate shared memory between virtual machines and send notifications +of updates to other virtual machines. + +Required properties: +- compatible: Must be "qvm,guest_shm". +- interrupt-parent: Parent interrupt controller. +- interrupts: Should contain QVM interrupt. +- reg: Physical address of the guest factory and length. + +Example: + qvm,guest_shm { + compatible = "qvm,guest_shm"; + interrupt-parent = <&gic>; + interrupts = <6 4>; + reg = <0x1c050000 0x1000>; + }; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 91412a10bf65..a75124322dc1 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -186,6 +186,7 @@ qcom Qualcomm Technologies, Inc qemu QEMU, a generic and open source machine emulator and virtualizer qi Qi Hardware qnap QNAP Systems, Inc. +qvm BlackBerry Ltd radxa Radxa raidsonic RaidSonic Technology GmbH ralink Mediatek/Ralink Technology Corp. diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 30166bcce52b..55db633d3761 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -1,6 +1,8 @@ # # QCOM Soc drivers # +source "drivers/soc/qcom/hab/Kconfig" + config MSM_INRUSH_CURRENT_MITIGATION bool "Inrush-current mitigation Driver" help diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 0bf54bedd6ea..229b13a04819 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -107,3 +107,4 @@ obj-$(CONFIG_QCOM_SMCINVOKE) += smcinvoke.o obj-$(CONFIG_QCOM_EARLY_RANDOM) += early_random.o obj-$(CONFIG_QCOM_CX_IPEAK) += cx_ipeak.o obj-$(CONFIG_MSM_CACHE_M4M_ERP64) += cache_m4m_erp64.o +obj-$(CONFIG_MSM_HAB) += hab/ diff --git a/drivers/soc/qcom/hab/Kconfig b/drivers/soc/qcom/hab/Kconfig new file mode 100644 index 000000000000..2e4f5114e29f --- /dev/null +++ b/drivers/soc/qcom/hab/Kconfig @@ -0,0 +1,7 @@ +config MSM_HAB + bool "Enable Multimedia driver Hypervisor Abstraction Layer" + help + Multimedia driver hypervisor abstraction layer. + Required for drivers to use the HAB API to communicate with the host + OS. + diff --git a/drivers/soc/qcom/hab/Makefile b/drivers/soc/qcom/hab/Makefile new file mode 100644 index 000000000000..83fc54d42202 --- /dev/null +++ b/drivers/soc/qcom/hab/Makefile @@ -0,0 +1,14 @@ +msm_hab-objs = \ + khab.o \ + hab.o \ + hab_msg.o \ + hab_vchan.o \ + hab_pchan.o \ + hab_open.o \ + hab_mimex.o \ + hab_mem_linux.o \ + hab_pipe.o \ + qvm_comm.o \ + hab_qvm.o + +obj-$(CONFIG_MSM_HAB) += msm_hab.o diff --git a/drivers/soc/qcom/hab/hab.c b/drivers/soc/qcom/hab/hab.c new file mode 100644 index 000000000000..c6df36f5c0a2 --- /dev/null +++ b/drivers/soc/qcom/hab/hab.c @@ -0,0 +1,726 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include "hab.h" + +#define HAB_DEVICE_CNSTR(__name__, __id__, __num__) { \ + .name = __name__,\ + .id = __id__,\ + .pchannels = LIST_HEAD_INIT(hab_devices[__num__].pchannels),\ + .pchan_lock = __MUTEX_INITIALIZER(hab_devices[__num__].pchan_lock),\ + .openq_list = LIST_HEAD_INIT(hab_devices[__num__].openq_list),\ + .openlock = __SPIN_LOCK_UNLOCKED(&hab_devices[__num__].openlock)\ + } + +/* the following has to match habmm definitions, order does not matter */ +static struct hab_device hab_devices[] = { + HAB_DEVICE_CNSTR(DEVICE_AUD1_NAME, MM_AUD_1, 0), + HAB_DEVICE_CNSTR(DEVICE_AUD2_NAME, MM_AUD_2, 1), + HAB_DEVICE_CNSTR(DEVICE_AUD3_NAME, MM_AUD_3, 2), + HAB_DEVICE_CNSTR(DEVICE_AUD4_NAME, MM_AUD_4, 3), + HAB_DEVICE_CNSTR(DEVICE_CAM_NAME, MM_CAM, 4), + HAB_DEVICE_CNSTR(DEVICE_DISP1_NAME, MM_DISP_1, 5), + HAB_DEVICE_CNSTR(DEVICE_DISP2_NAME, MM_DISP_2, 6), + HAB_DEVICE_CNSTR(DEVICE_DISP3_NAME, MM_DISP_3, 7), + HAB_DEVICE_CNSTR(DEVICE_DISP4_NAME, MM_DISP_4, 8), + HAB_DEVICE_CNSTR(DEVICE_DISP5_NAME, MM_DISP_5, 9), + HAB_DEVICE_CNSTR(DEVICE_GFX_NAME, MM_GFX, 10), + HAB_DEVICE_CNSTR(DEVICE_VID_NAME, MM_VID, 11), + HAB_DEVICE_CNSTR(DEVICE_MISC_NAME, MM_MISC, 12), + HAB_DEVICE_CNSTR(DEVICE_QCPE1_NAME, MM_QCPE_VM1, 13), + HAB_DEVICE_CNSTR(DEVICE_QCPE2_NAME, MM_QCPE_VM2, 14), + HAB_DEVICE_CNSTR(DEVICE_QCPE3_NAME, MM_QCPE_VM3, 15), + HAB_DEVICE_CNSTR(DEVICE_QCPE4_NAME, MM_QCPE_VM4, 16) +}; + +struct hab_driver hab_driver = { + .ndevices = ARRAY_SIZE(hab_devices), + .devp = hab_devices, +}; + +struct uhab_context *hab_ctx_alloc(int kernel) +{ + struct uhab_context *ctx; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return NULL; + + ctx->closing = 0; + INIT_LIST_HEAD(&ctx->vchannels); + INIT_LIST_HEAD(&ctx->exp_whse); + INIT_LIST_HEAD(&ctx->imp_whse); + + INIT_LIST_HEAD(&ctx->exp_rxq); + init_waitqueue_head(&ctx->exp_wq); + spin_lock_init(&ctx->expq_lock); + + spin_lock_init(&ctx->imp_lock); + rwlock_init(&ctx->exp_lock); + rwlock_init(&ctx->ctx_lock); + + kref_init(&ctx->refcount); + ctx->import_ctx = habmem_imp_hyp_open(); + if (!ctx->import_ctx) { + kfree(ctx); + return NULL; + } + ctx->kernel = kernel; + + return ctx; +} + +void hab_ctx_free(struct kref *ref) +{ + struct uhab_context *ctx = + container_of(ref, struct uhab_context, refcount); + struct hab_export_ack_recvd *ack_recvd, *tmp; + + habmem_imp_hyp_close(ctx->import_ctx, ctx->kernel); + + list_for_each_entry_safe(ack_recvd, tmp, &ctx->exp_rxq, node) { + list_del(&ack_recvd->node); + kfree(ack_recvd); + } + + kfree(ctx); +} + +struct virtual_channel *hab_get_vchan_fromvcid(int32_t vcid, + struct uhab_context *ctx) +{ + struct virtual_channel *vchan; + + read_lock(&ctx->ctx_lock); + list_for_each_entry(vchan, &ctx->vchannels, node) { + if (vcid == vchan->id) { + kref_get(&vchan->refcount); + read_unlock(&ctx->ctx_lock); + return vchan; + } + } + read_unlock(&ctx->ctx_lock); + return NULL; +} + +static struct hab_device *find_hab_device(unsigned int mm_id) +{ + int i; + + for (i = 0; i < hab_driver.ndevices; i++) { + if (hab_driver.devp[i].id == HAB_MMID_GET_MAJOR(mm_id)) + return &hab_driver.devp[i]; + } + + pr_err("find_hab_device failed: id=%d\n", mm_id); + return NULL; +} +/* + * open handshake in FE and BE + + * frontend backend + * send(INIT) wait(INIT) + * wait(INIT_ACK) send(INIT_ACK) + * send(ACK) wait(ACK) + + */ +struct virtual_channel *frontend_open(struct uhab_context *ctx, + unsigned int mm_id, + int dom_id) +{ + int ret, open_id = 0; + struct physical_channel *pchan = NULL; + struct hab_device *dev; + struct virtual_channel *vchan = NULL; + static atomic_t open_id_counter = ATOMIC_INIT(0); + struct hab_open_request request; + struct hab_open_request *recv_request; + int sub_id = HAB_MMID_GET_MINOR(mm_id); + + dev = find_hab_device(mm_id); + if (dev == NULL) { + ret = -EINVAL; + goto err; + } + + pchan = hab_pchan_find_domid(dev, dom_id); + if (!pchan) { + pr_err("hab_pchan_find_domid failed: dom_id=%d\n", dom_id); + ret = -EINVAL; + goto err; + } + + vchan = hab_vchan_alloc(ctx, pchan); + if (!vchan) { + ret = -ENOMEM; + goto err; + } + + /* Send Init sequence */ + open_id = atomic_inc_return(&open_id_counter); + hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT, pchan, + vchan->id, sub_id, open_id); + ret = hab_open_request_send(&request); + if (ret) { + pr_err("hab_open_request_send failed: %d\n", ret); + goto err; + } + + /* Wait for Init-Ack sequence */ + hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT_ACK, pchan, + 0, sub_id, open_id); + ret = hab_open_listen(ctx, dev, &request, &recv_request, 0); + if (ret || !recv_request) { + pr_err("hab_open_listen failed: %d\n", ret); + goto err; + } + + vchan->otherend_id = recv_request->vchan_id; + hab_open_request_free(recv_request); + + /* Send Ack sequence */ + hab_open_request_init(&request, HAB_PAYLOAD_TYPE_ACK, pchan, + 0, sub_id, open_id); + ret = hab_open_request_send(&request); + if (ret) + goto err; + + hab_pchan_put(pchan); + + return vchan; +err: + if (vchan) + hab_vchan_put(vchan); + if (pchan) + hab_pchan_put(pchan); + + return ERR_PTR(ret); +} + +struct virtual_channel *backend_listen(struct uhab_context *ctx, + unsigned int mm_id) +{ + int ret; + int open_id; + int sub_id = HAB_MMID_GET_MINOR(mm_id); + struct physical_channel *pchan = NULL; + struct hab_device *dev; + struct virtual_channel *vchan = NULL; + struct hab_open_request request; + struct hab_open_request *recv_request; + uint32_t otherend_vchan_id; + + dev = find_hab_device(mm_id); + if (dev == NULL) { + ret = -EINVAL; + goto err; + } + + while (1) { + /* Wait for Init sequence */ + hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT, + NULL, 0, sub_id, 0); + ret = hab_open_listen(ctx, dev, &request, &recv_request, 0); + if (ret || !recv_request) { + pr_err("hab_open_listen failed: %d\n", ret); + goto err; + } + + otherend_vchan_id = recv_request->vchan_id; + open_id = recv_request->open_id; + pchan = recv_request->pchan; + hab_pchan_get(pchan); + hab_open_request_free(recv_request); + + vchan = hab_vchan_alloc(ctx, pchan); + if (!vchan) { + ret = -ENOMEM; + goto err; + } + + vchan->otherend_id = otherend_vchan_id; + + /* Send Init-Ack sequence */ + hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT_ACK, + pchan, vchan->id, sub_id, open_id); + ret = hab_open_request_send(&request); + if (ret) + goto err; + + /* Wait for Ack sequence */ + hab_open_request_init(&request, HAB_PAYLOAD_TYPE_ACK, + pchan, 0, sub_id, open_id); + ret = hab_open_listen(ctx, dev, &request, &recv_request, HZ); + + if (ret != -EAGAIN) + break; + + hab_vchan_put(vchan); + vchan = NULL; + hab_pchan_put(pchan); + pchan = NULL; + } + + if (ret || !recv_request) { + pr_err("backend_listen failed: %d\n", ret); + ret = -EINVAL; + goto err; + } + + hab_open_request_free(recv_request); + hab_pchan_put(pchan); + return vchan; +err: + if (vchan) + hab_vchan_put(vchan); + if (pchan) + hab_pchan_put(pchan); + return ERR_PTR(ret); +} + +long hab_vchan_send(struct uhab_context *ctx, + int vcid, + size_t sizebytes, + void *data, + unsigned int flags) +{ + struct virtual_channel *vchan; + int ret; + struct hab_header header = HAB_HEADER_INITIALIZER; + int nonblocking_flag = flags & HABMM_SOCKET_SEND_FLAGS_NON_BLOCKING; + + if (sizebytes > HAB_MAX_MSG_SIZEBYTES) { + pr_err("Message too large, %lu bytes\n", sizebytes); + return -EINVAL; + } + + vchan = hab_get_vchan_fromvcid(vcid, ctx); + if (!vchan || vchan->otherend_closed) + return -ENODEV; + + HAB_HEADER_SET_SIZE(header, sizebytes); + HAB_HEADER_SET_TYPE(header, HAB_PAYLOAD_TYPE_MSG); + HAB_HEADER_SET_ID(header, vchan->otherend_id); + + while (1) { + ret = physical_channel_send(vchan->pchan, &header, data); + + if (vchan->otherend_closed || nonblocking_flag || + ret != -EAGAIN) + break; + + schedule(); + } + + hab_vchan_put(vchan); + return ret; +} + +struct hab_message *hab_vchan_recv(struct uhab_context *ctx, + int vcid, + unsigned int flags) +{ + struct virtual_channel *vchan; + struct hab_message *message; + int ret = 0; + int nonblocking_flag = flags & HABMM_SOCKET_RECV_FLAGS_NON_BLOCKING; + + vchan = hab_get_vchan_fromvcid(vcid, ctx); + if (!vchan || vchan->otherend_closed) + return ERR_PTR(-ENODEV); + + if (nonblocking_flag) { + /* + * Try to pull data from the ring in this context instead of + * IRQ handler. Any available messages will be copied and queued + * internally, then fetched by hab_msg_dequeue() + */ + physical_channel_rx_dispatch((unsigned long) vchan->pchan); + } + + message = hab_msg_dequeue(vchan, !nonblocking_flag); + if (!message) { + if (nonblocking_flag) + ret = -EAGAIN; + else + ret = -EPIPE; + } + + hab_vchan_put(vchan); + return ret ? ERR_PTR(ret) : message; +} + +bool hab_is_loopback(void) +{ + return hab_driver.b_loopback; +} + +int hab_vchan_open(struct uhab_context *ctx, + unsigned int mmid, + int32_t *vcid, + uint32_t flags) +{ + struct virtual_channel *vchan; + + if (!vcid) + return -EINVAL; + + if (hab_is_loopback()) { + if (!hab_driver.loopback_num) { + hab_driver.loopback_num = 1; + vchan = backend_listen(ctx, mmid); + } else { + hab_driver.loopback_num = 0; + vchan = frontend_open(ctx, mmid, LOOPBACK_DOM); + } + } else { + if (hab_driver.b_server_dom) + vchan = backend_listen(ctx, mmid); + else + vchan = frontend_open(ctx, mmid, 0); + } + + if (IS_ERR(vchan)) + return PTR_ERR(vchan); + + write_lock(&ctx->ctx_lock); + list_add_tail(&vchan->node, &ctx->vchannels); + write_unlock(&ctx->ctx_lock); + + *vcid = vchan->id; + + return 0; +} + +void hab_send_close_msg(struct virtual_channel *vchan) +{ + struct hab_header header; + + if (vchan && !vchan->otherend_closed) { + HAB_HEADER_SET_SIZE(header, 0); + HAB_HEADER_SET_TYPE(header, HAB_PAYLOAD_TYPE_CLOSE); + HAB_HEADER_SET_ID(header, vchan->otherend_id); + physical_channel_send(vchan->pchan, &header, NULL); + } +} + +static void hab_vchan_close_impl(struct kref *ref) +{ + struct virtual_channel *vchan = + container_of(ref, struct virtual_channel, usagecnt); + + list_del(&vchan->node); + hab_vchan_stop_notify(vchan); + hab_vchan_put(vchan); +} + + +void hab_vchan_close(struct uhab_context *ctx, int32_t vcid) +{ + struct virtual_channel *vchan, *tmp; + + if (!ctx) + return; + + write_lock(&ctx->ctx_lock); + list_for_each_entry_safe(vchan, tmp, &ctx->vchannels, node) { + if (vchan->id == vcid) { + kref_put(&vchan->usagecnt, hab_vchan_close_impl); + break; + } + } + + write_unlock(&ctx->ctx_lock); +} + +static int hab_open(struct inode *inodep, struct file *filep) +{ + int result = 0; + struct uhab_context *ctx; + + ctx = hab_ctx_alloc(0); + + if (!ctx) { + pr_err("hab_ctx_alloc failed\n"); + filep->private_data = NULL; + return -ENOMEM; + } + + filep->private_data = ctx; + + return result; +} + +static int hab_release(struct inode *inodep, struct file *filep) +{ + struct uhab_context *ctx = filep->private_data; + struct virtual_channel *vchan, *tmp; + + if (!ctx) + return 0; + + write_lock(&ctx->ctx_lock); + + list_for_each_entry_safe(vchan, tmp, &ctx->vchannels, node) { + list_del(&vchan->node); + hab_vchan_stop_notify(vchan); + hab_vchan_put(vchan); + } + + write_unlock(&ctx->ctx_lock); + + hab_ctx_put(ctx); + filep->private_data = NULL; + + return 0; +} + +static long hab_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + struct uhab_context *ctx = (struct uhab_context *)filep->private_data; + struct hab_open *open_param; + struct hab_close *close_param; + struct hab_recv *recv_param; + struct hab_send *send_param; + struct hab_message *msg; + void *send_data; + unsigned char data[256] = { 0 }; + long ret = 0; + + if (_IOC_SIZE(cmd) && (cmd & IOC_IN)) { + if (_IOC_SIZE(cmd) > sizeof(data)) + return -EINVAL; + + if (copy_from_user(data, (void __user *)arg, _IOC_SIZE(cmd))) { + pr_err("copy_from_user failed cmd=%x size=%d\n", + cmd, _IOC_SIZE(cmd)); + return -EFAULT; + } + } + + switch (cmd) { + case IOCTL_HAB_VC_OPEN: + open_param = (struct hab_open *)data; + ret = hab_vchan_open(ctx, open_param->mmid, + &open_param->vcid, open_param->flags); + break; + case IOCTL_HAB_VC_CLOSE: + close_param = (struct hab_close *)data; + hab_vchan_close(ctx, close_param->vcid); + break; + case IOCTL_HAB_SEND: + send_param = (struct hab_send *)data; + if (send_param->sizebytes > HAB_MAX_MSG_SIZEBYTES) { + ret = -EINVAL; + break; + } + + send_data = kzalloc(send_param->sizebytes, GFP_TEMPORARY); + if (!send_data) { + ret = -ENOMEM; + break; + } + + if (copy_from_user(send_data, (void __user *)send_param->data, + send_param->sizebytes)) { + ret = -EFAULT; + } else { + ret = hab_vchan_send(ctx, send_param->vcid, + send_param->sizebytes, + send_data, + send_param->flags); + } + kfree(send_data); + break; + case IOCTL_HAB_RECV: + recv_param = (struct hab_recv *)data; + if (!recv_param->data) { + ret = -EINVAL; + break; + } + + msg = hab_vchan_recv(ctx, recv_param->vcid, recv_param->flags); + + if (IS_ERR(msg)) { + recv_param->sizebytes = 0; + ret = PTR_ERR(msg); + break; + } + + if (recv_param->sizebytes < msg->sizebytes) { + recv_param->sizebytes = 0; + ret = -EINVAL; + } else if (copy_to_user((void __user *)recv_param->data, + msg->data, + msg->sizebytes)) { + pr_err("copy_to_user failed: vc=%x size=%d\n", + recv_param->vcid, (int)msg->sizebytes); + recv_param->sizebytes = 0; + ret = -EFAULT; + } else { + recv_param->sizebytes = msg->sizebytes; + } + + hab_msg_free(msg); + break; + case IOCTL_HAB_VC_EXPORT: + ret = hab_mem_export(ctx, (struct hab_export *)data, 0); + break; + case IOCTL_HAB_VC_IMPORT: + ret = hab_mem_import(ctx, (struct hab_import *)data, 0); + break; + case IOCTL_HAB_VC_UNEXPORT: + ret = hab_mem_unexport(ctx, (struct hab_unexport *)data, 0); + break; + case IOCTL_HAB_VC_UNIMPORT: + ret = hab_mem_unimport(ctx, (struct hab_unimport *)data, 0); + break; + default: + ret = -ENOIOCTLCMD; + } + + if (ret == 0 && _IOC_SIZE(cmd) && (cmd & IOC_OUT)) + if (copy_to_user((void __user *) arg, data, _IOC_SIZE(cmd))) { + pr_err("copy_to_user failed: cmd=%x\n", cmd); + ret = -EFAULT; + } + + return ret; +} + +static const struct file_operations hab_fops = { + .owner = THIS_MODULE, + .open = hab_open, + .release = hab_release, + .mmap = habmem_imp_hyp_mmap, + .unlocked_ioctl = hab_ioctl +}; + +/* + * These map sg functions are pass through because the memory backing the + * sg list is already accessible to the kernel as they come from a the + * dedicated shared vm pool + */ + +static int hab_map_sg(struct device *dev, struct scatterlist *sgl, + int nelems, enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + /* return nelems directly */ + return nelems; +} + +static void hab_unmap_sg(struct device *dev, + struct scatterlist *sgl, int nelems, + enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + /*Do nothing */ +} + +static const struct dma_map_ops hab_dma_ops = { + .map_sg = hab_map_sg, + .unmap_sg = hab_unmap_sg, +}; + +static int __init hab_init(void) +{ + int result; + int i; + dev_t dev; + struct hab_device *device; + + result = alloc_chrdev_region(&hab_driver.major, 0, 1, "hab"); + + if (result < 0) { + pr_err("alloc_chrdev_region failed: %d\n", result); + return result; + } + + cdev_init(&hab_driver.cdev, &hab_fops); + hab_driver.cdev.owner = THIS_MODULE; + hab_driver.cdev.ops = &hab_fops; + dev = MKDEV(MAJOR(hab_driver.major), 0); + + result = cdev_add(&hab_driver.cdev, dev, 1); + + if (result < 0) { + unregister_chrdev_region(dev, 1); + pr_err("cdev_add failed: %d\n", result); + return result; + } + + hab_driver.class = class_create(THIS_MODULE, "hab"); + + if (IS_ERR(hab_driver.class)) { + result = PTR_ERR(hab_driver.class); + pr_err("class_create failed: %d\n", result); + goto err; + } + + hab_driver.dev = device_create(hab_driver.class, NULL, + dev, &hab_driver, "hab"); + + if (IS_ERR(hab_driver.dev)) { + result = PTR_ERR(hab_driver.dev); + pr_err("device_create failed: %d\n", result); + goto err; + } + + for (i = 0; i < hab_driver.ndevices; i++) { + device = &hab_driver.devp[i]; + init_waitqueue_head(&device->openq); + } + + hab_hypervisor_register(); + + hab_driver.kctx = hab_ctx_alloc(1); + if (!hab_driver.kctx) { + pr_err("hab_ctx_alloc failed"); + result = -ENOMEM; + hab_hypervisor_unregister(); + goto err; + } + + set_dma_ops(hab_driver.dev, &hab_dma_ops); + + return result; + +err: + if (!IS_ERR_OR_NULL(hab_driver.dev)) + device_destroy(hab_driver.class, dev); + if (!IS_ERR_OR_NULL(hab_driver.class)) + class_destroy(hab_driver.class); + cdev_del(&hab_driver.cdev); + unregister_chrdev_region(dev, 1); + + return result; +} + +static void __exit hab_exit(void) +{ + dev_t dev; + + hab_hypervisor_unregister(); + hab_ctx_put(hab_driver.kctx); + dev = MKDEV(MAJOR(hab_driver.major), 0); + device_destroy(hab_driver.class, dev); + class_destroy(hab_driver.class); + cdev_del(&hab_driver.cdev); + unregister_chrdev_region(dev, 1); +} + +subsys_initcall(hab_init); +module_exit(hab_exit); + +MODULE_DESCRIPTION("Hypervisor abstraction layer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/hab/hab.h b/drivers/soc/qcom/hab/hab.h new file mode 100644 index 000000000000..805e5b4a7008 --- /dev/null +++ b/drivers/soc/qcom/hab/hab.h @@ -0,0 +1,415 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __HAB_H +#define __HAB_H + +#define pr_fmt(fmt) "hab: " fmt + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum hab_payload_type { + HAB_PAYLOAD_TYPE_MSG = 0x0, + HAB_PAYLOAD_TYPE_INIT, + HAB_PAYLOAD_TYPE_INIT_ACK, + HAB_PAYLOAD_TYPE_ACK, + HAB_PAYLOAD_TYPE_EXPORT, + HAB_PAYLOAD_TYPE_EXPORT_ACK, + HAB_PAYLOAD_TYPE_PROFILE, + HAB_PAYLOAD_TYPE_CLOSE, +}; +#define LOOPBACK_DOM 0xFF + +/* + * Tuning required. If there are multiple clients, the aging of previous + * "request" might be discarded + */ +#define Q_AGE_THRESHOLD 1000000 + +/* match the name to dtsi if for real HYP framework */ +#define DEVICE_AUD1_NAME "hab_aud1" +#define DEVICE_AUD2_NAME "hab_aud2" +#define DEVICE_AUD3_NAME "hab_aud3" +#define DEVICE_AUD4_NAME "hab_aud4" +#define DEVICE_CAM_NAME "hab_cam" +#define DEVICE_DISP1_NAME "hab_disp1" +#define DEVICE_DISP2_NAME "hab_disp2" +#define DEVICE_DISP3_NAME "hab_disp3" +#define DEVICE_DISP4_NAME "hab_disp4" +#define DEVICE_DISP5_NAME "hab_disp5" +#define DEVICE_GFX_NAME "hab_ogles" +#define DEVICE_VID_NAME "hab_vid" +#define DEVICE_MISC_NAME "hab_misc" +#define DEVICE_QCPE1_NAME "hab_qcpe_vm1" +#define DEVICE_QCPE2_NAME "hab_qcpe_vm2" +#define DEVICE_QCPE3_NAME "hab_qcpe_vm3" +#define DEVICE_QCPE4_NAME "hab_qcpe_vm4" + +/* "Size" of the HAB_HEADER_ID and HAB_VCID_ID must match */ +#define HAB_HEADER_SIZE_SHIFT 0 +#define HAB_HEADER_TYPE_SHIFT 16 +#define HAB_HEADER_ID_SHIFT 24 +#define HAB_HEADER_SIZE_MASK 0x0000FFFF +#define HAB_HEADER_TYPE_MASK 0x00FF0000 +#define HAB_HEADER_ID_MASK 0xFF000000 +#define HAB_HEADER_INITIALIZER {0} + +#define HAB_MMID_GET_MAJOR(mmid) (mmid & 0xFFFF) +#define HAB_MMID_GET_MINOR(mmid) ((mmid>>16) & 0xFF) + +#define HAB_VCID_ID_SHIFT 0 +#define HAB_VCID_DOMID_SHIFT 8 +#define HAB_VCID_MMID_SHIFT 16 +#define HAB_VCID_ID_MASK 0x000000FF +#define HAB_VCID_DOMID_MASK 0x0000FF00 +#define HAB_VCID_MMID_MASK 0xFFFF0000 +#define HAB_VCID_GET_ID(vcid) \ + (((vcid) & HAB_VCID_ID_MASK) >> HAB_VCID_ID_SHIFT) + +#define HAB_HEADER_SET_SIZE(header, size) \ + ((header).info = (((header).info) & (~HAB_HEADER_SIZE_MASK)) | \ + (((size) << HAB_HEADER_SIZE_SHIFT) & HAB_HEADER_SIZE_MASK)) + +#define HAB_HEADER_SET_TYPE(header, type) \ + ((header).info = (((header).info) & (~HAB_HEADER_TYPE_MASK)) | \ + (((type) << HAB_HEADER_TYPE_SHIFT) & HAB_HEADER_TYPE_MASK)) + +#define HAB_HEADER_SET_ID(header, id) \ + ((header).info = (((header).info) & (~HAB_HEADER_ID_MASK)) | \ + ((HAB_VCID_GET_ID(id) << HAB_HEADER_ID_SHIFT) \ + & HAB_HEADER_ID_MASK)) + +#define HAB_HEADER_GET_SIZE(header) \ + ((((header).info) & HAB_HEADER_SIZE_MASK) >> HAB_HEADER_SIZE_SHIFT) + +#define HAB_HEADER_GET_TYPE(header) \ + ((((header).info) & HAB_HEADER_TYPE_MASK) >> HAB_HEADER_TYPE_SHIFT) + +#define HAB_HEADER_GET_ID(header) \ + (((((header).info) & HAB_HEADER_ID_MASK) >> \ + (HAB_HEADER_ID_SHIFT - HAB_VCID_ID_SHIFT)) & HAB_VCID_ID_MASK) + +struct hab_header { + uint32_t info; +}; + +struct physical_channel { + struct kref refcount; + struct hab_device *habdev; + struct list_head node; + struct idr vchan_idr; + spinlock_t vid_lock; + + struct idr expid_idr; + spinlock_t expid_lock; + + void *hyp_data; + int dom_id; + int closed; + + spinlock_t rxbuf_lock; +}; + +struct hab_open_send_data { + int vchan_id; + int sub_id; + int open_id; +}; + +struct hab_open_request { + int type; + struct physical_channel *pchan; + int vchan_id; + int sub_id; + int open_id; +}; + +struct hab_open_node { + struct hab_open_request request; + struct list_head node; + int age; +}; + +struct hab_export_ack { + uint32_t export_id; + int32_t vcid_local; + int32_t vcid_remote; +}; + +struct hab_export_ack_recvd { + struct hab_export_ack ack; + struct list_head node; + int age; +}; + +struct hab_message { + size_t sizebytes; + struct list_head node; + uint32_t data[]; +}; + +struct hab_device { + const char *name; + unsigned int id; + struct list_head pchannels; + struct mutex pchan_lock; + struct list_head openq_list; + spinlock_t openlock; + wait_queue_head_t openq; +}; + +struct uhab_context { + struct kref refcount; + struct list_head vchannels; + + struct list_head exp_whse; + uint32_t export_total; + + wait_queue_head_t exp_wq; + struct list_head exp_rxq; + rwlock_t exp_lock; + spinlock_t expq_lock; + + struct list_head imp_whse; + spinlock_t imp_lock; + uint32_t import_total; + + void *import_ctx; + + rwlock_t ctx_lock; + int closing; + int kernel; +}; + +struct hab_driver { + struct device *dev; + struct cdev cdev; + dev_t major; + struct class *class; + int irq; + + int ndevices; + struct hab_device *devp; + struct uhab_context *kctx; + int b_server_dom; + int loopback_num; + int b_loopback; +}; + +struct virtual_channel { + struct work_struct work; + /* + * refcount is used to track the references from hab core to the virtual + * channel such as references from physical channels, + * i.e. references from the "other" side + */ + struct kref refcount; + /* + * usagecnt is used to track the clients who are using this virtual + * channel such as local clients, client sowftware etc, + * i.e. references from "this" side + */ + struct kref usagecnt; + struct physical_channel *pchan; + struct uhab_context *ctx; + struct list_head node; + struct list_head rx_list; + wait_queue_head_t rx_queue; + spinlock_t rx_lock; + int id; + int otherend_id; + int otherend_closed; +}; + +/* + * Struct shared between local and remote, contents are composed by exporter, + * the importer only writes to pdata and local (exporter) domID + */ +struct export_desc { + uint32_t export_id; + int readonly; + uint64_t import_index; + + struct virtual_channel *vchan; + + int32_t vcid_local; + int32_t vcid_remote; + int domid_local; + int domid_remote; + + struct list_head node; + void *kva; + int payload_count; + unsigned char payload[1]; +}; + +int hab_vchan_open(struct uhab_context *ctx, + unsigned int mmid, int32_t *vcid, uint32_t flags); +void hab_vchan_close(struct uhab_context *ctx, + int32_t vcid); +long hab_vchan_send(struct uhab_context *ctx, + int vcid, + size_t sizebytes, + void *data, + unsigned int flags); +struct hab_message *hab_vchan_recv(struct uhab_context *ctx, + int vcid, + unsigned int flags); +void hab_vchan_stop(struct virtual_channel *vchan); +void hab_vchan_stop_notify(struct virtual_channel *vchan); + +int hab_mem_export(struct uhab_context *ctx, + struct hab_export *param, int kernel); +int hab_mem_import(struct uhab_context *ctx, + struct hab_import *param, int kernel); +int hab_mem_unexport(struct uhab_context *ctx, + struct hab_unexport *param, int kernel); +int hab_mem_unimport(struct uhab_context *ctx, + struct hab_unimport *param, int kernel); + +void habmem_remove_export(struct export_desc *exp); + +/* memory hypervisor framework plugin I/F */ +void *habmm_hyp_allocate_grantable(int page_count, + uint32_t *sizebytes); + +int habmem_hyp_grant_user(unsigned long address, + int page_count, + int flags, + int remotedom, + void *ppdata); + +int habmem_hyp_grant(unsigned long address, + int page_count, + int flags, + int remotedom, + void *ppdata); + +int habmem_hyp_revoke(void *expdata, uint32_t count); + +void *habmem_imp_hyp_open(void); +void habmem_imp_hyp_close(void *priv, int kernel); + +long habmem_imp_hyp_map(void *priv, void *impdata, uint32_t count, + uint32_t remotedom, + uint64_t *index, + void **pkva, + int kernel, + uint32_t userflags); + +long habmm_imp_hyp_unmap(void *priv, uint64_t index, + uint32_t count, + int kernel); + +int habmem_imp_hyp_mmap(struct file *flip, struct vm_area_struct *vma); + + + +void hab_msg_free(struct hab_message *message); +struct hab_message *hab_msg_dequeue(struct virtual_channel *vchan, + int wait_flag); + +void hab_msg_recv(struct physical_channel *pchan, + struct hab_header *header); + +void hab_open_request_init(struct hab_open_request *request, + int type, + struct physical_channel *pchan, + int vchan_id, + int sub_id, + int open_id); +int hab_open_request_send(struct hab_open_request *request); +int hab_open_request_add(struct physical_channel *pchan, + struct hab_header *header); +void hab_open_request_free(struct hab_open_request *request); +int hab_open_listen(struct uhab_context *ctx, + struct hab_device *dev, + struct hab_open_request *listen, + struct hab_open_request **recv_request, + int ms_timeout); + +struct virtual_channel *hab_vchan_alloc(struct uhab_context *ctx, + struct physical_channel *pchan); +struct virtual_channel *hab_vchan_get(struct physical_channel *pchan, + uint32_t vchan_id); +void hab_vchan_put(struct virtual_channel *vchan); + +struct virtual_channel *hab_get_vchan_fromvcid(int32_t vcid, + struct uhab_context *ctx); +struct physical_channel *hab_pchan_alloc(struct hab_device *habdev, + int otherend_id); +struct physical_channel *hab_pchan_find_domid(struct hab_device *dev, + int dom_id); +int hab_vchan_find_domid(struct virtual_channel *vchan); + +void hab_pchan_get(struct physical_channel *pchan); +void hab_pchan_put(struct physical_channel *pchan); + +struct uhab_context *hab_ctx_alloc(int kernel); + +void hab_ctx_free(struct kref *ref); + +static inline void hab_ctx_get(struct uhab_context *ctx) +{ + if (ctx) + kref_get(&ctx->refcount); +} + +static inline void hab_ctx_put(struct uhab_context *ctx) +{ + if (ctx) + kref_put(&ctx->refcount, hab_ctx_free); +} + +void hab_send_close_msg(struct virtual_channel *vchan); +int hab_hypervisor_register(void); +void hab_hypervisor_unregister(void); + +int physical_channel_read(struct physical_channel *pchan, + void *payload, + size_t read_size); + +int physical_channel_send(struct physical_channel *pchan, + struct hab_header *header, + void *payload); + +void physical_channel_rx_dispatch(unsigned long physical_channel); + +int loopback_pchan_create(char *dev_name); + +bool hab_is_loopback(void); + +/* Global singleton HAB instance */ +extern struct hab_driver hab_driver; + +#endif /* __HAB_H */ diff --git a/drivers/soc/qcom/hab/hab_grantable.h b/drivers/soc/qcom/hab/hab_grantable.h new file mode 100644 index 000000000000..8a3f9721a89a --- /dev/null +++ b/drivers/soc/qcom/hab/hab_grantable.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __HAB_GRANTABLE_H +#define __HAB_GRANTABLE_H + +/* Grantable should be common between exporter and importer */ +struct grantable { + unsigned long pfn; +}; + +struct compressed_pfns { + unsigned long first_pfn; + int nregions; + struct region { + int size; + int space; + } region[]; +}; +#endif /* __HAB_GRANTABLE_H */ diff --git a/drivers/soc/qcom/hab/hab_mem_linux.c b/drivers/soc/qcom/hab/hab_mem_linux.c new file mode 100644 index 000000000000..ab4b9d0885cb --- /dev/null +++ b/drivers/soc/qcom/hab/hab_mem_linux.c @@ -0,0 +1,451 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include "hab.h" +#include +#include +#include "hab_grantable.h" + + +struct pages_list { + struct list_head list; + struct page **pages; + long npages; + uint64_t index; /* for mmap first call */ + int kernel; + void *kva; + void *uva; + int refcntk; + int refcntu; + uint32_t userflags; + struct file *filp_owner; + struct file *filp_mapper; +}; + +struct importer_context { + int cnt; /* pages allocated for local file */ + struct list_head imp_list; + struct file *filp; +}; + +void *habmm_hyp_allocate_grantable(int page_count, + uint32_t *sizebytes) +{ + if (!sizebytes || !page_count) + return NULL; + + *sizebytes = page_count * sizeof(struct grantable); + return vmalloc(*sizebytes); +} + +static int match_file(const void *p, struct file *file, unsigned int fd) +{ + /* + * We must return fd + 1 because iterate_fd stops searching on + * non-zero return, but 0 is a valid fd. + */ + return (p == file) ? (fd + 1) : 0; +} + + +static int habmem_get_dma_pages(unsigned long address, + int page_count, + struct page **pages) +{ + struct vm_area_struct *vma; + struct dma_buf *dmabuf = NULL; + unsigned long offset; + unsigned long page_offset; + struct scatterlist *s; + struct sg_table *sg_table = NULL; + struct dma_buf_attachment *attach = NULL; + struct page *page; + int i, j, rc = 0; + int fd; + + vma = find_vma(current->mm, address); + if (!vma || !vma->vm_file) + goto err; + + /* Look for the fd that matches this the vma file */ + fd = iterate_fd(current->files, 0, match_file, vma->vm_file); + if (fd == 0) { + pr_err("iterate_fd failed\n"); + goto err; + } + + offset = address - vma->vm_start; + page_offset = offset/PAGE_SIZE; + + dmabuf = dma_buf_get(fd - 1); + + attach = dma_buf_attach(dmabuf, hab_driver.dev); + if (IS_ERR_OR_NULL(attach)) { + pr_err("dma_buf_attach failed\n"); + goto err; + } + + sg_table = dma_buf_map_attachment(attach, DMA_TO_DEVICE); + + if (IS_ERR_OR_NULL(sg_table)) { + pr_err("dma_buf_map_attachment failed\n"); + goto err; + } + + for_each_sg(sg_table->sgl, s, sg_table->nents, i) { + page = sg_page(s); + + for (j = page_offset; j < (s->length >> PAGE_SHIFT); j++) { + pages[rc] = nth_page(page, j); + rc++; + if (rc >= page_count) + break; + } + if (rc >= page_count) + break; + + if (page_offset > (s->length >> PAGE_SHIFT)) { + /* carry-over the remaining offset to next s list */ + page_offset = page_offset-(s->length >> PAGE_SHIFT); + } else { + /* + * the page_offset is within this s list + * there is no more offset for the next s list + */ + page_offset = 0; + } + + } + +err: + if (!IS_ERR_OR_NULL(sg_table)) + dma_buf_unmap_attachment(attach, sg_table, DMA_TO_DEVICE); + if (!IS_ERR_OR_NULL(attach)) + dma_buf_detach(dmabuf, attach); + if (!IS_ERR_OR_NULL(dmabuf)) + dma_buf_put(dmabuf); + return rc; +} + +int habmem_hyp_grant_user(unsigned long address, + int page_count, + int flags, + int remotedom, + void *ppdata) +{ + int i, ret = 0; + struct grantable *item = (struct grantable *)ppdata; + struct page **pages; + + pages = vmalloc(page_count * sizeof(struct page *)); + if (!pages) + return -ENOMEM; + + down_read(¤t->mm->mmap_sem); + + if (HABMM_EXP_MEM_TYPE_DMA & flags) { + ret = habmem_get_dma_pages(address, + page_count, + pages); + } else { + ret = get_user_pages(current, current->mm, + address, + page_count, + 1, + 1, + pages, + NULL); + } + + if (ret > 0) { + for (i = 0; i < page_count; i++) + item[i].pfn = page_to_pfn(pages[i]); + } else { + pr_err("get %d user pages failed: %d\n", page_count, ret); + } + + vfree(pages); + up_read(¤t->mm->mmap_sem); + return ret; +} +/* + * exporter - grant & revoke + * generate shareable page list based on CPU friendly virtual "address". + * The result as an array is stored in ppdata to return to caller + * page size 4KB is assumed + */ +int habmem_hyp_grant(unsigned long address, + int page_count, + int flags, + int remotedom, + void *ppdata) +{ + int i; + struct grantable *item; + void *kva = (void *)(uintptr_t)address; + int is_vmalloc = is_vmalloc_addr(kva); + + item = (struct grantable *)ppdata; + + for (i = 0; i < page_count; i++) { + kva = (void *)(uintptr_t)(address + i*PAGE_SIZE); + if (is_vmalloc) + item[i].pfn = page_to_pfn(vmalloc_to_page(kva)); + else + item[i].pfn = page_to_pfn(virt_to_page(kva)); + } + + return 0; +} + +int habmem_hyp_revoke(void *expdata, uint32_t count) +{ + return 0; +} + +void *habmem_imp_hyp_open(void) +{ + struct importer_context *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return NULL; + + INIT_LIST_HEAD(&priv->imp_list); + + return priv; +} + +void habmem_imp_hyp_close(void *imp_ctx, int kernel) +{ + struct importer_context *priv = imp_ctx; + struct pages_list *pglist, *pglist_tmp; + + if (!priv) + return; + + list_for_each_entry_safe(pglist, pglist_tmp, &priv->imp_list, list) { + if (kernel && pglist->kva) + vunmap(pglist->kva); + + list_del(&pglist->list); + priv->cnt--; + + vfree(pglist->pages); + kfree(pglist); + } + + kfree(priv); +} + +/* + * setup pages, be ready for the following mmap call + * index is output to refer to this imported buffer described by the import data + */ +long habmem_imp_hyp_map(void *imp_ctx, + void *impdata, + uint32_t count, + uint32_t remotedom, + uint64_t *index, + void **pkva, + int kernel, + uint32_t userflags) +{ + struct page **pages; + struct compressed_pfns *pfn_table = impdata; + struct pages_list *pglist; + struct importer_context *priv = imp_ctx; + unsigned long pfn; + int i, j, k = 0; + + if (!pfn_table || !priv) + return -EINVAL; + + pages = vmalloc(count * sizeof(struct page *)); + if (!pages) + return -ENOMEM; + + pglist = kzalloc(sizeof(*pglist), GFP_KERNEL); + if (!pglist) { + vfree(pages); + return -ENOMEM; + } + + pfn = pfn_table->first_pfn; + for (i = 0; i < pfn_table->nregions; i++) { + for (j = 0; j < pfn_table->region[i].size; j++) { + pages[k] = pfn_to_page(pfn+j); + k++; + } + pfn += pfn_table->region[i].size + pfn_table->region[i].space; + } + + pglist->pages = pages; + pglist->npages = count; + pglist->kernel = kernel; + pglist->index = page_to_phys(pages[0]) >> PAGE_SHIFT; + pglist->refcntk = pglist->refcntu = 0; + pglist->userflags = userflags; + + *index = pglist->index << PAGE_SHIFT; + + if (kernel) { + pgprot_t prot = PAGE_KERNEL; + + if (!(userflags & HABMM_IMPORT_FLAGS_CACHED)) + prot = pgprot_writecombine(prot); + + pglist->kva = vmap(pglist->pages, pglist->npages, VM_MAP, prot); + if (pglist->kva == NULL) { + vfree(pages); + kfree(pglist); + pr_err("%ld pages vmap failed\n", pglist->npages); + return -ENOMEM; + } + + pglist->uva = NULL; + pglist->refcntk++; + *pkva = pglist->kva; + *index = (uint64_t)((uintptr_t)pglist->kva); + } else { + pglist->kva = NULL; + } + + list_add_tail(&pglist->list, &priv->imp_list); + priv->cnt++; + + return 0; +} + +/* the input index is PHY address shifted for uhab, and kva for khab */ +long habmm_imp_hyp_unmap(void *imp_ctx, + uint64_t index, + uint32_t count, + int kernel) +{ + struct importer_context *priv = imp_ctx; + struct pages_list *pglist; + int found = 0; + uint64_t pg_index = index >> PAGE_SHIFT; + + list_for_each_entry(pglist, &priv->imp_list, list) { + if (kernel) { + if (pglist->kva == (void *)((uintptr_t)index)) + found = 1; + } else { + if (pglist->index == pg_index) + found = 1; + } + + if (found) { + list_del(&pglist->list); + priv->cnt--; + break; + } + } + + if (!found) { + pr_err("failed to find export id on index %llx\n", index); + return -EINVAL; + } + + if (kernel) + if (pglist->kva) + vunmap(pglist->kva); + + vfree(pglist->pages); + kfree(pglist); + + return 0; +} + +static int hab_map_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct page *page; + struct pages_list *pglist; + + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + + /* PHY address */ + unsigned long fault_offset = + (unsigned long)vmf->virtual_address - vma->vm_start + offset; + unsigned long fault_index = fault_offset>>PAGE_SHIFT; + int page_idx; + + if (vma == NULL) + return VM_FAULT_SIGBUS; + + pglist = vma->vm_private_data; + + page_idx = fault_index - pglist->index; + if (page_idx < 0 || page_idx >= pglist->npages) { + pr_err("Out of page array. page_idx %d, pg cnt %ld", + page_idx, pglist->npages); + return VM_FAULT_SIGBUS; + } + + page = pglist->pages[page_idx]; + get_page(page); + vmf->page = page; + return 0; +} + +static void hab_map_open(struct vm_area_struct *vma) +{ +} + +static void hab_map_close(struct vm_area_struct *vma) +{ +} + +static const struct vm_operations_struct habmem_vm_ops = { + + .fault = hab_map_fault, + .open = hab_map_open, + .close = hab_map_close, +}; + +int habmem_imp_hyp_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct uhab_context *ctx = (struct uhab_context *) filp->private_data; + struct importer_context *imp_ctx = ctx->import_ctx; + long length = vma->vm_end - vma->vm_start; + struct pages_list *pglist; + int bfound = 0; + + list_for_each_entry(pglist, &imp_ctx->imp_list, list) { + if (pglist->index == vma->vm_pgoff) { + bfound = 1; + break; + } + } + + if (!bfound) { + pr_err("Failed to find pglist vm_pgoff: %d\n", vma->vm_pgoff); + return -EINVAL; + } + + if (length > pglist->npages * PAGE_SIZE) { + pr_err("Error vma length %ld not matching page list %ld\n", + length, pglist->npages * PAGE_SIZE); + return -EINVAL; + } + + vma->vm_ops = &habmem_vm_ops; + + vma->vm_private_data = pglist; + + if (!(pglist->userflags & HABMM_IMPORT_FLAGS_CACHED)) + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + return 0; +} diff --git a/drivers/soc/qcom/hab/hab_mimex.c b/drivers/soc/qcom/hab/hab_mimex.c new file mode 100644 index 000000000000..aaef9aa9f414 --- /dev/null +++ b/drivers/soc/qcom/hab/hab_mimex.c @@ -0,0 +1,394 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include "hab.h" +#include "hab_grantable.h" + +/* + * use physical channel to send export parcel + + * local remote + * send(export) --> IRQ store to export warehouse + * wait(export ack) <-- send(export ack) + + * the actual data consists the following 3 parts listed in order + * 1. header (uint32_t) vcid|type|size + * 2. export parcel (full struct) + * 3. full contents in export->pdata + */ + + +static int hab_export_ack_find(struct uhab_context *ctx, + struct hab_export_ack *expect_ack) +{ + int ret = 0; + struct hab_export_ack_recvd *ack_recvd; + + spin_lock_bh(&ctx->expq_lock); + + list_for_each_entry(ack_recvd, &ctx->exp_rxq, node) { + if (ack_recvd->ack.export_id == expect_ack->export_id && + ack_recvd->ack.vcid_local == expect_ack->vcid_local && + ack_recvd->ack.vcid_remote == expect_ack->vcid_remote) { + list_del(&ack_recvd->node); + kfree(ack_recvd); + ret = 1; + break; + } + ack_recvd->age++; + if (ack_recvd->age > Q_AGE_THRESHOLD) { + list_del(&ack_recvd->node); + kfree(ack_recvd); + } + } + + spin_unlock_bh(&ctx->expq_lock); + + return ret; +} + +static int hab_export_ack_wait(struct uhab_context *ctx, + struct hab_export_ack *expect_ack) +{ + int ret; + + ret = wait_event_interruptible_timeout(ctx->exp_wq, + hab_export_ack_find(ctx, expect_ack), + HZ); + if (!ret || (ret == -ERESTARTSYS)) + ret = -EAGAIN; + else if (ret > 0) + ret = 0; + return ret; +} + +/* + * Get id from free list first. if not available, new id is generated. + * Once generated it will not be erased + * assumptions: no handshake or memory map/unmap in this helper function + */ +static struct export_desc *habmem_add_export(struct virtual_channel *vchan, + int sizebytes, + uint32_t flags) +{ + struct uhab_context *ctx; + struct export_desc *exp; + + if (!vchan || !sizebytes) + return NULL; + + exp = vmalloc(sizebytes); + if (!exp) + return NULL; + + idr_preload(GFP_KERNEL); + spin_lock(&vchan->pchan->expid_lock); + exp->export_id = + idr_alloc(&vchan->pchan->expid_idr, exp, 1, 0, GFP_NOWAIT); + spin_unlock(&vchan->pchan->expid_lock); + idr_preload_end(); + + exp->readonly = flags; + exp->vchan = vchan; + exp->vcid_local = vchan->id; + exp->vcid_remote = vchan->otherend_id; + exp->domid_local = -1; /* dom id, provided on the importer */ + exp->domid_remote = vchan->pchan->dom_id; + + ctx = vchan->ctx; + write_lock(&ctx->exp_lock); + ctx->export_total++; + list_add_tail(&exp->node, &ctx->exp_whse); + write_unlock(&ctx->exp_lock); + + return exp; +} + +void habmem_remove_export(struct export_desc *exp) +{ + struct physical_channel *pchan; + struct uhab_context *ctx; + + if (!exp || !exp->vchan || !exp->vchan->ctx || !exp->vchan->pchan) + return; + + ctx = exp->vchan->ctx; + ctx->export_total--; + + pchan = exp->vchan->pchan; + + spin_lock(&pchan->expid_lock); + idr_remove(&pchan->expid_idr, exp->export_id); + spin_unlock(&pchan->expid_lock); + + vfree(exp); +} + +static int compress_pfns(void **pfns, int npages, unsigned int *data_size) +{ + int i, j = 0; + struct grantable *item = (struct grantable *)*pfns; + int region_size = 1; + struct compressed_pfns *new_table = + vmalloc(sizeof(struct compressed_pfns) + + npages * sizeof(struct region)); + + if (!new_table) + return -ENOMEM; + + new_table->first_pfn = item[0].pfn; + for (i = 1; i < npages; i++) { + if (item[i].pfn-1 == item[i-1].pfn) { + region_size++; + } else { + new_table->region[j].size = region_size; + new_table->region[j].space = item[i].pfn - + item[i-1].pfn - 1; + j++; + region_size = 1; + } + } + new_table->region[j].size = region_size; + new_table->region[j].space = 0; + new_table->nregions = j+1; + vfree(*pfns); + + *data_size = sizeof(struct compressed_pfns) + + sizeof(struct region)*new_table->nregions; + *pfns = new_table; + return 0; +} + +/* + * store the parcel to the warehouse, then send the parcel to remote side + * both exporter composed export descriptor and the grantrefids are sent + * as one msg to the importer side + */ +static int habmem_export_vchan(struct uhab_context *ctx, + struct virtual_channel *vchan, + void *pdata, + int payload_size, + int nunits, + uint32_t flags, + uint32_t *export_id) { + int ret; + struct export_desc *exp; + uint32_t sizebytes = sizeof(*exp) + payload_size; + struct hab_export_ack expected_ack = {0}; + struct hab_header header = HAB_HEADER_INITIALIZER; + + exp = habmem_add_export(vchan, sizebytes, flags); + if (!exp) + return -ENOMEM; + + /* append the pdata to the export descriptor */ + exp->payload_count = nunits; + memcpy(exp->payload, pdata, payload_size); + + HAB_HEADER_SET_SIZE(header, sizebytes); + HAB_HEADER_SET_TYPE(header, HAB_PAYLOAD_TYPE_EXPORT); + HAB_HEADER_SET_ID(header, vchan->otherend_id); + ret = physical_channel_send(vchan->pchan, &header, exp); + + if (ret != 0) { + pr_err("failed to export payload to the remote %d\n", ret); + return ret; + } + + expected_ack.export_id = exp->export_id; + expected_ack.vcid_local = exp->vcid_local; + expected_ack.vcid_remote = exp->vcid_remote; + ret = hab_export_ack_wait(ctx, &expected_ack); + + *export_id = exp->export_id; + + return ret; +} + +int hab_mem_export(struct uhab_context *ctx, + struct hab_export *param, + int kernel) +{ + int ret = 0; + void *pdata_exp = NULL; + unsigned int pdata_size = 0; + uint32_t export_id = 0; + struct virtual_channel *vchan; + int page_count; + + if (!ctx || !param || param->sizebytes > HAB_MAX_EXPORT_SIZE) + return -EINVAL; + + vchan = hab_get_vchan_fromvcid(param->vcid, ctx); + if (!vchan || !vchan->pchan) { + ret = -ENODEV; + goto err; + } + + page_count = param->sizebytes/PAGE_SIZE; + pdata_exp = habmm_hyp_allocate_grantable(page_count, &pdata_size); + if (!pdata_exp) { + ret = -ENOMEM; + goto err; + } + + if (kernel) { + ret = habmem_hyp_grant((unsigned long)param->buffer, + page_count, + param->flags, + vchan->pchan->dom_id, + pdata_exp); + } else { + ret = habmem_hyp_grant_user((unsigned long)param->buffer, + page_count, + param->flags, + vchan->pchan->dom_id, + pdata_exp); + } + if (ret < 0) { + pr_err("habmem_hyp_grant failed size=%d ret=%d\n", + pdata_size, ret); + goto err; + } + + compress_pfns(&pdata_exp, page_count, &pdata_size); + + ret = habmem_export_vchan(ctx, + vchan, + pdata_exp, + pdata_size, + page_count, + param->flags, + &export_id); + + param->exportid = export_id; +err: + vfree(pdata_exp); + if (vchan) + hab_vchan_put(vchan); + return ret; +} + +int hab_mem_unexport(struct uhab_context *ctx, + struct hab_unexport *param, + int kernel) +{ + int ret = 0, found = 0; + struct export_desc *exp, *tmp; + + if (!ctx || !param) + return -EINVAL; + + write_lock(&ctx->exp_lock); + list_for_each_entry_safe(exp, tmp, &ctx->exp_whse, node) { + if ((param->exportid == exp->export_id) && + (param->vcid == exp->vcid_local)) { + list_del(&exp->node); + found = 1; + break; + } + } + write_unlock(&ctx->exp_lock); + + if (!found) + return -EINVAL; + + ret = habmem_hyp_revoke(exp->payload, exp->payload_count); + + habmem_remove_export(exp); + return ret; +} + +int hab_mem_import(struct uhab_context *ctx, + struct hab_import *param, + int kernel) +{ + int ret = 0, found = 0; + struct export_desc *exp = NULL; + + if (!ctx || !param) + return -EINVAL; + + spin_lock_bh(&ctx->imp_lock); + list_for_each_entry(exp, &ctx->imp_whse, node) { + if ((exp->export_id == param->exportid) && + (param->vcid == exp->vcid_remote)) { + found = 1; + break; + } + } + spin_unlock_bh(&ctx->imp_lock); + + if (!found) { + pr_err("Fail to get export descriptor from export id %d\n", + param->exportid); + ret = -ENODEV; + return ret; + } + + ret = habmem_imp_hyp_map(ctx->import_ctx, + exp->payload, + exp->payload_count, + exp->domid_local, + &exp->import_index, + &exp->kva, + kernel, + param->flags); + if (ret) { + pr_err("Import fail ret:%d pcnt:%d rem:%d 1st_ref:0x%X\n", + ret, exp->payload_count, + exp->domid_local, *((uint32_t *)exp->payload)); + return ret; + } + + param->index = exp->import_index; + param->kva = (uint64_t)exp->kva; + + return ret; +} + +int hab_mem_unimport(struct uhab_context *ctx, + struct hab_unimport *param, + int kernel) +{ + int ret = 0, found = 0; + struct export_desc *exp = NULL, *exp_tmp; + + if (!ctx || !param) + return -EINVAL; + + spin_lock_bh(&ctx->imp_lock); + list_for_each_entry_safe(exp, exp_tmp, &ctx->imp_whse, node) { + if ((exp->export_id == param->exportid) && + (param->vcid == exp->vcid_remote)) { + list_del(&exp->node); + ctx->import_total--; + found = 1; + break; + } + } + spin_unlock_bh(&ctx->imp_lock); + + if (!found) + ret = -EINVAL; + else { + ret = habmm_imp_hyp_unmap(ctx->import_ctx, + exp->import_index, + exp->payload_count, + kernel); + + param->kva = (uint64_t)exp->kva; + kfree(exp); + } + + return ret; +} diff --git a/drivers/soc/qcom/hab/hab_msg.c b/drivers/soc/qcom/hab/hab_msg.c new file mode 100644 index 000000000000..f08cc83fe9fc --- /dev/null +++ b/drivers/soc/qcom/hab/hab_msg.c @@ -0,0 +1,208 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include "hab.h" + +static int hab_rx_queue_empty(struct virtual_channel *vchan) +{ + int ret; + + spin_lock_bh(&vchan->rx_lock); + ret = list_empty(&vchan->rx_list); + spin_unlock_bh(&vchan->rx_lock); + return ret; +} + +static struct hab_message* +hab_msg_alloc(struct physical_channel *pchan, size_t sizebytes) +{ + struct hab_message *message; + + message = kzalloc(sizeof(*message) + sizebytes, GFP_ATOMIC); + if (!message) + return NULL; + + message->sizebytes = + physical_channel_read(pchan, message->data, sizebytes); + + return message; +} + +void hab_msg_free(struct hab_message *message) +{ + kfree(message); +} + +struct hab_message * +hab_msg_dequeue(struct virtual_channel *vchan, int wait_flag) +{ + struct hab_message *message = NULL; + int ret = 0; + + if (wait_flag) { + if (hab_rx_queue_empty(vchan)) + ret = wait_event_interruptible(vchan->rx_queue, + !hab_rx_queue_empty(vchan) || + vchan->otherend_closed); + } + + if (!ret && !vchan->otherend_closed) { + spin_lock_bh(&vchan->rx_lock); + if (!list_empty(&vchan->rx_list)) { + message = list_first_entry(&vchan->rx_list, + struct hab_message, node); + list_del(&message->node); + } + spin_unlock_bh(&vchan->rx_lock); + } + + return message; +} + +static void hab_msg_queue(struct virtual_channel *vchan, + struct hab_message *message) +{ + spin_lock_bh(&vchan->rx_lock); + list_add_tail(&message->node, &vchan->rx_list); + spin_unlock_bh(&vchan->rx_lock); + + wake_up_interruptible(&vchan->rx_queue); +} + +static int hab_export_enqueue(struct virtual_channel *vchan, + struct export_desc *exp) +{ + struct uhab_context *ctx = vchan->ctx; + + spin_lock_bh(&ctx->imp_lock); + list_add_tail(&exp->node, &ctx->imp_whse); + ctx->import_total++; + spin_unlock_bh(&ctx->imp_lock); + + return 0; +} + +static int hab_send_export_ack(struct physical_channel *pchan, + struct export_desc *exp) +{ + struct hab_export_ack exp_ack = { + .export_id = exp->export_id, + .vcid_local = exp->vcid_local, + .vcid_remote = exp->vcid_remote + }; + struct hab_header header = HAB_HEADER_INITIALIZER; + + HAB_HEADER_SET_SIZE(header, sizeof(exp_ack)); + HAB_HEADER_SET_TYPE(header, HAB_PAYLOAD_TYPE_EXPORT_ACK); + HAB_HEADER_SET_ID(header, exp->vcid_local); + return physical_channel_send(pchan, &header, &exp_ack); +} + +static int hab_receive_create_export_ack(struct physical_channel *pchan, + struct uhab_context *ctx) +{ + struct hab_export_ack_recvd *ack_recvd = + kzalloc(sizeof(*ack_recvd), GFP_ATOMIC); + + if (!ack_recvd) + return -ENOMEM; + + if (physical_channel_read(pchan, + &ack_recvd->ack, + sizeof(ack_recvd->ack)) != sizeof(ack_recvd->ack)) + return -EIO; + + spin_lock_bh(&ctx->expq_lock); + list_add_tail(&ack_recvd->node, &ctx->exp_rxq); + spin_unlock_bh(&ctx->expq_lock); + + return 0; +} + +void hab_msg_recv(struct physical_channel *pchan, + struct hab_header *header) +{ + int ret; + struct hab_message *message; + struct hab_device *dev = pchan->habdev; + size_t sizebytes = HAB_HEADER_GET_SIZE(*header); + uint32_t payload_type = HAB_HEADER_GET_TYPE(*header); + uint32_t vchan_id = HAB_HEADER_GET_ID(*header); + struct virtual_channel *vchan = NULL; + struct export_desc *exp_desc; + + /* get the local virtual channel if it isn't an open message */ + if (payload_type != HAB_PAYLOAD_TYPE_INIT && + payload_type != HAB_PAYLOAD_TYPE_INIT_ACK && + payload_type != HAB_PAYLOAD_TYPE_ACK) { + vchan = hab_vchan_get(pchan, vchan_id); + if (!vchan) { + return; + } else if (vchan->otherend_closed) { + hab_vchan_put(vchan); + return; + } + } + + switch (payload_type) { + case HAB_PAYLOAD_TYPE_MSG: + message = hab_msg_alloc(pchan, sizebytes); + if (!message) + break; + + hab_msg_queue(vchan, message); + break; + + case HAB_PAYLOAD_TYPE_INIT: + case HAB_PAYLOAD_TYPE_INIT_ACK: + case HAB_PAYLOAD_TYPE_ACK: + ret = hab_open_request_add(pchan, header); + if (ret) + break; + wake_up_interruptible(&dev->openq); + break; + + case HAB_PAYLOAD_TYPE_EXPORT: + exp_desc = kzalloc(sizebytes, GFP_ATOMIC); + if (!exp_desc) + break; + + if (physical_channel_read(pchan, exp_desc, sizebytes) != + sizebytes) { + vfree(exp_desc); + break; + } + + exp_desc->domid_local = pchan->dom_id; + + hab_export_enqueue(vchan, exp_desc); + hab_send_export_ack(pchan, exp_desc); + break; + + case HAB_PAYLOAD_TYPE_EXPORT_ACK: + ret = hab_receive_create_export_ack(pchan, vchan->ctx); + if (ret) + break; + + wake_up_interruptible(&vchan->ctx->exp_wq); + break; + + case HAB_PAYLOAD_TYPE_CLOSE: + hab_vchan_stop(vchan); + break; + + default: + break; + } + if (vchan) + hab_vchan_put(vchan); +} diff --git a/drivers/soc/qcom/hab/hab_open.c b/drivers/soc/qcom/hab/hab_open.c new file mode 100644 index 000000000000..66468aa43afd --- /dev/null +++ b/drivers/soc/qcom/hab/hab_open.c @@ -0,0 +1,154 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include "hab.h" + +void hab_open_request_init(struct hab_open_request *request, + int type, + struct physical_channel *pchan, + int vchan_id, + int sub_id, + int open_id) +{ + request->type = type; + request->pchan = pchan; + request->vchan_id = vchan_id; + request->sub_id = sub_id; + request->open_id = open_id; +} + +int hab_open_request_send(struct hab_open_request *request) +{ + struct hab_header header = HAB_HEADER_INITIALIZER; + struct hab_open_send_data data; + + HAB_HEADER_SET_SIZE(header, sizeof(struct hab_open_send_data)); + HAB_HEADER_SET_TYPE(header, request->type); + + data.vchan_id = request->vchan_id; + data.open_id = request->open_id; + data.sub_id = request->sub_id; + + return physical_channel_send(request->pchan, &header, &data); +} + +int hab_open_request_add(struct physical_channel *pchan, + struct hab_header *header) +{ + struct hab_open_node *node; + struct hab_device *dev = pchan->habdev; + struct hab_open_send_data data; + struct hab_open_request *request; + + node = kzalloc(sizeof(*node), GFP_ATOMIC); + if (!node) + return -ENOMEM; + + if (physical_channel_read(pchan, &data, HAB_HEADER_GET_SIZE(*header)) != + HAB_HEADER_GET_SIZE(*header)) + return -EIO; + + request = &node->request; + request->type = HAB_HEADER_GET_TYPE(*header); + request->pchan = pchan; + request->vchan_id = data.vchan_id; + request->sub_id = data.sub_id; + request->open_id = data.open_id; + node->age = 0; + hab_pchan_get(pchan); + + spin_lock_bh(&dev->openlock); + list_add_tail(&node->node, &dev->openq_list); + spin_unlock_bh(&dev->openlock); + + return 0; +} + +static int hab_open_request_find(struct uhab_context *ctx, + struct hab_device *dev, + struct hab_open_request *listen, + struct hab_open_request **recv_request) +{ + struct hab_open_node *node, *tmp; + struct hab_open_request *request; + int ret = 0; + + if (ctx->closing || + (listen->pchan && listen->pchan->closed)) { + *recv_request = NULL; + return 1; + } + + spin_lock_bh(&dev->openlock); + if (list_empty(&dev->openq_list)) + goto done; + + list_for_each_entry_safe(node, tmp, &dev->openq_list, node) { + request = (struct hab_open_request *)node; + if (request->type == listen->type && + (request->sub_id == listen->sub_id) && + (!listen->open_id || + request->open_id == listen->open_id) && + (!listen->pchan || + request->pchan == listen->pchan)) { + list_del(&node->node); + *recv_request = request; + ret = 1; + break; + } + node->age++; + if (node->age > Q_AGE_THRESHOLD) { + list_del(&node->node); + hab_open_request_free(request); + } + } + +done: + spin_unlock_bh(&dev->openlock); + return ret; +} + +void hab_open_request_free(struct hab_open_request *request) +{ + if (request) { + hab_pchan_put(request->pchan); + kfree(request); + } +} + +int hab_open_listen(struct uhab_context *ctx, + struct hab_device *dev, + struct hab_open_request *listen, + struct hab_open_request **recv_request, + int ms_timeout) +{ + int ret = 0; + + if (!ctx || !listen || !recv_request) + return -EINVAL; + + *recv_request = NULL; + if (ms_timeout > 0) { + ret = wait_event_interruptible_timeout(dev->openq, + hab_open_request_find(ctx, dev, listen, recv_request), + ms_timeout); + if (!ret || (-ERESTARTSYS == ret)) + ret = -EAGAIN; + else if (ret > 0) + ret = 0; + } else { + ret = wait_event_interruptible(dev->openq, + hab_open_request_find(ctx, dev, listen, recv_request)); + } + + return ret; +} diff --git a/drivers/soc/qcom/hab/hab_pchan.c b/drivers/soc/qcom/hab/hab_pchan.c new file mode 100644 index 000000000000..1ad727f7d90f --- /dev/null +++ b/drivers/soc/qcom/hab/hab_pchan.c @@ -0,0 +1,86 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include "hab.h" + +struct physical_channel * +hab_pchan_alloc(struct hab_device *habdev, int otherend_id) +{ + struct physical_channel *pchan = kzalloc(sizeof(*pchan), GFP_KERNEL); + + if (!pchan) + return NULL; + + idr_init(&pchan->vchan_idr); + spin_lock_init(&pchan->vid_lock); + idr_init(&pchan->expid_idr); + spin_lock_init(&pchan->expid_lock); + kref_init(&pchan->refcount); + + pchan->habdev = habdev; + pchan->dom_id = otherend_id; + pchan->closed = 1; + pchan->hyp_data = NULL; + + spin_lock_init(&pchan->rxbuf_lock); + + mutex_lock(&habdev->pchan_lock); + list_add_tail(&pchan->node, &habdev->pchannels); + mutex_unlock(&habdev->pchan_lock); + + return pchan; +} + +static void hab_pchan_free(struct kref *ref) +{ + struct physical_channel *pchan = + container_of(ref, struct physical_channel, refcount); + + mutex_lock(&pchan->habdev->pchan_lock); + list_del(&pchan->node); + mutex_unlock(&pchan->habdev->pchan_lock); + kfree(pchan->hyp_data); + kfree(pchan); +} + +struct physical_channel * +hab_pchan_find_domid(struct hab_device *dev, int dom_id) +{ + struct physical_channel *pchan; + + mutex_lock(&dev->pchan_lock); + list_for_each_entry(pchan, &dev->pchannels, node) + if (pchan->dom_id == dom_id) + break; + + if (pchan->dom_id != dom_id) + pchan = NULL; + + if (pchan && !kref_get_unless_zero(&pchan->refcount)) + pchan = NULL; + + mutex_unlock(&dev->pchan_lock); + + return pchan; +} + +void hab_pchan_get(struct physical_channel *pchan) +{ + if (pchan) + kref_get(&pchan->refcount); +} + +void hab_pchan_put(struct physical_channel *pchan) +{ + if (pchan) + kref_put(&pchan->refcount, hab_pchan_free); +} diff --git a/drivers/soc/qcom/hab/hab_pipe.c b/drivers/soc/qcom/hab/hab_pipe.c new file mode 100644 index 000000000000..e757b6cb1f01 --- /dev/null +++ b/drivers/soc/qcom/hab/hab_pipe.c @@ -0,0 +1,131 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include "hab.h" +#include "hab_pipe.h" + +size_t hab_pipe_calc_required_bytes(uint32_t shared_buf_size) +{ + return sizeof(struct hab_pipe) + + (2 * (sizeof(struct hab_shared_buf) + shared_buf_size)); +} + +struct hab_pipe_endpoint *hab_pipe_init(struct hab_pipe *pipe, + uint32_t shared_buf_size, int top) +{ + struct hab_pipe_endpoint *ep = NULL; + struct hab_shared_buf *buf_a; + struct hab_shared_buf *buf_b; + + if (!pipe) + return NULL; + + buf_a = (struct hab_shared_buf *) pipe->buf_base; + buf_b = (struct hab_shared_buf *) (pipe->buf_base + + sizeof(struct hab_shared_buf) + shared_buf_size); + + if (top) { + ep = &pipe->top; + memset(ep, 0, sizeof(*ep)); + ep->tx_info.sh_buf = buf_a; + ep->rx_info.sh_buf = buf_b; + } else { + ep = &pipe->bottom; + memset(ep, 0, sizeof(*ep)); + ep->tx_info.sh_buf = buf_b; + ep->rx_info.sh_buf = buf_a; + memset(ep->tx_info.sh_buf, 0, sizeof(struct hab_shared_buf)); + memset(ep->rx_info.sh_buf, 0, sizeof(struct hab_shared_buf)); + ep->tx_info.sh_buf->size = shared_buf_size; + ep->rx_info.sh_buf->size = shared_buf_size; + + pipe->buf_a = buf_a; + pipe->buf_b = buf_b; + pipe->total_size = + hab_pipe_calc_required_bytes(shared_buf_size); + } + return ep; +} + +uint32_t hab_pipe_write(struct hab_pipe_endpoint *ep, + unsigned char *p, uint32_t num_bytes) +{ + struct hab_shared_buf *sh_buf = ep->tx_info.sh_buf; + uint32_t space = + (sh_buf->size - (ep->tx_info.wr_count - sh_buf->rd_count)); + uint32_t count1, count2; + + if (!p || num_bytes > space || num_bytes == 0) + return 0; + + count1 = (num_bytes <= (sh_buf->size - ep->tx_info.index)) ? num_bytes : + (sh_buf->size - ep->tx_info.index); + count2 = num_bytes - count1; + + if (count1 > 0) { + memcpy(&sh_buf->data[ep->tx_info.index], p, count1); + ep->tx_info.wr_count += count1; + ep->tx_info.index += count1; + if (ep->tx_info.index >= sh_buf->size) + ep->tx_info.index = 0; + } + if (count2 > 0) {/* handle buffer wrapping */ + memcpy(&sh_buf->data[ep->tx_info.index], p + count1, count2); + ep->tx_info.wr_count += count2; + ep->tx_info.index += count2; + if (ep->tx_info.index >= sh_buf->size) + ep->tx_info.index = 0; + } + return num_bytes; +} + +/* Updates the write index which is shared with the other VM */ +void hab_pipe_write_commit(struct hab_pipe_endpoint *ep) +{ + struct hab_shared_buf *sh_buf = ep->tx_info.sh_buf; + + mb(); /* Must commit data before incrementing count */ + sh_buf->wr_count = ep->tx_info.wr_count; +} + +uint32_t hab_pipe_read(struct hab_pipe_endpoint *ep, + unsigned char *p, uint32_t size) +{ + struct hab_shared_buf *sh_buf = ep->rx_info.sh_buf; + uint32_t avail = sh_buf->wr_count - sh_buf->rd_count; + uint32_t count1, count2, to_read; + + if (!p || avail == 0 || size == 0) + return 0; + + to_read = (avail < size) ? avail : size; + count1 = (to_read <= (sh_buf->size - ep->rx_info.index)) ? to_read : + (sh_buf->size - ep->rx_info.index); + count2 = to_read - count1; + + if (count1 > 0) { + memcpy(p, &sh_buf->data[ep->rx_info.index], count1); + ep->rx_info.index += count1; + if (ep->rx_info.index >= sh_buf->size) + ep->rx_info.index = 0; + mb(); /*Must commit data before incremeting count*/ + sh_buf->rd_count += count1; + } + if (count2 > 0) { /* handle buffer wrapping */ + memcpy(p + count1, &sh_buf->data[ep->rx_info.index], count2); + ep->rx_info.index += count2; + mb(); /*Must commit data before incremeting count*/ + sh_buf->rd_count += count2; + } + + return to_read; +} diff --git a/drivers/soc/qcom/hab/hab_pipe.h b/drivers/soc/qcom/hab/hab_pipe.h new file mode 100644 index 000000000000..6ffdbd133868 --- /dev/null +++ b/drivers/soc/qcom/hab/hab_pipe.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef HAB_PIPE_H +#define HAB_PIPE_H + +struct hab_shared_buf { + uint32_t rd_count; + uint32_t wr_count; + uint32_t size; + unsigned char data[]; +}; + +struct hab_pipe_endpoint { + struct { + uint32_t wr_count; + uint32_t index; + struct hab_shared_buf *sh_buf; + } tx_info; + struct { + uint32_t index; + struct hab_shared_buf *sh_buf; + } rx_info; +}; + +struct hab_pipe { + struct hab_pipe_endpoint top; + struct hab_pipe_endpoint bottom; + + /* For debugging only */ + struct hab_shared_buf *buf_a; /* top TX, bottom RX */ + struct hab_shared_buf *buf_b; /* top RX, bottom TX */ + size_t total_size; + + unsigned char buf_base[]; +}; + +size_t hab_pipe_calc_required_bytes(uint32_t shared_buf_size); + +struct hab_pipe_endpoint *hab_pipe_init(struct hab_pipe *pipe, + uint32_t shared_buf_size, int top); + +uint32_t hab_pipe_write(struct hab_pipe_endpoint *ep, + unsigned char *p, uint32_t num_bytes); + +void hab_pipe_write_commit(struct hab_pipe_endpoint *ep); + +uint32_t hab_pipe_read(struct hab_pipe_endpoint *ep, + unsigned char *p, uint32_t size); + +#endif /* HAB_PIPE_H */ diff --git a/drivers/soc/qcom/hab/hab_qvm.c b/drivers/soc/qcom/hab/hab_qvm.c new file mode 100644 index 000000000000..a37590f23c61 --- /dev/null +++ b/drivers/soc/qcom/hab/hab_qvm.c @@ -0,0 +1,251 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include "hab.h" +#include "hab_qvm.h" + +#include +#include +#include +#include + +#include +#include + +#define DEFAULT_HAB_SHMEM_IRQ 7 + +#define SHMEM_PHYSICAL_ADDR 0x1c050000 + +static irqreturn_t shm_irq_handler(int irq, void *_pchan) +{ + irqreturn_t rc = IRQ_NONE; + struct physical_channel *pchan = _pchan; + struct qvm_channel *dev = + (struct qvm_channel *) (pchan ? pchan->hyp_data : NULL); + + if (dev && dev->guest_ctrl) { + int status = dev->guest_ctrl->status; + + if (status & dev->idx) { + rc = IRQ_HANDLED; + tasklet_schedule(&dev->task); + } + } + return rc; +} + +static uint64_t get_guest_factory_paddr(struct qvm_channel *dev, + const char *name, uint32_t pages) +{ + int i; + + dev->guest_factory = ioremap(SHMEM_PHYSICAL_ADDR, PAGE_SIZE); + + if (!dev->guest_factory) { + pr_err("Couldn't map guest_factory\n"); + return 0; + } + + if (dev->guest_factory->signature != GUEST_SHM_SIGNATURE) { + pr_err("shmem factory signature incorrect: %ld != %lu\n", + GUEST_SHM_SIGNATURE, dev->guest_factory->signature); + iounmap(dev->guest_factory); + return 0; + } + + dev->guest_intr = dev->guest_factory->vector; + + /* + * Set the name field on the factory page to identify the shared memory + * region + */ + for (i = 0; i < strlen(name) && i < GUEST_SHM_MAX_NAME - 1; i++) + dev->guest_factory->name[i] = name[i]; + dev->guest_factory->name[i] = (char) 0; + + guest_shm_create(dev->guest_factory, pages); + + /* See if we successfully created/attached to the region. */ + if (dev->guest_factory->status != GSS_OK) { + pr_err("create failed: %d\n", dev->guest_factory->status); + iounmap(dev->guest_factory); + return 0; + } + + pr_debug("shm creation size %x\n", dev->guest_factory->size); + + return dev->guest_factory->shmem; +} + +static int create_dispatcher(struct physical_channel *pchan, int id) +{ + struct qvm_channel *dev = (struct qvm_channel *)pchan->hyp_data; + int ret; + + tasklet_init(&dev->task, physical_channel_rx_dispatch, + (unsigned long) pchan); + + ret = request_irq(hab_driver.irq, shm_irq_handler, IRQF_SHARED, + hab_driver.devp[id].name, pchan); + + if (ret) + pr_err("request_irq for %s failed: %d\n", + hab_driver.devp[id].name, ret); + + return ret; +} + +static struct physical_channel *habhyp_commdev_alloc(int id) +{ + struct qvm_channel *dev; + struct physical_channel *pchan = NULL; + int ret = 0, channel = 0; + char *shmdata; + uint32_t pipe_alloc_size = + hab_pipe_calc_required_bytes(PIPE_SHMEM_SIZE); + uint32_t pipe_alloc_pages = + (pipe_alloc_size + PAGE_SIZE - 1) / PAGE_SIZE; + uint64_t paddr; + int temp; + int total_pages; + struct page **pages; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&dev->io_lock); + + paddr = get_guest_factory_paddr(dev, + hab_driver.devp[id].name, + pipe_alloc_pages); + + total_pages = dev->guest_factory->size + 1; + pages = kmalloc_array(total_pages, sizeof(struct page *), GFP_KERNEL); + if (!pages) { + ret = -ENOMEM; + goto err; + } + + for (temp = 0; temp < total_pages; temp++) + pages[temp] = pfn_to_page((paddr / PAGE_SIZE) + temp); + + dev->guest_ctrl = vmap(pages, total_pages, VM_MAP, PAGE_KERNEL); + if (!dev->guest_ctrl) { + ret = -ENOMEM; + kfree(pages); + goto err; + } + + shmdata = (char *)dev->guest_ctrl + PAGE_SIZE; + dev->idx = dev->guest_ctrl->idx; + + kfree(pages); + + dev->pipe = (struct hab_pipe *) shmdata; + dev->pipe_ep = hab_pipe_init(dev->pipe, PIPE_SHMEM_SIZE, + dev->be ? 0 : 1); + + pchan = hab_pchan_alloc(&hab_driver.devp[id], dev->be); + if (!pchan) { + ret = -ENOMEM; + goto err; + } + + pchan->closed = 0; + pchan->hyp_data = (void *)dev; + + dev->channel = channel; + + ret = create_dispatcher(pchan, id); + if (ret < 0) + goto err; + + return pchan; + +err: + kfree(dev); + + if (pchan) + hab_pchan_put(pchan); + pr_err("habhyp_commdev_alloc failed: %d\n", ret); + return ERR_PTR(ret); +} + +int hab_hypervisor_register(void) +{ + int ret = 0, i; + + hab_driver.b_server_dom = 0; + + /* + * Can still attempt to instantiate more channels if one fails. + * Others can be retried later. + */ + for (i = 0; i < hab_driver.ndevices; i++) { + if (IS_ERR(habhyp_commdev_alloc(i))) + ret = -EAGAIN; + } + + return ret; +} + +void hab_hypervisor_unregister(void) +{ +} + +static int hab_shmem_probe(struct platform_device *pdev) +{ + int irq = platform_get_irq(pdev, 0); + + if (irq > 0) + hab_driver.irq = irq; + else + hab_driver.irq = DEFAULT_HAB_SHMEM_IRQ; + + return 0; +} + +static int hab_shmem_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id hab_shmem_match_table[] = { + {.compatible = "qvm,guest_shm"}, + {}, +}; + +static struct platform_driver hab_shmem_driver = { + .probe = hab_shmem_probe, + .remove = hab_shmem_remove, + .driver = { + .name = "hab_shmem", + .of_match_table = of_match_ptr(hab_shmem_match_table), + }, +}; + +static int __init hab_shmem_init(void) +{ + return platform_driver_register(&hab_shmem_driver); +} + +static void __exit hab_shmem_exit(void) +{ + platform_driver_unregister(&hab_shmem_driver); +} + +core_initcall(hab_shmem_init); +module_exit(hab_shmem_exit); + +MODULE_DESCRIPTION("Hypervisor shared memory driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/hab/hab_qvm.h b/drivers/soc/qcom/hab/hab_qvm.h new file mode 100644 index 000000000000..e94b82f87942 --- /dev/null +++ b/drivers/soc/qcom/hab/hab_qvm.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __HAB_QNX_H +#define __HAB_QNX_H +#include "hab.h" +#include "hab_pipe.h" + +#include +#include + +#define PULSE_CODE_NOTIFY 0 +#define PULSE_CODE_INPUT 1 + +struct qvm_channel { + int be; + + struct hab_pipe *pipe; + struct hab_pipe_endpoint *pipe_ep; + spinlock_t io_lock; + struct tasklet_struct task; + struct guest_shm_factory *guest_factory; + struct guest_shm_control *guest_ctrl; + uint32_t idx; + + int channel; + int coid; + + unsigned int guest_intr; + unsigned int guest_iid; +}; + +/* Shared mem size in each direction for communication pipe */ +#define PIPE_SHMEM_SIZE (128 * 1024) + +void *qnx_hyp_rx_dispatch(void *data); + +#endif /* __HAB_QNX_H */ diff --git a/drivers/soc/qcom/hab/hab_vchan.c b/drivers/soc/qcom/hab/hab_vchan.c new file mode 100644 index 000000000000..75a3fad68ab5 --- /dev/null +++ b/drivers/soc/qcom/hab/hab_vchan.c @@ -0,0 +1,184 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include "hab.h" + +struct virtual_channel * +hab_vchan_alloc(struct uhab_context *ctx, struct physical_channel *pchan) +{ + int id; + struct virtual_channel *vchan; + + if (!pchan || !ctx) + return NULL; + + vchan = kzalloc(sizeof(*vchan), GFP_KERNEL); + if (!vchan) + return NULL; + + /* This should be the first thing we do in this function */ + idr_preload(GFP_KERNEL); + spin_lock_bh(&pchan->vid_lock); + id = idr_alloc(&pchan->vchan_idr, vchan, 1, 256, GFP_NOWAIT); + spin_unlock_bh(&pchan->vid_lock); + idr_preload_end(); + + if (id < 0) { + kfree(vchan); + return NULL; + } + mb(); /* id must be generated done before pchan_get */ + + hab_pchan_get(pchan); + vchan->pchan = pchan; + vchan->id = ((id << HAB_VCID_ID_SHIFT) & HAB_VCID_ID_MASK) | + ((pchan->habdev->id << HAB_VCID_MMID_SHIFT) & + HAB_VCID_MMID_MASK) | + ((pchan->dom_id << HAB_VCID_DOMID_SHIFT) & + HAB_VCID_DOMID_MASK); + spin_lock_init(&vchan->rx_lock); + INIT_LIST_HEAD(&vchan->rx_list); + init_waitqueue_head(&vchan->rx_queue); + + kref_init(&vchan->refcount); + kref_init(&vchan->usagecnt); + vchan->otherend_closed = pchan->closed; + + hab_ctx_get(ctx); + vchan->ctx = ctx; + + return vchan; +} + +static void +hab_vchan_free(struct kref *ref) +{ + int found; + struct virtual_channel *vchan = + container_of(ref, struct virtual_channel, refcount); + struct hab_message *message, *msg_tmp; + struct export_desc *exp; + struct physical_channel *pchan = vchan->pchan; + struct uhab_context *ctx = vchan->ctx; + + list_for_each_entry_safe(message, msg_tmp, &vchan->rx_list, node) { + list_del(&message->node); + hab_msg_free(message); + } + + do { + found = 0; + write_lock(&ctx->exp_lock); + list_for_each_entry(exp, &ctx->exp_whse, node) { + if (exp->vcid_local == vchan->id) { + list_del(&exp->node); + found = 1; + break; + } + } + write_unlock(&ctx->exp_lock); + if (found) { + habmem_hyp_revoke(exp->payload, exp->payload_count); + habmem_remove_export(exp); + } + } while (found); + + do { + found = 0; + spin_lock_bh(&ctx->imp_lock); + list_for_each_entry(exp, &ctx->imp_whse, node) { + if (exp->vcid_remote == vchan->id) { + list_del(&exp->node); + found = 1; + break; + } + } + spin_unlock_bh(&ctx->imp_lock); + if (found) { + habmm_imp_hyp_unmap(ctx->import_ctx, + exp->import_index, + exp->payload_count, + ctx->kernel); + ctx->import_total--; + kfree(exp); + } + } while (found); + + spin_lock_bh(&pchan->vid_lock); + idr_remove(&pchan->vchan_idr, HAB_VCID_GET_ID(vchan->id)); + spin_unlock_bh(&pchan->vid_lock); + + hab_pchan_put(pchan); + hab_ctx_put(ctx); + + kfree(vchan); +} + +struct virtual_channel* +hab_vchan_get(struct physical_channel *pchan, uint32_t vchan_id) +{ + struct virtual_channel *vchan; + + spin_lock_bh(&pchan->vid_lock); + vchan = idr_find(&pchan->vchan_idr, HAB_VCID_GET_ID(vchan_id)); + if (vchan) + if (!kref_get_unless_zero(&vchan->refcount)) + vchan = NULL; + spin_unlock_bh(&pchan->vid_lock); + + return vchan; +} + +void hab_vchan_stop(struct virtual_channel *vchan) +{ + if (vchan) { + vchan->otherend_closed = 1; + wake_up_interruptible(&vchan->rx_queue); + } +} + +void hab_vchan_stop_notify(struct virtual_channel *vchan) +{ + hab_send_close_msg(vchan); + hab_vchan_stop(vchan); +} + + +int hab_vchan_find_domid(struct virtual_channel *vchan) +{ + return vchan ? vchan->pchan->dom_id : -1; +} + +static void +hab_vchan_free_deferred(struct work_struct *work) +{ + struct virtual_channel *vchan = + container_of(work, struct virtual_channel, work); + + hab_vchan_free(&vchan->refcount); +} + +static void +hab_vchan_schedule_free(struct kref *ref) +{ + struct virtual_channel *vchan = + container_of(ref, struct virtual_channel, refcount); + + INIT_WORK(&vchan->work, hab_vchan_free_deferred); + schedule_work(&vchan->work); +} + +void hab_vchan_put(struct virtual_channel *vchan) +{ + if (vchan) + kref_put(&vchan->refcount, hab_vchan_schedule_free); +} diff --git a/drivers/soc/qcom/hab/khab.c b/drivers/soc/qcom/hab/khab.c new file mode 100644 index 000000000000..f7499773ae42 --- /dev/null +++ b/drivers/soc/qcom/hab/khab.c @@ -0,0 +1,140 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include "hab.h" + +int32_t habmm_socket_open(int32_t *handle, uint32_t mm_ip_id, + uint32_t timeout, uint32_t flags) +{ + return hab_vchan_open(hab_driver.kctx, mm_ip_id, handle, flags); +} +EXPORT_SYMBOL(habmm_socket_open); + +int32_t habmm_socket_close(int32_t handle) +{ + hab_vchan_close(hab_driver.kctx, handle); + return 0; +} +EXPORT_SYMBOL(habmm_socket_close); + +int32_t habmm_socket_send(int32_t handle, void *src_buff, + uint32_t size_bytes, uint32_t flags) +{ + struct hab_send param = {0}; + + param.vcid = handle; + param.data = (uint64_t)(uintptr_t)src_buff; + param.sizebytes = size_bytes; + param.flags = flags; + + return hab_vchan_send(hab_driver.kctx, handle, + size_bytes, src_buff, flags); +} +EXPORT_SYMBOL(habmm_socket_send); + +int32_t habmm_socket_recv(int32_t handle, void *dst_buff, uint32_t *size_bytes, + uint32_t timeout, uint32_t flags) +{ + int ret = 0; + struct hab_message *msg; + + if (!size_bytes || !dst_buff) + return -EINVAL; + + msg = hab_vchan_recv(hab_driver.kctx, handle, flags); + + if (IS_ERR(msg)) { + *size_bytes = 0; + return PTR_ERR(msg); + } + + if (*size_bytes < msg->sizebytes) { + *size_bytes = 0; + ret = -EINVAL; + } else { + memcpy(dst_buff, msg->data, msg->sizebytes); + *size_bytes = msg->sizebytes; + } + + hab_msg_free(msg); + return ret; +} +EXPORT_SYMBOL(habmm_socket_recv); + +int32_t habmm_export(int32_t handle, void *buff_to_share, uint32_t size_bytes, + uint32_t *export_id, uint32_t flags) +{ + int ret; + struct hab_export param = {0}; + + if (!export_id) + return -EINVAL; + + param.vcid = handle; + param.buffer = (uint64_t)(uintptr_t)buff_to_share; + param.sizebytes = size_bytes; + + ret = hab_mem_export(hab_driver.kctx, ¶m, 1); + + *export_id = param.exportid; + return ret; +} +EXPORT_SYMBOL(habmm_export); + +int32_t habmm_unexport(int32_t handle, uint32_t export_id, uint32_t flags) +{ + struct hab_unexport param = {0}; + + param.vcid = handle; + param.exportid = export_id; + + return hab_mem_unexport(hab_driver.kctx, ¶m, 1); +} +EXPORT_SYMBOL(habmm_unexport); + +int32_t habmm_import(int32_t handle, void **buff_shared, uint32_t size_bytes, + uint32_t export_id, uint32_t flags) +{ + int ret; + struct hab_import param = {0}; + + if (!buff_shared) + return -EINVAL; + + param.vcid = handle; + param.sizebytes = size_bytes; + param.exportid = export_id; + param.flags = flags; + + ret = hab_mem_import(hab_driver.kctx, ¶m, 1); + if (!IS_ERR(ret)) + *buff_shared = (void *)(uintptr_t)param.kva; + + return ret; +} +EXPORT_SYMBOL(habmm_import); + +int32_t habmm_unimport(int32_t handle, + uint32_t export_id, + void *buff_shared, + uint32_t flags) +{ + struct hab_unimport param = {0}; + + param.vcid = handle; + param.exportid = export_id; + param.kva = (uint64_t)(uintptr_t)buff_shared; + + return hab_mem_unimport(hab_driver.kctx, ¶m, 1); +} +EXPORT_SYMBOL(habmm_unimport); diff --git a/drivers/soc/qcom/hab/qvm_comm.c b/drivers/soc/qcom/hab/qvm_comm.c new file mode 100644 index 000000000000..20a631e13794 --- /dev/null +++ b/drivers/soc/qcom/hab/qvm_comm.c @@ -0,0 +1,95 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include "hab.h" +#include "hab_qvm.h" + +static inline void habhyp_notify(void *commdev) +{ + struct qvm_channel *dev = (struct qvm_channel *)commdev; + + if (dev && dev->guest_ctrl) + dev->guest_ctrl->notify = ~0; +} + +int physical_channel_read(struct physical_channel *pchan, + void *payload, + size_t read_size) +{ + struct qvm_channel *dev = (struct qvm_channel *)pchan->hyp_data; + + if (dev) + return hab_pipe_read(dev->pipe_ep, payload, read_size); + else + return 0; +} + +int physical_channel_send(struct physical_channel *pchan, + struct hab_header *header, + void *payload) +{ + int sizebytes = HAB_HEADER_GET_SIZE(*header); + struct qvm_channel *dev = (struct qvm_channel *)pchan->hyp_data; + int total_size = sizeof(*header) + sizebytes; + + if (total_size > dev->pipe_ep->tx_info.sh_buf->size) + return -EINVAL; /* too much data for ring */ + + spin_lock_bh(&dev->io_lock); + + if ((dev->pipe_ep->tx_info.sh_buf->size - + (dev->pipe_ep->tx_info.wr_count - + dev->pipe_ep->tx_info.sh_buf->rd_count)) < total_size) { + spin_unlock_bh(&dev->io_lock); + return -EAGAIN; /* not enough free space */ + } + + if (hab_pipe_write(dev->pipe_ep, + (unsigned char *)header, + sizeof(*header)) != sizeof(*header)) { + spin_unlock_bh(&dev->io_lock); + return -EIO; + } + + if (sizebytes) { + if (hab_pipe_write(dev->pipe_ep, + (unsigned char *)payload, + sizebytes) != sizebytes) { + spin_unlock_bh(&dev->io_lock); + return -EIO; + } + } + + hab_pipe_write_commit(dev->pipe_ep); + spin_unlock_bh(&dev->io_lock); + habhyp_notify(dev); + + return 0; +} + +void physical_channel_rx_dispatch(unsigned long data) +{ + struct hab_header header; + struct physical_channel *pchan = (struct physical_channel *)data; + struct qvm_channel *dev = (struct qvm_channel *)pchan->hyp_data; + + spin_lock_bh(&pchan->rxbuf_lock); + while (1) { + if (hab_pipe_read(dev->pipe_ep, + (unsigned char *)&header, + sizeof(header)) != sizeof(header)) + break; /* no data available */ + + hab_msg_recv(pchan, &header); + } + spin_unlock_bh(&pchan->rxbuf_lock); +} diff --git a/include/linux/habmm.h b/include/linux/habmm.h new file mode 100644 index 000000000000..4d3a81f536d9 --- /dev/null +++ b/include/linux/habmm.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include + +#ifndef _HABMM_H +#define _HABMM_H + +int32_t habmm_socket_open(int32_t *handle, uint32_t mm_ip_id, + uint32_t timeout, uint32_t flags); +int32_t habmm_socket_close(int32_t handle); +int32_t habmm_socket_send(int32_t handle, void *src_buff, uint32_t size_bytes, + uint32_t flags); +int32_t habmm_socket_recv(int32_t handle, void *dst_buff, uint32_t *size_bytes, + uint32_t timeout, uint32_t flags); +int32_t habmm_socket_sendto(int32_t handle, void *src_buff, uint32_t size_bytes, + int32_t remote_handle, uint32_t flags); +int32_t habmm_socket_recvfrom(int32_t handle, void *dst_buff, + uint32_t *size_bytes, uint32_t timeout, + int32_t *remote_handle, uint32_t flags); +int32_t habmm_export(int32_t handle, void *buff_to_share, uint32_t size_bytes, + uint32_t *export_id, uint32_t flags); +int32_t habmm_unexport(int32_t handle, uint32_t export_id, uint32_t flags); +int32_t habmm_import(int32_t handle, void **buff_shared, uint32_t size_bytes, + uint32_t export_id, uint32_t flags); +int32_t habmm_unimport(int32_t handle, uint32_t export_id, void *buff_shared, + uint32_t flags); + +#endif diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index 3d912dd57c08..b041334338f9 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -528,3 +528,4 @@ header-y += ipa_qmi_service_v01.h header-y += rmnet_ipa_fd_ioctl.h header-y += msm_ipa.h header-y += smcinvoke.h +header-y += habmm.h diff --git a/include/uapi/linux/habmm.h b/include/uapi/linux/habmm.h new file mode 100644 index 000000000000..902bd35ee474 --- /dev/null +++ b/include/uapi/linux/habmm.h @@ -0,0 +1,143 @@ +#ifndef HABMM_H +#define HABMM_H + +#include + +struct hab_send { + __u64 data; + __s32 vcid; + __u32 sizebytes; + __u32 flags; +}; + +struct hab_recv { + __u64 data; + __s32 vcid; + __u32 sizebytes; + __u32 flags; +}; + +struct hab_open { + __s32 vcid; + __u32 mmid; + __u32 timeout; + __u32 flags; +}; + +struct hab_close { + __s32 vcid; + __u32 flags; +}; + +struct hab_export { + __u64 buffer; + __s32 vcid; + __u32 sizebytes; + __u32 exportid; + __u32 flags; +}; + +struct hab_import { + __u64 index; + __u64 kva; + __s32 vcid; + __u32 sizebytes; + __u32 exportid; + __u32 flags; +}; + +struct hab_unexport { + __s32 vcid; + __u32 exportid; + __u32 flags; +}; + +struct hab_unimport { + __s32 vcid; + __u32 exportid; + __u64 kva; + __u32 flags; +}; + +#define HAB_IOC_TYPE 0x0A +#define HAB_MAX_MSG_SIZEBYTES 0x1000 +#define HAB_MAX_EXPORT_SIZE 0x8000000 + +#define HAB_MMID_CREATE(major, minor) ((major&0xFFFF) | ((minor&0xFF)<<16)) + +#define MM_AUD_START 100 +#define MM_AUD_1 101 +#define MM_AUD_2 102 +#define MM_AUD_3 103 +#define MM_AUD_4 104 +#define MM_AUD_END 105 + +#define MM_CAM_START 200 +#define MM_CAM 201 +#define MM_CAM_END 202 + +#define MM_DISP_START 300 +#define MM_DISP_1 301 +#define MM_DISP_2 302 +#define MM_DISP_3 303 +#define MM_DISP_4 304 +#define MM_DISP_5 305 +#define MM_DISP_END 306 + +#define MM_GFX_START 400 +#define MM_GFX 401 +#define MM_GFX_END 402 + +#define MM_VID_START 500 +#define MM_VID 501 +#define MM_VID_END 502 + +#define MM_MISC_START 600 +#define MM_MISC 601 +#define MM_MISC_END 602 + +#define MM_QCPE_START 700 +#define MM_QCPE_VM1 701 +#define MM_QCPE_VM2 702 +#define MM_QCPE_VM3 703 +#define MM_QCPE_VM4 704 +#define MM_QCPE_END 705 +#define MM_ID_MAX 706 + +#define HABMM_SOCKET_OPEN_FLAGS_SINGLE_BE_SINGLE_FE 0x00000000 +#define HABMM_SOCKET_OPEN_FLAGS_SINGLE_BE_SINGLE_DOMU 0x00000001 +#define HABMM_SOCKET_OPEN_FLAGS_SINGLE_BE_MULTI_DOMUS 0x00000002 + +#define HABMM_SOCKET_SEND_FLAGS_NON_BLOCKING 0x00000001 + +#define HABMM_SOCKET_RECV_FLAGS_NON_BLOCKING 0x00000001 + +#define HABMM_EXP_MEM_TYPE_DMA 0x00000001 + +#define HABMM_IMPORT_FLAGS_CACHED 0x00000001 + +#define IOCTL_HAB_SEND \ + _IOW(HAB_IOC_TYPE, 0x2, struct hab_send) + +#define IOCTL_HAB_RECV \ + _IOWR(HAB_IOC_TYPE, 0x3, struct hab_recv) + +#define IOCTL_HAB_VC_OPEN \ + _IOWR(HAB_IOC_TYPE, 0x4, struct hab_open) + +#define IOCTL_HAB_VC_CLOSE \ + _IOW(HAB_IOC_TYPE, 0x5, struct hab_close) + +#define IOCTL_HAB_VC_EXPORT \ + _IOWR(HAB_IOC_TYPE, 0x6, struct hab_export) + +#define IOCTL_HAB_VC_IMPORT \ + _IOWR(HAB_IOC_TYPE, 0x7, struct hab_import) + +#define IOCTL_HAB_VC_UNEXPORT \ + _IOW(HAB_IOC_TYPE, 0x8, struct hab_unexport) + +#define IOCTL_HAB_VC_UNIMPORT \ + _IOW(HAB_IOC_TYPE, 0x9, struct hab_unimport) + +#endif /* HABMM_H */