@h2loop/autosar 1.2.1 → 1.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h2loop/autosar",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "AUTOSAR Classic pack for Hydron: ECUC read/edit-in-place/validate/diff on ARXML, config-to-code trace, generator and build runners",
5
5
  "type": "module",
6
6
  "private": false,
@@ -19,6 +19,9 @@ Path language (a list of steps, evaluated left to right from the row container):
19
19
  finds its neighbour's PDU handle (TPS_ECUC 3.4.1.3, via EcucPduCollection/Pdu)
20
20
  call:<name> a module generator's resolver (Generator.path_steps()) for what the defs cannot
21
21
  express as a path, e.g. Com's notification callouts (terminal, token)
22
+ child the one chosen sub-container of a choice container (any def)
23
+ def def short name of the current container (terminal), e.g. which choice is configured
24
+ count:<Def> number of direct sub-containers of def <Def> (terminal)
22
25
  sym TPS_ECUC_02108 symbol of the current container (terminal)
23
26
  module | name | index the current container's module name / SHORT-NAME / table index (terminal)
24
27
  Macro values: count:<Def> | count:@<Set> | max:<Def>/<Param>, optionally followed by +N or -N.
@@ -29,7 +32,12 @@ Module binding keys besides tables/objects/macros:
29
32
  vars RAM objects per row that tables point to (e.g. I-PDU buffers), sized by a path
30
33
  require [{"rows": "<Def>", "path": <path>, "in": [...], "why": "..."}]: what the static code supports;
31
34
  a configuration outside it is an error, never silently mis-generated
32
- Field keys: path, default, enum (literal -> C token), fmt ("hex" | "int" | template with {} / {MIP} / {Mip}),
35
+ Table keys besides name/type/rows/fields: order (sort param), contiguous (that param must be exactly
36
+ 0..n-1 after sorting, e.g. a position index), designated, per (<ParentDef>: one table per parent instance,
37
+ rows = its direct <Def> children, "{}" in name = parent SHORT-NAME; a parent with no rows is an error,
38
+ C has no empty arrays). unsupported entries may set "max": N (more than N containers is the error).
39
+ Field keys: path, default, enum (literal or token -> C token), scale (multiply, must give an integer:
40
+ seconds -> ms), fmt ("hex" | "int" | template with {} / {MIP} / {Mip}),
33
41
  divide (a path from the row whose value divides this one, rounded: seconds -> main-function ticks),
34
42
  agg ({"over": <multi-ref>, "params": [...], "op": "min_nonzero" | "max", "skipIf": <param>}).
