From 549c0266e570da686f19e4435d76411cd7137954 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Mon, 11 Mar 2019 16:35:23 +0000 Subject: [PATCH] staging: vc-sm-cma: Add in userspace allocation API Replacing the functionality from the older vc-sm driver, add in a userspace API that allows allocation of buffers, and importing of dma-bufs. The driver hands out dma-buf fds, therefore much of the handling around lifespan and odd mmaps from the old driver goes away. Signed-off-by: Dave Stevenson --- .../staging/vc04_services/vc-sm-cma/vc_sm.c | 371 ++++++++++++++++-- .../vc04_services/vc-sm-cma/vc_sm_cma.c | 3 +- .../vc04_services/vc-sm-cma/vc_sm_cma.h | 2 +- include/linux/broadcom/vc_sm_cma_ioctl.h | 87 ++++ 4 files changed, 435 insertions(+), 28 deletions(-) create mode 100644 include/linux/broadcom/vc_sm_cma_ioctl.h --- a/drivers/staging/vc04_services/vc-sm-cma/vc_sm.c +++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,7 @@ #include "vc_sm.h" #include "vc_sm_cma.h" #include "vc_sm_knl.h" +#include /* ---- Private Constants and Types --------------------------------------- */ @@ -83,6 +85,8 @@ struct sm_pde_t { struct sm_state_t { struct platform_device *pdev; + struct miscdevice misc_dev; + struct sm_instance *sm_handle; /* Handle for videocore service. */ struct cma *cma_heap; @@ -346,7 +350,6 @@ static void vc_sm_release_resource(struc defer: mutex_unlock(&buffer->lock); - return; } /* Create support for private data tracking. */ @@ -381,7 +384,7 @@ static struct sg_table *dup_sg_table(str ret = sg_alloc_table(new_table, table->nents, GFP_KERNEL); if (ret) { kfree(new_table); - return ERR_PTR(-ENOMEM); + return ERR_PTR(ret); } new_sg = new_table->sgl; @@ -417,7 +420,7 @@ static int vc_sm_dma_buf_attach(struct d table = dup_sg_table(buf->sg_table); if (IS_ERR(table)) { kfree(a); - return -ENOMEM; + return PTR_ERR(table); } a->table = table; @@ -433,8 +436,8 @@ static int vc_sm_dma_buf_attach(struct d return 0; } -static void vc_sm_dma_buf_detatch(struct dma_buf *dmabuf, - struct dma_buf_attachment *attachment) +static void vc_sm_dma_buf_detach(struct dma_buf *dmabuf, + struct dma_buf_attachment *attachment) { struct vc_sm_dma_buf_attachment *a = attachment->priv; struct vc_sm_buffer *buf = dmabuf->priv; @@ -544,6 +547,9 @@ static void vc_sm_dma_buf_release(struct vc_sm_clean_up_dmabuf(buffer); pr_debug("%s clean_up dmabuf done\n", __func__); + /* buffer->lock will be destroyed by vc_sm_release_resource if finished + * with, otherwise unlocked. Do NOT unlock here. + */ vc_sm_release_resource(buffer); pr_debug("%s done\n", __func__); } @@ -613,7 +619,7 @@ static const struct dma_buf_ops dma_buf_ .mmap = vc_sm_dmabuf_mmap, .release = vc_sm_dma_buf_release, .attach = vc_sm_dma_buf_attach, - .detach = vc_sm_dma_buf_detatch, + .detach = vc_sm_dma_buf_detach, .begin_cpu_access = vc_sm_dma_buf_begin_cpu_access, .end_cpu_access = vc_sm_dma_buf_end_cpu_access, .map = vc_sm_dma_buf_kmap, @@ -762,6 +768,7 @@ static const struct dma_buf_ops dma_buf_ int vc_sm_cma_import_dmabuf_internal(struct vc_sm_privdata_t *private, struct dma_buf *dma_buf, + int fd, struct dma_buf **imported_buf) { DEFINE_DMA_BUF_EXPORT_INFO(exp_info); @@ -775,10 +782,15 @@ vc_sm_cma_import_dmabuf_internal(struct int status; /* Setup our allocation parameters */ - pr_debug("%s: importing dma_buf %p\n", __func__, dma_buf); + pr_debug("%s: importing dma_buf %p/fd %d\n", __func__, dma_buf, fd); - get_dma_buf(dma_buf); - dma_buf = dma_buf; + if (fd < 0) + get_dma_buf(dma_buf); + else + dma_buf = dma_buf_get(fd); + + if (!dma_buf) + return -EINVAL; attach = dma_buf_attach(dma_buf, &sm_state->pdev->dev); if (IS_ERR(attach)) { @@ -921,6 +933,10 @@ static int vc_sm_cma_vpu_alloc(u32 size, return -ENOMEM; mutex_init(&buffer->lock); + /* Acquire the mutex as vc_sm_release_resource will release it in the + * error path. + */ + mutex_lock(&buffer->lock); if (vc_sm_cma_buffer_allocate(sm_state->cma_heap, &buffer->alloc, aligned_size)) { @@ -976,6 +992,8 @@ static int vc_sm_cma_vpu_alloc(u32 size, vc_sm_add_resource(sm_state->vpu_allocs, buffer); + mutex_unlock(&buffer->lock); + *ret_buffer = buffer; return 0; error: @@ -1065,6 +1083,297 @@ vc_sm_vpu_event(struct sm_instance *inst } } +/* Userspace handling */ +/* + * Open the device. Creates a private state to help track all allocation + * associated with this device. + */ +static int vc_sm_cma_open(struct inode *inode, struct file *file) +{ + /* Make sure the device was started properly. */ + if (!sm_state) { + pr_err("[%s]: invalid device\n", __func__); + return -EPERM; + } + + file->private_data = vc_sm_cma_create_priv_data(current->tgid); + if (!file->private_data) { + pr_err("[%s]: failed to create data tracker\n", __func__); + + return -ENOMEM; + } + + return 0; +} + +/* + * Close the vcsm-cma device. + * All allocations are file descriptors to the dmabuf objects, so we will get + * the clean up request on those as those are cleaned up. + */ +static int vc_sm_cma_release(struct inode *inode, struct file *file) +{ + struct vc_sm_privdata_t *file_data = + (struct vc_sm_privdata_t *)file->private_data; + int ret = 0; + + /* Make sure the device was started properly. */ + if (!sm_state || !file_data) { + pr_err("[%s]: invalid device\n", __func__); + ret = -EPERM; + goto out; + } + + pr_debug("[%s]: using private data %p\n", __func__, file_data); + + /* Terminate the private data. */ + kfree(file_data); + +out: + return ret; +} + +/* + * Allocate a shared memory handle and block. + * Allocation is from CMA, and then imported into the VPU mappings. + */ +int vc_sm_cma_ioctl_alloc(struct vc_sm_privdata_t *private, + struct vc_sm_cma_ioctl_alloc *ioparam) +{ + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + struct vc_sm_buffer *buffer = NULL; + struct vc_sm_import import = { 0 }; + struct vc_sm_import_result result = { 0 }; + struct dma_buf *dmabuf = NULL; + int aligned_size; + int ret = 0; + int status; + int fd = -1; + + aligned_size = PAGE_ALIGN(ioparam->size); + + if (!aligned_size) + return -EINVAL; + + /* Allocate local buffer to track this allocation. */ + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto error; + } + + if (vc_sm_cma_buffer_allocate(sm_state->cma_heap, &buffer->alloc, + aligned_size)) { + pr_err("[%s]: cma alloc of %d bytes failed\n", + __func__, aligned_size); + kfree(buffer); + return -ENOMEM; + } + buffer->sg_table = buffer->alloc.sg_table; + + if (dma_map_sg(&sm_state->pdev->dev, buffer->sg_table->sgl, + buffer->sg_table->nents, DMA_BIDIRECTIONAL) <= 0) { + pr_err("[%s]: dma_map_sg failed\n", __func__); + ret = -ENOMEM; + goto error; + } + + import.type = VC_SM_ALLOC_NON_CACHED; + import.allocator = current->tgid; + + if (*ioparam->name) + memcpy(import.name, ioparam->name, sizeof(import.name) - 1); + else + memcpy(import.name, VC_SM_RESOURCE_NAME_DEFAULT, + sizeof(VC_SM_RESOURCE_NAME_DEFAULT)); + + mutex_init(&buffer->lock); + INIT_LIST_HEAD(&buffer->attachments); + memcpy(buffer->name, import.name, + min(sizeof(buffer->name), sizeof(import.name) - 1)); + + exp_info.ops = &dma_buf_ops; + exp_info.size = aligned_size; + exp_info.flags = O_RDWR; + exp_info.priv = buffer; + + dmabuf = dma_buf_export(&exp_info); + if (IS_ERR(dmabuf)) { + ret = PTR_ERR(dmabuf); + goto error; + } + buffer->dma_buf = dmabuf; + + import.addr = (uint32_t)sg_dma_address(buffer->sg_table->sgl); + import.size = aligned_size; + import.kernel_id = (uint32_t)buffer; + + /* Wrap it into a videocore buffer. */ + status = vc_sm_cma_vchi_import(sm_state->sm_handle, &import, &result, + &sm_state->int_trans_id); + if (status == -EINTR) { + pr_debug("[%s]: requesting import memory action restart (trans_id: %u)\n", + __func__, sm_state->int_trans_id); + ret = -ERESTARTSYS; + private->restart_sys = -EINTR; + private->int_action = VC_SM_MSG_TYPE_IMPORT; + goto error; + } else if (status || !result.res_handle) { + pr_err("[%s]: failed to import memory on videocore (status: %u, trans_id: %u)\n", + __func__, status, sm_state->int_trans_id); + ret = -ENOMEM; + goto error; + } + + /* Keep track of the buffer we created. */ + buffer->private = private; + buffer->vc_handle = result.res_handle; + buffer->size = import.size; + buffer->dma_addr = import.addr; + buffer->vpu_state = VPU_MAPPED; + //buffer->res_cached = ioparam->cached; + + fd = dma_buf_fd(dmabuf, O_CLOEXEC); + if (fd < 0) + goto error; + + vc_sm_add_resource(private, buffer); + + pr_debug("[%s]: Added resource as fd %d, buffer %p, private %p, dma_addr %pad\n", + __func__, fd, buffer, private, &buffer->dma_addr); + + /* We're done */ + ioparam->handle = fd; + ioparam->vc_handle = buffer->vc_handle; + ioparam->dma_addr = buffer->dma_addr; + return 0; + +error: + if (buffer) { + pr_err("[%s]: something failed - cleanup. ret %d\n", __func__, + ret); + + dma_buf_put(dmabuf); + } + return ret; +} + +static long vc_sm_cma_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + unsigned int cmdnr = _IOC_NR(cmd); + struct vc_sm_privdata_t *file_data = + (struct vc_sm_privdata_t *)file->private_data; + + /* Validate we can work with this device. */ + if (!sm_state || !file_data) { + pr_err("[%s]: invalid device\n", __func__); + return -EPERM; + } + + pr_debug("[%s]: cmd %x tgid %u, owner %u\n", __func__, cmdnr, + current->tgid, file_data->pid); + + /* Action is a re-post of a previously interrupted action? */ + if (file_data->restart_sys == -EINTR) { + struct vc_sm_action_clean_t action_clean; + + pr_debug("[%s]: clean up of action %u (trans_id: %u) following EINTR\n", + __func__, file_data->int_action, + file_data->int_trans_id); + + action_clean.res_action = file_data->int_action; + action_clean.action_trans_id = file_data->int_trans_id; + + file_data->restart_sys = 0; + } + + /* Now process the command. */ + switch (cmdnr) { + /* New memory allocation. + */ + case VC_SM_CMA_CMD_ALLOC: + { + struct vc_sm_cma_ioctl_alloc ioparam; + + /* Get the parameter data. */ + if (copy_from_user + (&ioparam, (void *)arg, sizeof(ioparam)) != 0) { + pr_err("[%s]: failed to copy-from-user for cmd %x\n", + __func__, cmdnr); + ret = -EFAULT; + break; + } + + ret = vc_sm_cma_ioctl_alloc(file_data, &ioparam); + if (!ret && + (copy_to_user((void *)arg, &ioparam, + sizeof(ioparam)) != 0)) { + /* FIXME: Release allocation */ + pr_err("[%s]: failed to copy-to-user for cmd %x\n", + __func__, cmdnr); + ret = -EFAULT; + } + break; + } + + case VC_SM_CMA_CMD_IMPORT_DMABUF: + { + struct vc_sm_cma_ioctl_import_dmabuf ioparam; + struct dma_buf *new_dmabuf; + + /* Get the parameter data. */ + if (copy_from_user + (&ioparam, (void *)arg, sizeof(ioparam)) != 0) { + pr_err("[%s]: failed to copy-from-user for cmd %x\n", + __func__, cmdnr); + ret = -EFAULT; + break; + } + + ret = vc_sm_cma_import_dmabuf_internal(file_data, + NULL, + ioparam.dmabuf_fd, + &new_dmabuf); + + if (!ret) { + struct vc_sm_buffer *buf = new_dmabuf->priv; + + ioparam.size = buf->size; + ioparam.handle = dma_buf_fd(new_dmabuf, + O_CLOEXEC); + ioparam.vc_handle = buf->vc_handle; + ioparam.dma_addr = buf->dma_addr; + + if (ioparam.handle < 0 || + (copy_to_user((void *)arg, &ioparam, + sizeof(ioparam)) != 0)) { + dma_buf_put(new_dmabuf); + /* FIXME: Release allocation */ + ret = -EFAULT; + } + } + break; + } + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/* Device operations that we managed in this driver. */ +static const struct file_operations vc_sm_ops = { + .owner = THIS_MODULE, + .unlocked_ioctl = vc_sm_cma_ioctl, + .open = vc_sm_cma_open, + .release = vc_sm_cma_release, +}; + +/* Driver load/unload functions */ /* Videocore connected. */ static void vc_sm_connected_init(void) { @@ -1075,12 +1384,11 @@ static void vc_sm_connected_init(void) pr_info("[%s]: start\n", __func__); - if (vc_sm_cma_add_heaps(&sm_state->cma_heap) || - !sm_state->cma_heap) { - pr_err("[%s]: failed to initialise CMA heaps\n", + vc_sm_cma_add_heaps(&sm_state->cma_heap); + if (!sm_state->cma_heap) { + pr_err("[%s]: failed to initialise CMA heap\n", __func__); - ret = -EIO; - goto err_free_mem; + return; } /* @@ -1092,8 +1400,7 @@ static void vc_sm_connected_init(void) pr_err("[%s]: failed to initialise VCHI instance (ret=%d)\n", __func__, ret); - ret = -EIO; - goto err_failed; + return; } ret = vchi_connect(NULL, 0, vchi_instance); @@ -1101,8 +1408,7 @@ static void vc_sm_connected_init(void) pr_err("[%s]: failed to connect VCHI instance (ret=%d)\n", __func__, ret); - ret = -EIO; - goto err_failed; + return; } /* Initialize an instance of the shared memory service. */ @@ -1112,8 +1418,7 @@ static void vc_sm_connected_init(void) pr_err("[%s]: failed to initialize shared memory service\n", __func__); - ret = -EPERM; - goto err_failed; + return; } /* Create a debug fs directory entry (root). */ @@ -1127,11 +1432,22 @@ static void vc_sm_connected_init(void) INIT_LIST_HEAD(&sm_state->buffer_list); + /* Create a shared memory device. */ + sm_state->misc_dev.minor = MISC_DYNAMIC_MINOR; + sm_state->misc_dev.name = DEVICE_NAME; + sm_state->misc_dev.fops = &vc_sm_ops; + sm_state->misc_dev.parent = NULL; + ret = misc_register(&sm_state->misc_dev); + if (ret) { + pr_err("vcsm-cma: failed to register misc device.\n"); + goto err_remove_debugfs; + } + sm_state->data_knl = vc_sm_cma_create_priv_data(0); if (!sm_state->data_knl) { pr_err("[%s]: failed to create kernel private data tracker\n", __func__); - goto err_remove_shared_memory; + goto err_remove_misc_dev; } version.version = 2; @@ -1148,11 +1464,13 @@ static void vc_sm_connected_init(void) pr_info("[%s]: installed successfully\n", __func__); return; -err_remove_shared_memory: +err_remove_misc_dev: + misc_deregister(&sm_state->misc_dev); +err_remove_debugfs: debugfs_remove_recursive(sm_state->dir_root); vc_sm_cma_vchi_stop(&sm_state->sm_handle); -err_failed: - pr_info("[%s]: failed, ret %d\n", __func__, ret); + + return; } /* Driver loading. */ @@ -1184,6 +1502,8 @@ static int bcm2835_vc_sm_cma_remove(stru { pr_debug("[%s]: start\n", __func__); if (sm_inited) { + misc_deregister(&sm_state->misc_dev); + /* Remove all proc entries. */ debugfs_remove_recursive(sm_state->dir_root); @@ -1202,6 +1522,7 @@ static int bcm2835_vc_sm_cma_remove(stru return 0; } +/* Kernel API calls */ /* Get an internal resource handle mapped from the external one. */ int vc_sm_cma_int_handle(void *handle) { @@ -1252,7 +1573,7 @@ int vc_sm_cma_import_dmabuf(struct dma_b } ret = vc_sm_cma_import_dmabuf_internal(sm_state->data_knl, src_dmabuf, - &new_dma_buf); + -1, &new_dma_buf); if (!ret) { pr_debug("%s: imported to ptr %p\n", __func__, new_dma_buf); --- a/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma.c +++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma.c @@ -92,8 +92,7 @@ int __vc_sm_cma_add_heaps(struct cma *cm return 0; } -int vc_sm_cma_add_heaps(struct cma **cma_heap) +void vc_sm_cma_add_heaps(struct cma **cma_heap) { cma_for_each_area(__vc_sm_cma_add_heaps, cma_heap); - return 0; } --- a/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma.h +++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma.h @@ -34,6 +34,6 @@ int vc_sm_cma_buffer_allocate(struct cma unsigned long len); void vc_sm_cma_buffer_free(struct vc_sm_cma_alloc_data *buffer); -int vc_sm_cma_add_heaps(struct cma **cma_heap); +void vc_sm_cma_add_heaps(struct cma **cma_heap); #endif --- /dev/null +++ b/include/linux/broadcom/vc_sm_cma_ioctl.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Copyright 2019 Raspberry Pi (Trading) Ltd. All rights reserved. + * + * Based on vmcs_sm_ioctl.h Copyright Broadcom Corporation. + */ + +#ifndef __VC_SM_CMA_IOCTL_H +#define __VC_SM_CMA_IOCTL_H + +/* ---- Include Files ---------------------------------------------------- */ + +#if defined(__KERNEL__) +#include /* Needed for standard types */ +#else +#include +#endif + +#include + +/* ---- Constants and Types ---------------------------------------------- */ + +#define VC_SM_CMA_RESOURCE_NAME 32 +#define VC_SM_CMA_RESOURCE_NAME_DEFAULT "sm-host-resource" + +/* Type define used to create unique IOCTL number */ +#define VC_SM_CMA_MAGIC_TYPE 'J' + +/* IOCTL commands on /dev/vc-sm-cma */ +enum vc_sm_cma_cmd_e { + VC_SM_CMA_CMD_ALLOC = 0x5A, /* Start at 0x5A arbitrarily */ + + VC_SM_CMA_CMD_IMPORT_DMABUF, + + VC_SM_CMA_CMD_LAST /* Do not delete */ +}; + +/* Cache type supported, conveniently matches the user space definition in + * user-vcsm.h. + */ +enum vc_sm_cma_cache_e { + VC_SM_CMA_CACHE_NONE, + VC_SM_CMA_CACHE_HOST, + VC_SM_CMA_CACHE_VC, + VC_SM_CMA_CACHE_BOTH, +}; + +/* IOCTL Data structures */ +struct vc_sm_cma_ioctl_alloc { + /* user -> kernel */ + __u32 size; + __u32 num; + __u32 cached; /* enum vc_sm_cma_cache_e */ + __u32 pad; + __u8 name[VC_SM_CMA_RESOURCE_NAME]; + + /* kernel -> user */ + __s32 handle; + __u32 vc_handle; + __u64 dma_addr; +}; + +struct vc_sm_cma_ioctl_import_dmabuf { + /* user -> kernel */ + __s32 dmabuf_fd; + __u32 cached; /* enum vc_sm_cma_cache_e */ + __u8 name[VC_SM_CMA_RESOURCE_NAME]; + + /* kernel -> user */ + __s32 handle; + __u32 vc_handle; + __u32 size; + __u32 pad; + __u64 dma_addr; +}; + +/* IOCTL numbers */ +#define VC_SM_CMA_IOCTL_MEM_ALLOC\ + _IOR(VC_SM_CMA_MAGIC_TYPE, VC_SM_CMA_CMD_ALLOC,\ + struct vc_sm_cma_ioctl_alloc) + +#define VC_SM_CMA_IOCTL_MEM_IMPORT_DMABUF\ + _IOR(VC_SM_CMA_MAGIC_TYPE, VC_SM_CMA_CMD_IMPORT_DMABUF,\ + struct vc_sm_cma_ioctl_import_dmabuf) + +#endif /* __VC_SM_CMA_IOCTL_H */