3б этап

This commit is contained in:
Maxim
2026-07-16 14:30:17 +03:00
parent b9846fcbdc
commit 3b6a56e54c
3 changed files with 252 additions and 33 deletions

View File

@@ -60,6 +60,11 @@ static pthread_mutex_t g_fb_mtx = PTHREAD_MUTEX_INITIALIZER;
static fb_status_t g_fb_st;
static int g_fb_fresh = 0; // 1 = снимок не прочитан main'ом
// COMPLETE — отдельная защёлка, а не поле снимка: снимок «съедает» arq_service,
// и подтверждение, пришедшее в том же STATUS, потерялось бы. Флаг монотонный —
// once complete, always complete.
static volatile sig_atomic_t g_fb_complete = 0;
// Забрать свежий снимок. Возврат 1 — *out заполнен, 0 — нового STATUS не было.
static int fb_take(fb_status_t *out) {
int fresh;
@@ -87,6 +92,7 @@ static int fb_callback(unsigned char *hdr, int hdr_valid,
if (fb_status_unpack(dec, dlen, &st) != 0) return 0;
g_fb_status_rx++;
if (st.flags & FB_FLAG_COMPLETE) g_fb_complete = 1;
pthread_mutex_lock(&g_fb_mtx);
g_fb_st = st;
g_fb_fresh = 1;
@@ -123,12 +129,83 @@ static void *fb_rx_thread(void *arg) {
return NULL;
}
// --- TX-окно и ARQ (этап 3b) --------------------------------------------
// Копия каждого переданного кадра: без неё дослать нечего — stdin уже прочитан
// и не перематывается. Кольцо по seq % TXWIN, хранит СЫРОЙ payload (до RS/OFDM):
// phy_tx_frame детерминирован, поэтому повтор уходит в эфир байт-в-байт.
// TXWIN(2048) > RB_CAP(1024) приёмника — кадр, который B ещё может принять в
// своё окно переупорядочивания, гарантированно есть у нас в окне.
#define TXWIN 2048
#define ARQ_RETX_MS 400 // per-seq пауза между повторами одного seq.
// 400 = петля «потеря → детект (~10 мс) → слот STATUS (~50 мс) → доставка при
// p≈0.5 (~100300 мс) → ретрансмит (~30 мс) → подтверждение (~150250 мс)».
// Меньше — дубли жгут эфир (кадр 9.3 мс); больше — дыра дольше держит окно B.
typedef struct {
uint32_t seq;
int64_t last_ms; // когда seq последний раз уходил в эфир
uint16_t len;
uint8_t hdr11; // флаги group-FEC (0 = legacy)
uint8_t valid;
uint8_t pay[GF_PAR_PAY]; // 1026: паритетный кадр GF — самый большой
} txslot_t;
static txslot_t txwin[TXWIN]; // ~2.1 МБ .bss (из 1 ГБ платы)
static int g_arq_on = 0; // ARQ включён (FDD + legacy; GF — этап 3c)
static uint64_t g_retx = 0; // отправлено ретрансмитов
static uint64_t g_retx_old = 0; // NACK на seq, вышедший из окна (≈21 с эфира)
// Последний известный список дыр B. Живёт между снимками: пока не пришёл новый
// STATUS, дожимаем прежний — от дублей защищает ARQ_RETX_MS, а не свежесть.
static uint32_t g_nack[FB_MAX_NACK];
static int g_nack_cnt = 0;
// Передать НОВЫЙ кадр данных/паритета, попутно сохранив его в окне.
static void tx_send_data(ofdmflexframegen fg, struct iio_buffer *buf, fec enc,
uint32_t seq, uint8_t hdr11, const uint8_t *pl, size_t n,
float amp, int pause_us) {
txslot_t *s = &txwin[seq % TXWIN];
s->seq = seq; s->hdr11 = hdr11; s->len = (uint16_t)n; s->valid = 1;
memcpy(s->pay, pl, n);
phy_tx_frame(fg, buf, BUF_SAMPLES, enc, SIG_DATA, seq, hdr11, pl, n, amp, pause_us);
s->last_ms = now_ms(); // после отправки: паузу считаем от конца кадра
}
// Досылка по NACK. budget — потолок ретрансмитов за один вызов: очередь дыр
// не должна вытеснять новые данные (голодание фронта). Вызывается ТОЛЬКО из
// main — TX-буфер один и владеет им этот поток.
static void arq_service(ofdmflexframegen fg, struct iio_buffer *buf, fec enc,
float amp, int pause_us, int budget) {
if (!g_arq_on) return;
fb_status_t st;
if (fb_take(&st)) { // свежий снимок → обновить список дыр
g_nack_cnt = st.nack_cnt < FB_MAX_NACK ? st.nack_cnt : FB_MAX_NACK;
memcpy(g_nack, st.nack, (size_t)g_nack_cnt * sizeof(g_nack[0]));
}
int64_t t = now_ms();
for (int i = 0; i < g_nack_cnt && budget > 0; i++) {
uint32_t seq = g_nack[i];
txslot_t *s = &txwin[seq % TXWIN];
if (!s->valid || s->seq != seq) { // окно провернулось — дослать нечем
g_retx_old++; // → END-таймаут, честный отказ
continue;
}
if (t - s->last_ms < ARQ_RETX_MS) continue; // повтор ещё в полёте
phy_tx_frame(fg, buf, BUF_SAMPLES, enc, SIG_DATA, seq, s->hdr11,
s->pay, s->len, amp, pause_us);
t = now_ms();
s->last_ms = t;
g_retx++;
budget--;
}
}
// EOF-хендшейк (этап 3, ядро 0/main): шлём END с итогом передачи, пока B не
// подтвердит COMPLETE флагом в STATUS. Возврат 0 — подтверждено, 2 — таймаут или
// прерывание: на той стороне файл неполон. Честный отказ вместо тихой дыры
// (техдолг #6) — раньше A просто закрывал буфер и рапортовал успех.
static int end_phase(ofdmflexframegen fg, struct iio_buffer *buf, fec enc,
float amp, uint32_t total_seq, uint64_t total_bytes) {
float amp, int pause_us, uint32_t total_seq, uint64_t total_bytes) {
fb_end_t e = { .total_seq = total_seq, .total_bytes = total_bytes };
uint8_t pay[FB_END_LEN];
size_t n = fb_end_pack(&e, pay);
@@ -140,19 +217,20 @@ static int end_phase(ofdmflexframegen fg, struct iio_buffer *buf, fec enc,
fprintf(stderr, "[ARQ] END: кадров %u, байт %llu — жду COMPLETE (таймаут %d мс)\n",
total_seq, (unsigned long long)total_bytes, END_TIMEOUT_MS);
while (!stop && now_ms() - t0 < END_TIMEOUT_MS) {
while (!stop && !g_fb_complete && now_ms() - t0 < END_TIMEOUT_MS) {
phy_tx_frame(fg, buf, BUF_SAMPLES, enc, SIG_END, eseq++, 0, pay, n, amp, 0);
sent++;
// Бюджет выше, чем в data-фазе: новых данных больше нет, эфир свободен,
// и закрыть остаток дыр надо именно сейчас — таймаут тикает.
arq_service(fg, buf, enc, amp, pause_us, 4);
int64_t w = now_ms();
while (!stop && now_ms() - w < END_PERIOD_MS) { // пауза между END —
fb_status_t st; // опрос снимка, не sleep
if (fb_take(&st) && (st.flags & FB_FLAG_COMPLETE)) {
fprintf(stderr, "[ARQ] COMPLETE подтверждён: END × %d за %lld мс\n",
sent, (long long)(now_ms() - t0));
return 0;
}
usleep(5000);
}
while (!stop && !g_fb_complete && now_ms() - w < END_PERIOD_MS)
usleep(5000); // пауза между END: опрос защёлки, не sleep
}
if (g_fb_complete) {
fprintf(stderr, "[ARQ] COMPLETE подтверждён: END × %d, ретрансмитов %llu, %lld мс\n",
sent, (unsigned long long)g_retx, (long long)(now_ms() - t0));
return 0;
}
fprintf(stderr, "[ARQ] таймаут COMPLETE: END × %d за %lld мс — приём НЕ подтверждён\n",
sent, (long long)(now_ms() - t0));
@@ -275,8 +353,16 @@ int main(int argc, char *argv[]) {
pin_to_cpu(0); // TX-данные — ядро 0
pthread_create(&fb_tid, NULL, fb_rx_thread, NULL);
fb_started = 1;
// ARQ пока только на legacy-пути: ретрансмит кадра старой группы
// ломает сборку group_route на RX (правило base < g.base — этап 3c).
g_arq_on = !group_fec;
fprintf(stderr, "Обратный канал: слушаю STATUS на %.1f МГц (ядро 1)\n",
fb_freq / 1e6);
if (g_arq_on)
fprintf(stderr, "ARQ: вкл (окно %d кадров, повтор не чаще %d мс)\n",
TXWIN, ARQ_RETX_MS);
else
fprintf(stderr, "ARQ: выкл (group-FEC — этап 3c)\n");
}
}
@@ -298,8 +384,12 @@ int main(int argc, char *argv[]) {
usleep(10000);
continue;
}
phy_tx_frame(fg, buf, BUF_SAMPLES, enc, SIG_DATA, seq++, 0, raw, n, amp, pause_us);
tx_send_data(fg, buf, enc, seq++, 0, raw, n, amp, pause_us);
frames++; bytes_tx += n;
// Досылка вперемешку с новыми данными: дыра закрывается, пока B ещё
// держит её в окне переупорядочивания. Бюджет 2 — потолок, чтобы
// очередь NACK не вытеснила фронт передачи.
arq_service(fg, buf, enc, amp, pause_us, 2);
if (time(NULL) > last) {
fprintf(stderr, "\r📤 Передано: %llu кадров, %llu байт",
(unsigned long long)frames, (unsigned long long)bytes_tx);
@@ -326,8 +416,8 @@ int main(int argc, char *argv[]) {
if (k == GF_K || (done_reading && k > 0)) {
// k кадров данных: idx=0..k-1
for (int i = 0; i < k; i++) {
phy_tx_frame(fg, buf, BUF_SAMPLES, enc, SIG_DATA, seq++, GF_FLAG_MODE | i,
gdata[i], glen[i], amp, pause_us);
tx_send_data(fg, buf, enc, seq++, GF_FLAG_MODE | i,
gdata[i], glen[i], amp, pause_us);
frames++; bytes_tx += glen[i];
}
// паритеты P (idx=k) и Q (idx=k+1); мета = k + длина хвоста
@@ -336,11 +426,11 @@ int main(int argc, char *argv[]) {
uint16_t meta = GF_META_PACK(k, glen[k - 1]);
par[0] = meta >> 8; par[1] = meta & 0xFF;
memcpy(par + GF_META, P, GF_CHUNK);
phy_tx_frame(fg, buf, BUF_SAMPLES, enc, SIG_DATA, seq++, GF_FLAG_MODE | GF_FLAG_PAR | k,
par, GF_PAR_PAY, amp, pause_us);
tx_send_data(fg, buf, enc, seq++, GF_FLAG_MODE | GF_FLAG_PAR | k,
par, GF_PAR_PAY, amp, pause_us);
memcpy(par + GF_META, Q, GF_CHUNK);
phy_tx_frame(fg, buf, BUF_SAMPLES, enc, SIG_DATA, seq++, GF_FLAG_MODE | GF_FLAG_PAR | GF_FLAG_Q | (k + 1),
par, GF_PAR_PAY, amp, pause_us);
tx_send_data(fg, buf, enc, seq++, GF_FLAG_MODE | GF_FLAG_PAR | GF_FLAG_Q | (k + 1),
par, GF_PAR_PAY, amp, pause_us);
frames += 2;
k = 0;
if (time(NULL) > last) {
@@ -361,7 +451,7 @@ int main(int argc, char *argv[]) {
// прежним путём — молча в дренаж, exit 0 (регрессия §12 не меняется).
int rc = 0;
if (fb_started)
rc = end_phase(fg, buf, enc, amp, seq, bytes_tx);
rc = end_phase(fg, buf, enc, amp, pause_us, seq, bytes_tx);
// Дренаж: выпихнуть последний кадр из kernel-буферов DMA перед закрытием.
// Без паузы iio_buffer_destroy() срезает хвост файла в эфире (техдолг #5).
@@ -379,6 +469,8 @@ int main(int argc, char *argv[]) {
pthread_join(fb_tid, NULL);
fprintf(stderr, "Обратный канал: принято STATUS: %llu\n",
(unsigned long long)g_fb_status_rx);
fprintf(stderr, "ARQ: ретрансмитов: %llu | NACK вне окна: %llu\n",
(unsigned long long)g_retx, (unsigned long long)g_retx_old);
}
// Освобождение ресурсов