35
43
  """
@@ -147,6 +155,16 @@ class Eval:
147
155
  (not wdef or c.def_name == wdef)]
148
156
  hits.sort(key=lambda c: (c.module.name, self.cg.by_path_index(c)))
149
157
  cur = hits[0] if hits else None
158
+ elif op == "child":
159
+ kids = list(cur.children)
160
+ if len(kids) > 1:
161
+ raise BindingError(f"{cur.path}: path step child: {len(kids)} sub-containers, expected the one "
162
+ "chosen sub-container of a choice container")
163
+ cur = kids[0] if kids else None
164
+ elif op == "def":
165
+ return ("token", cur.def_name)
166
+ elif op == "count":
167
+ return ("token", W.c_uint(len(cur.sub(arg))))
150
168
  elif op == "sym":
151
169
  s = self.cg.symbol(cur)
152
170
  if s and self.ctx is not None:
@@ -231,6 +249,12 @@ class Eval:
231
249
  s = self.ctx.xsym(v, self.mc, self.used_mips)
232
250
  return (s, "")
233
251
  if kind == "token":
252
+ if "enum" in spec:
253
+ if v not in spec["enum"]:
254
+ raise BindingError(f"{row.path}: {spec['path']} = {v} is not supported by the static code "
255
+ f"(the {self.mc.name} binding maps only {', '.join(spec['enum'])})")
256
+ e = spec["enum"][v]
257
+ return (W.c_uint(e) if isinstance(e, int) else str(e), v)
234
258
  if fmt and fmt not in ("hex", "int"):
235
259
  return (fmt.replace("{MIP}", v.upper()).replace("{Mip}", v).replace("{}", v), "")
236
260
  return (v, "")
@@ -244,6 +268,8 @@ class Eval:
244
268
  return (W.c_uint(e) if isinstance(e, int) else str(e), raw.strip())
245
269
  if "divide" in spec:
246
270
  return (self._ticks(row, raw, spec), "")
271
+ if "scale" in spec:
272
+ return (self._scaled(row, raw, spec), "")
247
273
  if fmt == "int": # a numeric value held in a STRING param (e.g. ComSignalInitValue)
248
274
  n = parse_int(raw.strip())
249
275
  if n is None:
@@ -261,6 +287,20 @@ class Eval:
261
287
  raise BindingError(f"{row.path}: cannot convert {spec.get('name')} = {raw} to ticks of {spec['divide']} = {base}")
262
288
  return W.c_uint(int(round(v / b)))
263
289
 
290
+ def _scaled(self, row: Container, raw, spec: dict) -> str:
291
+ """value * scale as an integer (e.g. LinIfDelay seconds -> ms); a value that is not a whole multiple is
292
+ an error, never rounded silently."""
293
+ from ..values import parse_float
294
+ v = parse_float(str(raw).strip())
295
+ if v is None:
296
+ raise BindingError(f"{row.path}: {spec.get('name')} = {raw} is not a number")
297
+ x = v * spec["scale"]
298
+ n = int(round(x))
299
+ if abs(x - n) > 1e-6 * max(1.0, abs(x)):
300
+ raise BindingError(f"{row.path}: {spec['path']} = {str(raw).strip()} is not a whole multiple of "
301
+ f"1/{spec['scale']}; the static code's {spec.get('name')} field holds an integer")
302
+ return W.c_uint(n)
303
+
264
304
  def _agg(self, row: Container, spec: dict) -> tuple:
265
305
  """Aggregate params over the targets of a multi-valued reference of the row (e.g. the smallest non-zero
266
306
  timeout of an I-PDU's signals, SWS_Com_00291)."""
@@ -373,6 +373,124 @@
373
373
  "init": ["Com_TxSignalInfo", "Com_RxSignalInfo", "Com_TxIPduInfo", "Com_RxIPduInfo"]
374
374
  }
375
375
  ]
376
+ },
377
+ "LinIf": {
378
+ "detect": {"header": "LinIf.h", "contains": "LinIf_FrameType"},
379
+ "sourceIncludes": ["LinIf.h"],
380
+ "sets": {"TxPdus": {"rows": "LinIfTxPdu"}},
381
+ "handleLayout": [{"param": "LinIfTxPduId", "sets": ["TxPdus"]}],
382
+ "require": [
383
+ {
384
+ "rows": "LinIfFrame",
385
+ "path": "param:LinIfFrameType",
386
+ "in": ["UNCONDITIONAL"],
387
+ "why": "LinIf_FrameType has no frame type: LinIf.c sends every slot as an unconditional frame (no sporadic, event-triggered, diagnostic MRF/SRF or node configuration frames)"
388
+ },
389
+ {
390
+ "rows": "LinIfFrame",
391
+ "path": ["sub:LinIfPduDirection", "child", "def"],
392
+ "in": ["LinIfTxPdu", "LinIfRxPdu"],
393
+ "why": "LinIf_FrameType.Direction is LINIF_DIR_TX or LINIF_DIR_RX only (no slave-to-slave or internal PDUs)"
394
+ },
395
+ {
396
+ "rows": "LinIfScheduleTable",
397
+ "path": "param:LinIfRunMode",
398
+ "in": ["RUN_CONTINUOUS"],
399
+ "why": "LinIf_ScheduleType has no run mode: LinIf_MainFunction cycles its entries continuously"
400
+ }
401
+ ],
402
+ "unsupported": [
403
+ {
404
+ "rows": "LinIfChannel",
405
+ "max": 1,
406
+ "why": "LinIf_ConfigType has one frame and one schedule list and no channel field"
407
+ },
408
+ {"rows": "LinIfSlave", "why": "LinIf.h is a master-node LinIf (LinIf_MainFunction runs the schedule)"},
409
+ {"rows": "LinIfSubstitutionFrames", "why": "LinIf_FrameType has no substitution frame list"},
410
+ {"rows": "LinIfFixedFrameSdu", "why": "LinIf_FrameType has no fixed response SDU"}
411
+ ],
412
+ "macros": [
413
+ {"name": "LINIF_NUMBER_OF_CHANNELS", "value": "count:LinIfChannel"},
414
+ {"name": "LINIF_NUMBER_OF_TX_PDUS", "value": "count:LinIfTxPdu"},
415
+ {"name": "LINIF_NUMBER_OF_FRAMES", "value": "count:LinIfFrame"},
416
+ {"name": "LINIF_NUMBER_OF_SCHEDULES", "value": "count:LinIfScheduleTable"}
417
+ ],
418
+ "tables": [
419
+ {
420
+ "name": "LinIf_Frames",
421
+ "type": "LinIf_FrameType",
422
+ "qualifier": "LINIF_CONST",
423
+ "static": true,
424
+ "rows": "LinIfFrame",
425
+ "size": "LINIF_NUMBER_OF_FRAMES",
426
+ "doc": "LinIfFrame in configuration order; LinIf_EntryType.FrameIndex indexes this table",
427
+ "fields": [
428
+ {"name": "FrameId", "path": "param:LinIfFrameId", "fmt": "hex"},
429
+ {
430
+ "name": "Length",
431
+ "path": [
432
+ ["sub:LinIfPduDirection", "sub:LinIfTxPdu", "ref:LinIfTxPduRef", "param:PduLength"],
433
+ ["sub:LinIfPduDirection", "sub:LinIfRxPdu", "ref:LinIfRxPduRef", "param:PduLength"]
434
+ ]
435
+ },
436
+ {
437
+ "name": "Checksum",
438
+ "path": "param:LinIfChecksumType",
439
+ "enum": {"CLASSIC": "LINIF_CHECKSUM_CLASSIC", "ENHANCED": "LINIF_CHECKSUM_ENHANCED"}
440
+ },
441
+ {
442
+ "name": "Direction",
443
+ "path": ["sub:LinIfPduDirection", "child", "def"],
444
+ "enum": {"LinIfTxPdu": "LINIF_DIR_TX", "LinIfRxPdu": "LINIF_DIR_RX"}
445
+ },
446
+ {
447
+ "name": "PduRPduId",
448
+ "path": [
449
+ ["sub:LinIfPduDirection", "sub:LinIfTxPdu", "peer:LinIfTxPduRef@PduR/PduRDestPdu", "sym"],
450
+ ["sub:LinIfPduDirection", "sub:LinIfRxPdu", "peer:LinIfRxPduRef@PduR/PduRSrcPdu", "sym"]
451
+ ]
452
+ }
453
+ ]
454
+ },
455
+ {
456
+ "name": "LinIf_Entries_{}",
457
+ "type": "LinIf_EntryType",
458
+ "qualifier": "LINIF_CONST",
459
+ "static": true,
460
+ "per": "LinIfScheduleTable",
461
+ "rows": "LinIfEntry",
462
+ "order": "LinIfEntryIndex",
463
+ "contiguous": true,
464
+ "doc": "{} slots in LinIfEntryIndex order (ECUC_LinIf_00011: position in the schedule table)",
465
+ "fields": [
466
+ {"name": "FrameIndex", "path": ["ref:LinIfFrameRef", "index"]},
467
+ {"name": "DelayMs", "path": "param:LinIfDelay", "scale": 1000}
468
+ ]
469
+ },
470
+ {
471
+ "name": "LinIf_Schedules",
472
+ "type": "LinIf_ScheduleType",
473
+ "qualifier": "LINIF_CONST",
474
+ "static": true,
475
+ "rows": "LinIfScheduleTable",
476
+ "size": "LINIF_NUMBER_OF_SCHEDULES",
477
+ "designated": true,
478
+ "doc": "indexed by LinIfScheduleTableIndex",
479
+ "comment": "name",
480
+ "fields": [
481
+ {"name": "Entries", "path": "name", "fmt": "LinIf_Entries_{}"},
482
+ {"name": "NumEntries", "path": "count:LinIfEntry"}
483
+ ]
484
+ }
485
+ ],
486
+ "objects": [
487
+ {
488
+ "name": "LinIf_Config",
489
+ "type": "LinIf_ConfigType",
490
+ "qualifier": "LINIF_CONST",
491
+ "init": ["LinIf_Frames", "LinIf_Schedules", "LINIF_NUMBER_OF_FRAMES", "LINIF_NUMBER_OF_SCHEDULES"]
492
+ }
493
+ ]
376
494
  }
