@h2loop/autosar 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +11 -1
  2. package/agents/autosar-integrator.md +45 -2
  3. package/commands/autosar-trace.md +2 -2
  4. package/package.json +4 -1
  5. package/plugins/index.js +55 -23
  6. package/python/ecuc_engine/__init__.py +1 -1
  7. package/python/ecuc_engine/add_signal.py +351 -0
  8. package/python/ecuc_engine/arxml_edit.py +387 -0
  9. package/python/ecuc_engine/audit.py +352 -50
  10. package/python/ecuc_engine/cli.py +125 -5
  11. package/python/ecuc_engine/codegen/__init__.py +233 -0
  12. package/python/ecuc_engine/codegen/binding.py +305 -0
  13. package/python/ecuc_engine/codegen/bindings/sip_standin.json +378 -0
  14. package/python/ecuc_engine/codegen/cdiff.py +395 -0
  15. package/python/ecuc_engine/codegen/cwriter.py +150 -0
  16. package/python/ecuc_engine/codegen/emit.py +463 -0
  17. package/python/ecuc_engine/codegen/handles.py +176 -0
  18. package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
  19. package/python/ecuc_engine/codegen/model.py +264 -0
  20. package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
  21. package/python/ecuc_engine/codegen/modules/com.py +145 -0
  22. package/python/ecuc_engine/codegen/modules/comstack.py +14 -0
  23. package/python/ecuc_engine/codegen/modules/dem.py +22 -0
  24. package/python/ecuc_engine/codegen/modules/mcal.py +170 -0
  25. package/python/ecuc_engine/codegen/modules/memstack.py +24 -0
  26. package/python/ecuc_engine/codegen/registry.py +208 -0
  27. package/python/ecuc_engine/codegen/schm_memmap.py +159 -0
  28. package/python/ecuc_engine/context.py +481 -0
  29. package/python/ecuc_engine/csrc.py +189 -0
  30. package/python/ecuc_engine/defs.py +30 -0
  31. package/python/ecuc_engine/ecucjson.py +75 -22
  32. package/python/ecuc_engine/find.py +164 -0
  33. package/python/ecuc_engine/gen.py +105 -0
  34. package/python/ecuc_engine/history.py +146 -0
  35. package/python/ecuc_engine/model.py +4 -1
  36. package/python/ecuc_engine/probe.py +229 -0
  37. package/python/ecuc_engine/rte_api.py +935 -0
  38. package/python/ecuc_engine/rules/__init__.py +5 -3
  39. package/python/ecuc_engine/rules/mem.py +84 -0
  40. package/python/ecuc_engine/rules/nvm.py +131 -0
  41. package/python/ecuc_engine/rules/pdur.py +87 -0
  42. package/python/ecuc_engine/rules/system.py +162 -0
  43. package/python/ecuc_engine/specids.py +163 -0
  44. package/python/ecuc_engine/sysmodel.py +152 -0
  45. package/python/ecuc_engine/validate.py +15 -2
  46. package/python/ecuc_engine/values.py +26 -0
  47. package/python/ecuc_engine/verify_rte.py +445 -0
  48. package/python/pyproject.toml +1 -1
  49. package/skills/autosar/SKILL.md +78 -6
  50. package/skills/autosar/references/codegen.md +58 -0
  51. package/skills/autosar/references/ecuc-model.md +4 -1
  52. package/skills/autosar/references/project-layout.md +10 -1
  53. package/skills/autosar/references/runnable-code.md +50 -0
