1208 lines
41 KiB
C

// 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 */