377
495
  }
378
496
  }
@@ -0,0 +1,53 @@
1
+ """LinIf: LinIf_Cfg.h / LinIf_PBcfg.c.
2
+
3
+ Def-driven (generic emitter): the LinIfGeneral switches and the TPS_ECUC_02108 / SWS_BSW_00200 symbols
4
+ LinIfConf_LinIfTxPdu_<Pdu> (LinIfTxPduId, the handle PduR passes to LinIf_Transmit) and
5
+ LinIfConf_LinIfScheduleTable_<Table> (LinIfScheduleTableIndex, used with LinIf_ScheduleRequest).
6
+
7
+ The static LinIf's own layout (frame table, one entry list per schedule table, schedule table list,
8
+ root config object) comes from its binding (bindings/*.json): channels, frames with their EcuC PDU
9
+ length and PduR handle (via the shared EcucPduCollection/Pdu), schedule entries in LinIfEntryIndex
10
+ order with LinIfDelay, schedule tables by LinIfScheduleTableIndex. Configuration the binding cannot
11
+ represent (non-unconditional frames, slave nodes, more channels than the static code has, ...) is
12
+ listed in the binding's `require` / `unsupported` and fails generation.
13
+
14
+ This module adds only the SWS_LinIf consistency rules that hold for any static code:
15
+ * a schedule entry (LinIfEntry/LinIfFrameRef) must reference a frame of the schedule table's own
16
+ channel (ECUC_LinIf_00016; schedule tables are per channel, ECUC_LinIf_00365);
17
+ * the LinIfFrameId of the unconditional frames of a channel must be unique (one publisher per
18
+ frame identifier on a LIN cluster).
19
+ """
20
+ from __future__ import annotations
21
+
22
+ from ..registry import Generator, register
23
+
24
+
25
+ def _channel(c):
26
+ while c is not None and c.def_name != "LinIfChannel":
27
+ c = c.parent
28
+ return c
29
+
30
+
31
+ @register("LinIf")
32
+ class LinIf(Generator):
33
+ std_header = "ComStack_Types.h"
34
+
35
+ def specific(self, mo):
36
+ for e in self.cg.instances(self.mc, "LinIfEntry"):
37
+ fr = self.cg.target(e.value("LinIfFrameRef"))
38
+ if fr is None:
39
+ mo.errors.append(f"{e.path}: LinIfFrameRef is not set or does not resolve to a LinIfFrame")
40
+ elif _channel(fr) is not _channel(e):
41
+ mo.errors.append(f"{e.path}: LinIfFrameRef points to {fr.path}, a frame of another channel "
42
+ f"(schedule tables are per channel, ECUC_LinIf_00365)")
43
+ for ch in self.cg.instances(self.mc, "LinIfChannel"):
44
+ seen: dict = {}
45
+ for fr in ch.sub("LinIfFrame"):
46
+ if (fr.text("LinIfFrameType") or "").strip() != "UNCONDITIONAL":
47
+ continue
48
+ fid = (fr.text("LinIfFrameId") or "").strip()
49
+ if fid in seen:
50
+ mo.errors.append(f"{fr.path}: LinIfFrameId {fid} is also used by {seen[fid]} on channel "
51
+ f"{ch.name}; unconditional frame identifiers must be unique per channel")
52
+ else:
53
+ seen[fid] = fr.name
@@ -98,10 +98,11 @@ def apply_binding(cg, mo: E.ModuleOutput, mb: dict, ext: Optional[dict] = None):
98
98
  f"{', '.join('(not configured)' if x is None else str(x) for x in req['in'])}: {req.get('why', '')}")
99
99
  for un in mb.get("unsupported", []):
100
100
  rows = cg.instances(mo.mc, un["rows"])
101
- if rows:
101
+ mx = un.get("max", 0)
102
+ if len(rows) > mx:
102
103
  mo.errors.append(f"{len(rows)} {un['rows']} container(s) configured ({', '.join(r.name for r in rows[:5])}"
103
104
  f"{', ...' if len(rows) > 5 else ''}), which the {mb['_binding']} static code cannot "
104
- f"represent: {un.get('why', '')}")
105
+ f"represent{f' (at most {mx})' if mx else ''}: {un.get('why', '')}")
105
106
  for inc in mb.get("sourceIncludes", []):
