fix(rx): release descriptors individually instead of flipping the whole ring

The receive loop re-armed all 16 descriptors into the other slot after every
completed frame.  During a burst that re-pointed descriptors the chip had
ALREADY filled but the consumer had not read, so those frames were lost without
a trace: the LANCE DMA log shows all 23 dictionary datagrams written by the
chip, 1.132 ms apart (the MTU/bandwidth poll cadence), while the payload's own
counters accounted for 4 (DICT_BYTES=1448, dict_bad=3).  Nothing was dropped by
MAME; the ring was being rewritten under the frames it held.

rx_switch()/rx_armed are gone.  A descriptor is held (rx_hold) while its
datagram belongs to the frame being assembled, and rx_release() hands each one
back -- into the other slot -- only once that frame has been parsed or
abandoned.  Nothing the chip delivered is ever re-pointed unread.

Measured, same zero-gap 23-datagram dictionary upload:
  before                      dgram=4   DICT_BYTES=1448   dict_bad=3
  release fix, 16 descriptors dgram=18  DICT_BYTES=24616  dict_bad=0
  plus a 32-descriptor ring   dgram=23  DICT_BYTES=32768  dict_bad=0

The ring is now 32 descriptors because 23 datagrams is the burst the protocol
actually defines (a whole 32 KB dictionary at MAX_BODY 1448), and 16 could not
hold it while the consumer copied.  SLOT_BYTES grows to 49152 to hold the ring;
POOL_BYTES does NOT grow -- the 160 KB "framebuffer reserve" was unused slack
(the decoder paints straight from the fragments), so the extra 24,576 B per slot
comes out of it and the LANCE block keeps its fixed 0x3C000 tail.

RX_OK is now published after rx_release(), not inside the dispatch.  It is the
"frame done" signal a reader polls, and it used to become visible while
descriptors were still being rewritten: ring mode caught a descriptor holding
slot B's high byte with slot A's low word.  A reader that sees RX_OK move now
sees a painted picture and a consistent ring.

vidstream.lua's ring mode follows: it asserts the invariant that replaced the
flip -- the consumed descriptors move to the other slot, the untouched ones stay
-- instead of "all 16 in slot B".

Evidence: 146 unit tests pass; modes rx, draw, ring, tiles, delta and replay all
PASS with their pixel checkers (draw 1024/1024 rows, tiles and replay via
tile_check).  End to end over rdebug with the STOCK sender (no pacing): the
dictionary burst completes (DICT_READY=1, DICT_BYTES=32768, dict_bad=0) and
RX_OK=100 frames paint, TILES_DROP=0.
This commit is contained in:
arti committed 2026-10-31 19:45:34 +02:00
1 parent 5af949b338
commit 9af7b4fb8d
3 files changed
+172 -71

No files matched your search

