Add option to automatically reset RIFO on underrun/overflow

Whenever the RIFO buffer fill drifts away from its target, it can be
automatically reset and filled to the initial prefill_frame_count value.
The average fill is measured over several seconds. A given deviation
in percent of the prefill_frame_count is used to trigger that reset.
If the deviation is not set (0), this feature is deactivated.

There are two reasons for this to happen: The GPS clock is missing, so
the receiving interface is not in sync with the transmitting interface.
The delay changes significantly, due to congestion on the path between
both peers. (poor internet connection)

Change-Id: Id7ccbfbdb288990c01f185dec79a1022a68b4748
This commit is contained in:
Andreas Eversberg 2023-09-24 16:43:45 +00:00 committed by Andreas Eversberg
parent 6b0f9179b1
commit 8d65c3ff4d
11 changed files with 54 additions and 13 deletions

View File

@ -24,6 +24,7 @@ struct octoi_account {
uint8_t batching_factor; /* E1 frames per UDP packet (Tx) */
bool force_send_all_ts; /* force transmission of all timeslots */
uint32_t prefill_frame_count; /* FIFO prefill/preseed count (Rx) */
uint8_t buffer_reset_percent; /* When to reset Rx RIFO in percent */
union {
struct {
char *usb_serial; /* USB serial string (ASCII) of icE1usb */

View File

@ -250,7 +250,7 @@ int e1oip_rcvmsg_tdm_data(struct e1oip_line *iline, struct msgb *msg)
"RxIP: %u extraneous bytes (len=%u, num_ts=%u, n_frames=%u)\n",
msgb_length(msg) % num_ts, msgb_length(msg), num_ts, n_frames);
}
LOGPEER(peer, LOGL_INFO, "RxIP: frame=%05u ts_mask=0x%08x num_ts=%02u, n_frames=%u\n",
LOGPEER(peer, LOGL_DEBUG, "RxIP: frame=%05u ts_mask=0x%08x num_ts=%02u, n_frames=%u\n",
frame_nr, ts_mask, num_ts, n_frames);
} else {
if (msgb_l3len(msg) < 1) {
@ -271,7 +271,26 @@ int e1oip_rcvmsg_tdm_data(struct e1oip_line *iline, struct msgb *msg)
rc = frame_rifo_in(&iline->e1t.rifo, frame_buf, fn32+i);
if (rc < 0)
iline_ctr_add(iline, LINE_CTR_E1oIP_E1T_OVERFLOW, 1);
/* Continue the for loop, if buffer reset is not configured. */
if (!iline->cfg.buffer_reset_percent)
continue;
/* Calculate average RIFO delay. */
int32_t d = fn32 + i - iline->e1t.rifo.next_out_fn;
iline->e1t.delay += d;
if (++iline->e1t.delay_cnt == FRAMES_BUFFER_RESET_AVG) {
int offset;
d = iline->e1t.delay / iline->e1t.delay_cnt;
iline->e1t.delay = iline->e1t.delay_cnt = 0;
offset = abs((int32_t)iline->cfg.prefill_frame_count - d) * 100 / iline->cfg.prefill_frame_count;
LOGPEER(peer, LOGL_INFO, "RxIP: Buffer fill %u frames, %u%% off target.\n", d, offset);
if (offset > iline->cfg.buffer_reset_percent) {
LOGPEER(peer, LOGL_ERROR, "RxIP: frame number out of range. Reset buffer.\n");
frame_rifo_init(&iline->e1t.rifo, fn32 + i);
iline->e1t.primed_rx_tdm = false;
}
}
}
/* update local state */
memcpy(iline->e1t.last_frame, frame_buf, BYTES_PER_FRAME);
if (update_next)
@ -318,10 +337,11 @@ struct e1oip_line *e1oip_line_alloc(struct octoi_peer *peer)
}
void e1oip_line_configure(struct e1oip_line *iline, uint8_t batching_factor,
uint32_t prefill_frame_count, bool force_send_all_ts)
uint32_t prefill_frame_count, uint8_t buffer_reset_percent, bool force_send_all_ts)
{
iline->cfg.batching_factor = batching_factor;
iline->cfg.prefill_frame_count = prefill_frame_count;
iline->cfg.buffer_reset_percent = buffer_reset_percent;
iline->cfg.force_send_all_ts = force_send_all_ts;
}
@ -331,10 +351,12 @@ void e1oip_line_reset(struct e1oip_line *iline)
memset(&iline->e1o.last_frame, 0xff, sizeof(iline->e1o.last_frame));
iline->e1o.next_seq = 0;
frame_rifo_init(&iline->e1t.rifo);
frame_rifo_init(&iline->e1t.rifo, 0);
memset(&iline->e1t.last_frame, 0xff, sizeof(iline->e1t.last_frame));
iline->e1t.next_fn32 = 0;
iline->e1t.primed_rx_tdm = false;
iline->e1t.delay = 0;
iline->e1t.delay_cnt = 0;
}
void e1oip_line_destroy(struct e1oip_line *iline)

