3б этап
This commit is contained in:
@@ -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 (~100–300 мс) → ретрансмит (~30 мс) → подтверждение (~150–250 мс)».
|
||||
// Меньше — дубли жгут эфир (кадр 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);
|
||||
}
|
||||
|
||||
// Освобождение ресурсов
|
||||
|
||||
Reference in New Issue
Block a user