android_kernel_xiaomi_sm8350/drivers/soc/qcom/mem-buf.c
Vijayanand Jitta 3a4f3d2090 mem_buf: Validate n_acl_entries
Validate n_acl_entries specified by the requesting VM.

Limiting n_acl_entries to 1 for now based on the existing check in
mem_buf_alloc().

Change-Id: I802219ada9ee9109d18a42b2a80539d1cbd4df64
Signed-off-by: Vijayanand Jitta <quic_vjitta@quicinc.com>
2022-08-02 04:13:35 -07:00

2302 lines
58 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
#include <linux/anon_inodes.h>
#include <linux/cdev.h>
#include <linux/dma-buf.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/haven/hh_rm_drv.h>
#include <linux/haven/hh_msgq.h>
#include <linux/ion.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/mem-buf.h>
#include <linux/memblock.h>
#include <linux/memory_hotplug.h>
#include <linux/module.h>
#include <linux/msm_ion.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <soc/qcom/secure_buffer.h>
#include <uapi/linux/mem-buf.h>
#include "mem-buf-private.h"
#define CREATE_TRACE_POINTS
#include "trace-mem-buf.h"
#define MEM_BUF_MAX_DEVS 1
#define MEM_BUF_MHP_ALIGNMENT (1UL << SUBSECTION_SHIFT)
#define MEM_BUF_TIMEOUT_MS 3500
#define to_rmt_msg(_work) container_of(_work, struct mem_buf_rmt_msg, work)
#define MEM_BUF_CAP_SUPPLIER BIT(0)
#define MEM_BUF_CAP_CONSUMER BIT(1)
#define MEM_BUF_CAP_DUAL (MEM_BUF_CAP_SUPPLIER | MEM_BUF_CAP_CONSUMER)
/* Data structures for requesting/maintaining memory from other VMs */
static dev_t mem_buf_dev_no;
static struct class *mem_buf_class;
static struct cdev mem_buf_char_dev;
/* Maintains a list of memory buffers requested from other VMs */
static DEFINE_MUTEX(mem_buf_list_lock);
static LIST_HEAD(mem_buf_list);
/*
* Data structures for tracking request/reply transactions, as well as message
* queue usage
*/
static DEFINE_MUTEX(mem_buf_idr_mutex);
static DEFINE_IDR(mem_buf_txn_idr);
static struct task_struct *mem_buf_msgq_recv_thr;
static void *mem_buf_hh_msgq_hdl;
static unsigned char mem_buf_capability;
static struct workqueue_struct *mem_buf_wq;
/**
* struct mem_buf_txn: Represents a transaction (request/response pair) in the
* membuf driver.
* @txn_id: Transaction ID used to match requests and responses (i.e. a new ID
* is allocated per request, and the response will have a matching ID).
* @txn_ret: The return value of the transaction.
* @txn_done: Signals that a response has arrived.
* @resp_buf: A pointer to a buffer where the response should be decoded into.
*/
struct mem_buf_txn {
int txn_id;
int txn_ret;
struct completion txn_done;
void *resp_buf;
};
/* Data structures for maintaining memory shared to other VMs */
static struct device *mem_buf_dev;
/* Maintains a list of memory buffers lent out to other VMs */
static DEFINE_MUTEX(mem_buf_xfer_mem_list_lock);
static LIST_HEAD(mem_buf_xfer_mem_list);
/**
* struct mem_buf_rmt_msg: Represents a message sent from a remote VM
* @msg: A pointer to the message buffer
* @msg_size: The size of the message
* @work: work structure for dispatching the message processing to a worker
* thread, so as to not block the message queue receiving thread.
*/
struct mem_buf_rmt_msg {
void *msg;
size_t msg_size;
struct work_struct work;
};
/**
* struct mem_buf_xfer_mem: Represents a memory buffer lent out or transferred
* to another VM.
* @size: The size of the memory buffer
* @mem_type: The type of memory that was allocated and transferred
* @mem_type_data: Data associated with the type of memory
* @mem_sgt: An SG-Table representing the memory transferred
* @secure_alloc: Denotes if the memory was assigned to the targeted VMs as part
* of the allocation step
* @hdl: The memparcel handle associated with the memory
* @entry: List entry for maintaining a list of memory buffers that are lent
* out.
* @nr_acl_entries: The number of VMIDs and permissions associated with the
* memory
* @dst_vmids: The VMIDs that have access to the memory
* @dst_perms: The access permissions for the VMIDs that can access the memory
*/
struct mem_buf_xfer_mem {
size_t size;
enum mem_buf_mem_type mem_type;
void *mem_type_data;
struct sg_table *mem_sgt;
bool secure_alloc;
hh_memparcel_handle_t hdl;
struct list_head entry;
u32 nr_acl_entries;
int *dst_vmids;
int *dst_perms;
};
/**
* struct mem_buf_desc - Internal data structure, which contains information
* about a particular memory buffer.
* @size: The size of the memory buffer
* @acl_desc: A HH ACL descriptor that describes the VMIDs that have access to
* the memory, as well as the permissions each VMID has.
* @sgl_desc: An HH SG-List descriptor that describes the IPAs of the memory
* associated with the memory buffer that was allocated from another VM.
* @memparcel_hdl: The handle associated with the memparcel that represents the
* memory buffer.
* @src_mem_type: The type of memory that was allocated on the remote VM
* @src_data: Memory type specific data used by the remote VM when performing
* the allocation.
* @dst_mem_type: The memory type of the memory buffer on the native VM
* @dst_data: Memory type specific data used by the native VM when adding the
* memory to the system.
* @filp: Pointer to the file structure for the membuf
* @entry: List head for maintaing a list of memory buffers that have been
* provided by remote VMs.
*/
struct mem_buf_desc {
size_t size;
struct hh_acl_desc *acl_desc;
struct hh_sgl_desc *sgl_desc;
hh_memparcel_handle_t memparcel_hdl;
enum mem_buf_mem_type src_mem_type;
void *src_data;
enum mem_buf_mem_type dst_mem_type;
void *dst_data;
struct file *filp;
struct list_head entry;
};
/**
* struct mem_buf_xfer_ion_mem: Data specific to memory buffers allocated
* through ION and have been transferred to another VM.
* @heap_id: ID of the heap from which the allocation was performed from
* @dmabuf: dmabuf associated with the memory buffer
* @attachment: dmabuf attachment associated with the memory buffer
*/
struct mem_buf_xfer_ion_mem {
u32 heap_id;
struct dma_buf *dmabuf;
struct dma_buf_attachment *attachment;
};
/**
* struct mem_buf_export: Represents a dmabuf that has been exported to other
* VM(s).
* @dmabuf: The dmabuf that was exported to other VM(s)
* @attachment: The dma-buf attachment for @dmabuf
* @mem_sgt: The SG-Table for the dmabuf that was exported.
* @nr_vmids: The number of VMIDs that have access to the memory that was
* exported.
* @dst_vmids: The VMIDs of the VMs that have access to the buffer.
* @filp: A file structure that corresponds to the buffer that was exported.
*/
struct mem_buf_export {
struct dma_buf *dmabuf;
struct dma_buf_attachment *attachment;
struct sg_table *mem_sgt;
unsigned int nr_vmids;
int *dst_vmids;
struct file *filp;
};
static int mem_buf_init_txn(struct mem_buf_txn *txn, void *resp_buf)
{
int ret;
mutex_lock(&mem_buf_idr_mutex);
ret = idr_alloc_cyclic(&mem_buf_txn_idr, txn, 0, U16_MAX, GFP_KERNEL);
mutex_unlock(&mem_buf_idr_mutex);
if (ret < 0) {
pr_err("%s: failed to allocate transaction id rc: %d\n",
__func__, ret);
return ret;
}
txn->txn_id = ret;
init_completion(&txn->txn_done);
txn->resp_buf = resp_buf;
return 0;
}
static int mem_buf_msg_send(void *msg, size_t msg_size)
{
int ret;
ret = hh_msgq_send(mem_buf_hh_msgq_hdl, msg, msg_size, 0);
if (ret < 0)
pr_err("%s: failed to send allocation request rc: %d\n",
__func__, ret);
else
pr_debug("%s: alloc request sent\n", __func__);
return ret;
}
static int mem_buf_txn_wait(struct mem_buf_txn *txn)
{
int ret;
pr_debug("%s: waiting for allocation response\n", __func__);
ret = wait_for_completion_timeout(&txn->txn_done,
msecs_to_jiffies(MEM_BUF_TIMEOUT_MS));
if (ret == 0) {
pr_err("%s: timed out waiting for allocation response\n",
__func__);
return -ETIMEDOUT;
} else {
pr_debug("%s: alloc response received\n", __func__);
}
return txn->txn_ret;
}
static void mem_buf_destroy_txn(struct mem_buf_txn *txn)
{
mutex_lock(&mem_buf_idr_mutex);
idr_remove(&mem_buf_txn_idr, txn->txn_id);
mutex_unlock(&mem_buf_idr_mutex);
}
/* Functions invoked when treating allocation requests from other VMs. */
static int mem_buf_rmt_alloc_ion_mem(struct mem_buf_xfer_mem *xfer_mem)
{
unsigned int i;
struct dma_buf *dmabuf;
struct dma_buf_attachment *attachment;
struct sg_table *mem_sgt;
struct mem_buf_xfer_ion_mem *ion_mem_data = xfer_mem->mem_type_data;
int ion_flags = 0, flag;
int heap_id = ion_mem_data->heap_id;
u32 nr_acl_entries = xfer_mem->nr_acl_entries;
pr_debug("%s: Starting ION allocation\n", __func__);
if (msm_ion_heap_is_secure(heap_id)) {
xfer_mem->secure_alloc = true;
ion_flags |= ION_FLAG_SECURE;
for (i = 0; i < nr_acl_entries; i++) {
flag = get_ion_flags(xfer_mem->dst_vmids[i]);
if (flag < 0)
return flag;
ion_flags |= flag;
}
} else {
xfer_mem->secure_alloc = false;
}
/*
* If the buffer needs to be freed because of error handling, ensure
* that dma_buf_put_sync() is invoked, instead of dma_buf_put(). Doing
* so ensures that the memory is freed before the next allocation
* request is serviced.
*/
dmabuf = ion_alloc(xfer_mem->size, heap_id, ion_flags);
if (IS_ERR(dmabuf)) {
pr_err("%s ion_alloc failure sz: 0x%x heap_id: %d flags: 0x%x rc: %d\n",
__func__, xfer_mem->size, heap_id, ion_flags,
PTR_ERR(dmabuf));
return PTR_ERR(dmabuf);
}
attachment = dma_buf_attach(dmabuf, mem_buf_dev);
if (IS_ERR(attachment)) {
pr_err("%s dma_buf_attach failure rc: %d\n", __func__,
PTR_ERR(attachment));
dma_buf_put_sync(dmabuf);
return PTR_ERR(attachment);
}
mem_sgt = dma_buf_map_attachment(attachment, DMA_BIDIRECTIONAL);
if (IS_ERR(mem_sgt)) {
pr_err("%s dma_buf_map_attachment failure rc: %d\n", __func__,
PTR_ERR(mem_sgt));
dma_buf_detach(dmabuf, attachment);
dma_buf_put_sync(dmabuf);
return PTR_ERR(mem_sgt);
}
ion_mem_data->dmabuf = dmabuf;
ion_mem_data->attachment = attachment;
xfer_mem->mem_sgt = mem_sgt;
pr_debug("%s: ION allocation complete\n", __func__);
return 0;
}
static int mem_buf_rmt_alloc_mem(struct mem_buf_xfer_mem *xfer_mem)
{
int ret = -EINVAL;
if (xfer_mem->mem_type == MEM_BUF_ION_MEM_TYPE)
ret = mem_buf_rmt_alloc_ion_mem(xfer_mem);
return ret;
}
static void mem_buf_rmt_free_ion_mem(struct mem_buf_xfer_mem *xfer_mem)
{
struct mem_buf_xfer_ion_mem *ion_mem_data = xfer_mem->mem_type_data;
struct dma_buf *dmabuf = ion_mem_data->dmabuf;
struct dma_buf_attachment *attachment = ion_mem_data->attachment;
struct sg_table *mem_sgt = xfer_mem->mem_sgt;
pr_debug("%s: Freeing ION memory\n", __func__);
dma_buf_unmap_attachment(attachment, mem_sgt, DMA_BIDIRECTIONAL);
dma_buf_detach(dmabuf, attachment);
/*
* Use dma_buf_put_sync() instead of dma_buf_put() to ensure that the
* memory is actually freed, before the next allocation request.
*/
dma_buf_put_sync(ion_mem_data->dmabuf);
pr_debug("%s: ION memory freed\n", __func__);
}
static void mem_buf_rmt_free_mem(struct mem_buf_xfer_mem *xfer_mem)
{
if (xfer_mem->mem_type == MEM_BUF_ION_MEM_TYPE)
mem_buf_rmt_free_ion_mem(xfer_mem);
}
static int mem_buf_hh_acl_desc_to_vmid_perm_list(struct hh_acl_desc *acl_desc,
int **vmids, int **perms)
{
int *vmids_arr = NULL, *perms_arr = NULL;
u32 nr_acl_entries = acl_desc->n_acl_entries;
unsigned int i;
if (!vmids || !perms)
return -EINVAL;
vmids_arr = kmalloc_array(nr_acl_entries, sizeof(*vmids_arr),
GFP_KERNEL);
if (!vmids_arr)
return -ENOMEM;
perms_arr = kmalloc_array(nr_acl_entries, sizeof(*perms_arr),
GFP_KERNEL);
if (!perms_arr) {
kfree(vmids_arr);
return -ENOMEM;
}
*vmids = vmids_arr;
*perms = perms_arr;
for (i = 0; i < nr_acl_entries; i++) {
vmids_arr[i] = acl_desc->acl_entries[i].vmid;
perms_arr[i] = acl_desc->acl_entries[i].perms;
}
return 0;
}
static int mem_buf_assign_mem(struct sg_table *sgt, int *dst_vmids,
int *dst_perms, unsigned int nr_acl_entries)
{
u32 src_vmid = VMID_HLOS;
int ret;
if (!sgt || !dst_vmids || !dst_perms || !nr_acl_entries)
return -EINVAL;
pr_debug("%s: Assigning memory to target VMIDs\n", __func__);
ret = hyp_assign_table(sgt, &src_vmid, 1, dst_vmids, dst_perms,
nr_acl_entries);
if (ret < 0)
pr_err("%s: failed to assign memory for rmt allocation rc:%d\n",
__func__, ret);
else
pr_debug("%s: Memory assigned to target VMIDs\n", __func__);
return ret;
}
static int mem_buf_unassign_mem(struct sg_table *sgt, int *src_vmids,
unsigned int nr_acl_entries)
{
int dst_vmid = VMID_HLOS;
int dst_perms = PERM_READ | PERM_WRITE | PERM_EXEC;
int ret;
if (!sgt || !src_vmids || !nr_acl_entries)
return -EINVAL;
pr_debug("%s: Unassigning memory to HLOS\n", __func__);
ret = hyp_assign_table(sgt, src_vmids, nr_acl_entries, &dst_vmid,
&dst_perms, 1);
if (ret < 0)
pr_err("%s: failed to assign memory from rmt allocation rc: %d\n",
__func__, ret);
else
pr_debug("%s: Unassigned memory to HLOS\n", __func__);
return ret;
}
static int mem_buf_retrieve_memparcel_hdl(struct sg_table *sgt,
int *dst_vmids, int *dst_perms,
u32 nr_acl_entries,
hh_memparcel_handle_t *memparcel_hdl)
{
struct hh_sgl_desc *sgl_desc;
struct hh_acl_desc *acl_desc;
unsigned int i, nr_sg_entries;
struct scatterlist *sg;
int ret;
size_t sgl_desc_size, acl_desc_size;
if (!sgt || !dst_vmids || !dst_perms || !nr_acl_entries ||
!memparcel_hdl)
return -EINVAL;
nr_sg_entries = sgt->nents;
sgl_desc_size = offsetof(struct hh_sgl_desc,
sgl_entries[nr_sg_entries]);
sgl_desc = kzalloc(sgl_desc_size, GFP_KERNEL);
if (!sgl_desc)
return -ENOMEM;
acl_desc_size = offsetof(struct hh_acl_desc,
acl_entries[nr_acl_entries]);
acl_desc = kzalloc(acl_desc_size, GFP_KERNEL);
if (!acl_desc) {
kfree(sgl_desc);
return -ENOMEM;
}
sgl_desc->n_sgl_entries = nr_sg_entries;
for_each_sg(sgt->sgl, sg, nr_sg_entries, i) {
sgl_desc->sgl_entries[i].ipa_base = page_to_phys(sg_page(sg));
sgl_desc->sgl_entries[i].size = sg->length;
}
acl_desc->n_acl_entries = nr_acl_entries;
for (i = 0; i < nr_acl_entries; i++) {
acl_desc->acl_entries[i].vmid = dst_vmids[i];
acl_desc->acl_entries[i].perms = dst_perms[i];
}
ret = hh_rm_mem_qcom_lookup_sgl(HH_RM_MEM_TYPE_NORMAL, 0, acl_desc,
sgl_desc, NULL, memparcel_hdl);
trace_lookup_sgl(sgl_desc, ret, *memparcel_hdl);
if (ret < 0)
pr_err("%s: hh_rm_mem_qcom_lookup_sgl failure rc: %d\n",
__func__, ret);
kfree(acl_desc);
kfree(sgl_desc);
return ret;
}
static
struct mem_buf_xfer_ion_mem *mem_buf_alloc_ion_xfer_mem_type_data(
void *rmt_data)
{
struct mem_buf_xfer_ion_mem *xfer_ion_mem;
struct mem_buf_ion_alloc_data *ion_data = rmt_data;
xfer_ion_mem = kmalloc(sizeof(*xfer_ion_mem), GFP_KERNEL);
if (!xfer_ion_mem)
return ERR_PTR(-ENOMEM);
xfer_ion_mem->heap_id = ion_data->heap_id;
pr_debug("%s: ION source heap ID: 0x%x\n", __func__,
xfer_ion_mem->heap_id);
return xfer_ion_mem;
}
static void *mem_buf_alloc_xfer_mem_type_data(enum mem_buf_mem_type type,
void *rmt_data)
{
void *data = ERR_PTR(-EINVAL);
if (type == MEM_BUF_ION_MEM_TYPE)
data = mem_buf_alloc_ion_xfer_mem_type_data(rmt_data);
return data;
}
static
void mem_buf_free_ion_xfer_mem_type_data(struct mem_buf_xfer_ion_mem *mem)
{
kfree(mem);
}
static void mem_buf_free_xfer_mem_type_data(enum mem_buf_mem_type type,
void *data)
{
if (type == MEM_BUF_ION_MEM_TYPE)
mem_buf_free_ion_xfer_mem_type_data(data);
}
static
struct mem_buf_xfer_mem *mem_buf_prep_xfer_mem(void *req_msg)
{
int ret;
struct mem_buf_xfer_mem *xfer_mem;
struct mem_buf_alloc_req *req = req_msg;
u32 nr_acl_entries;
size_t alloc_req_msg_size;
void *arb_payload;
void *mem_type_data;
nr_acl_entries = req->acl_desc.n_acl_entries;
if (nr_acl_entries != 1)
return ERR_PTR(-EINVAL);
alloc_req_msg_size = offsetof(struct mem_buf_alloc_req,
acl_desc.acl_entries[nr_acl_entries]);
arb_payload = req_msg + alloc_req_msg_size;
xfer_mem = kzalloc(sizeof(*xfer_mem), GFP_KERNEL);
if (!xfer_mem)
return ERR_PTR(-ENOMEM);
xfer_mem->size = req->size;
xfer_mem->mem_type = req->src_mem_type;
xfer_mem->nr_acl_entries = req->acl_desc.n_acl_entries;
ret = mem_buf_hh_acl_desc_to_vmid_perm_list(&req->acl_desc,
&xfer_mem->dst_vmids,
&xfer_mem->dst_perms);
if (ret) {
pr_err("%s failed to create VMID and permissions list: %d\n",
__func__, ret);
kfree(xfer_mem);
return ERR_PTR(ret);
}
mem_type_data = mem_buf_alloc_xfer_mem_type_data(req->src_mem_type,
arb_payload);
if (IS_ERR(mem_type_data)) {
pr_err("%s: failed to allocate mem type specific data: %d\n",
__func__, PTR_ERR(mem_type_data));
kfree(xfer_mem->dst_vmids);
kfree(xfer_mem->dst_perms);
kfree(xfer_mem);
return ERR_CAST(mem_type_data);
}
xfer_mem->mem_type_data = mem_type_data;
INIT_LIST_HEAD(&xfer_mem->entry);
return xfer_mem;
}
static void mem_buf_free_xfer_mem(struct mem_buf_xfer_mem *xfer_mem)
{
mem_buf_free_xfer_mem_type_data(xfer_mem->mem_type,
xfer_mem->mem_type_data);
kfree(xfer_mem->dst_vmids);
kfree(xfer_mem->dst_perms);
kfree(xfer_mem);
}
static struct mem_buf_xfer_mem *mem_buf_process_alloc_req(void *req)
{
int ret;
struct mem_buf_alloc_req *req_msg = req;
struct mem_buf_xfer_mem *xfer_mem;
xfer_mem = mem_buf_prep_xfer_mem(req_msg);
if (IS_ERR(xfer_mem))
return xfer_mem;
ret = mem_buf_rmt_alloc_mem(xfer_mem);
if (ret < 0)
goto err_rmt_alloc;
if (!xfer_mem->secure_alloc) {
ret = mem_buf_assign_mem(xfer_mem->mem_sgt, xfer_mem->dst_vmids,
xfer_mem->dst_perms,
xfer_mem->nr_acl_entries);
if (ret < 0)
goto err_assign_mem;
}
ret = mem_buf_retrieve_memparcel_hdl(xfer_mem->mem_sgt,
xfer_mem->dst_vmids,
xfer_mem->dst_perms,
xfer_mem->nr_acl_entries,
&xfer_mem->hdl);
if (ret < 0)
goto err_retrieve_hdl;
mutex_lock(&mem_buf_xfer_mem_list_lock);
list_add(&xfer_mem->entry, &mem_buf_xfer_mem_list);
mutex_unlock(&mem_buf_xfer_mem_list_lock);
return xfer_mem;
err_retrieve_hdl:
if (!xfer_mem->secure_alloc &&
(mem_buf_unassign_mem(xfer_mem->mem_sgt, xfer_mem->dst_vmids,
xfer_mem->nr_acl_entries) < 0))
return ERR_PTR(ret);
err_assign_mem:
if (ret != -EADDRNOTAVAIL)
mem_buf_rmt_free_mem(xfer_mem);
err_rmt_alloc:
mem_buf_free_xfer_mem(xfer_mem);
return ERR_PTR(ret);
}
static void mem_buf_cleanup_alloc_req(struct mem_buf_xfer_mem *xfer_mem)
{
int ret;
if (!xfer_mem->secure_alloc) {
ret = mem_buf_unassign_mem(xfer_mem->mem_sgt,
xfer_mem->dst_vmids,
xfer_mem->nr_acl_entries);
if (ret < 0)
return;
}
mem_buf_rmt_free_mem(xfer_mem);
mem_buf_free_xfer_mem(xfer_mem);
}
static int mem_buf_get_mem_xfer_type(struct hh_acl_desc *acl_desc)
{
u32 i, nr_acl_entries = acl_desc->n_acl_entries;
for (i = 0; i < nr_acl_entries; i++)
if (acl_desc->acl_entries[i].vmid == VMID_HLOS &&
acl_desc->acl_entries[i].perms != 0)
return HH_RM_TRANS_TYPE_SHARE;
return HH_RM_TRANS_TYPE_LEND;
}
static void mem_buf_alloc_req_work(struct work_struct *work)
{
struct mem_buf_rmt_msg *rmt_msg = to_rmt_msg(work);
struct mem_buf_alloc_req *req_msg = rmt_msg->msg;
struct mem_buf_alloc_resp *resp_msg;
struct mem_buf_xfer_mem *xfer_mem;
int ret = 0;
trace_receive_alloc_req(req_msg);
resp_msg = kzalloc(sizeof(*resp_msg), GFP_KERNEL);
if (!resp_msg)
goto err_alloc_resp;
resp_msg->hdr.txn_id = req_msg->hdr.txn_id;
resp_msg->hdr.msg_type = MEM_BUF_ALLOC_RESP;
xfer_mem = mem_buf_process_alloc_req(rmt_msg->msg);
if (IS_ERR(xfer_mem)) {
ret = PTR_ERR(xfer_mem);
pr_err("%s: failed to process rmt memory alloc request: %d\n",
__func__, ret);
} else {
resp_msg->hdl = xfer_mem->hdl;
}
resp_msg->ret = ret;
trace_send_alloc_resp_msg(resp_msg);
ret = hh_msgq_send(mem_buf_hh_msgq_hdl, resp_msg, sizeof(*resp_msg), 0);
/*
* Free the buffer regardless of the return value as the hypervisor
* would have consumed the data in the case of a success.
*/
kfree(resp_msg);
if (ret < 0) {
pr_err("%s: failed to send memory allocation response rc: %d\n",
__func__, ret);
mutex_lock(&mem_buf_xfer_mem_list_lock);
list_del(&xfer_mem->entry);
mutex_unlock(&mem_buf_xfer_mem_list_lock);
mem_buf_cleanup_alloc_req(xfer_mem);
} else {
pr_debug("%s: Allocation response sent\n", __func__);
}
err_alloc_resp:
kfree(rmt_msg->msg);
kfree(rmt_msg);
}
static void mem_buf_relinquish_work(struct work_struct *work)
{
struct mem_buf_xfer_mem *xfer_mem_iter, *tmp, *xfer_mem = NULL;
struct mem_buf_rmt_msg *rmt_msg = to_rmt_msg(work);
struct mem_buf_alloc_relinquish *relinquish_msg = rmt_msg->msg;
hh_memparcel_handle_t hdl = relinquish_msg->hdl;
trace_receive_relinquish_msg(relinquish_msg);
mutex_lock(&mem_buf_xfer_mem_list_lock);
list_for_each_entry_safe(xfer_mem_iter, tmp, &mem_buf_xfer_mem_list,
entry)
if (xfer_mem_iter->hdl == hdl) {
xfer_mem = xfer_mem_iter;
list_del(&xfer_mem->entry);
break;
}
mutex_unlock(&mem_buf_xfer_mem_list_lock);
if (xfer_mem)
mem_buf_cleanup_alloc_req(xfer_mem);
else
pr_err("%s: transferred memory with handle 0x%x not found\n",
__func__, hdl);
kfree(rmt_msg->msg);
kfree(rmt_msg);
}
static int mem_buf_decode_alloc_resp(void *buf, size_t size,
hh_memparcel_handle_t *ret_hdl)
{
struct mem_buf_alloc_resp *alloc_resp = buf;
if (size != sizeof(*alloc_resp)) {
pr_err("%s response received is not of correct size\n",
__func__);
return -EINVAL;
}
trace_receive_alloc_resp_msg(alloc_resp);
if (alloc_resp->ret < 0)
pr_err("%s remote allocation failed rc: %d\n", __func__,
alloc_resp->ret);
else
*ret_hdl = alloc_resp->hdl;
return alloc_resp->ret;
}
static void mem_buf_relinquish_mem(u32 memparcel_hdl);
static void mem_buf_process_alloc_resp(struct mem_buf_msg_hdr *hdr, void *buf,
size_t size)
{
struct mem_buf_txn *txn;
hh_memparcel_handle_t hdl;
mutex_lock(&mem_buf_idr_mutex);
txn = idr_find(&mem_buf_txn_idr, hdr->txn_id);
if (!txn) {
pr_err("%s no txn associated with id: %d\n", __func__,
hdr->txn_id);
/*
* If this was a legitimate allocation, we should let the
* allocator know that the memory is not in use, so that
* it can be reclaimed.
*/
if (!mem_buf_decode_alloc_resp(buf, size, &hdl))
mem_buf_relinquish_mem(hdl);
} else {
txn->txn_ret = mem_buf_decode_alloc_resp(buf, size,
txn->resp_buf);
complete(&txn->txn_done);
}
mutex_unlock(&mem_buf_idr_mutex);
}
static void mem_buf_process_msg(void *buf, size_t size)
{
struct mem_buf_msg_hdr *hdr = buf;
struct mem_buf_rmt_msg *rmt_msg;
work_func_t work_fn;
pr_debug("%s: mem-buf message received\n", __func__);
if (size < sizeof(*hdr)) {
pr_err("%s: message received is not of a proper size: 0x%lx\n",
__func__, size);
kfree(buf);
return;
}
if ((hdr->msg_type == MEM_BUF_ALLOC_RESP) &&
(mem_buf_capability & MEM_BUF_CAP_CONSUMER)) {
mem_buf_process_alloc_resp(hdr, buf, size);
kfree(buf);
} else if ((hdr->msg_type == MEM_BUF_ALLOC_REQ ||
hdr->msg_type == MEM_BUF_ALLOC_RELINQUISH) &&
(mem_buf_capability & MEM_BUF_CAP_SUPPLIER)) {
rmt_msg = kmalloc(sizeof(*rmt_msg), GFP_KERNEL);
if (!rmt_msg) {
kfree(buf);
return;
}
rmt_msg->msg = buf;
rmt_msg->msg_size = size;
work_fn = hdr->msg_type == MEM_BUF_ALLOC_REQ ?
mem_buf_alloc_req_work : mem_buf_relinquish_work;
INIT_WORK(&rmt_msg->work, work_fn);
queue_work(mem_buf_wq, &rmt_msg->work);
} else {
pr_err("%s: received message of unknown type: %d\n", __func__,
hdr->msg_type);
kfree(buf);
return;
}
}
static int mem_buf_msgq_recv_fn(void *unused)
{
void *buf;
size_t size;
int ret;
while (!kthread_should_stop()) {
buf = kzalloc(HH_MSGQ_MAX_MSG_SIZE_BYTES, GFP_KERNEL);
if (!buf)
continue;
ret = hh_msgq_recv(mem_buf_hh_msgq_hdl, buf,
HH_MSGQ_MAX_MSG_SIZE_BYTES, &size, 0);
if (ret < 0) {
kfree(buf);
pr_err_ratelimited("%s failed to receive message rc: %d\n",
__func__, ret);
} else {
mem_buf_process_msg(buf, size);
}
}
return 0;
}
/* Functions invoked when treating allocation requests to other VMs. */
static size_t mem_buf_get_mem_type_alloc_req_size(enum mem_buf_mem_type type)
{
if (type == MEM_BUF_ION_MEM_TYPE)
return sizeof(struct mem_buf_ion_alloc_data);
return 0;
}
static void mem_buf_populate_alloc_req_arb_payload(void *dst, void *src,
enum mem_buf_mem_type type)
{
struct mem_buf_ion_alloc_data *alloc_req_data;
struct mem_buf_ion_data *src_ion_data;
if (type == MEM_BUF_ION_MEM_TYPE) {
alloc_req_data = dst;
src_ion_data = src;
alloc_req_data->heap_id = src_ion_data->heap_id;
}
}
static void *mem_buf_construct_alloc_req(struct mem_buf_desc *membuf,
int txn_id, size_t *msg_size_ptr)
{
size_t tot_size, alloc_req_size, acl_desc_size;
void *req_buf, *arb_payload;
unsigned int nr_acl_entries = membuf->acl_desc->n_acl_entries;
struct mem_buf_alloc_req *req;
alloc_req_size = offsetof(struct mem_buf_alloc_req,
acl_desc.acl_entries[nr_acl_entries]);
tot_size = alloc_req_size +
mem_buf_get_mem_type_alloc_req_size(membuf->src_mem_type);
req_buf = kzalloc(tot_size, GFP_KERNEL);
if (!req_buf)
return ERR_PTR(-ENOMEM);
*msg_size_ptr = tot_size;
req = req_buf;
req->hdr.txn_id = txn_id;
req->hdr.msg_type = MEM_BUF_ALLOC_REQ;
req->size = membuf->size;
req->src_mem_type = membuf->src_mem_type;
acl_desc_size = offsetof(struct hh_acl_desc,
acl_entries[nr_acl_entries]);
memcpy(&req->acl_desc, membuf->acl_desc, acl_desc_size);
arb_payload = req_buf + alloc_req_size;
mem_buf_populate_alloc_req_arb_payload(arb_payload, membuf->src_data,
membuf->src_mem_type);
trace_send_alloc_req(req);
return req_buf;
}
static int mem_buf_request_mem(struct mem_buf_desc *membuf)
{
struct mem_buf_txn txn;
void *alloc_req_msg;
size_t msg_size;
hh_memparcel_handle_t resp_hdl;
int ret;
ret = mem_buf_init_txn(&txn, &resp_hdl);
if (ret)
return ret;
alloc_req_msg = mem_buf_construct_alloc_req(membuf, txn.txn_id,
&msg_size);
if (IS_ERR(alloc_req_msg)) {
ret = PTR_ERR(alloc_req_msg);
goto out;
}
ret = mem_buf_msg_send(alloc_req_msg, msg_size);
/*
* Free the buffer regardless of the return value as the hypervisor
* would have consumed the data in the case of a success.
*/
kfree(alloc_req_msg);
if (ret < 0)
goto out;
ret = mem_buf_txn_wait(&txn);
if (ret < 0)
goto out;
membuf->memparcel_hdl = resp_hdl;
out:
mem_buf_destroy_txn(&txn);
return ret;
}
static void mem_buf_relinquish_mem(u32 memparcel_hdl)
{
struct mem_buf_alloc_relinquish *msg;
int ret;
msg = kzalloc(sizeof(*msg), GFP_KERNEL);
if (!msg)
return;
msg->hdr.msg_type = MEM_BUF_ALLOC_RELINQUISH;
msg->hdl = memparcel_hdl;
trace_send_relinquish_msg(msg);
ret = hh_msgq_send(mem_buf_hh_msgq_hdl, msg, sizeof(*msg), 0);
/*
* Free the buffer regardless of the return value as the hypervisor
* would have consumed the data in the case of a success.
*/
kfree(msg);
if (ret < 0)
pr_err("%s failed to send memory relinquish message rc: %d\n",
__func__, ret);
else
pr_debug("%s: allocation relinquish message sent\n", __func__);
}
static struct hh_sgl_desc *mem_buf_map_mem_s2(
hh_memparcel_handle_t memparcel_hdl,
struct hh_acl_desc *acl_desc)
{
struct hh_sgl_desc *sgl_desc;
if (!acl_desc)
return ERR_PTR(-EINVAL);
pr_debug("%s: adding CPU MMU stage 2 mappings\n", __func__);
sgl_desc = hh_rm_mem_accept(memparcel_hdl, HH_RM_MEM_TYPE_NORMAL,
mem_buf_get_mem_xfer_type(acl_desc),
HH_RM_MEM_ACCEPT_VALIDATE_ACL_ATTRS |
HH_RM_MEM_ACCEPT_DONE, 0, acl_desc, NULL,
NULL, 0);
if (IS_ERR(sgl_desc)) {
pr_err("%s failed to map memory in stage 2 rc: %d\n", __func__,
PTR_ERR(sgl_desc));
return sgl_desc;
}
trace_map_mem_s2(memparcel_hdl, sgl_desc);
return sgl_desc;
}
static int mem_buf_unmap_mem_s2(hh_memparcel_handle_t memparcel_hdl)
{
int ret;
pr_debug("%s: removing CPU MMU stage 2 mappings\n", __func__);
ret = hh_rm_mem_release(memparcel_hdl, 0);
if (ret < 0)
pr_err("%s: Failed to release memparcel hdl: 0x%lx rc: %d\n",
__func__, memparcel_hdl, ret);
else
pr_debug("%s: CPU MMU stage 2 mappings removed\n", __func__);
return ret;
}
#ifdef CONFIG_MEMORY_HOTPLUG
static int mem_buf_map_mem_s1(struct hh_sgl_desc *sgl_desc)
{
int i, ret;
unsigned int nid;
u64 base, size;
struct mhp_restrictions restrictions = {};
if (!sgl_desc || !sgl_desc->n_sgl_entries)
return -EINVAL;
pr_debug("%s: Creating CPU MMU stage 1 mappings\n", __func__);
mem_hotplug_begin();
for (i = 0; i < sgl_desc->n_sgl_entries; i++) {
base = sgl_desc->sgl_entries[i].ipa_base;
size = sgl_desc->sgl_entries[i].size;
if (!IS_ALIGNED(base, MEM_BUF_MHP_ALIGNMENT) ||
!IS_ALIGNED(size, MEM_BUF_MHP_ALIGNMENT)) {
ret = -EINVAL;
pr_err("%s: IPA base: 0x%lx or size: 0x%lx not aligned properly\n",
__func__, base, size);
goto err_add_mem;
}
nid = memory_add_physaddr_to_nid(base);
memblock_add_node(base, size, nid);
ret = arch_add_memory(nid, base, size, &restrictions);
if (ret) {
pr_err("%s failed to map memory in stage 1 rc: %d\n",
__func__, ret);
goto err_add_mem;
}
}
mem_hotplug_done();
pr_debug("%s: CPU MMU stage 1 mappings created\n", __func__);
return 0;
err_add_mem:
for (i = i - 1; i >= 0; i--) {
base = sgl_desc->sgl_entries[i].ipa_base;
size = sgl_desc->sgl_entries[i].size;
nid = memory_add_physaddr_to_nid(base);
arch_remove_memory(nid, base, size, NULL);
memblock_remove(base, size);
}
mem_hotplug_done();
return ret;
}
#else /* CONFIG_MEMORY_HOTPLUG */
static inline int mem_buf_map_mem_s1(struct hh_sgl_desc *sgl_desc)
{
return -EINVAL;
}
#endif /* CONFIG_MEMORY_HOTPLUG */
#ifdef CONFIG_MEMORY_HOTREMOVE
static int mem_buf_unmap_mem_s1(struct hh_sgl_desc *sgl_desc)
{
int ret;
unsigned int i, nid;
u64 base, size;
if (!sgl_desc || !sgl_desc->n_sgl_entries)
return -EINVAL;
pr_debug("%s: Removing CPU MMU stage 1 mappings\n", __func__);
mem_hotplug_begin();
for (i = 0; i < sgl_desc->n_sgl_entries; i++) {
base = sgl_desc->sgl_entries[i].ipa_base;
size = sgl_desc->sgl_entries[i].size;
nid = memory_add_physaddr_to_nid(base);
arch_remove_memory(nid, base, size, NULL);
ret = memblock_remove(base, size);
if (ret) {
pr_err("%s: memblock_remove failed rc: %d\n", __func__,
ret);
goto out;
}
}
out:
mem_hotplug_done();
if (!ret)
pr_debug("%s: CPU MMU stage 1 mappings removed\n", __func__);
return ret;
}
#else /* CONFIG_MEMORY_HOTREMOVE */
static inline int mem_buf_unmap_mem_s1(struct hh_sgl_desc *sgl_desc)
{
return -EINVAL;
}
#endif /* CONFIG_MEMORY_HOTREMOVE */
static int mem_buf_add_ion_mem(struct sg_table *sgt, void *dst_data)
{
struct mem_buf_ion_data *dst_ion_data = dst_data;
return msm_ion_heap_add_memory(dst_ion_data->heap_id, sgt);
}
static int mem_buf_add_mem_type(enum mem_buf_mem_type type, void *dst_data,
struct sg_table *sgt)
{
if (type == MEM_BUF_ION_MEM_TYPE)
return mem_buf_add_ion_mem(sgt, dst_data);
return -EINVAL;
}
static int mem_buf_add_mem(struct mem_buf_desc *membuf)
{
int i, ret, nr_sgl_entries;
struct sg_table sgt;
struct scatterlist *sgl;
u64 base, size;
pr_debug("%s: adding memory to destination\n", __func__);
nr_sgl_entries = membuf->sgl_desc->n_sgl_entries;
ret = sg_alloc_table(&sgt, nr_sgl_entries, GFP_KERNEL);
if (ret)
return ret;
for_each_sg(sgt.sgl, sgl, nr_sgl_entries, i) {
base = membuf->sgl_desc->sgl_entries[i].ipa_base;
size = membuf->sgl_desc->sgl_entries[i].size;
sg_set_page(sgl, phys_to_page(base), size, 0);
}
ret = mem_buf_add_mem_type(membuf->dst_mem_type, membuf->dst_data,
&sgt);
if (ret)
pr_err("%s failed to add memory to destination rc: %d\n",
__func__, ret);
else
pr_debug("%s: memory added to destination\n", __func__);
sg_free_table(&sgt);
return ret;
}
static int mem_buf_remove_ion_mem(struct sg_table *sgt, void *dst_data)
{
struct mem_buf_ion_data *dst_ion_data = dst_data;
return msm_ion_heap_remove_memory(dst_ion_data->heap_id, sgt);
}
static int mem_buf_remove_mem_type(enum mem_buf_mem_type type, void *dst_data,
struct sg_table *sgt)
{
if (type == MEM_BUF_ION_MEM_TYPE)
return mem_buf_remove_ion_mem(sgt, dst_data);
return -EINVAL;
}
static int mem_buf_remove_mem(struct mem_buf_desc *membuf)
{
int i, ret, nr_sgl_entries;
struct sg_table sgt;
struct scatterlist *sgl;
u64 base, size;
pr_debug("%s: removing memory from destination\n", __func__);
nr_sgl_entries = membuf->sgl_desc->n_sgl_entries;
ret = sg_alloc_table(&sgt, nr_sgl_entries, GFP_KERNEL);
if (ret)
return ret;
for_each_sg(sgt.sgl, sgl, nr_sgl_entries, i) {
base = membuf->sgl_desc->sgl_entries[i].ipa_base;
size = membuf->sgl_desc->sgl_entries[i].size;
sg_set_page(sgl, phys_to_page(base), size, 0);
}
ret = mem_buf_remove_mem_type(membuf->dst_mem_type, membuf->dst_data,
&sgt);
if (ret)
pr_err("%s failed to remove memory from destination rc: %d\n",
__func__, ret);
else
pr_debug("%s: memory removed from destination\n", __func__);
sg_free_table(&sgt);
return ret;
}
static bool is_valid_mem_buf_vmid(u32 mem_buf_vmid)
{
if ((mem_buf_vmid == MEM_BUF_VMID_PRIMARY_VM) ||
(mem_buf_vmid == MEM_BUF_VMID_TRUSTED_VM))
return true;
pr_err_ratelimited("%s: Invalid mem-buf VMID detected\n", __func__);
return false;
}
static bool is_valid_mem_buf_perms(u32 mem_buf_perms)
{
if (mem_buf_perms & ~MEM_BUF_PERM_VALID_FLAGS) {
pr_err_ratelimited("%s: Invalid mem-buf permissions detected\n",
__func__);
return false;
}
return true;
}
static int mem_buf_vmid_to_vmid(u32 mem_buf_vmid)
{
int ret;
hh_vmid_t vmid;
enum hh_vm_names vm_name;
if (!is_valid_mem_buf_vmid(mem_buf_vmid))
return -EINVAL;
if (mem_buf_vmid == MEM_BUF_VMID_PRIMARY_VM)
vm_name = HH_PRIMARY_VM;
else if (mem_buf_vmid == MEM_BUF_VMID_TRUSTED_VM)
vm_name = HH_TRUSTED_VM;
else
return -EINVAL;
ret = hh_rm_get_vmid(vm_name, &vmid);
if (!ret)
return vmid;
return ret;
}
static int mem_buf_perms_to_perms(u32 mem_buf_perms)
{
int perms = 0;
if (!is_valid_mem_buf_perms(mem_buf_perms))
return -EINVAL;
if (mem_buf_perms & MEM_BUF_PERM_FLAG_READ)
perms |= PERM_READ;
if (mem_buf_perms & MEM_BUF_PERM_FLAG_WRITE)
perms |= PERM_WRITE;
if (mem_buf_perms & MEM_BUF_PERM_FLAG_EXEC)
perms |= PERM_EXEC;
return perms;
}
static struct hh_acl_desc *mem_buf_acl_to_hh_acl(unsigned int nr_acl_entries,
struct acl_entry *entries)
{
unsigned int i;
int ret;
u32 mem_buf_vmid, mem_buf_perms;
int vmid, perms;
struct hh_acl_desc *acl_desc = kzalloc(offsetof(struct hh_acl_desc,
acl_entries[nr_acl_entries]),
GFP_KERNEL);
if (!acl_desc)
return ERR_PTR(-ENOMEM);
acl_desc->n_acl_entries = nr_acl_entries;
for (i = 0; i < nr_acl_entries; i++) {
mem_buf_vmid = entries[i].vmid;
mem_buf_perms = entries[i].perms;
vmid = mem_buf_vmid_to_vmid(mem_buf_vmid);
perms = mem_buf_perms_to_perms(mem_buf_perms);
if (vmid < 0 || perms < 0) {
ret = -EINVAL;
goto err_inv_vmid_perms;
}
acl_desc->acl_entries[i].vmid = vmid;
acl_desc->acl_entries[i].perms = perms;
}
return acl_desc;
err_inv_vmid_perms:
kfree(acl_desc);
return ERR_PTR(ret);
}
static void *mem_buf_retrieve_ion_mem_type_data_user(
struct mem_buf_ion_data __user *mem_type_data)
{
return memdup_user((const void __user *)mem_type_data,
sizeof(*mem_type_data));
}
static void *mem_buf_retrieve_mem_type_data_user(enum mem_buf_mem_type mem_type,
void __user *mem_type_data)
{
void *data = ERR_PTR(-EINVAL);
if (mem_type == MEM_BUF_ION_MEM_TYPE)
data = mem_buf_retrieve_ion_mem_type_data_user(mem_type_data);
return data;
}
static void *mem_buf_retrieve_ion_mem_type_data(
struct mem_buf_ion_data *mem_type_data)
{
pr_debug("%s: ION heap ID: 0x%x\n", __func__, mem_type_data->heap_id);
return kmemdup(mem_type_data, sizeof(*mem_type_data), GFP_KERNEL);
}
static void *mem_buf_retrieve_mem_type_data(enum mem_buf_mem_type mem_type,
void *mem_type_data)
{
void *data = ERR_PTR(-EINVAL);
if (mem_type == MEM_BUF_ION_MEM_TYPE)
data = mem_buf_retrieve_ion_mem_type_data(mem_type_data);
return data;
}
static void mem_buf_free_ion_mem_type_data(struct mem_buf_ion_data *ion_data)
{
kfree(ion_data);
}
static void mem_buf_free_mem_type_data(enum mem_buf_mem_type mem_type,
void *mem_type_data)
{
if (mem_type == MEM_BUF_ION_MEM_TYPE)
mem_buf_free_ion_mem_type_data(mem_type_data);
}
static int mem_buf_buffer_release(struct inode *inode, struct file *filp)
{
struct mem_buf_desc *membuf = filp->private_data;
int ret;
mutex_lock(&mem_buf_list_lock);
list_del(&membuf->entry);
mutex_unlock(&mem_buf_list_lock);
ret = mem_buf_remove_mem(membuf);
if (ret < 0)
goto out_free_mem;
ret = mem_buf_unmap_mem_s1(membuf->sgl_desc);
if (ret < 0)
goto out_free_mem;
ret = mem_buf_unmap_mem_s2(membuf->memparcel_hdl);
if (ret < 0)
goto out_free_mem;
mem_buf_relinquish_mem(membuf->memparcel_hdl);
out_free_mem:
mem_buf_free_mem_type_data(membuf->dst_mem_type, membuf->dst_data);
mem_buf_free_mem_type_data(membuf->src_mem_type, membuf->src_data);
kfree(membuf->sgl_desc);
kfree(membuf->acl_desc);
kfree(membuf);
return ret;
}
static const struct file_operations mem_buf_fops = {
.release = mem_buf_buffer_release,
};
static bool is_valid_mem_type(enum mem_buf_mem_type mem_type)
{
return mem_type >= MEM_BUF_ION_MEM_TYPE &&
mem_type < MEM_BUF_MAX_MEM_TYPE;
}
void *mem_buf_alloc(struct mem_buf_allocation_data *alloc_data)
{
int ret;
struct file *filp;
struct mem_buf_desc *membuf;
struct hh_sgl_desc *sgl_desc;
if (!(mem_buf_capability & MEM_BUF_CAP_CONSUMER))
return ERR_PTR(-ENOTSUPP);
if (!alloc_data || !alloc_data->size || !alloc_data->nr_acl_entries ||
!alloc_data->acl_list ||
(alloc_data->nr_acl_entries > MEM_BUF_MAX_NR_ACL_ENTS) ||
!is_valid_mem_type(alloc_data->src_mem_type) ||
!is_valid_mem_type(alloc_data->dst_mem_type))
return ERR_PTR(-EINVAL);
membuf = kzalloc(sizeof(*membuf), GFP_KERNEL);
if (!membuf)
return ERR_PTR(-ENOMEM);
pr_debug("%s: mem buf alloc begin\n", __func__);
membuf->size = ALIGN(alloc_data->size, MEM_BUF_MHP_ALIGNMENT);
membuf->acl_desc = mem_buf_acl_to_hh_acl(alloc_data->nr_acl_entries,
alloc_data->acl_list);
if (IS_ERR(membuf->acl_desc)) {
ret = PTR_ERR(membuf->acl_desc);
goto err_alloc_acl_list;
}
membuf->src_mem_type = alloc_data->src_mem_type;
membuf->dst_mem_type = alloc_data->dst_mem_type;
membuf->src_data =
mem_buf_retrieve_mem_type_data(alloc_data->src_mem_type,
alloc_data->src_data);
if (IS_ERR(membuf->src_data)) {
ret = PTR_ERR(membuf->src_data);
goto err_alloc_src_data;
}
membuf->dst_data =
mem_buf_retrieve_mem_type_data(alloc_data->dst_mem_type,
alloc_data->dst_data);
if (IS_ERR(membuf->dst_data)) {
ret = PTR_ERR(membuf->dst_data);
goto err_alloc_dst_data;
}
trace_mem_buf_alloc_info(membuf->size, membuf->src_mem_type,
membuf->dst_mem_type, membuf->acl_desc);
ret = mem_buf_request_mem(membuf);
if (ret)
goto err_mem_req;
sgl_desc = mem_buf_map_mem_s2(membuf->memparcel_hdl, membuf->acl_desc);
if (IS_ERR(sgl_desc))
goto err_map_mem_s2;
membuf->sgl_desc = sgl_desc;
ret = mem_buf_map_mem_s1(membuf->sgl_desc);
if (ret)
goto err_map_mem_s1;
ret = mem_buf_add_mem(membuf);
if (ret)
goto err_add_mem;
filp = anon_inode_getfile("membuf", &mem_buf_fops, membuf, O_RDWR);
if (IS_ERR(filp)) {
ret = PTR_ERR(filp);
goto err_get_file;
}
membuf->filp = filp;
mutex_lock(&mem_buf_list_lock);
list_add_tail(&membuf->entry, &mem_buf_list);
mutex_unlock(&mem_buf_list_lock);
pr_debug("%s: mem buf alloc success\n", __func__);
return membuf;
err_get_file:
if (mem_buf_remove_mem(membuf) < 0) {
kfree(membuf->sgl_desc);
goto err_mem_req;
}
err_add_mem:
if (mem_buf_unmap_mem_s1(membuf->sgl_desc) < 0) {
kfree(membuf->sgl_desc);
goto err_mem_req;
}
err_map_mem_s1:
kfree(membuf->sgl_desc);
if (mem_buf_unmap_mem_s2(membuf->memparcel_hdl) < 0)
goto err_mem_req;
err_map_mem_s2:
mem_buf_relinquish_mem(membuf->memparcel_hdl);
err_mem_req:
mem_buf_free_mem_type_data(membuf->dst_mem_type, membuf->dst_data);
err_alloc_dst_data:
mem_buf_free_mem_type_data(membuf->src_mem_type, membuf->src_data);
err_alloc_src_data:
kfree(membuf->acl_desc);
err_alloc_acl_list:
kfree(membuf);
return ERR_PTR(ret);
}
EXPORT_SYMBOL(mem_buf_alloc);
static int _mem_buf_get_fd(struct file *filp)
{
int fd;
if (!filp)
return -EINVAL;
fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0)
return fd;
fd_install(fd, filp);
return fd;
}
int mem_buf_get_fd(void *membuf_desc)
{
struct mem_buf_desc *membuf = membuf_desc;
if (!membuf_desc)
return -EINVAL;
return _mem_buf_get_fd(membuf->filp);
}
EXPORT_SYMBOL(mem_buf_get_fd);
static int mem_buf_get_export_fd(struct mem_buf_export *export_buf)
{
return _mem_buf_get_fd(export_buf->filp);
}
static int mem_buf_get_import_fd(struct mem_buf_import *import_buf)
{
return dma_buf_fd(import_buf->dmabuf, O_CLOEXEC);
}
static void _mem_buf_put(struct file *filp)
{
fput(filp);
}
void mem_buf_put(void *membuf_desc)
{
struct mem_buf_desc *membuf = membuf_desc;
if (membuf && membuf->filp)
_mem_buf_put(membuf->filp);
}
EXPORT_SYMBOL(mem_buf_put);
static void mem_buf_export_put(struct mem_buf_export *export_buf)
{
_mem_buf_put(export_buf->filp);
}
static void mem_buf_import_put(struct mem_buf_import *import_buf)
{
dma_buf_put(import_buf->dmabuf);
}
static bool is_mem_buf_file(struct file *filp)
{
return filp->f_op == &mem_buf_fops;
}
void *mem_buf_get(int fd)
{
struct file *filp;
filp = fget(fd);
if (!filp)
return ERR_PTR(-EBADF);
if (!is_mem_buf_file(filp)) {
fput(filp);
return ERR_PTR(-EINVAL);
}
return filp->private_data;
}
EXPORT_SYMBOL(mem_buf_get);
/* Userspace machinery */
static int mem_buf_prep_alloc_data(struct mem_buf_allocation_data *alloc_data,
struct mem_buf_alloc_ioctl_arg *allocation_args)
{
struct acl_entry *acl_list;
unsigned int nr_acl_entries = allocation_args->nr_acl_entries;
int ret;
alloc_data->size = allocation_args->size;
alloc_data->nr_acl_entries = nr_acl_entries;
alloc_data->acl_list =
memdup_user((const void __user *)allocation_args->acl_list,
sizeof(*acl_list) * nr_acl_entries);
if (IS_ERR(alloc_data->acl_list))
return PTR_ERR(alloc_data->acl_list);
alloc_data->src_mem_type = allocation_args->src_mem_type;
alloc_data->dst_mem_type = allocation_args->dst_mem_type;
alloc_data->src_data =
mem_buf_retrieve_mem_type_data_user(
allocation_args->src_mem_type,
(void __user *)allocation_args->src_data);
if (IS_ERR(alloc_data->src_data)) {
ret = PTR_ERR(alloc_data->src_data);
goto err_alloc_src_data;
}
alloc_data->dst_data =
mem_buf_retrieve_mem_type_data_user(
allocation_args->dst_mem_type,
(void __user *)allocation_args->dst_data);
if (IS_ERR(alloc_data->dst_data)) {
ret = PTR_ERR(alloc_data->dst_data);
goto err_alloc_dst_data;
}
return 0;
err_alloc_dst_data:
mem_buf_free_mem_type_data(alloc_data->src_mem_type,
alloc_data->src_data);
err_alloc_src_data:
kfree(alloc_data->acl_list);
return ret;
}
static void mem_buf_free_alloc_data(struct mem_buf_allocation_data *alloc_data)
{
mem_buf_free_mem_type_data(alloc_data->dst_mem_type,
alloc_data->dst_data);
mem_buf_free_mem_type_data(alloc_data->src_mem_type,
alloc_data->src_data);
kfree(alloc_data->acl_list);
}
static int mem_buf_alloc_fd(struct mem_buf_alloc_ioctl_arg *allocation_args)
{
struct mem_buf_desc *membuf;
struct mem_buf_allocation_data alloc_data;
int ret;
ret = mem_buf_prep_alloc_data(&alloc_data, allocation_args);
if (ret < 0)
return ret;
membuf = mem_buf_alloc(&alloc_data);
if (IS_ERR(membuf)) {
ret = PTR_ERR(membuf);
goto out;
}
ret = mem_buf_get_fd(membuf);
if (ret < 0)
mem_buf_put(membuf);
out:
mem_buf_free_alloc_data(&alloc_data);
return ret;
}
union mem_buf_ioctl_arg {
struct mem_buf_alloc_ioctl_arg allocation;
struct mem_buf_export_ioctl_arg export;
struct mem_buf_import_ioctl_arg import;
};
static int validate_ioctl_arg(union mem_buf_ioctl_arg *arg, unsigned int cmd)
{
switch (cmd) {
case MEM_BUF_IOC_ALLOC:
{
struct mem_buf_alloc_ioctl_arg *allocation = &arg->allocation;
if (!allocation->size || !allocation->nr_acl_entries ||
!allocation->acl_list ||
(allocation->nr_acl_entries > MEM_BUF_MAX_NR_ACL_ENTS) ||
!is_valid_mem_type(allocation->src_mem_type) ||
!is_valid_mem_type(allocation->dst_mem_type) ||
allocation->reserved0 || allocation->reserved1 ||
allocation->reserved2)
return -EINVAL;
break;
}
case MEM_BUF_IOC_EXPORT:
{
struct mem_buf_export_ioctl_arg *export = &arg->export;
if (!export->nr_acl_entries || !export->acl_list ||
export->nr_acl_entries > MEM_BUF_MAX_NR_ACL_ENTS ||
export->reserved0 || export->reserved1 || export->reserved2)
return -EINVAL;
break;
}
case MEM_BUF_IOC_IMPORT:
{
struct mem_buf_import_ioctl_arg *import = &arg->import;
if (!import->nr_acl_entries || !import->acl_list ||
import->nr_acl_entries > MEM_BUF_MAX_NR_ACL_ENTS ||
import->reserved0 || import->reserved1 || import->reserved2)
return -EINVAL;
break;
}
default:
return -EINVAL;
}
return 0;
}
static bool mem_buf_hlos_accessible(int *vmids, u32 nr_vmids)
{
int i;
if (!vmids || !nr_vmids)
return false;
for (i = 0; i < nr_vmids; i++)
if (vmids[i] == VMID_HLOS)
return true;
return false;
}
static int mem_buf_export_release(struct inode *inode, struct file *filp)
{
int ret;
struct mem_buf_export *export_buf = filp->private_data;
bool dma_buf_freeable = true;
ret = mem_buf_unassign_mem(export_buf->mem_sgt, export_buf->dst_vmids,
export_buf->nr_vmids);
if (ret < 0)
dma_buf_freeable = false;
if (!mem_buf_hlos_accessible(export_buf->dst_vmids,
export_buf->nr_vmids) && dma_buf_freeable)
msm_ion_dma_buf_unlock(export_buf->dmabuf);
kfree(export_buf->dst_vmids);
if (dma_buf_freeable) {
dma_buf_unmap_attachment(export_buf->attachment,
export_buf->mem_sgt,
DMA_BIDIRECTIONAL);
dma_buf_detach(export_buf->dmabuf, export_buf->attachment);
dma_buf_put(export_buf->dmabuf);
}
kfree(export_buf);
return ret;
}
static const struct file_operations mem_buf_export_fops = {
.release = mem_buf_export_release,
};
static int mem_buf_acl_to_vmid_perms_list(unsigned int nr_acl_entries,
const void __user *acl_entries,
int **dst_vmids, int **dst_perms)
{
int ret, i, *vmids, *perms;
struct acl_entry entry;
if (!nr_acl_entries || !acl_entries)
return -EINVAL;
vmids = kmalloc_array(nr_acl_entries, sizeof(*vmids), GFP_KERNEL);
if (!vmids)
return -ENOMEM;
perms = kmalloc_array(nr_acl_entries, sizeof(*perms), GFP_KERNEL);
if (!perms) {
kfree(vmids);
return -ENOMEM;
}
for (i = 0; i < nr_acl_entries; i++) {
ret = copy_struct_from_user(&entry, sizeof(entry),
acl_entries + (sizeof(entry) * i),
sizeof(entry));
if (ret < 0)
goto out;
vmids[i] = mem_buf_vmid_to_vmid(entry.vmid);
perms[i] = mem_buf_perms_to_perms(entry.perms);
if (vmids[i] < 0 || perms[i] < 0) {
ret = -EINVAL;
goto out;
}
}
*dst_vmids = vmids;
*dst_perms = perms;
return ret;
out:
kfree(perms);
kfree(vmids);
return ret;
}
static struct mem_buf_export *mem_buf_export_dma_buf(int dma_buf_fd,
unsigned int nr_acl_entries,
const void __user *acl_entries,
hh_memparcel_handle_t *memparcel_hdl)
{
int ret;
struct mem_buf_export *export_buf;
struct dma_buf *dmabuf;
struct dma_buf_attachment *attachment;
struct sg_table *sgt;
int *dst_vmids, *dst_perms;
bool dma_buf_freeable = true;
struct file *filp;
unsigned long flags = 0;
if (!nr_acl_entries || !acl_entries || !memparcel_hdl)
return ERR_PTR(-EINVAL);
export_buf = kmalloc(sizeof(*export_buf), GFP_KERNEL);
if (!export_buf)
return ERR_PTR(-ENOMEM);
dmabuf = dma_buf_get(dma_buf_fd);
if (IS_ERR(dmabuf)) {
pr_err_ratelimited("%s: dma_buf_get failed rc: %d\n", __func__,
PTR_ERR(dmabuf));
ret = PTR_ERR(dmabuf);
goto err_dma_buf_get;
}
export_buf->dmabuf = dmabuf;
ret = dma_buf_get_flags(dmabuf, &flags);
if (ret < 0) {
pr_err_ratelimited("%s: dma_buf_get_flags failed rc: %d\n",
__func__, ret);
goto err_dma_buf_attach;
} else if (!(flags & ION_FLAG_CACHED)) {
ret = -EINVAL;
pr_err_ratelimited("%s: only cached buffers can be exported\n",
__func__);
goto err_dma_buf_attach;
} else if (flags & (ION_FLAG_SECURE | ION_FLAGS_CP_MASK)) {
ret = -EINVAL;
pr_err_ratelimited("%s: only non-secure allocations can be exported\n",
__func__);
goto err_dma_buf_attach;
}
attachment = dma_buf_attach(dmabuf, mem_buf_dev);
if (IS_ERR(attachment)) {
pr_err_ratelimited("%s: dma_buf_attach failed rc: %d\n",
__func__, PTR_ERR(attachment));
ret = PTR_ERR(attachment);
goto err_dma_buf_attach;
}
export_buf->attachment = attachment;
sgt = dma_buf_map_attachment(attachment, DMA_BIDIRECTIONAL);
if (IS_ERR(sgt)) {
pr_err_ratelimited("%s dma_buf_map_attachment failed rc: %d\n",
__func__, PTR_ERR(sgt));
ret = PTR_ERR(sgt);
goto err_map_attachment;
}
export_buf->mem_sgt = sgt;
ret = mem_buf_acl_to_vmid_perms_list(nr_acl_entries, acl_entries,
&dst_vmids, &dst_perms);
if (ret < 0) {
pr_err_ratelimited("%s failed to copy ACL rc: %d\n", __func__,
ret);
goto err_cpy_acl_entries;
}
export_buf->nr_vmids = nr_acl_entries;
export_buf->dst_vmids = dst_vmids;
if (!mem_buf_hlos_accessible(dst_vmids, nr_acl_entries)) {
ret = msm_ion_dma_buf_lock(dmabuf);
if (ret < 0) {
pr_err_ratelimited("%s failed to lock buffer rc: %d\n",
__func__, ret);
goto err_lock_mem;
}
}
ret = mem_buf_assign_mem(sgt, dst_vmids, dst_perms, nr_acl_entries);
if (ret < 0) {
if (ret == -EADDRNOTAVAIL)
dma_buf_freeable = false;
goto err_assign_mem;
}
ret = mem_buf_retrieve_memparcel_hdl(sgt, dst_vmids, dst_perms,
nr_acl_entries, memparcel_hdl);
if (ret < 0)
goto err_retrieve_hdl;
filp = anon_inode_getfile("membuf", &mem_buf_export_fops, export_buf,
O_RDWR);
if (IS_ERR(filp)) {
ret = PTR_ERR(filp);
goto err_retrieve_hdl;
}
export_buf->filp = filp;
kfree(dst_perms);
return export_buf;
err_retrieve_hdl:
if (mem_buf_unassign_mem(sgt, dst_vmids, nr_acl_entries) < 0)
dma_buf_freeable = false;
err_assign_mem:
if (!mem_buf_hlos_accessible(dst_vmids, nr_acl_entries) &&
dma_buf_freeable)
msm_ion_dma_buf_unlock(dmabuf);
err_lock_mem:
kfree(dst_vmids);
kfree(dst_perms);
err_cpy_acl_entries:
if (dma_buf_freeable)
dma_buf_unmap_attachment(attachment, sgt, DMA_BIDIRECTIONAL);
err_map_attachment:
if (dma_buf_freeable)
dma_buf_detach(dmabuf, attachment);
err_dma_buf_attach:
if (dma_buf_freeable)
dma_buf_put(dmabuf);
err_dma_buf_get:
kfree(export_buf);
return ERR_PTR(ret);
}
static size_t mem_buf_get_sgl_buf_size(struct hh_sgl_desc *sgl_desc)
{
size_t size = 0;
unsigned int i;
for (i = 0; i < sgl_desc->n_sgl_entries; i++)
size += sgl_desc->sgl_entries[i].size;
return size;
}
static struct mem_buf_import *mem_buf_import_dma_buf(
hh_memparcel_handle_t memparcel_hdl,
unsigned int nr_acl_entries,
const void __user *acl_list)
{
int ret;
struct mem_buf_import *import;
struct hh_acl_desc *acl_desc;
struct hh_sgl_desc *sgl_desc;
struct acl_entry *k_acl_list;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
struct dma_buf *dmabuf;
if (!nr_acl_entries || !acl_list)
return ERR_PTR(-EINVAL);
import = kzalloc(sizeof(*import), GFP_KERNEL);
if (!import)
return ERR_PTR(-ENOMEM);
import->memparcel_hdl = memparcel_hdl;
mutex_init(&import->lock);
INIT_LIST_HEAD(&import->attachments);
k_acl_list = memdup_user(acl_list, sizeof(*k_acl_list) *
nr_acl_entries);
if (IS_ERR(k_acl_list)) {
ret = PTR_ERR(k_acl_list);
goto err_out;
}
acl_desc = mem_buf_acl_to_hh_acl(nr_acl_entries, k_acl_list);
kfree(k_acl_list);
if (IS_ERR(acl_desc)) {
ret = PTR_ERR(acl_desc);
goto err_out;
}
sgl_desc = mem_buf_map_mem_s2(memparcel_hdl, acl_desc);
kfree(acl_desc);
if (IS_ERR(sgl_desc)) {
ret = PTR_ERR(sgl_desc);
goto err_out;
}
import->sgl_desc = sgl_desc;
import->size = mem_buf_get_sgl_buf_size(sgl_desc);
ret = mem_buf_map_mem_s1(sgl_desc);
if (ret < 0)
goto err_map_mem_s1;
exp_info.ops = &mem_buf_dma_buf_ops;
exp_info.size = import->size;
exp_info.flags = O_RDWR;
exp_info.priv = import;
dmabuf = dma_buf_export(&exp_info);
if (IS_ERR(dmabuf))
goto err_export_dma_buf;
import->dmabuf = dmabuf;
return import;
err_export_dma_buf:
mem_buf_unmap_mem_s1(sgl_desc);
err_map_mem_s1:
kfree(import->sgl_desc);
mem_buf_unmap_mem_s2(memparcel_hdl);
err_out:
kfree(import);
return ERR_PTR(ret);
}
void mem_buf_unimport_dma_buf(struct mem_buf_import *import_buf)
{
mem_buf_unmap_mem_s1(import_buf->sgl_desc);
kfree(import_buf->sgl_desc);
mem_buf_unmap_mem_s2(import_buf->memparcel_hdl);
mutex_destroy(&import_buf->lock);
kfree(import_buf);
}
static long mem_buf_dev_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
int fd;
unsigned int dir = _IOC_DIR(cmd);
union mem_buf_ioctl_arg ioctl_arg;
if (_IOC_SIZE(cmd) > sizeof(ioctl_arg))
return -EINVAL;
if (copy_from_user(&ioctl_arg, (void __user *)arg, _IOC_SIZE(cmd)))
return -EFAULT;
if (validate_ioctl_arg(&ioctl_arg, cmd) < 0)
return -EINVAL;
if (!(dir & _IOC_WRITE))
memset(&ioctl_arg, 0, sizeof(ioctl_arg));
switch (cmd) {
case MEM_BUF_IOC_ALLOC:
{
struct mem_buf_alloc_ioctl_arg *allocation =
&ioctl_arg.allocation;
if (!(mem_buf_capability & MEM_BUF_CAP_CONSUMER))
return -ENOTSUPP;
fd = mem_buf_alloc_fd(allocation);
if (fd < 0)
return fd;
allocation->mem_buf_fd = fd;
break;
}
case MEM_BUF_IOC_EXPORT:
{
struct mem_buf_export_ioctl_arg *export = &ioctl_arg.export;
u32 ret_memparcel_hdl;
struct mem_buf_export *export_buf;
if (!(mem_buf_capability & MEM_BUF_CAP_SUPPLIER))
return -ENOTSUPP;
export_buf = mem_buf_export_dma_buf(export->dma_buf_fd,
export->nr_acl_entries,
(const void __user *)export->acl_list,
&ret_memparcel_hdl);
if (IS_ERR(export_buf))
return PTR_ERR(export_buf);
fd = mem_buf_get_export_fd(export_buf);
if (fd < 0) {
mem_buf_export_put(export_buf);
return fd;
}
export->export_fd = fd;
export->memparcel_hdl = ret_memparcel_hdl;
break;
}
case MEM_BUF_IOC_IMPORT:
{
struct mem_buf_import_ioctl_arg *import = &ioctl_arg.import;
struct mem_buf_import *import_buf;
if (!(mem_buf_capability & MEM_BUF_CAP_CONSUMER))
return -ENOTSUPP;
import_buf = mem_buf_import_dma_buf(import->memparcel_hdl,
import->nr_acl_entries,
(const void __user *)import->acl_list);
if (IS_ERR(import_buf))
return PTR_ERR(import_buf);
fd = mem_buf_get_import_fd(import_buf);
if (fd < 0) {
mem_buf_import_put(import_buf);
return fd;
}
import->dma_buf_import_fd = fd;
break;
}
default:
return -ENOTTY;
}
if (dir & _IOC_READ) {
if (copy_to_user((void __user *)arg, &ioctl_arg,
_IOC_SIZE(cmd)))
return -EFAULT;
}
return 0;
}
static const struct file_operations mem_buf_dev_fops = {
.unlocked_ioctl = mem_buf_dev_ioctl,
};
static int mem_buf_probe(struct platform_device *pdev)
{
int ret;
struct device *dev = &pdev->dev;
struct device *class_dev;
u64 dma_mask = IS_ENABLED(CONFIG_ARM64) ? DMA_BIT_MASK(64) :
DMA_BIT_MASK(32);
if (of_property_match_string(dev->of_node, "qcom,mem-buf-capabilities",
"supplier") >= 0) {
mem_buf_capability = MEM_BUF_CAP_SUPPLIER;
} else if (of_property_match_string(dev->of_node,
"qcom,mem-buf-capabilities",
"consumer") >= 0) {
mem_buf_capability = MEM_BUF_CAP_CONSUMER;
} else if (of_property_match_string(dev->of_node,
"qcom,mem-buf-capabilities",
"dual") >= 0) {
mem_buf_capability = MEM_BUF_CAP_DUAL;
} else {
dev_err(dev, "Transfer direction property not present or not valid\n");
return -EINVAL;
}
ret = dma_set_mask_and_coherent(dev, dma_mask);
if (ret) {
dev_err(dev, "Unable to set dma mask: %d\n", ret);
return ret;
}
mem_buf_wq = alloc_ordered_workqueue("mem_buf_wq", WQ_HIGHPRI);
if (!mem_buf_wq) {
dev_err(dev, "Unable to initialize workqueue\n");
return -EINVAL;
}
mem_buf_msgq_recv_thr = kthread_create(mem_buf_msgq_recv_fn, NULL,
"mem_buf_rcvr");
if (IS_ERR(mem_buf_msgq_recv_thr)) {
dev_err(dev, "Failed to create msgq receiver thread rc: %d\n",
PTR_ERR(mem_buf_msgq_recv_thr));
ret = PTR_ERR(mem_buf_msgq_recv_thr);
goto err_kthread_create;
}
mem_buf_hh_msgq_hdl = hh_msgq_register(HH_MSGQ_LABEL_MEMBUF);
if (IS_ERR(mem_buf_hh_msgq_hdl)) {
ret = PTR_ERR(mem_buf_hh_msgq_hdl);
if (ret != -EPROBE_DEFER)
dev_err(dev,
"Message queue registration failed: rc: %d\n",
ret);
goto err_msgq_register;
}
cdev_init(&mem_buf_char_dev, &mem_buf_dev_fops);
ret = cdev_add(&mem_buf_char_dev, mem_buf_dev_no, MEM_BUF_MAX_DEVS);
if (ret < 0)
goto err_cdev_add;
mem_buf_dev = dev;
class_dev = device_create(mem_buf_class, NULL, mem_buf_dev_no, NULL,
"membuf");
if (IS_ERR(class_dev)) {
ret = PTR_ERR(class_dev);
goto err_dev_create;
}
wake_up_process(mem_buf_msgq_recv_thr);
return 0;
err_dev_create:
mem_buf_dev = NULL;
cdev_del(&mem_buf_char_dev);
err_cdev_add:
hh_msgq_unregister(mem_buf_hh_msgq_hdl);
mem_buf_hh_msgq_hdl = NULL;
err_msgq_register:
kthread_stop(mem_buf_msgq_recv_thr);
mem_buf_msgq_recv_thr = NULL;
err_kthread_create:
destroy_workqueue(mem_buf_wq);
mem_buf_wq = NULL;
return ret;
}
static int mem_buf_remove(struct platform_device *pdev)
{
mutex_lock(&mem_buf_list_lock);
if (!list_empty(&mem_buf_list))
dev_err(mem_buf_dev,
"Removing mem-buf driver while there are membufs\n");
mutex_unlock(&mem_buf_list_lock);
mutex_lock(&mem_buf_xfer_mem_list_lock);
if (!list_empty(&mem_buf_xfer_mem_list))
dev_err(mem_buf_dev,
"Removing mem-buf driver while memory is still lent\n");
mutex_unlock(&mem_buf_xfer_mem_list_lock);
device_destroy(mem_buf_class, mem_buf_dev_no);
mem_buf_dev = NULL;
cdev_del(&mem_buf_char_dev);
hh_msgq_unregister(mem_buf_hh_msgq_hdl);
mem_buf_hh_msgq_hdl = NULL;
kthread_stop(mem_buf_msgq_recv_thr);
mem_buf_msgq_recv_thr = NULL;
destroy_workqueue(mem_buf_wq);
mem_buf_wq = NULL;
return 0;
}
static const struct of_device_id mem_buf_match_tbl[] = {
{.compatible = "qcom,mem-buf"},
{},
};
static struct platform_driver mem_buf_driver = {
.probe = mem_buf_probe,
.remove = mem_buf_remove,
.driver = {
.name = "mem-buf",
.of_match_table = of_match_ptr(mem_buf_match_tbl),
},
};
static int __init mem_buf_init(void)
{
int ret;
ret = alloc_chrdev_region(&mem_buf_dev_no, 0, MEM_BUF_MAX_DEVS,
"membuf");
if (ret < 0)
goto err_chrdev_region;
mem_buf_class = class_create(THIS_MODULE, "membuf");
if (IS_ERR(mem_buf_class)) {
ret = PTR_ERR(mem_buf_class);
goto err_class_create;
}
ret = platform_driver_register(&mem_buf_driver);
if (ret < 0)
goto err_platform_drvr_register;
return 0;
err_platform_drvr_register:
class_destroy(mem_buf_class);
err_class_create:
unregister_chrdev_region(mem_buf_dev_no, MEM_BUF_MAX_DEVS);
err_chrdev_region:
return ret;
}
module_init(mem_buf_init);
static void __exit mem_buf_exit(void)
{
platform_driver_unregister(&mem_buf_driver);
class_destroy(mem_buf_class);
unregister_chrdev_region(mem_buf_dev_no, MEM_BUF_MAX_DEVS);
}
module_exit(mem_buf_exit);
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Memory Buffer Sharing driver");
MODULE_LICENSE("GPL v2");