[][openwrt][mt7988][tops][refactor tops-tool and add logger suuport]

[Description]
Refactor tops-tool and add logger support

add menu config for tops-tool
move common code into common part

save log cmd will open log relayfs and save
its data(log) as file in the filesystem

log relayfs path :
/sys/kernel/debug/tops/log-mgmt0
/sys/kernel/debug/tops/log-offload0

log file path :
<LOG_DIR>/log-mgmt-<time>
<LOG_DIR>/log-offload-<time>

[Release-log]
N/A

Change-Id: I1c563efbb584540eeb1b78c2a438c0173c4cdbba
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/7988095
diff --git a/feed/app/tops-tool/Config.in b/feed/app/tops-tool/Config.in
new file mode 100644
index 0000000..6733c1b
--- /dev/null
+++ b/feed/app/tops-tool/Config.in
@@ -0,0 +1,14 @@
+menu "TOPS Tool Configurations"
+	depends on PACKAGE_tops-tool
+
+config MTK_TOPS_TOOL_SAVE_LOG
+	bool "Save Log"
+	depends on MTK_TOPS_LOGGER
+	default y
+	help
+	  Set y to enable tops-tool logging feature to save
+	  TOPS firmware log to file system for further
+	  debugging purpose.
+	  Use command echo ON to start collecting log and
+	  echo OFF to stop collecting log.
+endmenu
diff --git a/feed/app/tops-tool/Makefile b/feed/app/tops-tool/Makefile
index 67f5995..b7387d7 100644
--- a/feed/app/tops-tool/Makefile
+++ b/feed/app/tops-tool/Makefile
@@ -12,6 +12,16 @@
 include $(INCLUDE_DIR)/package.mk
 include $(INCLUDE_DIR)/package-defaults.mk
 
+
+TOPS_TOOL_CONFIGS += \
+	CONFIG_MTK_TOPS_TOOL_SAVE_LOG=$(CONFIG_MTK_TOPS_TOOL_SAVE_LOG)
+
+MAKE_VARS += $(TOPS_TOOL_CONFIGS)
+
+EXTRA_CFLAGS += \
+	-I$(PKG_BUILD_DIR)/inc \
+	$(patsubst CONFIG_%=y, -DCONFIG_%=1, $(filter %=y,$(TOPS_TOOL_CONFIGS)))
+
 define Package/tops-tool
   TITLE:=Mediatek Tunnel Offload Processor System User Tool
   SECTION:=MTK Properties
@@ -28,14 +38,8 @@
   it to start. The tool will support logging functionality in the future.
 endef
 
-TARGET_CFLAGS += \
-	-I$(PKG_BUILD_DIR)/inc
-
-define Build/Compile
-	$(MAKE) -C $(PKG_BUILD_DIR) \
-		CC="$(TARGET_CC)" \
-		CFLAGS="$(TARGET_CFLAGS) -Wall -Wextra" \
-		LDFLAGS="$(TARGET_LDFLAGS)"
+define Package/tops-tool/config
+	source "$(SOURCE)/Config.in"
 endef
 
 define Package/tops-tool/install
@@ -57,9 +61,18 @@
   boot up.
 endef
 
+TOPS_TOOL_INIT_FILES := \
+	./files/tops-tool.init \
+	./files/tops-tool-dump.init
+ifeq ($(CONFIG_MTK_TOPS_TOOL_SAVE_LOG), y)
+TOPS_TOOL_INIT_FILES += \
+	./files/tops-tool-log.init
+endif
+
 define Package/tops-tool-autoload/install
 	$(INSTALL_DIR) $(1)/etc/init.d
-	$(INSTALL_BIN) ./files/tops-tool.init $(1)/etc/init.d/tops-tool
+	$(foreach file, $(TOPS_TOOL_INIT_FILES), \
+		$(INSTALL_BIN) $(file) $(1)/etc/init.d/$(notdir $(basename $(file)));)
 endef
 
 $(eval $(call BuildPackage,tops-tool))
