blob: 9e82632d1e80f68f7478fa074bf1467ba73b088c [file] [log] [blame]
// 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_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;
}