From 2c9764da1dec638fa0618328828b5ed66f6cc031 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 13 Jun 2021 19:18:07 +0300 Subject: [PATCH] bluez5: parse bluez vendor/product ids It seems few devices support the Device Id via bluez. Try to figure out vendor/product ids for usb devices also via sysfs. Also try to figure out the adapter bus type. --- spa/plugins/bluez5/bluez5-dbus.c | 140 ++++++++++++++++++++++++++++++- spa/plugins/bluez5/defs.h | 19 +++++ 2 files changed, 157 insertions(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index afe88f7b9..24faf98f5 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -524,6 +524,38 @@ static bool check_iter_signature(DBusMessageIter *it, const char *sig) return res; } +static int parse_modalias(const char *modalias, uint16_t *source, uint16_t *vendor, + uint16_t *product, uint16_t *version) +{ + char *pos; + unsigned int src, i, j, k; + + if (strncmp(modalias, "bluetooth:", strlen("bluetooth:")) == 0) + src = SOURCE_ID_BLUETOOTH; + else if (strncmp(modalias, "usb:", strlen("usb:")) == 0) + src = SOURCE_ID_USB; + else + return -EINVAL; + + pos = strchr(modalias, ':'); + if (pos == NULL) + return -EINVAL; + + if (sscanf(pos + 1, "v%04Xp%04Xd%04X", &i, &j, &k) != 3) + return -EINVAL; + + /* Ignore BlueZ placeholder value */ + if (src == SOURCE_ID_USB && i == 0x1d6b && j == 0x0246) + return -ENXIO; + + *source = src; + *vendor = i; + *product = j; + *version = k; + + return 0; +} + static int adapter_update_props(struct spa_bt_adapter *adapter, DBusMessageIter *props_iter, DBusMessageIter *invalidated_iter) @@ -561,6 +593,14 @@ static int adapter_update_props(struct spa_bt_adapter *adapter, free(adapter->address); adapter->address = strdup(value); } + else if (spa_streq(key, "Modalias")) { + int ret; + ret = parse_modalias(value, &adapter->source_id, &adapter->vendor_id, + &adapter->product_id, &adapter->version_id); + if (ret < 0) + spa_log_debug(monitor->log, "adapter %p: %s=%s ignored: %s", + adapter, key, value, spa_strerror(ret)); + } } else if (type == DBUS_TYPE_UINT32) { uint32_t value; @@ -616,6 +656,63 @@ static int adapter_update_props(struct spa_bt_adapter *adapter, return 0; } +static int adapter_init_bus_type(struct spa_bt_monitor *monitor, struct spa_bt_adapter *d) +{ + char path[1024], buf[1024]; + const char *str; + ssize_t res = -EINVAL; + + d->bus_type = BUS_TYPE_OTHER; + + str = strrchr(d->path, '/'); /* hciXX */ + if (str == NULL) + return -ENOENT; + + snprintf(path, sizeof(path), "/sys/class/bluetooth/%s/device/subsystem", str); + if ((res = readlink(path, buf, sizeof(buf)-1)) < 0) + return -errno; + buf[res] = '\0'; + + str = strrchr(buf, '/'); + if (str && spa_streq(str, "/usb")) + d->bus_type = BUS_TYPE_USB; + return 0; +} + +static int adapter_init_modalias(struct spa_bt_monitor *monitor, struct spa_bt_adapter *d) +{ + char path[1024]; + FILE *f = NULL; + int vendor_id, product_id; + const char *str; + int res = -EINVAL; + + /* Lookup vendor/product id for the device, if present */ + str = strrchr(d->path, '/'); /* hciXX */ + if (str == NULL) + goto fail; + snprintf(path, sizeof(path), "/sys/class/bluetooth/%s/device/modalias", str); + if ((f = fopen(path, "rb")) == NULL) { + res = -errno; + goto fail; + } + if (fscanf(f, "usb:v%04Xp%04X", &vendor_id, &product_id) != 2) + goto fail; + d->source_id = SOURCE_ID_USB; + d->vendor_id = vendor_id; + d->product_id = product_id; + fclose(f); + + spa_log_debug(monitor->log, "adapter %p: usb vendor:%04x product:%04x", + d, vendor_id, product_id); + return 0; + +fail: + if (f) + fclose(f); + return res; +} + static struct spa_bt_adapter *adapter_create(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_adapter *d; @@ -629,6 +726,9 @@ static struct spa_bt_adapter *adapter_create(struct spa_bt_monitor *monitor, con spa_list_prepend(&monitor->adapter_list, &d->link); + adapter_init_bus_type(monitor, d); + adapter_init_modalias(monitor, d); + return d; } @@ -745,12 +845,33 @@ static void device_free(struct spa_bt_device *device) free(device); } +int spa_bt_format_vendor_product_id(uint16_t source_id, uint16_t vendor_id, uint16_t product_id, + char *vendor_str, int vendor_str_size, char *product_str, int product_str_size) +{ + char *source_str; + + switch (source_id) { + case SOURCE_ID_USB: + source_str = "usb"; + break; + case SOURCE_ID_BLUETOOTH: + source_str = "bluetooth"; + break; + default: + return -EINVAL; + } + + spa_scnprintf(vendor_str, vendor_str_size, "%s:%04x", source_str, (unsigned int)vendor_id); + spa_scnprintf(product_str, product_str_size, "%04x", (unsigned int)product_id); + return 0; +} + static void emit_device_info(struct spa_bt_monitor *monitor, struct spa_bt_device *device, bool with_connection) { struct spa_device_object_info info; - char dev[32], name[128], class[16]; - struct spa_dict_item items[20]; + char dev[32], name[128], class[16], vendor_id[64], product_id[64], product_id_tot[67]; + struct spa_dict_item items[23]; uint32_t n_items = 0; info = SPA_DEVICE_OBJECT_INFO_INIT(); @@ -767,6 +888,13 @@ static void emit_device_info(struct spa_bt_monitor *monitor, items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_NAME, name); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_DESCRIPTION, device->alias); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ALIAS, device->name); + if (spa_bt_format_vendor_product_id( + device->source_id, device->vendor_id, device->product_id, + vendor_id, sizeof(vendor_id), product_id, sizeof(product_id)) == 0) { + snprintf(product_id_tot, sizeof(product_id_tot), "0x%s", product_id); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, vendor_id); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, product_id_tot); + } items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_FORM_FACTOR, spa_bt_form_factor_name( spa_bt_form_factor_from_class(device->bluetooth_class))); @@ -1135,6 +1263,14 @@ static int device_update_props(struct spa_bt_device *device, free(device->icon); device->icon = strdup(value); } + else if (spa_streq(key, "Modalias")) { + int ret; + ret = parse_modalias(value, &device->source_id, &device->vendor_id, + &device->product_id, &device->version_id); + if (ret < 0) + spa_log_debug(monitor->log, "device %p: %s=%s ignored: %s", + device, key, value, spa_strerror(ret)); + } } else if (type == DBUS_TYPE_UINT32) { uint32_t value; diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 12b1dd226..87e3cadfd 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -154,6 +154,12 @@ extern "C" { #define HSP_HS_DEFAULT_CHANNEL 3 +#define SOURCE_ID_BLUETOOTH 0x1 /* Bluetooth SIG */ +#define SOURCE_ID_USB 0x2 /* USB Implementer's Forum */ + +#define BUS_TYPE_USB 1 +#define BUS_TYPE_OTHER 255 + #define HFP_AUDIO_CODEC_CVSD 0x01 #define HFP_AUDIO_CODEC_MSBC 0x02 @@ -206,6 +212,10 @@ static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid) } int spa_bt_profiles_from_json_array(const char *str); +int spa_bt_format_vendor_product_id(uint16_t source_id, uint16_t vendor_id, + uint16_t product_id, char *vendor_str, int vendor_str_size, + char *product_str, int product_str_size); + enum spa_bt_hfp_ag_feature { SPA_BT_HFP_AG_FEATURE_NONE = (0), SPA_BT_HFP_AG_FEATURE_3WAY = (1 << 0), @@ -290,6 +300,11 @@ struct spa_bt_adapter { char *alias; char *address; char *name; + int bus_type; + uint16_t source_id; + uint16_t vendor_id; + uint16_t product_id; + uint16_t version_id; uint32_t bluetooth_class; uint32_t profiles; int powered; @@ -405,6 +420,10 @@ struct spa_bt_device { char *battery_path; char *name; char *icon; + uint16_t source_id; + uint16_t vendor_id; + uint16_t product_id; + uint16_t version_id; uint32_t bluetooth_class; uint16_t appearance; uint16_t RSSI;