From f470354e67c8d716567d94ffbfbf012c55831d71 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 15 Mar 2022 18:16:00 +0100 Subject: [PATCH] avbtp: add beginnings of AVB manager module --- src/daemon/meson.build | 9 + src/daemon/pipewire-avb.conf.in | 84 ++++++ src/modules/meson.build | 18 ++ src/modules/module-avbtp.c | 134 ++++++++++ src/modules/module-avbtp/aaf.h | 139 ++++++++++ src/modules/module-avbtp/adp.c | 382 ++++++++++++++++++++++++++++ src/modules/module-avbtp/adp.h | 148 +++++++++++ src/modules/module-avbtp/avb.c | 93 +++++++ src/modules/module-avbtp/avb.h | 44 ++++ src/modules/module-avbtp/avdecc.c | 214 ++++++++++++++++ src/modules/module-avbtp/internal.h | 83 ++++++ src/modules/module-avbtp/maap.c | 108 ++++++++ src/modules/module-avbtp/maap.h | 86 +++++++ src/modules/module-avbtp/packets.h | 115 +++++++++ 14 files changed, 1657 insertions(+) create mode 100644 src/daemon/pipewire-avb.conf.in create mode 100644 src/modules/module-avbtp.c create mode 100644 src/modules/module-avbtp/aaf.h create mode 100644 src/modules/module-avbtp/adp.c create mode 100644 src/modules/module-avbtp/adp.h create mode 100644 src/modules/module-avbtp/avb.c create mode 100644 src/modules/module-avbtp/avb.h create mode 100644 src/modules/module-avbtp/avdecc.c create mode 100644 src/modules/module-avbtp/internal.h create mode 100644 src/modules/module-avbtp/maap.c create mode 100644 src/modules/module-avbtp/maap.h create mode 100644 src/modules/module-avbtp/packets.h diff --git a/src/daemon/meson.build b/src/daemon/meson.build index 345f8282b..5d5914e4f 100644 --- a/src/daemon/meson.build +++ b/src/daemon/meson.build @@ -71,6 +71,7 @@ conf_files = [ 'jack.conf', 'minimal.conf', 'pipewire-pulse.conf', + 'pipewire-avb.conf', ] foreach c : conf_files @@ -100,6 +101,14 @@ executable('pipewire-pulse', dependencies : [ spa_dep, pipewire_dep, ], ) +executable('pipewire-avb', + pipewire_daemon_sources, + install: true, + c_args : pipewire_c_args, + include_directories : [ configinc ], + dependencies : [ spa_dep, pipewire_dep, ], +) + ln = find_program('ln') custom_target('pipewire-uninstalled', diff --git a/src/daemon/pipewire-avb.conf.in b/src/daemon/pipewire-avb.conf.in new file mode 100644 index 000000000..6cf786ab1 --- /dev/null +++ b/src/daemon/pipewire-avb.conf.in @@ -0,0 +1,84 @@ +# PulseAudio config file for PipeWire version @VERSION@ # +# +# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# @PIPEWIRE_CONFIG_DIR@/pipewire-pulse.conf.d/ for system-wide changes or in +# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes. +# + +context.properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + #log.level = 2 + + #default.clock.quantum-limit = 8192 +} + +context.spa-libs = { + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = [ + { name = libpipewire-module-rt + args = { + nice.level = -11 + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = [ ifexists nofail ] + } + { name = libpipewire-module-client-device } + { name = libpipewire-module-adapter } + { name = libpipewire-module-avbtp + args = { + # contents of avb.properties can also be placed here + # to have config per server. + } + } +] + +# Extra modules can be loaded here. Setup in default.pa can be moved here +context.exec = [ + #{ path = "pactl" args = "load-module module-always-sink" } +] + +stream.properties = { + #node.latency = 1024/48000 + #node.autoconnect = true + #resample.quality = 4 + #channelmix.normalize = false + #channelmix.mix-lfe = false + #channelmix.upmix = true + #channelmix.lfe-cutoff = 120 + #channelmix.fc-cutoff = 6000 + #channelmix.rear-delay = 12.0 + #channelmix.stereo-widen = 0.1 + #channelmix.hilbert-taps = 0 +} + +avb.properties = { + # the addresses this server listens on + ifname = "enp3s0" + server.address = [ + "unix:native" + #"unix:/tmp/something" # absolute paths may be used + #"tcp:4713" # IPv4 and IPv6 on all addresses + #"tcp:[::]:9999" # IPv6 on all addresses + #"tcp:127.0.0.1:8888" # IPv4 on a single address + # + #{ address = "tcp:4713" # address + # max-clients = 64 # maximum number of clients + # listen-backlog = 32 # backlog in the server listen queue + # client.access = "restricted" # permissions for clients + #} + ] + # These overrides are only applied when running in a vm. + vm.overrides = { + } +} diff --git a/src/modules/meson.build b/src/modules/meson.build index 3c8d6db5b..0214abc78 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -5,6 +5,7 @@ subdir('spa') module_sources = [ 'module-access.c', 'module-adapter.c', + 'module-avbtp.c', 'module-client-device.c', 'module-client-node.c', 'module-echo-cancel.c', @@ -516,3 +517,20 @@ pipewire_module_fallback_sink = shared_library('pipewire-module-fallback-sink', install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], ) + +build_module_avbtp = get_option('avb').allowed() +if build_module_avbtp +pipewire_module_avbtp = shared_library('pipewire-module-avbtp', + [ 'module-avbtp.c', + 'module-avbtp/avb.c', + 'module-avbtp/adp.c', + 'module-avbtp/avdecc.c', + 'module-avbtp/maap.c' ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], +) +endif +summary({'avbtp': build_module_avbtp}, bool_yn: true, section: 'Optional Modules') diff --git a/src/modules/module-avbtp.c b/src/modules/module-avbtp.c new file mode 100644 index 000000000..a86b9590d --- /dev/null +++ b/src/modules/module-avbtp.c @@ -0,0 +1,134 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +#include "module-avbtp/avb.h" + +/** \page page_module_avb PipeWire Module: AVB + */ + +#define NAME "avb" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define MODULE_USAGE " " + +static const struct spa_dict_item module_props[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, + { PW_KEY_MODULE_DESCRIPTION, "Manage an AVB network" }, + { PW_KEY_MODULE_USAGE, MODULE_USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + + +struct impl { + struct pw_context *context; + + struct pw_impl_module *module; + struct spa_hook module_listener; + + struct pw_properties *properties; + + struct pw_avb *avb; +}; + +static void impl_free(struct impl *impl) +{ + pw_properties_free(impl->properties); + free(impl); +} + +static void module_destroy(void *data) +{ + struct impl *impl = data; + spa_hook_remove(&impl->module_listener); + impl_free(impl); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, +}; + +SPA_EXPORT +int pipewire__module_init(struct pw_impl_module *module, const char *args) +{ + struct pw_context *context = pw_impl_module_get_context(module); + struct pw_properties *props; + struct impl *impl; + int res; + + PW_LOG_TOPIC_INIT(mod_topic); + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + goto error_errno; + + pw_log_debug("module %p: new %s", impl, args); + + if (args == NULL) + args = ""; + + props = pw_properties_new_string(args); + if (props == NULL) + goto error_errno; + + impl->module = module; + impl->context = context; + impl->properties = props; + + impl->avb = pw_avb_new(context, props, 0); + if (impl->avb == NULL) + goto error_errno; + + pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + return 0; + +error_errno: + res = -errno; + if (impl) + impl_free(impl); + return res; +} diff --git a/src/modules/module-avbtp/aaf.h b/src/modules/module-avbtp/aaf.h new file mode 100644 index 000000000..45cb509df --- /dev/null +++ b/src/modules/module-avbtp/aaf.h @@ -0,0 +1,139 @@ +/* AVBTP support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVBTP_AAF_H +#define AVBTP_AAF_H + +struct avbtp_packet_aaf { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned mr:1; + unsigned _r1:1; + unsigned gv:1; + unsigned tv:1; + + uint8_t seq_number; + + unsigned _r2:7; + unsigned tu:1; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned tv:1; + unsigned gv:1; + unsigned _r1:1; + unsigned mr:1; + unsigned version:3; + unsigned sv:1; + + uint8_t seq_num; + + unsigned tu:1; + unsigned _r2:7; +#endif + uint64_t stream_id; + uint32_t timestamp; +#define AVBTP_AAF_FORMAT_USER 0x00 +#define AVBTP_AAF_FORMAT_FLOAT_32BIT 0x01 +#define AVBTP_AAF_FORMAT_INT_32BIT 0x02 +#define AVBTP_AAF_FORMAT_INT_24BIT 0x03 +#define AVBTP_AAF_FORMAT_INT_16BIT 0x04 +#define AVBTP_AAF_FORMAT_AES3_32BIT 0x05 + uint8_t format; + +#define AVBTP_AAF_PCM_NSR_USER 0x00 +#define AVBTP_AAF_PCM_NSR_8KHZ 0x01 +#define AVBTP_AAF_PCM_NSR_16KHZ 0x02 +#define AVBTP_AAF_PCM_NSR_32KHZ 0x03 +#define AVBTP_AAF_PCM_NSR_44_1KHZ 0x04 +#define AVBTP_AAF_PCM_NSR_48KHZ 0x05 +#define AVBTP_AAF_PCM_NSR_88_2KHZ 0x06 +#define AVBTP_AAF_PCM_NSR_96KHZ 0x07 +#define AVBTP_AAF_PCM_NSR_176_4KHZ 0x08 +#define AVBTP_AAF_PCM_NSR_192KHZ 0x09 +#define AVBTP_AAF_PCM_NSR_24KHZ 0x0A +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned nsr:4; + unsigned _r3:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned _r3:4; + unsigned nsr:4; +#endif + uint8_t chan_per_frame; + uint8_t bit_depth; + uint16_t data_len; + +#define AVBTP_AAF_PCM_SP_NORMAL 0x00 +#define AVBTP_AAF_PCM_SP_SPARSE 0x01 +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned _r4:3; + unsigned sp:1; + unsigned event:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned event:4; + unsigned sp:1; + unsigned _r4:3; +#endif + uint8_t _r5; + uint8_t payload[0]; +} __attribute__ ((__packed__)); + +#define AVBTP_PACKET_AAF_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define AVBTP_PACKET_AAF_SET_SV(p,v) ((p)->sv = (v)) +#define AVBTP_PACKET_AAF_SET_VERSION(p,v) ((p)->version = (v)) +#define AVBTP_PACKET_AAF_SET_MR(p,v) ((p)->mr = (v)) +#define AVBTP_PACKET_AAF_SET_GV(p,v) ((p)->gv = (v)) +#define AVBTP_PACKET_AAF_SET_TV(p,v) ((p)->tv = (v)) +#define AVBTP_PACKET_AAF_SET_SEQ_NUM(p,v) ((p)->seq_num = (v)) +#define AVBTP_PACKET_AAF_SET_TU(p,v) ((p)->tu = (v)) +#define AVBTP_PACKET_AAF_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) +#define AVBTP_PACKET_AAF_SET_TIMESTAMP(p,v) ((p)->timestamp = htonl(v)) +#define AVBTP_PACKET_AAF_SET_DATA_LEN(p,v) ((p)->data_len = htons(v)) +#define AVBTP_PACKET_AAF_SET_FORMAT(p,v) ((p)->format = (v)) +#define AVBTP_PACKET_AAF_SET_NSR(p,v) ((p)->nsr = (v)) +#define AVBTP_PACKET_AAF_SET_CHAN_PER_FRAME(p,v) ((p)->chan_per_frame = (v)) +#define AVBTP_PACKET_AAF_SET_BIT_DEPTH(p,v) ((p)->bit_depth = (v)) +#define AVBTP_PACKET_AAF_SET_SP(p,v) ((p)->sp = (v)) +#define AVBTP_PACKET_AAF_SET_EVENT(p,v) ((p)->event = (v)) + +#define AVBTP_PACKET_AAF_GET_SUBTYPE(p) ((p)->subtype) +#define AVBTP_PACKET_AAF_GET_SV(p) ((p)->sv) +#define AVBTP_PACKET_AAF_GET_VERSION(p) ((p)->version) +#define AVBTP_PACKET_AAF_GET_MR(p) ((p)->mr) +#define AVBTP_PACKET_AAF_GET_GV(p) ((p)->gv) +#define AVBTP_PACKET_AAF_GET_TV(p) ((p)->tv) +#define AVBTP_PACKET_AAF_GET_SEQ_NUM(p) ((p)->seq_num) +#define AVBTP_PACKET_AAF_GET_TU(p) ((p)->tu) +#define AVBTP_PACKET_AAF_GET_STREAM_ID(p) be64toh((p)->stream_id) +#define AVBTP_PACKET_AAF_GET_TIMESTAMP(p) ntohl((p)->timestamp) +#define AVBTP_PACKET_AAF_GET_DATA_LEN(p) ntohs((p)->data_len) +#define AVBTP_PACKET_AAF_GET_FORMAT(p) ((p)->format) +#define AVBTP_PACKET_AAF_GET_NSR(p) ((p)->nsr) +#define AVBTP_PACKET_AAF_GET_CHAN_PER_FRAME(p) ((p)->chan_per_frame) +#define AVBTP_PACKET_AAF_GET_BIT_DEPTH(p) ((p)->bit_depth) +#define AVBTP_PACKET_AAF_GET_SP(p) ((p)->sp) +#define AVBTP_PACKET_AAF_GET_EVENT(p) ((p)->event) + + +#endif /* AVBTP_AAF_H */ diff --git a/src/modules/module-avbtp/adp.c b/src/modules/module-avbtp/adp.c new file mode 100644 index 000000000..8f412b798 --- /dev/null +++ b/src/modules/module-avbtp/adp.c @@ -0,0 +1,382 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +#include + +#include "adp.h" +#include "internal.h" + +struct entity { + struct spa_list link; + struct avbtp_packet_adp packet; + uint64_t last_time; +}; + +struct adp { + struct server *server; + struct spa_hook server_listener; + + struct spa_list entities; +}; + +struct entity *find_entity_by_id(struct adp *adp, uint64_t id) +{ + struct entity *e; + spa_list_for_each(e, &adp->entities, link) + if (AVBTP_PACKET_ADP_GET_ENTITY_ID(&e->packet) == id) + return e; + return NULL; +} + +struct bit_info { + uint32_t bits; + const char *value; +}; + +static const struct bit_info entity_capabilities_info[] = { + { AVBTP_ADP_ENTITY_CAPABILITY_EFU_MODE, "EFU Mode" }, + { AVBTP_ADP_ENTITY_CAPABILITY_ADDRESS_ACCESS_SUPPORTED, "Address Access Supported" }, + { AVBTP_ADP_ENTITY_CAPABILITY_GATEWAY_ENTITY, "Gateway Entity" }, + { AVBTP_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED, "AEM Supported" }, + { AVBTP_ADP_ENTITY_CAPABILITY_LEGACY_AVC, "Legacy AVC" }, + { AVBTP_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_SUPPORTED, "Association Id Supported" }, + { AVBTP_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_VALID, "Association Id Valid" }, + { AVBTP_ADP_ENTITY_CAPABILITY_VENDOR_UNIQUE_SUPPORTED, "Vendor Unique Supported" }, + { AVBTP_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED, "Class A Supported" }, + { AVBTP_ADP_ENTITY_CAPABILITY_CLASS_B_SUPPORTED, "Class B Supported" }, + { AVBTP_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED, "gPTP Supported" }, + { AVBTP_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_SUPPORTED, "AEM Authentication Supported" }, + { AVBTP_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_REQUIRED, "AEM Authentication Required" }, + { AVBTP_ADP_ENTITY_CAPABILITY_AEM_PERSISTENT_ACQUIRE_SUPPORTED, "AEM Persisitent Acquire Supported" }, + { AVBTP_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID, "AEM Identify Control Index Valid" }, + { AVBTP_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID, "AEM Interface Index Valid" }, + { AVBTP_ADP_ENTITY_CAPABILITY_GENERAL_CONTROLLER_IGNORE, "General Controller Ignore" }, + { AVBTP_ADP_ENTITY_CAPABILITY_ENTITY_NOT_READY, "Entity Not Ready" }, + { 0, NULL }, +}; +static const struct bit_info talker_capabilities_info[] = { + { AVBTP_ADP_TALKER_CAPABILITY_IMPLEMENTED, "Implemented" }, + { AVBTP_ADP_TALKER_CAPABILITY_OTHER_SOURCE, "Other Source" }, + { AVBTP_ADP_TALKER_CAPABILITY_CONTROL_SOURCE, "Control Source" }, + { AVBTP_ADP_TALKER_CAPABILITY_MEDIA_CLOCK_SOURCE, "Media Clock Source" }, + { AVBTP_ADP_TALKER_CAPABILITY_SMPTE_SOURCE, "SMPTE Source" }, + { AVBTP_ADP_TALKER_CAPABILITY_MIDI_SOURCE, "MIDI Source" }, + { AVBTP_ADP_TALKER_CAPABILITY_AUDIO_SOURCE, "Audio Source" }, + { AVBTP_ADP_TALKER_CAPABILITY_VIDEO_SOURCE, "Video Source" }, + { 0, NULL }, +}; + +static const struct bit_info listener_capabilities_info[] = { + { AVBTP_ADP_LISTENER_CAPABILITY_IMPLEMENTED, "Implemented" }, + { AVBTP_ADP_LISTENER_CAPABILITY_OTHER_SINK, "Other Sink" }, + { AVBTP_ADP_LISTENER_CAPABILITY_CONTROL_SINK, "Control Sink" }, + { AVBTP_ADP_LISTENER_CAPABILITY_MEDIA_CLOCK_SINK, "Media Clock Sink" }, + { AVBTP_ADP_LISTENER_CAPABILITY_SMPTE_SINK, "SMPTE Sink" }, + { AVBTP_ADP_LISTENER_CAPABILITY_MIDI_SINK, "MIDI Sink" }, + { AVBTP_ADP_LISTENER_CAPABILITY_AUDIO_SINK, "Audio Sink" }, + { AVBTP_ADP_LISTENER_CAPABILITY_VIDEO_SINK, "Video Sink" }, + { 0, NULL }, +}; + +static const struct bit_info controller_capabilities_info[] = { + { AVBTP_ADP_CONTROLLER_CAPABILITY_IMPLEMENTED, "Implemented" }, + { AVBTP_ADP_CONTROLLER_CAPABILITY_LAYER3_PROXY, "Layer 3 Proxy" }, + { 0, NULL }, +}; +static void print_bit_info(int indent, uint32_t bits, const struct bit_info *info) +{ + uint32_t i; + for (i = 0; info[i].value; i++) { + if ((info[i].bits & bits) == info[i].bits) + pw_log_info("%*.s%08x %s", indent, "", info[i].bits, info[i].value); + } +} + +static const char *message_type_as_string(uint8_t message_type) +{ + switch (message_type) { + case AVBTP_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE: + return "ENTITY_AVAIALABLE"; + case AVBTP_ADP_MESSAGE_TYPE_ENTITY_DEPARTING: + return "ENTITY_DEPARTING"; + case AVBTP_ADP_MESSAGE_TYPE_ENTITY_DISCOVER: + return "ENTITY_DISCOVER"; + } + return "INVALID"; +} + +#define KEY_VALID_TIME "valid-time" +#define KEY_ENTITY_ID "entity-id" +#define KEY_ENTITY_MODEL_ID "entity-model-id" +#define KEY_ENTITY_CAPABILITIES "entity-capabilities" +#define KEY_TALKER_STREAM_SOURCES "talker-stream-sources" +#define KEY_TALKER_CAPABILITIES "talker-capabilities" +#define KEY_LISTENER_STREAM_SINKS "listener-stream-sinks" +#define KEY_LISTENER_CAPABILITIES "listener-capabilities" +#define KEY_CONTROLLER_CAPABILITIES "controller-capabilities" +#define KEY_AVAILABLE_INDEX "available-index" +#define KEY_GPTP_GRANDMASTER_ID "gptp-grandmaster-id" +#define KEY_ASSOCIATION_ID "association-id" + +static inline char *format_id(char *str, size_t size, const uint64_t id) +{ + snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x", + (uint8_t)(id >> 56), + (uint8_t)(id >> 48), + (uint8_t)(id >> 40), + (uint8_t)(id >> 32), + (uint8_t)(id >> 24), + (uint8_t)(id >> 16), + (uint16_t)(id)); + return str; +} +static void adp_message_debug(struct adp *adp, const struct avbtp_packet_adp *p) +{ + uint32_t v; + char buf[256]; + + v = AVBTP_PACKET_ADP_GET_MESSAGE_TYPE(p); + pw_log_info("message-type: %d (%s)", v, message_type_as_string(v)); + pw_log_info(" length: %d", AVBTP_PACKET_ADP_GET_LENGTH(p)); + pw_log_info(" "KEY_VALID_TIME": %d", AVBTP_PACKET_ADP_GET_VALID_TIME(p)); + pw_log_info(" "KEY_ENTITY_ID": %s", format_id(buf, sizeof(buf), AVBTP_PACKET_ADP_GET_ENTITY_ID(p))); + pw_log_info(" "KEY_ENTITY_MODEL_ID": 0x%"PRIx64, AVBTP_PACKET_ADP_GET_ENTITY_MODEL_ID(p)); + v = AVBTP_PACKET_ADP_GET_ENTITY_CAPABILITIES(p); + pw_log_info(" "KEY_ENTITY_CAPABILITIES": 0x%08x", v); + print_bit_info(4, v, entity_capabilities_info); + pw_log_info(" "KEY_TALKER_STREAM_SOURCES": %d", AVBTP_PACKET_ADP_GET_TALKER_STREAM_SOURCES(p)); + v = AVBTP_PACKET_ADP_GET_TALKER_CAPABILITIES(p); + pw_log_info(" "KEY_TALKER_CAPABILITIES": %04x", v); + print_bit_info(4, v, talker_capabilities_info); + pw_log_info(" "KEY_LISTENER_STREAM_SINKS": %d", AVBTP_PACKET_ADP_GET_LISTENER_STREAM_SINKS(p)); + v = AVBTP_PACKET_ADP_GET_LISTENER_CAPABILITIES(p); + pw_log_info(" "KEY_LISTENER_CAPABILITIES": %04x", v); + print_bit_info(4, v, listener_capabilities_info); + v = AVBTP_PACKET_ADP_GET_CONTROLLER_CAPABILITIES(p); + pw_log_info(" "KEY_CONTROLLER_CAPABILITIES": %08x", v); + print_bit_info(4, v, controller_capabilities_info); + pw_log_info(" "KEY_AVAILABLE_INDEX": 0x%08x", AVBTP_PACKET_ADP_GET_AVAILABLE_INDEX(p)); + pw_log_info(" "KEY_GPTP_GRANDMASTER_ID": 0x%"PRIx64, AVBTP_PACKET_ADP_GET_GPTP_GRANDMASTER_ID(p)); + pw_log_info(" "KEY_ASSOCIATION_ID": 0x%08x", AVBTP_PACKET_ADP_GET_ASSOCIATION_ID(p)); +} + +static int adp_message(void *data, uint64_t now, const void *message, int len) +{ + struct adp *adp = data; + const struct avbtp_packet_adp *p = message; + struct entity *e; + + if (AVBTP_PACKET_GET_SUBTYPE(p) != AVBTP_SUBTYPE_ADP) + return 0; + + + e = find_entity_by_id(adp, AVBTP_PACKET_ADP_GET_ENTITY_ID(p)); + if (e == NULL) { + e = calloc(1, sizeof(*e)); + if (e == NULL) + return -errno; + + e->packet = *p; + spa_list_append(&adp->entities, &e->link); + + if (adp->server->debug_messages) + adp_message_debug(adp, p); + } + e->last_time = now; + + return 0; +} + +static void adp_destroy(void *data) +{ + struct adp *adp = data; + spa_hook_remove(&adp->server_listener); + free(adp); +} + +static void adp_periodic(void *data, uint64_t now) +{ + struct adp *adp = data; +} + +static int parse_id(const char *value, int len, uint64_t *id) +{ + char str[64]; + uint8_t v[6]; + uint16_t unique_id; + if (spa_json_parse_stringn(value, len, str, sizeof(str)) <= 0) + return 0; + if (sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hx", + &v[0], &v[1], &v[2], &v[3], + &v[4], &v[5], &unique_id) != 7) + return -EINVAL; + *id = (uint64_t) v[0] << 56 | + (uint64_t) v[1] << 48 | + (uint64_t) v[2] << 40 | + (uint64_t) v[3] << 32 | + (uint64_t) v[4] << 24 | + (uint64_t) v[5] << 16 | + unique_id; + return 0; +} +static int parse_bits(const char *value, int len, int *bits) +{ + *bits = 0; + return 0; +} + +static int do_advertise(struct adp *adp, const char *args) +{ + struct entity *e; + struct spa_json it[2]; + char key[128]; + struct avbtp_packet_adp *p; + + e = calloc(1, sizeof(*e)); + if (e == NULL) + return -errno; + + spa_json_init(&it[0], args, strlen(args)); + if (spa_json_enter_object(&it[0], &it[1]) <= 0) + return -EINVAL; + + p = &e->packet; + + while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { + int len, int_val; + const char *value; + uint64_t id_val; + + if ((len = spa_json_next(&it[1], &value)) <= 0) + break; + + if (spa_json_is_null(value, len)) + continue; + + if (spa_streq(key, KEY_VALID_TIME)) { + if (spa_json_parse_int(value, len, &int_val)) + AVBTP_PACKET_ADP_SET_VALID_TIME(p, int_val); + } else if (spa_streq(key, KEY_ENTITY_ID)) { + if (parse_id(value, len, &id_val)) + AVBTP_PACKET_ADP_SET_ENTITY_ID(p, id_val); + } else if (spa_streq(key, KEY_ENTITY_MODEL_ID)) { + if (parse_id(value, len, &id_val)) + AVBTP_PACKET_ADP_SET_ENTITY_MODEL_ID(p, id_val); + } else if (spa_streq(key, KEY_ENTITY_CAPABILITIES)) { + if (parse_bits(value, len, &int_val)) + AVBTP_PACKET_ADP_SET_ENTITY_CAPABILITIES(p, int_val); + } else if (spa_streq(key, KEY_TALKER_STREAM_SOURCES)) { + if (spa_json_parse_int(value, len, &int_val)) + AVBTP_PACKET_ADP_SET_TALKER_STREAM_SOURCES(p, int_val); + } else if (spa_streq(key, KEY_TALKER_CAPABILITIES)) { + if (parse_bits(value, len, &int_val)) + AVBTP_PACKET_ADP_SET_TALKER_CAPABILITIES(p, int_val); + } else if (spa_streq(key, KEY_LISTENER_STREAM_SINKS)) { + if (spa_json_parse_int(value, len, &int_val)) + AVBTP_PACKET_ADP_SET_LISTENER_STREAM_SINKS(p, int_val); + } else if (spa_streq(key, KEY_LISTENER_CAPABILITIES)) { + if (parse_bits(value, len, &int_val)) + AVBTP_PACKET_ADP_SET_LISTENER_CAPABILITIES(p, int_val); + } else if (spa_streq(key, KEY_CONTROLLER_CAPABILITIES)) { + if (parse_bits(value, len, &int_val)) + AVBTP_PACKET_ADP_SET_CONTROLLER_CAPABILITIES(p, int_val); + } else if (spa_streq(key, KEY_AVAILABLE_INDEX)) { + if (spa_json_parse_int(value, len, &int_val)) + AVBTP_PACKET_ADP_SET_AVAILABLE_INDEX(p, int_val); + } else if (spa_streq(key, KEY_GPTP_GRANDMASTER_ID)) { + if (parse_id(value, len, &id_val)) + AVBTP_PACKET_ADP_SET_GPTP_GRANDMASTER_ID(p, id_val); + } else if (spa_streq(key, KEY_ASSOCIATION_ID)) { + if (spa_json_parse_int(value, len, &int_val)) + AVBTP_PACKET_ADP_SET_ASSOCIATION_ID(p, int_val); + } + } + if (find_entity_by_id(adp, AVBTP_PACKET_ADP_GET_ENTITY_ID(p))) { + free(e); + return -EEXIST; + } + spa_list_append(&adp->entities, &e->link); + + if (adp->server->debug_messages) + adp_message_debug(adp, p); + + return 0; +} + +static int do_depart(struct adp *adp, const char *args) +{ + return 0; +} + +static int do_discover(struct adp *adp, const char *args) +{ + return 0; +} + +static int adp_command(void *data, const char *command, const char *args) +{ + struct adp *adp = data; + int res; + + if (spa_streq(command, "/adp/advertise")) + res = do_advertise(adp, args); + else if (spa_streq(command, "/adp/depart")) + res = do_depart(adp, args); + else if (spa_streq(command, "/adp/discover")) + res = do_discover(adp, args); + else + res = -ENOTSUP; + return res; +} + +static const struct server_events server_events = { + AVBTP_VERSION_SERVER_EVENTS, + .destroy = adp_destroy, + .message = adp_message, + .periodic = adp_periodic, + .command = adp_command +}; + +struct avbtp_adp *avbtp_adp_register(struct server *server) +{ + struct adp *adp; + + adp = calloc(1, sizeof(*adp)); + if (adp == NULL) + return NULL; + + adp->server = server; + spa_list_init(&adp->entities); + + avdecc_server_add_listener(server, &adp->server_listener, &server_events, adp); + + return (struct avbtp_adp*)adp; +} + +void avbtp_adp_unregister(struct avbtp_adp *adp) +{ + adp_destroy(adp); +} diff --git a/src/modules/module-avbtp/adp.h b/src/modules/module-avbtp/adp.h new file mode 100644 index 000000000..02a08a1b3 --- /dev/null +++ b/src/modules/module-avbtp/adp.h @@ -0,0 +1,148 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVBTP_ADP_H +#define AVBTP_ADP_H + +#include "packets.h" +#include "internal.h" + +#define AVBTP_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE 0 +#define AVBTP_ADP_MESSAGE_TYPE_ENTITY_DEPARTING 1 +#define AVBTP_ADP_MESSAGE_TYPE_ENTITY_DISCOVER 2 + +#define AVBTP_ADP_ENTITY_CAPABILITY_EFU_MODE (1u<<0) +#define AVBTP_ADP_ENTITY_CAPABILITY_ADDRESS_ACCESS_SUPPORTED (1u<<1) +#define AVBTP_ADP_ENTITY_CAPABILITY_GATEWAY_ENTITY (1u<<2) +#define AVBTP_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED (1u<<3) +#define AVBTP_ADP_ENTITY_CAPABILITY_LEGACY_AVC (1u<<4) +#define AVBTP_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_SUPPORTED (1u<<5) +#define AVBTP_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_VALID (1u<<6) +#define AVBTP_ADP_ENTITY_CAPABILITY_VENDOR_UNIQUE_SUPPORTED (1u<<7) +#define AVBTP_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED (1u<<8) +#define AVBTP_ADP_ENTITY_CAPABILITY_CLASS_B_SUPPORTED (1u<<9) +#define AVBTP_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED (1u<<10) +#define AVBTP_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_SUPPORTED (1u<<11) +#define AVBTP_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_REQUIRED (1u<<12) +#define AVBTP_ADP_ENTITY_CAPABILITY_AEM_PERSISTENT_ACQUIRE_SUPPORTED (1u<<13) +#define AVBTP_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID (1u<<14) +#define AVBTP_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID (1u<<15) +#define AVBTP_ADP_ENTITY_CAPABILITY_GENERAL_CONTROLLER_IGNORE (1u<<16) +#define AVBTP_ADP_ENTITY_CAPABILITY_ENTITY_NOT_READY (1u<<17) + +#define AVBTP_ADP_TALKER_CAPABILITY_IMPLEMENTED (1u<<0) +#define AVBTP_ADP_TALKER_CAPABILITY_OTHER_SOURCE (1u<<9) +#define AVBTP_ADP_TALKER_CAPABILITY_CONTROL_SOURCE (1u<<10) +#define AVBTP_ADP_TALKER_CAPABILITY_MEDIA_CLOCK_SOURCE (1u<<11) +#define AVBTP_ADP_TALKER_CAPABILITY_SMPTE_SOURCE (1u<<12) +#define AVBTP_ADP_TALKER_CAPABILITY_MIDI_SOURCE (1u<<13) +#define AVBTP_ADP_TALKER_CAPABILITY_AUDIO_SOURCE (1u<<14) +#define AVBTP_ADP_TALKER_CAPABILITY_VIDEO_SOURCE (1u<<15) + +#define AVBTP_ADP_LISTENER_CAPABILITY_IMPLEMENTED (1u<<0) +#define AVBTP_ADP_LISTENER_CAPABILITY_OTHER_SINK (1u<<9) +#define AVBTP_ADP_LISTENER_CAPABILITY_CONTROL_SINK (1u<<10) +#define AVBTP_ADP_LISTENER_CAPABILITY_MEDIA_CLOCK_SINK (1u<<11) +#define AVBTP_ADP_LISTENER_CAPABILITY_SMPTE_SINK (1u<<12) +#define AVBTP_ADP_LISTENER_CAPABILITY_MIDI_SINK (1u<<13) +#define AVBTP_ADP_LISTENER_CAPABILITY_AUDIO_SINK (1u<<14) +#define AVBTP_ADP_LISTENER_CAPABILITY_VIDEO_SINK (1u<<15) + +#define AVBTP_ADP_CONTROLLER_CAPABILITY_IMPLEMENTED (1u<<0) +#define AVBTP_ADP_CONTROLLER_CAPABILITY_LAYER3_PROXY (1u<<1) + +struct avbtp_packet_adp { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned message_type:4; + + unsigned valid_time:5; + unsigned len1:3; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned message_type:4; + unsigned version:3; + unsigned sv:1; + + unsigned len1:3; + unsigned valid_time:5; +#endif + uint8_t len2:8; + uint64_t entity_id; + uint64_t entity_model_id; + uint32_t entity_capabilities; + uint16_t talker_stream_sources; + uint16_t talker_capabilities; + uint16_t listener_stream_sinks; + uint16_t listener_capabilities; + uint32_t controller_capabilities; + uint32_t available_index; + uint64_t gptp_grandmaster_id; + uint32_t reserved0; + uint16_t identify_control_index; + uint16_t interface_index; + uint32_t association_id; + uint32_t reserved1; +} __attribute__ ((__packed__)); + +#define AVBTP_PACKET_ADP_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define AVBTP_PACKET_ADP_SET_SV(p,v) ((p)->sv = (v)) +#define AVBTP_PACKET_ADP_SET_VERSION(p,v) ((p)->version = (v)) +#define AVBTP_PACKET_ADP_SET_MESSAGE_TYPE(p,v) ((p)->message_type = (v)) +#define AVBTP_PACKET_ADP_SET_VALID_TIME(p,v) ((p)->valid_time = (v)) +#define AVBTP_PACKET_ADP_SET_LENGTH(p,v) ((p)->len1 = ((v) >> 8),(p)->len2 = (v)) +#define AVBTP_PACKET_ADP_SET_ENTITY_ID(p,v) ((p)->entity_id = htobe64(v)) +#define AVBTP_PACKET_ADP_SET_ENTITY_MODEL_ID(p,v) ((p)->entity_model_id = htobe64(v)) +#define AVBTP_PACKET_ADP_SET_ENTITY_CAPABILITIES(p,v) ((p)->entity_capabilities = htonl(v)) +#define AVBTP_PACKET_ADP_SET_TALKER_STREAM_SOURCES(p,v) ((p)->talker_stream_sources = htons(v)) +#define AVBTP_PACKET_ADP_SET_TALKER_CAPABILITIES(p,v) ((p)->talker_capabilities = htons(v)) +#define AVBTP_PACKET_ADP_SET_LISTENER_STREAM_SINKS(p,v) ((p)->listener_stream_sinks = htons(v)) +#define AVBTP_PACKET_ADP_SET_LISTENER_CAPABILITIES(p,v) ((p)->listener_capabilities = htons(v)) +#define AVBTP_PACKET_ADP_SET_CONTROLLER_CAPABILITIES(p,v) ((p)->controller_capabilities = htonl(v)) +#define AVBTP_PACKET_ADP_SET_AVAILABLE_INDEX(p,v) ((p)->available_index = htonl(v)) +#define AVBTP_PACKET_ADP_SET_GPTP_GRANDMASTER_ID(p,v) ((p)->gptp_grandmaster_id = htobe64(v)) +#define AVBTP_PACKET_ADP_SET_ASSOCIATION_ID(p,v) ((p)->association_id = htonl(v)) + +#define AVBTP_PACKET_ADP_GET_SUBTYPE(p) ((p)->subtype) +#define AVBTP_PACKET_ADP_GET_SV(p) ((p)->sv) +#define AVBTP_PACKET_ADP_GET_VERSION(p) ((p)->version) +#define AVBTP_PACKET_ADP_GET_MESSAGE_TYPE(p) ((p)->message_type) +#define AVBTP_PACKET_ADP_GET_VALID_TIME(p) ((p)->valid_time) +#define AVBTP_PACKET_ADP_GET_LENGTH(p) (((p)->len1 << 8) | (p)->len2) +#define AVBTP_PACKET_ADP_GET_ENTITY_ID(p) be64toh((p)->entity_id) +#define AVBTP_PACKET_ADP_GET_ENTITY_MODEL_ID(p) be64toh((p)->entity_model_id) +#define AVBTP_PACKET_ADP_GET_ENTITY_CAPABILITIES(p) ntohl((p)->entity_capabilities) +#define AVBTP_PACKET_ADP_GET_TALKER_STREAM_SOURCES(p) ntohs((p)->talker_stream_sources) +#define AVBTP_PACKET_ADP_GET_TALKER_CAPABILITIES(p) ntohs((p)->talker_capabilities) +#define AVBTP_PACKET_ADP_GET_LISTENER_STREAM_SINKS(p) ntohs((p)->listener_stream_sinks) +#define AVBTP_PACKET_ADP_GET_LISTENER_CAPABILITIES(p) ntohs((p)->listener_capabilities) +#define AVBTP_PACKET_ADP_GET_CONTROLLER_CAPABILITIES(p) ntohl((p)->controller_capabilities) +#define AVBTP_PACKET_ADP_GET_AVAILABLE_INDEX(p) ntohl((p)->available_index) +#define AVBTP_PACKET_ADP_GET_GPTP_GRANDMASTER_ID(p) be64toh((p)->gptp_grandmaster_id) +#define AVBTP_PACKET_ADP_GET_ASSOCIATION_ID(p) ntohl((p)->association_id) + +struct avbtp_adp *avbtp_adp_register(struct server *server); + +#endif /* AVBTP_ADP_H */ diff --git a/src/modules/module-avbtp/avb.c b/src/modules/module-avbtp/avb.c new file mode 100644 index 000000000..29560c11d --- /dev/null +++ b/src/modules/module-avbtp/avb.c @@ -0,0 +1,93 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "internal.h" + +#include + +struct pw_avb *pw_avb_new(struct pw_context *context, + struct pw_properties *props, size_t user_data_size) +{ + struct impl *impl; + const struct spa_support *support; + uint32_t n_support; + struct spa_cpu *cpu; + const char *str; + int res = 0; + + impl = calloc(1, sizeof(*impl) + user_data_size); + if (impl == NULL) + goto error_exit; + + if (props == NULL) + props = pw_properties_new(NULL, NULL); + if (props == NULL) + goto error_free; + + support = pw_context_get_support(context, &n_support); + cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); + + pw_context_conf_update_props(context, "avb.properties", props); + + if ((str = pw_properties_get(props, "vm.overrides")) != NULL) { + if (cpu != NULL && spa_cpu_get_vm_type(cpu) != SPA_CPU_VM_NONE) + pw_properties_update_string(props, str, strlen(str)); + pw_properties_set(props, "vm.overrides", NULL); + } + + impl->context = context; + impl->loop = pw_context_get_main_loop(context); + impl->props = props; + + impl->work_queue = pw_context_get_work_queue(context); + + spa_list_init(&impl->servers); + + avdecc_server_new(impl, pw_properties_get(props, "ifname"), NULL); + + return (struct pw_avb*)impl; + +error_free: + free(impl); +error_exit: + pw_properties_free(props); + if (res < 0) + errno = -res; + return NULL; +} + +static void impl_free(struct impl *impl) +{ + struct server *s; + + spa_list_consume(s, &impl->servers, link) + avdecc_server_free(s); + free(impl); +} + +void pw_avb_destroy(struct pw_avb *avb) +{ + struct impl *impl = (struct impl*)avb; + impl_free(impl); +} diff --git a/src/modules/module-avbtp/avb.h b/src/modules/module-avbtp/avb.h new file mode 100644 index 000000000..cad7dd2f9 --- /dev/null +++ b/src/modules/module-avbtp/avb.h @@ -0,0 +1,44 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef PIPEWIRE_AVB_H +#define PIPEWIRE_AVB_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct pw_context; +struct pw_properties; +struct pw_avb; + +struct pw_avb *pw_avb_new(struct pw_context *context, + struct pw_properties *props, size_t user_data_size); +void pw_avb_destroy(struct pw_avb *avb); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_AVB_H */ diff --git a/src/modules/module-avbtp/avdecc.c b/src/modules/module-avbtp/avdecc.c new file mode 100644 index 000000000..4b1ad566a --- /dev/null +++ b/src/modules/module-avbtp/avdecc.c @@ -0,0 +1,214 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "avb.h" +#include "packets.h" +#include "internal.h" +#include "adp.h" +#include "maap.h" + +#define DEFAULT_INTERVAL 1 + +#define server_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct server_events, m, v, ##__VA_ARGS__) +#define server_emit_destroy(s) server_emit(s, destroy, 0) +#define server_emit_message(s,n,m,l) server_emit(s, message, 0, n, m, l) +#define server_emit_periodic(s,n) server_emit(s, periodic, 0, n) +#define server_emit_command(s,c,a) server_emit(s, command, 0, c, a) + +static void on_timer_event(void *data, uint64_t expirations) +{ + struct server *server = data; + struct timespec now; + + clock_gettime(CLOCK_REALTIME, &now); + server_emit_periodic(server, SPA_TIMESPEC_TO_NSEC(&now)); +} + +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct server *server = data; + struct timespec now; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer[2048]; + + len = read(fd, buffer, sizeof(buffer)); + if (len < 0) { + pw_log_warn("got error: %m"); + } + else if (len < (int)sizeof(struct avbtp_packet_common)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avbtp_packet_common)); + } else { + clock_gettime(CLOCK_REALTIME, &now); + server_emit_message(server, SPA_TIMESPEC_TO_NSEC(&now), + buffer, len); + } + } +} + +static int setup_socket(struct server *server) +{ + struct impl *impl = server->impl; + int fd, res, ifindex; + struct ifreq req; + struct packet_mreq mreq; + struct sockaddr_ll sll; + struct timespec value, interval; + + fd = socket(AF_PACKET, SOCK_DGRAM|SOCK_NONBLOCK, htons(ETH_P_TSN)); + if (fd < 0) { + pw_log_error("socket() failed: %m"); + return -errno; + } + + spa_zero(req); + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); + if (ioctl(fd, SIOCGIFINDEX, &req) < 0) { + res = -errno; + pw_log_error("SIOCGIFINDEX %s failed: %m", server->ifname); + goto error_close; + } + ifindex = req.ifr_ifindex; + + spa_zero(req); + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); + if (ioctl(fd, SIOCGIFHWADDR, &req) < 0) { + res = -errno; + pw_log_error("SIOCGIFHWADDR %s failed: %m", server->ifname); + goto error_close; + } + memcpy(server->mac_addr, req.ifr_hwaddr.sa_data, sizeof(server->mac_addr)); + + spa_zero(sll); + sll.sll_family = AF_PACKET; + sll.sll_protocol = htons(ETH_P_TSN); + sll.sll_ifindex = ifindex; + if (bind(fd, (struct sockaddr *) &sll, sizeof(sll)) < 0) { + res = -errno; + pw_log_error("bind() failed: %m"); + goto error_close; + } + + spa_zero(mreq); + mreq.mr_ifindex = ifindex; + mreq.mr_type = PACKET_MR_ALLMULTI; + if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, + &mreq, sizeof(struct packet_mreq)) < 0) { + res = -errno; + pw_log_error("setsockopt(ADD_MEMBERSHIP) failed: %m"); + goto error_close; + } + + server->source = pw_loop_add_io(impl->loop, fd, SPA_IO_IN, true, on_socket_data, server); + if (server->source == NULL) { + res = -errno; + pw_log_error("server %p: can't create server source: %m", impl); + goto error_close; + } + server->timer = pw_loop_add_timer(impl->loop, on_timer_event, server); + if (server->timer == NULL) { + res = -errno; + pw_log_error("server %p: can't create timer source: %m", impl); + goto error_close; + } + value.tv_sec = 0; + value.tv_nsec = 1; + interval.tv_sec = DEFAULT_INTERVAL; + interval.tv_nsec = 0; + pw_loop_update_timer(impl->loop, server->timer, &value, &interval, false); + + return 0; + +error_close: + close(fd); + return res; +} + +struct server *avdecc_server_new(struct impl *impl, const char *ifname, struct spa_dict *props) +{ + struct server *server; + int res = 0; + + server = calloc(1, sizeof(*server)); + if (server == NULL) + return NULL; + + server->impl = impl; + spa_list_append(&impl->servers, &server->link); + server->ifname = strdup(ifname); + spa_hook_list_init(&server->listener_list); + + server->debug_messages = true; + + if ((res = setup_socket(server)) < 0) + goto error_free; + + avbtp_adp_register(server); + avbtp_maap_register(server); + + return server; + +error_free: + free(server); + if (res < 0) + errno = -res; + return NULL; +} + +void avdecc_server_add_listener(struct server *server, + struct spa_hook *listener, + const struct server_events *events, + void *data) +{ + spa_hook_list_append(&server->listener_list, listener, events, data); +} + +void avdecc_server_free(struct server *server) +{ + struct impl *impl = server->impl; + + spa_list_remove(&server->link); + if (server->source) + pw_loop_destroy_source(impl->loop, server->source); + if (server->timer) + pw_loop_destroy_source(impl->loop, server->source); + spa_hook_list_clean(&server->listener_list); + free(server); +} diff --git a/src/modules/module-avbtp/internal.h b/src/modules/module-avbtp/internal.h new file mode 100644 index 000000000..5796a8562 --- /dev/null +++ b/src/modules/module-avbtp/internal.h @@ -0,0 +1,83 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_INTERNAL_H +#define AVB_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct impl { + struct pw_loop *loop; + struct pw_context *context; + struct spa_hook context_listener; + + struct pw_properties *props; + struct pw_work_queue *work_queue; + + struct spa_list servers; +}; + +struct server_events { +#define AVBTP_VERSION_SERVER_EVENTS 0 + uint32_t version; + + /** the server is destroyed */ + void (*destroy) (void *data); + + int (*message) (void *data, uint64_t now, const void *message, int len); + + void (*periodic) (void *data, uint64_t now); + + int (*command) (void *data, const char *command, const char *args); +}; + +struct server { + struct spa_list link; + struct impl *impl; + + char *ifname; + struct spa_source *source; + char mac_addr[6]; + struct spa_source *timer; + + struct spa_hook_list listener_list; + + unsigned debug_messages:1; +}; + +struct server *avdecc_server_new(struct impl *impl, const char *ifname, struct spa_dict *props); +void avdecc_server_free(struct server *server); + +void avdecc_server_add_listener(struct server *server, struct spa_hook *listener, + const struct server_events *events, void *data); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AVB_INTERNAL_H */ diff --git a/src/modules/module-avbtp/maap.c b/src/modules/module-avbtp/maap.c new file mode 100644 index 000000000..91902c29c --- /dev/null +++ b/src/modules/module-avbtp/maap.c @@ -0,0 +1,108 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +#include "maap.h" + +struct maap { + struct server *server; + struct spa_hook server_listener; +}; + +static const char *message_type_as_string(uint8_t message_type) +{ + switch (message_type) { + case AVBTP_MAAP_MESSAGE_TYPE_PROBE: + return "PROBE"; + case AVBTP_MAAP_MESSAGE_TYPE_DEFEND: + return "DEFEND"; + case AVBTP_MAAP_MESSAGE_TYPE_ANNOUNCE: + return "ANNOUNCE"; + } + return "INVALID"; +} + +static void maap_message_debug(struct maap *maap, const struct avbtp_packet_maap *p) +{ + uint32_t v; + const uint8_t *addr; + + v = AVBTP_PACKET_MAAP_GET_MESSAGE_TYPE(p); + pw_log_info("message-type: %d (%s)", v, message_type_as_string(v)); + pw_log_info(" maap-version: %d", AVBTP_PACKET_MAAP_GET_MAAP_VERSION(p)); + pw_log_info(" length: %d", AVBTP_PACKET_MAAP_GET_LENGTH(p)); + + pw_log_info(" stream-id: 0x%"PRIx64, AVBTP_PACKET_MAAP_GET_STREAM_ID(p)); + addr = AVBTP_PACKET_MAAP_GET_REQUEST_START(p); + pw_log_info(" request-start: %02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + pw_log_info(" request-count: %d", AVBTP_PACKET_MAAP_GET_REQUEST_COUNT(p)); + addr = AVBTP_PACKET_MAAP_GET_CONFLICT_START(p); + pw_log_info(" conflict-start: %02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + pw_log_info(" conflict-count: %d", AVBTP_PACKET_MAAP_GET_CONFLICT_COUNT(p)); +} + +static int maap_message(void *data, uint64_t now, const void *message, int len) +{ + struct maap *maap = data; + const struct avbtp_packet_maap *p = message; + + if (AVBTP_PACKET_GET_SUBTYPE(p) != AVBTP_SUBTYPE_MAAP) + return 0; + + if (maap->server->debug_messages) + maap_message_debug(maap, p); + + return 0; +} + +static void maap_destroy(void *data) +{ + struct maap *maap = data; + spa_hook_remove(&maap->server_listener); + free(maap); +} + +static const struct server_events server_events = { + AVBTP_VERSION_SERVER_EVENTS, + .destroy = maap_destroy, + .message = maap_message +}; + +int avbtp_maap_register(struct server *server) +{ + struct maap *maap; + + maap = calloc(1, sizeof(*maap)); + if (maap == NULL) + return -errno; + + maap->server = server; + + avdecc_server_add_listener(server, &maap->server_listener, &server_events, maap); + + return 0; +} diff --git a/src/modules/module-avbtp/maap.h b/src/modules/module-avbtp/maap.h new file mode 100644 index 000000000..af0b9cff2 --- /dev/null +++ b/src/modules/module-avbtp/maap.h @@ -0,0 +1,86 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVBTP_MAAP_H +#define AVBTP_MAAP_H + +#include "packets.h" +#include "internal.h" + +#define AVBTP_MAAP_MESSAGE_TYPE_PROBE 1 +#define AVBTP_MAAP_MESSAGE_TYPE_DEFEND 2 +#define AVBTP_MAAP_MESSAGE_TYPE_ANNOUNCE 3 + +struct avbtp_packet_maap { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned message_type:4; + + unsigned maap_version:5; + unsigned len1:3; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned message_type:4; + unsigned version:3; + unsigned sv:1; + + unsigned len1:3; + unsigned maap_version:5; +#endif + uint8_t len2:8; + uint64_t stream_id; + uint8_t request_start[6]; + uint16_t request_count; + uint8_t conflict_start[6]; + uint16_t conflict_count; +} __attribute__ ((__packed__)); + +#define AVBTP_PACKET_MAAP_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define AVBTP_PACKET_MAAP_SET_SV(p,v) ((p)->sv = (v)) +#define AVBTP_PACKET_MAAP_SET_VERSION(p,v) ((p)->version = (v)) +#define AVBTP_PACKET_MAAP_SET_MESSAGE_TYPE(p,v) ((p)->message_type = (v)) +#define AVBTP_PACKET_MAAP_SET_MAAP_VERSION(p,v) ((p)->maap_version = (v)) +#define AVBTP_PACKET_MAAP_SET_LENGTH(p,v) ((p)->len1 = ((v) >> 8),(p)->len2 = (v)) +#define AVBTP_PACKET_MAAP_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) +#define AVBTP_PACKET_MAAP_SET_REQUEST_START(p,v) memcpy((p)->request_start, (v), 6) +#define AVBTP_PACKET_MAAP_SET_REQUEST_COUNT(p,v) ((p)->request_count = htons(v)) +#define AVBTP_PACKET_MAAP_SET_CONFLICT_START(p,v) memcpy((p)->conflict_start, (v), 6) +#define AVBTP_PACKET_MAAP_SET_CONFLICT_COUNT(p,v) ((p)->conflict_count = htons(v)) + +#define AVBTP_PACKET_MAAP_GET_SUBTYPE(p) ((p)->subtype) +#define AVBTP_PACKET_MAAP_GET_SV(p) ((p)->sv) +#define AVBTP_PACKET_MAAP_GET_VERSION(p) ((p)->version) +#define AVBTP_PACKET_MAAP_GET_MESSAGE_TYPE(p) ((p)->message_type) +#define AVBTP_PACKET_MAAP_GET_MAAP_VERSION(p) ((p)->maap_version) +#define AVBTP_PACKET_MAAP_GET_LENGTH(p) (((p)->len1 << 8) | (p)->len2) +#define AVBTP_PACKET_MAAP_GET_STREAM_ID(p) be64toh((p)->stream_id) +#define AVBTP_PACKET_MAAP_GET_REQUEST_START(p) ((p)->request_start) +#define AVBTP_PACKET_MAAP_GET_REQUEST_COUNT(p) ntohs((p)->request_count) +#define AVBTP_PACKET_MAAP_GET_CONFLICT_START(p) ((p)->conflict_start) +#define AVBTP_PACKET_MAAP_GET_CONFLICT_COUNT(p) ntohs((p)->conflict_count) + +int avbtp_maap_register(struct server *server); + +#endif /* AVBTP_MAAP_H */ diff --git a/src/modules/module-avbtp/packets.h b/src/modules/module-avbtp/packets.h new file mode 100644 index 000000000..1bbf12138 --- /dev/null +++ b/src/modules/module-avbtp/packets.h @@ -0,0 +1,115 @@ +/* Spa AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVBTP_PACKETS_H +#define AVBTP_PACKETS_H + +#include + +#define AVBTP_SUBTYPE_61883_IIDC 0x00 +#define AVBTP_SUBTYPE_MMA_STREAM 0x01 +#define AVBTP_SUBTYPE_AAF 0x02 +#define AVBTP_SUBTYPE_CVF 0x03 +#define AVBTP_SUBTYPE_CRF 0x04 +#define AVBTP_SUBTYPE_TSCF 0x05 +#define AVBTP_SUBTYPE_SVF 0x06 +#define AVBTP_SUBTYPE_RVF 0x07 +#define AVBTP_SUBTYPE_AEF_CONTINUOUS 0x6E +#define AVBTP_SUBTYPE_VSF_STREAM 0x6F +#define AVBTP_SUBTYPE_EF_STREAM 0x7F +#define AVBTP_SUBTYPE_NTSCF 0x82 +#define AVBTP_SUBTYPE_ESCF 0xEC +#define AVBTP_SUBTYPE_EECF 0xED +#define AVBTP_SUBTYPE_AEF_DISCRETE 0xEE +#define AVBTP_SUBTYPE_ADP 0xFA +#define AVBTP_SUBTYPE_AECP 0xFB +#define AVBTP_SUBTYPE_ACMP 0xFC +#define AVBTP_SUBTYPE_MAAP 0xFE +#define AVBTP_SUBTYPE_EF_CONTROL 0xFF + +struct avbtp_packet_common { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; /* stream_id valid */ + unsigned version:3; + unsigned subtype_data1:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned subtype_data1:4; + unsigned version:3; + unsigned sv:1; +#elif +#error "Unknown byte order" +#endif + uint16_t subtype_data2; + uint64_t stream_id; + uint8_t payload[0]; +} __attribute__ ((__packed__)); + +#define AVBTP_PACKET_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define AVBTP_PACKET_SET_SV(p,v) ((p)->sv = (v)) +#define AVBTP_PACKET_SET_VERSION(p,v) ((p)->version = (v)) +#define AVBTP_PACKET_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) + +#define AVBTP_PACKET_GET_SUBTYPE(p) ((p)->subtype) +#define AVBTP_PACKET_GET_SV(p) ((p)->sv) +#define AVBTP_PACKET_GET_VERSION(p) ((p)->version) +#define AVBTP_PACKET_GET_STREAM_ID(p) be64toh((p)->stream_id) + +struct avbtp_packet_cc { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned control_data1:4; + + unsigned status:5; + unsigned len1:3; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned control_data1:4; + unsigned version:3; + unsigned sv:1; + + unsigned len1:3; + unsigned status:5; +#endif + uint8_t len2:8; + uint64_t stream_id; + uint8_t payload[0]; +} __attribute__ ((__packed__)); + +#define AVBTP_PACKET_CC_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define AVBTP_PACKET_CC_SET_SV(p,v) ((p)->sv = (v)) +#define AVBTP_PACKET_CC_SET_VERSION(p,v) ((p)->version = (v)) +#define AVBTP_PACKET_CC_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) +#define AVBTP_PACKET_CC_SET_STATUS(p,v) ((p)->status = (v)) +#define AVBTP_PACKET_CC_SET_LENGTH(p,v) ((p)->len1 = ((v) >> 8),(p)->len2 = (v)) + +#define AVBTP_PACKET_CC_GET_SUBTYPE(p) ((p)->subtype) +#define AVBTP_PACKET_CC_GET_SV(p) ((p)->sv) +#define AVBTP_PACKET_CC_GET_VERSION(p) ((p)->version) +#define AVBTP_PACKET_CC_GET_STREAM_ID(p) be64toh((p)->stream_id) +#define AVBTP_PACKET_CC_GET_STATUS(p) ((p)->status) +#define AVBTP_PACKET_CC_GET_LENGTH(p) ((p)->len1 << 8 || (p)->len2) + +#endif /* AVBTP_PACKETS_H */