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