View File

@ -15,6 +15,8 @@
#define FRAMES_PER_SEC_THRESHOLD 7500
#define FRAMES_BUFFER_RESET_AVG 40000
#define DEFAULT_BATCHING_FACTOR 32
#define DEFAULT_PREFILL_FRAME_COUNT 200 /* 25ms */
@ -54,6 +56,7 @@ struct e1oip_line {
struct {
uint8_t batching_factor;
uint32_t prefill_frame_count;
uint8_t buffer_reset_percent;
bool force_send_all_ts;
} cfg;
@ -70,6 +73,7 @@ struct e1oip_line {
uint8_t last_frame[BYTES_PER_FRAME]; /* last frame on the E1 side */
uint32_t next_fn32; /* next expected frame number */
bool primed_rx_tdm; /* Was RX RIFO primed */
int32_t delay, delay_cnt; /* Delay counter to calculate average delay */
} e1t;
};
@ -84,7 +88,7 @@ struct e1oip_line *e1oip_line_alloc(struct octoi_peer *peer);
void e1oip_line_set_name(struct e1oip_line *line, const char *name);
void e1oip_line_reset(struct e1oip_line *iline);
void e1oip_line_configure(struct e1oip_line *iline, uint8_t batching_factor,
uint32_t prefill_frame_count, bool force_send_all_ts);
uint32_t prefill_frame_count, uint8_t buffer_reset_percent, bool force_send_all_ts);
void e1oip_line_destroy(struct e1oip_line *iline);
int e1oip_rcvmsg_tdm_data(struct e1oip_line *iline, struct msgb *msg);

View File

@ -86,12 +86,12 @@ static bool bucket_bit_get(struct frame_rifo *rifo, uint32_t bucket_nr)
/*! Initialize a frame RIFO.
* \param rifo Caller-allocated memory for RIFO data structure */
void frame_rifo_init(struct frame_rifo *rifo)
void frame_rifo_init(struct frame_rifo *rifo, uint32_t fn)
{
memset(rifo->buf, 0xff, sizeof(rifo->buf));
rifo->next_out = rifo->buf;
rifo->next_out_fn = 0;
rifo->last_in_fn = -1;
rifo->next_out_fn = fn;
rifo->last_in_fn = fn - 1;
memset(rifo->bitvec, 0, sizeof(rifo->bitvec));
}

View File

@ -27,7 +27,7 @@ static inline unsigned int frame_rifo_depth(struct frame_rifo *rifo)
return rifo->last_in_fn - rifo->next_out_fn + 1;
}
void frame_rifo_init(struct frame_rifo *rifo);
void frame_rifo_init(struct frame_rifo *rifo, uint32_t fn);
/* number of frames currently available in FIFO */
static inline unsigned int frame_rifo_frames(struct frame_rifo *rifo)

View File

@ -123,7 +123,7 @@ static void clnt_st_accepted_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta
struct clnt_state *st = fi->priv;
e1oip_line_configure(st->peer->iline, st->acc->batching_factor,
st->acc->prefill_frame_count, st->acc->force_send_all_ts);
st->acc->prefill_frame_count, st->acc->buffer_reset_percent, st->acc->force_send_all_ts);
/* reset RIFO/FIFO etc. */
e1oip_line_reset(st->peer->iline);
iline_ctr_add(st->peer->iline, LINE_CTR_E1oIP_CONNECT_ACCEPT, 1);

View File

