Line data Source code
1 0 : /*
2 : * Copyright (c) 2025 Titouan Christophe
3 : * SPDX-License-Identifier: Apache-2.0
4 : */
5 :
6 : #include <string.h>
7 : #include <zephyr/devicetree.h>
8 : #include <zephyr/drivers/hwinfo.h>
9 : #include <zephyr/sys/byteorder.h>
10 :
11 : #include "ump_stream_responder.h"
12 :
13 0 : #define BIT_IF(cond, n) ((cond) ? BIT(n) : 0)
14 :
15 : /**
16 : * @brief MIDI-CI version identifier for UMP v1.1 devices
17 : * @see ump112: 7.1.8 FB Info Notification > MIDI-CI Message Version/Format
18 : */
19 1 : #define MIDI_CI_VERSION_FORMAT_UMP_1_1 0x01
20 :
21 0 : static inline bool ep_has_midi1(const struct ump_endpoint_dt_spec *ep)
22 : {
23 : for (size_t i = 0; i < ep->n_blocks; i++) {
24 : if (ep->blocks[i].is_midi1) {
25 : return true;
26 : }
27 : }
28 : return false;
29 : }
30 :
31 0 : static inline bool ep_has_midi2(const struct ump_endpoint_dt_spec *ep)
32 : {
33 : for (size_t i = 0; i < ep->n_blocks; i++) {
34 : if (!ep->blocks[i].is_midi1) {
35 : return true;
36 : }
37 : }
38 : return false;
39 : }
40 :
41 : /**
42 : * @brief Build an Endpoint Info Notification Universal MIDI Packet
43 : * @see ump112: 7.1.2 Endpoint Info Notification Message
44 : */
45 1 : static inline struct midi_ump make_endpoint_info(const struct ump_endpoint_dt_spec *ep)
46 : {
47 : struct midi_ump res;
48 :
49 : res.data[0] = (UMP_MT_UMP_STREAM << 28)
50 : | (UMP_STREAM_STATUS_EP_INFO << 16)
51 : | 0x0101; /* UMP version 1.1 */
52 :
53 : res.data[1] = BIT(31) /* Static function blocks */
54 : | ((ep->n_blocks) << 24)
55 : | BIT_IF(ep_has_midi2(ep), 9)
56 : | BIT_IF(ep_has_midi1(ep), 8);
57 :
58 : return res;
59 : }
60 :
61 : /**
62 : * @brief Build a Function Block Info Notification Universal MIDI Packet
63 : * @see ump112: 7.1.8 Function Block Info Notification
64 : */
65 1 : static inline struct midi_ump make_function_block_info(const struct ump_endpoint_dt_spec *ep,
66 : size_t block_num)
67 : {
68 : const struct ump_block_dt_spec *block = &ep->blocks[block_num];
69 : struct midi_ump res;
70 : uint8_t midi1_mode = block->is_31250bps ? 2 : block->is_midi1 ? 1 : 0;
71 :
72 : res.data[0] = (UMP_MT_UMP_STREAM << 28)
73 : | (UMP_STREAM_STATUS_FB_INFO << 16)
74 : | BIT(15) /* Block is active */
75 : | (block_num << 8)
76 : | BIT_IF(block->is_output, 5) /* UI hint Sender */
77 : | BIT_IF(block->is_input, 4) /* UI hint Receiver */
78 : | (midi1_mode << 2)
79 : | BIT_IF(block->is_output, 1) /* Function block is output */
80 : | BIT_IF(block->is_input, 0); /* Function block is input */
81 :
82 : res.data[1] = (block->first_group << 24)
83 : | (block->groups_spanned << 16)
84 : | (MIDI_CI_VERSION_FORMAT_UMP_1_1 << 8) /* MIDI-CI for UMP v1.1 */
85 : | 0xff; /* At most 255 simultaneous Sysex streams */
86 :
87 : return res;
88 : }
89 :
90 : /**
91 : * @brief Copy an ASCII string into a Universal MIDI Packet while leaving
92 : * some most significant bytes untouched, such that the caller can
93 : * set this prefix.
94 : * @param ump The ump into which the string is copied
95 : * @param[in] offset Number of bytes from the most-significant side to leave free
96 : * @param[in] src The source string
97 : * @param[in] len The length of the source string
98 : * @return The number of bytes copied
99 : */
100 1 : static inline size_t fill_str(struct midi_ump *ump, size_t offset,
101 : const char *src, size_t len)
102 : {
103 : size_t i, j;
104 :
105 : if (offset >= sizeof(struct midi_ump)) {
106 : return 0;
107 : }
108 :
109 : for (i = 0; i < len && (j = i + offset) < sizeof(struct midi_ump); i++) {
110 : ump->data[j / 4] |= src[i] << (8 * (3 - (j % 4)));
111 : }
112 :
113 : return i;
114 : }
115 :
116 : /**
117 : * @brief Send a string as UMP Stream, possibly splitting into multiple
118 : * packets if the string length is larger than 1 UMP
119 : * @param[in] cfg The responder configuration
120 : * @param[in] string The string to send
121 : * @param[in] prefix The fixed prefix of UMP packets to send
122 : * @param[in] offset The offset the strings starts in the packet, in bytes
123 : *
124 : * @return The number of packets sent
125 : */
126 1 : static inline int send_string(const struct ump_stream_responder_cfg *cfg,
127 : const char *string, uint32_t prefix, size_t offset)
128 : {
129 : struct midi_ump reply;
130 : size_t stringlen = strlen(string);
131 : size_t strwidth = sizeof(reply) - offset;
132 : uint8_t format;
133 : size_t i = 0;
134 : int res = 0;
135 :
136 : while (i < stringlen) {
137 : memset(&reply, 0, sizeof(reply));
138 : format = (i == 0)
139 : ? (stringlen - i <= strwidth)
140 : ? UMP_STREAM_FORMAT_COMPLETE
141 : : UMP_STREAM_FORMAT_START
142 : : (stringlen - i > strwidth)
143 : ? UMP_STREAM_FORMAT_CONTINUE
144 : : UMP_STREAM_FORMAT_END;
145 :
146 : reply.data[0] = (UMP_MT_UMP_STREAM << 28)
147 : | (format << 26)
148 : | prefix;
149 :
150 : i += fill_str(&reply, offset, &string[i], stringlen - i);
151 : cfg->send(cfg->dev, reply);
152 : res++;
153 : }
154 :
155 : return res;
156 : }
157 :
158 : /**
159 : * @brief Handle Endpoint Discovery messages
160 : * @param[in] cfg The responder configuration
161 : * @param[in] pkt The discovery packet to handle
162 : * @return The number of UMP sent as reply
163 : */
164 1 : static inline int ump_ep_discover(const struct ump_stream_responder_cfg *cfg,
165 : const struct midi_ump pkt)
166 : {
167 : int res = 0;
168 : uint8_t filter = UMP_STREAM_EP_DISCOVERY_FILTER(pkt);
169 :
170 : /* Request for Endpoint Info Notification */
171 : if ((filter & UMP_EP_DISC_FILTER_EP_INFO) != 0U) {
172 : cfg->send(cfg->dev, make_endpoint_info(cfg->ep_spec));
173 : res++;
174 : }
175 :
176 : /* Request for Endpoint Name Notification */
177 : if ((filter & UMP_EP_DISC_FILTER_EP_NAME) != 0U && cfg->ep_spec->name != NULL) {
178 : res += send_string(cfg, cfg->ep_spec->name,
179 : UMP_STREAM_STATUS_EP_NAME << 16, 2);
180 : }
181 :
182 : /* Request for Product Instance ID */
183 : if ((filter & UMP_EP_DISC_FILTER_PRODUCT_ID) != 0U && IS_ENABLED(CONFIG_HWINFO)) {
184 : res += send_string(cfg, ump_product_instance_id(),
185 : UMP_STREAM_STATUS_PROD_ID << 16, 2);
186 : }
187 :
188 : return res;
189 : }
190 :
191 0 : static inline int ump_fb_discover_block(const struct ump_stream_responder_cfg *cfg,
192 : size_t block_num, uint8_t filter)
193 : {
194 : int res = 0;
195 : const struct ump_block_dt_spec *blk = &cfg->ep_spec->blocks[block_num];
196 :
197 : if ((filter & UMP_FB_DISC_FILTER_INFO) != 0U) {
198 : cfg->send(cfg->dev, make_function_block_info(cfg->ep_spec, block_num));
199 : res++;
200 : }
201 :
202 : if ((filter & UMP_FB_DISC_FILTER_NAME) != 0U && blk->name != NULL) {
203 : res += send_string(cfg, blk->name,
204 : (UMP_STREAM_STATUS_FB_NAME << 16) | (block_num << 8), 3);
205 : }
206 :
207 : return res;
208 : }
209 :
210 : /**
211 : * @brief Handle Function Block Discovery messages
212 : * @param[in] cfg The responder configuration
213 : * @param[in] pkt The discovery packet to handle
214 : * @return The number of UMP sent as reply
215 : */
216 1 : static inline int ump_fb_discover(const struct ump_stream_responder_cfg *cfg,
217 : const struct midi_ump pkt)
218 : {
219 : int res = 0;
220 : uint8_t block_num = UMP_STREAM_FB_DISCOVERY_NUM(pkt);
221 : uint8_t filter = UMP_STREAM_FB_DISCOVERY_FILTER(pkt);
222 :
223 : if (block_num < cfg->ep_spec->n_blocks) {
224 : res += ump_fb_discover_block(cfg, block_num, filter);
225 : } else if (block_num == 0xff) {
226 : /* Requesting information for all blocks at once */
227 : for (block_num = 0; block_num < cfg->ep_spec->n_blocks; block_num++) {
228 : res += ump_fb_discover_block(cfg, block_num, filter);
229 : }
230 : }
231 :
232 : return res;
233 : }
234 :
235 : const char *ump_product_instance_id(void)
236 : {
237 : static char product_id[43] = "";
238 : static const char hex[] = "0123456789ABCDEF";
239 :
240 : if (IS_ENABLED(CONFIG_HWINFO) && product_id[0] == '\0') {
241 : uint8_t devid[sizeof(product_id) / 2];
242 : ssize_t len = hwinfo_get_device_id(devid, sizeof(devid));
243 :
244 : if (len == -ENOSYS && hwinfo_get_device_eui64(devid) == 0) {
245 : /* device id unavailable, but there is an eui64,
246 : * which has a fixed length of 8
247 : */
248 : len = 8;
249 : } else if (len < 0) {
250 : /* Other hwinfo driver error; mark as empty */
251 : len = 0;
252 : }
253 :
254 : /* Convert to hex string */
255 : for (ssize_t i = 0; i < len; i++) {
256 : product_id[2 * i] = hex[devid[i] >> 4];
257 : product_id[2 * i + 1] = hex[devid[i] & 0xf];
258 : }
259 : product_id[2*len] = '\0';
260 : }
261 :
262 : return product_id;
263 : }
264 :
265 : int ump_stream_respond(const struct ump_stream_responder_cfg *cfg,
266 : const struct midi_ump pkt)
267 : {
268 : if (cfg->send == NULL) {
269 : return -EINVAL;
270 : }
271 :
272 : if (UMP_MT(pkt) != UMP_MT_UMP_STREAM) {
273 : return 0;
274 : }
275 :
276 : switch (UMP_STREAM_STATUS(pkt)) {
277 : case UMP_STREAM_STATUS_EP_DISCOVERY:
278 : return ump_ep_discover(cfg, pkt);
279 : case UMP_STREAM_STATUS_FB_DISCOVERY:
280 : return ump_fb_discover(cfg, pkt);
281 : }
282 :
283 : return 0;
284 : }
|