mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-06-08 03:01:52 -04:00
milan-avb: ACMP listener self-heal, CBS-exclusive egress, per-iface MVRP, Milan MaxFrameSize + channel-strict RX
This commit is contained in:
parent
90114c9839
commit
895e3a4fa1
4 changed files with 91 additions and 43 deletions
|
|
@ -2457,6 +2457,38 @@ void acmp_periodic_milan_v12(struct acmp *acmp, uint64_t now)
|
|||
stream_out->last_probe_rx_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Milan Section 4.3.3.1 / 5.5.3: a settled Listener with no reservation yet
|
||||
* (SETTLED_NO_RSV) re-evaluates Listener Ready against the Talker Advertise
|
||||
* registrar each tick. After a bridge convergence delay the TA arrives with no
|
||||
* fresh ACMP event, so this re-declares Ready and advances to SETTLED_RSV_OK the
|
||||
* instant the TA is IN — the stall self-heals, no controller re-bind needed. */
|
||||
for (uint16_t desc_index = 0; desc_index < UINT16_MAX; desc_index++) {
|
||||
struct descriptor *desc;
|
||||
struct aecp_aem_stream_input_state_milan_v12 *si_m;
|
||||
struct stream_common *common;
|
||||
bool ta_in;
|
||||
|
||||
desc = server_find_descriptor(acmp->server, AVB_AEM_DESC_STREAM_INPUT,
|
||||
desc_index);
|
||||
if (desc == NULL)
|
||||
break;
|
||||
|
||||
si_m = desc->ptr;
|
||||
if (si_m->acmp_sta.fsm_acmp_state != FSM_ACMP_STATE_MILAN_V12_SETTLED_NO_RSV)
|
||||
continue;
|
||||
|
||||
common = &si_m->stream_in_sta.common;
|
||||
ta_in = common->tastream_attr.mrp != NULL &&
|
||||
avb_mrp_attribute_get_registrar_state(common->tastream_attr.mrp) == AVB_MRP_IN;
|
||||
common->lstream_attr.param = ta_in
|
||||
? AVB_MSRP_LISTENER_PARAM_READY
|
||||
: AVB_MSRP_LISTENER_PARAM_ASKING_FAILED;
|
||||
if (common->lstream_attr.mrp != NULL)
|
||||
avb_mrp_attribute_join(common->lstream_attr.mrp, now, true);
|
||||
if (ta_in)
|
||||
si_m->acmp_sta.fsm_acmp_state = FSM_ACMP_STATE_MILAN_V12_SETTLED_RSV_OK;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *probing_status_name(uint8_t s)
|
||||
|
|
|
|||
|
|
@ -480,23 +480,15 @@ static int raw_stream_setup_socket(struct server *server, struct stream *stream)
|
|||
stream->sock_addr.sll_ifindex = req.ifr_ifindex;
|
||||
|
||||
if (stream->direction == SPA_DIRECTION_OUTPUT) {
|
||||
struct sock_txtime txtime_cfg;
|
||||
|
||||
/* CBS/Qav-exclusive: set only the traffic-class priority so the egress
|
||||
* CBS qdisc shapes this stream. SO_TXTIME (launch-time/ETF) is NOT set --
|
||||
* CBS and SO_TXTIME cannot coexist on the same queue. */
|
||||
res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &stream->prio,
|
||||
sizeof(stream->prio));
|
||||
if (res < 0) {
|
||||
pw_log_error("setsockopt(SO_PRIORITY %d) failed: %m", stream->prio);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
txtime_cfg.clockid = CLOCK_TAI;
|
||||
txtime_cfg.flags = 0;
|
||||
res = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &txtime_cfg,
|
||||
sizeof(txtime_cfg));
|
||||
if (res < 0) {
|
||||
pw_log_error("setsockopt(SO_TXTIME) failed: %m");
|
||||
return -errno;
|
||||
}
|
||||
} else {
|
||||
struct packet_mreq mreq;
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,15 @@ static struct descriptor *es_buidler_desc_avb_interface(struct server *server,
|
|||
avb_mrp_attribute_begin(if_ptr->domain_attr.mrp, 0);
|
||||
avb_mrp_attribute_join(if_ptr->domain_attr.mrp, 0, true);
|
||||
|
||||
/* milan-avb: declare VID membership (MVRP) once per interface, held for the
|
||||
* life of the interface like the SR Domain above — NOT per stream, so that
|
||||
* destroying one stream cannot withdraw the VLAN other streams still need. */
|
||||
avb_mvrp_attribute_new(server->mvrp, &if_ptr->vlan_attr,
|
||||
AVB_MVRP_ATTRIBUTE_TYPE_VID);
|
||||
if_ptr->vlan_attr.attr.vid.vlan = htons(AVB_DEFAULT_VLAN);
|
||||
avb_mrp_attribute_begin(if_ptr->vlan_attr.mrp, 0);
|
||||
avb_mrp_attribute_join(if_ptr->vlan_attr.mrp, 0, true);
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -229,34 +229,42 @@ static void on_flush_tick(void *data, uint64_t expirations)
|
|||
{
|
||||
struct stream *stream = data;
|
||||
struct server *server = stream->server;
|
||||
uint64_t now_ns;
|
||||
struct timespec ts;
|
||||
uint64_t now_mono, now_gptp, stamp;
|
||||
int owed;
|
||||
|
||||
(void)expirations;
|
||||
|
||||
now_ns = stream_gptp_now(server);
|
||||
if (now_ns == 0) {
|
||||
/* Pace the send rate off CLOCK_MONOTONIC (a stable local 1x clock); use the gPTP
|
||||
* clock only for the AVTP presentation timestamp. Pacing must not ride the absolute
|
||||
* PHC interpolation, whose steps during gPTP re-convergence burst the talker past
|
||||
* its SRP reservation and get the stream policed away by the bridge. */
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
now_mono = SPA_TIMESPEC_TO_NSEC(&ts);
|
||||
now_gptp = stream_gptp_now(server);
|
||||
stamp = now_gptp != 0 ? now_gptp : now_mono;
|
||||
|
||||
if (stream->pdu_period == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stream->flush_last_ns == 0) {
|
||||
stream->flush_last_ns = now_ns;
|
||||
stream->flush_last_ns = now_mono;
|
||||
return;
|
||||
}
|
||||
if (stream->pdu_period == 0)
|
||||
return;
|
||||
|
||||
owed = (int)((now_ns - stream->flush_last_ns) / (uint64_t)stream->pdu_period);
|
||||
if (owed <= 0)
|
||||
owed = (int)((now_mono - stream->flush_last_ns) / (uint64_t)stream->pdu_period);
|
||||
if (owed <= 0) {
|
||||
return;
|
||||
}
|
||||
stream->flush_last_ns += (uint64_t)owed * (uint64_t)stream->pdu_period;
|
||||
|
||||
pad_ringbuffer_with_silence(stream, owed);
|
||||
|
||||
if (server->avb_mode == AVB_MODE_MILAN_V12)
|
||||
flush_write_milan_v12(stream, now_ns, owed);
|
||||
else
|
||||
flush_write_legacy(stream, now_ns);
|
||||
if (server->avb_mode == AVB_MODE_MILAN_V12) {
|
||||
flush_write_milan_v12(stream, stamp, owed);
|
||||
} else {
|
||||
flush_write_legacy(stream, stamp);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_source_stream_process(void *data)
|
||||
|
|
@ -467,7 +475,7 @@ static int flush_write_milan_v12(struct stream *stream, uint64_t current_time, i
|
|||
ptime = txtime + stream->mtt;
|
||||
|
||||
while (pdu_count--) {
|
||||
*(uint64_t*)CMSG_DATA(stream->cmsg) = txtime;
|
||||
/* CBS-exclusive: no SCM_TXTIME; txtime feeds ptime only */
|
||||
|
||||
set_iovec(&stream->ring,
|
||||
stream->buffer_data,
|
||||
|
|
@ -515,7 +523,7 @@ static int flush_write_legacy(struct stream *stream, uint64_t current_time)
|
|||
ptime = txtime + stream->mtt;
|
||||
|
||||
while (pdu_count--) {
|
||||
*(uint64_t*)CMSG_DATA(stream->cmsg) = txtime;
|
||||
/* CBS-exclusive: no SCM_TXTIME; txtime feeds ptime only */
|
||||
|
||||
set_iovec(&stream->ring,
|
||||
stream->buffer_data,
|
||||
|
|
@ -671,12 +679,11 @@ static int setup_msg(struct stream *stream)
|
|||
stream->msg.msg_namelen = sizeof(stream->sock_addr);
|
||||
stream->msg.msg_iov = stream->iov;
|
||||
stream->msg.msg_iovlen = 3;
|
||||
stream->msg.msg_control = stream->control;
|
||||
stream->msg.msg_controllen = sizeof(stream->control);
|
||||
stream->cmsg = CMSG_FIRSTHDR(&stream->msg);
|
||||
stream->cmsg->cmsg_level = SOL_SOCKET;
|
||||
stream->cmsg->cmsg_type = SCM_TXTIME;
|
||||
stream->cmsg->cmsg_len = CMSG_LEN(sizeof(__u64));
|
||||
/* CBS/Qav-exclusive: no SCM_TXTIME control message -- CBS and SO_TXTIME
|
||||
* cannot coexist; the egress CBS qdisc paces the stream. */
|
||||
stream->msg.msg_control = NULL;
|
||||
stream->msg.msg_controllen = 0;
|
||||
stream->cmsg = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -855,8 +862,14 @@ struct stream *server_create_stream(struct server *server, struct stream *stream
|
|||
|
||||
common->tastream_attr.attr.talker.vlan_id = htons(stream->vlan_id);
|
||||
if (server->avb_mode == AVB_MODE_MILAN_V12)
|
||||
/* Milan v1.2 Section 4.3.3.2 Table 4.4: MaxFrameSize is the AVTPDU
|
||||
* (header + payload) ONLY, plus 1 byte to account for the PAAD
|
||||
* sampling clock possibly running slightly fast. The Ethernet header
|
||||
* and FCS are added separately by the bandwidth rule (F = MaxFrameSize
|
||||
* + 22), so exclude our avb_frame_header (the L2 header) from pdu_size. */
|
||||
common->tastream_attr.attr.talker.tspec_max_frame_size =
|
||||
htons((uint16_t)stream->pdu_size);
|
||||
htons((uint16_t)(stream->pdu_size -
|
||||
sizeof(struct avb_frame_header) + 1));
|
||||
else
|
||||
common->tastream_attr.attr.talker.tspec_max_frame_size =
|
||||
htons((uint16_t)(32 + stream->frames_per_pdu * stream->stride));
|
||||
|
|
@ -1015,9 +1028,9 @@ static void stream_mc_recover(struct stream *stream, const struct avb_packet_aaf
|
|||
#define AVB_STREAM_SEQ_SETTLE 8
|
||||
|
||||
/* Milan v1.2 Section 5.4: the listener supports only the Milan base stream
|
||||
* format for decode — AAF PCM, 32-bit integer, 48 kHz, non-sparse. Channel
|
||||
* count is a stream parameter (the ring buffers by data_len), not part of the
|
||||
* format check, so any Milan channel count passes. */
|
||||
* format for decode — AAF PCM, 32-bit integer, 48 kHz, non-sparse. The channel
|
||||
* count is checked separately by handle_aaf_packet against the stream's
|
||||
* negotiated channel count (a frame from a mismatched talker is rejected). */
|
||||
static inline bool aaf_is_milan_format(const struct avb_packet_aaf *p)
|
||||
{
|
||||
return p->subtype == AVB_SUBTYPE_AAF &&
|
||||
|
|
@ -1039,13 +1052,15 @@ static void handle_aaf_packet(struct stream *stream,
|
|||
filled = spa_ringbuffer_get_write_index(&stream->ring, &index);
|
||||
n_bytes = ntohs(p->data_len);
|
||||
|
||||
/* milan-avb: support only the Milan format. EVERY received frame that is
|
||||
* not a well-formed Milan AAF PDU — bad length, or subtype/format/sample-
|
||||
* rate/bit-depth/sparse not the Milan base format — bumps UNSUPPORTED_FORMAT
|
||||
* and is dropped, per frame: not counted as a valid frame, not media-locked,
|
||||
* not written. (Channel count is a stream parameter, not part of the format
|
||||
* check, so a valid 4ch talker like the DS20 is not flagged.) */
|
||||
if (n_bytes > (uint32_t)(len - (int)sizeof(*p)) || !aaf_is_milan_format(p)) {
|
||||
/* milan-avb: accept ONLY frames matching this stream's negotiated format.
|
||||
* EVERY received frame that is not a well-formed Milan AAF PDU — bad length,
|
||||
* subtype/format/sample-rate/bit-depth/sparse not the Milan base format, or
|
||||
* whose channel count differs from this stream's negotiated channel count —
|
||||
* bumps UNSUPPORTED_FORMAT and is dropped, per frame: not counted as a valid
|
||||
* frame, not media-locked, not written. The channel check rejects frames from
|
||||
* a different talker/format sharing the VLAN (Milan Section 5.5.1.2). */
|
||||
if (n_bytes > (uint32_t)(len - (int)sizeof(*p)) || !aaf_is_milan_format(p) ||
|
||||
p->chan_per_frame != stream->info.info.raw.channels) {
|
||||
cnt->unsupported_format++;
|
||||
stream_in_mark_counters_dirty(stream);
|
||||
return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue