From 5e83d222b6d0bcd6572f3b92899fd3fdbe3ee715 Mon Sep 17 00:00:00 2001 From: Titouan Christophe Date: Thu, 31 Jul 2025 11:51:40 +0200 Subject: [PATCH] lib: midi2: new UMP Stream responder library Add a new top-level, transport independent library to respond to UMP Stream Discovery messages. This allows MIDI2.0 clients to discover UMP endpoints hosted on Zephyr over the UMP protocol. The endpoint specification can be gathered from the device tree, so that the same information used to generate USB descriptors in usb-midi2.0 can be delivered over UMP Stream. Signed-off-by: Titouan Christophe --- doc/zephyr.doxyfile.in | 1 + dts/bindings/usb/zephyr,midi2-device.yaml | 14 ++ include/zephyr/audio/midi.h | 148 ++++++++++- lib/CMakeLists.txt | 1 + lib/Kconfig | 2 + lib/midi2/CMakeLists.txt | 8 + lib/midi2/Kconfig | 13 + lib/midi2/ump_stream_responder.c | 284 ++++++++++++++++++++++ lib/midi2/ump_stream_responder.h | 121 +++++++++ 9 files changed, 591 insertions(+), 1 deletion(-) create mode 100644 lib/midi2/CMakeLists.txt create mode 100644 lib/midi2/Kconfig create mode 100644 lib/midi2/ump_stream_responder.c create mode 100644 lib/midi2/ump_stream_responder.h diff --git a/doc/zephyr.doxyfile.in b/doc/zephyr.doxyfile.in index 1dbc5ecd0d2..cea0300cfb0 100644 --- a/doc/zephyr.doxyfile.in +++ b/doc/zephyr.doxyfile.in @@ -1017,6 +1017,7 @@ INPUT = @ZEPHYR_BASE@/doc/_doxygen/mainpage.md \ @ZEPHYR_BASE@/include/zephyr/sys/atomic.h \ @ZEPHYR_BASE@/include/ \ @ZEPHYR_BASE@/lib/libc/minimal/include/ \ + @ZEPHYR_BASE@/lib/midi2/ \ @ZEPHYR_BASE@/subsys/testsuite/include/ \ @ZEPHYR_BASE@/subsys/testsuite/ztest/include/ \ @ZEPHYR_BASE@/subsys/secure_storage/include/ \ diff --git a/dts/bindings/usb/zephyr,midi2-device.yaml b/dts/bindings/usb/zephyr,midi2-device.yaml index 851916e7e87..1113c1886ef 100644 --- a/dts/bindings/usb/zephyr,midi2-device.yaml +++ b/dts/bindings/usb/zephyr,midi2-device.yaml @@ -14,6 +14,10 @@ properties: type: int const: 1 + label: + type: string + description: Name of the UMP (MIDI 2.0) endpoint + child-binding: description: | MIDI2 Group terminal block. @@ -21,6 +25,10 @@ child-binding: device exchange Universal MIDI Packets with the host. properties: + label: + type: string + description: Name of the corresponding UMP Function block + reg: type: array required: true @@ -49,3 +57,9 @@ child-binding: - "output-only" description: | Type (data direction) of Group Terminals in this Block. + + serial-31250bps: + type: boolean + description: | + This represent a physical MIDI1 serial port, which is limited + to a transmission speed of 31.25kb/s. diff --git a/include/zephyr/audio/midi.h b/include/zephyr/audio/midi.h index acc7d51338f..5e59dd444a1 100644 --- a/include/zephyr/audio/midi.h +++ b/include/zephyr/audio/midi.h @@ -63,7 +63,10 @@ struct midi_ump { #define UMP_MT_DATA_128 0x05 /** Flex Data Messages */ #define UMP_MT_FLEX_DATA 0x0d -/** UMP Stream Message */ +/** + * UMP Stream Message + * @see midi_ump_stream + */ #define UMP_MT_UMP_STREAM 0x0f /** @} */ @@ -208,6 +211,149 @@ struct midi_ump { #define UMP_SYS_RESET 0xff /**< Reset (no param) */ /** @} */ + +/** + * @defgroup midi_ump_stream UMP Stream specific fields + * @ingroup midi_ump + * @see ump112: 7.1 UMP Stream Messages + * + * @{ + */ + +/** + * @brief Format of a UMP Stream message + * @param[in] ump Universal MIDI Packet (containing a UMP Stream message) + * @see midi_ump_stream_format + */ +#define UMP_STREAM_FORMAT(ump) \ + (((ump).data[0] >> 26) & BIT_MASK(2)) + +/** + * @defgroup midi_ump_stream_format UMP Stream format + * @ingroup midi_ump_stream + * @see ump112: 7.1 UMP Stream Messages: Format + * @remark When UMP_MT(x)=UMP_MT_UMP_STREAM, + * then UMP_STREAM_FORMAT(x) may be one of: + * @{ + */ + +/** Complete message in one UMP */ +#define UMP_STREAM_FORMAT_COMPLETE 0x00 +/** Start of a message which spans two or more UMPs */ +#define UMP_STREAM_FORMAT_START 0x01 +/** Continuing a message which spans three or more UMPs. + * There might be multiple Continue UMPs in a single message + */ +#define UMP_STREAM_FORMAT_CONTINUE 0x02 +/** End of message which spans two or more UMPs */ +#define UMP_STREAM_FORMAT_END 0x03 + +/** @} */ + +/** + * @brief Status field of a UMP Stream message + * @param[in] ump Universal MIDI Packet (containing a UMP Stream message) + * @see midi_ump_stream_status + */ +#define UMP_STREAM_STATUS(ump) \ + (((ump).data[0] >> 16) & BIT_MASK(10)) + +/** + * @defgroup midi_ump_stream_status UMP Stream status + * @ingroup midi_ump_stream + * @see ump112: 7.1 UMP Stream Messages + * @remark When UMP_MT(x)=UMP_MT_UMP_STREAM, + * then UMP_STREAM_STATUS(x) may be one of: + * @{ + */ + +/** Endpoint Discovery Message */ +#define UMP_STREAM_STATUS_EP_DISCOVERY 0x00 +/** Endpoint Info Notification Message */ +#define UMP_STREAM_STATUS_EP_INFO 0x01 +/** Device Identity Notification Message */ +#define UMP_STREAM_STATUS_DEVICE_IDENT 0x02 +/** Endpoint Name Notification */ +#define UMP_STREAM_STATUS_EP_NAME 0x03 +/** Product Instance Id Notification Message */ +#define UMP_STREAM_STATUS_PROD_ID 0x04 +/** Stream Configuration Request Message */ +#define UMP_STREAM_STATUS_CONF_REQ 0x05 +/** Stream Configuration Notification Message */ +#define UMP_STREAM_STATUS_CONF_NOTIF 0x06 +/** Function Block Discovery Message */ +#define UMP_STREAM_STATUS_FB_DISCOVERY 0x10 +/** Function Block Info Notification */ +#define UMP_STREAM_STATUS_FB_INFO 0x11 +/** Function Block Name Notification */ +#define UMP_STREAM_STATUS_FB_NAME 0x12 +/** @} */ + +/** + * @brief Filter bitmap of an Endpoint Discovery message + * @param[in] ump Universal MIDI Packet (containing an Endpoint Discovery message) + * @see ump112: 7.1.1 Endpoint Discovery Message + * @see midi_ump_ep_disc + */ +#define UMP_STREAM_EP_DISCOVERY_FILTER(ump) \ + ((ump).data[1] & BIT_MASK(8)) + +/** + * @defgroup midi_ump_ep_disc UMP Stream endpoint discovery message filter bits + * @ingroup midi_ump_stream + * @see ump112: 7.1.1 Fig. 12: Endpoint Discovery Message Filter Bitmap Field + * @remark When UMP_MT(x)=UMP_MT_UMP_STREAM and + * UMP_STREAM_STATUS(x)=UMP_STREAM_STATUS_EP_DISCOVERY, + * then UMP_STREAM_EP_DISCOVERY_FILTER(x) may be an ORed combination of: + * @{ + */ + +/** Requesting an Endpoint Info Notification */ +#define UMP_EP_DISC_FILTER_EP_INFO BIT(0) +/** Requesting a Device Identity Notification */ +#define UMP_EP_DISC_FILTER_DEVICE_ID BIT(1) +/** Requesting an Endpoint Name Notification */ +#define UMP_EP_DISC_FILTER_EP_NAME BIT(2) +/** Requesting a Product Instance Id Notification */ +#define UMP_EP_DISC_FILTER_PRODUCT_ID BIT(3) +/** Requesting a Stream Configuration Notification */ +#define UMP_EP_DISC_FILTER_STREAM_CFG BIT(4) +/** @} */ + +/** + * @brief Filter bitmap of a Function Block Discovery message + * @param[in] ump Universal MIDI Packet (containing a Function Block Discovery message) + * @see ump112: 7.1.7 Function Block Discovery Message + * @see midi_ump_fb_disc + */ +#define UMP_STREAM_FB_DISCOVERY_FILTER(ump) \ + ((ump).data[0] & BIT_MASK(8)) + +/** + * @brief Block number requested in a Function Block Discovery message + * @param[in] ump Universal MIDI Packet (containing a Function Block Discovery message) + * @see ump112: 7.1.7 Function Block Discovery Message + */ +#define UMP_STREAM_FB_DISCOVERY_NUM(ump) \ + (((ump).data[0] >> 8) & BIT_MASK(8)) + +/** + * @defgroup midi_ump_fb_disc UMP Stream Function Block discovery message filter bits + * @ingroup midi_ump_stream + * @see ump112: 7.1.7 Fig. 21: Function Block Discovery Filter Bitmap Field Format + * @remark When UMP_MT(x)=UMP_MT_UMP_STREAM and + * UMP_STREAM_STATUS(x)=UMP_STREAM_STATUS_FB_DISCOVERY, + * then UMP_STREAM_FB_DISCOVERY_FILTER(x) may be an ORed combination of: + * @{ + */ +/** Requesting a Function Block Info Notification */ +#define UMP_FB_DISC_FILTER_INFO BIT(0) +/** Requesting a Function Block Name Notification */ +#define UMP_FB_DISC_FILTER_NAME BIT(1) +/** @} */ + +/** @} */ + /** @} */ #ifdef __cplusplus diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index fad0e383c90..f960141b7af 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory_ifdef(CONFIG_CPP cpp) add_subdirectory(hash) add_subdirectory(heap) add_subdirectory(mem_blocks) +add_subdirectory(midi2) add_subdirectory_ifdef(CONFIG_NET_BUF net_buf) add_subdirectory(os) add_subdirectory(utils) diff --git a/lib/Kconfig b/lib/Kconfig index 149c1317e49..7f2a5b2a15b 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -15,6 +15,8 @@ source "lib/heap/Kconfig" source "lib/mem_blocks/Kconfig" +source "lib/midi2/Kconfig" + source "lib/net_buf/Kconfig" source "lib/os/Kconfig" diff --git a/lib/midi2/CMakeLists.txt b/lib/midi2/CMakeLists.txt new file mode 100644 index 00000000000..49a11c2ed85 --- /dev/null +++ b/lib/midi2/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Titouan Christophe +# SPDX-License-Identifier: Apache-2.0 + +zephyr_include_directories(.) + +if(CONFIG_MIDI2_UMP_STREAM_RESPONDER) + zephyr_sources(ump_stream_responder.c) +endif() diff --git a/lib/midi2/Kconfig b/lib/midi2/Kconfig new file mode 100644 index 00000000000..d4ab789c303 --- /dev/null +++ b/lib/midi2/Kconfig @@ -0,0 +1,13 @@ +# Copyright (c) 2025 Titouan Christophe +# SPDX-License-Identifier: Apache-2.0 + +menu "MIDI2" + +config MIDI2_UMP_STREAM_RESPONDER + bool "MIDI2 UMP Stream responder" + help + Library to respond to UMP Stream discovery messages, as specified + in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" + version 1.1.2 section 7.1: "UMP Stream Messages" + +endmenu diff --git a/lib/midi2/ump_stream_responder.c b/lib/midi2/ump_stream_responder.c new file mode 100644 index 00000000000..7ffffbf8732 --- /dev/null +++ b/lib/midi2/ump_stream_responder.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2025 Titouan Christophe + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "ump_stream_responder.h" + +#define BIT_IF(cond, n) ((cond) ? BIT(n) : 0) + +/** + * @brief MIDI-CI version identifier for UMP v1.1 devices + * @see ump112: 7.1.8 FB Info Notification > MIDI-CI Message Version/Format + */ +#define MIDI_CI_VERSION_FORMAT_UMP_1_1 0x01 + +static inline bool ep_has_midi1(const struct ump_endpoint_dt_spec *ep) +{ + for (size_t i = 0; i < ep->n_blocks; i++) { + if (ep->blocks[i].is_midi1) { + return true; + } + } + return false; +} + +static inline bool ep_has_midi2(const struct ump_endpoint_dt_spec *ep) +{ + for (size_t i = 0; i < ep->n_blocks; i++) { + if (!ep->blocks[i].is_midi1) { + return true; + } + } + return false; +} + +/** + * @brief Build an Endpoint Info Notification Universal MIDI Packet + * @see ump112: 7.1.2 Endpoint Info Notification Message + */ +static inline struct midi_ump make_endpoint_info(const struct ump_endpoint_dt_spec *ep) +{ + struct midi_ump res; + + res.data[0] = (UMP_MT_UMP_STREAM << 28) + | (UMP_STREAM_STATUS_EP_INFO << 16) + | 0x0101; /* UMP version 1.1 */ + + res.data[1] = BIT(31) /* Static function blocks */ + | ((ep->n_blocks) << 24) + | BIT_IF(ep_has_midi2(ep), 9) + | BIT_IF(ep_has_midi1(ep), 8); + + return res; +} + +/** + * @brief Build a Function Block Info Notification Universal MIDI Packet + * @see ump112: 7.1.8 Function Block Info Notification + */ +static inline struct midi_ump make_function_block_info(const struct ump_endpoint_dt_spec *ep, + size_t block_num) +{ + const struct ump_block_dt_spec *block = &ep->blocks[block_num]; + struct midi_ump res; + uint8_t midi1_mode = block->is_31250bps ? 2 : block->is_midi1 ? 1 : 0; + + res.data[0] = (UMP_MT_UMP_STREAM << 28) + | (UMP_STREAM_STATUS_FB_INFO << 16) + | BIT(15) /* Block is active */ + | (block_num << 8) + | BIT_IF(block->is_output, 5) /* UI hint Sender */ + | BIT_IF(block->is_input, 4) /* UI hint Receiver */ + | (midi1_mode << 2) + | BIT_IF(block->is_output, 1) /* Function block is output */ + | BIT_IF(block->is_input, 0); /* Function block is input */ + + res.data[1] = (block->first_group << 24) + | (block->groups_spanned << 16) + | (MIDI_CI_VERSION_FORMAT_UMP_1_1 << 8) /* MIDI-CI for UMP v1.1 */ + | 0xff; /* At most 255 simultaneous Sysex streams */ + + return res; +} + +/** + * @brief Copy an ASCII string into a Universal MIDI Packet while leaving + * some most significant bytes untouched, such that the caller can + * set this prefix. + * @param ump The ump into which the string is copied + * @param[in] offset Number of bytes from the most-significant side to leave free + * @param[in] src The source string + * @param[in] len The length of the source string + * @return The number of bytes copied + */ +static inline size_t fill_str(struct midi_ump *ump, size_t offset, + const char *src, size_t len) +{ + size_t i, j; + + if (offset >= sizeof(struct midi_ump)) { + return 0; + } + + for (i = 0; i < len && (j = i + offset) < sizeof(struct midi_ump); i++) { + ump->data[j / 4] |= src[i] << (8 * (3 - (j % 4))); + } + + return i; +} + +/** + * @brief Send a string as UMP Stream, possibly splitting into multiple + * packets if the string length is larger than 1 UMP + * @param[in] cfg The responder configuration + * @param[in] string The string to send + * @param[in] prefix The fixed prefix of UMP packets to send + * @param[in] offset The offset the strings starts in the packet, in bytes + * + * @return The number of packets sent + */ +static inline int send_string(const struct ump_stream_responder_cfg *cfg, + const char *string, uint32_t prefix, size_t offset) +{ + struct midi_ump reply; + size_t stringlen = strlen(string); + size_t strwidth = sizeof(reply) - offset; + uint8_t format; + size_t i = 0; + int res = 0; + + while (i < stringlen) { + memset(&reply, 0, sizeof(reply)); + format = (i == 0) + ? (stringlen - i <= strwidth) + ? UMP_STREAM_FORMAT_COMPLETE + : UMP_STREAM_FORMAT_START + : (stringlen - i > strwidth) + ? UMP_STREAM_FORMAT_CONTINUE + : UMP_STREAM_FORMAT_END; + + reply.data[0] = (UMP_MT_UMP_STREAM << 28) + | (format << 26) + | prefix; + + i += fill_str(&reply, offset, &string[i], stringlen - i); + cfg->send(cfg->dev, reply); + res++; + } + + return res; +} + +/** + * @brief Handle Endpoint Discovery messages + * @param[in] cfg The responder configuration + * @param[in] pkt The discovery packet to handle + * @return The number of UMP sent as reply + */ +static inline int ump_ep_discover(const struct ump_stream_responder_cfg *cfg, + const struct midi_ump pkt) +{ + int res = 0; + uint8_t filter = UMP_STREAM_EP_DISCOVERY_FILTER(pkt); + + /* Request for Endpoint Info Notification */ + if ((filter & UMP_EP_DISC_FILTER_EP_INFO) != 0U) { + cfg->send(cfg->dev, make_endpoint_info(cfg->ep_spec)); + res++; + } + + /* Request for Endpoint Name Notification */ + if ((filter & UMP_EP_DISC_FILTER_EP_NAME) != 0U && cfg->ep_spec->name != NULL) { + res += send_string(cfg, cfg->ep_spec->name, + UMP_STREAM_STATUS_EP_NAME << 16, 2); + } + + /* Request for Product Instance ID */ + if ((filter & UMP_EP_DISC_FILTER_PRODUCT_ID) != 0U && IS_ENABLED(CONFIG_HWINFO)) { + res += send_string(cfg, ump_product_instance_id(), + UMP_STREAM_STATUS_PROD_ID << 16, 2); + } + + return res; +} + +static inline int ump_fb_discover_block(const struct ump_stream_responder_cfg *cfg, + size_t block_num, uint8_t filter) +{ + int res = 0; + const struct ump_block_dt_spec *blk = &cfg->ep_spec->blocks[block_num]; + + if ((filter & UMP_FB_DISC_FILTER_INFO) != 0U) { + cfg->send(cfg->dev, make_function_block_info(cfg->ep_spec, block_num)); + res++; + } + + if ((filter & UMP_FB_DISC_FILTER_NAME) != 0U && blk->name != NULL) { + res += send_string(cfg, blk->name, + (UMP_STREAM_STATUS_FB_NAME << 16) | (block_num << 8), 3); + } + + return res; +} + +/** + * @brief Handle Function Block Discovery messages + * @param[in] cfg The responder configuration + * @param[in] pkt The discovery packet to handle + * @return The number of UMP sent as reply + */ +static inline int ump_fb_discover(const struct ump_stream_responder_cfg *cfg, + const struct midi_ump pkt) +{ + int res = 0; + uint8_t block_num = UMP_STREAM_FB_DISCOVERY_NUM(pkt); + uint8_t filter = UMP_STREAM_FB_DISCOVERY_FILTER(pkt); + + if (block_num < cfg->ep_spec->n_blocks) { + res += ump_fb_discover_block(cfg, block_num, filter); + } else if (block_num == 0xff) { + /* Requesting information for all blocks at once */ + for (block_num = 0; block_num < cfg->ep_spec->n_blocks; block_num++) { + res += ump_fb_discover_block(cfg, block_num, filter); + } + } + + return res; +} + +const char *ump_product_instance_id(void) +{ + static char product_id[43] = ""; + static const char hex[] = "0123456789ABCDEF"; + + if (IS_ENABLED(CONFIG_HWINFO) && product_id[0] == '\0') { + uint8_t devid[sizeof(product_id) / 2]; + ssize_t len = hwinfo_get_device_id(devid, sizeof(devid)); + + if (len == -ENOSYS && hwinfo_get_device_eui64(devid) == 0) { + /* device id unavailable, but there is an eui64, + * which has a fixed length of 8 + */ + len = 8; + } else if (len < 0) { + /* Other hwinfo driver error; mark as empty */ + len = 0; + } + + /* Convert to hex string */ + for (ssize_t i = 0; i < len; i++) { + product_id[2 * i] = hex[devid[i] >> 4]; + product_id[2 * i + 1] = hex[devid[i] & 0xf]; + } + product_id[2*len] = '\0'; + } + + return product_id; +} + +int ump_stream_respond(const struct ump_stream_responder_cfg *cfg, + const struct midi_ump pkt) +{ + if (cfg->send == NULL) { + return -EINVAL; + } + + if (UMP_MT(pkt) != UMP_MT_UMP_STREAM) { + return 0; + } + + switch (UMP_STREAM_STATUS(pkt)) { + case UMP_STREAM_STATUS_EP_DISCOVERY: + return ump_ep_discover(cfg, pkt); + case UMP_STREAM_STATUS_FB_DISCOVERY: + return ump_fb_discover(cfg, pkt); + } + + return 0; +} diff --git a/lib/midi2/ump_stream_responder.h b/lib/midi2/ump_stream_responder.h new file mode 100644 index 00000000000..420e206de38 --- /dev/null +++ b/lib/midi2/ump_stream_responder.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2025 Titouan Christophe + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_LIB_MIDI2_UMP_STREAM_RESPONDER_H_ +#define ZEPHYR_LIB_MIDI2_UMP_STREAM_RESPONDER_H_ + +/** + * @brief Respond to UMP Stream message Endpoint or Function Block discovery + * @defgroup ump_stream_responder UMP Stream Responder + * @ingroup midi_ump + * @since 4.3 + * @version 0.1.0 + * @see ump112 7.1: UMP Stream Messages + * @{ + */ + +#include +#include + +/** + * @brief UMP Function Block specification + * @see ump112: 6: Function Blocks + */ +struct ump_block_dt_spec { + /** Name of this function block, or NULL if unnamed */ + const char *name; + /** Number of the first UMP group in this block */ + uint8_t first_group; + /** Number of (contiguous) UMP groups spanned by this block */ + uint8_t groups_spanned; + /** True if this function block is an input */ + bool is_input; + /** True if this function block is an output */ + bool is_output; + /** True if this function block carries MIDI1 data only */ + bool is_midi1; + /** True if this function block is physically wired to a (MIDI1) + * serial interface, where data is transmitted at the standard + * baud rate of 31250 b/s + */ + bool is_31250bps; +}; + +/** + * @brief UMP endpoint specification + */ +struct ump_endpoint_dt_spec { + /** Name of this endpoint, or NULL if unnamed */ + const char *name; + /** Number of function blocks in this endpoint */ + size_t n_blocks; + /** Function blocks in this endpoint */ + struct ump_block_dt_spec blocks[]; +}; + +/** + * @brief Configuration for the UMP Stream responder + */ +struct ump_stream_responder_cfg { + /** The device to send reply packets */ + void *dev; + /** The function to call to send a reply packet */ + void (*send)(void *dev, const struct midi_ump ump); + /** The UMP endpoint specification */ + const struct ump_endpoint_dt_spec *ep_spec; +}; + +/** + * @brief Get a Universal MIDI Packet endpoint function block from its + * device-tree representation + * @param _node The device tree node representing the midi2 block + */ +#define UMP_BLOCK_DT_SPEC_GET(_node) \ +{ \ + .name = DT_PROP_OR(_node, label, NULL), \ + .first_group = DT_REG_ADDR(_node), \ + .groups_spanned = DT_REG_SIZE(_node), \ + .is_input = !DT_ENUM_HAS_VALUE(_node, terminal_type, output_only), \ + .is_output = !DT_ENUM_HAS_VALUE(_node, terminal_type, input_only), \ + .is_midi1 = !DT_ENUM_HAS_VALUE(_node, protocol, midi2), \ + .is_31250bps = DT_PROP(_node, serial_31250bps), \ +} + +#define UMP_BLOCK_SEP_IF_OKAY(_node) \ + COND_CODE_1(DT_NODE_HAS_STATUS_OKAY(_node), \ + (UMP_BLOCK_DT_SPEC_GET(_node),), \ + ()) + +/** + * @brief Get a Universal MIDI Packet endpoint description from + * the device-tree representation of a midi2 device + * @param _node The device tree node representing a midi2 device + */ +#define UMP_ENDPOINT_DT_SPEC_GET(_node) \ +{ \ + .name = DT_PROP_OR(_node, label, NULL), \ + .n_blocks = DT_FOREACH_CHILD_SEP(_node, DT_NODE_HAS_STATUS_OKAY, (+)), \ + .blocks = {DT_FOREACH_CHILD(_node, UMP_BLOCK_SEP_IF_OKAY)}, \ +} + +/** + * @brief Respond to an UMP Stream message + * @param[in] cfg The responder configuration + * @param[in] pkt The message to respond to + * @return The number of UMP packets sent as reply, + * or -errno in case of error + */ +int ump_stream_respond(const struct ump_stream_responder_cfg *cfg, + const struct midi_ump pkt); + +/** + * @return The UMP Product Instance ID of this device, based on the device + * hwinfo if available, otherwise an empty string + */ +const char *ump_product_instance_id(void); + +/** @} */ + +#endif