@ -283,6 +283,7 @@ void octoi_client_vty_init(void)
install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_force_all_ts_cmd);
install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_no_force_all_ts_cmd);
install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_prefill_frame_count_cmd);
install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_buffer_reset_cmd);
#ifdef HAVE_DAHDI_TRUNKDEV
install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_trunkdev_name_cmd);
install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_trunkdev_line_cmd);

View File

@ -176,7 +176,7 @@ static void srv_st_accepted_onenter(struct osmo_fsm_inst *fi, uint32_t prev_stat
struct srv_state *st = fi->priv;
e1oip_line_configure(st->peer->iline, st->acc->batching_factor,
st->acc->prefill_frame_count, st->acc->force_send_all_ts);
st->acc->prefill_frame_count, st->acc->buffer_reset_percent, st->acc->force_send_all_ts);
/* reset RIFO/FIFO etc. */
e1oip_line_reset(st->peer->iline);
iline_ctr_add(st->peer->iline, LINE_CTR_E1oIP_CONNECT_ACCEPT, 1);

View File

@ -412,7 +412,7 @@ gDEFUN(cfg_account_no_force_all_ts, cfg_account_no_force_all_ts_cmd,
}
gDEFUN(cfg_account_prefill_frame_count, cfg_account_prefill_frame_count_cmd,
"prefill-frame-count <0-8000>",
"prefill-frame-count <0-900>",
"Number of E1 frames to pre-fill/pre-seed in Rx RIFO\n"
"Number of E1 frames to pre-fill/pre-seed in Rx RIFO\n")
{
@ -422,6 +422,17 @@ gDEFUN(cfg_account_prefill_frame_count, cfg_account_prefill_frame_count_cmd,
return CMD_SUCCESS;
}
gDEFUN(cfg_account_buffer_reset, cfg_account_buffer_reset_cmd,
"buffer-reset <0-100>",
"Reset Rx RIFO when fill in Rx RIFO drifts too much off normal\n"
"Give fill in percentage, use 0 to disable this feature\n")
{
struct octoi_account *acc = vty->index;
acc->buffer_reset_percent = atoi(argv[0]);
return CMD_SUCCESS;
}
void octoi_vty_show_one_account(struct vty *vty, const char *pfx, struct octoi_account *acc)
{
vty_out(vty, "%sAccount '%s': Mode=%s, Batching=%u, Prefill=%u%s", pfx,
@ -563,6 +574,7 @@ void octoi_server_vty_init(void)
install_element(OCTOI_ACCOUNT_NODE, &cfg_account_force_all_ts_cmd);
install_element(OCTOI_ACCOUNT_NODE, &cfg_account_no_force_all_ts_cmd);
install_element(OCTOI_ACCOUNT_NODE, &cfg_account_prefill_frame_count_cmd);
install_element(OCTOI_ACCOUNT_NODE, &cfg_account_buffer_reset_cmd);
#ifdef HAVE_DAHDI_TRUNKDEV
install_element(OCTOI_ACCOUNT_NODE, &cfg_account_trunkdev_name_cmd);
install_element(OCTOI_ACCOUNT_NODE, &cfg_account_trunkdev_line_cmd);

View File

@ -11,6 +11,7 @@ extern struct cmd_element cfg_account_batching_factor_cmd;
extern struct cmd_element cfg_account_force_all_ts_cmd;
extern struct cmd_element cfg_account_no_force_all_ts_cmd;
extern struct cmd_element cfg_account_prefill_frame_count_cmd;
extern struct cmd_element cfg_account_buffer_reset_cmd;
extern struct cmd_element cfg_account_trunkdev_name_cmd;
extern struct cmd_element cfg_account_trunkdev_line_cmd;

View File

@ -23,7 +23,7 @@ static uint32_t init_next_out_fn;
static void rifo_init(struct frame_rifo *rifo)
{
frame_rifo_init(rifo);
frame_rifo_init(rifo, 0);
rifo->next_out_fn = init_next_out_fn;
rifo->last_in_fn = init_next_out_fn - 1;
}
@ -48,7 +48,7 @@ static int rifo_out(struct frame_rifo *rifo, uint8_t *out)
static void missing_frames(uint8_t modulo)
{
struct frame_rifo rifo;
frame_rifo_init(&rifo);
frame_rifo_init(&rifo, 0);
rifo.next_out_fn = init_next_out_fn;
rifo.last_in_fn = init_next_out_fn - 1;