1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
|
"""libos port receive-path model (the B5 message-loop blocker, first half).
Status: **offline development tool (new, additive)**. This module models the
part of the GSP firmware's libos *port receive* machinery that the B3
message-loop task needs before it can make progress: the argument contract of
``TASK_PORT_SEND_RECV_WAIT`` (syscall 1), the asynchronous receive post, the
synchronous send/reply, the cross-payload handoff performed when a receive
completes, and the external-interrupt port retirement that a host doorbell wake
performs.
Evidence kind: **firmware use + RTOS port implementation** (the same evidence
class as :mod:`sim.libos`). Every structure below was observed on the real
570.144 image, not inferred from a stub:
* the message-loop task posts four asynchronous receives through a wrapper at
firmware vaddr ``0x4018000`` that moves its arguments ``a0-a3`` into
``a4-a7``, zeroes ``a0-a3`` and ``t1``/``t2``, and executes ``ecall`` with
``t0 = 1`` (an async receive post):
``(shuttle 10, port 6, payload 0, size 0)`` immediately followed by
``INTERRUPT_ARM_EXTERNAL(0xFF)`` -- the external-interrupt receive;
``(shuttle 11, port 7, payload 0x5ba8e10, size 4)``;
``(shuttle 9, port 8, payload 0x4015070, size 0x808)``;
``(shuttle 14, port 13, payload 0x5ba8e20, size 0x24)``;
* it then performs one synchronous send on the default shuttle id (1) to port
12 with a zero-length payload, ``waitId = 1``, ``timeout = -1`` (infinite);
* it blocks in ``PortWait(LIBOS_SHUTTLE_ANY, timeout = -1)`` (ecall at
``0x4019df4``);
* on an external interrupt the port that was armed retires with
``error_code = LIBOS_OK`` and ``payload_size = 0`` (the report path), and the
wait returns with ``a0 = status``, ``a4 = completedSize``,
``a5 = completedRemoteTaskId``, ``a6 = completedShuttle``.
The argument contract modelled (mirrors the RTOS ``libosPortSendRecvAndWait``):
=============== =========================================================
register meaning at ``ecall``
=============== =========================================================
``t0`` syscall number (1)
``a0`` send shuttle id (0 = none)
``a1`` send port id
``a2``/``a3`` send payload address / size
``a4`` receive shuttle id (0 = none)
``a5`` receive port id
``a6``/``a7`` receive payload address / capacity
``t1`` wait id (0 = do not block, ``0xFFFF`` = any)
``t2`` timeout in ns (``0xFFFFFFFFFFFFFFFF`` = infinite)
=============== =========================================================
On return: ``a0`` = status, ``a4`` = completed size, ``a5`` = originating task
id, ``a6`` = completed shuttle (local handle).
Honesty rules (mandatory; docs/51 section 6, docs/06-ACCURACY.md):
* the model is **data-driven by observation**: port roles come from the posted
receive sequence of the real image; nothing infers a port number that was
not observed;
* every modelled event carries ``"modelled": true`` and a label -- the host
doorbell wake is ``shim:host-doorbell-wake``, the boot handshake send is
``modelled:peer-consumes-on-send``. These are not firmware behaviour;
* **receives are never completed with invented payloads**: a wake only retires
the external-interrupt receive with the RTOS report contract (rare, zero
length). No command, status or event payload is fabricated;
* wait semantics are strictly "return the first completed shuttle or stop";
the model never returns from a blocking wait that nothing completed;
* L0/L1 only: no timing (L2) and no device-environment (L3) claim; with a
single modelled task there is no scheduler and no other task is run.
Development-only, offline: no GPU, kernel module, mdev, guest, network, QEMU,
``/dev`` access or reboot. Nothing here is a product dependency and nothing
is injected into a guest.
"""
from __future__ import annotations
from sim.libos import (
INTERRUPT_ARM_EXTERNAL,
NAMES,
TASK_PORT_SEND_RECV_WAIT,
)
#: Report schema tag for :meth:`LibosPortModel.state`.
REPORT_FORMAT = "greeneye-msgq-port/1"
#: ``LIBOS_SHUTTLE_ANY`` (RTOS port API).
SHUTTLE_ANY = 0xFFFF
#: The task's default shuttle id (RTOS port API).
SHUTTLE_DEFAULT = 1
#: ``LIBOS_TIMEOUT_INFINITE``.
TIMEOUT_INFINITE = 0xFFFFFFFFFFFFFFFF
#: LIBOS_OK.
LIBOS_OK = 0
#: Observed port roles for the real 570.144 message-loop task. The values are
#: the port ids carried by the task's posted receives (resource handles); the
#: labels describe behaviour only.
PORT_ROLES = {
6: "extintr", # external-interrupt receive (armed with 0xFF)
7: "notification", # 4-byte notification buffer
8: "request", # 0x808-byte request/reply buffer
12: "handshake", # zero-length boot handshake send target
13: "event", # 36-byte event buffer
}
#: Port label used when a port was not part of the observed post sequence.
ROLE_DATA = "data"
#: The wake contract observed for the external-interrupt receive: the RTOS
#: report retires the waiting shuttle with error LIBOS_OK and a zero payload.
EXTINTR_WAKE_CONTRACT = "shim:host-doorbell-wake"
HANDSHAKE_CONTRACT = "modelled:peer-consumes-on-send"
def role_for(port: int) -> str:
"""Role label for a port id (observed table; ``"data"`` otherwise)."""
return PORT_ROLES.get(int(port), ROLE_DATA)
class LibosPortModel:
"""Deterministic model of the libos receive path for one GSP task.
The model is a ``Machine.syscall_handler``: it owns syscall 1
(``TASK_PORT_SEND_RECV_WAIT``) and the interrupt-arm call, and delegates
every other syscall to ``base`` (:class:`sim.libos.LibosSyscalls`, which
keeps recording). ``machine.x`` is the only machine state touched.
"""
name = "libos-port-model"
def __init__(self, base=None, *, task: str = "gsp-rm-task"):
self.base = base
self.task = task
#: Async receive posts in observation order.
self.posts: list[dict] = []
#: Pending receives by shuttle id.
self.pending: dict[int, dict] = {}
#: Completed handoffs awaiting a wait (FIFO).
self.completed: list[dict] = []
#: Synchronous sends observed (send payloads are not copied; the peer
#: side is not modelled).
self.sends: list[dict] = []
#: Blocking waits observed.
self.waits: list[dict] = []
#: Labelled model events (every one is explicitly ``modelled``).
self.events: list[dict] = []
#: External interrupt mask last armed (RTOS ``IRQMSET`` semantics).
#: An installed base model may already have observed an arm before this
#: model was attached (the recorded boot arm), so seed from it.
self.armed_mask = int(getattr(base, "armed_mask", 0))
#: Recorded host doorbell wakes.
self.host_kicks = 0
#: Host wakes not yet handed to a receive (the firmware may post and
#: arm after the host has rung the doorbell; the pending interrupt is
#: delivered at the first armed receive).
self.kicks_pending = 0
#: The request that stopped the run (unmodelled or blocking), if any.
self.stopped: dict | None = None
# -- observation -------------------------------------------------------
def _event(self, name: str, **fields) -> dict:
event = {"event": name, "modelled": True, "task": self.task}
event.update(fields)
self.events.append(event)
return event
def host_kick(self, *, label=EXTINTR_WAKE_CONTRACT, queue=None, value=None,
bit=None) -> dict:
"""Record the host doorbell write; deliver it when a receive is armed.
The host submit path writes ``NV_PGSP_QUEUE_HEAD(0)``; the mapping from
that write to a falcon interrupt bit is **not modelled** (unobserved).
The wake is delivered to the external-interrupt receive once it has
been posted and interrupts are armed (the firmware may do both after
the host has already rung). Returns the event; ``"delivered"`` is the
retired shuttle id, or ``None`` when the wake remains pending.
"""
self.host_kicks += 1
self.kicks_pending += 1
delivered = self._try_kick()
return self._event(
"host_kick",
contract=label,
queue=queue,
value=value,
falcon_irq_bit=bit,
delivered=delivered,
pending=bool(self.kicks_pending),
note=("queue-head write -> falcon interrupt bit is unobserved; "
"the wake is the documented shim"),
)
def _try_kick(self):
"""Deliver one pending host wake to an armed extintr receive, if any."""
if not self.kicks_pending or not self.armed_mask:
return None
for shuttle in sorted(self.pending):
post = self.pending[shuttle]
if post["role"] != "extintr":
continue
self.kicks_pending -= 1
self._complete(
shuttle,
payload_size=0,
origin_task=0,
contract=EXTINTR_WAKE_CONTRACT,
reason="external-interrupt report (armed mask "
f"{self.armed_mask:#x}, pending host doorbell wake)",
)
return shuttle
return None
def _complete(self, shuttle: int, *, payload_size: int, origin_task: int,
contract: str, reason: str) -> dict:
post = self.pending.pop(int(shuttle))
entry = {
"shuttle": int(shuttle),
"port": int(post["port"]),
"role": post["role"],
"payload": int(post["payload"]),
"capacity": int(post["capacity"]),
"payload_size": int(payload_size),
"origin_task": int(origin_task),
"contract": contract,
"reason": reason,
"post_pc": int(post["pc"]),
}
self.completed.append(entry)
self._event("receive_complete", **entry)
return entry
# -- syscall surface ---------------------------------------------------
def handle(self, machine, pc: int = 0):
"""``Machine`` syscall hook: ``(handled, result)``."""
num = int(machine.x[5])
if num == TASK_PORT_SEND_RECV_WAIT:
return self._handle_port(machine, pc)
if num == INTERRUPT_ARM_EXTERNAL:
self.armed_mask |= int(machine.x[10])
self._event("interrupt_arm_external", mask=int(machine.x[10]), pc=pc)
self._try_kick()
if self.base is not None:
handled, result = self.base.handle(machine, pc)
else:
handled, result = (True, LIBOS_OK) if num == INTERRUPT_ARM_EXTERNAL \
else (False, None)
if not handled and self.stopped is None:
entry = getattr(self.base, "stopped", None)
number = int(entry["num"]) if entry else num
self.stopped = {
"num": number,
"name": (entry or {}).get("name", NAMES.get(number, f"unknown_{number}")),
"pc": int(pc),
"reason": "unmodelled-syscall",
}
return handled, result
def _handle_port(self, machine, pc: int):
a0, a1, a2, a3 = (int(machine.x[10]), int(machine.x[11]),
int(machine.x[12]), int(machine.x[13]))
a4, a5, a6, a7 = (int(machine.x[14]), int(machine.x[15]),
int(machine.x[16]), int(machine.x[17]))
t1, t2 = int(machine.x[6]), int(machine.x[7])
send = a0 != 0 or a1 != 0
recv = a4 != 0 or a5 != 0
if recv:
self._post(shuttle=a4, port=a5, payload=a6, capacity=a7, pc=pc,
wait_id=t1, timeout=t2)
if send:
self._send(shuttle=a0, port=a1, payload=a2, size=a3, wait_id=t1,
timeout=t2, pc=pc, machine=machine)
return True, LIBOS_OK
if recv:
# Async receive post: the kernel returns LIBOS_OK without touching
# the wait outputs (a4-a6 still hold the receive arguments).
return True, LIBOS_OK
# Pure blocking wait: return the first completed shuttle, or stop.
if not self.completed:
self._try_kick()
if self.completed:
entry = self.completed.pop(0)
machine.x[14] = entry["payload_size"] & 0xFFFFFFFFFFFFFFFF
machine.x[15] = entry["origin_task"] & 0xFFFFFFFFFFFFFFFF
machine.x[16] = entry["shuttle"] & 0xFFFFFFFFFFFFFFFF
machine.x[17] = 0
record = {
"pc": int(pc),
"wait_id": t1,
"timeout": t2,
"completed": entry["shuttle"],
"payload_size": entry["payload_size"],
"origin_task": entry["origin_task"],
"role": entry["role"],
"contract": entry["contract"],
}
self.waits.append(record)
self._event("wait_complete", **record)
return True, LIBOS_OK
record = {
"pc": int(pc),
"wait_id": t1,
"timeout": t2,
"reason": "port-wait-blocks",
"pending": sorted(self.pending),
}
self.waits.append(record)
self._event("wait_block", **record)
if self.stopped is None:
self.stopped = {
"num": TASK_PORT_SEND_RECV_WAIT,
"name": "task_port_send_recv_wait",
"pc": int(pc),
"reason": "port-wait-blocks",
"pending": sorted(self.pending),
}
return False, None
def _post(self, *, shuttle: int, port: int, payload: int, capacity: int,
pc: int, wait_id: int, timeout: int) -> dict:
post = {
"shuttle": int(shuttle),
"port": int(port),
"role": role_for(port),
"payload": int(payload),
"capacity": int(capacity),
"pc": int(pc),
"wait_id": int(wait_id),
"timeout": int(timeout),
}
self.posts.append(post)
self.pending[int(shuttle)] = post
self._event("receive_post", **post)
self._try_kick()
return post
def _send(self, *, shuttle: int, port: int, payload: int, size: int,
wait_id: int, timeout: int, pc: int, machine) -> dict:
"""Model a synchronous send/reply as completed by its peer.
Only the boot handshake shape was observed (zero-length payload to port
12 on the default shuttle). The peer is not modelled: consumption is
an explicit, labelled assumption. The wait outputs receive the RTOS
transcribe contract for a completed send (``a4`` = copied size,
``a5`` = 0, ``a6`` = send shuttle).
"""
record = {
"shuttle": int(shuttle),
"port": int(port),
"role": role_for(port) if port else "reply",
"payload": int(payload),
"size": int(size),
"pc": int(pc),
"wait_id": int(wait_id),
"timeout": int(timeout),
"contract": HANDSHAKE_CONTRACT,
}
self.sends.append(record)
machine.x[14] = int(size) & 0xFFFFFFFFFFFFFFFF
machine.x[15] = 0
machine.x[16] = int(shuttle) & 0xFFFFFFFFFFFFFFFF
machine.x[17] = 0
self._event("send_complete", **record)
return record
# -- report surface ----------------------------------------------------
def state(self) -> dict:
"""Deterministic serialisable state for the ``greeneye-msgq-attach/1``
report (no host timestamps)."""
return {
"format": REPORT_FORMAT,
"task": self.task,
"armed_mask": int(self.armed_mask),
"host_kicks": int(self.host_kicks),
"kicks_pending": int(self.kicks_pending),
"posts": [dict(post) for post in self.posts],
"pending": sorted(self.pending),
"completed": [dict(entry) for entry in self.completed],
"sends": [dict(record) for record in self.sends],
"waits": [dict(record) for record in self.waits],
"events": [dict(event) for event in self.events],
"stopped": dict(self.stopped) if self.stopped else None,
}
|