[][openwrt][mt7988][tops][TOPS Alpha release]

[Description]
Add alpha version of TOPS(tunnel offload processor system) and tops-tool
package.

TOPS package supports tunnel protocol HW offload. The support offload
tunnel protocols for Alpha version are L2oGRE and L2TPv2.
Notice that, TOPS only guarantees that inner packets are TCP. It is still
unstable for UDP inner packet flow.

tops-tool package provides several debug features such as logger, coredump
for TOPS.

[Release-log]
N/A

Change-Id: Iab6e4a89bebbe42c967f28e0c9e9c0611673f354
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/7852683
diff --git a/feed/tops-tool/Makefile b/feed/tops-tool/Makefile
new file mode 100644
index 0000000..621c4cd
--- /dev/null
+++ b/feed/tops-tool/Makefile
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2023 Mediatek Inc. All Rights Reserved.
+# Author: Alvin Kuo <alvin.kuo@mediatek.com>
+#
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=tops-tool
+PKG_RELEASE:=1
+
+include $(INCLUDE_DIR)/package.mk
+include $(INCLUDE_DIR)/package-defaults.mk
+
+define Package/tops-tool
+  TITLE:=Mediatek Tunnel Offload Processor System User Tool
+  SECTION:=MTK Properties
+  CATEGORY:=MTK Properties
+  DEFAULT:=y
+  SUBMENU:=Applications
+  DEPENDS:=kmod-tops
+endef
+
+define Package/tops-tool/description
+  Mediatek Tunnel Offload Processor System User Tool
+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)"
+endef
+
+define Package/tops-tool/install
+	$(INSTALL_DIR) $(1)/usr/sbin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/tops-tool $(1)/usr/sbin/
+
+	$(INSTALL_DIR) $(1)/etc/init.d
+	$(INSTALL_BIN) ./files/tops-tool.init $(1)/etc/init.d/tops-tool
+endef
+
+$(eval $(call BuildPackage,tops-tool))
diff --git a/feed/tops-tool/files/tops-tool.init b/feed/tops-tool/files/tops-tool.init
new file mode 100644
index 0000000..6c4e612
--- /dev/null
+++ b/feed/tops-tool/files/tops-tool.init
@@ -0,0 +1,19 @@
+#!/bin/sh /etc/rc.common
+
+#the priority of TOPS driver is 41
+START=42
+
+USE_PROCD=1
+NAME=tops-tool
+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
+}
+
+stop_service() {
+	service_stop "${PROG}"
+}
diff --git a/feed/tops-tool/src/Makefile b/feed/tops-tool/src/Makefile
new file mode 100644
index 0000000..b125bdb
--- /dev/null
+++ b/feed/tops-tool/src/Makefile
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-or-later */
+#
+# Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
+#
+# Author: Alvin Kuo <Alvin.Kuo@mediatek.com>
+#
+
+PROJECT := tops-tool
+OBJECTS := tops-tool.o dump.o
+
+all: $(PROJECT)
+
+$(PROJECT): $(OBJECTS) Makefile
+	$(CC) $(CFLAGS) $(LDFLAGS) $(OBJECTS) -o $@
+
+%.o: %.c %.h Makefile
+	$(CC) $(CFLAGS) -c $< -o $@
+
+.PHONY : clean
+clean:
+	rm -f $(PROJECT) *.o
diff --git a/feed/tops-tool/src/dump.c b/feed/tops-tool/src/dump.c
new file mode 100644
index 0000000..d3a8bf1
--- /dev/null
+++ b/feed/tops-tool/src/dump.c
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 MediaTek Inc. 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 "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)
+{
+	size_t dump_file_size = dd_hdr->info.size + sizeof(struct dump_info);
+	char dump_time_str[32];
+	struct stat st = { 0 };
+	char *dump_file = NULL;
+	char *dump_dir = NULL;
+	int ret = 0;
+	int fd;
+
+	ret = time_to_str((time_t *)&dd_hdr->info.dump_time_sec,
+			  dump_time_str, sizeof(dump_time_str));
+	if (ret < 0) {
+		fprintf(stderr,
+			DUMP_LOG_FMT("time_to_str(%lu) fail(%d)\n"),
+			dd_hdr->info.dump_time_sec, ret);
+		ret = -1;
+		goto out;
+	}
+
+	dump_dir = malloc(strlen(dump_root_dir) + 1 +
+			  strlen(dump_time_str) + 1);
+	if (!dump_dir) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	sprintf(dump_dir, "%s/%s", dump_root_dir, dump_time_str);
+
+	dump_file = malloc(strlen(dump_dir) + 1 +
+			   strlen(dd_hdr->info.name) + 1);
+	if (!dump_file) {
+		ret = -ENOMEM;
+		goto free_dump_dir;
+	}
+	sprintf(dump_file, "%s/%s", dump_dir, dd_hdr->info.name);
+
+	if (stat(dump_dir, &st)) {
+		ret = mkdir(dump_dir, 0775);
+		if (ret) {
+			fprintf(stderr,
+				DUMP_LOG_FMT("mkdir(%s) fail(%s)\n"),
+				dump_dir, strerror(errno));
+			ret = -1;
+			goto free_dump_file;
+		}
+
+		/* TODO: only keep latest three dump directories */
+	}
+
+	fd = open(dump_file, 0664);
+	if (fd < 0) {
+		fprintf(stderr,
+			DUMP_LOG_FMT("open(%s) fail(%s)\n"),
+			dump_file, strerror(errno));
+		ret = -1;
+		goto free_dump_file;
+	}
+
+	/* write information of dump at begining of the file */
+	lseek(fd, 0, SEEK_SET);
+	write(fd, &dd_hdr->info, sizeof(struct dump_info));
+
+	/* write data of dump start from data offset of the file */
+	lseek(fd, dd_hdr->data_offset, SEEK_CUR);
+	write(fd, dd, dd_hdr->data_len);
+
+	close(fd);
+
+	if (dd_hdr->last_frag) {
+		stat(dump_file, &st);
+		if ((size_t)st.st_size != dump_file_size) {
+			fprintf(stderr,
+				DUMP_LOG_FMT("file(%s) size %zu != %zu\n"),
+				dump_file, st.st_size, dump_file_size);
+			ret = -1;
+			goto free_dump_file;
+		}
+	}
+
+free_dump_file:
+	free(dump_file);
+	dump_file = NULL;
+
+free_dump_dir:
+	free(dump_dir);
+	dump_dir = NULL;
+
+out:
+	return ret;
+}
+
+static int read_retry(int fd, void *buf, int len)
+{
+	int out_len = 0;
+	int ret;
+
+	while (len > 0) {
+		ret = read(fd, buf, len);
+		if (ret < 0) {
+			if (errno == EINTR || errno == EAGAIN)
+				continue;
+
+			return -1;
+		}
+
+		if (!ret)
+			return 0;
+
+		out_len += ret;
+		len -= ret;
+		buf += ret;
+	}
+
+	return out_len;
+}
+
+static int mkdir_p(char *path, mode_t mode)
+{
+	struct stat st = { 0 };
+	char *cpy_path = NULL;
+	char *cur_path = NULL;
+	char *tmp_path = NULL;
+	int ret = 0;
+	char *dir;
+
+	cpy_path = malloc(strlen(path) + 1);
+	if (!cpy_path) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	strcpy(cpy_path, path);
+
+	cur_path = malloc(strlen(path) + 1);
+	if (!cur_path) {
+		ret = -ENOMEM;
+		goto free_cpy_path;
+	}
+	memset(cur_path, 0, strlen(path) + 1);
+
+	for (dir = strtok(cpy_path, "/");
+	     dir != NULL;
+	     dir = strtok(NULL, "/")) {
+		/* keep current path */
+		tmp_path = malloc(strlen(cur_path) + 1);
+		if (!tmp_path) {
+			ret = -ENOMEM;
+			goto free_cur_path;
+		}
+		strcpy(tmp_path, cur_path);
+
+		/* append directory in current path */
+		sprintf(cur_path, "%s/%s", tmp_path, dir);
+
+		free(tmp_path);
+		tmp_path = NULL;
+
+		if (stat(cur_path, &st)) {
+			ret = mkdir(cur_path, mode);
+			if (ret) {
+				fprintf(stderr,
+					DUMP_LOG_FMT("mkdir(%s) fail(%s)\n"),
+					cur_path, strerror(errno));
+				goto free_cur_path;
+			}
+		}
+	}
+
+free_cur_path:
+	free(cur_path);
+	cur_path = NULL;
+
+free_cpy_path:
+	free(cpy_path);
+	cpy_path = NULL;
+
+out:
+	return ret;
+}
+
+int tops_save_dump_data(char *dump_root_dir)
+{
+	struct stat st = { 0 };
+	int ret = 0;
+	int fd;
+
+	if (!dump_root_dir) {
+		ret = -1;
+		goto out;
+	}
+
+	/* 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"),
+			dump_root_dir, strlen(dump_root_dir), PATH_MAX - 256);
+		return -1;
+	}
+
+	if (stat(dump_root_dir, &st)) {
+		ret = mkdir_p(dump_root_dir, 0775);
+		if (ret) {
+			fprintf(stderr,
+				DUMP_LOG_FMT("mkdir_p(%s) fail(%d)\n"),
+				dump_root_dir, ret);
+			ret = -1;
+			goto out;
+		}
+	}
+
+	fd = open(DUMP_DATA_PATH, O_RDONLY);
+	if (fd < 0) {
+		fprintf(stderr,
+			DUMP_LOG_FMT("open(%s) fail(%s)\n"),
+			DUMP_DATA_PATH, strerror(errno));
+		ret = -1;
+		goto out;
+	}
+
+	while (1) {
+		char dd[RELAY_DUMP_SUBBUF_SIZE - sizeof(struct dump_data_header)];
+		struct dump_data_header dd_hdr;
+		struct pollfd pfd = {
+			.fd = fd,
+			.events = POLLIN | POLLHUP | POLLERR,
+		};
+
+		poll(&pfd, 1, -1);
+
+		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);
+			ret = -1;
+			break;
+		}
+
+		if (!ret)
+			continue;
+
+		if (dd_hdr.data_len == 0) {
+			fprintf(stderr,
+				DUMP_LOG_FMT("read empty data\n"));
+			continue;
+		}
+
+		if (dd_hdr.data_len > sizeof(dd)) {
+			fprintf(stderr,
+				DUMP_LOG_FMT("data length %u > %lu\n"),
+				dd_hdr.data_len, sizeof(dd));
+			ret = -1;
+			break;
+		}
+
+		ret = read_retry(fd, dd, dd_hdr.data_len);
+		if (ret < 0) {
+			fprintf(stderr,
+				DUMP_LOG_FMT("read dd fail(%d)\n"), ret);
+			ret = -1;
+			break;
+		}
+
+		if ((uint32_t)ret != dd_hdr.data_len) {
+			fprintf(stderr,
+				DUMP_LOG_FMT("read dd length %u != %u\n"),
+				(uint32_t)ret, dd_hdr.data_len);
+			ret = -1;
+			break;
+		}
+
+		ret = save_dump_data(dump_root_dir, &dd_hdr, dd);
+		if (ret) {
+			fprintf(stderr,
+				DUMP_LOG_FMT("save_dump_data(%s) fail(%d)\n"),
+				dump_root_dir, ret);
+			break;
+		}
+	}
+
+	close(fd);
+
+out:
+	return ret;
+}
diff --git a/feed/tops-tool/src/inc/dump.h b/feed/tops-tool/src/inc/dump.h
new file mode 100644
index 0000000..0d331c9
--- /dev/null
+++ b/feed/tops-tool/src/inc/dump.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Alvin Kuo <Alvin.Kuo@mediatek.com>
+ */
+
+#ifndef __DUMP_H__
+#define __DUMP_H__
+
+#include <sys/types.h>
+
+#define DUMP_INFO_NAME_MAX_LEN		32
+#define RELAY_DUMP_SUBBUF_SIZE		2048
+#define DUMP_DATA_PATH			"/sys/kernel/debug/tops/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;
+	uint32_t start_addr;
+	uint32_t size;
+	uint32_t dump_rsn;
+#define DUMP_RSN_NULL				(0x0000)
+#define DUMP_RSN_WDT_TIMEOUT_CORE0		(0x0001)
+#define DUMP_RSN_WDT_TIMEOUT_CORE1		(0x0002)
+#define DUMP_RSN_WDT_TIMEOUT_CORE2		(0x0004)
+#define DUMP_RSN_WDT_TIMEOUT_CORE3		(0x0008)
+#define DUMP_RSN_WDT_TIMEOUT_COREM		(0x0010)
+#define DUMP_RSN_FE_RESET			(0x0020)
+};
+
+struct dump_data_header {
+	struct dump_info info;
+	uint32_t data_offset;
+	uint32_t data_len;
+	uint8_t last_frag;
+};
+
+int tops_save_dump_data(char *dump_dir);
+
+#endif /* __DUMP_H__ */
diff --git a/feed/tops-tool/src/tops-tool.c b/feed/tops-tool/src/tops-tool.c
new file mode 100644
index 0000000..32ab579
--- /dev/null
+++ b/feed/tops-tool/src/tops-tool.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Alvin Kuo <Alvin.Kuo@mediatek.com>
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "dump.h"
+
+static void print_usage(void)
+{
+	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");
+}
+
+static int verify_parameters(int argc,
+			     char *argv[])
+{
+	char *cmd;
+
+	if (argc < 2) {
+		fprintf(stderr, DUMP_LOG_FMT("missing cmd\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;
+		}
+	}
+
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	int ret = 0;
+	char *cmd;
+
+	ret = verify_parameters(argc, argv);
+	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);
+		goto error;
+	}
+
+error:
+	return ret;
+}