kernel_samsung_a34x-permissive/drivers/input/touchscreen/TD4320/synaptics_tcm_diagnostics.c
2024-04-28 15:51:13 +02:00

537 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include "synaptics_tcm_core.h"
#define SYSFS_DIR_NAME "diagnostics"
enum pingpong_state {
PING = 0,
PONG = 1,
};
struct diag_hcd {
pid_t pid;
unsigned char report_type;
enum pingpong_state state;
struct kobject *sysfs_dir;
struct siginfo sigio;
struct task_struct *task;
struct syna_tcm_buffer ping;
struct syna_tcm_buffer pong;
struct syna_tcm_hcd *tcm_hcd;
};
DECLARE_COMPLETION(diag_remove_complete);
static struct diag_hcd *diag_hcd;
STORE_PROTOTYPE(diag, pid)
SHOW_PROTOTYPE(diag, size)
STORE_PROTOTYPE(diag, type)
SHOW_PROTOTYPE(diag, rows)
SHOW_PROTOTYPE(diag, cols)
SHOW_PROTOTYPE(diag, hybrid)
SHOW_PROTOTYPE(diag, buttons)
static struct device_attribute *attrs[] = {
ATTRIFY(pid),
ATTRIFY(size),
ATTRIFY(type),
ATTRIFY(rows),
ATTRIFY(cols),
ATTRIFY(hybrid),
ATTRIFY(buttons),
};
static ssize_t diag_sysfs_data_show(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count);
static struct bin_attribute bin_attr = {
.attr = {
.name = "data",
.mode = 0444,
},
.size = 0,
.read = diag_sysfs_data_show,
};
static ssize_t diag_sysfs_pid_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
unsigned int input;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
if (kstrtouint(buf, 0, &input) != 0)
return -EINVAL;
mutex_lock(&tcm_hcd->extif_mutex);
diag_hcd->pid = input;
if (diag_hcd->pid) {
diag_hcd->task = pid_task(find_vpid(diag_hcd->pid),
PIDTYPE_PID);
if (!diag_hcd->task) {
LOG_ERR(tcm_hcd->pdev->dev.parent,
"Failed to locate task\n");
retval = -EINVAL;
goto exit;
}
}
retval = count;
exit:
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_size_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
if (diag_hcd->state == PING) {
LOCK_BUFFER(diag_hcd->ping);
retval = snprintf(buf, PAGE_SIZE,
"%u\n",
diag_hcd->ping.data_length);
UNLOCK_BUFFER(diag_hcd->ping);
} else {
LOCK_BUFFER(diag_hcd->pong);
retval = snprintf(buf, PAGE_SIZE,
"%u\n",
diag_hcd->pong.data_length);
UNLOCK_BUFFER(diag_hcd->pong);
}
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_type_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int input;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
if (kstrtouint(buf, 0, &input) != 0)
return -EINVAL;
mutex_lock(&tcm_hcd->extif_mutex);
diag_hcd->report_type = (unsigned char)input;
mutex_unlock(&tcm_hcd->extif_mutex);
return count;
}
static ssize_t diag_sysfs_rows_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval;
unsigned int rows;
struct syna_tcm_app_info *app_info;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
tcm_hcd->app_status != APP_STATUS_OK) {
retval = -ENODEV;
goto exit;
}
app_info = &tcm_hcd->app_info;
rows = le2_to_uint(app_info->num_of_image_rows);
retval = snprintf(buf, PAGE_SIZE, "%u\n", rows);
exit:
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_cols_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval;
unsigned int cols;
struct syna_tcm_app_info *app_info;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
tcm_hcd->app_status != APP_STATUS_OK) {
retval = -ENODEV;
goto exit;
}
app_info = &tcm_hcd->app_info;
cols = le2_to_uint(app_info->num_of_image_cols);
retval = snprintf(buf, PAGE_SIZE, "%u\n", cols);
exit:
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_hybrid_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval;
unsigned int hybrid;
struct syna_tcm_app_info *app_info;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
tcm_hcd->app_status != APP_STATUS_OK) {
retval = -ENODEV;
goto exit;
}
app_info = &tcm_hcd->app_info;
hybrid = le2_to_uint(app_info->has_hybrid_data);
retval = snprintf(buf, PAGE_SIZE, "%u\n", hybrid);
exit:
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_buttons_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval;
unsigned int buttons;
struct syna_tcm_app_info *app_info;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
tcm_hcd->app_status != APP_STATUS_OK) {
retval = -ENODEV;
goto exit;
}
app_info = &tcm_hcd->app_info;
buttons = le2_to_uint(app_info->num_of_buttons);
retval = snprintf(buf, PAGE_SIZE, "%u\n", buttons);
exit:
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_data_show(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count)
{
int retval;
unsigned int readlen;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
retval = 0;
if (diag_hcd->state == PING) {
LOCK_BUFFER(diag_hcd->ping);
if (diag_hcd->ping.data_length == 0) {
readlen = 0;
goto exit;
}
readlen = MIN(count, diag_hcd->ping.data_length - pos);
if (diag_hcd->ping.data_length) {
retval = secure_memcpy(buf,
count,
&diag_hcd->ping.buf[pos],
diag_hcd->ping.buf_size - pos,
readlen);
}
UNLOCK_BUFFER(diag_hcd->ping);
} else {
LOCK_BUFFER(diag_hcd->pong);
if (diag_hcd->pong.data_length == 0) {
readlen = 0;
goto exit;
}
readlen = MIN(count, diag_hcd->pong.data_length - pos);
if (diag_hcd->pong.data_length) {
retval = secure_memcpy(buf,
count,
&diag_hcd->pong.buf[pos],
diag_hcd->pong.buf_size - pos,
readlen);
}
UNLOCK_BUFFER(diag_hcd->pong);
}
exit:
if (retval < 0) {
LOG_ERR(tcm_hcd->pdev->dev.parent,
"Failed to copy report data\n");
} else {
retval = readlen;
}
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static void diag_report(void)
{
int retval;
static enum pingpong_state state = PING;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
if (state == PING) {
LOCK_BUFFER(diag_hcd->ping);
retval = syna_tcm_alloc_mem(tcm_hcd,
&diag_hcd->ping,
tcm_hcd->report.buffer.data_length);
if (retval < 0) {
LOG_ERR(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for diag_hcd->ping.buf\n");
UNLOCK_BUFFER(diag_hcd->ping);
return;
}
retval = secure_memcpy(diag_hcd->ping.buf,
diag_hcd->ping.buf_size,
tcm_hcd->report.buffer.buf,
tcm_hcd->report.buffer.buf_size,
tcm_hcd->report.buffer.data_length);
if (retval < 0) {
LOG_ERR(tcm_hcd->pdev->dev.parent,
"Failed to copy report data\n");
UNLOCK_BUFFER(diag_hcd->ping);
return;
}
diag_hcd->ping.data_length = tcm_hcd->report.buffer.data_length;
UNLOCK_BUFFER(diag_hcd->ping);
diag_hcd->state = state;
state = PONG;
} else {
LOCK_BUFFER(diag_hcd->pong);
retval = syna_tcm_alloc_mem(tcm_hcd,
&diag_hcd->pong,
tcm_hcd->report.buffer.data_length);
if (retval < 0) {
LOG_ERR(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for diag_hcd->pong.buf\n");
UNLOCK_BUFFER(diag_hcd->pong);
return;
}
retval = secure_memcpy(diag_hcd->pong.buf,
diag_hcd->pong.buf_size,
tcm_hcd->report.buffer.buf,
tcm_hcd->report.buffer.buf_size,
tcm_hcd->report.buffer.data_length);
if (retval < 0) {
LOG_ERR(tcm_hcd->pdev->dev.parent,
"Failed to copy report data\n");
UNLOCK_BUFFER(diag_hcd->pong);
return;
}
diag_hcd->pong.data_length = tcm_hcd->report.buffer.data_length;
UNLOCK_BUFFER(diag_hcd->pong);
diag_hcd->state = state;
state = PING;
}
if (diag_hcd->pid)
send_sig_info(SIGIO, &diag_hcd->sigio, diag_hcd->task);
}
static int diag_init(struct syna_tcm_hcd *tcm_hcd)
{
int retval;
int idx;
diag_hcd = kzalloc(sizeof(*diag_hcd), GFP_KERNEL);
if (!diag_hcd) {
LOG_ERR(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for diag_hcd\n");
return -ENOMEM;
}
diag_hcd->tcm_hcd = tcm_hcd;
diag_hcd->state = PING;
INIT_BUFFER(diag_hcd->ping, false);
INIT_BUFFER(diag_hcd->pong, false);
memset(&diag_hcd->sigio, 0x00, sizeof(diag_hcd->sigio));
diag_hcd->sigio.si_signo = SIGIO;
diag_hcd->sigio.si_code = SI_USER;
diag_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME,
tcm_hcd->sysfs_dir);
if (!diag_hcd->sysfs_dir) {
LOG_ERR(tcm_hcd->pdev->dev.parent,
"Failed to create sysfs directory\n");
retval = -EINVAL;
goto err_sysfs_create_dir;
}
for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
retval = sysfs_create_file(diag_hcd->sysfs_dir,
&(*attrs[idx]).attr);
if (retval < 0) {
LOG_ERR(tcm_hcd->pdev->dev.parent,
"Failed to create sysfs file\n");
goto err_sysfs_create_file;
}
}
retval = sysfs_create_bin_file(diag_hcd->sysfs_dir, &bin_attr);
if (retval < 0) {
LOG_ERR(tcm_hcd->pdev->dev.parent,
"Failed to create sysfs bin file\n");
goto err_sysfs_create_bin_file;
}
return 0;
err_sysfs_create_bin_file:
err_sysfs_create_file:
for (idx--; idx >= 0; idx--)
sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr);
kobject_put(diag_hcd->sysfs_dir);
err_sysfs_create_dir:
RELEASE_BUFFER(diag_hcd->pong);
RELEASE_BUFFER(diag_hcd->ping);
kfree(diag_hcd);
diag_hcd = NULL;
return retval;
}
static int diag_remove(struct syna_tcm_hcd *tcm_hcd)
{
int idx;
if (!diag_hcd)
goto exit;
sysfs_remove_bin_file(diag_hcd->sysfs_dir, &bin_attr);
for (idx = 0; idx < ARRAY_SIZE(attrs); idx++)
sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr);
kobject_put(diag_hcd->sysfs_dir);
RELEASE_BUFFER(diag_hcd->pong);
RELEASE_BUFFER(diag_hcd->ping);
kfree(diag_hcd);
diag_hcd = NULL;
exit:
complete(&diag_remove_complete);
return 0;
}
static int diag_syncbox(struct syna_tcm_hcd *tcm_hcd)
{
if (!diag_hcd)
return 0;
if (tcm_hcd->report.id == diag_hcd->report_type)
diag_report();
return 0;
}
static int diag_reset(struct syna_tcm_hcd *tcm_hcd)
{
int retval;
if (!diag_hcd) {
retval = diag_init(tcm_hcd);
return retval;
}
return 0;
}
static struct syna_tcm_module_cb diag_module = {
.type = TCM_DIAGNOSTICS,
.init = diag_init,
.remove = diag_remove,
.syncbox = diag_syncbox,
.asyncbox = NULL,
.reset = diag_reset,
.suspend = NULL,
.resume = NULL,
.early_suspend = NULL,
};
static int __init diag_module_init(void)
{
return syna_tcm_add_module(&diag_module, true);
}
static void __exit diag_module_exit(void)
{
syna_tcm_add_module(&diag_module, false);
wait_for_completion(&diag_remove_complete);
}
module_init(diag_module_init);
module_exit(diag_module_exit);
MODULE_AUTHOR("Synaptics, Inc.");
MODULE_DESCRIPTION("Synaptics TCM Diagnostics Module");
MODULE_LICENSE("GPL v2");