1 /* Device/adapter/kernel quirk table
2 *
3 * Copyright © 2021 Pauli Virtanen
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 */
24
25 #include <errno.h>
26 #include <stddef.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/socket.h>
32 #include <fcntl.h>
33 #include <regex.h>
34 #include <limits.h>
35 #include <sys/utsname.h>
36 #include <sys/stat.h>
37 #include <sys/mman.h>
38 #include <sys/types.h>
39 #include <unistd.h>
40
41 #include <bluetooth/bluetooth.h>
42
43 #include <dbus/dbus.h>
44
45 #include <spa/support/log.h>
46 #include <spa/support/loop.h>
47 #include <spa/support/dbus.h>
48 #include <spa/support/plugin.h>
49 #include <spa/monitor/device.h>
50 #include <spa/monitor/utils.h>
51 #include <spa/utils/hook.h>
52 #include <spa/utils/type.h>
53 #include <spa/utils/keys.h>
54 #include <spa/utils/names.h>
55 #include <spa/utils/result.h>
56 #include <spa/utils/json.h>
57 #include <spa/utils/string.h>
58
59 #include "a2dp-codecs.h"
60 #include "defs.h"
61
62 static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.quirks");
63 #undef SPA_LOG_TOPIC_DEFAULT
64 #define SPA_LOG_TOPIC_DEFAULT &log_topic
65
66 struct spa_bt_quirks {
67 struct spa_log *log;
68
69 int force_msbc;
70 int force_hw_volume;
71 int force_sbc_xq;
72 int force_faststream;
73 int force_a2dp_duplex;
74
75 char *device_rules;
76 char *adapter_rules;
77 char *kernel_rules;
78 };
79
parse_feature(const char * str)80 static enum spa_bt_feature parse_feature(const char *str)
81 {
82 static const struct { const char *key; enum spa_bt_feature value; } feature_keys[] = {
83 { "msbc", SPA_BT_FEATURE_MSBC },
84 { "msbc-alt1", SPA_BT_FEATURE_MSBC_ALT1 },
85 { "msbc-alt1-rtl", SPA_BT_FEATURE_MSBC_ALT1_RTL },
86 { "hw-volume", SPA_BT_FEATURE_HW_VOLUME },
87 { "hw-volume-mic", SPA_BT_FEATURE_HW_VOLUME_MIC },
88 { "sbc-xq", SPA_BT_FEATURE_SBC_XQ },
89 { "faststream", SPA_BT_FEATURE_FASTSTREAM },
90 { "a2dp-duplex", SPA_BT_FEATURE_A2DP_DUPLEX },
91 };
92 size_t i;
93 for (i = 0; i < SPA_N_ELEMENTS(feature_keys); ++i) {
94 if (spa_streq(str, feature_keys[i].key))
95 return feature_keys[i].value;
96 }
97 return 0;
98 }
99
do_match(const char * rules,struct spa_dict * dict,uint32_t * no_features)100 static int do_match(const char *rules, struct spa_dict *dict, uint32_t *no_features)
101 {
102 struct spa_json rules_json = SPA_JSON_INIT(rules, strlen(rules));
103 struct spa_json rules_arr, it[2];
104
105 if (spa_json_enter_array(&rules_json, &rules_arr) <= 0)
106 return 1;
107
108 while (spa_json_enter_object(&rules_arr, &it[0]) > 0) {
109 char key[256];
110 int match = true;
111 uint32_t no_features_cur = 0;
112
113 while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) {
114 char val[4096];
115 const char *str, *value;
116 int len;
117 bool success = false;
118
119 if (spa_streq(key, "no-features")) {
120 if (spa_json_enter_array(&it[0], &it[1]) > 0) {
121 while (spa_json_get_string(&it[1], val, sizeof(val)) > 0)
122 no_features_cur |= parse_feature(val);
123 }
124 continue;
125 }
126
127 if ((len = spa_json_next(&it[0], &value)) <= 0)
128 break;
129
130 if (spa_json_is_null(value, len)) {
131 value = NULL;
132 } else {
133 if (spa_json_parse_stringn(value, len, val, sizeof(val)) < 0)
134 continue;
135 value = val;
136 }
137
138 str = spa_dict_lookup(dict, key);
139 if (value == NULL) {
140 success = str == NULL;
141 } else if (str != NULL) {
142 if (value[0] == '~') {
143 regex_t r;
144 if (regcomp(&r, value+1, REG_EXTENDED | REG_NOSUB) == 0) {
145 if (regexec(&r, str, 0, NULL, 0) == 0)
146 success = true;
147 regfree(&r);
148 }
149 } else if (spa_streq(str, value)) {
150 success = true;
151 }
152 }
153
154 if (!success) {
155 match = false;
156 break;
157 }
158 }
159
160 if (match) {
161 *no_features = no_features_cur;
162 return 0;
163 }
164 }
165 return 0;
166 }
167
parse_force_flag(const struct spa_dict * info,const char * key)168 static int parse_force_flag(const struct spa_dict *info, const char *key)
169 {
170 const char *str;
171 str = spa_dict_lookup(info, key);
172 if (str == NULL)
173 return -1;
174 else
175 return (strcmp(str, "true") == 0 || atoi(str)) ? 1 : 0;
176 }
177
load_quirks(struct spa_bt_quirks * this,const char * str,size_t len)178 static void load_quirks(struct spa_bt_quirks *this, const char *str, size_t len)
179 {
180 struct spa_json data = SPA_JSON_INIT(str, len);
181 struct spa_json rules;
182 char key[1024];
183
184 if (spa_json_enter_object(&data, &rules) <= 0)
185 spa_json_init(&rules, str, len);
186
187 while (spa_json_get_string(&rules, key, sizeof(key)) > 0) {
188 int sz;
189 const char *value;
190
191 if ((sz = spa_json_next(&rules, &value)) <= 0)
192 break;
193
194 if (!spa_json_is_container(value, sz))
195 continue;
196
197 sz = spa_json_container_len(&rules, value, sz);
198
199 if (spa_streq(key, "bluez5.features.kernel") && !this->kernel_rules)
200 this->kernel_rules = strndup(value, sz);
201 else if (spa_streq(key, "bluez5.features.adapter") && !this->adapter_rules)
202 this->adapter_rules = strndup(value, sz);
203 else if (spa_streq(key, "bluez5.features.device") && !this->device_rules)
204 this->device_rules = strndup(value, sz);
205 }
206 }
207
load_conf(struct spa_bt_quirks * this,const char * path)208 static int load_conf(struct spa_bt_quirks *this, const char *path)
209 {
210 char *data;
211 struct stat sbuf;
212 int fd = -1;
213
214 spa_log_debug(this->log, "loading %s", path);
215
216 if ((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0)
217 goto fail;
218 if (fstat(fd, &sbuf) < 0)
219 goto fail;
220 if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED)
221 goto fail;
222 close(fd);
223
224 load_quirks(this, data, sbuf.st_size);
225 munmap(data, sbuf.st_size);
226
227 return 0;
228
229 fail:
230 if (fd >= 0)
231 close(fd);
232 return -errno;
233 }
234
spa_bt_quirks_create(const struct spa_dict * info,struct spa_log * log)235 struct spa_bt_quirks *spa_bt_quirks_create(const struct spa_dict *info, struct spa_log *log)
236 {
237 struct spa_bt_quirks *this;
238 const char *str;
239
240 if (!info) {
241 errno = -EINVAL;
242 return NULL;
243 }
244
245 this = calloc(1, sizeof(struct spa_bt_quirks));
246 if (this == NULL)
247 return NULL;
248
249 this->log = log;
250
251 spa_log_topic_init(this->log, &log_topic);
252
253 this->force_sbc_xq = parse_force_flag(info, "bluez5.enable-sbc-xq");
254 this->force_msbc = parse_force_flag(info, "bluez5.enable-msbc");
255 this->force_hw_volume = parse_force_flag(info, "bluez5.enable-hw-volume");
256 this->force_faststream = parse_force_flag(info, "bluez5.enable-faststream");
257 this->force_a2dp_duplex = parse_force_flag(info, "bluez5.enable-a2dp-duplex");
258
259 if ((str = spa_dict_lookup(info, "bluez5.hardware-database")) != NULL) {
260 spa_log_debug(this->log, "loading session manager provided data");
261 load_quirks(this, str, strlen(str));
262 } else {
263 char path[PATH_MAX];
264 const char *dir = getenv("SPA_DATA_DIR");
265
266 if (dir == NULL)
267 dir = SPADATADIR;
268
269 if (spa_scnprintf(path, sizeof(path), "%s/bluez5/bluez-hardware.conf", dir) >= 0)
270 load_conf(this, path);
271 }
272
273 if (!(this->kernel_rules && this->adapter_rules && this->device_rules))
274 spa_log_warn(this->log, "failed to load bluez-hardware.conf");
275
276 return this;
277 }
278
spa_bt_quirks_destroy(struct spa_bt_quirks * this)279 void spa_bt_quirks_destroy(struct spa_bt_quirks *this)
280 {
281 free(this->kernel_rules);
282 free(this->adapter_rules);
283 free(this->device_rules);
284 free(this);
285 }
286
log_props(struct spa_log * log,const struct spa_dict * dict)287 static void log_props(struct spa_log *log, const struct spa_dict *dict)
288 {
289 const struct spa_dict_item *item;
290 spa_dict_for_each(item, dict)
291 spa_log_debug(log, "quirk property %s=%s", item->key, item->value);
292 }
293
strtolower(char * src,char * dst,int maxsize)294 static void strtolower(char *src, char *dst, int maxsize)
295 {
296 while (maxsize > 1 && *src != '\0') {
297 *dst = (*src >= 'A' && *src <= 'Z') ? ('a' + (*src - 'A')) : *src;
298 ++src;
299 ++dst;
300 --maxsize;
301 }
302 if (maxsize > 0)
303 *dst = '\0';
304 }
305
spa_bt_quirks_get_features(const struct spa_bt_quirks * this,const struct spa_bt_adapter * adapter,const struct spa_bt_device * device,uint32_t * features)306 int spa_bt_quirks_get_features(const struct spa_bt_quirks *this,
307 const struct spa_bt_adapter *adapter,
308 const struct spa_bt_device *device,
309 uint32_t *features)
310 {
311 struct spa_dict props;
312 struct spa_dict_item items[5];
313 int res;
314
315 *features = ~(uint32_t)0;
316
317 /* Kernel */
318 if (this->kernel_rules) {
319 uint32_t no_features = 0;
320 int nitems = 0;
321 struct utsname name;
322 if ((res = uname(&name)) < 0)
323 return res;
324 items[nitems++] = SPA_DICT_ITEM_INIT("sysname", name.sysname);
325 items[nitems++] = SPA_DICT_ITEM_INIT("release", name.release);
326 items[nitems++] = SPA_DICT_ITEM_INIT("version", name.version);
327 props = SPA_DICT_INIT(items, nitems);
328 log_props(this->log, &props);
329 do_match(this->kernel_rules, &props, &no_features);
330 spa_log_debug(this->log, "kernel quirks:%08x", no_features);
331 *features &= ~no_features;
332 }
333
334 /* Adapter */
335 if (this->adapter_rules) {
336 uint32_t no_features = 0;
337 int nitems = 0;
338 char vendor_id[64], product_id[64], address[64];
339
340 if (spa_bt_format_vendor_product_id(
341 adapter->source_id, adapter->vendor_id, adapter->product_id,
342 vendor_id, sizeof(vendor_id), product_id, sizeof(product_id)) == 0) {
343 items[nitems++] = SPA_DICT_ITEM_INIT("vendor-id", vendor_id);
344 items[nitems++] = SPA_DICT_ITEM_INIT("product-id", product_id);
345 }
346 items[nitems++] = SPA_DICT_ITEM_INIT("bus-type",
347 (adapter->bus_type == BUS_TYPE_USB) ? "usb" : "other");
348 if (adapter->address) {
349 strtolower(adapter->address, address, sizeof(address));
350 items[nitems++] = SPA_DICT_ITEM_INIT("address", address);
351 }
352 props = SPA_DICT_INIT(items, nitems);
353 log_props(this->log, &props);
354 do_match(this->adapter_rules, &props, &no_features);
355 spa_log_debug(this->log, "adapter quirks:%08x", no_features);
356 *features &= ~no_features;
357 }
358
359 /* Device */
360 if (this->device_rules) {
361 uint32_t no_features = 0;
362 int nitems = 0;
363 char vendor_id[64], product_id[64], version_id[64], address[64];
364 if (spa_bt_format_vendor_product_id(
365 device->source_id, device->vendor_id, device->product_id,
366 vendor_id, sizeof(vendor_id), product_id, sizeof(product_id)) == 0) {
367 snprintf(version_id, sizeof(version_id), "%04x",
368 (unsigned int)device->version_id);
369 items[nitems++] = SPA_DICT_ITEM_INIT("vendor-id", vendor_id);
370 items[nitems++] = SPA_DICT_ITEM_INIT("product-id", product_id);
371 items[nitems++] = SPA_DICT_ITEM_INIT("version-id", version_id);
372 }
373 if (device->name)
374 items[nitems++] = SPA_DICT_ITEM_INIT("name", device->name);
375 if (device->address) {
376 strtolower(device->address, address, sizeof(address));
377 items[nitems++] = SPA_DICT_ITEM_INIT("address", address);
378 }
379 props = SPA_DICT_INIT(items, nitems);
380 log_props(this->log, &props);
381 do_match(this->device_rules, &props, &no_features);
382 spa_log_debug(this->log, "device quirks:%08x", no_features);
383 *features &= ~no_features;
384 }
385
386 /* Force flags */
387 if (this->force_msbc != -1) {
388 SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_MSBC, this->force_msbc);
389 SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_MSBC_ALT1, this->force_msbc);
390 SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_MSBC_ALT1_RTL, this->force_msbc);
391 }
392
393 if (this->force_hw_volume != -1)
394 SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_HW_VOLUME, this->force_hw_volume);
395
396 if (this->force_sbc_xq != -1)
397 SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_SBC_XQ, this->force_sbc_xq);
398
399 if (this->force_faststream != -1)
400 SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_FASTSTREAM, this->force_faststream);
401
402 if (this->force_a2dp_duplex != -1)
403 SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_A2DP_DUPLEX, this->force_a2dp_duplex);
404
405 return 0;
406 }
407