Add the osmo_trx_ep module, encapsulating the clock/ctrl/data UDP socket management of the TRX protocol on top of osmo_io. One endpoint serves an (optional) clock socket plus N channels, each channel being a ctrl + data socket pair.
The module is role-neutral: struct osmo_trx_ep_cfg carries a mode (OSMO_TRX_EP_MODE_{L1,TRX}) selecting which side of the protocol the endpoint implements. The mode is needed to parse datagrams received on the data sockets, as the wire format alone does not identify the direction (BURST.ind vs BURST.req). All local and remote ports are derived from a single base port (default: 5700).
To avoid indirect calls on the hot path, the decoded Rx handlers (osmo_trx_ep_rx_*) are plain function prototypes bound at link-time, not function pointers. The library provides weak default stubs to stay link-complete (-no-undefined), so applications only implement the handlers for the directions they consume, overriding the stubs.
The Tx path implements TRXDv2 PDU batching for burst requests: osmo_trx_ep_send_burst_req() accumulates PDUs until it is called with br == NULL (the batching breaker), like in osmo-bts-trx.
osmo-trx itself will not adopt this module: its per-channel/thread socket ownership model (dedicated blocking-read threads per socket) is incompatible with osmo_io's single-select-loop dispatch. It is meant for osmo-bts, osmocom-bb/trxcon, and the upcoming C rewrite of fake_trx.