diff --git a/feed/app/tops-tool/files/tops-tool-dump.init b/feed/app/tops-tool/files/tops-tool-dump.init
new file mode 100644
index 0000000..a0c6320
--- /dev/null
+++ b/feed/app/tops-tool/files/tops-tool-dump.init
@@ -0,0 +1,9 @@
+#!/bin/sh /etc/rc.common
+
+USE_PROCD=1
+PROG=/usr/sbin/tops-tool
+
+procd_open_instance
+procd_set_param command "${PROG}" save_dump /log/tops
+procd_set_param respawn
+procd_close_instance
diff --git a/feed/app/tops-tool/files/tops-tool-log.init b/feed/app/tops-tool/files/tops-tool-log.init
new file mode 100644
index 0000000..cdd0e84
--- /dev/null
+++ b/feed/app/tops-tool/files/tops-tool-log.init
@@ -0,0 +1,8 @@
+#!/bin/sh /etc/rc.common
+
+USE_PROCD=1
+PROG=/usr/sbin/tops-tool
+
+procd_open_instance
+procd_set_param command "${PROG}" save_log /log/tops
+procd_close_instance
diff --git a/feed/app/tops-tool/files/tops-tool.init b/feed/app/tops-tool/files/tops-tool.init
index 57ec788..f4da790 100644
--- a/feed/app/tops-tool/files/tops-tool.init
+++ b/feed/app/tops-tool/files/tops-tool.init
@@ -8,10 +8,11 @@
 PROG=/usr/sbin/tops-tool
 
 start_service() {
-	procd_open_instance
-	procd_set_param command "${PROG}" save_dump /log/tops
-	procd_set_param respawn
-	procd_close_instance
+	for script in /etc/init.d/tops-tool*; do
+		if [[ -f "$script" && -x "$script" ]]; then
+			source "$script"
+		fi
+	done
 }
 
 stop_service() {
diff --git a/feed/app/tops-tool/src/Makefile b/feed/app/tops-tool/src/Makefile
index b125bdb..3d03c51 100644
--- a/feed/app/tops-tool/src/Makefile
+++ b/feed/app/tops-tool/src/Makefile
@@ -5,15 +5,18 @@
 # Author: Alvin Kuo <Alvin.Kuo@mediatek.com>
 #
 
-PROJECT := tops-tool
-OBJECTS := tops-tool.o dump.o
+PROJECT:= tops-tool
+
+OBJECTS-$(CONFIG_MTK_TOPS_TOOL_SAVE_LOG)+= logger.o
+
+OBJECTS:= tops-tool.o common.o dump.o $(OBJECTS-y)
 
 all: $(PROJECT)
 
 $(PROJECT): $(OBJECTS) Makefile
 	$(CC) $(CFLAGS) $(LDFLAGS) $(OBJECTS) -o $@
 
-%.o: %.c %.h Makefile
+%.o: %.c inc/%.h Makefile
 	$(CC) $(CFLAGS) -c $< -o $@
 
 .PHONY : clean
diff --git a/feed/app/tops-tool/src/common.c b/feed/app/tops-tool/src/common.c
new file mode 100644
index 0000000..d88e59b
--- /dev/null
+++ b/feed/app/tops-tool/src/common.c
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier:	GPL-2.0+ */
+/*
+ * Copyright (C) 2022 MediaTek Incorporation. All Rights Reserved.
+ *
+ * Author: Alvin Kuo <Alvin.Kuo@mediatek.com>
+ */
+
+#include <limits.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <poll.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "common.h"
+
+int time_to_str(time_t *time_sec, char *time_str, unsigned int time_str_size)
+{
+	struct tm *ptm;
+	int ret;
+
+	ptm = gmtime(time_sec);
+	if (!ptm)
+		return -1;
+
+	ret = strftime(time_str, time_str_size, "%Y%m%d%H%M%S", ptm);
+	if (!ret)
+		return -2;
+
+	return 0;
+}
+
+int mkdir_p(const char *path, mode_t mode)
+{
+	size_t path_len;
+	char *cpy_path;
+	char *cur_path;
+	char *tmp_path;
+	char *dir;
+	int ret;
+
+	path_len = strlen(path) + 1;
+	if (path_len == 0)
+		return -EINVAL;
+
+	cpy_path = malloc(path_len);
+	if (!cpy_path)
+		return -ENOMEM;
+	strncpy(cpy_path, path, path_len);
+
+	cur_path = calloc(1, path_len);
+	if (!cur_path) {
+		ret = -ENOMEM;
+		goto free_cpy_path;
+	}
+
+	tmp_path = malloc(path_len);
+	if (!tmp_path) {
+		ret = -ENOMEM;
+		goto free_cur_path;
+	}
+
+	for (dir = strtok(cpy_path, "/");
+	     dir != NULL;
+	     dir = strtok(NULL, "/")) {
+		/* keep current path */
+		strncpy(tmp_path, cur_path, path_len);
+
+		/* append directory in current path */
+		ret = snprintf(cur_path, path_len, "%s/%s", tmp_path, dir);
+		if (ret < 0) {
+			fprintf(stderr,
+				LOG_FMT("append dir(%s) in cur_path(%s) fail(%d)\n"),
+				dir, cur_path, ret);
+			goto free_tmp_path;
+		}
+
+		ret = mkdir(cur_path, mode);
+		if (ret && errno != EEXIST) {
+			fprintf(stderr,
+				LOG_FMT("mkdir(%s) fail(%s)\n"),
+				cur_path, strerror(errno));
+			goto free_tmp_path;
+		}
+	}
+
+	ret = 0;
+
+free_tmp_path:
+	free(tmp_path);
+
+free_cur_path:
+	free(cur_path);
+
+free_cpy_path:
+	free(cpy_path);
+
+	return ret;
+}
diff --git a/feed/app/tops-tool/src/dump.c b/feed/app/tops-tool/src/dump.c
index e037eff..4169f5a 100644
--- a/feed/app/tops-tool/src/dump.c
+++ b/feed/app/tops-tool/src/dump.c
@@ -19,24 +19,9 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 
+#include "common.h"
 #include "dump.h"
 
-static int time_to_str(time_t *time_sec, char *time_str, unsigned int time_str_size)
-{
-	struct tm *ptm;
-	int ret;
-
-	ptm = gmtime(time_sec);
-	if (!ptm)
-		return -1;
-
-	ret = strftime(time_str, time_str_size, "%Y%m%d%H%M%S", ptm);
-	if (!ret)
-		return -2;
-
-	return 0;
-}
-
 static int save_dump_data(char *dump_root_dir,
 			  struct dump_data_header *dd_hdr,
 			  char *dd)
@@ -54,7 +39,7 @@
 			  dump_time_str, sizeof(dump_time_str));
 	if (ret < 0) {
 		fprintf(stderr,
-			DUMP_LOG_FMT("time_to_str(%lu) fail(%d)\n"),
+			LOG_FMT("time_to_str(%lu) fail(%d)\n"),
 			dd_hdr->info.dump_time_sec, ret);
 		return ret;
 	}