+43 -10
View File
@@ -254,11 +254,11 @@ local DICT_BAD = MARK_PAGE + 0x58 * 8
local TILES_DROP = MARK_PAGE + 0x5c * 8
-- Must match vidstream.c.
local SLOT_BYTES = 24576
local SLOT_BYTES = 49152
-- The RX ring (Task 7), from vidstream.c: RX_DESC descriptors of 8 bytes each,
-- one RXBUFLEN-byte buffer per descriptor inside the armed slot.
local RX_DESC = 16
local RX_DESC = 32
local RXBUFLEN = 1526
local ETH_HDR = 14
local ETH_FCS = 4
@@ -1045,6 +1045,32 @@ local function ring_armed_for(base, what)
what, want, RXBUFLEN, badown, badaddr, badlen, badstride))
end
-- How many descriptors point into slot A, slot B, and neither?
-- The payload now releases descriptors one at a time as their data is consumed
-- (rx_release in vidstream.c), so after a frame the consumed ones have moved to
-- the other slot while a descriptor the consumer has not reached yet stays
-- where it was. Asserting "all 16 in slot B" was the old all-at-once flip, and
-- that flip is what silently discarded frames the chip had already delivered.
local function ring_slot_counts(a_base, b_base)
local want_a, want_b = a_base // 8, b_base // 8
local i, n_a, n_b, bad = 0, 0, 0, 0
local in_a, in_b, in_bad = {}, {}, {}
for i = 0, RX_DESC - 1 do
local addr = buf_addr(ring_ringb, i)
if addr == want_a + i * RXBUFLEN then
n_a = n_a + 1; in_a[#in_a + 1] = i
elseif addr == want_b + i * RXBUFLEN then
n_b = n_b + 1; in_b[#in_b + 1] = i
else
bad = bad + 1; in_bad = in_bad or {}
in_bad[#in_bad + 1] = string.format("%d:%06X", i, addr)
end
end
return n_a, n_b, bad, table.concat(in_a, ","), table.concat(in_b, ","),
table.concat(in_bad, ",")
end
-- The 32 bytes at the start of screen row 0. Enough to see a blit: the frame
-- ring mode delivers paints row 0 with 0xF0 bytes, so any paint changes them.
local function read_vram_head(n)
@@ -1186,9 +1212,12 @@ local function ring_step()
ring_check(prog:read_u32(RX_RING_ACC) == 0,
"the frame left no bytes in progress")
ring_check(ring_dgrams >= 15, string.format("the consumer took all 15 datagrams (%d)", ring_dgrams))
ring_check(prog:read_u32(RX_RING_ARMED) == 1,
"the completion moved the write pointer to slot B")
ring_armed_for(b, "slot B after the frame")
local n_a, n_b, bad, ia, ib, ibad = ring_slot_counts(a, b)
ring_check(n_b == 15 and n_a == RX_DESC - 15 and bad == 0,
string.format("the 15 consumed descriptors moved to slot B and the %d untouched ones kept slot A (A=%d[%s] B=%d[%s] bad=%d[%s])", RX_DESC - 15,
n_a, ia, n_b, ib, bad, ibad))
ring_check(prog:read_u32(RX_RING_ARMED) == 0,
"the next descriptor is still the one in slot A")
ring_vram0 = read_vram_head(32)
-- Now the impossible frame, into the slot the ring is armed on (B).
local big, blen = oversize_frame()
@@ -1214,9 +1243,12 @@ local function ring_step()
local after = read_vram_head(32)
ring_check(ring_vram0 ~= nil and after == ring_vram0,
"the screen did not change: the oversize frame was not blitted")
local r_a, r_b, r_bad = ring_slot_counts(a, b)
ring_check(r_b == 16 and r_a == RX_DESC - 16 and r_bad == 0,
string.format("the rejected descriptor moved to slot B with the consumed ones (A=%d B=%d bad=%d)",
r_a, r_b, r_bad))
ring_check(prog:read_u32(RX_RING_ARMED) == 0,
"the rejection re-armed the ring on slot A")
ring_armed_for(a, "slot A after the rejection")
"the next descriptor is still the untouched one in slot A")
ring_check(prog:read_u32(RX_RING_DROPS) == 0,
"no frame was dropped for want of descriptors")
-- Phase 3: one datagram the chip flagged bad (FCS). The chip gives
@@ -1254,9 +1286,10 @@ local function ring_step()
"the FCS error abandoned its frame: nothing is left in progress")
ring_check(prog:read_u32(RX_OK) == 1,
"RX_OK did not move: the errored datagram painted nothing")
ring_check(prog:read_u32(RX_RING_ARMED) == 1,
"the FCS error re-armed the ring on slot B")
ring_armed_for(b, "slot B after the FCS error")
local f_a, f_b, f_bad = ring_slot_counts(a, b)
ring_check(f_b == 17 and f_a == RX_DESC - 17 and f_bad == 0,
string.format("the errored datagram's descriptor was released too (A=%d B=%d bad=%d)",
f_a, f_b, f_bad))
local after = read_vram_head(32)
ring_check(ring_vram0 ~= nil and after == ring_vram0,
"the screen did not change: the errored datagram was not blitted")
+2 -2
View File
@@ -9,8 +9,8 @@
# run -- see harness/c-hello/README.md).
#
# POOL_BYTES is compile-time: the script writes poolcfg.h from
# STREAM_POOL_BYTES (default 249856 = 244 KB: two 24 KB slots + 160 KB
# framebuffer + 32 KB dictionary + 4 KB slack).
# STREAM_POOL_BYTES (default 249856 = 244 KB: two 48 KB slots, 32 KB
# dictionary, and the LANCE block's fixed 4 KB tail).
#
# Outputs: build/vidstream.bin (raw .text, what vidstream.lua loads),
# build/vidstream.coff, vidstream.map, vidstream.lst, entry.lst,
+127 -59
View File
@@ -24,10 +24,11 @@
*
* Pool layout inside the single allocation (POOL_BYTES = 249856 = 244 KB):
*
* +0x00000 frame slot A 24576 bytes (16 x 1526 B RX buffers)
* +0x06000 frame slot B 24576 bytes (16 x 1526 B RX buffers)
* +0x0C000 dictionary 32768 bytes (256 x 128 B, Task 3)
* +0x14000 framebuffer 163840 bytes (scheme A reserve; see below)
* +0x00000 frame slot A 49152 bytes (32 x 1526 B RX buffers)
* +x0C000 frame slot B 49152 bytes (32 x 1526 B RX buffers)
* +0x18000 dictionary 32768 bytes (256 x 128 B, Task 3)
* +0x20000 slack 114688 bytes (unused; the LANCE block's
* fixed 0x3C000 tail follows)
* +0x3C000 LANCE block 4096 bytes (init block, TX ring, status
* frame, RX ring)
*
@@ -35,8 +36,10 @@
* plan fixes and plan 2's Task 4 consumes. The 160 KB framebuffer reserve
* follows it: this decoder paints straight from the RX fragments (there is no
* per-frame CPU copy, spec 8.1) and never touches that region, so it is slack
* today, but the layout adds up to exactly POOL_BYTES -- 24576 + 24576 + 32768
* + 163840 + 4096 = 249856. dict_slack_check below pins the part Task 3
* today, but the layout adds up to exactly POOL_BYTES -- 49152 + 49152 + 32768
* + 114688 + 4096 = 249856. The pool size did NOT have to grow for the deeper
* ring: the 160 KB framebuffer reserve was unused slack, and 114,688 B of it
* is still free between the dictionary and the LANCE block. dict_slack_check below pins the part Task 3
* depends on (the slot pair and the 32 KB dictionary); the LANCE block's
* offsets at 0x3C000 were fixed before it and are not re-checked here. Had
* the dictionary gone after the framebuffer, the plan's base would not be
@@ -46,8 +49,8 @@
* (lance_start() explains why the offset alone is not enough), and the status
* frame sits right after the 8 TX descriptors, the RX ring after that.
*
* A slot is 16 packet buffers of RXBUFLEN = 1526 bytes (16 x 1526 = 24416, so
* it fits 24576 with 160 bytes to spare). The chip writes ONE Ethernet frame
* A slot is RX_DESC packet buffers of RXBUFLEN = 1526 bytes (32 x 1526 =
* 48832, so it fits 49152 with 320 bytes to spare). The chip writes ONE Ethernet frame
* per buffer -- 14-byte header, the VS frame's chunk, and the FCS it appends --
* so a VS frame arrives as a series of datagrams in ring order, and the frame's
* bytes are NOT contiguous in the slot: they are 14 bytes into each buffer,
@@ -152,13 +155,19 @@ static unsigned int decode_worst; /* worst since the last status sent */
#define SENT_VALUE 0xA5C3
/*
* The two frame slots. SLOT_BYTES is the plan's 24 KB: a 128-row strip frame
* (128 x 160 B + 16 B header = 20,496 B = 15 packets) fits, and so does any
* smaller strip. Slot addresses are bit addresses, because that is what a
* The two frame slots. SLOT_BYTES holds a whole RX ring: 32 descriptors x
* RXBUFLEN (1526) = 48,832 B, rounded up. It also still holds a 128-row strip
* frame (128 x 160 B + 16 B header = 20,496 B = 15 packets).
*
* 32, not 16, because 16 could not absorb the tile dictionary: it arrives as
* 23 back-to-back datagrams (one TILES_DICT_CHUNK each), and with a 16-deep
* ring the chip dropped the tail while the consumer was still copying -- the
* measured zero-gap upload stalled at 24,616 of 32,768 B. The heap has
* megabytes free; the ring, not the memory, was the limit. Slot addresses are bit addresses, because that is what a
* pointer holds in this toolchain (see harness/c-hello/README.md: "pointers
* are bit addresses").
*/
#define SLOT_BYTES 24576
#define SLOT_BYTES 49152
/*
* Plan 2's Task 3: the tile dictionary.
@@ -270,9 +279,16 @@ char tile_grid_check[(TILES_SCREEN == FP_TILES_SCREEN) ? 1 : -1];
* 1514-byte Ethernet frame plus margin, the figure lance_cmd.c uses on the
* unit: a datagram from vidsrc.py is 14 + 1450 = 1464 bytes, the chip appends
* the 4-byte FCS, writes it after the frame and reports the total in RMD3.
* RX_DESC * RXBUFLEN = 24416 bytes, which fits a 24576-byte slot.
* RX_DESC * RXBUFLEN = 48832 bytes, which fits a 49152-byte slot.
*
* The depth is the burst size the protocol actually uses: a whole 32 KB
* dictionary upload is 23 datagrams (MAX_BODY 1448), so 32 descriptors let the
* chip take the entire upload before the consumer has copied the first chunk.
* It is not a substitute for the sender pacing -- any ring can be outrun by a
* longer burst -- but it removes the accidental failure of the one burst the
* protocol defines.
*/
#define RX_DESC 16
#define RX_DESC 32
#define RXBUFLEN 1526
#define RMD1_OWN 0x8000 /* the chip owns the descriptor */
@@ -352,9 +368,10 @@ static unsigned int drops_total; /* CSR0 MISS windows, lower bound on misses */
/*
* Task 7 receive state.
*
* rx_armed the slot the ring's 16 descriptors point into (0 = A, 1 = B).
* Only ever written together with a full re-arm, so a descriptor
* never points at a slot the payload is not expecting.
* rx_slot_of[] which slot each descriptor points into (0 = A, 1 = B).
* Per descriptor, so descriptors can be recycled one at a time
* and nothing the chip has already delivered is re-pointed
* unread (see rx_release()).
* rx_pos the next descriptor the consumer expects. The chip fills
* descriptors strictly in ring order (am79c90.cpp's
* recv_complete_cb advances m_rx_ring_pos by one per packet and
@@ -379,10 +396,21 @@ static unsigned int drops_total; /* CSR0 MISS windows, lower bound on misses */
* for them -- so without this cell a body-corrupting packet would be invisible
* to both crc_err and drops. Each one abandons the frame it belongs to.
*/
static unsigned int rx_armed;
static unsigned int rx_pos;
static unsigned int rx_acc;
static unsigned int rx_nfrag;
/* Which slot each descriptor currently points into. Per descriptor, not one
* global "armed" slot: a burst is handled by releasing descriptors one at a
* time as their data is consumed, so a descriptor the chip has already filled
* is never re-pointed behind the consumer's back. */
static unsigned int rx_slot_of[RX_DESC];
/* Descriptors consumed for the frame being assembled -- their data must stay
* put until the frame is parsed or abandoned, so they are released together
* at the end. */
static unsigned int rx_hold[RX_DESC];
static unsigned int rx_nhold;
/* The slot the first fragment of the current frame landed in (RX_SLOT). */
static unsigned int rx_frame_slot;
static unsigned int rx_frag_off[RX_DESC];
static unsigned int rx_frag_ptr[RX_DESC];
static unsigned int rx_frag_len[RX_DESC];
@@ -1058,34 +1086,54 @@ static unsigned int slot_base[2];
* descriptor completed by anything but the chip reads as a zero-length packet
* rather than as the previous frame's length.
*/
static void rx_arm_one(i, slot)
unsigned int i, slot;
{
unsigned int cb;
cb = (slot_base[slot] >> 3) + i * RXBUFLEN;
/* OWN clear, with the new high byte: a fetch here is a MISS. */
w16(lance_rxring + i * 64 + 16, (cb >> 16) & 0xFF);
w16(lance_rxring + i * 64 + 0, cb & 0xFFFF);
w16(lance_rxring + i * 64 + 32, (0x10000 - RXBUFLEN) & 0xFFFF);
w16(lance_rxring + i * 64 + 48, 0);
/* Ownership last, over a complete descriptor. */
w16(lance_rxring + i * 64 + 16, RMD1_OWN | ((cb >> 16) & 0xFF));
rx_slot_of[i] = slot;
}
static void rx_arm(slot)
unsigned int slot;
{
unsigned int i, cb, base;
unsigned int i;
base = slot_base[slot];
for (i = 0; i < RX_DESC; i++) {
cb = (base >> 3) + i * RXBUFLEN;
/* OWN clear, with the new high byte: a fetch here is a MISS. */
w16(lance_rxring + i * 64 + 16, (cb >> 16) & 0xFF);
w16(lance_rxring + i * 64 + 0, cb & 0xFFFF);
w16(lance_rxring + i * 64 + 32, (0x10000 - RXBUFLEN) & 0xFFFF);
w16(lance_rxring + i * 64 + 48, 0);
/* Ownership last, over a complete descriptor. */
w16(lance_rxring + i * 64 + 16,
RMD1_OWN | ((cb >> 16) & 0xFF));
rx_arm_one(i, slot);
}
}
/*
* rx_switch() -- arm the other slot. Called at the end of a frame, before the
* picture is drawn, so the chip can fill N+1 while we paint N and can never
* write into the slot being read (spec 8.1's overlap).
* rx_release() -- give the frame's descriptors back, pointing each at the OTHER
* slot (spec 8.1's overlap: the buffer the chip next writes must not be one the
* consumer is still reading).
*
* This replaces a per-frame "switch slots and re-arm all 16". That flip was
* the burst bug: an all-16 re-arm re-points descriptors the chip had ALREADY
* completed but the consumer had not yet read, so their frames were silently
* lost -- measured on a 23-datagram dictionary upload: the chip delivered all
* 23 (LANCE DMA log, 1.13 ms apart) and only 4 reached these counters. Holding
* and releasing each descriptor individually means nothing the chip delivered
* is ever re-pointed unread, and the ring recycles as fast as the consumer.
*/
static void rx_switch()
static void rx_release()
{
rx_armed = 1 - rx_armed;
rx_arm(rx_armed);
unsigned int k, i;
for (k = 0; k < rx_nhold; k++) {
i = rx_hold[k];
rx_arm_one(i, 1 - rx_slot_of[i]);
}
rx_nhold = 0;
}
/*
@@ -1108,11 +1156,14 @@ static void rx_dgram(i)
unsigned int i;
{
unsigned int rmd1, mlen, len, src, plen, total, lim, slot;
unsigned int accepted, refused;
fp_header h;
accepted = 0;
refused = 0;
rmd1 = r16(lance_rxring + i * 64 + 16);
mlen = r16(lance_rxring + i * 64 + 48) & 0x0FFF;
src = slot_base[rx_armed] + i * RXBUFLEN * 8 + ETH_HDR_LEN * 8;
src = slot_base[rx_slot_of[i]] + i * RXBUFLEN * 8 + ETH_HDR_LEN * 8;
len = 0;
if (rmd1 & RMD1_ERR) {
/*
@@ -1145,7 +1196,7 @@ unsigned int i;
*(unsigned int *)RX_FCS_DROP + 1;
rx_acc = 0;
rx_nfrag = 0;
rx_switch();
rx_release();
}
return;
}
@@ -1169,6 +1220,9 @@ unsigned int i;
}
}
if (rx_nfrag == 0) {
rx_frame_slot = rx_slot_of[i]; /* what RX_SLOT reports */
}
rx_frag_off[rx_nfrag] = rx_acc;
rx_frag_ptr[rx_nfrag] = src;
rx_frag_len[rx_nfrag] = len;
@@ -1192,17 +1246,17 @@ unsigned int i;
*(unsigned int *)RX_BAD = *(unsigned int *)RX_BAD + 1;
rx_acc = 0;
rx_nfrag = 0;
rx_switch();
rx_release();
return;
}
if (rx_acc < total) {
return; /* still arriving */
}
/* The frame is complete. Hand the OTHER slot to the chip first, so
* nothing it receives from now on lands in the picture below. */
slot = rx_armed;
rx_switch();
/* The frame is complete. Its descriptors stay held -- so the chip
* cannot overwrite the picture while it is parsed and painted -- and are
* released just below, each into the other slot. */
slot = rx_frame_slot;
/*
* Task 4 finding (b): the length handed to fp_parse() is clamped to the
@@ -1219,9 +1273,7 @@ unsigned int i;
*(unsigned int *)RX_LAST_LEN = h.payload_len;
*(unsigned int *)RX_SLOT = slot;
paint(&h);
/* Published after the paint: a reader that sees RX_OK move
* sees a picture that is already on the screen. */
*(unsigned int *)RX_OK = *(unsigned int *)RX_OK + 1;
accepted = 1;
} else if (h.kind == FP_KIND_DICT_CHUNK) {
/* A chunk is not a picture: it is counted by DICT_BYTES
* and DICT_BAD, never by RX_OK. */
@@ -1239,11 +1291,9 @@ unsigned int i;
if (paint_tiles(&h)) {
*(unsigned int *)RX_LAST_LEN = h.payload_len;
*(unsigned int *)RX_SLOT = slot;
*(unsigned int *)RX_OK =
*(unsigned int *)RX_OK + 1;
accepted = 1;
} else {
tiles_drop = tiles_drop + 1;
*(unsigned int *)TILES_DROP = tiles_drop;
refused = 1;
}
} else if (h.kind == FP_KIND_TILES_DELTA) {
/*
@@ -1255,11 +1305,9 @@ unsigned int i;
if (paint_delta(&h)) {
*(unsigned int *)RX_LAST_LEN = h.payload_len;
*(unsigned int *)RX_SLOT = slot;
*(unsigned int *)RX_OK =
*(unsigned int *)RX_OK + 1;
accepted = 1;
} else {
tiles_drop = tiles_drop + 1;
*(unsigned int *)TILES_DROP = tiles_drop;
refused = 1;
}
} else {
/* A well-formed header whose kind nothing here dispatches
@@ -1272,6 +1320,23 @@ unsigned int i;
}
rx_acc = 0;
rx_nfrag = 0;
/* Released only now: every byte this frame needed has been read. */
rx_release();
/*
* Published after the release, not inside the dispatch. RX_OK is the
* "frame done" signal a reader polls, and it must not become visible
* while rx_release() is still rewriting descriptors: a sample taken then
* catches a descriptor mid-update (measured: slot B's high byte with
* slot A's low word). A reader that sees RX_OK move now sees a painted
* picture AND a consistent ring.
*/
if (refused) {
tiles_drop = tiles_drop + 1;
*(unsigned int *)TILES_DROP = tiles_drop;
}
if (accepted) {
*(unsigned int *)RX_OK = *(unsigned int *)RX_OK + 1;
}
}
/*
@@ -1300,18 +1365,20 @@ static void rx_poll()
}
rx_pos = (i + 1) & (RX_DESC - 1);
*(unsigned int *)RX_DGRAM = *(unsigned int *)RX_DGRAM + 1;
rx_hold[rx_nhold] = i;
rx_nhold = rx_nhold + 1;
rx_dgram(i);
if (rx_nfrag == RX_DESC && rx_acc != 0) {
*(unsigned int *)RX_RING_DROPS =
*(unsigned int *)RX_RING_DROPS + 1;
rx_acc = 0;
rx_nfrag = 0;
rx_switch();
rx_release();
}
}
*(unsigned int *)RX_RING_POS = rx_pos;
*(unsigned int *)RX_RING_ACC = rx_acc;
*(unsigned int *)RX_RING_ARMED = rx_armed;
*(unsigned int *)RX_RING_ARMED = rx_slot_of[rx_pos];
}
/*
@@ -1375,7 +1442,7 @@ unsigned int ptr;
w16(init + 7 * 16, 0);
cb = rxring >> 3; /* BYTE address of the RX ring */
w16(init + 8 * 16, cb & 0xFFFF);
w16(init + 9 * 16, ((cb >> 16) & 0xFF) | (4 << 13)); /* 16 desc */
w16(init + 9 * 16, ((cb >> 16) & 0xFF) | (5 << 13)); /* 32 desc */
cb = ring >> 3; /* BYTE address of the ring */
w16(init + 10 * 16, cb & 0xFFFF);
w16(init + 11 * 16, ((cb >> 16) & 0xFF) | (3 << 13));
@@ -1396,8 +1463,8 @@ unsigned int ptr;
/* The RX ring, armed for slot A: this is the state the first packet
* finds, and it is what the MAME ring check asserts. */
rx_armed = 0;
rx_arm(rx_armed);
rx_nhold = 0;
rx_arm(0);
*(unsigned int *)RX_RING_BASE = rxring;
lance_csr_w(1, (init >> 3) & 0xFFFF);
@@ -1702,10 +1769,11 @@ int main()
status_seq = 0;
status_seen = 0;
drops_total = 0;
rx_armed = 0;
rx_pos = 0;
rx_acc = 0;
rx_nfrag = 0;
rx_nhold = 0;
rx_frame_slot = 0;
vid_dint_();
lance_ok = lance_start(ptr);