537 lines
11 KiB
C
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");
|