444 lines
11 KiB
C
444 lines
11 KiB
C
|
/* abc_spec_manager.c
|
||
|
*
|
||
|
* Abnormal Behavior Catcher's spec manager.
|
||
|
*
|
||
|
* Copyright 2021 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_spec_manager.h>
|
||
|
#if IS_ENABLED(CONFIG_SEC_KUNIT)
|
||
|
#include <linux/sti/abc_kunit.h>
|
||
|
#endif
|
||
|
struct list_head abc_spec_list;
|
||
|
EXPORT_SYMBOL_KUNIT(abc_spec_list);
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_OF)
|
||
|
int abc_parse_dt(struct device *dev)
|
||
|
{
|
||
|
struct abc_platform_data *pdata = dev->platform_data;
|
||
|
struct spec_data_type1 *spec_type1;
|
||
|
struct device_node *np;
|
||
|
struct device_node *type1_np;
|
||
|
int idx, rc;
|
||
|
|
||
|
np = dev->of_node;
|
||
|
pdata->nItem = of_get_child_count(np);
|
||
|
if (!pdata->nItem) {
|
||
|
dev_err(dev, "There are no items");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
/* spec_type_1 */
|
||
|
type1_np = of_find_node_by_name(np, "abc_spec_type1");
|
||
|
rc = of_property_count_strings(type1_np, ERROR_KEY);
|
||
|
INIT_LIST_HEAD(&abc_spec_list);
|
||
|
|
||
|
for (idx = 0; idx < rc; idx++) {
|
||
|
spec_type1 = devm_kzalloc(dev, sizeof(struct spec_data_type1), GFP_KERNEL);
|
||
|
|
||
|
if (!spec_type1)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
if (abc_parse_dt_type1(dev, type1_np, idx, spec_type1)) {
|
||
|
ABC_PRINT("failed parse dt spec_type1 idx : %d", idx);
|
||
|
continue;
|
||
|
}
|
||
|
list_add_tail(&spec_type1->node, &abc_spec_list);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
int sec_abc_get_normal_token_value(char *dst, char *src, char *token)
|
||
|
{
|
||
|
int token_len = strlen(token);
|
||
|
|
||
|
if (strncmp(src, token, token_len) || !*(src + token_len)) {
|
||
|
ABC_DEBUG("Invalid input : src-%s, token-%s", src, token);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
strlcpy(dst, src + token_len, ABC_EVENT_STR_MAX);
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_get_normal_token_value);
|
||
|
|
||
|
int sec_abc_get_event_module(char *dst, char *src)
|
||
|
{
|
||
|
return sec_abc_get_normal_token_value(dst, src, "MODULE=");
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_get_event_module);
|
||
|
|
||
|
int sec_abc_get_ext_log(char *dst, char *src)
|
||
|
{
|
||
|
return sec_abc_get_normal_token_value(dst, src, "EXT_LOG=");
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_get_ext_log);
|
||
|
|
||
|
int sec_abc_get_event_name(char *dst, char *src)
|
||
|
{
|
||
|
int ret_info = 0, ret_warn = 0;
|
||
|
|
||
|
ret_info = sec_abc_get_normal_token_value(dst, src, "INFO=");
|
||
|
ret_warn = sec_abc_get_normal_token_value(dst, src, "WARN=");
|
||
|
|
||
|
return (ret_info & ret_warn) ? -EINVAL : 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_get_event_name);
|
||
|
|
||
|
int sec_abc_get_event_type(char *dst, char *src)
|
||
|
{
|
||
|
if (strncmp(src, "WARN", 4) && strncmp(src, "INFO", 4)) {
|
||
|
ABC_PRINT("Invalid input : %s", src);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
strlcpy(dst, src, 5);
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_get_event_type);
|
||
|
|
||
|
int sec_abc_get_count(int *dst, char *src)
|
||
|
{
|
||
|
if (strncmp(src, "COUNT=", 6) || !*(src + 6)) {
|
||
|
ABC_PRINT("Invalid input : %s", src);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!strncmp(src + 6, "DEFAULT", 7)) {
|
||
|
*dst = ABC_DEFAULT_COUNT;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (kstrtoint(src + 6, 0, dst) || *dst <= 0 || *dst > ABC_EVENT_BUFFER_MAX) {
|
||
|
ABC_PRINT("Invalid input : %s", src);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_get_count);
|
||
|
|
||
|
unsigned int sec_abc_get_ktime_ms(void)
|
||
|
{
|
||
|
u64 ktime;
|
||
|
|
||
|
/* Calculate current kernel time */
|
||
|
ktime = local_clock();
|
||
|
do_div(ktime, NSEC_PER_MSEC);
|
||
|
|
||
|
return (unsigned int)ktime;
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_get_ktime_ms);
|
||
|
|
||
|
int sec_abc_make_key_data(struct abc_key_data *key_data, char *str)
|
||
|
{
|
||
|
char *event_strings[ABC_UEVENT_MAX] = {0,};
|
||
|
char temp[ABC_BUFFER_MAX];
|
||
|
char *c, *p;
|
||
|
int idx = 0, i;
|
||
|
|
||
|
ABC_DEBUG("start : %s", str);
|
||
|
|
||
|
strlcpy(temp, str, ABC_BUFFER_MAX);
|
||
|
p = temp;
|
||
|
|
||
|
while ((c = strsep(&p, "@")) != NULL && idx < ABC_UEVENT_MAX) {
|
||
|
event_strings[idx] = c;
|
||
|
idx++;
|
||
|
}
|
||
|
|
||
|
if (idx >= ABC_UEVENT_MAX)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (sec_abc_get_event_module(key_data->event_module, event_strings[0]))
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (sec_abc_get_event_name(key_data->event_name, event_strings[1]))
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (sec_abc_get_event_type(key_data->event_type, event_strings[1]))
|
||
|
return -EINVAL;
|
||
|
|
||
|
for (i = 2; i < idx; i++) {
|
||
|
if (!strncmp(event_strings[i], "EXT_LOG=", 8)) {
|
||
|
if (sec_abc_get_ext_log(key_data->ext_log, event_strings[i]))
|
||
|
return -EINVAL;
|
||
|
} else
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
key_data->cur_time = sec_abc_get_ktime_ms();
|
||
|
|
||
|
ABC_DEBUG("Module(%s) Level(%s) Event(%s) EXT_LOG(%s) cur_time(%d)",
|
||
|
key_data->event_module,
|
||
|
key_data->event_type,
|
||
|
key_data->event_name,
|
||
|
key_data->ext_log,
|
||
|
key_data->cur_time);
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_make_key_data);
|
||
|
|
||
|
struct abc_common_spec_data *sec_abc_get_matched_common_spec(char *module_name, char *error_name)
|
||
|
{
|
||
|
return sec_abc_get_matched_common_spec_type1(module_name, error_name);
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_get_matched_common_spec);
|
||
|
|
||
|
int sec_abc_get_buffer_size_from_threshold_cnt(int threshold_cnt)
|
||
|
{
|
||
|
int i, min_buffer_size = 16;
|
||
|
|
||
|
for (i = 1; i < threshold_cnt; i *= 2)
|
||
|
;
|
||
|
|
||
|
return min_buffer_size > i ? min_buffer_size : i;
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_get_buffer_size_from_threshold_cnt);
|
||
|
|
||
|
void sec_abc_enqueue_event_data(struct abc_key_data *key_data)
|
||
|
{
|
||
|
struct abc_common_spec_data *common_spec = NULL;
|
||
|
|
||
|
common_spec = sec_abc_get_matched_common_spec(key_data->event_module, key_data->event_name);
|
||
|
|
||
|
if (!common_spec || !strcmp(key_data->event_type, "INFO")) {
|
||
|
ABC_PRINT_KUNIT("There is no matched buffer");
|
||
|
} else {
|
||
|
ABC_DEBUG_KUNIT("There is a matched buffer. Enqueue data");
|
||
|
sec_abc_enqueue_event_data_type1(common_spec, key_data->cur_time);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_enqueue_event_data);
|
||
|
|
||
|
void sec_abc_reset_event_buffer(struct abc_key_data *key_data)
|
||
|
{
|
||
|
struct abc_common_spec_data *common_spec;
|
||
|
struct spec_data_type1 *spec_type1;
|
||
|
|
||
|
common_spec = sec_abc_get_matched_common_spec(key_data->event_module, key_data->event_name);
|
||
|
|
||
|
if (!common_spec || !strcmp(key_data->event_type, "INFO")) {
|
||
|
ABC_PRINT_KUNIT("There is no matched buffer");
|
||
|
} else {
|
||
|
ABC_PRINT_KUNIT("There is a matched buffer. Reset buffer");
|
||
|
spec_type1 = container_of(common_spec, struct spec_data_type1, common_spec);
|
||
|
sec_abc_reset_buffer_type1(spec_type1);
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_reset_event_buffer);
|
||
|
|
||
|
bool sec_abc_reached_spec(struct abc_key_data *key_data)
|
||
|
{
|
||
|
struct abc_common_spec_data *common_spec;
|
||
|
|
||
|
if (!strcmp(key_data->event_type, "INFO")) {
|
||
|
ABC_PRINT("INFO doesn't have spec");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
common_spec = sec_abc_get_matched_common_spec(key_data->event_module, key_data->event_name);
|
||
|
if (!common_spec)
|
||
|
return true;
|
||
|
|
||
|
return sec_abc_reached_spec_type1(common_spec, key_data->cur_time);
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_reached_spec);
|
||
|
|
||
|
int sec_abc_apply_changed_spec(char *str)
|
||
|
{
|
||
|
struct abc_common_spec_data *common_spec;
|
||
|
struct spec_data_type1 *spec_type1;
|
||
|
char *sys_input_strings[3] = {0,};
|
||
|
char module_name[ABC_EVENT_STR_MAX];
|
||
|
char error_name[ABC_EVENT_STR_MAX];
|
||
|
char temp[ABC_BUFFER_MAX];
|
||
|
char *c, *p;
|
||
|
int idx = 0, count = 0;
|
||
|
|
||
|
ABC_PRINT("start : %s", str);
|
||
|
|
||
|
strlcpy(temp, str, ABC_BUFFER_MAX);
|
||
|
p = temp;
|
||
|
|
||
|
while ((c = strsep(&p, "@")) != NULL && idx < ABC_SPEC_CMD_STR_MAX) {
|
||
|
sys_input_strings[idx] = c;
|
||
|
idx++;
|
||
|
}
|
||
|
|
||
|
if (idx != ABC_SPEC_CMD_STR_MAX)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (sec_abc_get_event_module(module_name, sys_input_strings[0]))
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (sec_abc_get_event_name(error_name, sys_input_strings[1]))
|
||
|
return -EINVAL;
|
||
|
|
||
|
idx = sec_abc_get_idx_of_registered_event(module_name, error_name);
|
||
|
|
||
|
if (idx < 0)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (!strcmp(sys_input_strings[2], "OFF")) {
|
||
|
abc_event_list[idx].enabled = false;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (sec_abc_get_count(&count, sys_input_strings[2]))
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (abc_event_list[idx].singular_spec) {
|
||
|
if (count == ABC_DEFAULT_COUNT) {
|
||
|
abc_event_list[idx].enabled = true;
|
||
|
return 0;
|
||
|
} else
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
common_spec = sec_abc_get_matched_common_spec(module_name, error_name);
|
||
|
|
||
|
if (!common_spec)
|
||
|
return -EINVAL;
|
||
|
|
||
|
abc_event_list[idx].enabled = false;
|
||
|
spec_type1 = container_of(common_spec, struct spec_data_type1, common_spec);
|
||
|
|
||
|
if (count == ABC_DEFAULT_COUNT)
|
||
|
count = spec_type1->default_count;
|
||
|
|
||
|
spec_type1->threshold_cnt = count;
|
||
|
spec_type1->buffer.size = count + 1;
|
||
|
|
||
|
if (abc_alloc_memory_to_buffer_type1(spec_type1, spec_type1->buffer.size))
|
||
|
return -ENOMEM;
|
||
|
|
||
|
sec_abc_reset_buffer_type1(spec_type1);
|
||
|
abc_event_list[idx].enabled = true;
|
||
|
|
||
|
ABC_PRINT("MODULE(%s) ERROR(%s) COUNT(%d) TIME(%d) Enabled(%d)",
|
||
|
common_spec->module_name,
|
||
|
common_spec->error_name,
|
||
|
spec_type1->threshold_cnt,
|
||
|
spec_type1->threshold_time,
|
||
|
abc_event_list[idx].enabled);
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_apply_changed_spec);
|
||
|
|
||
|
void sec_abc_change_spec(const char *str)
|
||
|
{
|
||
|
char *cmd_string, *p;
|
||
|
char temp[ABC_BUFFER_MAX * 5];
|
||
|
int cnt = 0;
|
||
|
|
||
|
ABC_PRINT("start : %s", str);
|
||
|
|
||
|
if (!strncmp(str, "reset", 5)) {
|
||
|
sec_abc_reset_all_spec();
|
||
|
sec_abc_enable_all_spec();
|
||
|
ABC_PRINT("end : %s", str);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
strlcpy(temp, str, ABC_BUFFER_MAX * 5);
|
||
|
p = temp;
|
||
|
|
||
|
while ((cmd_string = strsep(&p, ",\n")) != NULL && cnt < REGISTERED_ABC_EVENT_TOTAL) {
|
||
|
|
||
|
cnt++;
|
||
|
if (!cmd_string[0])
|
||
|
continue;
|
||
|
if (sec_abc_apply_changed_spec(cmd_string)) {
|
||
|
ABC_PRINT_KUNIT("Invalid change cmd. Check the Input");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void sec_abc_reset_all_buffer(void)
|
||
|
{
|
||
|
struct spec_data_type1 *spec_type1;
|
||
|
|
||
|
list_for_each_entry(spec_type1, &abc_spec_list, node) {
|
||
|
sec_abc_reset_buffer_type1(spec_type1);
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_reset_all_buffer);
|
||
|
|
||
|
void sec_abc_reset_all_spec(void)
|
||
|
{
|
||
|
struct spec_data_type1 *spec_type1;
|
||
|
|
||
|
list_for_each_entry(spec_type1, &abc_spec_list, node) {
|
||
|
spec_type1->threshold_cnt = spec_type1->default_count;
|
||
|
spec_type1->buffer.size = spec_type1->threshold_cnt + 1;
|
||
|
sec_abc_reset_buffer_type1(spec_type1);
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_reset_all_spec);
|
||
|
|
||
|
void sec_abc_enable_all_spec(void)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < REGISTERED_ABC_EVENT_TOTAL; i++)
|
||
|
abc_event_list[i].enabled = true;
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_enable_all_spec);
|
||
|
|
||
|
int sec_abc_read_spec(char *buf)
|
||
|
{
|
||
|
struct spec_data_type1 *spec_type1;
|
||
|
int len = 0, idx;
|
||
|
|
||
|
len += scnprintf(buf + len, PAGE_SIZE - len, "spec type1\n");
|
||
|
|
||
|
list_for_each_entry(spec_type1, &abc_spec_list, node) {
|
||
|
|
||
|
len += scnprintf(buf + len, PAGE_SIZE - len, "MODULE=%s ",
|
||
|
spec_type1->common_spec.module_name);
|
||
|
len += scnprintf(buf + len, PAGE_SIZE - len, "WARN=%s ",
|
||
|
spec_type1->common_spec.error_name);
|
||
|
len += scnprintf(buf + len, PAGE_SIZE - len, "THRESHOLD_CNT=%d ",
|
||
|
spec_type1->threshold_cnt);
|
||
|
len += scnprintf(buf + len, PAGE_SIZE - len, "THRESHOLD_TIME=%d ",
|
||
|
spec_type1->threshold_time);
|
||
|
idx = spec_type1->common_spec.idx;
|
||
|
len += scnprintf(buf + len, PAGE_SIZE - len, "ENABLE=%s",
|
||
|
((abc_event_list[idx].enabled) ? "ON" : "OFF"));
|
||
|
len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
|
||
|
}
|
||
|
|
||
|
ABC_PRINT("%d", len);
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
void sec_abc_free_spec_buffer(void)
|
||
|
{
|
||
|
struct spec_data_type1 *spec_buf;
|
||
|
|
||
|
list_for_each_entry(spec_buf, &abc_spec_list, node) {
|
||
|
kfree(spec_buf->buffer.abc_element);
|
||
|
spec_buf->buffer.abc_element = NULL;
|
||
|
spec_buf->buffer.buffer_max = 0;
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL_KUNIT(sec_abc_free_spec_buffer);
|
||
|
|
||
|
MODULE_DESCRIPTION("Samsung ABC Driver's spec manager");
|
||
|
MODULE_AUTHOR("Samsung Electronics");
|
||
|
MODULE_LICENSE("GPL");
|