summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rwxr-xr-xutil/build_with_clang.py10
-rwxr-xr-xutil/clangd_config.py33
-rwxr-xr-xutil/crash_analyzer.py339
-rw-r--r--util/ec_panicinfo_fuzzer.cc22
-rw-r--r--util/ectool.cc67
-rw-r--r--util/stm32mon.cc1
6 files changed, 314 insertions, 158 deletions
diff --git a/util/build_with_clang.py b/util/build_with_clang.py
index 4951c53f30..71957107a0 100755
--- a/util/build_with_clang.py
+++ b/util/build_with_clang.py
@@ -51,7 +51,7 @@ BOARDS_THAT_COMPILE_SUCCESSFULLY_WITH_CLANG = [
"max32660-eval",
# Boards that use CHIP:=npcx
# git grep --name-only 'CHIP:=npcx' | sed 's#^board/\(.*\)/build.mk#"\1",#'
- # "adlrvpp_npcx",
+ "adlrvpp_npcx",
"agah",
"akemi",
"aleena",
@@ -75,7 +75,7 @@ BOARDS_THAT_COMPILE_SUCCESSFULLY_WITH_CLANG = [
"copano",
"coral",
"corori",
- # "corori2",
+ "corori2",
"cret",
"crota",
"dalboz",
@@ -89,7 +89,7 @@ BOARDS_THAT_COMPILE_SUCCESSFULLY_WITH_CLANG = [
"eldrid",
"elemi",
"endeavour",
- # "eve",
+ "eve",
"ezkinil",
"felwinter",
"fizz",
@@ -144,7 +144,7 @@ BOARDS_THAT_COMPILE_SUCCESSFULLY_WITH_CLANG = [
"pazquel",
"phaser",
"pompom",
- # "poppy",
+ "poppy",
"primus",
"puff",
"quackingstick",
@@ -161,7 +161,7 @@ BOARDS_THAT_COMPILE_SUCCESSFULLY_WITH_CLANG = [
"treeya",
"trembyle",
"trogdor",
- # "vell",
+ "vell",
"vilboz",
"voema",
"volet",
diff --git a/util/clangd_config.py b/util/clangd_config.py
index 96bcb7ebf1..2553707c43 100755
--- a/util/clangd_config.py
+++ b/util/clangd_config.py
@@ -65,7 +65,38 @@ def ec_build(ec_root: Path, board: str, image: str) -> Optional[Path]:
def zephyr_build(ec_root: Path, board: str, image: str) -> Optional[Path]:
"""Build the correct compile_commands.json for Zephyr board/image"""
- raise NotImplementedError("Zephyr is currently unsupported.")
+ target = Path(
+ f"build/zephyr/{board}/build-{image.lower()}/compile_commands.json"
+ )
+ cmd = ["zmake", "configure", board]
+
+ print(" ".join(cmd))
+ status = subprocess.run(cmd, check=False, cwd=ec_root)
+
+ if status.returncode != 0:
+ return None
+
+ # Replace /mnt/host/source with path of chromiumos outside chroot
+ default_chromiumos_path_outside_chroot = os.path.join(
+ Path.home(), "chromiumos"
+ )
+ chromiumos_path_outside_chroot = os.environ.get(
+ "EXTERNAL_TRUNK_PATH", default_chromiumos_path_outside_chroot
+ )
+ chromiumos_path_inside_chroot = "/mnt/host/source"
+
+ print(
+ f"Replacing '{chromiumos_path_inside_chroot}' with "
+ + f"'{chromiumos_path_outside_chroot}' in file {target}"
+ )
+
+ target.write_text(
+ target.read_text().replace(
+ chromiumos_path_inside_chroot, chromiumos_path_outside_chroot
+ )
+ )
+
+ return target
def copy(ec_root: Path, target: Path) -> None:
diff --git a/util/crash_analyzer.py b/util/crash_analyzer.py
index 2e51511461..c2e07face2 100755
--- a/util/crash_analyzer.py
+++ b/util/crash_analyzer.py
@@ -10,9 +10,8 @@ import pathlib
import re
import sys
-# Regex tested here: https://regex101.com/r/K5S8cB/1
-# This Regex has only been tested in Cortex-M0+ crash reporter.
# TODO(b/253492108): Add regexp for missing architectures.
+# Regex tested here: https://regex101.com/r/K5S8cB/1
_REGEX_CORTEX_M0 = (
r"^Saved.*$\n=== .* EXCEPTION: (.*) ====== xPSR: (.*) ===$\n"
r"r0 :(.*) r1 :(.*) r2 :(.*) r3 :(.*)$\n"
@@ -22,10 +21,152 @@ _REGEX_CORTEX_M0 = (
r"\n"
r"^.*cfsr=(.*), shcsr=(.*), hfsr=(.*), dfsr=(.*), ipsr=(.*)$"
)
+
+# Regex tested here: https://regex101.com/r/FL7T0n/1
+_REGEX_NDS32 = (
+ r"^Saved.*$\n===.*ITYPE=(.*) ===$\n"
+ r"R0 (.*) R1 (.*) R2 (.*) R3 (.*)$\n"
+ r"R4 (.*) R5 (.*) R6 (.*) R7 (.*)$\n"
+ r"R8 (.*) R9 (.*) R10 (.*) R15 (.*)$\n"
+ r"FP (.*) GP (.*) LP (.*) SP (.*)$\n"
+ r"IPC (.*) IPSW (.*)$\n"
+)
+
+
+# List of symbols. Each entry is tuple: address, name
_symbols = []
+# List of crashes. Each entry is dictionary.
+# Contains all crashes found.
_entries = []
+# This is function, and not a global dictionary, so that
+# we can reference the different functions without placing
+# the functions above the dictionary.
+def get_architectures() -> list:
+ """Returns a dictionary with the supported architectures"""
+
+ archs = {
+ "cm0": {
+ "regex": _REGEX_CORTEX_M0,
+ "parser": cm0_parse,
+ "extra_regs": [
+ "sp",
+ "lr",
+ "pc",
+ "cfsr",
+ "chcsr",
+ "hfsr",
+ "dfsr",
+ "ipsr",
+ ],
+ },
+ "nds32": {
+ "regex": _REGEX_NDS32,
+ "parser": nds32_parse,
+ "extra_regs": ["fp", "gp", "lp", "sp", "ipc", "ipsw"],
+ },
+ }
+ return archs
+
+
+def get_crash_cause(cause: int) -> str:
+ """Returns the cause of crash in human-readable format"""
+ causes = {
+ 0xDEAD6660: "div-by-0",
+ 0xDEAD6661: "stack-overflow",
+ 0xDEAD6662: "pd-crash",
+ 0xDEAD6663: "assert",
+ 0xDEAD6664: "watchdog",
+ 0xDEAD6665: "bad-rng",
+ 0xDEAD6666: "pmic-fault",
+ 0xDEAD6667: "exit",
+ }
+
+ if cause in causes:
+ return causes[cause]
+ return f"unknown-cause-{format(cause, '#x')}"
+
+
+def cm0_parse(match) -> dict:
+ """Regex parser for Cortex-M0+ architecture"""
+
+ # Expecting something like:
+ # Saved panic data: (NEW)
+ # === PROCESS EXCEPTION: ff ====== xPSR: ffffffff ===
+ # r0 : r1 : r2 : r3 :
+ # r4 :dead6664 r5 :10092632 r6 :00000000 r7 :00000000
+ # r8 :00000000 r9 :00000000 r10:00000000 r11:00000000
+ # r12: sp :00000000 lr : pc :
+ #
+ # cfsr=00000000, shcsr=00000000, hfsr=00000000, dfsr=00000000, ipsr=000000ff
+ regs = {}
+ values = []
+
+ for i in match.groups():
+ try:
+ val = int(i, 16)
+ except ValueError:
+ # Value might be empty, so we must handle the exception
+ val = -1
+ values.append(val)
+
+ regs["task"] = values[0]
+ regs["xPSR"] = values[1]
+ regs["regs"] = values[3:15]
+ regs["sp"] = values[15]
+ regs["lr"] = values[16]
+ regs["pc"] = values[17]
+ regs["cfsr"] = values[18]
+ regs["chcsr"] = values[19]
+ regs["hfsr"] = values[20]
+ regs["dfsr"] = values[21]
+ regs["ipsr"] = values[22]
+
+ regs["cause"] = get_crash_cause(values[6]) # r4
+ regs["symbol"] = get_symbol(values[7]) # r5
+
+ return regs
+
+
+def nds32_parse(match) -> dict:
+ """Regex parser for Andes (NDS32) architecture"""
+
+ # Expecting something like:
+ # Saved panic data: (NEW)
+ # === EXCEP: ITYPE=0 ===
+ # R0 00000000 R1 00000000 R2 00000000 R3 00000000
+ # R4 00000000 R5 00000000 R6 dead6664 R7 00000000
+ # R8 00000000 R9 00000000 R10 00000000 R15 00000000
+ # FP 00000000 GP 00000000 LP 00000000 SP 00000000
+ # IPC 00050d5e IPSW 00000
+ # SWID of ITYPE: 0
+ regs = {}
+ values = []
+
+ for i in match.groups():
+ try:
+ val = int(i, 16)
+ except ValueError:
+ # Value might be empty, so we must handle the exception
+ val = -1
+ values.append(val)
+
+ # NDS32 is not reporting task info.
+ regs["task"] = -1
+ regs["regs"] = values[1:13]
+ regs["fp"] = values[13]
+ regs["gp"] = values[14]
+ regs["lp"] = values[15]
+ regs["sp"] = values[16]
+ regs["ipc"] = values[17]
+ regs["ipsw"] = values[18]
+
+ regs["cause"] = get_crash_cause(values[7]) # r6
+ regs["symbol"] = get_symbol(regs["ipc"])
+ return regs
+
+
def read_map_file(map_file):
"""Reads the map file, and populates the _symbols list with the tuple address/name"""
lines = map_file.readlines()
@@ -51,8 +192,8 @@ def get_symbol_bisec(addr: int, low: int, high: int) -> str:
if _symbols[mid][0] <= addr < _symbols[mid + 1][0]:
symbol = _symbols[mid][1]
- # Start of a sequence of Thumb instructions. When this happens, query
- # for the next address.
+ # Start of a sequence of Thumb instructions. When this happens, return
+ # the next address.
if symbol == "$t":
symbol = _symbols[mid + 1][1]
return symbol
@@ -73,8 +214,10 @@ def get_symbol(addr: int) -> str:
return symbol
-def process_log_file(file_name: str) -> str:
- """Reads a .log file and extracts the FW version"""
+def process_log_file(file_name: str) -> tuple:
+ """Reads a .log file and extracts the EC and BIOS versions"""
+ ec_ver = None
+ bios_ver = None
try:
with open(file_name, "r") as log_file:
lines = log_file.readlines()
@@ -84,64 +227,62 @@ def process_log_file(file_name: str) -> str:
# vendor | Nuvoton
# name | NPCX586G
# fw_version | rammus_v2.0.460-d1d2aeb01f
- if line.startswith("fw_version"):
- _, value = line.split("|")
- return value.strip()
+ # ...
+ # ===bios_info===
+ # fwid = Google_Rammus.11275.193.0 # [RO/str] ...
+ # ro_fwid = Google_Rammus.11275.28.0 # [RO/str] ...
+
+ # Get EC version.
+ # There could be more than one "fw_version". Only the first one
+ # corresponds to the EC version.
+ if line.startswith("fw_version") and ec_ver is None:
+ _, ec_ver = line.split("|")
+ ec_ver = ec_ver.strip(" \n")
+
+ # Get BIOS version.
+ if line.startswith("fwid"):
+ _, value = line.split("=")
+ # Only get the first element.
+ bios_ver, _ = value.split("#")
+ bios_ver = bios_ver.strip()
+
+ if ec_ver is not None and bios_ver is not None:
+ return ec_ver, bios_ver
+
except FileNotFoundError:
- return ".log file not found"
- return "unknown fw version"
+ return ".log file not found", "not found"
+ return (
+ "unknown fw version" if ec_ver is None else ec_ver,
+ "unknown BIOS version" if bios_ver is None else bios_ver,
+ )
-def process_crash_file(file_name: str) -> dict:
+def process_crash_file(filename: str) -> dict:
"""Process a single crash report, and convert it to a dictionary"""
- regs = {}
- with open(file_name, "r") as crash_file:
+
+ with open(filename, "r") as crash_file:
content = crash_file.read()
- # TODO(b/253492108): This is hardcoded to Cortex-M0+ crash reports.
- # New ones (Risc-V, NDS32, etc.) will be added on demand.
- #
- # Expecting something like:
- # Saved panic data: (NEW)
- # === PROCESS EXCEPTION: ff ====== xPSR: ffffffff ===
- # r0 : r1 : r2 : r3 :
- # r4 :dead6664 r5 :10092632 r6 :00000000 r7 :00000000
- # r8 :00000000 r9 :00000000 r10:00000000 r11:00000000
- # r12: sp :00000000 lr : pc :
- #
- # cfsr=00000000, shcsr=00000000, hfsr=00000000, dfsr=00000000, ipsr=000000ff
-
- match = re.match(_REGEX_CORTEX_M0, content, re.MULTILINE)
- values = []
- # Convert the values to numbers, invalid the invalid ones.
- # Cannot use list comprehension due to possible invalid values
- if match is not None:
- for i in match.groups():
- try:
- val = int(i, 16)
- except ValueError:
- # Value might be empty, so we must handle the exception
- val = -1
- values.append(val)
-
- regs["exp"] = values[0]
- regs["xPSR"] = values[1]
- regs["regs"] = values[2:15]
- regs["sp"] = values[15]
- regs["lr"] = values[16]
- regs["pc"] = values[17]
- regs["cfsr"] = values[18]
- regs["chcsr"] = values[19]
- regs["hfsr"] = values[20]
- regs["dfsr"] = values[21]
- regs["ipsr"] = values[22]
- regs["symbol"] = get_symbol(regs["regs"][5])
- return regs
+ for key, arch in get_architectures().items():
+ regex = arch["regex"]
+ match = re.match(regex, content, re.MULTILINE)
+ if match is None:
+ continue
+ entry = arch["parser"](match)
+
+ # Add "arch" entry since it is needed to process
+ # the "extra_regs", among other things.
+ entry["arch"] = key
+ return entry
+ return {}
-def process_crash_files(crash_folder):
+
+def process_crash_files(crash_folder: pathlib.Path) -> None:
"""Process the crash reports that are in the crash_folder"""
- processed = 0
+ total = 0
+ failed = 0
+ good = 0
for file in crash_folder.iterdir():
# .log and .upload_file_eccrash might not be in order.
# To avoid processing it more than once, only process the
@@ -150,65 +291,87 @@ def process_crash_files(crash_folder):
continue
entry = process_crash_file(file)
if len(entry) != 0:
- fw_ver = process_log_file(file.parent.joinpath(file.stem + ".log"))
- entry["fw_version"] = fw_ver
+ ec_ver, bios_ver = process_log_file(
+ file.parent.joinpath(file.stem + ".log")
+ )
+ entry["ec_version"] = ec_ver
+ entry["bios_version"] = bios_ver
+ entry["filename"] = file.stem
if len(entry) != 0:
_entries.append(entry)
- processed += 1
- print(f"Processed: {processed}", file=sys.stderr)
+ good += 1
+ else:
+ failed += 1
+ total += 1
+ print(f"Total: {total}, OK: {good}, Failed: {failed}", file=sys.stderr)
-def cmd_report_lite(crash_folder):
+def cmd_report_lite(crash_folder: pathlib.Path, with_filename: bool) -> None:
"""Generates a 'lite' report that only contains a few fields"""
process_crash_files(crash_folder)
for entry in _entries:
print(
- f"Task: {format(entry['exp'],'#04x')} - "
- f"cause: {format(entry['regs'][4], '#x')} - "
+ f"Task: {format(entry['task'],'#04x')} - "
+ f"cause: {entry['cause']} - "
f"PC: {entry['symbol']} - "
- f"EC ver:{entry['fw_version']}"
+ f"{entry['ec_version']} - "
+ f"{entry['bios_version']}",
+ end="",
)
+ if with_filename:
+ print(f" - {entry['filename']}", end="")
+
+ print()
+
-def cmd_report_full(crash_folder):
+def cmd_report_full(crash_folder: pathlib.Path, with_filename: bool) -> None:
"""Generates a full report in .cvs format"""
process_crash_files(crash_folder)
# Print header
- print(
- "Task,xPSR,r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,"
- "sp,lr,pc,cfsr,chcsr,hfsr,dfsr,ipsr,symbol,fw_version"
- )
for entry in _entries:
print(
- f"{format(entry['exp'],'#04x')},{format(entry['xPSR'],'#x')}",
+ f"Task: {format(entry['task'],'#04x')} - "
+ f"cause: {entry['cause']} - "
+ f"PC: {entry['symbol']} - ",
end="",
)
- for i in range(12):
- print(f",{format(entry['regs'][i],'#x')}", end="")
+ # Print registers
+ hex_regs = [hex(x) for x in entry["regs"]]
+ print(f"{hex_regs} - ", end="")
+
+ # Print extra registers. Varies from architecture to architecture.
+ arch = get_architectures()[entry["arch"]]
+ for reg in arch["extra_regs"]:
+ print(f"{reg}: {format(entry[reg], '#x')} ", end="")
+
+ # Print EC & BIOS info
print(
- f",{format(entry['sp'],'#x')}"
- f",{format(entry['lr'],'#x')}"
- f",{format(entry['pc'],'#x')}"
- f",{format(entry['cfsr'],'#x')}"
- f",{format(entry['hfsr'],'#x')}"
- f",{format(entry['dfsr'],'#x')}"
- f",{format(entry['ipsr'],'#x')}"
- f",\"{(entry['symbol'])}\""
- f",\"{(entry['fw_version'])}\""
+ f"{entry['ec_version']} - {entry['bios_version']}",
+ end="",
)
+ # Finally the filename. Useful for debugging.
+ if with_filename:
+ print(f" - {entry['filename']}", end="")
+
+ print()
+
def main(argv):
"""Main entry point"""
example_text = """Example:
+# For further details see: go/cros-ec-crash-analyzer
+#
+# Summary:
# 1st:
# Collect the crash reports using this script:
-# https://source.corp.google.com/piper///depot/google3/experimental/users/ricardoq/crashpad/main.py
+# https://source.corp.google.com/piper///depot/google3/experimental/users/ricardoq/ec_crash_report_fetcher
# MUST be run within a Google3 Workspace. E.g:
-(google3) blaze run //experimental/users/ricardoq/crashpad:main -- --outdir=/tmp/dumps/ --limit=3000 --offset=15000 --hwclass=shyvana --milestone=105
+(google3) blaze run //experimental/users/ricardoq/ec_crash_report_fetcher:main -- --outdir=/tmp/dumps/ --limit=3000 --offset=15000 --hwclass=shyvana --milestone=105
# 2nd:
# Assuming that you don't have the .map file of the EC image, you can download the EC image from LUCI
@@ -244,12 +407,18 @@ crash_analyzer.py lite -m /tmp/rammus_193.map -f /tmp/dumps | sort | uniq -c | l
)
parser.add_argument(
"-f",
- "--crash_folder",
+ "--crash-folder",
type=pathlib.Path,
required=True,
help="Folder with the EC crash report files",
)
parser.add_argument(
+ "-n",
+ "--with-filename",
+ action="store_true",
+ help="Includes the filename in the report. Useful for debugging.",
+ )
+ parser.add_argument(
"command", choices=["lite", "full"], help="Command to run."
)
args = parser.parse_args(argv)
@@ -258,9 +427,9 @@ crash_analyzer.py lite -m /tmp/rammus_193.map -f /tmp/dumps | sort | uniq -c | l
read_map_file(args.map_file)
if args.command == "lite":
- cmd_report_lite(args.crash_folder)
+ cmd_report_lite(args.crash_folder, args.with_filename)
elif args.command == "full":
- cmd_report_full(args.crash_folder)
+ cmd_report_full(args.crash_folder, args.with_filename)
else:
print(f"Unsupported command: {args.command}")
diff --git a/util/ec_panicinfo_fuzzer.cc b/util/ec_panicinfo_fuzzer.cc
new file mode 100644
index 0000000000..95f1871837
--- /dev/null
+++ b/util/ec_panicinfo_fuzzer.cc
@@ -0,0 +1,22 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "ec_panicinfo.h"
+
+/* Fuzzing Build command:
+ * $ clang++ ec_panicinfo_fuzzer.cc ec_panicinfo.cc -g -fsanitize=address,fuzzer
+ * -o ec_panicinfo_fuzzer
+ * -I../include/ -I../chip/host/ -I../board/host/ -I../fuzz -I../test
+ *
+ * Run Fuzzing:
+ * $ ./ec_panicinfo_fuzzer -runs=5000
+ */
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, unsigned int size)
+{
+ parse_panic_info((const char *)data, size);
+
+ return 0;
+}
diff --git a/util/ectool.cc b/util/ectool.cc
index c116b35fcc..844fa15ac6 100644
--- a/util/ectool.cc
+++ b/util/ectool.cc
@@ -273,8 +273,6 @@ const char help_str[] =
" Set 16 bit duty cycle of given PWM\n"
" rand <num_bytes>\n"
" generate <num_bytes> of random numbers\n"
- " readtest <patternoffset> <size>\n"
- " Reads a pattern from the EC via LPC\n"
" reboot_ec <RO|RW|cold|hibernate|hibernate-clear-ap-off|disable-jump|cold-ap-off>"
" [at-shutdown|switch-slot]\n"
" Reboot EC to RO or RW\n"
@@ -1228,70 +1226,6 @@ exit:
return rv;
}
-int cmd_read_test(int argc, char *argv[])
-{
- struct ec_params_read_test p;
- struct ec_response_read_test r;
- int offset, size;
- int errors = 0;
- int rv;
- int i;
- char *e;
- char *buf;
- uint32_t *b;
-
- if (argc < 3) {
- fprintf(stderr, "Usage: %s <pattern_offset> <size>\n", argv[0]);
- return -1;
- }
- offset = strtol(argv[1], &e, 0);
- size = strtol(argv[2], &e, 0);
- if ((e && *e) || size <= 0 || size > MAX_FLASH_SIZE) {
- fprintf(stderr, "Bad size.\n");
- return -1;
- }
- printf("Reading %d bytes with pattern offset 0x%x...\n", size, offset);
-
- buf = (char *)malloc(size);
- if (!buf) {
- fprintf(stderr, "Unable to allocate buffer.\n");
- return -1;
- }
-
- /* Read data in chunks */
- for (i = 0; i < size; i += sizeof(r.data)) {
- p.offset = offset + i / sizeof(uint32_t);
- p.size = MIN(size - i, sizeof(r.data));
- rv = ec_command(EC_CMD_READ_TEST, 0, &p, sizeof(p), &r,
- sizeof(r));
- if (rv < 0) {
- fprintf(stderr, "Read error at offset %d\n", i);
- free(buf);
- return rv;
- }
- memcpy(buf + i, r.data, p.size);
- }
-
- /* Check data */
- for (i = 0, b = (uint32_t *)buf; i < size / 4; i++, b++) {
- if (*b != i + offset) {
- printf("Mismatch at byte offset 0x%x: "
- "expected 0x%08x, got 0x%08x\n",
- (int)(i * sizeof(uint32_t)), i + offset, *b);
- errors++;
- }
- }
-
- free(buf);
- if (errors) {
- printf("Found %d errors\n", errors);
- return -1;
- }
-
- printf("done.\n");
- return 0;
-}
-
int cmd_reboot_ec(int argc, char *argv[])
{
struct ec_params_reboot_ec p;
@@ -11002,7 +10936,6 @@ const struct command commands[] = {
{ "pwmsetkblight", cmd_pwm_set_keyboard_backlight },
{ "pwmsetduty", cmd_pwm_set_duty },
{ "rand", cmd_rand },
- { "readtest", cmd_read_test },
{ "reboot_ec", cmd_reboot_ec },
{ "rgbkbd", cmd_rgbkbd },
{ "rollbackinfo", cmd_rollback_info },
diff --git a/util/stm32mon.cc b/util/stm32mon.cc
index 352158ae73..39837bd3dd 100644
--- a/util/stm32mon.cc
+++ b/util/stm32mon.cc
@@ -633,6 +633,7 @@ int wait_for_ack(int fd)
}
/* Do not break so that it can be handled as junk */
+ __attribute__((fallthrough));
default:
stat_resp[JUNK_IDX].event_count++;
if (mode == MODE_SERIAL)