/* * Synaptics TCM touchscreen driver * * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. * * Copyright (C) 2017-2019 Scott Lin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. * DOLLARS. */ #include #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, 10, &input)) 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) { LOGE(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, 10, &input)) 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) { LOGE(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) { LOGE(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for 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) { LOGE(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) { LOGE(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for 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) { LOGE(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; } } 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) { LOGE(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) { LOGE(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) { LOGE(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) { LOGE(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");