summaryrefslogtreecommitdiff
path: root/lwis_ioctl_cmd.c
diff options
context:
space:
mode:
authorHolmes Chou <holmeschou@google.com>2023-03-21 08:14:19 +0000
committerTreeHugger Robot <treehugger-gerrit@google.com>2023-03-29 08:18:51 +0000
commit2be217d996f1027d9650e82739166d09eec7f6e4 (patch)
tree52d349f919fa5d861d0b62f0e48feef5f0dacf74 /lwis_ioctl_cmd.c
parentf70c8f6e18455fae6b56887670ee7eef0a49baf4 (diff)
downloadlwis-2be217d996f1027d9650e82739166d09eec7f6e4.tar.gz
LWIS: rename lwis_cmd to lwis_ioctl_cmd
Rename lwis_cmd.c(.h) to lwis_ioctl_cmd.c(.h) Bug: 274557494 Test: build pass, GCA Change-Id: Idafd92b45154185e88acf6950f83cc5d352b6871 Signed-off-by: Holmes Chou <holmeschou@google.com>
Diffstat (limited to 'lwis_ioctl_cmd.c')
-rw-r--r--lwis_ioctl_cmd.c1433
1 files changed, 1433 insertions, 0 deletions
diff --git a/lwis_ioctl_cmd.c b/lwis_ioctl_cmd.c
new file mode 100644
index 0000000..6644b33
--- /dev/null
+++ b/lwis_ioctl_cmd.c
@@ -0,0 +1,1433 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Google LWIS Command packets
+ *
+ * Copyright (c) 2022 Google, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "lwis_ioctl_cmd.h"
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include "lwis_allocator.h"
+#include "lwis_buffer.h"
+#include "lwis_commands.h"
+#include "lwis_device.h"
+#include "lwis_device_dpm.h"
+#include "lwis_device_i2c.h"
+#include "lwis_device_ioreg.h"
+#include "lwis_event.h"
+#include "lwis_fence.h"
+#include "lwis_i2c.h"
+#include "lwis_io_entry.h"
+#include "lwis_ioctl.h"
+#include "lwis_ioreg.h"
+#include "lwis_periodic_io.h"
+#include "lwis_platform.h"
+#include "lwis_transaction.h"
+#include "lwis_util.h"
+
+static int copy_pkt_to_user(struct lwis_device *lwis_dev, void __user *u_msg, void *k_msg,
+ size_t size)
+{
+ if (copy_to_user(u_msg, k_msg, size)) {
+ dev_err(lwis_dev->dev, "Failed to copy %zu bytes to user\n", size);
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int cmd_echo(struct lwis_device *lwis_dev, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_echo __user *u_msg)
+{
+ struct lwis_cmd_echo echo_msg;
+ char *buffer = NULL;
+
+ if (copy_from_user((void *)&echo_msg, (void __user *)u_msg, sizeof(echo_msg))) {
+ dev_err(lwis_dev->dev, "Failed to copy %zu bytes from user\n", sizeof(echo_msg));
+ return -EFAULT;
+ }
+
+ if (echo_msg.msg.size == 0) {
+ header->ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ buffer = kmalloc(echo_msg.msg.size + 1, GFP_KERNEL);
+ if (!buffer) {
+ dev_err(lwis_dev->dev, "Failed to allocate buffer for echo message\n");
+ header->ret_code = -ENOMEM;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+ if (copy_from_user(buffer, (void __user *)echo_msg.msg.msg, echo_msg.msg.size)) {
+ dev_err(lwis_dev->dev, "Failed to copy %zu bytes echo message from user\n",
+ echo_msg.msg.size);
+ kfree(buffer);
+ header->ret_code = -EFAULT;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+ buffer[echo_msg.msg.size] = '\0';
+
+ if (echo_msg.msg.kernel_log) {
+ dev_info(lwis_dev->dev, "LWIS_ECHO: %s\n", buffer);
+ }
+ kfree(buffer);
+
+ header->ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_time_query(struct lwis_device *lwis_dev, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_time_query __user *u_msg)
+{
+ struct lwis_cmd_time_query time_query;
+ time_query.timestamp_ns = ktime_to_ns(lwis_get_time());
+ time_query.header.cmd_id = header->cmd_id;
+ time_query.header.next = header->next;
+ time_query.header.ret_code = 0;
+
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)&time_query, sizeof(time_query));
+}
+
+static int cmd_get_device_info(struct lwis_device *lwis_dev, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_device_info __user *u_msg)
+{
+ int i;
+ struct lwis_cmd_device_info k_info = { .header.cmd_id = header->cmd_id,
+ .header.next = header->next,
+ .info.id = lwis_dev->id,
+ .info.type = lwis_dev->type,
+ .info.num_clks = 0,
+ .info.num_regs = 0,
+ .info.transaction_worker_thread_pid = -1,
+ .info.periodic_io_thread_pid = -1 };
+ strscpy(k_info.info.name, lwis_dev->name, LWIS_MAX_NAME_STRING_LEN);
+
+ if (lwis_dev->clocks) {
+ k_info.info.num_clks = lwis_dev->clocks->count;
+ for (i = 0; i < lwis_dev->clocks->count; i++) {
+ if (i >= LWIS_MAX_CLOCK_NUM) {
+ dev_err(lwis_dev->dev,
+ "Clock count larger than LWIS_MAX_CLOCK_NUM\n");
+ break;
+ }
+ strscpy(k_info.info.clks[i].name, lwis_dev->clocks->clk[i].name,
+ LWIS_MAX_NAME_STRING_LEN);
+ k_info.info.clks[i].clk_index = i;
+ k_info.info.clks[i].frequency = 0;
+ }
+ }
+
+ if (lwis_dev->type == DEVICE_TYPE_IOREG) {
+ struct lwis_ioreg_device *ioreg_dev;
+ ioreg_dev = container_of(lwis_dev, struct lwis_ioreg_device, base_dev);
+ if (ioreg_dev->reg_list.count > 0) {
+ k_info.info.num_regs = ioreg_dev->reg_list.count;
+ for (i = 0; i < ioreg_dev->reg_list.count; i++) {
+ if (i >= LWIS_MAX_REG_NUM) {
+ dev_err(lwis_dev->dev,
+ "Reg count larger than LWIS_MAX_REG_NUM\n");
+ break;
+ }
+ strscpy(k_info.info.regs[i].name, ioreg_dev->reg_list.block[i].name,
+ LWIS_MAX_NAME_STRING_LEN);
+ k_info.info.regs[i].reg_index = i;
+ k_info.info.regs[i].start = ioreg_dev->reg_list.block[i].start;
+ k_info.info.regs[i].size = ioreg_dev->reg_list.block[i].size;
+ }
+ }
+ }
+
+ if (lwis_dev->transaction_worker_thread) {
+ k_info.info.transaction_worker_thread_pid =
+ lwis_dev->transaction_worker_thread->pid;
+ }
+
+ k_info.header.ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)&k_info, sizeof(k_info));
+}
+
+static int cmd_device_enable(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_pkt __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+
+ if (lwis_client->is_enabled) {
+ header->ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ mutex_lock(&lwis_dev->client_lock);
+ if (lwis_dev->enabled > 0 && lwis_dev->enabled < INT_MAX) {
+ lwis_dev->enabled++;
+ lwis_client->is_enabled = true;
+ ret = 0;
+ goto exit_locked;
+ } else if (lwis_dev->enabled == INT_MAX) {
+ dev_err(lwis_dev->dev, "Enable counter overflow\n");
+ ret = -EINVAL;
+ goto exit_locked;
+ }
+
+ /* Clear event queues to make sure there is no stale event from
+ * previous session */
+ lwis_client_event_queue_clear(lwis_client);
+ lwis_client_error_event_queue_clear(lwis_client);
+
+ ret = lwis_dev_power_up_locked(lwis_dev);
+ if (ret < 0) {
+ dev_err(lwis_dev->dev, "Failed to power up device\n");
+ goto exit_locked;
+ }
+
+ lwis_dev->enabled++;
+ lwis_client->is_enabled = true;
+ lwis_dev->is_suspended = false;
+ dev_info(lwis_dev->dev, "Device enabled\n");
+exit_locked:
+ mutex_unlock(&lwis_dev->client_lock);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_device_disable(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_pkt __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+
+ if (!lwis_client->is_enabled) {
+ header->ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ mutex_lock(&lwis_dev->client_lock);
+ /* Clear event states for this client */
+ lwis_client_event_states_clear(lwis_client);
+ mutex_unlock(&lwis_dev->client_lock);
+
+ /* Flush all periodic io to complete */
+ ret = lwis_periodic_io_client_flush(lwis_client);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to wait for in-process periodic io to complete\n");
+ }
+
+ /* Flush all pending transactions */
+ ret = lwis_transaction_client_flush(lwis_client);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to flush pending transactions\n");
+ }
+
+ /* Run cleanup transactions. */
+ lwis_transaction_client_cleanup(lwis_client);
+
+ mutex_lock(&lwis_dev->client_lock);
+ if (lwis_dev->enabled > 1) {
+ lwis_dev->enabled--;
+ lwis_client->is_enabled = false;
+ ret = 0;
+ goto exit_locked;
+ } else if (lwis_dev->enabled <= 0) {
+ dev_err(lwis_dev->dev, "Disabling a device that is already disabled\n");
+ ret = -EINVAL;
+ goto exit_locked;
+ }
+
+ ret = lwis_dev_power_down_locked(lwis_dev);
+ if (ret < 0) {
+ dev_err(lwis_dev->dev, "Failed to power down device\n");
+ goto exit_locked;
+ }
+ lwis_device_event_states_clear_locked(lwis_dev);
+
+ lwis_dev->enabled--;
+ lwis_client->is_enabled = false;
+ lwis_dev->is_suspended = false;
+ dev_info(lwis_dev->dev, "Device disabled\n");
+exit_locked:
+ mutex_unlock(&lwis_dev->client_lock);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int copy_io_entries_from_cmd(struct lwis_device *lwis_dev,
+ struct lwis_cmd_io_entries __user *u_msg,
+ struct lwis_cmd_io_entries *k_msg,
+ struct lwis_io_entry **k_entries)
+{
+ struct lwis_io_entry *io_entries;
+ uint32_t buf_size;
+
+ /* Register io is not supported for the lwis device, return */
+ if (!lwis_dev->vops.register_io) {
+ dev_err(lwis_dev->dev, "Register IO not supported on this LWIS device\n");
+ return -EINVAL;
+ }
+
+ /* Copy io_entries from userspace */
+ if (copy_from_user(k_msg, (void __user *)u_msg, sizeof(*k_msg))) {
+ dev_err(lwis_dev->dev, "Failed to copy io_entries header from userspace.\n");
+ return -EFAULT;
+ }
+ buf_size = sizeof(struct lwis_io_entry) * k_msg->io.num_io_entries;
+ if (buf_size / sizeof(struct lwis_io_entry) != k_msg->io.num_io_entries) {
+ dev_err(lwis_dev->dev, "Failed to copy io_entries due to integer overflow.\n");
+ return -EOVERFLOW;
+ }
+ io_entries = lwis_allocator_allocate(lwis_dev, buf_size);
+ if (!io_entries) {
+ dev_err(lwis_dev->dev, "Failed to allocate io_entries buffer\n");
+ return -ENOMEM;
+ }
+ if (copy_from_user(io_entries, (void __user *)k_msg->io.io_entries, buf_size)) {
+ dev_err(lwis_dev->dev, "Failed to copy io_entries from userspace.\n");
+ lwis_allocator_free(lwis_dev, io_entries);
+ return -EFAULT;
+ }
+ *k_entries = io_entries;
+
+ return 0;
+}
+
+static int cmd_device_reset(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_io_entries __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+ struct lwis_cmd_io_entries k_msg;
+ struct lwis_io_entry *k_entries = NULL;
+ unsigned long flags;
+ bool device_enabled = false;
+
+ ret = copy_io_entries_from_cmd(lwis_dev, u_msg, &k_msg, &k_entries);
+ if (ret) {
+ goto soft_reset_exit;
+ }
+
+ /* Clear event states, event queues and transactions for this client */
+ mutex_lock(&lwis_dev->client_lock);
+ lwis_client_event_states_clear(lwis_client);
+ lwis_client_event_queue_clear(lwis_client);
+ lwis_client_error_event_queue_clear(lwis_client);
+ device_enabled = lwis_dev->enabled;
+ mutex_unlock(&lwis_dev->client_lock);
+
+ /* Flush all periodic io to complete */
+ ret = lwis_periodic_io_client_flush(lwis_client);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to wait for in-process periodic io to complete\n");
+ }
+
+ /* Flush all pending transactions */
+ ret = lwis_transaction_client_flush(lwis_client);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to flush all pending transactions\n");
+ }
+
+ /* Perform reset routine defined by the io_entries */
+ if (device_enabled) {
+ ret = lwis_ioctl_util_synchronous_process_io_entries(
+ lwis_dev, k_msg.io.num_io_entries, k_entries, k_msg.io.io_entries);
+ } else {
+ dev_warn(lwis_dev->dev,
+ "Device is not enabled, IoEntries will not be executed in DEVICE_RESET\n");
+ }
+
+ spin_lock_irqsave(&lwis_dev->lock, flags);
+ lwis_device_event_states_clear_locked(lwis_dev);
+ spin_unlock_irqrestore(&lwis_dev->lock, flags);
+soft_reset_exit:
+ if (k_entries) {
+ lwis_allocator_free(lwis_dev, k_entries);
+ }
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_device_suspend(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_pkt __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+
+ if (!lwis_dev->suspend_sequence) {
+ dev_err(lwis_dev->dev, "No suspend sequence defined\n");
+ header->ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ if (!lwis_client->is_enabled) {
+ dev_err(lwis_dev->dev, "Trying to suspend a disabled device\n");
+ header->ret_code = -EINVAL;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ if (lwis_dev->is_suspended) {
+ header->ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ mutex_lock(&lwis_dev->client_lock);
+ /* Clear event states for this client */
+ lwis_client_event_states_clear(lwis_client);
+ mutex_unlock(&lwis_dev->client_lock);
+
+ /* Flush all periodic io to complete */
+ ret = lwis_periodic_io_client_flush(lwis_client);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to wait for in-process periodic io to complete\n");
+ }
+
+ /* Flush all pending transactions */
+ ret = lwis_transaction_client_flush(lwis_client);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to flush pending transactions\n");
+ }
+
+ /* Run cleanup transactions. */
+ lwis_transaction_client_cleanup(lwis_client);
+
+ mutex_lock(&lwis_dev->client_lock);
+ ret = lwis_dev_process_power_sequence(lwis_dev, lwis_dev->suspend_sequence,
+ /*set_active=*/false, /*skip_error=*/false);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Error lwis_dev_process_power_sequence (%d)\n", ret);
+ goto exit_locked;
+ }
+
+ lwis_device_event_states_clear_locked(lwis_dev);
+
+ lwis_dev->is_suspended = true;
+ dev_info(lwis_dev->dev, "Device suspended\n");
+exit_locked:
+ mutex_unlock(&lwis_dev->client_lock);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_device_resume(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_pkt __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+
+ if (!lwis_dev->resume_sequence) {
+ dev_err(lwis_dev->dev, "No resume sequence defined\n");
+ header->ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ if (!lwis_dev->is_suspended) {
+ header->ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ mutex_lock(&lwis_dev->client_lock);
+ /* Clear event queues to make sure there is no stale event from
+ * previous session */
+ lwis_client_event_queue_clear(lwis_client);
+ lwis_client_error_event_queue_clear(lwis_client);
+
+ ret = lwis_dev_process_power_sequence(lwis_dev, lwis_dev->resume_sequence,
+ /*set_active=*/true, /*skip_error=*/false);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Error lwis_dev_process_power_sequence (%d)\n", ret);
+ goto exit_locked;
+ }
+
+ lwis_dev->is_suspended = false;
+ dev_info(lwis_dev->dev, "Device resumed\n");
+exit_locked:
+ mutex_unlock(&lwis_dev->client_lock);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_dump_debug_state(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_pkt __user *u_msg)
+{
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+
+ mutex_lock(&lwis_dev->client_lock);
+ /* Dump lwis device crash info */
+ lwis_device_crash_info_dump(lwis_dev);
+ mutex_unlock(&lwis_dev->client_lock);
+
+ header->ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_dma_buffer_enroll(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_dma_buffer_enroll __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_cmd_dma_buffer_enroll buf_info;
+ struct lwis_enrolled_buffer *buffer;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+
+ buffer = kmalloc(sizeof(*buffer), GFP_KERNEL);
+ if (!buffer) {
+ dev_err(lwis_dev->dev, "Failed to allocate lwis_enrolled_buffer struct\n");
+ header->ret_code = -ENOMEM;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ if (copy_from_user((void *)&buf_info, (void __user *)u_msg, sizeof(buf_info))) {
+ dev_err(lwis_dev->dev, "Failed to copy %zu bytes from user\n", sizeof(buf_info));
+ ret = -EFAULT;
+ goto error_enroll;
+ }
+
+ buffer->info.fd = buf_info.info.fd;
+ buffer->info.dma_read = buf_info.info.dma_read;
+ buffer->info.dma_write = buf_info.info.dma_write;
+
+ ret = lwis_buffer_enroll(lwis_client, buffer);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to enroll buffer\n");
+ goto error_enroll;
+ }
+
+ buf_info.info.dma_vaddr = buffer->info.dma_vaddr;
+ buf_info.header.cmd_id = header->cmd_id;
+ buf_info.header.next = header->next;
+ buf_info.header.ret_code = ret;
+ ret = copy_pkt_to_user(lwis_dev, u_msg, (void *)&buf_info, sizeof(buf_info));
+ if (ret) {
+ lwis_buffer_disenroll(lwis_client, buffer);
+ goto error_enroll;
+ }
+
+ return ret;
+
+error_enroll:
+ kfree(buffer);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_dma_buffer_disenroll(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_dma_buffer_disenroll __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_cmd_dma_buffer_disenroll info;
+ struct lwis_enrolled_buffer *buffer;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+
+ if (copy_from_user((void *)&info, (void __user *)u_msg, sizeof(info))) {
+ dev_err(lwis_dev->dev, "Failed to copy DMA virtual address from user\n");
+ return -EFAULT;
+ }
+
+ buffer = lwis_client_enrolled_buffer_find(lwis_client, info.info.fd, info.info.dma_vaddr);
+ if (!buffer) {
+ dev_err(lwis_dev->dev, "Failed to find dma buffer for fd %d vaddr %pad\n",
+ info.info.fd, &info.info.dma_vaddr);
+ header->ret_code = -ENOENT;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ ret = lwis_buffer_disenroll(lwis_client, buffer);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to disenroll dma buffer for fd %d vaddr %pad\n",
+ info.info.fd, &info.info.dma_vaddr);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ kfree(buffer);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_dma_buffer_cpu_access(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_dma_buffer_cpu_access __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_cmd_dma_buffer_cpu_access op;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+
+ if (copy_from_user((void *)&op, (void __user *)u_msg, sizeof(op))) {
+ dev_err(lwis_dev->dev, "Failed to copy buffer CPU access operation from user\n");
+ return -EFAULT;
+ }
+
+ ret = lwis_buffer_cpu_access(lwis_client, &op.op);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to prepare for cpu access for fd %d\n", op.op.fd);
+ }
+
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_dma_buffer_alloc(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_dma_buffer_alloc __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_cmd_dma_buffer_alloc alloc_info;
+ struct lwis_allocated_buffer *buffer;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+
+ buffer = kmalloc(sizeof(*buffer), GFP_KERNEL);
+ if (!buffer) {
+ dev_err(lwis_dev->dev, "Failed to allocated lwis_allocated_buffer\n");
+ return -ENOMEM;
+ }
+
+ if (copy_from_user((void *)&alloc_info, (void __user *)u_msg, sizeof(alloc_info))) {
+ dev_err(lwis_dev->dev, "Failed to copy %zu bytes from user\n", sizeof(alloc_info));
+ ret = -EFAULT;
+ goto error_alloc;
+ }
+
+ ret = lwis_buffer_alloc(lwis_client, &alloc_info.info, buffer);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to allocate buffer\n");
+ goto error_alloc;
+ }
+
+ alloc_info.header.ret_code = 0;
+ ret = copy_pkt_to_user(lwis_dev, u_msg, (void *)&alloc_info, sizeof(alloc_info));
+ if (ret) {
+ lwis_buffer_free(lwis_client, buffer);
+ ret = -EFAULT;
+ goto error_alloc;
+ }
+
+ return ret;
+
+error_alloc:
+ kfree(buffer);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_dma_buffer_free(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_dma_buffer_free __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_cmd_dma_buffer_free info;
+ struct lwis_allocated_buffer *buffer;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+
+ if (copy_from_user((void *)&info, (void __user *)u_msg, sizeof(info))) {
+ dev_err(lwis_dev->dev, "Failed to copy file descriptor from user\n");
+ return -EFAULT;
+ }
+
+ buffer = lwis_client_allocated_buffer_find(lwis_client, info.fd);
+ if (!buffer) {
+ dev_err(lwis_dev->dev, "Cannot find allocated buffer FD %d\n", info.fd);
+ header->ret_code = -ENOENT;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ ret = lwis_buffer_free(lwis_client, buffer);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to free buffer FD %d\n", info.fd);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ kfree(buffer);
+
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_reg_io(struct lwis_device *lwis_dev, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_io_entries __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_cmd_io_entries k_msg;
+ struct lwis_io_entry *k_entries = NULL;
+
+ ret = copy_io_entries_from_cmd(lwis_dev, u_msg, &k_msg, &k_entries);
+ if (ret) {
+ goto reg_io_exit;
+ }
+
+ /* Walk through and execute the entries */
+ ret = lwis_ioctl_util_synchronous_process_io_entries(lwis_dev, k_msg.io.num_io_entries,
+ k_entries, k_msg.io.io_entries);
+
+reg_io_exit:
+ if (k_entries) {
+ lwis_allocator_free(lwis_dev, k_entries);
+ }
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_event_control_get(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_event_control_get __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_cmd_event_control_get control;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+
+ if (copy_from_user((void *)&control, (void __user *)u_msg, sizeof(control))) {
+ dev_err(lwis_dev->dev, "Failed to copy %zu bytes from user\n", sizeof(control));
+ return -EFAULT;
+ }
+
+ ret = lwis_client_event_control_get(lwis_client, control.ctl.event_id, &control.ctl);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to get event: %lld (err:%d)\n", control.ctl.event_id,
+ ret);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ control.header.ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)&control, sizeof(control));
+}
+
+static int cmd_event_control_set(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_event_control_set __user *u_msg)
+{
+ struct lwis_cmd_event_control_set k_msg;
+ struct lwis_event_control *k_event_controls;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+ int ret = 0;
+ int i;
+ size_t buf_size;
+
+ if (copy_from_user((void *)&k_msg, (void __user *)u_msg, sizeof(k_msg))) {
+ dev_err(lwis_dev->dev, "Failed to copy ioctl message from user\n");
+ return -EFAULT;
+ }
+
+ /* Copy event controls from user buffer. */
+ buf_size = sizeof(struct lwis_event_control) * k_msg.list.num_event_controls;
+ if (buf_size / sizeof(struct lwis_event_control) != k_msg.list.num_event_controls) {
+ dev_err(lwis_dev->dev, "Failed to copy event controls due to integer overflow.\n");
+ header->ret_code = -EOVERFLOW;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+ k_event_controls = kmalloc(buf_size, GFP_KERNEL);
+ if (!k_event_controls) {
+ dev_err(lwis_dev->dev, "Failed to allocate event controls\n");
+ header->ret_code = -ENOMEM;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+ if (copy_from_user(k_event_controls, (void __user *)k_msg.list.event_controls, buf_size)) {
+ dev_err(lwis_dev->dev, "Failed to copy event controls from user\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ for (i = 0; i < k_msg.list.num_event_controls; i++) {
+ ret = lwis_client_event_control_set(lwis_client, &k_event_controls[i]);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to apply event control 0x%llx\n",
+ k_event_controls[i].event_id);
+ goto exit;
+ }
+ }
+exit:
+ kfree(k_event_controls);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_event_dequeue(struct lwis_client *lwis_client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_event_dequeue __user *u_msg)
+{
+ struct lwis_cmd_event_dequeue info;
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+ struct lwis_event_entry *event;
+ int ret = 0;
+ int err = 0;
+ bool is_error_event = false;
+
+ if (copy_from_user((void *)&info, (void __user *)u_msg, sizeof(info))) {
+ dev_err(lwis_dev->dev, "Failed to copy %zu bytes from user\n", sizeof(info));
+ return -EFAULT;
+ }
+
+ mutex_lock(&lwis_dev->client_lock);
+ /* Peek at the front element of error event queue first */
+ ret = lwis_client_error_event_peek_front(lwis_client, &event);
+ if (ret == 0) {
+ is_error_event = true;
+ } else if (ret != -ENOENT) {
+ dev_err(lwis_dev->dev, "Error dequeueing error event: %d\n", ret);
+ mutex_unlock(&lwis_dev->client_lock);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ } else {
+ /* Nothing at error event queue, continue to check normal
+ * event queue */
+ ret = lwis_client_event_peek_front(lwis_client, &event);
+ if (ret) {
+ if (ret != -ENOENT) {
+ dev_err(lwis_dev->dev, "Error dequeueing event: %d\n", ret);
+ }
+ mutex_unlock(&lwis_dev->client_lock);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+ }
+
+ /* We need to check if we have an adequate payload buffer */
+ if (event->event_info.payload_size > info.info.payload_buffer_size) {
+ /* Nope, we don't. Let's inform the user and bail */
+ info.info.payload_size = event->event_info.payload_size;
+ err = -EAGAIN;
+ } else {
+ info.info.event_id = event->event_info.event_id;
+ info.info.event_counter = event->event_info.event_counter;
+ info.info.timestamp_ns = event->event_info.timestamp_ns;
+ info.info.payload_size = event->event_info.payload_size;
+
+ /* Here we have a payload and the buffer is big enough */
+ if (event->event_info.payload_size > 0 && info.info.payload_buffer) {
+ /* Copy over the payload buffer to userspace */
+ if (copy_to_user((void __user *)info.info.payload_buffer,
+ (void *)event->event_info.payload_buffer,
+ event->event_info.payload_size)) {
+ dev_err(lwis_dev->dev, "Failed to copy %zu bytes to user\n",
+ event->event_info.payload_size);
+ mutex_unlock(&lwis_dev->client_lock);
+ return -EFAULT;
+ }
+ }
+ }
+ /* If we didn't -EAGAIN up above, we can pop and discard the front of
+ * the event queue because we're done dealing with it. If we got the
+ * -EAGAIN case, we didn't actually dequeue this event and userspace
+ * should try again with a bigger payload_buffer.
+ */
+ if (!err) {
+ if (is_error_event) {
+ ret = lwis_client_error_event_pop_front(lwis_client, NULL);
+ } else {
+ ret = lwis_client_event_pop_front(lwis_client, NULL);
+ }
+ if (ret) {
+ dev_err(lwis_dev->dev, "Error dequeueing event: %d\n", ret);
+ mutex_unlock(&lwis_dev->client_lock);
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+ }
+ mutex_unlock(&lwis_dev->client_lock);
+ /* Now let's copy the actual info struct back to user */
+ info.header.ret_code = err;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)&info, sizeof(info));
+}
+
+static int construct_transaction_from_cmd(struct lwis_client *client,
+ struct lwis_cmd_transaction_info __user *u_msg,
+ struct lwis_transaction **transaction)
+{
+ int ret;
+ struct lwis_cmd_transaction_info k_info;
+ struct lwis_transaction *k_transaction;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+
+ k_transaction = kmalloc(sizeof(*k_transaction), GFP_KERNEL);
+ if (!k_transaction) {
+ dev_err(lwis_dev->dev, "Failed to allocate transaction info\n");
+ return -ENOMEM;
+ }
+
+ if (copy_from_user((void *)&k_info, (void __user *)u_msg, sizeof(k_info))) {
+ dev_err(lwis_dev->dev, "Failed to copy transaction info from user\n");
+ ret = -EFAULT;
+ goto error_free_transaction;
+ }
+
+ memcpy(&k_transaction->info, &k_info.info, sizeof(k_transaction->info));
+
+ ret = lwis_ioctl_util_construct_io_entry(client, k_transaction->info.io_entries,
+ k_transaction->info.num_io_entries,
+ &k_transaction->info.io_entries);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to prepare lwis io entries for transaction\n");
+ goto error_free_transaction;
+ }
+
+ k_transaction->resp = NULL;
+ k_transaction->is_weak_transaction = false;
+ INIT_LIST_HEAD(&k_transaction->event_list_node);
+ INIT_LIST_HEAD(&k_transaction->process_queue_node);
+ INIT_LIST_HEAD(&k_transaction->completion_fence_list);
+
+ *transaction = k_transaction;
+ return 0;
+
+error_free_transaction:
+ kfree(k_transaction);
+ return ret;
+}
+
+static int cmd_transaction_submit(struct lwis_client *client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_transaction_info __user *u_msg)
+{
+ struct lwis_transaction *k_transaction = NULL;
+ struct lwis_cmd_transaction_info k_transaction_info;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+ int ret = 0;
+ unsigned long flags;
+
+ if (lwis_dev->type == DEVICE_TYPE_SLC || lwis_dev->type == DEVICE_TYPE_DPM) {
+ dev_err(lwis_dev->dev, "not supported device type: %d\n", lwis_dev->type);
+ ret = -EINVAL;
+ goto err_exit;
+ }
+
+ ret = construct_transaction_from_cmd(client, u_msg, &k_transaction);
+ if (ret) {
+ goto err_exit;
+ }
+
+ ret = lwis_initialize_transaction_fences(client, k_transaction);
+ if (ret) {
+ lwis_transaction_free(lwis_dev, k_transaction);
+ goto err_exit;
+ }
+
+ spin_lock_irqsave(&client->transaction_lock, flags);
+ ret = lwis_transaction_submit_locked(client, k_transaction);
+ k_transaction_info.info = k_transaction->info;
+ spin_unlock_irqrestore(&client->transaction_lock, flags);
+ if (ret) {
+ k_transaction_info.info.id = LWIS_ID_INVALID;
+ lwis_transaction_free(lwis_dev, k_transaction);
+ }
+
+ k_transaction_info.header.cmd_id = header->cmd_id;
+ k_transaction_info.header.next = header->next;
+ k_transaction_info.header.ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)&k_transaction_info,
+ sizeof(k_transaction_info));
+
+err_exit:
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_transaction_cancel(struct lwis_client *client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_transaction_cancel __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_cmd_transaction_cancel k_msg;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+
+ if (copy_from_user((void *)&k_msg, (void __user *)u_msg, sizeof(k_msg))) {
+ dev_err(lwis_dev->dev, "Failed to copy transaction ID from user\n");
+ return -EFAULT;
+ }
+
+ ret = lwis_transaction_cancel(client, k_msg.id);
+ if (ret) {
+ dev_info_ratelimited(
+ lwis_dev->dev,
+ "Transaction id 0x%llx does not exist or is already done, not available for cancel(%d)\n",
+ k_msg.id, ret);
+ }
+
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_transaction_replace(struct lwis_client *client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_transaction_info __user *u_msg)
+{
+ struct lwis_transaction *k_transaction = NULL;
+ struct lwis_cmd_transaction_info k_transaction_info;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+ int ret = 0;
+ unsigned long flags;
+
+ ret = construct_transaction_from_cmd(client, u_msg, &k_transaction);
+ if (ret) {
+ goto err_exit;
+ }
+
+ ret = lwis_initialize_transaction_fences(client, k_transaction);
+ if (ret) {
+ lwis_transaction_free(lwis_dev, k_transaction);
+ goto err_exit;
+ }
+
+ spin_lock_irqsave(&client->transaction_lock, flags);
+ ret = lwis_transaction_replace_locked(client, k_transaction);
+ k_transaction_info.info = k_transaction->info;
+ spin_unlock_irqrestore(&client->transaction_lock, flags);
+ if (ret) {
+ k_transaction_info.info.id = LWIS_ID_INVALID;
+ lwis_transaction_free(lwis_dev, k_transaction);
+ }
+
+ k_transaction_info.header.cmd_id = header->cmd_id;
+ k_transaction_info.header.next = header->next;
+ k_transaction_info.header.ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)&k_transaction_info,
+ sizeof(k_transaction_info));
+
+err_exit:
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int construct_periodic_io_from_cmd(struct lwis_client *client,
+ struct lwis_cmd_periodic_io_info __user *u_msg,
+ struct lwis_periodic_io **periodic_io)
+{
+ int ret = 0;
+ struct lwis_periodic_io *k_periodic_io;
+ struct lwis_cmd_periodic_io_info k_info;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+
+ k_periodic_io = kmalloc(sizeof(struct lwis_periodic_io), GFP_KERNEL);
+ if (!k_periodic_io) {
+ dev_err(lwis_dev->dev, "Failed to allocate periodic io\n");
+ return -ENOMEM;
+ }
+
+ if (copy_from_user((void *)&k_info, (void __user *)u_msg, sizeof(k_info))) {
+ dev_err(lwis_dev->dev, "Failed to copy periodic io info from user\n");
+ ret = -EFAULT;
+ goto error_free_periodic_io;
+ }
+
+ memcpy(&k_periodic_io->info, &k_info.info, sizeof(k_periodic_io->info));
+
+ ret = lwis_ioctl_util_construct_io_entry(client, k_periodic_io->info.io_entries,
+ k_periodic_io->info.num_io_entries,
+ &k_periodic_io->info.io_entries);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to prepare lwis io entries for periodic io\n");
+ goto error_free_periodic_io;
+ }
+
+ k_periodic_io->resp = NULL;
+ k_periodic_io->periodic_io_list = NULL;
+
+ *periodic_io = k_periodic_io;
+ return 0;
+
+error_free_periodic_io:
+ kfree(k_periodic_io);
+ return ret;
+}
+
+static int cmd_periodic_io_submit(struct lwis_client *client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_periodic_io_info __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_cmd_periodic_io_info k_periodic_io_info;
+ struct lwis_periodic_io *k_periodic_io = NULL;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+
+ ret = construct_periodic_io_from_cmd(client, u_msg, &k_periodic_io);
+ if (ret) {
+ goto err_exit;
+ }
+
+ ret = lwis_periodic_io_submit(client, k_periodic_io);
+ k_periodic_io_info.info = k_periodic_io->info;
+ if (ret) {
+ k_periodic_io_info.info.id = LWIS_ID_INVALID;
+ lwis_periodic_io_free(lwis_dev, k_periodic_io);
+ goto err_exit;
+ }
+
+ k_periodic_io_info.header.cmd_id = header->cmd_id;
+ k_periodic_io_info.header.next = header->next;
+ k_periodic_io_info.header.ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)&k_periodic_io_info,
+ sizeof(k_periodic_io_info));
+
+err_exit:
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_periodic_io_cancel(struct lwis_client *client, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_periodic_io_cancel __user *u_msg)
+{
+ int ret = 0;
+ struct lwis_cmd_periodic_io_cancel k_msg;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+
+ if (copy_from_user((void *)&k_msg, (void __user *)u_msg, sizeof(k_msg))) {
+ dev_err(lwis_dev->dev, "Failed to copy periodic io ID from user\n");
+ return -EFAULT;
+ }
+
+ ret = lwis_periodic_io_cancel(client, k_msg.id);
+ if (ret) {
+ dev_err_ratelimited(lwis_dev->dev, "Failed to clear periodic io id 0x%llx\n",
+ k_msg.id);
+ }
+
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_dpm_clk_update(struct lwis_device *lwis_dev, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_dpm_clk_update __user *u_msg)
+{
+ int ret;
+ struct lwis_cmd_dpm_clk_update k_msg;
+ struct lwis_clk_setting *clk_settings;
+ size_t buf_size;
+
+ if (copy_from_user((void *)&k_msg, (void __user *)u_msg, sizeof(k_msg))) {
+ dev_err(lwis_dev->dev, "Failed to copy ioctl message from user\n");
+ return -EFAULT;
+ }
+
+ buf_size = sizeof(struct lwis_clk_setting) * k_msg.settings.num_settings;
+ if (buf_size / sizeof(struct lwis_clk_setting) != k_msg.settings.num_settings) {
+ dev_err(lwis_dev->dev, "Failed to copy clk settings due to integer overflow.\n");
+ ret = -EOVERFLOW;
+ goto exit;
+ }
+ clk_settings = kmalloc(buf_size, GFP_KERNEL);
+ if (!clk_settings) {
+ dev_err(lwis_dev->dev, "Failed to allocate clock settings\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ if (copy_from_user(clk_settings, (void __user *)k_msg.settings.settings, buf_size)) {
+ dev_err(lwis_dev->dev, "Failed to copy clk settings from user\n");
+ kfree(clk_settings);
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ ret = lwis_dpm_update_clock(lwis_dev, clk_settings, k_msg.settings.num_settings);
+ kfree(clk_settings);
+exit:
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_dpm_qos_update(struct lwis_device *lwis_dev, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_dpm_qos_update __user *u_msg)
+{
+ struct lwis_cmd_dpm_qos_update k_msg;
+ struct lwis_qos_setting *k_qos_settings;
+ int ret = 0;
+ int i;
+ size_t buf_size;
+
+ if (lwis_dev->type != DEVICE_TYPE_DPM) {
+ dev_err(lwis_dev->dev, "not supported device type: %d\n", lwis_dev->type);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ if (copy_from_user((void *)&k_msg, (void __user *)u_msg, sizeof(k_msg))) {
+ dev_err(lwis_dev->dev, "Failed to copy ioctl message from user\n");
+ return -EFAULT;
+ }
+
+ // Copy qos settings from user buffer.
+ buf_size = sizeof(struct lwis_qos_setting) * k_msg.reqs.num_settings;
+ if (buf_size / sizeof(struct lwis_qos_setting) != k_msg.reqs.num_settings) {
+ dev_err(lwis_dev->dev, "Failed to copy qos settings due to integer overflow.\n");
+ ret = -EOVERFLOW;
+ goto exit;
+ }
+ k_qos_settings = kmalloc(buf_size, GFP_KERNEL);
+ if (!k_qos_settings) {
+ dev_err(lwis_dev->dev, "Failed to allocate qos settings\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+ if (copy_from_user(k_qos_settings, (void __user *)k_msg.reqs.qos_settings, buf_size)) {
+ dev_err(lwis_dev->dev, "Failed to copy clk settings from user\n");
+ kfree(k_qos_settings);
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ for (i = 0; i < k_msg.reqs.num_settings; i++) {
+ ret = lwis_dpm_update_qos(lwis_dev, &k_qos_settings[i]);
+ if (ret) {
+ dev_err(lwis_dev->dev, "Failed to apply qos setting, ret: %d\n", ret);
+ kfree(k_qos_settings);
+ goto exit;
+ }
+ }
+ kfree(k_qos_settings);
+exit:
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+static int cmd_dpm_get_clock(struct lwis_device *lwis_dev, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_dpm_clk_get __user *u_msg)
+{
+ struct lwis_cmd_dpm_clk_get current_setting;
+ struct lwis_device *target_device;
+ int ret = 0;
+
+ if (lwis_dev->type != DEVICE_TYPE_DPM) {
+ dev_err(lwis_dev->dev, "not supported device type: %d\n", lwis_dev->type);
+ ret = -EINVAL;
+ goto err_exit;
+ }
+
+ if (copy_from_user((void *)&current_setting, (void __user *)u_msg,
+ sizeof(current_setting))) {
+ dev_err(lwis_dev->dev, "failed to copy from user\n");
+ return -EFAULT;
+ }
+
+ target_device = lwis_find_dev_by_id(current_setting.setting.device_id);
+ if (!target_device) {
+ dev_err(lwis_dev->dev, "could not find lwis device by id %d\n",
+ current_setting.setting.device_id);
+ ret = -ENODEV;
+ goto err_exit;
+ }
+
+ if (target_device->enabled == 0 && target_device->type != DEVICE_TYPE_DPM) {
+ dev_warn(target_device->dev, "%s disabled, can't get clk\n", target_device->name);
+ ret = -EPERM;
+ goto err_exit;
+ }
+
+ current_setting.setting.frequency_hz = (int64_t)lwis_dpm_read_clock(target_device);
+ current_setting.header.ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)&current_setting, sizeof(current_setting));
+
+err_exit:
+ header->ret_code = ret;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+}
+
+#ifdef LWIS_FENCE_ENABLED
+static int cmd_fence_create(struct lwis_device *lwis_dev, struct lwis_cmd_pkt *header,
+ struct lwis_cmd_fence_create __user *u_msg)
+{
+ int32_t fd_or_err;
+ struct lwis_cmd_fence_create fence_create;
+
+ if (copy_from_user((void *)&fence_create, (void __user *)u_msg, sizeof(fence_create))) {
+ dev_err(lwis_dev->dev, "failed to copy from user\n");
+ return -EFAULT;
+ }
+
+ fd_or_err = lwis_fence_create(lwis_dev);
+ if (fd_or_err < 0) {
+ header->ret_code = fd_or_err;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)header, sizeof(*header));
+ }
+
+ fence_create.fd = fd_or_err;
+ fence_create.header.ret_code = 0;
+ return copy_pkt_to_user(lwis_dev, u_msg, (void *)&fence_create, sizeof(fence_create));
+}
+#endif
+
+int lwis_ioctl_handle_cmd_pkt(struct lwis_client *lwis_client, struct lwis_cmd_pkt __user *user_msg)
+{
+ struct lwis_device *lwis_dev = lwis_client->lwis_dev;
+ struct lwis_cmd_pkt header;
+ int ret = 0;
+
+ while (user_msg) {
+ /* Copy cmd packet header from userspace */
+ if (copy_from_user(&header, (void __user *)user_msg, sizeof(header))) {
+ dev_err(lwis_dev->dev,
+ "Failed to copy cmd packet header from userspace.\n");
+ return -EFAULT;
+ }
+
+ switch (header.cmd_id) {
+ case LWIS_CMD_ID_ECHO:
+ ret = cmd_echo(lwis_dev, &header, (struct lwis_cmd_echo __user *)user_msg);
+ break;
+ case LWIS_CMD_ID_TIME_QUERY:
+ ret = cmd_time_query(lwis_dev, &header,
+ (struct lwis_cmd_time_query __user *)user_msg);
+ break;
+ case LWIS_CMD_ID_GET_DEVICE_INFO:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_get_device_info(lwis_dev, &header,
+ (struct lwis_cmd_device_info __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DEVICE_ENABLE:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_device_enable(lwis_client, &header,
+ (struct lwis_cmd_pkt __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DEVICE_DISABLE:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_device_disable(lwis_client, &header,
+ (struct lwis_cmd_pkt __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DEVICE_RESET:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_device_reset(lwis_client, &header,
+ (struct lwis_cmd_io_entries __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DEVICE_SUSPEND:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_device_suspend(lwis_client, &header,
+ (struct lwis_cmd_pkt __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DEVICE_RESUME:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_device_resume(lwis_client, &header,
+ (struct lwis_cmd_pkt __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DUMP_DEBUG_STATE:
+ ret = cmd_dump_debug_state(lwis_client, &header,
+ (struct lwis_cmd_pkt __user *)user_msg);
+ break;
+ case LWIS_CMD_ID_DMA_BUFFER_ENROLL:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_dma_buffer_enroll(
+ lwis_client, &header,
+ (struct lwis_cmd_dma_buffer_enroll __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DMA_BUFFER_DISENROLL:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_dma_buffer_disenroll(
+ lwis_client, &header,
+ (struct lwis_cmd_dma_buffer_disenroll __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DMA_BUFFER_CPU_ACCESS:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_dma_buffer_cpu_access(
+ lwis_client, &header,
+ (struct lwis_cmd_dma_buffer_cpu_access __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DMA_BUFFER_ALLOC:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_dma_buffer_alloc(
+ lwis_client, &header,
+ (struct lwis_cmd_dma_buffer_alloc __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DMA_BUFFER_FREE:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_dma_buffer_free(
+ lwis_client, &header,
+ (struct lwis_cmd_dma_buffer_free __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_REG_IO:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_reg_io(lwis_dev, &header,
+ (struct lwis_cmd_io_entries __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_EVENT_CONTROL_GET:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_event_control_get(
+ lwis_client, &header,
+ (struct lwis_cmd_event_control_get __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_EVENT_CONTROL_SET:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_event_control_set(
+ lwis_client, &header,
+ (struct lwis_cmd_event_control_set __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_EVENT_DEQUEUE:
+ ret = cmd_event_dequeue(lwis_client, &header,
+ (struct lwis_cmd_event_dequeue __user *)user_msg);
+ break;
+ case LWIS_CMD_ID_TRANSACTION_SUBMIT:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_transaction_submit(
+ lwis_client, &header,
+ (struct lwis_cmd_transaction_info __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_TRANSACTION_CANCEL:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_transaction_cancel(
+ lwis_client, &header,
+ (struct lwis_cmd_transaction_cancel __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_TRANSACTION_REPLACE:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_transaction_replace(
+ lwis_client, &header,
+ (struct lwis_cmd_transaction_info __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_PERIODIC_IO_SUBMIT:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_periodic_io_submit(
+ lwis_client, &header,
+ (struct lwis_cmd_periodic_io_info __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_PERIODIC_IO_CANCEL:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_periodic_io_cancel(
+ lwis_client, &header,
+ (struct lwis_cmd_periodic_io_cancel __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DPM_CLK_UPDATE:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_dpm_clk_update(lwis_dev, &header,
+ (struct lwis_cmd_dpm_clk_update __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DPM_QOS_UPDATE:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_dpm_qos_update(lwis_dev, &header,
+ (struct lwis_cmd_dpm_qos_update __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+ case LWIS_CMD_ID_DPM_GET_CLOCK:
+ mutex_lock(&lwis_client->lock);
+ ret = cmd_dpm_get_clock(lwis_dev, &header,
+ (struct lwis_cmd_dpm_clk_get __user *)user_msg);
+ mutex_unlock(&lwis_client->lock);
+ break;
+#ifdef LWIS_FENCE_ENABLED
+ case LWIS_CMD_ID_FENCE_CREATE:
+ ret = cmd_fence_create(lwis_dev, &header,
+ (struct lwis_cmd_fence_create __user *)user_msg);
+ break;
+#endif
+ default:
+ dev_err_ratelimited(lwis_dev->dev, "Unknown command id\n");
+ header.ret_code = -EINVAL;
+ ret = copy_pkt_to_user(lwis_dev, user_msg, (void *)&header, sizeof(header));
+ }
+ if (ret) {
+ return ret;
+ }
+ user_msg = header.next;
+ }
+
+ return ret;
+}