diff --git a/scripts/gdb/README.md b/scripts/gdb/README.md index 9b2e74a87c..75d6e8ba5c 100644 --- a/scripts/gdb/README.md +++ b/scripts/gdb/README.md @@ -17,6 +17,7 @@ py import lvglgdb dump obj dump cache image dump cache image_header +dump draw_task # Inspect a single lv_style_t variable info style my_style diff --git a/scripts/gdb/lvglgdb/__init__.py b/scripts/gdb/lvglgdb/__init__.py index d8436fddf1..64e5c071a1 100644 --- a/scripts/gdb/lvglgdb/__init__.py +++ b/scripts/gdb/lvglgdb/__init__.py @@ -3,6 +3,11 @@ from .lvgl import ( curr_inst, LVDisplay, LVDrawBuf, + LVDrawTask, + DRAW_TASK_TYPE_NAMES, + DRAW_TASK_STATE_NAMES, + DRAW_UNIT_TYPE_NAMES, + LVDrawUnit, LVList, LVObject, ObjStyle, @@ -30,6 +35,8 @@ __all__ = [ "curr_inst", "LVDisplay", "LVDrawBuf", + "LVDrawTask", + "LVDrawUnit", "LVList", "LVCache", "LVRedBlackTree", diff --git a/scripts/gdb/lvglgdb/cmds/__init__.py b/scripts/gdb/lvglgdb/cmds/__init__.py index 2ef1a894f4..0094588c90 100644 --- a/scripts/gdb/lvglgdb/cmds/__init__.py +++ b/scripts/gdb/lvglgdb/cmds/__init__.py @@ -2,8 +2,8 @@ import gdb from .core import DumpObj from .display import DumpDisplayBuf -from .draw import InfoDrawUnit from .misc import InfoStyle, DumpCache, CheckPrefix, CheckCache +from .draw import InfoDrawUnit, DumpDrawTask from .debugger import Debugger from .drivers import Lvglobal @@ -25,6 +25,7 @@ DumpDisplayBuf() DumpCache() CheckPrefix() CheckCache() +DumpDrawTask() # Infos InfoStyle() diff --git a/scripts/gdb/lvglgdb/cmds/draw/__init__.py b/scripts/gdb/lvglgdb/cmds/draw/__init__.py index 9c31c8a938..a69f48b46e 100644 --- a/scripts/gdb/lvglgdb/cmds/draw/__init__.py +++ b/scripts/gdb/lvglgdb/cmds/draw/__init__.py @@ -1,5 +1,7 @@ from .lv_draw import InfoDrawUnit +from .lv_draw_task import DumpDrawTask __all__ = [ "InfoDrawUnit", + "DumpDrawTask", ] diff --git a/scripts/gdb/lvglgdb/cmds/draw/lv_draw.py b/scripts/gdb/lvglgdb/cmds/draw/lv_draw.py index 9530a1ef59..3342fb8a59 100644 --- a/scripts/gdb/lvglgdb/cmds/draw/lv_draw.py +++ b/scripts/gdb/lvglgdb/cmds/draw/lv_draw.py @@ -1,7 +1,8 @@ import gdb -from lvglgdb.value import Value from lvglgdb.lvgl import curr_inst +from lvglgdb.lvgl.draw.lv_draw_unit import LVDrawUnit +from lvglgdb.lvgl.draw.lv_draw_consts import DRAW_UNIT_TYPE_NAMES class InfoDrawUnit(gdb.Command): @@ -12,39 +13,22 @@ class InfoDrawUnit(gdb.Command): "info draw_unit", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION ) - def dump_draw_unit(self, draw_unit: Value): - # Dereference to get the string content of the name from draw_unit - name = draw_unit.name.string() + def invoke(self, args, from_tty): + for unit in curr_inst().draw_units(): + self._dump_unit(unit) - # Print draw_unit information and the name - print(f"Draw Unit: {draw_unit}, Name: {name}") + def _dump_unit(self, unit: LVDrawUnit): + name = unit.name + print(f"Draw Unit: {unit}, Name: {name}") - # Handle different draw_units based on the name - def lookup_type(name): - try: - return gdb.lookup_type(name) - except gdb.error: - return None + type_name = DRAW_UNIT_TYPE_NAMES.get(name, "lv_draw_unit_t") + try: + target_type = gdb.lookup_type(type_name) + except gdb.error: + target_type = gdb.lookup_type("lv_draw_unit_t") - types = { - "DMA2D": lookup_type("lv_draw_dma2d_unit_t"), - "NEMA_GFX": lookup_type("lv_draw_nema_gfx_unit_t"), - "NXP_PXP": lookup_type("lv_draw_pxp_unit_t"), - "NXP_VGLITE": lookup_type("lv_draw_vglite_unit_t"), - "OPENGLES": lookup_type("lv_draw_opengles_unit_t"), - "DAVE2D": lookup_type("lv_draw_dave2d_unit_t"), - "SDL": lookup_type("lv_draw_sdl_unit_t"), - "SW": lookup_type("lv_draw_sw_unit_t"), - "VG_LITE": lookup_type("lv_draw_vg_lite_unit_t"), - } - - type = types.get(name, lookup_type("lv_draw_unit_t")) print( - draw_unit.cast(type, ptr=True) + unit.cast(target_type, ptr=True) .dereference() .format_string(pretty_structs=True, symbols=True) ) - - def invoke(self, args, from_tty): - for unit in curr_inst().draw_units(): - self.dump_draw_unit(unit) diff --git a/scripts/gdb/lvglgdb/cmds/draw/lv_draw_task.py b/scripts/gdb/lvglgdb/cmds/draw/lv_draw_task.py new file mode 100644 index 0000000000..37ca155542 --- /dev/null +++ b/scripts/gdb/lvglgdb/cmds/draw/lv_draw_task.py @@ -0,0 +1,25 @@ +import gdb + +from lvglgdb.value import Value +from lvglgdb.lvgl.draw.lv_draw_task import LVDrawTask + + +class DumpDrawTask(gdb.Command): + """dump draw tasks from a layer""" + + def __init__(self): + super(DumpDrawTask, self).__init__( + "dump draw_task", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION + ) + + def invoke(self, args, from_tty): + if not args.strip(): + print("Usage: dump draw_task ") + return + try: + val = gdb.parse_and_eval(args.strip()) + except gdb.error as e: + print(f"Error: {e}") + return + + LVDrawTask.print_entries(LVDrawTask(Value(val))) diff --git a/scripts/gdb/lvglgdb/lvgl/__init__.py b/scripts/gdb/lvglgdb/lvgl/__init__.py index f005aebbe7..4025e2af03 100644 --- a/scripts/gdb/lvglgdb/lvgl/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/__init__.py @@ -1,6 +1,13 @@ from .core import LVObject, ObjStyle, curr_inst, dump_obj_info, dump_obj_styles from .display import LVDisplay -from .draw import LVDrawBuf +from .draw import ( + LVDrawBuf, + LVDrawTask, + LVDrawUnit, + DRAW_TASK_TYPE_NAMES, + DRAW_TASK_STATE_NAMES, + DRAW_UNIT_TYPE_NAMES, +) from .misc import ( LVList, LVStyle, @@ -29,6 +36,11 @@ __all__ = [ "ObjStyle", "LVDisplay", "LVDrawBuf", + "LVDrawTask", + "DRAW_TASK_TYPE_NAMES", + "DRAW_TASK_STATE_NAMES", + "DRAW_UNIT_TYPE_NAMES", + "LVDrawUnit", "curr_inst", "LVList", "LVStyle", diff --git a/scripts/gdb/lvglgdb/lvgl/core/lv_global.py b/scripts/gdb/lvglgdb/lvgl/core/lv_global.py index 39c27b788f..02b6f820c5 100644 --- a/scripts/gdb/lvglgdb/lvgl/core/lv_global.py +++ b/scripts/gdb/lvglgdb/lvgl/core/lv_global.py @@ -50,12 +50,11 @@ class LVGL: return disp.act_scr if disp else None def draw_units(self): - unit = self.lv_global.draw_info.unit_head + from ..draw.lv_draw_unit import LVDrawUnit - # Iterate through all draw units - while unit: - yield unit - unit = unit.next + head = self.lv_global.draw_info.unit_head + if int(head): + yield from LVDrawUnit(head) def image_cache(self): from ..misc.lv_image_cache import LVImageCache diff --git a/scripts/gdb/lvglgdb/lvgl/draw/__init__.py b/scripts/gdb/lvglgdb/lvgl/draw/__init__.py index 3cb93d1c11..7366a003cf 100644 --- a/scripts/gdb/lvglgdb/lvgl/draw/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/draw/__init__.py @@ -1,5 +1,17 @@ from .lv_draw_buf import LVDrawBuf +from .lv_draw_task import LVDrawTask +from .lv_draw_unit import LVDrawUnit +from .lv_draw_consts import ( + DRAW_TASK_TYPE_NAMES, + DRAW_TASK_STATE_NAMES, + DRAW_UNIT_TYPE_NAMES, +) __all__ = [ "LVDrawBuf", + "LVDrawTask", + "DRAW_TASK_TYPE_NAMES", + "DRAW_TASK_STATE_NAMES", + "DRAW_UNIT_TYPE_NAMES", + "LVDrawUnit", ] diff --git a/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_consts.py b/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_consts.py new file mode 100644 index 0000000000..a25b2da489 --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_consts.py @@ -0,0 +1,48 @@ +""" +Auto-generated draw constants from LVGL headers. + +Do not edit manually. Regenerate with: + python3 scripts/gen_draw_consts.py +""" + +DRAW_TASK_TYPE_NAMES = { + 0: "NONE", + 1: "FILL", + 2: "BORDER", + 3: "BOX_SHADOW", + 4: "LETTER", + 5: "LABEL", + 6: "IMAGE", + 7: "LAYER", + 8: "LINE", + 9: "ARC", + 10: "TRIANGLE", + 11: "MASK_RECTANGLE", + 12: "MASK_BITMAP", + 13: "BLUR", + 14: "VECTOR", + 15: "3D", +} + +DRAW_TASK_STATE_NAMES = { + 0: "BLOCKED", + 1: "WAITING", + 2: "QUEUED", + 3: "IN_PROGRESS", + 4: "FINISHED", +} + +DRAW_UNIT_TYPE_NAMES = { + "DAVE2D": "lv_draw_dave2d_unit_t", + "DMA2D": "lv_draw_dma2d_unit_t", + "ESP_PPA": "lv_draw_ppa_unit_t", + "G2D": "lv_draw_g2d_unit_t", + "NANOVG": "lv_draw_nanovg_unit_t", + "NEMA_GFX": "lv_draw_nema_gfx_unit_t", + "NXP_PXP": "lv_draw_pxp_unit_t", + "OPENGLES": "lv_draw_opengles_unit_t", + "SDL": "lv_draw_sdl_unit_t", + "SW": "lv_draw_sw_unit_t", + "SW_ARM2D": "lv_draw_sw_unit_t", + "VG_LITE": "lv_draw_vg_lite_unit_t", +} diff --git a/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_task.py b/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_task.py new file mode 100644 index 0000000000..93f2409aea --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_task.py @@ -0,0 +1,77 @@ +from prettytable import PrettyTable + +from lvglgdb.value import Value, ValueInput +from .lv_draw_consts import DRAW_TASK_TYPE_NAMES, DRAW_TASK_STATE_NAMES + + +class LVDrawTask(Value): + """LVGL draw task wrapper""" + + def __init__(self, task: ValueInput): + super().__init__(Value.normalize(task, "lv_draw_task_t")) + + @property + def type(self) -> int: + return int(self.super_value("type")) + + @property + def type_name(self) -> str: + return DRAW_TASK_TYPE_NAMES.get(self.type, f"UNKNOWN({self.type})") + + @property + def state(self) -> int: + return int(self.super_value("state")) + + @property + def state_name(self) -> str: + return DRAW_TASK_STATE_NAMES.get(self.state, f"UNKNOWN({self.state})") + + @property + def area(self) -> tuple: + a = self.super_value("area") + return (int(a["x1"]), int(a["y1"]), int(a["x2"]), int(a["y2"])) + + @property + def opa(self) -> int: + return int(self.super_value("opa")) + + @property + def next(self): + n = self.super_value("next") + return LVDrawTask(n) if int(n) else None + + def __iter__(self): + node = self + while node: + yield node + node = node.next + + @property + def preferred_draw_unit_id(self) -> int: + return int(self.super_value("preferred_draw_unit_id")) + + @staticmethod + def print_entries(tasks): + """Print draw tasks as a PrettyTable.""" + table = PrettyTable() + table.field_names = ["#", "type", "state", "area", "opa", "unit_id"] + table.align = "l" + + count = 0 + for i, t in enumerate(tasks): + table.add_row( + [ + i, + t.type_name, + t.state_name, + t.area, + t.opa, + t.preferred_draw_unit_id, + ] + ) + count += 1 + + if count == 0: + print("No draw tasks.") + else: + print(table) diff --git a/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_unit.py b/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_unit.py new file mode 100644 index 0000000000..a0b3fb4264 --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/draw/lv_draw_unit.py @@ -0,0 +1,47 @@ +from prettytable import PrettyTable + +from lvglgdb.value import Value, ValueInput + + +class LVDrawUnit(Value): + """LVGL draw unit wrapper""" + + def __init__(self, unit: ValueInput): + super().__init__(Value.normalize(unit, "lv_draw_unit_t")) + + @property + def name(self) -> str: + n = self.super_value("name") + return n.string() if int(n) else "(unnamed)" + + @property + def idx(self) -> int: + return int(self.super_value("idx")) + + @property + def next(self): + n = self.super_value("next") + return LVDrawUnit(n) if int(n) else None + + def __iter__(self): + node = self + while node: + yield node + node = node.next + + @staticmethod + def print_entries(units): + """Print draw units as a PrettyTable.""" + table = PrettyTable() + table.field_names = ["#", "name", "idx"] + table.align = "l" + + count = 0 + for i, unit in enumerate(units): + table.add_row([i, unit.name, unit.idx]) + count += 1 + + if count == 0: + print("No draw units.") + else: + print(table) diff --git a/scripts/gdb/scripts/gen_draw_consts.py b/scripts/gdb/scripts/gen_draw_consts.py new file mode 100644 index 0000000000..b6e6e5eda8 --- /dev/null +++ b/scripts/gdb/scripts/gen_draw_consts.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +""" +Generate draw constant tables from LVGL header and source files. + +Parses lv_draw.h for task type/state enums, and scans draw unit source +files for name-to-struct-type mappings. + +Usage: + python3 scripts/gen_draw_consts.py +""" + +import re +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent +GDB_ROOT = SCRIPT_DIR.parent +LVGL_SRC = GDB_ROOT.parent.parent / "src" +OUTPUT = GDB_ROOT / "lvglgdb" / "lvgl" / "draw" / "lv_draw_consts.py" + +DRAW_H = LVGL_SRC / "draw" / "lv_draw.h" +DRAW_DIR = LVGL_SRC / "draw" + + +def parse_enum(path: Path, enum_type: str, prefix: str) -> dict[int, str]: + """Parse a C enum from a header file.""" + text = path.read_text() + + pattern = rf"\}}\s*{re.escape(enum_type)}\s*;" + m = re.search(rf"typedef\s+enum\s*\{{(.*?){pattern}", text, re.DOTALL) + if not m: + raise RuntimeError(f"Cannot find {enum_type} enum in {path}") + + entries = {} + current_val = 0 + for line in m.group(1).splitlines(): + line = line.strip().rstrip(",") + if ( + not line + or line.startswith("/*") + or line.startswith("//") + or line.startswith("*") + or line.startswith("#") + ): + continue + + match = re.match(rf"({re.escape(prefix)}\w+)\s*=\s*(0x[\da-fA-F]+|\d+)", line) + if match: + name = match.group(1) + current_val = int(match.group(2), 0) + else: + match = re.match(rf"({re.escape(prefix)}\w+)", line) + if not match: + continue + name = match.group(1) + + short = name.removeprefix(prefix) + entries[current_val] = short + current_val += 1 + + return entries + + +def parse_draw_unit_types(draw_dir: Path) -> dict[str, str]: + """Scan draw unit .c files for name-to-struct-type mappings. + + Looks for patterns like: unit->base_unit.name = "SW"; + Then finds the corresponding struct type from the variable declaration. + """ + mappings = {} + + for c_file in draw_dir.rglob("*.c"): + text = c_file.read_text() + + for m in re.finditer(r'(\w+)->base_unit\.name\s*=\s*"(\w+)"', text): + var_name = m.group(1) + unit_name = m.group(2) + + decl = re.search( + rf"(lv_draw_\w+_unit_t)\s*\*\s*{re.escape(var_name)}\b", text + ) + if decl: + mappings[unit_name] = decl.group(1) + + return mappings + + +def generate( + task_types: dict[int, str], + task_states: dict[int, str], + unit_types: dict[str, str], +) -> str: + """Generate Python source for the draw constants module.""" + lines = [ + '"""', + "Auto-generated draw constants from LVGL headers.", + "", + "Do not edit manually. Regenerate with:", + " python3 scripts/gen_draw_consts.py", + '"""', + "", + ] + + lines.append("DRAW_TASK_TYPE_NAMES = {") + for k in sorted(task_types): + lines.append(f' {k}: "{task_types[k]}",') + lines.append("}") + lines.append("") + + lines.append("DRAW_TASK_STATE_NAMES = {") + for k in sorted(task_states): + lines.append(f' {k}: "{task_states[k]}",') + lines.append("}") + lines.append("") + + lines.append("DRAW_UNIT_TYPE_NAMES = {") + for name in sorted(unit_types): + lines.append(f' "{name}": "{unit_types[name]}",') + lines.append("}") + lines.append("") + + return "\n".join(lines) + + +def main(): + task_types = parse_enum(DRAW_H, "lv_draw_task_type_t", "LV_DRAW_TASK_TYPE_") + task_states = parse_enum(DRAW_H, "lv_draw_task_state_t", "LV_DRAW_TASK_STATE_") + unit_types = parse_draw_unit_types(DRAW_DIR) + + src = generate(task_types, task_states, unit_types) + OUTPUT.write_text(src) + print( + f"Generated {OUTPUT} ({len(task_types)} task types, " + f"{len(task_states)} task states, {len(unit_types)} unit types)" + ) + + +if __name__ == "__main__": + main()