kernel_samsung_a34x-permissive/drivers/sti/abc/abc_common.c

990 lines
28 KiB
C
Raw Permalink Normal View History

/* abc_common.c
*
* Abnormal Behavior Catcher Common Driver
*
* Copyright (C) 2017 Samsung Electronics
*
*
* 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.
*
*/
#include <linux/sti/abc_common.h>
#include <linux/sti/abc_spec_manager.h>
#if IS_ENABLED(CONFIG_SEC_KUNIT)
#include <linux/sti/abc_kunit.h>
#endif
#define DEBUG_ABC
#define WARNING_REPORT
struct abc_info *pinfo;
EXPORT_SYMBOL_KUNIT(pinfo);
struct list_head abc_pre_event_list;
EXPORT_SYMBOL_KUNIT(abc_pre_event_list);
int abc_pre_event_cnt;
EXPORT_SYMBOL_KUNIT(abc_pre_event_cnt);
bool abc_save_pre_event;
EXPORT_SYMBOL_KUNIT(abc_save_pre_event);
struct device *sec_abc;
EXPORT_SYMBOL_KUNIT(sec_abc);
int abc_enable_mode;
EXPORT_SYMBOL_KUNIT(abc_enable_mode);
int abc_init;
EXPORT_SYMBOL_KUNIT(abc_init);
int REGISTERED_ABC_EVENT_TOTAL;
EXPORT_SYMBOL_KUNIT(REGISTERED_ABC_EVENT_TOTAL);
#if IS_ENABLED(CONFIG_SEC_KUNIT)
char abc_common_kunit_test_work_str[ABC_TEST_UEVENT_MAX][ABC_TEST_STR_MAX] = {"", };
EXPORT_SYMBOL_KUNIT(abc_common_kunit_test_work_str);
char sec_abc_kunit_test_log_str[ABC_TEST_STR_MAX];
EXPORT_SYMBOL_KUNIT(sec_abc_kunit_test_log_str);
char abc_hub_kunit_test_uevent_str[ABC_HUB_TEST_STR_MAX];
EXPORT_SYMBOL_KUNIT(abc_hub_kunit_test_uevent_str);
#endif
/* "module_name", "error_name", "host", on, singular_spec, error_count */
struct registered_abc_event_struct abc_event_list[] = {
{"audio", "spk_amp", "it", true, true, 0, ABC_GROUP_NONE},
{"audio", "spk_amp_short", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "dc_i2c_fail", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "over_voltage", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "pd_input_ocp", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "safety_timer", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "pp_open", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "lim_stuck", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "lim_i2c_fail", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "vsys_ovp", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "dc_current", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "store_fg_asoc0", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "show_fg_asoc0", "it", true, true, 0, ABC_GROUP_NONE},
{"battery", "charger_irq_error", "", true, true, 0, ABC_GROUP_NONE},
{"bootc", "boot_time_fail", "", true, true, 0, ABC_GROUP_NONE},
{"camera", "camera_error", "", true, true, 0, ABC_GROUP_NONE},
{"camera", "i2c_fail", "", true, false, 0, ABC_GROUP_NONE},
{"camera", "icp_error", "", true, true, 0, ABC_GROUP_NONE},
{"camera", "ipp_overflow", "", true, true, 0, ABC_GROUP_NONE},
{"camera", "mipi_overflow", "", true, false, 0, ABC_GROUP_NONE},
#if IS_ENABLED(CONFIG_SEC_FACTORY)
{"camera", "mipi_error_rw1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_rs1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_rt1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_rt2", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_fw1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_uw1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_rm1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_rb1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_fs1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
#else
{"camera", "mipi_error_rw1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_rs1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_rt1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_rt2", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_fw1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_uw1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_rm1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_rb1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
{"camera", "mipi_error_fs1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL},
#endif
{"cond", "CAM_CONNECT", "", true, true, 0, ABC_GROUP_NONE},
{"cond", "LOWER_C2C_DETECT", "", true, true, 0, ABC_GROUP_NONE},
{"cond", "MAIN_BAT_DETECT", "", true, true, 0, ABC_GROUP_NONE},
{"cond", "MAIN_DIGITIZER_DETECT", "", true, true, 0, ABC_GROUP_NONE},
{"cond", "SUB_BAT_DETECT", "", true, true, 0, ABC_GROUP_NONE},
{"cond", "SUB_CONNECT", "", true, true, 0, ABC_GROUP_NONE},
{"cond", "SUB_LOWER_DETECT", "", true, true, 0, ABC_GROUP_NONE},
{"cond", "SUB_UB_DETECT", "", true, true, 0, ABC_GROUP_NONE},
{"cond", "TOF_CONNECT", "", true, true, 0, ABC_GROUP_NONE},
{"cond", "UPPER_C2C_DETECT", "", true, true, 0, ABC_GROUP_NONE},
{"decon", "fence_timeout", "", true, true, 0, ABC_GROUP_NONE},
{"display", "act_section_dsierr0", "", true, true, 0, ABC_GROUP_NONE},
{"display", "act_section_dsierr1", "", true, true, 0, ABC_GROUP_NONE},
{"display", "panel_main_no_te", "", true, true, 0, ABC_GROUP_NONE},
{"display", "panel_sub_no_te", "", true, true, 0, ABC_GROUP_NONE},
{"gpu", "gpu_fault", "", true, false, 0, ABC_GROUP_NONE},
{"gpu", "gpu_job_timeout", "", true, true, 0, ABC_GROUP_NONE},
{"gpu_qc", "gpu_fault", "", true, false, 0, ABC_GROUP_NONE},
{"gpu_qc", "gpu_page_fault", "", true, false, 0, ABC_GROUP_NONE},
{"mm", "venus_data_corrupt", "", true, true, 0, ABC_GROUP_NONE},
{"mm", "venus_hung", "", true, false, 0, ABC_GROUP_NONE},
{"muic", "afc_hv_fail", "", true, true, 0, ABC_GROUP_NONE},
{"muic", "cable_short", "", true, true, 0, ABC_GROUP_NONE},
{"muic", "qc_hv_fail", "", true, true, 0, ABC_GROUP_NONE},
{"npu", "npu_fw_warning", "", true, true, 0, ABC_GROUP_NONE},
{"pdic", "i2c_fail", "it", true, true, 0, ABC_GROUP_NONE},
{"pdic", "water_det", "", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_bulk_read", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_bulk_write", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_read_reg", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_read_word", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_update_reg", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_write_reg", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_bulk_read_fail", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_bulk_write_fail", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_read_reg_fail", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_read_word_fail", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_update_reg_fail", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_write_reg_fail", "it", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_scp", "", true, true, 0, ABC_GROUP_NONE},
{"pmic", "s2dos05_ssd", "", true, true, 0, ABC_GROUP_NONE},
{"storage", "mmc_hwreset_err", "", true, true, 0, ABC_GROUP_NONE},
{"storage", "sd_removed_err", "", true, true, 0, ABC_GROUP_NONE},
{"storage", "ufs_hwreset_err", "it", true, true, 0, ABC_GROUP_NONE},
{"storage", "ufs_medium_err", "it", true, true, 0, ABC_GROUP_NONE},
{"storage", "ufs_hardware_err", "it", true, true, 0, ABC_GROUP_NONE},
{"tsp", "tsp_int_fault", "", true, false, 0, ABC_GROUP_NONE},
{"tsp_sub", "tsp_int_fault", "", true, false, 0, ABC_GROUP_NONE},
{"ub_main", "ub_disconnected", "it", true, true, 0, ABC_GROUP_NONE},
{"ub_sub", "ub_disconnected", "it", true, true, 0, ABC_GROUP_NONE},
{"vib", "fw_load_fail", "it", true, true, 0, ABC_GROUP_NONE},
{"vib", "int_gnd_short", "", true, false, 0, ABC_GROUP_NONE},
{"wacom", "digitizer_not_connected", "", true, true, 0, ABC_GROUP_NONE},
#if IS_ENABLED(CONFIG_SEC_KUNIT)
{"kunit", "test_warn", "", true, true, 0, ABC_GROUP_NONE},
{"kunit", "test_info", "", true, true, 0, ABC_GROUP_NONE},
{"kunit", "test_error", "", true, true, 0, ABC_GROUP_NONE},
#endif
};
EXPORT_SYMBOL_KUNIT(abc_event_list);
struct abc_enable_cmd_struct enable_cmd_list[] = {
{ERROR_REPORT_MODE_ENABLE, ERROR_REPORT_MODE_BIT, "ERROR_REPORT=1"},
{ERROR_REPORT_MODE_DISABLE, ERROR_REPORT_MODE_BIT, "ERROR_REPORT=0"},
{ALL_REPORT_MODE_ENABLE, ALL_REPORT_MODE_BIT, "ALL_REPORT=1"},
{ALL_REPORT_MODE_DISABLE, ALL_REPORT_MODE_BIT, "ALL_REPORT=0"},
{PRE_EVENT_ENABLE, PRE_EVENT_ENABLE_BIT, "PRE_EVENT=1"},
{PRE_EVENT_DISABLE, PRE_EVENT_ENABLE_BIT, "PRE_EVENT=0"},
};
#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id sec_abc_dt_match[] = {
{ .compatible = "samsung,sec_abc" },
{ }
};
#endif
#if IS_ENABLED(CONFIG_SEC_KUNIT)
void abc_common_test_get_work_str(char *utest_event_str[])
{
int i;
for (i = 0; i < ABC_TEST_UEVENT_MAX; i++) {
if (utest_event_str[i]) {
if (i >= 2 && !strncmp(utest_event_str[i], TIME_KEYWORD, strlen(TIME_KEYWORD)))
strlcpy(abc_common_kunit_test_work_str[i],
TIME_KEYWORD, ABC_TEST_STR_MAX);
else
strlcpy(abc_common_kunit_test_work_str[i],
utest_event_str[i], ABC_TEST_STR_MAX);
}
}
}
EXPORT_SYMBOL_KUNIT(abc_common_test_get_work_str);
void abc_common_test_get_log_str(char *log_str)
{
strlcpy(sec_abc_kunit_test_log_str, log_str, sizeof(sec_abc_kunit_test_log_str));
}
EXPORT_SYMBOL_KUNIT(abc_common_test_get_log_str);
#endif
static int sec_abc_resume(struct device *dev)
{
return 0;
}
static int sec_abc_remove(struct platform_device *pdev)
{
return 0;
}
static const struct dev_pm_ops sec_abc_pm = {
.resume = sec_abc_resume,
};
int sec_abc_get_idx_of_registered_event(char *module_name, char *error_name)
{
int i;
for (i = 0; i < REGISTERED_ABC_EVENT_TOTAL; i++)
if (!strncmp(module_name, abc_event_list[i].module_name, ABC_EVENT_STR_MAX) &&
!strncmp(error_name, abc_event_list[i].error_name, ABC_EVENT_STR_MAX))
return i;
return -1;
}
EXPORT_SYMBOL_KUNIT(sec_abc_get_idx_of_registered_event);
#if !IS_ENABLED(CONFIG_SEC_FACTORY) && !IS_ENABLED(CONFIG_SEC_KUNIT)
static int sec_abc_get_error_count(char *module_name, char *error_name)
{
int i = sec_abc_get_idx_of_registered_event(module_name, error_name);
if (i >= 0)
return abc_event_list[i].error_count;
return 0;
}
static void sec_abc_update_error_count(char *module_name, char *error_name)
{
int i = sec_abc_get_idx_of_registered_event(module_name, error_name);
if (i >= 0)
abc_event_list[i].error_count++;
}
static void sec_abc_reset_error_count(void)
{
int i;
for (i = 0; i < REGISTERED_ABC_EVENT_TOTAL; i++)
abc_event_list[i].error_count = 0;
}
static bool sec_abc_is_skip_event(char *abc_str)
{
struct abc_key_data key_data;
int count;
if (sec_abc_make_key_data(&key_data, abc_str)) {
ABC_PRINT("Event string isn't valid. Check Input : %s", abc_str);
return true;
}
count = sec_abc_get_error_count(key_data.event_module, key_data.event_name);
if (count >= ABC_SKIP_EVENT_COUNT_THRESHOLD) {
if (count == ABC_SKIP_EVENT_COUNT_THRESHOLD) {
ABC_PRINT("[%s-%s] ABC Error already detected %d times! It is skipped from now on!",
key_data.event_module, key_data.event_name, ABC_SKIP_EVENT_COUNT_THRESHOLD);
sec_abc_update_error_count(key_data.event_module, key_data.event_name);
}
return true;
}
return false;
}
#endif
void sec_abc_send_uevent(struct abc_key_data *key_data, char *uevent_type)
{
char *uevent_str[ABC_UEVENT_MAX] = {0,};
char uevent_module_str[ABC_EVENT_STR_MAX + 7];
char uevent_event_str[ABC_EVENT_STR_MAX + ABC_TYPE_STR_MAX];
char uevent_host_str[ABC_EVENT_STR_MAX];
char uevent_ext_log_str[ABC_EVENT_STR_MAX];
char timestamp[TIME_STAMP_STR_MAX];
int idx;
if (!sec_abc_get_enabled()) {
ABC_PRINT("ABC isn't enabled. Save pre_event");
sec_abc_save_pre_events(key_data, uevent_type);
return;
}
snprintf(uevent_module_str, ABC_EVENT_STR_MAX, "MODULE=%s", key_data->event_module);
snprintf(uevent_event_str, ABC_EVENT_STR_MAX, "%s=%s", uevent_type, key_data->event_name);
snprintf(timestamp, TIME_STAMP_STR_MAX, "TIMESTAMP=%d", key_data->cur_time);
uevent_str[0] = uevent_module_str;
uevent_str[1] = uevent_event_str;
uevent_str[2] = timestamp;
if (abc_event_list[key_data->idx].host[0]) {
snprintf(uevent_host_str, ABC_EVENT_STR_MAX, "HOST=%s", abc_event_list[key_data->idx].host);
uevent_str[3] = uevent_host_str;
}
if (key_data->ext_log[0]) {
snprintf(uevent_ext_log_str, ABC_EVENT_STR_MAX, "EXT_LOG=%s", key_data->ext_log);
uevent_str[4] = uevent_ext_log_str;
}
for (idx = 0; uevent_str[idx]; idx++)
ABC_PRINT("%s", uevent_str[idx]);
#if IS_ENABLED(CONFIG_SEC_KUNIT)
abc_common_test_get_work_str(uevent_str);
complete(&pinfo->test_uevent_done);
#endif
#if !IS_ENABLED(CONFIG_SEC_FACTORY) && !IS_ENABLED(CONFIG_SEC_KUNIT)
sec_abc_update_error_count(key_data->event_module, key_data->event_name);
#endif
kobject_uevent_env(&sec_abc->kobj, KOBJ_CHANGE, uevent_str);
}
EXPORT_SYMBOL_KUNIT(sec_abc_send_uevent);
__visible_for_testing
struct abc_pre_event *sec_abc_get_pre_event_node(struct abc_key_data *key_data)
{
struct abc_pre_event *pre_event;
list_for_each_entry(pre_event, &abc_pre_event_list, node) {
if (!strcmp(pre_event->key_data.event_module, key_data->event_module) &&
!strcmp(pre_event->key_data.event_name, key_data->event_name)) {
ABC_PRINT("return matched node");
return pre_event;
}
}
pre_event = NULL;
if (abc_pre_event_cnt < ABC_PREOCCURRED_EVENT_MAX) {
pre_event = kzalloc(sizeof(*pre_event), GFP_KERNEL);
if (pre_event) {
list_add_tail(&pre_event->node, &abc_pre_event_list);
abc_pre_event_cnt++;
ABC_PRINT("return new node");
} else
ABC_PRINT("failed to get node");
}
return pre_event;
}
EXPORT_SYMBOL_KUNIT(sec_abc_get_pre_event_node);
__visible_for_testing
int sec_abc_clear_pre_events(void)
{
struct abc_pre_event *pre_event;
int cnt = 0;
ABC_PRINT("start");
mutex_lock(&pinfo->pre_event_mutex);
while (!list_empty(&abc_pre_event_list)) {
pre_event = list_first_entry(&abc_pre_event_list,
struct abc_pre_event,
node);
list_del(&pre_event->node);
kfree(pre_event);
cnt++;
}
abc_pre_event_cnt = 0;
/* Once Pre_events were cleared, don't save pre_event anymore. */
abc_save_pre_event = false;
abc_enable_mode &= ~(PRE_EVENT_ENABLE_BIT);
#if IS_ENABLED(CONFIG_SEC_KUNIT)
complete(&pinfo->test_work_done);
#endif
mutex_unlock(&pinfo->pre_event_mutex);
ABC_PRINT("end");
return cnt;
}
EXPORT_SYMBOL_KUNIT(sec_abc_clear_pre_events);
__visible_for_testing
int sec_abc_process_pre_events(void)
{
struct abc_pre_event *pre_event;
int i, cnt = 0;
ABC_PRINT("start");
mutex_lock(&pinfo->pre_event_mutex);
list_for_each_entry(pre_event, &abc_pre_event_list, node) {
if (abc_enable_mode & ALL_REPORT_MODE_BIT) {
ABC_PRINT("All report mode. Send uevent");
cnt += pre_event->all_cnt;
for (i = 0; i < pre_event->all_cnt; i++)
sec_abc_send_uevent(&pre_event->key_data, pre_event->key_data.event_type);
}
cnt += pre_event->error_cnt;
for (i = 0; i < pre_event->error_cnt; i++)
if (abc_event_list[pre_event->key_data.idx].enabled)
sec_abc_send_uevent(&pre_event->key_data, "ERROR");
}
mutex_unlock(&pinfo->pre_event_mutex);
ABC_PRINT("pre_event cnt : %d end", cnt);
return cnt;
}
EXPORT_SYMBOL_KUNIT(sec_abc_process_pre_events);
int sec_abc_save_pre_events(struct abc_key_data *key_data, char *uevent_type)
{
struct abc_pre_event *pre_event;
int ret = 0;
mutex_lock(&pinfo->pre_event_mutex);
ABC_PRINT("start Module(%s) Event(%s) Type(%s)",
key_data->event_module,
key_data->event_name,
uevent_type);
pre_event = sec_abc_get_pre_event_node(key_data);
if (!pre_event) {
ABC_PRINT_KUNIT("Failed to add Pre_event");
ret = -EINVAL;
goto out;
}
pre_event->key_data = *key_data;
if (!strncmp(uevent_type, "ERROR", 5))
pre_event->error_cnt++;
else
pre_event->all_cnt++;
out:
mutex_unlock(&pinfo->pre_event_mutex);
ABC_PRINT("end");
return ret;
}
EXPORT_SYMBOL_KUNIT(sec_abc_save_pre_events);
__visible_for_testing
void sec_abc_process_changed_enable_mode(void)
{
if (sec_abc_get_enabled()) {
if (abc_enable_mode & PRE_EVENT_ENABLE_BIT) {
sec_abc_process_pre_events();
ABC_PRINT_KUNIT("Pre_events processed");
} else {
ABC_PRINT_KUNIT("ABC enabled. Pre_event disabled");
}
complete(&pinfo->enable_done);
} else {
ABC_PRINT_KUNIT("ABC is disabled. Clear events");
sec_abc_reset_all_buffer();
#if !IS_ENABLED(CONFIG_SEC_FACTORY) && !IS_ENABLED(CONFIG_SEC_KUNIT)
sec_abc_reset_error_count();
#endif
}
ABC_PRINT("%d Pre_events cleared", sec_abc_clear_pre_events());
}
EXPORT_SYMBOL_KUNIT(sec_abc_process_changed_enable_mode);
/* Change ABC driver's enable mode.
*
* Interface with ACT : write "1" or "0"
* Interfcae with LABO
* ex) "ERROR_REPORT=1,PRE_EVENT=1", "ALL_REPORT=1,ERROR_REPORT=0,PRE_EVENT=0" ...
*
*/
__visible_for_testing
ssize_t enabled_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
char *c, *p, *enable_cmd[ABC_CMD_MAX];
char temp[ABC_CMD_STR_MAX * 3];
int items, i, j, idx = 0;
bool chk = false;
int origin_enable_mode = abc_enable_mode;
if (!strncmp(buf, "1", 1)) {
/* Interface with ACT */
ABC_PRINT("Error report mode enabled");
abc_enable_mode |= ERROR_REPORT_MODE_BIT;
} else if (!strncmp(buf, "0", 1)) {
ABC_PRINT("Error report mode disabled");
abc_enable_mode &= (~ERROR_REPORT_MODE_BIT);
} else {
strlcpy(temp, buf, ABC_CMD_STR_MAX * 3);
p = temp;
items = ARRAY_SIZE(enable_cmd_list);
while ((c = strsep(&p, ",")) != NULL && idx < ABC_CMD_MAX) {
enable_cmd[idx] = c;
idx++;
}
for (i = 0; i < idx; i++) {
chk = false;
for (j = 0; j < items; j++) {
if (!strncmp(enable_cmd[i],
enable_cmd_list[j].abc_cmd_str,
strlen(enable_cmd_list[j].abc_cmd_str))) {
if (strstr(enable_cmd_list[j].abc_cmd_str, "=1"))
abc_enable_mode |= enable_cmd_list[j].enable_value;
else
abc_enable_mode &= ~(enable_cmd_list[j].enable_value);
chk = true;
}
}
if (!chk) {
ABC_PRINT_KUNIT("Invalid string. Check the Input");
abc_enable_mode = origin_enable_mode;
return count;
}
}
}
sec_abc_process_changed_enable_mode();
return count;
}
EXPORT_SYMBOL_KUNIT(enabled_store);
__visible_for_testing
ssize_t enabled_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
if (sec_abc_get_enabled())
return sprintf(buf, "1\n");
else
return sprintf(buf, "0\n");
}
EXPORT_SYMBOL_KUNIT(enabled_show);
static DEVICE_ATTR_RW(enabled);
__visible_for_testing
ssize_t spec_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
mutex_lock(&pinfo->spec_mutex);
sec_abc_change_spec(buf);
mutex_unlock(&pinfo->spec_mutex);
return count;
}
EXPORT_SYMBOL_KUNIT(spec_store);
__visible_for_testing
ssize_t spec_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int count = 0;
mutex_lock(&pinfo->spec_mutex);
count = sec_abc_read_spec(buf);
mutex_unlock(&pinfo->spec_mutex);
return count;
}
EXPORT_SYMBOL_KUNIT(spec_show);
static DEVICE_ATTR_RW(spec);
__visible_for_testing
ssize_t features_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
return count;
}
__visible_for_testing
ssize_t features_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int count = 0;
count += scnprintf(buf, PAGE_SIZE, "spec_control\nhost\n");
return count;
}
EXPORT_SYMBOL_KUNIT(features_show);
static DEVICE_ATTR_RW(features);
static struct attribute *sec_abc_attr[] = {
&dev_attr_enabled.attr,
&dev_attr_spec.attr,
&dev_attr_features.attr,
NULL,
};
static struct attribute_group sec_abc_attr_group = {
.attrs = sec_abc_attr,
};
int sec_abc_get_enabled(void)
{
return (abc_enable_mode & (ERROR_REPORT_MODE_BIT | ALL_REPORT_MODE_BIT));
}
EXPORT_SYMBOL(sec_abc_get_enabled);
static void sec_abc_work_func_clear_pre_events(struct work_struct *work)
{
ABC_DEBUG("start");
sec_abc_clear_pre_events();
ABC_DEBUG("end");
}
#if IS_ENABLED(CONFIG_UML)
static void sec_abc_init_key_data(struct abc_key_data *key_data)
{
memset(key_data->event_module, 0, sizeof(key_data->event_module));
memset(key_data->event_name, 0, sizeof(key_data->event_name));
memset(key_data->event_type, 0, sizeof(key_data->event_type));
memset(key_data->ext_log, 0, sizeof(key_data->ext_log));
}
#endif
static void sec_abc_work_func(struct work_struct *work)
{
struct abc_event_work *event_work_data;
struct abc_key_data key_data;
int idx;
mutex_lock(&pinfo->work_mutex);
event_work_data = container_of(work, struct abc_event_work, work);
ABC_DEBUG("work start. str : %s", event_work_data->abc_str);
#if IS_ENABLED(CONFIG_UML)
sec_abc_init_key_data(&key_data);
#endif
if (sec_abc_make_key_data(&key_data, event_work_data->abc_str)) {
ABC_PRINT("Event string isn't valid. Check Input : %s", event_work_data->abc_str);
goto abc_work_end;
}
idx = sec_abc_get_idx_of_registered_event(key_data.event_module, key_data.event_name);
if (idx < 0) {
ABC_PRINT_KUNIT("%s : %s isn't registered", key_data.event_module, key_data.event_name);
goto abc_work_end;
}
key_data.idx = idx;
#if IS_ENABLED(CONFIG_SEC_ABC_MOTTO)
#if !IS_ENABLED(CONFIG_UML)
motto_send_device_info(key_data.event_module, key_data.event_name);
#endif
#endif
if (abc_enable_mode & (ALL_REPORT_MODE_BIT | PRE_EVENT_ENABLE_BIT)) {
ABC_PRINT("All report mode may be enabled. Send uevent");
sec_abc_send_uevent(&key_data, key_data.event_type);
}
if (!abc_event_list[idx].enabled || !strncmp(key_data.event_type, "INFO", 4)) {
ABC_PRINT_KUNIT("Don't send error report");
goto abc_work_end;
}
if (abc_event_list[idx].singular_spec) {
ABC_PRINT("Send uevent : %s", event_work_data->abc_str);
sec_abc_send_uevent(&key_data, "ERROR");
} else {
sec_abc_enqueue_event_data(&key_data);
if (sec_abc_reached_spec(&key_data)) {
ABC_PRINT("Send uevent : %s", event_work_data->abc_str);
sec_abc_send_uevent(&key_data, "ERROR");
sec_abc_reset_event_buffer(&key_data);
}
}
abc_work_end:
ABC_DEBUG("work done");
mutex_unlock(&pinfo->work_mutex);
#if IS_ENABLED(CONFIG_SEC_KUNIT)
complete(&pinfo->test_work_done);
#endif
}
/* event string format
*
* ex)
* MODULE=tsp@WARN=power_status_mismatch
* MODULE=gpu@INFO=gpu_fault
* MODULE=tsp@ERROR=power_status_mismatch@EXT_LOG=fw_ver(0108)
*
*/
__visible_for_testing
void sec_abc_enqueue_work(struct abc_event_work work_data[], char *str)
{
int idx;
for (idx = 0; idx < ABC_WORK_MAX; idx++) {
if (!work_pending(&work_data[idx].work)) {
ABC_DEBUG("Event %s use work[%d]", str, idx);
strlcpy(work_data[idx].abc_str, str, ABC_BUFFER_MAX);
queue_work(pinfo->workqueue, &work_data[idx].work);
return;
}
}
ABC_PRINT("Failed. All works are in queue");
}
void sec_abc_send_event(char *str)
{
#if !IS_ENABLED(CONFIG_SEC_FACTORY) && !IS_ENABLED(CONFIG_SEC_KUNIT)
if (sec_abc_is_skip_event(str))
return;
#endif
if (!abc_init) {
ABC_PRINT_KUNIT("ABC driver is not initialized!(%s)", str);
return;
}
if (!sec_abc_get_enabled() && !abc_save_pre_event) {
ABC_PRINT_KUNIT("ABC is disabled and pre_event is disabled.(%s)", str);
return;
}
ABC_PRINT("ABC is working. Queue work.(%s)", str);
sec_abc_enqueue_work(pinfo->event_work_data, str);
}
EXPORT_SYMBOL(sec_abc_send_event);
/**
* sec_abc_wait_enable() - wait for abc enable done
* Return : 0 for success, -1 for fail(timeout or abc not initialized)
*/
int sec_abc_wait_enabled(void)
{
unsigned long timeout;
if (!abc_init) {
ABC_PRINT_KUNIT("ABC driver is not initialized!");
return -1;
}
if (sec_abc_get_enabled())
return 0;
reinit_completion(&pinfo->enable_done);
timeout = wait_for_completion_timeout(&pinfo->enable_done,
msecs_to_jiffies(ABC_WAIT_ENABLE_TIMEOUT));
if (timeout == 0) {
ABC_PRINT_KUNIT("timeout!");
return -1;
}
return 0;
}
EXPORT_SYMBOL(sec_abc_wait_enabled);
__visible_for_testing
void sec_abc_init_work(struct abc_info *pinfo)
{
int idx;
pinfo->workqueue = create_singlethread_workqueue("sec_abc_wq");
INIT_DELAYED_WORK(&pinfo->clear_pre_events, sec_abc_work_func_clear_pre_events);
/* After timeout clear abc events occurred when abc disalbed. */
queue_delayed_work(pinfo->workqueue,
&pinfo->clear_pre_events,
msecs_to_jiffies(ABC_CLEAR_EVENT_TIMEOUT));
/* Work for abc_events & pre_events (events occurred before enabled) */
for (idx = 0; idx < ABC_WORK_MAX; idx++)
INIT_WORK(&pinfo->event_work_data[idx].work, sec_abc_work_func);
}
EXPORT_SYMBOL_KUNIT(sec_abc_init_work);
__visible_for_testing
int sec_abc_get_registered_abc_event_total(void)
{
return ARRAY_SIZE(abc_event_list);
}
EXPORT_SYMBOL_KUNIT(sec_abc_get_registered_abc_event_total);
static int sec_abc_probe(struct platform_device *pdev)
{
struct abc_platform_data *pdata;
int ret = 0;
ABC_PRINT("start");
abc_init = false;
REGISTERED_ABC_EVENT_TOTAL = sec_abc_get_registered_abc_event_total();
if (pdev->dev.of_node) {
pdata = devm_kzalloc(&pdev->dev,
sizeof(struct abc_platform_data), GFP_KERNEL);
if (!pdata) {
dev_err(&pdev->dev, "Failed to allocate platform data");
ret = -ENOMEM;
goto out;
}
pdev->dev.platform_data = pdata;
ret = abc_parse_dt(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Failed to parse dt data");
goto err_parse_dt;
}
ABC_PRINT("parse dt done");
} else {
pdata = pdev->dev.platform_data;
}
if (!pdata) {
dev_err(&pdev->dev, "There are no platform data");
ret = -EINVAL;
goto out;
}
pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL);
if (!pinfo) {
ret = -ENOMEM;
goto err_alloc_pinfo;
}
#if IS_ENABLED(CONFIG_DRV_SAMSUNG)
pinfo->dev = sec_device_create(pinfo, "sec_abc");
#else
pinfo->dev = device_create(sec_class, NULL, 0, NULL, "sec_abc");
#endif
if (IS_ERR(pinfo->dev)) {
pr_err("%s Failed to create device(sec_abc)!", __func__);
ret = -ENODEV;
goto err_create_device;
}
sec_abc = pinfo->dev;
ret = sysfs_create_group(&pinfo->dev->kobj, &sec_abc_attr_group);
if (ret) {
pr_err("%s: Failed to create device attribute group", __func__);
goto err_create_abc_attr_group;
}
INIT_LIST_HEAD(&abc_pre_event_list);
sec_abc_init_work(pinfo);
if (!pinfo->workqueue)
goto err_create_abc_wq;
#if IS_ENABLED(CONFIG_SEC_KUNIT)
init_completion(&pinfo->test_uevent_done);
init_completion(&pinfo->test_work_done);
#endif
init_completion(&pinfo->enable_done);
mutex_init(&pinfo->pre_event_mutex);
mutex_init(&pinfo->enable_mutex);
mutex_init(&pinfo->work_mutex);
mutex_init(&pinfo->spec_mutex);
pinfo->pdata = pdata;
platform_set_drvdata(pdev, pinfo);
#if IS_ENABLED(CONFIG_SEC_ABC_MOTTO)
motto_init(pdev);
#endif
abc_init = true;
abc_enable_mode |= PRE_EVENT_ENABLE_BIT;
abc_save_pre_event = true;
ABC_PRINT("success");
return ret;
err_create_abc_wq:
sysfs_remove_group(&pinfo->dev->kobj, &sec_abc_attr_group);
err_create_abc_attr_group:
#if IS_ENABLED(CONFIG_DRV_SAMSUNG)
sec_device_destroy(sec_abc->devt);
#else
device_destroy(sec_class, sec_abc->devt);
#endif
err_create_device:
kfree(pinfo);
err_alloc_pinfo:
err_parse_dt:
devm_kfree(&pdev->dev, pdata);
pdev->dev.platform_data = NULL;
out:
return ret;
}
void sec_abc_free_pre_events(void)
{
struct abc_pre_event *pre_event;
while (!list_empty(&abc_pre_event_list)) {
pre_event = list_first_entry(
&abc_pre_event_list,
struct abc_pre_event,
node);
list_del(&pre_event->node);
kfree(pre_event);
}
}
EXPORT_SYMBOL_KUNIT(sec_abc_free_pre_events);
__visible_for_testing
void sec_abc_free_allocated_memory(void)
{
sec_abc_free_pre_events();
sec_abc_free_spec_buffer();
}
EXPORT_SYMBOL_KUNIT(sec_abc_free_allocated_memory);
static struct platform_driver sec_abc_driver = {
.probe = sec_abc_probe,
.remove = sec_abc_remove,
.driver = {
.name = "sec_abc",
.owner = THIS_MODULE,
#if IS_ENABLED(CONFIG_PM)
.pm = &sec_abc_pm,
#endif
#if IS_ENABLED(CONFIG_OF)
.of_match_table = of_match_ptr(sec_abc_dt_match),
#endif
},
};
static int __init sec_abc_init(void)
{
ABC_PRINT("start");
return platform_driver_register(&sec_abc_driver);
}
static void __exit sec_abc_exit(void)
{
ABC_PRINT("exit");
sec_abc_free_allocated_memory();
return platform_driver_unregister(&sec_abc_driver);
}
module_init(sec_abc_init);
module_exit(sec_abc_exit);
MODULE_DESCRIPTION("Samsung ABC Driver");
MODULE_AUTHOR("Samsung Electronics");
MODULE_LICENSE("GPL");