106
107
  mo.include(inc)
107
108
  macros = []
@@ -121,41 +122,9 @@ def apply_binding(cg, mo: E.ModuleOutput, mb: dict, ext: Optional[dict] = None):
121
122
  name = vs["name"].replace("{}", r.name)
122
123
  lines.append(f"{'static ' if vs.get('static') else ''}VAR({vs['type']}, {vs['qualifier']}) {name}[{size}];")
123
124
  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))
125
+ for t0 in mb.get("tables", []):
126
+ for t, rows in _table_instances(cg, mo, ev, t0, sets):
127
+ _emit_table(cg, mo, ev, t, rows, values, pending)
159
128
  objects = mb.get("objects", [])
160
129
  if objects:
161
130
  # a root config object points at the (static) tables and buffers, so they all live in its file:
@@ -173,6 +142,67 @@ def apply_binding(cg, mo: E.ModuleOutput, mb: dict, ext: Optional[dict] = None):
173
142
  mo.used_mips |= ev.used_mips
174
143
 
175
144
 
145
+ def _table_instances(cg, mo, ev, t: dict, sets: dict) -> list:
146
+ """[(table spec, rows)]: one table, or one per parent instance for a "per" table."""
147
+ from ..values import parse_int
148
+ num = lambda r: parse_int(r.text(t["order"]) or "") # noqa: E731
149
+ out = []
150
+ if t.get("per"):
151
+ specs = []
152
+ for par in cg.sorted_instances(mo.mc, t["per"]):
153
+ rows = par.sub(t["rows"])
154
+ if not rows:
155
+ raise B.BindingError(f"{par.path}: no {t['rows']} configured; {t['name'].replace('{}', par.name)} "
156
+ "would be an empty array, which C does not allow")
157
+ specs.append((dict(t, name=t["name"].replace("{}", par.name),
158
+ doc=t.get("doc", t["rows"]).replace("{}", par.name)), rows))
159
+ else:
160
+ specs = [(t, ev.rows(t["rows"], sets))]
161
+ for spec, rows in specs:
162
+ if t.get("order"):
163
+ rows = sorted(rows, key=lambda r: (num(r) is None, num(r) or 0))
164
+ if t.get("contiguous"):
165
+ got = [num(r) for r in rows]
166
+ if got != list(range(len(rows))):
167
+ raise B.BindingError(f"{spec['name']}: {t['order']} of the {len(rows)} {t['rows']} rows must be exactly "
168
+ f"0..{len(rows) - 1} (table position), got {', '.join(map(str, got))}")
169
+ out.append((spec, rows))
170
+ return out
171
+
172
+
173
+ def _emit_table(cg, mo, ev, t: dict, rows: list, values: dict, pending: list):
174
+ ev.rank = 1
175
+ lines, externs = [], []
176
+ des = t.get("designated")
177
+ if des:
178
+ _check_index(cg, mo, t, rows, des, values)
179
+ for r in rows:
180
+ vals = []
181
+ for f in t["fields"]:
182
+ v, _ = ev.field(r, f)
183
+ vals.append(v)
184
+ if f.get("extern") and v not in ("NULL_PTR", "0", "0u") and not v.startswith('"'):
185
+ m = re.search(r"[A-Za-z_]\w*", v)
186
+ if m:
187
+ ext_ = "extern " + f["extern"].replace("{}", m.group(0)) + ";"
188
+ if ext_ not in externs:
189
+ externs.append(ext_)
190
+ lead = ""
191
+ if des:
192
+ lead = f"[{des.replace('{sym}', cg.symbol(r)) if isinstance(des, str) else cg.symbol(r)}] = "
193
+ comment = t.get("comment", "name")
194
+ cm = {"name": r.name, "sym": cg.symbol(r) or r.name}.get(comment, r.name)
195
+ if t.get("scalar"):
196
+ lines.append(f" {lead}{vals[0]} /* {cm} */")
197
+ else:
198
+ lines.append(f" {lead}{{ {', '.join(vals)} }} /* {cm} */")
199
+ st = "static " if t.get("static") else ""
200
+ size = t.get("size", "")
201
+ body = (f"/* {t.get('doc', t['rows'])} */\n{st}CONST({t['type']}, {t['qualifier']}) {t['name']}[{size}] = {{\n" +
202
+ ",\n".join(lines) + "\n};")
203
+ pending.append(("table", ev.rank, body, externs))
204
+
205
+
176
206
  def _check_index(cg, mo, t: dict, rows: list, des, values: dict):
