// SPDX-License-Identifier: GPL-2.0-or-later #define LOG_TAG "Tool" #include "cts_config.h" #include "cts_platform.h" #include "cts_core.h" #include "cts_firmware.h" #include "cts_test.h" #include "cts_tcs.h" #ifdef CONFIG_CTS_LEGACY_TOOL #define CTS_TOOL_IOCTL_TCS_RW_BUF_MAX_SIZ 1024 #define CTS_TOOL_IOCTL_TCS_RW_OPFLAG_READ 1 #define CTS_TOOL_IOCTL_TCS_RW_OPFLAG_WRITE 2 #pragma pack(1) /** Tool command structure */ struct cts_tool_cmd { u8 cmd; u8 flag; u8 circle; u8 times; u8 retry; u32 data_len; u8 addr_len; u8 addr[4]; u8 tcs[2]; u8 data[PAGE_SIZE]; }; struct cts_ioctl_tcs_rw_data { u32 opflags; u32 txlen; u32 rxlen; u8 __user *txbuf; u8 __user *rxbuf; }; #pragma pack() #define CTS_TOOL_CMD_HEADER_LENGTH (16) enum cts_tool_cmd_code { CTS_TOOL_CMD_GET_PANEL_PARAM = 0, CTS_TOOL_CMD_GET_DOWNLOAD_STATUS = 2, CTS_TOOL_CMD_GET_RAW_DATA = 4, CTS_TOOL_CMD_GET_DIFF_DATA = 6, CTS_TOOL_CMD_READ_HOSTCOMM = 12, CTS_TOOL_CMD_READ_ADC_STATUS = 14, CTS_TOOL_CMD_READ_GESTURE_INFO = 16, CTS_TOOL_CMD_READ_HOSTCOMM_MULTIBYTE = 18, CTS_TOOL_CMD_READ_PROGRAM_MODE_MULTIBYTE = 20, CTS_TOOL_CMD_READ_ICTYPE = 22, CTS_TOOL_CMD_I2C_DIRECT_READ = 24, CTS_TOOL_CMD_GET_DRIVER_INFO = 26, CTS_TOOL_CMD_TCS_READ_HW_CMD = 28, CTS_TOOL_CMD_TCS_READ_DDI_CMD = 30, CTS_TOOL_CMD_TCS_READ_FW_CMD = 32, CTS_TOOL_CMD_GET_BASE_DATA = 34, CTS_TOOL_CMD_GET_INT_DATAS = 36, CTS_TOOL_CMD_UPDATE_PANEL_PARAM_IN_SRAM = 1, CTS_TOOL_CMD_DOWNLOAD_FIRMWARE_WITH_FILENAME = 3, CTS_TOOL_CMD_DOWNLOAD_FIRMWARE = 5, CTS_TOOL_CMD_WRITE_HOSTCOMM = 11, CTS_TOOL_CMD_WRITE_HOSTCOMM_MULTIBYTE = 15, CTS_TOOL_CMD_WRITE_PROGRAM_MODE_MULTIBYTE = 17, CTS_TOOL_CMD_I2C_DIRECT_WRITE = 19, CTS_TOOL_CMD_TCS_WRITE_HW_CMD = 21, CTS_TOOL_CMD_TCS_WRITE_DDI_CMD = 23, CTS_TOOL_CMD_TCS_WRITE_FW_CMD = 25, CTS_TOOL_CMD_TCS_ENABLE_GET_RAWDATA_CMD = 27, CTS_TOOL_CMD_TCS_DISABLE_GET_RAWDATA_CMD = 29, CTS_TOOL_CMD_SET_INT_DATA_TYPE_AND_METHOD = 31, }; struct cts_test_ioctl_data { __u32 ntests; struct cts_test_param __user *tests; }; #define CTS_TOOL_IOCTL_GET_DRIVER_VERSION _IOR('C', 0x00, u32 *) #define CTS_TOOL_IOCTL_GET_DEVICE_TYPE _IOR('C', 0x01, u32 *) #define CTS_TOOL_IOCTL_GET_FW_VERSION _IOR('C', 0x02, u16 *) #define CTS_TOOL_IOCTL_GET_RESOLUTION _IOR('C', 0x03, u32 *) /* X in LSW, Y in MSW */ #define CTS_TOOL_IOCTL_GET_ROW_COL _IOR('C', 0x04, u16 *) /* row in LSW, col in MSW */ #define CTS_TOOL_IOCTL_GET_MODULE_ID _IOWR('C', 0x05, u32 *) #define CTS_TOOL_IOCTL_TEST _IOWR('C', 0x10, struct cts_test_ioctl_data *) #define CTS_TOOL_IOCTL_RDWR_REG _IOWR('C', 0x20, struct cts_rdwr_reg_ioctl_data *) #define CTS_TOOL_IOCTL_UPGRADE_FW _IOWR('C', 0x21, struct cts_upgrade_fw_ioctl_data *) #define CTS_TOOL_IOCTL_TCS_RW _IOWR('C', 0x30, struct cts_ioctl_tcs_rw_data *) #define CTS_DRIVER_VERSION_CODE ((CFG_CTS_DRIVER_MAJOR_VERSION << 16) | \ (CFG_CTS_DRIVER_MINOR_VERSION << 8) | \ (CFG_CTS_DRIVER_PATCH_VERSION << 0)) static struct cts_tool_cmd cts_tool_cmd; static char cts_tool_firmware_filepath[PATH_MAX]; /* If CFG_CTS_MAX_I2C_XFER_SIZE < 58(PC tool length), this is neccessary */ #ifdef CONFIG_CTS_I2C_HOST static u32 cts_tool_direct_access_addr = 0; #endif /* CONFIG_CTS_I2C_HOST */ static int cts_tool_open(struct inode *inode, struct file *file) { file->private_data = pde_data(inode); return 0; } static ssize_t cts_tool_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { #define RAWDATA_BUFFER_SIZE(cts_dev) \ (cts_dev->hwdata->num_row * cts_dev->hwdata->num_col * 2) struct chipone_ts_data *cts_data; struct cts_tool_cmd *cmd; struct cts_device *cts_dev; int ret = 0; cts_data = (struct chipone_ts_data *)file->private_data; if (cts_data == NULL) { cts_err("Read with private_data = NULL"); return -EIO; } cmd = &cts_tool_cmd; cts_dev = &cts_data->cts_dev; cts_lock_device(cts_dev); cts_dbg("read: flag:%d, addr:0x%06x, tcm:0x%04x, datalen:%d", cmd->cmd, get_unaligned_le32(cmd->addr), get_unaligned_le16(cmd->tcs), cmd->data_len); switch (cmd->cmd) { case CTS_TOOL_CMD_TCS_READ_HW_CMD: cts_tcs_read_hw_reg(cts_dev, get_unaligned_le32(cmd->addr), cmd->data, cmd->data_len); break; case CTS_TOOL_CMD_TCS_READ_DDI_CMD: cts_tcs_read_ddi_reg(cts_dev, get_unaligned_le32(cmd->addr), cmd->data, cmd->data_len); break; case CTS_TOOL_CMD_TCS_READ_FW_CMD: cts_tcs_read_reg(cts_dev, get_unaligned_le16(cmd->tcs), cmd->data, cmd->data_len); break; case CTS_TOOL_CMD_GET_PANEL_PARAM: cts_info("Get panel param len: %u", cmd->data_len); break; case CTS_TOOL_CMD_GET_DOWNLOAD_STATUS: cmd->data[0] = 100; cts_info("Get update status = %hhu", cmd->data[0]); break; case CTS_TOOL_CMD_GET_RAW_DATA: case CTS_TOOL_CMD_GET_DIFF_DATA: case CTS_TOOL_CMD_GET_BASE_DATA: cts_err("Get %s data row: %u col: %u len: %u, source: %x", cmd->cmd == CTS_TOOL_CMD_GET_RAW_DATA ? "raw" : "diff", cmd->addr[1], cmd->addr[0], cmd->data_len, (cmd->data[1] & 0xf0)); if (cmd->cmd == CTS_TOOL_CMD_GET_RAW_DATA) { ret = cts_tcs_tool_get_rawdata(cts_dev, (u8 *) cmd->data, (cmd->data[1] & 0xf0) << 8); } else if (cmd->cmd == CTS_TOOL_CMD_GET_DIFF_DATA) { ret = cts_tcs_tool_get_manual_diff(cts_dev, (u8 *) cmd->data, (cmd->data[1] & 0xf0) << 8); } else if (cmd->cmd == CTS_TOOL_CMD_GET_BASE_DATA) { ret = cts_tcs_tool_get_basedata(cts_dev, (u8 *) cmd->data, (cmd->data[1] & 0xf0) << 8); } if (ret) { cts_err("Get %s data failed %d", cmd->cmd == CTS_TOOL_CMD_GET_RAW_DATA ? "raw" : cmd->cmd == CTS_TOOL_CMD_GET_DIFF_DATA ? "diff" : "base", ret); } break; case CTS_TOOL_CMD_GET_INT_DATAS: memcpy((u8 *)cmd->data, cts_dev->int_data, cts_dev->fwdata.int_data_size); cmd->data_len = cts_dev->fwdata.int_data_size; break; case CTS_TOOL_CMD_READ_HOSTCOMM: ret = cts_fw_reg_readb(cts_dev, get_unaligned_le16(cmd->addr), cmd->data); if (ret) { cts_err("Read firmware reg addr 0x%04x failed %d", get_unaligned_le16(cmd->addr), ret); } else { cts_dbg("Read firmware reg addr 0x%04x, val=0x%02x", get_unaligned_le16(cmd->addr), cmd->data[0]); } break; #ifdef CFG_CTS_GESTURE case CTS_TOOL_CMD_READ_GESTURE_INFO: ret = cts_get_gesture_info(cts_dev, cmd->data); if (ret) cts_err("Get gesture info failed %d", ret); break; #endif /* CFG_CTS_GESTURE */ case CTS_TOOL_CMD_READ_HOSTCOMM_MULTIBYTE: cmd->data_len = min((size_t)cmd->data_len, sizeof(cmd->data)); ret = cts_fw_reg_readsb(cts_dev, get_unaligned_le16(cmd->addr), cmd->data, cmd->data_len); if (ret) { cts_err("Read firmware reg addr 0x%04x len %u failed %d", get_unaligned_le16(cmd->addr), cmd->data_len, ret); } else { cts_dbg("Read firmware reg addr 0x%04x len %u", get_unaligned_le16(cmd->addr), cmd->data_len); } break; case CTS_TOOL_CMD_READ_PROGRAM_MODE_MULTIBYTE: cts_dbg("Read under program mode addr 0x%06x len %u", (cmd->flag << 16) | get_unaligned_le16(cmd->addr), cmd->data_len); ret = cts_enter_program_mode(cts_dev); if (ret) { cts_err("Enter program mode failed %d", ret); break; } ret = cts_sram_readsb(&cts_data->cts_dev, get_unaligned_le16(cmd->addr), cmd->data, cmd->data_len); if (ret) { cts_err("Read under program mode I2C xfer failed %d", ret); } ret = cts_enter_normal_mode(cts_dev); if (ret) { cts_err("Enter normal mode failed %d", ret); break; } break; case CTS_TOOL_CMD_READ_ICTYPE: cts_info("Get IC type"); break; #ifdef CONFIG_CTS_I2C_HOST case CTS_TOOL_CMD_I2C_DIRECT_READ: { u32 addr_width; char *wr_buff = NULL; u8 addr_buff[4]; size_t left_size, max_xfer_size; u8 *data; if (cmd->addr[0] != CTS_DEV_PROGRAM_MODE_I2CADDR) { cmd->addr[0] = CTS_DEV_NORMAL_MODE_I2CADDR; addr_width = 2; } else { addr_width = cts_dev->hwdata->program_addr_width; } cts_dbg("Direct read from i2c_addr 0x%02x addr 0x%06x size %u", cmd->addr[0], cts_tool_direct_access_addr, cmd->data_len); left_size = cmd->data_len; max_xfer_size = cts_plat_get_max_i2c_xfer_size(cts_dev->pdata); data = cmd->data; while (left_size) { size_t xfer_size = min(left_size, max_xfer_size); ret = cts_plat_i2c_read(cts_data->pdata, cmd->addr[0], wr_buff, addr_width, data, xfer_size, 1, 0); if (ret) { cts_err("Direct read i2c_addr 0x%02x addr 0x%06x " "len %zu failed %d", cmd->addr[0], cts_tool_direct_access_addr, xfer_size, ret); break; } left_size -= xfer_size; if (left_size) { data += xfer_size; cts_tool_direct_access_addr += xfer_size; if (addr_width == 2) { put_unaligned_be16(cts_tool_direct_access_addr, addr_buff); } else if (addr_width == 3) { put_unaligned_be24(cts_tool_direct_access_addr, addr_buff); } wr_buff = addr_buff; } } } break; #endif case CTS_TOOL_CMD_GET_DRIVER_INFO: break; default: cts_err("Read unknown command %u", cmd->cmd); ret = -EINVAL; break; } cts_unlock_device(cts_dev); if (ret == 0) { if (cmd->cmd == CTS_TOOL_CMD_I2C_DIRECT_READ) { ret = copy_to_user(buffer + CTS_TOOL_CMD_HEADER_LENGTH, cmd->data, cmd->data_len); } else { ret = copy_to_user(buffer, cmd->data, cmd->data_len); } if (ret) { cts_err("Copy data to user buffer failed %d", ret); return 0; } return cmd->data_len; } return 0; } static ssize_t cts_tool_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { struct chipone_ts_data *cts_data; struct cts_device *cts_dev; struct cts_tool_cmd *cmd; int ret = 0; if (count < CTS_TOOL_CMD_HEADER_LENGTH || count > PAGE_SIZE) { cts_err("Write len %zu invalid", count); return -EFAULT; } cts_data = (struct chipone_ts_data *)file->private_data; if (cts_data == NULL) { cts_err("Write with private_data = NULL"); return -EIO; } cmd = &cts_tool_cmd; ret = copy_from_user(cmd, buffer, CTS_TOOL_CMD_HEADER_LENGTH); cts_info("write: flag:%d, addr:0x%06x, tcmd:0x%04x, datalen:%d", cmd->cmd, get_unaligned_le32(cmd->addr), get_unaligned_le16(cmd->tcs), cmd->data_len); if (ret) { cts_err("Copy command header from user buffer failed %d", ret); return -EIO; } else ret = CTS_TOOL_CMD_HEADER_LENGTH; if (cmd->data_len > PAGE_SIZE) { cts_err("Write with invalid count %d", cmd->data_len); return -EIO; } if (count > CTS_TOOL_CMD_HEADER_LENGTH) { ret = copy_from_user(cmd->data, buffer + CTS_TOOL_CMD_HEADER_LENGTH, count - CTS_TOOL_CMD_HEADER_LENGTH); if (ret) { cts_err("Copy command payload from user buffer len %u failed %d", cmd->data_len, ret); return -EIO; } } if (cmd->cmd & BIT(0)) { if (cmd->data_len) { ret = copy_from_user(cmd->data, buffer + CTS_TOOL_CMD_HEADER_LENGTH, cmd->data_len); if (ret) { cts_err("Copy command payload from user buffer len %u failed %d", cmd->data_len, ret); return -EIO; } } } else { /* Prepare for read command */ cts_dbg("Write read command(%d) header, prepare read size: %d", cmd->cmd, cmd->data_len); return CTS_TOOL_CMD_HEADER_LENGTH + cmd->data_len; } cts_dev = &cts_data->cts_dev; cts_lock_device(cts_dev); switch (cmd->cmd) { case CTS_TOOL_CMD_TCS_WRITE_HW_CMD: cts_tcs_write_hw_reg(cts_dev, get_unaligned_le32(cmd->addr), cmd->data, cmd->data_len); break; case CTS_TOOL_CMD_TCS_WRITE_DDI_CMD: cts_tcs_write_ddi_reg(cts_dev, get_unaligned_le32(cmd->addr), cmd->data, cmd->data_len); break; case CTS_TOOL_CMD_TCS_WRITE_FW_CMD: cts_tcs_write_reg(cts_dev, get_unaligned_le16(cmd->tcs), cmd->data, cmd->data_len); break; case CTS_TOOL_CMD_TCS_ENABLE_GET_RAWDATA_CMD: case CTS_TOOL_CMD_TCS_DISABLE_GET_RAWDATA_CMD: cts_info("Do nothing"); break; case CTS_TOOL_CMD_SET_INT_DATA_TYPE_AND_METHOD: cts_set_int_data_types(cts_dev, cmd->data[0] | (cmd->data[1] << 8)); cts_set_int_data_method(cts_dev, cmd->data[2]); break; case CTS_TOOL_CMD_UPDATE_PANEL_PARAM_IN_SRAM: break; case CTS_TOOL_CMD_DOWNLOAD_FIRMWARE_WITH_FILENAME: cts_info("Write firmware path: '%.*s'", cmd->data_len, cmd->data); memcpy(cts_tool_firmware_filepath, cmd->data, cmd->data_len); cts_tool_firmware_filepath[cmd->data_len] = '\0'; break; case CTS_TOOL_CMD_DOWNLOAD_FIRMWARE: cts_info("Start download firmware path: '%s'", cts_tool_firmware_filepath); ret = cts_stop_device(cts_dev); if (ret) { cts_err("Stop device failed %d", ret); break; } ret = cts_update_firmware_from_file(cts_dev, cts_tool_firmware_filepath); if (ret) cts_err("Updata firmware failed %d", ret); ret = cts_start_device(cts_dev); if (ret) { cts_err("Start device failed %d", ret); break; } break; case CTS_TOOL_CMD_WRITE_HOSTCOMM: cts_dbg("Write firmware reg addr: 0x%04x val=0x%02x", get_unaligned_le16(cmd->addr), cmd->data[0]); ret = cts_fw_reg_writeb(cts_dev, get_unaligned_le16(cmd->addr), cmd->data[0]); if (ret) { cts_err("Write firmware reg addr: 0x%04x val=0x%02x failed %d", get_unaligned_le16(cmd->addr), cmd->data[0], ret); } break; case CTS_TOOL_CMD_WRITE_HOSTCOMM_MULTIBYTE: cts_dbg("Write firmare reg addr: 0x%04x len %u", get_unaligned_le16(cmd->addr), cmd->data_len); ret = cts_fw_reg_writesb(cts_dev, get_unaligned_le16(cmd->addr), cmd->data, cmd->data_len); if (ret) { cts_err("Write firmare reg addr: 0x%04x len %u failed %d", get_unaligned_le16(cmd->addr), cmd->data_len, ret); } break; case CTS_TOOL_CMD_WRITE_PROGRAM_MODE_MULTIBYTE: cts_dbg("Write to addr 0x%06x size %u under program mode", (cmd->flag << 16) | (cmd->addr[1] << 8) | cmd->addr[0], cmd->data_len); ret = cts_enter_program_mode(cts_dev); if (ret) { cts_err("Enter program mode failed %d", ret); break; } ret = cts_sram_writesb(cts_dev, (cmd->flag << 16) | (cmd->addr[1] << 8) | cmd->addr[0], cmd->data, cmd->data_len); if (ret) cts_err("Write program mode multibyte failed %d", ret); ret = cts_enter_normal_mode(cts_dev); if (ret) { cts_err("Enter normal mode failed %d", ret); break; } break; #ifdef CONFIG_CTS_I2C_HOST case CTS_TOOL_CMD_I2C_DIRECT_WRITE: { u32 addr_width; size_t left_payload_size; /* Payload exclude address field */ size_t max_xfer_size; char *payload; if (cmd->addr[0] != CTS_DEV_PROGRAM_MODE_I2CADDR) { cmd->addr[0] = CTS_DEV_NORMAL_MODE_I2CADDR; addr_width = 2; cts_tool_direct_access_addr = get_unaligned_be16(cmd->data); } else { addr_width = cts_dev->hwdata->program_addr_width; cts_tool_direct_access_addr = get_unaligned_be24(cmd->data); } if (cmd->data_len < addr_width) { cts_err("Direct write too short %d < address width %d", cmd->data_len, addr_width); ret = -EINVAL; break; } cts_dbg("Direct write to i2c_addr 0x%02x addr 0x%06x size %u", cmd->addr[0], cts_tool_direct_access_addr, cmd->data_len); left_payload_size = cmd->data_len - addr_width; max_xfer_size = cts_plat_get_max_i2c_xfer_size(cts_dev->pdata); payload = cmd->data + addr_width; do { size_t xfer_payload_size = min(left_payload_size, max_xfer_size - addr_width); size_t xfer_len = xfer_payload_size + addr_width; ret = cts_plat_i2c_write(cts_data->pdata, cmd->addr[0], payload - addr_width, xfer_len, 1, 0); if (ret) { cts_err("Direct write i2c_addr 0x%02x addr 0x%06x len %zu failed %d", cmd->addr[0], cts_tool_direct_access_addr, xfer_len, ret); break; } left_payload_size -= xfer_payload_size; if (left_payload_size) { payload += xfer_payload_size; cts_tool_direct_access_addr += xfer_payload_size; if (addr_width == 2) { put_unaligned_be16(cts_tool_direct_access_addr, payload - addr_width); } else if (addr_width == 3) { put_unaligned_be24(cts_tool_direct_access_addr, payload - addr_width); } } } while (left_payload_size); } break; #endif default: cts_warn("Write unknown command %u", cmd->cmd); ret = -EINVAL; break; } cts_unlock_device(cts_dev); return ret ? 0 : cmd->data_len + CTS_TOOL_CMD_HEADER_LENGTH; } static long cts_ioctl_tcs_rw(struct cts_device *cts_dev, struct file *file, unsigned int cmd, unsigned long arg) { int ret; struct cts_ioctl_tcs_rw_data rwdata; u8 *txbuf = NULL; u8 *rxbuf = NULL; if (copy_from_user(&rwdata, (struct cts_ioctl_tcs_rw_data __user *)arg, sizeof(rwdata))) { cts_err("Copy ioctl tcs rw arg to kernel failed"); return -EFAULT; } if ((rwdata.opflags != CTS_TOOL_IOCTL_TCS_RW_OPFLAG_READ) && (rwdata.opflags != CTS_TOOL_IOCTL_TCS_RW_OPFLAG_WRITE)) { cts_err("ioctl tcs rw with invalid opflags %u", rwdata.opflags); return -EINVAL; } if (rwdata.txlen > CTS_TOOL_IOCTL_TCS_RW_BUF_MAX_SIZ) { cts_err("Send too long: %d", rwdata.txlen); return -EINVAL; } if (rwdata.rxlen > CTS_TOOL_IOCTL_TCS_RW_BUF_MAX_SIZ) { cts_err("Recv too long: %d", rwdata.rxlen); return -EINVAL; } if (!rwdata.txbuf || !rwdata.rxbuf) { cts_err("Txbuf of Rxbuf is NULL!"); return -EINVAL; } txbuf = memdup_user(rwdata.txbuf, rwdata.txlen); if (IS_ERR(txbuf)) { ret = PTR_ERR(txbuf); cts_err("Memdup txbuf failed %d", ret); txbuf = NULL; goto free_buf; } rxbuf = memdup_user(rwdata.rxbuf, rwdata.rxlen); if (IS_ERR(rxbuf)) { ret = PTR_ERR(rxbuf); cts_err("Memdup rxbuf failed %d", ret); rxbuf = NULL; goto free_buf; } cts_lock_device(cts_dev); ret = cts_tcs_tool_xtrans(cts_dev, txbuf, rwdata.txlen, rxbuf, rwdata.rxlen); cts_unlock_device(cts_dev); if (ret < 0) { cts_err("Trans failed %d", ret); goto free_buf; } ret = copy_to_user(rwdata.rxbuf, rxbuf, rwdata.rxlen); if (ret < 0) { cts_err("Copy to user failed %d", ret); goto free_buf; } ret = 0; free_buf: if (txbuf) { kfree(txbuf); txbuf = NULL; } if (rxbuf) { kfree(rxbuf); rxbuf = NULL; } return ret; } static int cts_ioctl_test(struct cts_device *cts_dev, u32 ntests, struct cts_test_param *tests) { u32 num_nodes = 0; int i, ret = 0; cts_info("ioctl test total %u items", ntests); num_nodes = cts_dev->fwdata.rows * cts_dev->fwdata.cols; for (i = 0; i < ntests; i++) { bool validate_data = false; bool validate_data_per_node = false; bool validate_min = false; bool validate_max = false; bool skip_invalid_node = false; bool stop_test_if_validate_fail = false; bool dump_test_data_to_console = false; bool dump_test_data_to_user = false; bool dump_test_data_to_file = false; bool dump_test_data_to_file_append = false; bool driver_log_to_user = false; bool driver_log_to_file = false; bool driver_log_to_file_append = false; u32 __user *user_min_threshold = NULL; u32 __user *user_max_threshold = NULL; u32 __user *user_invalid_nodes = NULL; int __user *user_test_result = NULL; void __user *user_test_data = NULL; int __user *user_test_data_wr_size = NULL; const char __user *user_test_data_filepath = NULL; void __user *user_driver_log_buf = NULL; int __user *user_driver_log_wr_size = NULL; const char __user *user_driver_log_filepath = NULL; void __user *user_priv_param = NULL; int test_result = 0; int test_data_wr_size = 0; int driver_log_wr_size = 0; cts_info("ioctl test item %d: %d(%s) flags: %08x priv param size: %d", i, tests[i].test_item, cts_test_item_str(tests[i].test_item), tests[i].flags, tests[i].priv_param_size); /* * Validate arguement */ validate_data = !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_DATA); validate_data_per_node = !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_PER_NODE); validate_min = !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_MIN); validate_max = !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_MAX); skip_invalid_node = !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_SKIP_INVALID_NODE); stop_test_if_validate_fail = !!(tests[i].flags & CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED); dump_test_data_to_user = !!(tests[i].flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE); dump_test_data_to_file = !!(tests[i].flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE); dump_test_data_to_file_append = !!(tests[i].flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND); driver_log_to_user = !!(tests[i].flags & CTS_TEST_FLAG_DRIVER_LOG_TO_USERSPACE); driver_log_to_file = !!(tests[i].flags & CTS_TEST_FLAG_DRIVER_LOG_TO_FILE); driver_log_to_file_append = !!(tests[i].flags & CTS_TEST_FLAG_DRIVER_LOG_TO_FILE_APPEND); if (tests[i].test_result == NULL) { cts_err("Result pointer = NULL"); return -EFAULT; } if (validate_data) { cts_info(" - Flag: Validate data"); if (validate_data_per_node) { cts_info(" - Flag: Validate data per-node"); } if (validate_min) { cts_info(" - Flag: Validate min threshold"); } if (validate_max) { cts_info(" - Flag: Validate max threshold"); } if (stop_test_if_validate_fail) { cts_info(" - Flag: Stop test if validate fail"); } if (validate_min && tests[i].min == NULL) { cts_err("Min threshold pointer = NULL"); ret = -EINVAL; goto store_result; } if (validate_max && tests[i].max == NULL) { cts_err("Max threshold pointer = NULL"); ret = -EINVAL; goto store_result; } if (skip_invalid_node) { cts_info(" - Flag: Skip invalid node"); if (tests[i].num_invalid_node == 0 || tests[i].num_invalid_node >= num_nodes) { cts_err("Num invalid node %u out of range[0, %u]", tests[i].num_invalid_node, num_nodes); ret = -EINVAL; goto store_result; } if (tests[i].invalid_nodes == NULL) { cts_err("Invalid nodes pointer = NULL"); ret = -EINVAL; goto store_result; } } } if (dump_test_data_to_console) { cts_info(" - Flag: Dump test data to console"); } if (dump_test_data_to_user) { cts_info(" - Flag: Dump test data to user, size: %d", tests[i].test_data_buf_size); if (tests[i].test_data_buf == NULL) { cts_err("Test data pointer = NULL"); ret = -EINVAL; goto store_result; } if (tests[i].test_data_wr_size == NULL) { cts_err("Test data write size pointer = NULL"); ret = -EINVAL; goto store_result; } if (tests[i].test_data_buf_size < num_nodes) { cts_err("Test data size %d too small < %u", tests[i].test_data_buf_size, num_nodes); ret = -EINVAL; goto store_result; } } if (dump_test_data_to_file) { cts_info(" - Flag: Dump test data to file%s", dump_test_data_to_file_append ? "[Append]" : ""); if (tests[i].test_data_filepath == NULL) { cts_err("Test data filepath = NULL"); ret = -EINVAL; goto store_result; } } if (driver_log_to_user) { cts_info(" - Flag: Dump driver log to user, size: %d", tests[i].driver_log_buf_size); if (tests[i].driver_log_buf == NULL) { cts_err("Driver log buf pointer = NULL"); ret = -EINVAL; goto store_result; } if (tests[i].driver_log_wr_size == NULL) { cts_err("Driver log write size pointer = NULL"); ret = -EINVAL; goto store_result; } if (tests[i].driver_log_buf_size < 1024) { cts_err("Driver log buf size %d too small < 1024", tests[i].test_data_buf_size); ret = -EINVAL; goto store_result; } } if (driver_log_to_file) { cts_info(" - Flag: Dump driver log to file %s", driver_log_to_file_append ? "[Append]" : ""); if (tests[i].driver_log_filepath == NULL) { cts_err("Driver log filepath = NULL"); ret = -EINVAL; goto store_result; } } /* * Dump input parameter from user, * Aallocate memory for output, * Replace __user pointer with kernel pointer. */ user_test_result = (int __user *)tests[i].test_result; tests[i].test_result = &test_result; if (validate_data) { int num_threshold = validate_data_per_node ? num_nodes : 1; int threshold_size = num_threshold * sizeof(tests[i].min[0]); if (validate_min) { user_min_threshold = (int __user *)tests[i].min; tests[i].min = memdup_user(user_min_threshold, threshold_size); if (IS_ERR(tests[i].min)) { ret = PTR_ERR(tests[i].min); tests[i].min = NULL; cts_err("Memdup min threshold from user failed %d", ret); goto store_result; } } else { tests[i].min = NULL; } if (validate_max) { user_max_threshold = (int __user *)tests[i].max; tests[i].max = memdup_user(user_max_threshold, threshold_size); if (IS_ERR(tests[i].max)) { ret = PTR_ERR(tests[i].max); tests[i].max = NULL; cts_err("Memdup max threshold from user failed %d", ret); goto store_result; } } else { tests[i].max = NULL; } if (skip_invalid_node) { user_invalid_nodes = (u32 __user *)tests[i].invalid_nodes; tests[i].invalid_nodes = memdup_user(user_invalid_nodes, tests[i].num_invalid_node * sizeof(tests[i].invalid_nodes[0])); if (IS_ERR(tests[i].invalid_nodes)) { ret = PTR_ERR(tests[i].invalid_nodes); tests[i].invalid_nodes = NULL; cts_err("Memdup invalid node from user failed %d", ret); goto store_result; } } } if (dump_test_data_to_user) { user_test_data = (void __user *)tests[i].test_data_buf; tests[i].test_data_buf = vmalloc(tests[i].test_data_buf_size); if (tests[i].test_data_buf == NULL) { ret = -ENOMEM; cts_err("Alloc test data mem failed"); goto store_result; } user_test_data_wr_size = (int __user *)tests[i].test_data_wr_size; tests[i].test_data_wr_size = &test_data_wr_size; } if (dump_test_data_to_file) { #ifdef CFG_CTS_FOR_GKI cts_info("%s(): strndup_user is forbiddon with GKI Version!", __func__); #else user_test_data_filepath = (const char __user *)tests[i].test_data_filepath; tests[i].test_data_filepath = strndup_user(user_test_data_filepath, PATH_MAX); if (tests[i].test_data_filepath == NULL) { cts_err("Strdup test data filepath failed"); goto store_result; } #endif } if (driver_log_to_user) { user_driver_log_buf = (void __user *)tests[i].driver_log_buf; tests[i].driver_log_buf = kmalloc(tests[i].driver_log_buf_size, GFP_KERNEL); if (tests[i].driver_log_buf == NULL) { ret = -ENOMEM; cts_err("Alloc driver log mem failed"); goto store_result; } user_driver_log_wr_size = (int __user *)tests[i].driver_log_wr_size; tests[i].driver_log_wr_size = &driver_log_wr_size; } if (driver_log_to_file) { #ifdef CFG_CTS_FOR_GKI cts_info("%s(): strndup_user is forbiddon with GKI Version!", __func__); #else user_driver_log_filepath = (const char __user *)tests[i].driver_log_filepath; tests[i].driver_log_filepath = strndup_user(user_driver_log_filepath, PATH_MAX); if (tests[i].driver_log_filepath == NULL) { cts_err("Strdup driver log filepath failed"); goto store_result; } #endif cts_info("Log driver log to file '%s'", tests[i].driver_log_filepath); } if (driver_log_to_file || driver_log_to_user) { ret = cts_start_driver_log_redirect( tests[i].driver_log_filepath, driver_log_to_file_append, tests[i].driver_log_buf, tests[i].driver_log_buf_size, tests[i].driver_log_level); if (ret) { cts_err("Start driver log redirect failed %d", ret); goto store_result; } } if (tests[i].priv_param_size && tests[i].priv_param) { user_priv_param = (void __user *)tests[i].priv_param; tests[i].priv_param = memdup_user(user_priv_param, tests[i].priv_param_size); if (IS_ERR(tests[i].priv_param)) { ret = PTR_ERR(tests[i].priv_param); tests[i].priv_param = NULL; cts_err("Memdup priv param from user failed %d", ret); goto store_result; } } switch (tests[i].test_item) { case CTS_TEST_RESET_PIN: ret = cts_test_reset_pin(cts_dev, &tests[i]); break; case CTS_TEST_INT_PIN: ret = cts_test_int_pin(cts_dev, &tests[i]); break; case CTS_TEST_RAWDATA: ret = cts_test_rawdata(cts_dev, &tests[i]); break; case CTS_TEST_NOISE: ret = cts_test_noise(cts_dev, &tests[i]); break; case CTS_TEST_OPEN: ret = cts_test_open(cts_dev, &tests[i]); break; case CTS_TEST_SHORT: ret = cts_test_short(cts_dev, &tests[i]); break; case CTS_TEST_COMPENSATE_CAP: ret = cts_test_compensate_cap(cts_dev, &tests[i]); break; case CTS_TEST_STYLUS_RAWDATA: ret = cts_test_stylus_rawdata(cts_dev, &tests[i]); break; case CTS_TEST_STYLUS_NOISE: ret = cts_test_stylus_noise(cts_dev, &tests[i]); break; default: ret = ENOTSUPP; cts_err("Un-supported test item"); break; } /* * Copy result and test data back to userspace. */ store_result: if (dump_test_data_to_user) { if (user_test_data != NULL && test_data_wr_size > 0) { cts_info("Copy test data to user, size: %d", test_data_wr_size); if (copy_to_user(user_test_data, tests[i].test_data_buf, test_data_wr_size)) { cts_err("Copy test data to user failed"); test_data_wr_size = 0; // Skip this error } } if (user_test_data_wr_size != NULL) { put_user(test_data_wr_size, user_test_data_wr_size); } } if (driver_log_to_user) { driver_log_wr_size = cts_get_driver_log_redirect_size(); if (user_driver_log_buf != NULL && driver_log_wr_size > 0) { cts_info("Copy driver log to user, size: %d", driver_log_wr_size); if (copy_to_user(user_driver_log_buf, tests[i].driver_log_buf, driver_log_wr_size)) { cts_err("Copy driver log to user failed"); driver_log_wr_size = 0; // Skip this error } } if (user_driver_log_wr_size != NULL) { put_user(driver_log_wr_size, user_driver_log_wr_size); } } if (user_test_result != NULL) { put_user(ret, user_test_result); } else if (tests[i].test_result != NULL) { put_user(ret, tests[i].test_result); } /* * Free memory */ if (dump_test_data_to_user) { if (user_test_data != NULL && tests[i].test_data_buf != NULL) { vfree(tests[i].test_data_buf); } } if (validate_data) { if (validate_min && user_min_threshold != NULL && tests[i].min != NULL) { kfree(tests[i].min); } if (validate_max && user_max_threshold != NULL && tests[i].max != NULL) { kfree(tests[i].max); } if (skip_invalid_node) { if (user_invalid_nodes != NULL && tests[i].invalid_nodes != NULL) { kfree(tests[i].invalid_nodes); } } } if (dump_test_data_to_file && user_test_data_filepath != NULL && tests[i].test_data_filepath != NULL) { kfree(tests[i].test_data_filepath); } if (driver_log_to_user) { if (user_driver_log_buf != NULL && tests[i].driver_log_buf != NULL) { kfree(tests[i].driver_log_buf); } } if (driver_log_to_file) { if (user_driver_log_filepath != NULL && tests[i].driver_log_filepath != NULL) { kfree(tests[i].driver_log_filepath); } } if (driver_log_to_file || driver_log_to_user) { cts_stop_driver_log_redirect(); } if (user_priv_param && tests[i].priv_param) { kfree(tests[i].priv_param); } if (ret && stop_test_if_validate_fail) { break; } } kfree(tests); return ret; } static long cts_tool_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct chipone_ts_data *cts_data; struct cts_device *cts_dev; unsigned int modid; int ret; cts_info("ioctl, cmd=0x%016x, arg=0x%016lx", cmd, arg); cts_data = file->private_data; if (cts_data == NULL) { cts_err("IOCTL with private data = NULL"); return -EFAULT; } cts_dev = &cts_data->cts_dev; switch (cmd) { case CTS_TOOL_IOCTL_GET_DRIVER_VERSION: return put_user(CTS_DRIVER_VERSION_CODE, (unsigned int __user *)arg); case CTS_TOOL_IOCTL_GET_DEVICE_TYPE: return put_user(cts_dev->hwdata->hwid, (unsigned int __user *)arg); case CTS_TOOL_IOCTL_GET_FW_VERSION: return put_user(cts_dev->fwdata.version, (unsigned short __user *)arg); case CTS_TOOL_IOCTL_GET_ROW_COL:{ u16 rows_cols = (cts_dev->fwdata.rows << 8) | cts_dev->fwdata.cols; return put_user(rows_cols, (unsigned short __user *)arg); } case CTS_TOOL_IOCTL_GET_MODULE_ID: ret = cts_tcs_get_module_id(cts_dev, &modid); if (ret) { cts_err("Get module Id failed"); return -EFAULT; } return put_user(modid, (unsigned int __user *)arg); case CTS_TOOL_IOCTL_TEST:{ struct cts_test_ioctl_data test_arg; struct cts_test_param *tests_pa; if (copy_from_user(&test_arg, (struct cts_test_ioctl_data __user *)arg, sizeof(test_arg))) { cts_err("Copy ioctl test arg to kernel failed"); return -EFAULT; } if (test_arg.ntests > 8) { cts_err("ioctl test with too many tests %u", test_arg.ntests); return -EINVAL; } tests_pa = memdup_user(test_arg.tests, test_arg.ntests * sizeof(struct cts_test_param)); if (IS_ERR(tests_pa)) { int ret = PTR_ERR(tests_pa); cts_err("Memdump test param to kernel failed %d", ret); return ret; } return cts_ioctl_test(cts_dev, test_arg.ntests, tests_pa); } case CTS_TOOL_IOCTL_TCS_RW:{ return cts_ioctl_tcs_rw(cts_dev, file, cmd, arg); } default: break; } return -ENOTSUPP; } #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) static struct file_operations cts_tool_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .open = cts_tool_open, .read = cts_tool_read, .write = cts_tool_write, .unlocked_ioctl = cts_tool_ioctl, }; #else static struct proc_ops cts_tool_fops = { .proc_lseek = no_llseek, .proc_open = cts_tool_open, .proc_read = cts_tool_read, .proc_write = cts_tool_write, .proc_ioctl = cts_tool_ioctl, }; #endif int cts_tool_init(struct chipone_ts_data *cts_data) { cts_info("Init"); cts_data->procfs_entry = proc_create_data(CFG_CTS_TOOL_PROC_FILENAME, 0660, NULL, &cts_tool_fops, cts_data); if (cts_data->procfs_entry == NULL) { cts_err("Create proc entry failed"); return -EFAULT; } return 0; } void cts_tool_deinit(struct chipone_ts_data *data) { cts_info("Deinit"); if (data->procfs_entry) remove_proc_entry(CFG_CTS_TOOL_PROC_FILENAME, NULL); } #endif /* CONFIG_CTS_LEGACY_TOOL */