@@ -72,7 +57,7 @@
 	ret = mkdir(dump_dir, 0775);
 	if (ret && errno != EEXIST) {
 		fprintf(stderr,
-			DUMP_LOG_FMT("mkdir(%s) fail(%s)\n"),
+			LOG_FMT("mkdir(%s) fail(%s)\n"),
 			dump_dir, strerror(errno));
 		goto free_dump_dir;
 	}
@@ -94,7 +79,7 @@
 	fd = open(dump_file, O_WRONLY | O_CREAT, 0664);
 	if (fd < 0) {
 		fprintf(stderr,
-			DUMP_LOG_FMT("open(%s) fail(%s)\n"),
+			LOG_FMT("open(%s) fail(%s)\n"),
 			dump_file, strerror(errno));
 		ret = fd;
 		goto free_dump_file;
@@ -104,7 +89,7 @@
 	ret = lseek(fd, 0, SEEK_SET);
 	if (ret < 0) {
 		fprintf(stderr,
-			DUMP_LOG_FMT("lseek fail(%s)\n"),
+			LOG_FMT("lseek fail(%s)\n"),
 			strerror(errno));
 		goto close_dump_file;
 	}
@@ -115,7 +100,7 @@
 	ret = lseek(fd, dd_hdr->data_offset, SEEK_CUR);
 	if (ret < 0) {
 		fprintf(stderr,
-			DUMP_LOG_FMT("lseek fail(%s)\n"),
+			LOG_FMT("lseek fail(%s)\n"),
 			strerror(errno));
 		goto close_dump_file;
 	}
@@ -126,14 +111,14 @@
 		ret = stat(dump_file, &st);
 		if (ret < 0) {
 			fprintf(stderr,
-				DUMP_LOG_FMT("stat(%s) fail(%s)\n"),
+				LOG_FMT("stat(%s) fail(%s)\n"),
 				dump_file, strerror(errno));
 			goto close_dump_file;
 		}
 
 		if ((size_t)st.st_size != dump_file_size) {
 			fprintf(stderr,
-				DUMP_LOG_FMT("file(%s) size %zu != %zu\n"),
+				LOG_FMT("file(%s) size %zu != %zu\n"),
 				dump_file, st.st_size, dump_file_size);
 			ret = -EINVAL;
 			goto close_dump_file;
@@ -179,77 +164,9 @@
 	return out_len;
 }
 
-static int mkdir_p(char *path, mode_t mode)
+int tops_tool_save_dump_data(int argc, char *argv[])
 {
-	size_t path_len;
-	char *cpy_path;
-	char *cur_path;
-	char *tmp_path;
-	char *dir;
-	int ret;
-
-	path_len = strlen(path) + 1;
-	if (path_len == 0)
-		return -EINVAL;
-
-	cpy_path = malloc(path_len);
-	if (!cpy_path)
-		return -ENOMEM;
-	strncpy(cpy_path, path, path_len);
-
-	cur_path = calloc(1, path_len);
-	if (!cur_path) {
-		ret = -ENOMEM;
-		goto free_cpy_path;
-	}
-
-	tmp_path = malloc(path_len);
-	if (!tmp_path) {
-		ret = -ENOMEM;
-		goto free_cur_path;
-	}
-
-	for (dir = strtok(cpy_path, "/");
-	     dir != NULL;
-	     dir = strtok(NULL, "/")) {
-		/* keep current path */
-		strncpy(tmp_path, cur_path, path_len);
-
-		/* append directory in current path */
-		ret = snprintf(cur_path, path_len, "%s/%s", tmp_path, dir);
-		if (ret < 0) {
-			fprintf(stderr,
-				DUMP_LOG_FMT("append dir(%s) in cur_path(%s) fail(%d)\n"),
-				dir, cur_path, ret);
-			goto free_tmp_path;
-		}
-
-		ret = mkdir(cur_path, mode);
-		if (ret && errno != EEXIST) {
-			fprintf(stderr,
-				DUMP_LOG_FMT("mkdir(%s) fail(%s)\n"),
-				cur_path, strerror(errno));
-			goto free_tmp_path;
-		}
-	}
-
-	ret = 0;
-
-free_tmp_path:
-	free(tmp_path);
-
-free_cur_path:
-	free(cur_path);
-
-free_cpy_path:
-	free(cpy_path);
-
-	return ret;
-}
-
-int tops_save_dump_data(char *dump_root_dir)
-{
-	struct stat st = { 0 };
+	char *dump_root_dir = argv[2];
 	int ret = 0;
 	int fd;
 
@@ -259,7 +176,7 @@
 	/* reserve 256 bytes for saving name of dump directory and dump file */
 	if (strlen(dump_root_dir) > (PATH_MAX - 256)) {
 		fprintf(stderr,
-			DUMP_LOG_FMT("dump_root_dir(%s) length %zu > %u\n"),
+			LOG_FMT("dump_root_dir(%s) length %zu > %u\n"),
 			dump_root_dir, strlen(dump_root_dir), PATH_MAX - 256);
 		return -EINVAL;
 	}
@@ -267,7 +184,7 @@
 	ret = mkdir_p(dump_root_dir, 0775);
 	if (ret < 0) {
 		fprintf(stderr,
-			DUMP_LOG_FMT("mkdir_p(%s) fail(%d)\n"),
+			LOG_FMT("mkdir_p(%s) fail(%d)\n"),
 			dump_root_dir, ret);
 		return ret;
 	}
@@ -275,7 +192,7 @@
 	fd = open(DUMP_DATA_PATH, O_RDONLY);
 	if (fd < 0) {
 		fprintf(stderr,
-			DUMP_LOG_FMT("open(%s) fail(%s)\n"),
+			LOG_FMT("open(%s) fail(%s)\n"),
 			DUMP_DATA_PATH, strerror(errno));
 		return fd;
 	}
@@ -291,7 +208,7 @@
 		ret = poll(&pfd, 1, -1);
 		if (ret < 0) {
 			fprintf(stderr,
-				DUMP_LOG_FMT("poll fail(%s)\n"),
+				LOG_FMT("poll fail(%s)\n"),
 				strerror(errno));
 			break;
 		}
@@ -299,7 +216,7 @@
 		ret = read_retry(fd, &dd_hdr, sizeof(struct dump_data_header));
 		if (ret < 0) {
 			fprintf(stderr,
-				DUMP_LOG_FMT("read dd_hdr fail(%d)\n"), ret);
+				LOG_FMT("read dd_hdr fail(%d)\n"), ret);
 			break;
 		}
 
@@ -308,13 +225,13 @@
 
 		if (dd_hdr.data_len == 0) {
 			fprintf(stderr,
-				DUMP_LOG_FMT("read empty data\n"));
+				LOG_FMT("read empty data\n"));
 			continue;
 		}
 
 		if (dd_hdr.data_len > sizeof(dd)) {
 			fprintf(stderr,
-				DUMP_LOG_FMT("data length %u > %lu\n"),
+				LOG_FMT("data length %u > %lu\n"),
 				dd_hdr.data_len, sizeof(dd));
 			ret = -ENOMEM;
 			break;
@@ -323,13 +240,13 @@
 		ret = read_retry(fd, dd, dd_hdr.data_len);
 		if (ret < 0) {
 			fprintf(stderr,
-				DUMP_LOG_FMT("read dd fail(%d)\n"), ret);
+				LOG_FMT("read dd fail(%d)\n"), ret);
 			break;
 		}
 
 		if ((uint32_t)ret != dd_hdr.data_len) {
 			fprintf(stderr,
-				DUMP_LOG_FMT("read dd length %u != %u\n"),
+				LOG_FMT("read dd length %u != %u\n"),
 				(uint32_t)ret, dd_hdr.data_len);
 			ret = -EAGAIN;
 			break;
@@ -338,7 +255,7 @@
 		ret = save_dump_data(dump_root_dir, &dd_hdr, dd);
 		if (ret) {
 			fprintf(stderr,
-				DUMP_LOG_FMT("save_dump_data(%s) fail(%d)\n"),
+				LOG_FMT("save_dump_data(%s) fail(%d)\n"),
 				dump_root_dir, ret);
 			break;
 		}
diff --git a/feed/app/tops-tool/src/inc/common.h b/feed/app/tops-tool/src/inc/common.h
new file mode 100644
index 0000000..339b8e1
--- /dev/null
+++ b/feed/app/tops-tool/src/inc/common.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier:	GPL-2.0+ */
+/*
+ * Copyright (C) 2021 MediaTek Incorporation. All Rights Reserved.
+ *
+ * Author: Alvin Kuo <Alvin.Kuo@mediatek.com>
+ */
+
+#ifndef __COMMON_H__
+#define __COMMON_H__
+
+#include <sys/types.h>
+#include <time.h>
+
+#define LOG_FMT(FMT) "[TOPS_TOOL] [%s]: " FMT, __func__
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
+int time_to_str(time_t *time_sec, char *time_str, unsigned int time_str_size);
+int mkdir_p(const char *path, mode_t mode);
+
+#endif /* __COMMON_H__ */
diff --git a/feed/app/tops-tool/src/inc/dump.h b/feed/app/tops-tool/src/inc/dump.h
index 69b4340..4aa09ac 100644
--- a/feed/app/tops-tool/src/inc/dump.h
+++ b/feed/app/tops-tool/src/inc/dump.h
@@ -14,8 +14,6 @@
 #define RELAY_DUMP_SUBBUF_SIZE		2048
 #define DUMP_DATA_PATH			"/sys/kernel/debug/tops/trm/dump-data"
 
-#define DUMP_LOG_FMT(FMT) "[TOPS_TOOL] [%s]: " FMT, __func__
-
 struct dump_info {
 	char name[DUMP_INFO_NAME_MAX_LEN];
 	uint64_t dump_time_sec;
@@ -38,6 +36,6 @@
 	uint8_t last_frag;
 };
 
-int tops_save_dump_data(char *dump_dir);
+int tops_tool_save_dump_data(int argc, char *argv[]);
 
 #endif /* __DUMP_H__ */
diff --git a/feed/app/tops-tool/src/inc/logger.h b/feed/app/tops-tool/src/inc/logger.h
new file mode 100644
index 0000000..27a802a
--- /dev/null
+++ b/feed/app/tops-tool/src/inc/logger.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2023 MediaTek Incorporation. All Rights Reserved.
+ *
+ * Author: Alvin Kuo <Alvin.Kuo@mediatek.com>
+ */
+
+#ifndef __LOGGER_H__
+#define __LOGGER_H__
+
+#include <glob.h>
+
+#define LOG_MGMT_RELAYFS_PATH		"/sys/kernel/debug/tops/log-mgmt*"
+#define LOG_MGMT_NAME			"log-mgmt"
+
+#define LOG_OFFLOAD_RELAYFS_PATH	"/sys/kernel/debug/tops/log-offload*"
+#define LOG_OFFLOAD_NAME		"log-offload"
+
+#define LOGGER_DEBUGFS_PATH		"/sys/kernel/debug/tops/logger"
+
+#define BUFFER_LEN			(0x1000)
+
+enum log_num {
+	LOG_NUM_MGMT = 0,
+	LOG_NUM_OFFLOAD,
+
+	__LOG_NUM_MAX
+};
+#define LOG_NUM_MAX			__LOG_NUM_MAX
+
+struct logger_runtime_info {
+	const char *relayfs_path;
+	glob_t relayfs_glob;
+	int relayfs_fd;
+	const char *log_name;
+	char *log_file_path;
+};
+
+int tops_tool_run_logger(int argc, char *argv[]);
+
+#endif /* __LOGGER_H__ */
diff --git a/feed/app/tops-tool/src/inc/tops-tool-cmds.h b/feed/app/tops-tool/src/inc/tops-tool-cmds.h
new file mode 100644
index 0000000..bbcd48e
--- /dev/null
+++ b/feed/app/tops-tool/src/inc/tops-tool-cmds.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier:	GPL-2.0+ */
+/*
+ * Copyright (C) 2023 MediaTek Incorporation. All Rights Reserved.
+ *
+ * Author: Alvin Kuo <Alvin.Kuo@mediatek.com>
+ */
+#ifndef __TOPS_TOOL_CMDS_H__
+#define __TOPS_TOOL_CMDS_H__
+
+#include "dump.h"
+#include "logger.h"
+
+#define TOPS_TOOL_CMD_SAVE_DUMP						\
+	{								\
+		.name = "save_dump",					\
+		.usage = "save_dump [DUMP DIRECTORY PATH]",		\
+		.desc = "save dump data as file in dump directory",	\
+		.func = tops_tool_save_dump_data,			\
+		.num_of_parms = 1,					\
+	},
+
+#if defined(CONFIG_MTK_TOPS_TOOL_SAVE_LOG)
+#define TOPS_TOOL_CMD_SAVE_LOG						\
+	{								\
+		.name = "save_log",					\
+		.usage = "save_log [LOG DIRECTORY PATH]",		\
+		.desc = "save log as file in log directory",		\
+		.func = tops_tool_run_logger,				\
+		.num_of_parms = 1,					\
+	},
+#else
+#define TOPS_TOOL_CMD_SAVE_LOG
+#endif
+#endif /* __TOPS_TOOL_CMDS_H__ */
diff --git a/feed/app/tops-tool/src/inc/tops-tool.h b/feed/app/tops-tool/src/inc/tops-tool.h
new file mode 100644
index 0000000..f7a394a
--- /dev/null
+++ b/feed/app/tops-tool/src/inc/tops-tool.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier:	GPL-2.0+ */
+/*
+ * Copyright (C) 2023 MediaTek Incorporation. All Rights Reserved.
+ *
+ * Author: Alvin Kuo <Alvin.Kuo@mediatek.com>
+ */
+
+#ifndef __TOPS_TOOL_H__
+#define __TOPS_TOOL_H__
+
+#include "tops-tool-cmds.h"
+
+#define TOPS_TOOL_CMD(cmd_name)			TOPS_TOOL_CMD_ ## cmd_name
+
+typedef int (*tops_tool_cmd_func_t)(int argc, char *argv[]);
+
+struct tops_tool_cmd {
+	char *name;
+	char *usage;
+	char *desc;
+	tops_tool_cmd_func_t func;
+	uint8_t num_of_parms;
+};
+
+#endif /* __TOPS_TOOL_H__ */
diff --git a/feed/app/tops-tool/src/logger.c b/feed/app/tops-tool/src/logger.c
new file mode 100644
index 0000000..8ab75fd
--- /dev/null
+++ b/feed/app/tops-tool/src/logger.c
@@ -0,0 +1,340 @@
+/* SPDX-License-Identifier:	GPL-2.0+ */
+/*
+ * Copyright (C) 2023 MediaTek Incorporation. All Rights Reserved.
+ *
+ * Author: Alvin Kuo <Alvin.Kuo@mediatek.com>
+ */
+
+#include <stdbool.h>
+#include <signal.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <poll.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "common.h"
+#include "logger.h"
+
+static struct logger_runtime_info runtime_infos[LOG_NUM_MAX] = {
+	[LOG_NUM_MGMT] = {
+		.relayfs_path = LOG_MGMT_RELAYFS_PATH,
+		.log_name = LOG_MGMT_NAME,
+	},
+	[LOG_NUM_OFFLOAD] = {
+		.relayfs_path = LOG_OFFLOAD_RELAYFS_PATH,
+		.log_name = LOG_OFFLOAD_NAME,
+	},
+};
+
+static bool sig_rcv;
+
+static void __tops_logger_runtime_info_deinit(int log_num)
+{
+	struct logger_runtime_info *ri;
+
+	for (; log_num >= 0; log_num--) {
+		ri = &runtime_infos[log_num];
+
+		free(ri->log_file_path);
+		close(ri->relayfs_fd);
+		globfree(&ri->relayfs_glob);
+	}
+}
+
+static void tops_logger_runtime_info_deinit(void)
+{
+	__tops_logger_runtime_info_deinit(LOG_NUM_MAX - 1);
+}
+
+static int __tops_logger_runtime_info_init(struct logger_runtime_info *ri,
+				   const char *log_file_dir,
+				   char *log_time_str)
+{
+	int ret;
+
+	ret = glob(ri->relayfs_path, 0, NULL, &ri->relayfs_glob);
+	if (ret != 0) {
+		fprintf(stderr,
+			LOG_FMT("glob(%s) fail(%d)\n"),
+			ri->relayfs_path, ret);
+		goto out;
+	} else if (ri->relayfs_glob.gl_pathc > 1) {
+		fprintf(stderr,
+			LOG_FMT("glob(%s) match %lu paths\n"),
+			ri->relayfs_path, ri->relayfs_glob.gl_pathc);
+		ret = -EINVAL;
+		goto free_glob;
+	}
+
+	ri->relayfs_fd = open(ri->relayfs_glob.gl_pathv[0], O_RDONLY);
+	if (ri->relayfs_fd < 0) {
+		fprintf(stderr,
+			LOG_FMT("open(%s) fail(%s)\n"),
+			ri->relayfs_glob.gl_pathv[0], strerror(errno));
+		ret = ri->relayfs_fd;
+		goto free_glob;
+	}
+
+	ri->log_file_path = malloc(strlen(log_file_dir) + 1 +
+				   strlen(ri->log_name) + 1 +
+				   strlen(log_time_str) + 1);
+	if (!ri->log_file_path) {
+		ret = -ENOMEM;
+		goto close_fd;
+	}
+
+	sprintf(ri->log_file_path, "%s/%s-%s", log_file_dir, ri->log_name, log_time_str);
+
+out:
+	return ret;
+
+close_fd:
+	close(ri->relayfs_fd);
+
+free_glob:
+	globfree(&ri->relayfs_glob);
+
+	return ret;
+}
+
+static int tops_logger_runtime_info_init(const char *log_file_dir)
+{
+	char log_time_str[32];
+	time_t log_time;
+	int log_num;
+	int ret;
+
+	time(&log_time);
+	ret = time_to_str(&log_time, log_time_str, sizeof(log_time_str));
+	if (ret < 0) {
+		fprintf(stderr,
+			LOG_FMT("time_to_str(%lu) fail(%d)\n"),
+			log_time, ret);
+		goto out;
+	}
+
+	for (log_num = 0; log_num < LOG_NUM_MAX; log_num++) {
+		ret = __tops_logger_runtime_info_init(
+			&runtime_infos[log_num],
+			log_file_dir,
+			log_time_str);
+		if (ret)
+			goto deinit_logger_runtime_info;
+	}
+
+out:
+	return ret;
+
+deinit_logger_runtime_info:
+	__tops_logger_runtime_info_deinit(log_num - 1);
+
+	return ret;
+}
+
+static int tops_logger_log_save(char *file, char *data, uint32_t data_len)
+{
+	int ret = 0;
+	int fd;
+
+	fd = open(file, O_RDWR | O_APPEND | O_CREAT, 0664);
+	if (fd < 0) {
+		fprintf(stderr,
+			LOG_FMT("open(%s) fail(%s)\n"),
+			file, strerror(errno));
+		ret = -1;
+		goto out;
+	}
+
+	write(fd, data, data_len);
+	close(fd);
+
+out:
+	return ret;
+}
+
+static int tops_logger_log_proc(struct pollfd *pfds)
+{
+	struct logger_runtime_info *ri;
+	char buffer[BUFFER_LEN];
+	int log_num;
+	int ret = 0;
+
+	for (log_num = 0; log_num < LOG_NUM_MAX; log_num++) {
+		ri = &runtime_infos[log_num];
+		if (pfds[log_num].revents & (POLLIN | POLLHUP | POLLERR)) {
+			ret = read(pfds[log_num].fd, buffer, BUFFER_LEN);
+			if (ret < 0) {
+				fprintf(stderr,
+					LOG_FMT("read log from %s failed(%d)\n"),
+					ri->relayfs_glob.gl_pathv[0], ret);
+
+				if (errno == EINTR || errno == EAGAIN)
+					continue;
+
+				break;
+			} else if (ret == 0) {
+				continue;
+			}
+
+			ret = tops_logger_log_save(ri->log_file_path,
+						   buffer, ret);
+			if (ret) {
+				fprintf(stderr,
+					LOG_FMT("save log to %s failed(%d)\n"),
+					ri->log_file_path, ret);
+				break;
+			}
+		}
+	}
+
+	return ret;
+}
+
+static void signal_handler(int sig)
+{
+	(void)sig;
+
+	fprintf(stderr,
+		LOG_FMT("killall or Ctrl+C received, stop saving log\n"));
+	sig_rcv = true;
+}
+
+static int tops_logger_is_running(bool *running)
+{
+	char result[4];
+	int ret = 0;
+	ssize_t n;
+	int fd;
+
+	fd = open(LOGGER_DEBUGFS_PATH, O_RDONLY);
+	if (fd < 0) {
+		fprintf(stderr,
+			LOG_FMT("open(%s) fail(%s)\n"),
+			LOGGER_DEBUGFS_PATH, strerror(errno));
+		ret = errno;
+		goto out;
+	}
+
+	n = read(fd, result, sizeof(result) - 1);
+	if (n == -1) {
+		fprintf(stderr,
+			LOG_FMT("read(%s) fail(%s)\n"),
+			LOGGER_DEBUGFS_PATH, strerror(errno));
+		ret = errno;
+		goto close_file;
+	}
+
+	result[n] = '\0';
+
+	if (strcmp(result, "ON") == 0) {
+		fprintf(stderr, LOG_FMT("logger is running\n"));
+		*running = true;
+	} else {
+		*running = false;
+	}
+
+close_file:
+	close(fd);
+
+out:
+	return ret;
+}
+
+int tops_tool_run_logger(int argc, char *argv[])
+{
+	const char *log_file_dir = argv[2];
+	bool running = false;
+	struct stat st;
+	int ret;
+
+	if (!log_file_dir) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* reserve 256 bytes for log file name */
+	if (strlen(log_file_dir) > (PATH_MAX - 256)) {
+		fprintf(stderr,
+			LOG_FMT("log_file_dir(%s) length %zu > %u\n"),
+			log_file_dir, strlen(log_file_dir), PATH_MAX - 256);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = tops_logger_is_running(&running);
+	if (ret || running) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (stat(log_file_dir, &st)) {
+		if (errno == ENOENT) {
+			ret = mkdir_p(log_file_dir, 0775);
+			if (ret) {
+				fprintf(stderr,
+					LOG_FMT("mkdir_p(%s) failed(%d)\n"),
+					log_file_dir, ret);
+				goto out;
+			}
+		} else {
+			fprintf(stderr,
+				LOG_FMT("stat(%s) failed(%s)\n"),
+				log_file_dir, strerror(errno));
+			ret = -EINVAL;
+			goto out;
+		}
+	}
+
+	signal(SIGTERM, signal_handler);
+	signal(SIGINT, signal_handler);
+	signal(SIGQUIT, signal_handler);
+
+	ret = tops_logger_runtime_info_init(log_file_dir);
+	if (ret)
+		goto out;
+
+	system("echo ON > " LOGGER_DEBUGFS_PATH);
+
+	while (1) {
+		struct pollfd pfds[LOG_NUM_MAX] = {
+			[LOG_NUM_MGMT] = {
+				.fd = runtime_infos[LOG_NUM_MGMT].relayfs_fd,
+				.events = POLLIN | POLLHUP | POLLERR,
+				.revents = 0,
+			},
+			[LOG_NUM_OFFLOAD] = {
+				.fd = runtime_infos[LOG_NUM_OFFLOAD].relayfs_fd,
+				.events = POLLIN | POLLHUP | POLLERR,
+				.revents = 0,
+			},
+		};
+
+		if (sig_rcv)
+			break;
+
+		poll(pfds, LOG_NUM_MAX, -1);
+
+		if (sig_rcv)
+			break;
+
+		ret = tops_logger_log_proc(pfds);
+		if (ret)
+			break;
+	}
+
+	system("echo OFF > " LOGGER_DEBUGFS_PATH);
+
+	tops_logger_runtime_info_deinit();
+
+out:
+	return ret;
+}
diff --git a/feed/app/tops-tool/src/tops-tool.c b/feed/app/tops-tool/src/tops-tool.c
index 32ab579..8dd16e9 100644
--- a/feed/app/tops-tool/src/tops-tool.c
+++ b/feed/app/tops-tool/src/tops-tool.c
@@ -9,60 +9,73 @@
 #include <stdio.h>
 #include <errno.h>
 
-#include "dump.h"
+#include "common.h"
+#include "tops-tool.h"
+
+static struct tops_tool_cmd cmds[] = {
+	TOPS_TOOL_CMD(SAVE_DUMP)
+	TOPS_TOOL_CMD(SAVE_LOG)
+};
 
 static void print_usage(void)
 {
+	unsigned int idx;
+
 	printf("Usage:\n");
-	printf(" tops-tool [CMD] [DUMP_DIR]\n");
-	printf(" [CMD] are:\n");
-	printf("    save_dump   save dump data as file in directory [DUMP_DIR]\n");
-	printf(" [DUMP_DIR] is directory of dump file\n");
+	printf("tops-tool [CMD]\n");
+	printf("[CMD] are:\n");
+	for (idx = 0; idx < ARRAY_SIZE(cmds); idx++)
+		printf("\t%s\n\t%s\n\n", cmds[idx].usage, cmds[idx].desc);
 }
 
-static int verify_parameters(int argc,
-			     char *argv[])
+static int verify_parameters(int argc, char *argv[], unsigned int *cmd_idx)
 {
+	unsigned int idx;
 	char *cmd;
 
 	if (argc < 2) {
-		fprintf(stderr, DUMP_LOG_FMT("missing cmd\n"));
+		fprintf(stderr, LOG_FMT("CMD missing\n"));
 		return -EINVAL;
 	}
 
 	cmd = argv[1];
-	if (!strncmp(cmd, "save_dump", 9)) {
-		if (argc < 3) {
-			fprintf(stderr, DUMP_LOG_FMT("too few parameters\n"));
-			return -EINVAL;
+	for (idx = 0; idx < ARRAY_SIZE(cmds); idx++) {
+		if (!strncmp(cmds[idx].name, cmd, strlen(cmds[idx].name))) {
+			if (argc - 2 < cmds[idx].num_of_parms) {
+				fprintf(stderr,
+					LOG_FMT("CMD(%s) needs %d parameter(s)\n"),
+					cmds[idx].name,
+					cmds[idx].num_of_parms);
+				return -EINVAL;
+			}
+
+			*cmd_idx = idx;
+
+			return 0;
 		}
 	}
 
-	return 0;
+	fprintf(stderr, LOG_FMT("CMD(%s) not support\n"), cmd);
+
+	return -EINVAL;
 }
 
 int main(int argc, char *argv[])
 {
+	unsigned int cmd_idx;
 	int ret = 0;
-	char *cmd;
 
-	ret = verify_parameters(argc, argv);
+	ret = verify_parameters(argc, argv, &cmd_idx);
 	if (ret) {
 		print_usage();
 		goto error;
 	}
 
-	cmd = argv[1];
-	if (!strncmp(cmd, "save_dump", 9)) {
-		ret = tops_save_dump_data(argv[2]);
-		if (ret) {
-			fprintf(stderr,
-				DUMP_LOG_FMT("cmd %s: save dump data fail(%d)\n"),
-					     cmd, ret);
-			goto error;
-		}
-	} else {
-		fprintf(stderr, DUMP_LOG_FMT("unsupported cmd %s\n"), cmd);
+	ret = cmds[cmd_idx].func(argc, argv);
+	if (ret) {
+		fprintf(stderr,
+			LOG_FMT("CMD(%s) execution failed(%d)\n"),
+			cmds[cmd_idx].name, ret);
 		goto error;
 	}