177
207
  """A designated table is indexed by handle (minus an offset): every row must land in [0, size) exactly once,
178
208
  or the static code's table lookup would be wrong. Reported as an error, never renumbered here."""
@@ -37,12 +37,30 @@ ComUserCallback of type COM_RX_ACK / COM_TX_ACK; `call:comUserCallback`), or, fo
37
37
  callbacks, the RTE's `Rte_COMCbk_<sn>()` / `Rte_COMCbkTAck_<sn>()` when the RTE's Rte_Cbk.h declares it
38
38
  (`call:rteSignalCallback`). Nothing is bound from guesses: no declaration, no callback.
39
39
 
40
+ ## LinIf
41
+ `LinIf_Cfg.h`: LinIfGeneral switches, `LinIfConf_LinIfTxPdu_*` (LinIfTxPduId, the handle PduR passes to `LinIf_Transmit`) and
42
+ `LinIfConf_LinIfScheduleTable_*` (LinIfScheduleTableIndex) symbols (TPS_ECUC_02108 / SWS_BSW_00200), `LINIF_NUMBER_OF_*`.
43
+ `LinIf_PBcfg.c` (binding): frame table in configuration order (LinIfFrameId, length from the EcuC Pdu, checksum, direction
44
+ from the LinIfPduDirection choice, PduR dest (Tx) / src (Rx) handle via the shared EcuC Pdu), one entry list per schedule
45
+ table (`per` table, LinIfEntryIndex order, which must be 0..n-1; FrameIndex = position of LinIfFrameRef's frame;
46
+ LinIfDelay s -> ms, a non-whole ms is an error), schedule list indexed by LinIfScheduleTableIndex, root `LinIf_Config`.
47
+ Tx PDU handles are laid out 0..n-1 (handleLayout; `LinIf_Transmit` range-checks them).
48
+ Errors, never dropped: frame types other than UNCONDITIONAL, PDU directions other than Tx/Rx, RUN_ONCE schedules, more
49
+ than one channel, slave nodes, substitution frames, fixed frame SDUs (stand-in binding); an entry whose frame belongs to
50
+ another channel, duplicate unconditional LinIfFrameIds on a channel (any binding).
51
+
52
+ Binding path/table additions used by LinIf: `child` (chosen sub-container of a choice), `def` (its def name, mapped with
53
+ `enum`), `count:<Def>`, field `scale`, table `per` / `contiguous`, `unsupported[].max`.
54
+
40
55
  ## Known differences from the sample/large fixture oracle (all explained)
41
56
  | Item | Oracle | Ours | Why |
42
57
  |---|---|---|---|
43
58
  | `NvM_BlockDescriptorTable[*].RamBlockDataAddress` | `X` | `&X` (as configured) | ECUC_NvM_00482 value is the address expression; kept verbatim, same address for arrays |
44
59
  | Rx I-PDU `TimeoutTicks` with mixed signal timeouts | largest ComTimeout | smallest non-zero | SWS_Com_00291 |
45
60
  | `<MIP>_CFG_*` version macros, generic ComGeneral/NvMCommon/... switches, Dem/Fee tables | absent | extra | def-driven output the oracle does not emit |
61
+ | LinIf entry list name | `LinIf_Sched_<Table>` | `LinIf_Entries_<Table>` | `static` table, internal linkage: not part of the LinIf.h contract |
62
+ | `LinSMConf_LinSMSchedule_*` in `LinIf_Cfg.h` | present | absent (in `LinSM_Cfg.h`) | a `LinSMConf_` symbol belongs to the LinSM config header (SWS_BSW_00200) |
63
+ | `LINIF_NUMBER_OF_SCHEDULES`, LinIfGeneral switches, version macros | absent | extra | def/binding-driven output the oracle does not emit |
46
64
  | SchM main function names | `Com_MainFunctionTx` | same | SWS_Com says `Com_MainFunctionTx_<shortName>`; the instance named like its def keeps the SIP's legacy name |
47
65
 
48
66
  ## Reading a diff