@@ -0,0 +1,170 @@
1
+ """Port and Dio from the STANDARD R25-11 param defs, plus a per-silicon vendor-extension hook.
2
+
3
+ Standard part (no vendor data): Port_Cfg.h switches and PortConf_PortPin_* symbols, Port tables
4
+ (pin direction / initial mode / level / allowed modes bitmask / changeable flags) in the file of
5
+ their config class; Dio_Cfg.h with DioConf_DioPort_* / DioConf_DioChannel_* / DioConf_DioChannelGroup_*
6
+ symbols and the Dio_ChannelGroupType objects the group symbols point to (SWS_Dio_00184: mask,
7
+ offset, port; ECUC_Dio_00149: the symbol is the address expression).
8
+
9
+ Vendor hook: `.hydron/autosar.json` {"codegen": {"mcal": {"silicon": "tc4d", "pinMap": "mcal/pins.json"}}}
10
+ loads codegen/mcal/<silicon>.json (or a project file) that maps the standard pin attributes to
11
+ register fields, and a project pin map {"<PortPin SHORT-NAME>": {"port": "P00", "pin": 5,
12
+ "modes": {"PORT_PIN_MODE_CAN": 2}, "fields": {"PD": 0}}}. With both, Port_PBcfg.c / Port_Cfg.c gets
13
+ a register image (address, value) per pin. Missing data is reported, never guessed.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import os
19
+ import re
20
+
21
+ from .. import cwriter as W
22
+ from ..model import CLASS_RANK
23
+ from ..registry import Generator, register
24
+
25
+ HERE = os.path.dirname(os.path.dirname(__file__))
26
+
27
+
28
+ def _pins(spec: str) -> set:
29
+ out = set()
30
+ for part in str(spec).split(","):
31
+ a, _, b = part.strip().partition("-")
32
+ if a:
33
+ out.update(range(int(a), int(b or a) + 1))
34
+ return out
35
+
36
+
37
+ def load_silicon(root: str, name: str) -> dict:
38
+ for f in (os.path.join(root, name), os.path.join(root, ".hydron", "codegen", "mcal", f"{name}.json"),
39
+ os.path.join(HERE, "mcal", f"{name}.json")):
40
+ if os.path.isfile(f):
41
+ with open(f) as fh:
42
+ return json.load(fh)
43
+ raise FileNotFoundError(f"no MCAL silicon mapping {name!r} (bundled: "
44
+ f"{', '.join(sorted(x[:-5] for x in os.listdir(os.path.join(HERE, 'mcal'))))})")
45
+
46
+
47
+ def register_image(sil: dict, pins: list, pin_map: dict) -> tuple:
48
+ """[(address, value, comment)], [missing-data notes] for Port pins (container, attrs)."""
49
+ regs = sil["registers"]
50
+ fields = sil["fields"]
51
+ rows, missing = [], []
52
+ for c, attrs in pins:
53
+ pm = pin_map.get(c.name)
54
+ if pm is None:
55
+ missing.append(f"{c.name}: not in the pin map (port/pin unknown)")
56
+ continue
57
+ port = sil["ports"].get(pm.get("port"))
58
+ if port is None:
59
+ missing.append(f"{c.name}: port {pm.get('port')!r} not in the {sil['silicon']} mapping")
60
+ continue
61
+ pin = int(pm.get("pin", -1))
62
+ if pin not in _pins(port.get("pins", "0-15")):
63
+ missing.append(f"{c.name}: {pm['port']}.{pin} is not an available port slice ({sil.get('portsCite', '')})")
64
+ continue
65
+ vals: dict = {}
66
+ d = sil.get("direction", {}).get(attrs.get("PortPinDirection"))
67
+ if d is None:
68
+ missing.append(f"{c.name}: no field mapping for direction {attrs.get('PortPinDirection')}")
69
+ continue
70
+ vals.update({k: v for k, v in d.items() if k != "cite"})
71
+ mode = attrs.get("PortPinInitialMode")
72
+ mv = (pm.get("modes") or {}).get(mode)
73
+ if mv is not None:
74
+ vals["MODE"] = int(mv)
75
+ elif mode in sil.get("modes", {}):
76
+ vals.update({k: v for k, v in sil["modes"][mode].items() if k != "cite"})
77
+ else:
78
+ missing.append(f"{c.name}: alternate-function number for {mode} on {pm['port']}.{pin} "
79
+ "(per-pin, from the vendor VSMD / data sheet pin table)")
80
+ continue
81
+ for k, v in (pm.get("fields") or {}).items():
82
+ vals[k] = int(v)
83
+ level = sil.get("level", {}).get(attrs.get("PortPinLevelValue"), {})
84
+ if attrs.get("PortPinDirection") == "PORT_PIN_OUT":
85
+ vals.update(level)
86
+ by_reg: dict = {}
87
+ for k, v in vals.items():
88
+ f = fields.get(k)
89
+ if f is None:
90
+ missing.append(f"{c.name}: field {k} unknown in the {sil['silicon']} mapping")
91
+ continue
92
+ if v >> f["width"]:
93
+ missing.append(f"{c.name}: {k}={v} does not fit {f['width']} bit(s)")
94
+ continue
95
+ by_reg[f["reg"]] = by_reg.get(f["reg"], 0) | (int(v) << f["lsb"])
96
+ base = int(port["base"], 16)
97
+ # level request (GPIO) before the driver configuration (DRVCFG): no glitch when the output turns on
98
+ for reg in sorted(by_reg, key=lambda r: (r != "GPIO", r)):
99
+ r = regs[reg]
100
+ addr = base + int(r["offset"], 16) + pin * int(r["stride"], 16)
101
+ fl = ", ".join(f"{k}={v}" for k, v in sorted(vals.items()) if fields.get(k, {}).get("reg") == reg)
102
+ rows.append((addr, by_reg[reg], f"{c.name}: {pm['port']}.{pin} {reg} {fl}"))
103
+ return rows, missing
104
+
105
+
106
+ @register("Port")
107
+ class Port(Generator):
108
+ def specific(self, mo):
109
+ opts = (getattr(self.ctx, "options", {}) or {}).get("mcal") or {}
110
+ if not opts.get("silicon"):
111
+ return
112
+ root = self.cg.p.root
113
+ sil = load_silicon(root, opts["silicon"])
114
+ pin_map = {}
115
+ if opts.get("pinMap"):
116
+ pf = opts["pinMap"] if os.path.isabs(opts["pinMap"]) else os.path.join(root, opts["pinMap"])
117
+ with open(pf) as fh:
118
+ pin_map = json.load(fh)
119
+ pins = []
120
+ rank = 1
121
+ for c in self.cg.sorted_instances(self.mc, "PortPin"):
122
+ attrs = {n: c.text(n) for n in ("PortPinDirection", "PortPinInitialMode", "PortPinLevelValue")}
123
+ pins.append((c, attrs))
124
+ for n in attrs:
125
+ rank = max(rank, self.cg.eff_class(self.mc, self.cg.param_def(c, n)))
126
+ rows, missing = register_image(sil, pins, pin_map)
127
+ mo.notes += [f"{sil['silicon']} ({sil.get('status', '')}): {m}" for m in missing]
128
+ if not rows:
129
+ mo.notes.append(f"{sil['silicon']}: no register image generated; needs vendor data: "
130
+ + "; ".join(sil.get("needsVendorData", [])))
131
+ return
132
+ t = f"Port_Cfg{sil['silicon'].capitalize()}RegType"
133
+ mo.types.append("typedef struct {\n uint32 Address;\n uint32 Value;\n} " + t + ";")
134
+ cnt = f"PORT_CFG_NUM_{sil['silicon'].upper()}_REG_INIT"
135
+ mo.group(f"{sil['title']} ({sil.get('status')})", [(cnt, W.c_uint(len(rows)))])
136
+ decl = f"CONST({t}, PORT_CONST) Port_Cfg{sil['silicon'].capitalize()}RegInit[{cnt}]"
137
+ mo.hdecls.append(f"extern {decl};")
138
+ lines = [W.initializer([W.c_uint(a, True), W.c_uint(v, True)], cm) for a, v, cm in rows]
139
+ cites = "; ".join(f"{k}: {r['cite']}" for k, r in sil["registers"].items())
140
+ mo.table(rank, f"/* Register image from the {sil['silicon']} mapping ({cites}).\n"
141
+ f" * {sil.get('statusNote', '')[:300]} */\n{decl} = {{\n" + ",\n".join(lines) + "\n};")
142
+
143
+
144
+ @register("Dio")
145
+ class Dio(Generator):
146
+ def specific(self, mo):
147
+ if "Dio.h" not in self.ctx.headers:
148
+ mo.notes.append("no Dio.h (static Dio code) in the project: Dio_ChannelGroupType objects not generated")
149
+ return
150
+ objs, decls = [], []
151
+ for g in self.cg.instances(self.mc, "DioChannelGroup"):
152
+ ident = (g.text("DioChannelGroupIdentification") or "").strip()
153
+ m = re.fullmatch(r"&\s*([A-Za-z_]\w*)", ident)
154
+ if not m:
155
+ mo.notes.append(f"{g.path}: DioChannelGroupIdentification {ident!r} is not '&<object>'; "
156
+ "the Dio_ChannelGroupType object is not generated")
157
+ continue
158
+ port = g.parent.text("DioPortId") if g.parent is not None else None
159
+ vals = [W.c_uint(int(g.text("DioPortMask") or 0, 0), True), W.c_uint(int(g.text("DioPortOffset") or 0, 0)),
160
+ W.c_uint(int(port or 0, 0))]
161
+ decls.append(f"extern CONST(Dio_ChannelGroupType, DIO_CONST) {m.group(1)};")
162
+ objs.append(f"CONST(Dio_ChannelGroupType, DIO_CONST) {m.group(1)} = {{ {', '.join(vals)} }}; "
163
+ f"/* {g.name}: mask, offset, port (SWS_Dio_00184) */")
164
+ if objs:
165
+ mo.hdecls += ["/* Channel groups (ECUC_Dio_00149); Dio.h defines Dio_ChannelGroupType before including Dio_Cfg.h */",
166
+ *decls]
167
+ rank = max(self.cg.eff_class(self.mc, self.cg.param_def(g, "DioPortMask"))
168
+ for g in self.cg.instances(self.mc, "DioChannelGroup"))
169
+ mo.table(max(rank, CLASS_RANK["PRE-COMPILE"]), "\n".join(objs))
170
+ mo.include("Dio.h")
@@ -0,0 +1,24 @@
1
+ """Memory stack: NvM, MemIf, Fee, Ea. Everything they need is expressible by the defs (block
2
+ descriptors, symbolic block IDs/numbers, pre-compile switches), so they are plain registrations;
3
+ the NvM block-descriptor layout of a given static NvM comes from its binding."""
4
+ from ..registry import Generator, register
5
+
6
+
7
+ @register("NvM")
8
+ class NvM(Generator):
9
+ pass
10
+
11
+
12
+ @register("MemIf")
13
+ class MemIf(Generator):
14
+ pass
15
+
16
+
17
+ @register("Fee")
18
+ class Fee(Generator):
19
+ pass
20
+
21
+
22
+ @register("Ea")
23
+ class Ea(Generator):
24
+ pass
@@ -0,0 +1,208 @@
1
+ """Generator registry. A module generator self-registers with @register("<ModuleDefName>").
2
+
3
+ Any configured module can be emitted by the generic Generator (macros, symbols, tables straight
4
+ from the param defs). A registered subclass adds only what the defs cannot express (and is what
5
+ `autosar_codegen` runs by default). To add a module: drop a file in codegen/modules/ with a
6
+ @register'ed subclass; modules/__init__.py imports every file in the folder.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from typing import Optional
12
+
13
+ from . import binding as B
14
+ from . import cwriter as W
15
+ from . import emit as E
16
+
17
+ GENERATORS: dict = {}
18
+
19
+
20
+ def register(name: str):
21
+ def deco(cls):
22
+ cls.module = name
23
+ GENERATORS[name] = cls
24
+ return cls
25
+ return deco
26
+
27
+
28
+ def generator_for(name: str):
29
+ from . import modules # noqa: F401 (self-registration)
30
+ return GENERATORS.get(name, Generator)
31
+
32
+
33
+ def registered() -> list:
34
+ from . import modules # noqa: F401
35
+ return sorted(GENERATORS)
36
+
37
+
38
+ class Generator:
39
+ module: Optional[str] = None
40
+ std_header = "Std_Types.h"
41
+
42
+ def __init__(self, ctx: E.Ctx, mc):
43
+ self.ctx = ctx
44
+ self.cg = ctx.cg
45
+ self.mc = mc
46
+
47
+ def run(self) -> E.ModuleOutput:
48
+ mo = E.ModuleOutput(self.ctx, self.mc)
49
+ mo.h_includes = [self.default_std_header()]
50
+ mb = B.select(self.ctx.bindings, self.mc.name, self.ctx.headers, self.ctx.binding_name)
51
+ mo.binding = mb
52
+ if mb and mb.get("headerIncludes"):
53
+ mo.h_includes = list(mb["headerIncludes"])
54
+ E.scalar_macros(self.cg, mo)
55
+ E.symbolic_names(self.cg, mo)
56
+ self.specific(mo)
57
+ if mb is None or mb.get("genericTables", False):
58
+ E.generic_tables(self.cg, mo, set(self.skip_tables(mo)))
59
+ if mb is not None:
60
+ try:
61
+ apply_binding(self.cg, mo, mb, self.path_steps(mo))
62
+ except B.BindingError as ex:
63
+ mo.errors.append(str(ex))
64
+ return mo
65
+
66
+ def path_steps(self, mo) -> dict:
67
+ """call:<name> resolvers for the binding path language: {name: fn(container, Eval) -> token | None}."""
68
+ return {}
69
+
70
+ def default_std_header(self) -> str:
71
+ """ComStack_Types.h for modules that reference EcuC PDUs (communication stack), else Std_Types.h."""
72
+ md = self.mc.defn
73
+ if md is not None:
74
+ for d in self.cg.defs.by_path.values():
75
+ if d.module == md.path and d.kind and "REFERENCE" in d.kind and any(
76
+ r.endswith("/EcucPduCollection/Pdu") for r in d.dest_refs):
77
+ return "ComStack_Types.h"
78
+ return self.std_header
79
+
80
+ def skip_tables(self, mo) -> list:
81
+ return []
82
+
83
+ def specific(self, mo: E.ModuleOutput):
84
+ """Module-specific emission (only what the param defs cannot express)."""
85
+
86
+
87
+ def apply_binding(cg, mo: E.ModuleOutput, mb: dict, ext: Optional[dict] = None):
88
+ ev = B.Eval(cg, mo.mc, mo.ctx)
89
+ ev.ext = ext or {}
90
+ sets = mb.get("sets", {})
91
+ for req in mb.get("require", []):
92
+ bad = [r for r in ev.rows(req["rows"], sets) if not ev.matches(r, [req])]
93
+ if bad:
94
+ vals = sorted({str(ev.raw(r, req["path"])) for r in bad})
95
+ mo.errors.append(f"{len(bad)} {req['rows']} container(s) have {req['path']} = {', '.join(vals)} "
96
+ f"({', '.join(r.name for r in bad[:5])}{', ...' if len(bad) > 5 else ''}); the "
97
+ f"{mb['_binding']} static code supports only "
98
+ f"{', '.join('(not configured)' if x is None else str(x) for x in req['in'])}: {req.get('why', '')}")
99
+ for un in mb.get("unsupported", []):
100
+ rows = cg.instances(mo.mc, un["rows"])
101
+ if rows:
102
+ mo.errors.append(f"{len(rows)} {un['rows']} container(s) configured ({', '.join(r.name for r in rows[:5])}"
103
+ f"{', ...' if len(rows) > 5 else ''}), which the {mb['_binding']} static code cannot "
104
+ f"represent: {un.get('why', '')}")
105
+ for inc in mb.get("sourceIncludes", []):
106
+ mo.include(inc)
107
+ macros = []
108
+ values: dict = {}
109
+ for m in mb.get("macros", []):
110
+ val = m["value"]
111
+ v = ev.macro(val, sets) if re.match(r"^(count|max):", val) else val
112
+ macros.append((m["name"], v))
113
+ values[m["name"]] = v
114
+ mo.group(f"Static-code interface ({mb['_binding']} binding)", macros)
115
+ pending: list = []
116
+ for vs in mb.get("vars", []):
117
+ ev.rank = 1
118
+ lines = []
119
+ for r in ev.rows(vs["rows"], sets):
120
+ size, _ = ev.field(r, {"name": vs["name"], "path": vs["size"]})
121
+ name = vs["name"].replace("{}", r.name)
122
+ lines.append(f"{'static ' if vs.get('static') else ''}VAR({vs['type']}, {vs['qualifier']}) {name}[{size}];")
123
+ pending.append(("var", ev.rank, vs.get("section", "VAR_NOINIT_UNSPECIFIED"), "\n".join(lines)))
124
+ for t in mb.get("tables", []):
125
+ ev.rank = 1
126
+ rows = ev.rows(t["rows"], sets)
127
+ if t.get("order"):
128
+ from ..values import parse_int
129
+ rows.sort(key=lambda r: (parse_int(r.text(t["order"]) or "") is None, parse_int(r.text(t["order"]) or "") or 0))
130
+ lines, externs = [], []
131
+ des = t.get("designated")
132
+ if des:
133
+ _check_index(cg, mo, t, rows, des, values)
134
+ for r in rows:
135
+ vals = []
136
+ for f in t["fields"]:
137
+ v, _ = ev.field(r, f)
138
+ vals.append(v)
139
+ if f.get("extern") and v not in ("NULL_PTR", "0", "0u") and not v.startswith('"'):
140
+ m = re.search(r"[A-Za-z_]\w*", v)
141
+ if m:
142
+ ext_ = "extern " + f["extern"].replace("{}", m.group(0)) + ";"
143
+ if ext_ not in externs:
144
+ externs.append(ext_)
145
+ lead = ""
146
+ if des:
147
+ lead = f"[{des.replace('{sym}', cg.symbol(r)) if isinstance(des, str) else cg.symbol(r)}] = "
148
+ comment = t.get("comment", "name")
149
+ cm = {"name": r.name, "sym": cg.symbol(r) or r.name}.get(comment, r.name)
150
+ if t.get("scalar"):
151
+ lines.append(f" {lead}{vals[0]} /* {cm} */")
152
+ else:
153
+ lines.append(f" {lead}{{ {', '.join(vals)} }} /* {cm} */")
154
+ st = "static " if t.get("static") else ""
155
+ size = t.get("size", "")
156
+ body = (f"/* {t.get('doc', t['rows'])} */\n{st}CONST({t['type']}, {t['qualifier']}) {t['name']}[{size}] = {{\n" +
157
+ ",\n".join(lines) + "\n};")
158
+ pending.append(("table", ev.rank, body, externs))
159
+ objects = mb.get("objects", [])
160
+ if objects:
161
+ # a root config object points at the (static) tables and buffers, so they all live in its file:
162
+ # the most dynamic config class among them (e.g. <Mip>_PBcfg.c for a post-build PduR_Config)
163
+ top = max([p[1] for p in pending] + [1])
164
+ pending = [(p[0], top) + p[2:] for p in pending]
165
+ for o in objects:
166
+ body = (f"CONST({o['type']}, {o['qualifier']}) {o['name']} = {{\n " + ", ".join(o["init"]) + "\n};")
167
+ pending.append(("table", top, body, []))
168
+ for p in pending:
169
+ if p[0] == "var":
170
+ mo.var(p[1], p[2], p[3])
171
+ else:
172
+ mo.table(p[1], p[2], p[3])
173
+ mo.used_mips |= ev.used_mips
174
+
175
+
176
+ def _check_index(cg, mo, t: dict, rows: list, des, values: dict):
177
+ """A designated table is indexed by handle (minus an offset): every row must land in [0, size) exactly once,
178
+ or the static code's table lookup would be wrong. Reported as an error, never renumbered here."""
179
+ from ..values import parse_int
180
+ off_name = None
181
+ if isinstance(des, str):
182
+ m = re.fullmatch(r"\{sym\}\s*-\s*(\w+)", des.strip())
183
+ if not m:
184
+ return
185
+ off_name = m.group(1)
186
+ num = lambda v: parse_int(re.sub(r"[uUlL]+$", "", str(v))) # noqa: E731
187
+ off = num(values.get(off_name, "0")) if off_name else 0
188
+ size = num(values[t["size"]]) if t.get("size") in values else len(rows)
189
+ seen: dict = {}
190
+ bad = 0
191
+ for r in rows:
192
+ h = parse_int(cg.symbol_value(r) or "")
193
+ if h is None:
194
+ continue
195
+ i = h - (off or 0)
196
+ if not 0 <= i < (size or 0) or i in seen:
197
+ bad += 1
198
+ if bad > 5:
199
+ continue
200
+ pm = cg.symbolic_param(r)
201
+ mo.errors.append(f"{r.path}: {pm.name if pm else 'handle'}={h} does not fit the static code's "
202
+ f"{t['name']}[{t.get('size')}] indexed by {des if isinstance(des, str) else '{sym}'} "
203
+ f"({'collides with ' + seen[i] if i in seen else f'index {i} outside 0..{(size or 0) - 1}'}); "
204
+ f"the {t['rows']} handles must be contiguous in the order the static code expects")
205
+ continue
206
+ seen[i] = r.name
207
+ if bad > 5:
208
+ mo.errors.append(f"... {bad - 5} more {t['rows']} handle(s) outside {t['name']}")
@@ -0,0 +1,159 @@
1
+ """SchM_<Mip>.h and <Mip>_MemMap.h (+ an aggregating MemMap.h when the project has none).
2
+
3
+ SchM_<Mip>.h (SWS_Rte BSW scheduler part): exclusive-area enter/exit macros and the module's main
4
+ function prototypes.
5
+ * Exclusive areas: the ones the module's static code actually uses (`SchM_Enter_<Mip>_<EA>` found
6
+ in the project's non-generated C), plus any configured for the module in the Rte ECUC.
7
+ Mechanism per RteExclusiveAreaImplMechanism when configured (RteBswExclusiveAreaImpl), else
8
+ ALL_INTERRUPT_BLOCKING (OS SuspendAllInterrupts/ResumeAllInterrupts).
9
+ * Main functions: <Mip>_MainFunction for a `...MainFunctionPeriod` parameter, and one per instance
10
+ of a `<Mip>MainFunction<X>` container (Com-style named main functions).
11
+ <Mip>_MemMap.h: SWS_MemMap_00032 file name, keyword evaluation and START/STOP sequence checks in the
12
+ shape of the SWS_MemMap example (chapter A.3); pragmas are left to the project's MemMapAllocation
13
+ (`.hydron/autosar.json` codegen.memmap.pragmas), so the default build is compiler-neutral.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import re
18
+
19
+ from . import cwriter as W
20
+
21
+ DEFAULT_SECTIONS = ["CODE", "CONST_UNSPECIFIED", "CONFIG_DATA_UNSPECIFIED", "VAR_INIT_UNSPECIFIED",
22
+ "VAR_NOINIT_UNSPECIFIED", "VAR_CLEARED_UNSPECIFIED"]
23
+
24
+ MECHANISMS = {
25
+ "ALL_INTERRUPT_BLOCKING": ("SuspendAllInterrupts()", "ResumeAllInterrupts()"),
26
+ "OS_INTERRUPT_BLOCKING": ("SuspendOSInterrupts()", "ResumeOSInterrupts()"),
27
+ }
28
+
29
+
30
+ def _scan(ctx, rx) -> dict:
31
+ hits: dict = {}
32
+ for f in ctx.sources:
33
+ try:
34
+ with open(f, errors="replace") as fh:
35
+ text = fh.read()
36
+ except OSError:
37
+ continue
38
+ for m in rx.finditer(text):
39
+ hits.setdefault(m.group(1), set()).add(ctx.cg.p.rel(f))
40
+ return hits
41
+
42
+
43
+ def _ea_mechanisms(cg) -> dict:
44
+ """EA short name -> (mechanism, os resource) from the Rte ECUC, when configured."""
45
+ out = {}
46
+ rte = cg.module("Rte")
47
+ if rte is None:
48
+ return out
49
+ for c in rte.containers:
50
+ if c.def_name != "RteBswExclusiveAreaImpl":
51
+ continue
52
+ ref = c.text("RteBswExclusiveAreaRef") or ""
53
+ mech = c.text("RteExclusiveAreaImplMechanism") or "ALL_INTERRUPT_BLOCKING"
54
+ res = (c.text("RteBswExclusiveAreaOsResourceRef") or "").rsplit("/", 1)[-1]
55
+ if ref:
56
+ out[ref.rsplit("/", 1)[-1]] = (mech, res)
57
+ return out
58
+
59
+
60
+ def main_functions(cg, mo) -> list:
61
+ mc = mo.mc
62
+ out = list(getattr(mo, "main_functions", []))
63
+ for c in mc.containers:
64
+ if c.defn is None:
65
+ continue
66
+ m = re.match(rf"^{re.escape(mc.name)}MainFunction(\w*)$", c.def_name)
67
+ if m:
68
+ suffix = m.group(1)
69
+ fn = f"{mo.mip}_MainFunction{suffix}" + ("" if c.name == c.def_name else f"_{c.name}")
70
+ if fn not in out:
71
+ out.append(fn)
72
+ continue
73
+ if cg.is_multi(c):
74
+ continue
75
+ for p in c.defn.params:
76
+ if p.endswith("MainFunctionPeriod") and c.text(p.rsplit("/", 1)[-1]) is not None:
77
+ fn = f"{mo.mip}_MainFunction"
78
+ if fn not in out:
79
+ out.append(fn)
80
+ return out
81
+
82
+
83
+ def schm_header(ctx, mo) -> tuple:
84
+ mip, MIP = mo.mip, mo.MIP
85
+ eas = _scan(ctx, re.compile(rf"\bSchM_(?:Enter|Exit)_{re.escape(mip)}_(\w+)\s*\("))
86
+ mech = _ea_mechanisms(ctx.cg)
87
+ for ea in mech:
88
+ if ea.upper().startswith(MIP + "_") and ea not in eas:
89
+ eas[ea] = {"Rte ECUC"}
90
+ mfs = main_functions(ctx.cg, mo)
91
+ name = f"SchM_{mip}.h"
92
+ g = W.guard(name)
93
+ lines = [mo._banner(name), "", f"#ifndef {g}", f"#define {g}", "", '#include "Std_Types.h"']
94
+ if eas:
95
+ lines.append('#include "Os.h"')
96
+ if eas:
97
+ lines += ["", "/* Exclusive areas (SWS_Rte SchM_Enter/SchM_Exit); used in: "
98
+ + ", ".join(sorted({f for s in eas.values() for f in s})) + " */"]
99
+ for ea in sorted(eas):
100
+ m, res = mech.get(ea, ("ALL_INTERRUPT_BLOCKING", ""))
101
+ if m == "OS_RESOURCE" and res:
102
+ enter, exit_ = f"(void)GetResource({res})", f"(void)ReleaseResource({res})"
103
+ else:
104
+ enter, exit_ = MECHANISMS.get(m, ("do { } while (0)", "do { } while (0)"))
105
+ lines += [W.define(f"SchM_Enter_{mip}_{ea}()", enter, m), W.define(f"SchM_Exit_{mip}_{ea}()", exit_)]
106
+ if mfs:
107
+ lines += ["", "/* Main functions called by the BSW scheduler */"]
108
+ lines += [f"extern FUNC(void, {MIP}_CODE) {fn}(void);" for fn in mfs]
109
+ lines += ["", f"#endif /* {g} */", ""]
110
+ return name, "\n".join(lines)
111
+
112
+
113
+ def module_memmap(ctx, mo, pragmas: dict) -> tuple:
114
+ mip, MIP = mo.mip, mo.MIP
115
+ used = _scan(ctx, re.compile(rf"\b{re.escape(MIP)}_START_SEC_(\w+)"))
116
+ sections = list(DEFAULT_SECTIONS) + sorted(s for s in used if s not in DEFAULT_SECTIONS)
117
+ name = f"{mip}_MemMap.h"
118
+ lines = [mo._banner(name),
119
+ "/* Memory mapping of the module's sections (SWS_MemMap_00032). No include guard: it is included",
120
+ " * once per section switch. Pragmas come from codegen.memmap.pragmas (MemMapAllocation); none by default. */",
121
+ "", "#define MEMMAP_ERROR", ""]
122
+ for i, sec in enumerate(sections):
123
+ kw = f"{MIP}_SEC_{sec}"
124
+ start = pragmas.get(sec, {}).get("start", "") if isinstance(pragmas.get(sec), dict) else ""
125
+ stop = pragmas.get(sec, {}).get("stop", "") if isinstance(pragmas.get(sec), dict) else ""
126
+ lines += [
127
+ f"{'#if' if i == 0 else '#elif'} defined({MIP}_START_SEC_{sec})",
128
+ f" #undef {MIP}_START_SEC_{sec}",
129
+ " #undef MEMMAP_ERROR",
130
+ f" #ifdef {MIP}_MEMMAP_SEQUENCE_OPEN",
131
+ f' #error "{name}: {kw}: STOP the open section before starting a new one"',
132
+ " #endif",
133
+ f" #define {MIP}_MEMMAP_SEQUENCE_OPEN",
134
+ *( [f" {start}"] if start else []),
135
+ f"#elif defined({MIP}_STOP_SEC_{sec})",
136
+ f" #undef {MIP}_STOP_SEC_{sec}",
137
+ " #undef MEMMAP_ERROR",
138
+ f" #ifndef {MIP}_MEMMAP_SEQUENCE_OPEN",
139
+ f' #error "{name}: {kw}: STOP without START"',
140
+ " #endif",
141
+ f" #undef {MIP}_MEMMAP_SEQUENCE_OPEN",
142
+ *( [f" {stop}"] if stop else []),
143
+ ]
144
+ lines += ["#endif", "", "#if defined(MEMMAP_ERROR)", f' #error "{name}: unknown memory section"', "#endif", ""]
145
+ return name, "\n".join(lines), sections
146
+
147
+
148
+ def aggregate_memmap(ctx, entries: list) -> str:
149
+ """Legacy MemMap.h dispatching to the per-module files (only emitted when the project has none)."""
150
+ lines = [W.banner("MemMap.h", "MemMap", "-", ctx.release, [], "-"),
151
+ "/* Dispatches each <MIP>_START_SEC_/<MIP>_STOP_SEC_ keyword to <Mip>_MemMap.h. No include guard. */", ""]
152
+ first = True
153
+ for mip, sections in entries:
154
+ MIP = mip.upper()
155
+ cond = " || \\\n ".join(f"defined({MIP}_{k}_SEC_{s})" for s in sections for k in ("START", "STOP"))
156
+ lines += [f"{'#if' if first else '#elif'} {cond}", f' #include "{mip}_MemMap.h"']
157
+ first = False
158
+ lines += ["#else", ' #error "MemMap.h: unknown memory section"', "#endif", ""]
159
+ return "\n".join(lines)