| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Common file system operations for devices with MCU support. |
| * |
| * Copyright (C) 2022 Google LLC |
| */ |
| |
| #include <linux/bits.h> |
| #include <linux/dma-fence-array.h> |
| #include <linux/fs.h> |
| #include <linux/mm_types.h> |
| #include <linux/rwsem.h> |
| #include <linux/slab.h> |
| |
| #include <gcip/gcip-fence-array.h> |
| #include <gcip/gcip-fence.h> |
| #include <gcip/gcip-telemetry.h> |
| |
| #include "gxp-client.h" |
| #include "gxp-internal.h" |
| #include "gxp-mcu-fs.h" |
| #include "gxp-mcu-telemetry.h" |
| #include "gxp-mcu.h" |
| #include "gxp-uci.h" |
| #include "gxp.h" |
| |
| static int gxp_ioctl_uci_command_compat(struct gxp_client *client, |
| struct gxp_mailbox_uci_command_compat_ioctl __user *argp) |
| { |
| struct gxp_mailbox_uci_command_compat_ioctl ibuf; |
| struct gxp_dev *gxp = client->gxp; |
| struct gxp_mcu *mcu = gxp_mcu_of(gxp); |
| u64 cmd_seq; |
| int ret; |
| |
| if (copy_from_user(&ibuf, argp, sizeof(ibuf))) |
| return -EFAULT; |
| |
| cmd_seq = gcip_mailbox_inc_seq_num(mcu->uci.mbx->mbx_impl.gcip_mbx, 1); |
| |
| ret = gxp_uci_create_and_send_cmd(client, cmd_seq, 0, ibuf.opaque, 0, NULL, NULL); |
| if (ret) { |
| dev_err(gxp->dev, "Failed to request an UCI command (ret=%d)", ret); |
| return ret; |
| } |
| |
| ibuf.sequence_number = cmd_seq; |
| |
| if (copy_to_user(argp, &ibuf, sizeof(ibuf))) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| /** |
| * polled_dma_fence_get() - Generate a dma-fence to represent all the fan-in fences. |
| * @in_fences: The pointer to the gcip_fence_array object containing fan-in fances. |
| * |
| * Use dma_fence_array to handle all the fan-in fences if @in_fences->size > 1. |
| * The caller should hold the reference count of the fences in @in_fences to make sure they will not |
| * be released during the process. |
| * The output fence will acquired 1 reference count in this function either with dma_fence_get() or |
| * dma_fence_array_create(). |
| * |
| * Return: The pointer to the generated dma-fence or the error pointer on error. |
| * A NULL pointer is returned if no in-kernel fence is passed in. |
| */ |
| static struct dma_fence *polled_dma_fence_get(struct gcip_fence_array *in_fences) |
| { |
| static int array_seq; |
| const int size = in_fences->size; |
| struct dma_fence_array *fence_array; |
| struct dma_fence **in_dma_fences; |
| int i; |
| |
| if (!in_fences->size || !in_fences->same_type || in_fences->type != GCIP_IN_KERNEL_FENCE) |
| return NULL; |
| |
| /* TODO(b/320401031): Remove this constraint after dma-fence-unwrap adopted. */ |
| /* dma-fence-array as in-fences is currently not supported. */ |
| for (i = 0; i < size; i++) { |
| if (dma_fence_is_array(in_fences->fences[i]->fence.ikf)) |
| return ERR_PTR(-EINVAL); |
| } |
| |
| if (size == 1) |
| return dma_fence_get(in_fences->fences[0]->fence.ikf); |
| |
| in_dma_fences = kcalloc(size, sizeof(*in_dma_fences), GFP_KERNEL); |
| if (!in_dma_fences) |
| return ERR_PTR(-ENOMEM); |
| |
| for (i = 0; i < size; i++) |
| in_dma_fences[i] = dma_fence_get(in_fences->fences[i]->fence.ikf); |
| |
| /* fence_array will take care of the life cycle of in_dma_fences.*/ |
| fence_array = dma_fence_array_create(size, in_dma_fences, dma_fence_context_alloc(1), |
| array_seq++, false); |
| if (!fence_array) { |
| kfree(in_dma_fences); |
| /* dma_fence_array_create only returns NULL on allocation failure. */ |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| return &fence_array->base; |
| } |
| |
| /* |
| * Returns the number of fences. If the runtime passed an invalid fence, returns an errno |
| * accordingly. |
| */ |
| static int get_num_fences(const int *fences) |
| { |
| int i; |
| struct gcip_fence *fence; |
| |
| for (i = 0; i < GXP_MAX_FENCES_PER_UCI_COMMAND; i++) { |
| if (fences[i] == GXP_FENCE_ARRAY_TERMINATION) |
| break; |
| fence = gcip_fence_fdget(fences[i]); |
| /* |
| * TODO(b/312819593): once the runtime adopts `GXP_FENCE_ARRAY_TERMINATION` to |
| * indicate the end of array, always returns PTR_ERR(fence). |
| */ |
| if (IS_ERR(fence)) |
| return !fences[i] ? 0 : PTR_ERR(fence); |
| gcip_fence_put(fence); |
| } |
| |
| return i; |
| } |
| |
| static int gxp_ioctl_uci_command(struct gxp_client *client, |
| struct gxp_mailbox_uci_command_ioctl __user *argp) |
| { |
| struct gxp_mcu *mcu = gxp_mcu_of(client->gxp); |
| struct gxp_mailbox_uci_command_ioctl ibuf; |
| struct gcip_fence_array *in_fences, *out_fences; |
| struct dma_fence *polled_dma_fence; |
| u64 cmd_seq; |
| int ret, num_in_fences, num_out_fences; |
| |
| if (copy_from_user(&ibuf, argp, sizeof(ibuf))) |
| return -EFAULT; |
| |
| cmd_seq = gcip_mailbox_inc_seq_num(mcu->uci.mbx->mbx_impl.gcip_mbx, 1); |
| |
| num_in_fences = get_num_fences(ibuf.in_fences); |
| if (num_in_fences < 0) |
| return num_in_fences; |
| |
| num_out_fences = get_num_fences(ibuf.out_fences); |
| if (num_out_fences < 0) |
| return num_out_fences; |
| |
| in_fences = gcip_fence_array_create(ibuf.in_fences, num_in_fences, true); |
| if (IS_ERR(in_fences)) |
| return PTR_ERR(in_fences); |
| |
| out_fences = gcip_fence_array_create(ibuf.out_fences, num_out_fences, false); |
| if (IS_ERR(out_fences)) { |
| gcip_fence_array_put(in_fences); |
| return PTR_ERR(out_fences); |
| } |
| |
| polled_dma_fence = polled_dma_fence_get(in_fences); |
| if (IS_ERR(polled_dma_fence)) { |
| ret = PTR_ERR(polled_dma_fence); |
| goto out; |
| } |
| |
| ret = gxp_uci_cmd_work_create_and_schedule(polled_dma_fence, client, &ibuf, cmd_seq, |
| in_fences, out_fences); |
| if (ret) { |
| dev_err(client->gxp->dev, "Failed to request an UCI command (ret=%d)", ret); |
| goto err_put_fence; |
| } |
| |
| ibuf.sequence_number = cmd_seq; |
| |
| if (copy_to_user(argp, &ibuf, sizeof(ibuf))) |
| ret = -EFAULT; |
| |
| err_put_fence: |
| /* |
| * Put the reference count of the fence acqurired in polled_dma_fence_get. |
| * If the fence is a dma_fence_array and the callback is failed to be added, |
| * the whole object and the array it holds will be freed. |
| * If it is a NULL pointer, it's still safe to call this function. |
| */ |
| dma_fence_put(polled_dma_fence); |
| out: |
| gcip_fence_array_put(out_fences); |
| gcip_fence_array_put(in_fences); |
| return ret; |
| } |
| |
| static int |
| gxp_ioctl_uci_response(struct gxp_client *client, |
| struct gxp_mailbox_uci_response_ioctl __user *argp) |
| { |
| struct gxp_mailbox_uci_response_ioctl ibuf; |
| int ret = 0; |
| |
| if (copy_from_user(&ibuf, argp, sizeof(ibuf))) |
| return -EFAULT; |
| |
| down_read(&client->semaphore); |
| |
| if (!client->vd) { |
| dev_err(client->gxp->dev, |
| "GXP_MAILBOX_UCI_RESPONSE requires the client allocate a VIRTUAL_DEVICE\n"); |
| ret = -ENODEV; |
| goto out; |
| } |
| |
| /* Caller must hold BLOCK wakelock */ |
| if (!client->has_block_wakelock) { |
| dev_err(client->gxp->dev, |
| "GXP_MAILBOX_UCI_RESPONSE requires the client hold a BLOCK wakelock\n"); |
| ret = -ENODEV; |
| goto out; |
| } |
| |
| ret = gxp_uci_wait_async_response( |
| &client->vd->mailbox_resp_queues[UCI_RESOURCE_ID], |
| &ibuf.sequence_number, &ibuf.error_code, ibuf.opaque); |
| if (ret == -ENOENT || ret == -EAGAIN) |
| goto out; |
| |
| if (copy_to_user(argp, &ibuf, sizeof(ibuf))) |
| ret = -EFAULT; |
| |
| out: |
| up_read(&client->semaphore); |
| |
| return ret; |
| } |
| |
| static int gxp_ioctl_set_device_properties( |
| struct gxp_dev *gxp, |
| struct gxp_set_device_properties_ioctl __user *argp) |
| { |
| struct gxp_dev_prop *device_prop = &gxp->device_prop; |
| struct gxp_set_device_properties_ioctl ibuf; |
| |
| if (copy_from_user(&ibuf, argp, sizeof(ibuf))) |
| return -EFAULT; |
| |
| mutex_lock(&device_prop->lock); |
| |
| memcpy(&device_prop->opaque, &ibuf.opaque, sizeof(device_prop->opaque)); |
| device_prop->initialized = true; |
| |
| mutex_unlock(&device_prop->lock); |
| |
| return 0; |
| } |
| |
| static int gxp_ioctl_create_iif_fence(struct gxp_client *client, |
| struct gxp_create_iif_fence_ioctl __user *argp) |
| { |
| struct gxp_create_iif_fence_ioctl ibuf; |
| int fd; |
| |
| if (copy_from_user(&ibuf, argp, sizeof(ibuf))) |
| return -EFAULT; |
| |
| fd = gcip_fence_create_iif(client->gxp->iif_mgr, ibuf.signaler_ip, ibuf.total_signalers); |
| if (fd < 0) |
| return fd; |
| |
| ibuf.fence = fd; |
| |
| if (copy_to_user(argp, &ibuf, sizeof(ibuf))) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| static int |
| gxp_ioctl_fence_remaining_signalers(struct gxp_client *client, |
| struct gxp_fence_remaining_signalers_ioctl __user *argp) |
| { |
| struct gxp_fence_remaining_signalers_ioctl ibuf; |
| struct gcip_fence_array *fences; |
| int ret, num_fences; |
| |
| if (copy_from_user(&ibuf, argp, sizeof(ibuf))) |
| return -EFAULT; |
| |
| num_fences = get_num_fences(ibuf.fences); |
| if (num_fences < 0) |
| return num_fences; |
| |
| fences = gcip_fence_array_create(ibuf.fences, num_fences, true); |
| if (IS_ERR(fences)) |
| return PTR_ERR(fences); |
| |
| ret = gcip_fence_array_wait_signaler_submission(fences, ibuf.eventfd, |
| ibuf.remaining_signalers); |
| if (ret) |
| goto out; |
| |
| if (copy_to_user(argp, &ibuf, sizeof(ibuf))) |
| ret = -EFAULT; |
| |
| out: |
| gcip_fence_array_put(fences); |
| return ret; |
| } |
| |
| static inline enum gcip_telemetry_type to_gcip_telemetry_type(u8 type) |
| { |
| if (type == GXP_TELEMETRY_TYPE_LOGGING) |
| return GCIP_TELEMETRY_LOG; |
| else |
| return GCIP_TELEMETRY_TRACE; |
| } |
| |
| static int |
| gxp_ioctl_register_mcu_telemetry_eventfd(struct gxp_client *client, |
| struct gxp_register_telemetry_eventfd_ioctl __user *argp) |
| { |
| struct gxp_mcu *mcu = gxp_mcu_of(client->gxp); |
| struct gxp_register_telemetry_eventfd_ioctl ibuf; |
| |
| if (copy_from_user(&ibuf, argp, sizeof(ibuf))) |
| return -EFAULT; |
| |
| return gxp_mcu_telemetry_register_eventfd( |
| mcu, to_gcip_telemetry_type(ibuf.type), ibuf.eventfd); |
| } |
| |
| static int |
| gxp_ioctl_unregister_mcu_telemetry_eventfd(struct gxp_client *client, |
| struct gxp_register_telemetry_eventfd_ioctl __user *argp) |
| { |
| struct gxp_mcu *mcu = gxp_mcu_of(client->gxp); |
| struct gxp_register_telemetry_eventfd_ioctl ibuf; |
| |
| if (copy_from_user(&ibuf, argp, sizeof(ibuf))) |
| return -EFAULT; |
| |
| return gxp_mcu_telemetry_unregister_eventfd( |
| mcu, to_gcip_telemetry_type(ibuf.type)); |
| } |
| |
| long gxp_mcu_ioctl(struct file *file, uint cmd, ulong arg) |
| { |
| struct gxp_client *client = file->private_data; |
| void __user *argp = (void __user *)arg; |
| long ret; |
| |
| if (gxp_is_direct_mode(client->gxp)) |
| return -ENOTTY; |
| switch (cmd) { |
| case GXP_MAILBOX_COMMAND: |
| ret = -EOPNOTSUPP; |
| break; |
| case GXP_MAILBOX_RESPONSE: |
| ret = -EOPNOTSUPP; |
| break; |
| case GXP_REGISTER_MCU_TELEMETRY_EVENTFD: |
| ret = gxp_ioctl_register_mcu_telemetry_eventfd(client, argp); |
| break; |
| case GXP_UNREGISTER_MCU_TELEMETRY_EVENTFD: |
| ret = gxp_ioctl_unregister_mcu_telemetry_eventfd(client, argp); |
| break; |
| case GXP_MAILBOX_UCI_COMMAND_COMPAT: |
| ret = gxp_ioctl_uci_command_compat(client, argp); |
| break; |
| case GXP_MAILBOX_UCI_COMMAND: |
| ret = gxp_ioctl_uci_command(client, argp); |
| break; |
| case GXP_MAILBOX_UCI_RESPONSE: |
| ret = gxp_ioctl_uci_response(client, argp); |
| break; |
| case GXP_SET_DEVICE_PROPERTIES: |
| ret = gxp_ioctl_set_device_properties(client->gxp, argp); |
| break; |
| case GXP_CREATE_IIF_FENCE: |
| ret = gxp_ioctl_create_iif_fence(client, argp); |
| break; |
| case GXP_FENCE_REMAINING_SIGNALERS: |
| ret = gxp_ioctl_fence_remaining_signalers(client, argp); |
| break; |
| default: |
| ret = -ENOTTY; /* unknown command */ |
| } |
| |
| return ret; |
| } |
| |
| int gxp_mcu_mmap(struct file *file, struct vm_area_struct *vma) |
| { |
| struct gxp_client *client = file->private_data; |
| struct gxp_mcu *mcu = gxp_mcu_of(client->gxp); |
| int ret; |
| |
| if (gxp_is_direct_mode(client->gxp)) |
| return -EOPNOTSUPP; |
| |
| switch (vma->vm_pgoff << PAGE_SHIFT) { |
| case GXP_MMAP_MCU_LOG_BUFFER_OFFSET: |
| ret = gxp_mcu_telemetry_mmap_buffer(mcu, GCIP_TELEMETRY_LOG, |
| vma); |
| break; |
| case GXP_MMAP_MCU_TRACE_BUFFER_OFFSET: |
| ret = gxp_mcu_telemetry_mmap_buffer(mcu, GCIP_TELEMETRY_TRACE, |
| vma); |
| break; |
| default: |
| ret = -EOPNOTSUPP; /* unknown offset */ |
| } |
| |
| return ret; |
| } |