pulseaudio/src/modules/udev-util.c
Takashi Sakamoto 3fcd5e398d Revert "udev: use ID_MODEL/ID_VENDOR to give friendly name for FireWire devices"
This reverts commit 3ac73598c6.

Systemd v249 has new entries of hwdb for node and unit in IEEE 1394 bus
(hwdb.d/80-ieee1394-unit-function.hwdb). It can obsolete my workaround
added by commit 3ac73598c6 ("udev: use ID_MODEL/ID_VENDOR to give
friendly name for FireWire devices"). The hwdb entry is handy prepared.
When user finds missing entry, it's preferable to file issue or merge
request in systemd project site.

IEEE 1394 bus is enough legacy and it's easy to expect that few developer
can evaluate the change. For reviewers, I describe the original issues and
the integration of hwdb in systemd side.

In systemd, udev rule for sound card (rules.d/78-sound-card.rules) has
below line to assign information in hwdb to instance for sound card.

```
IMPORT{builtin}="hwdb"
```

In the case, the udev hwdb builtin finds information according to
modalias by following nodes in device topology tree toward root. For
sound card associated to unit in node in IEEE 1394 bus, it's inconvenient
since hwdb had no entry for the unit. The instance for node in IEEE 1394
bus doesn't have modalias. As a result, the builtin reaches 1394 OHCI
controller in PCI Express bus which maintains the IEEE 1394 bus, then the
value for ID_VENDOR_FROM_DATABASE and ID_MODEL_FROM_DATABASE properties
from hwdb of pci device (hwdb.d/20-pci-vendor-model.hwdb) for the sound
card.

For example, when two nodes are in IEEE 1394 bus and one of them has
unit instance for audio and music functions, the topology of the bus is
depicted in following diagram:

```
 * 1394 OHCI controller (pci*, modalias)
   * node A - (pci*/fw0, /dev/fw0)
   * node B - (pci*/fw1, /dev/fw1)
     * unit B-1 - (pci*/fw1/fw1.0, modalias)
       * sound card 0 - (pci*/fw1/fw1.0/sound/card0, card0)
```

In the case,  the udev hwdb builtin picks up from hwdb of pci device for
the sound card:

```
$ udevadm test-builtin hwdb /sys/class/sound/card2
Load module index
Parsed configuration file /usr/lib/systemd/network/99-default.link
Parsed configuration file /usr/lib/systemd/network/73-usb-net-by-mac.link
Created link configuration context.
ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
ID_PCI_SUBCLASS_FROM_DATABASE=FireWire (IEEE 1394)
ID_PCI_INTERFACE_FROM_DATABASE=OHCI
ID_VENDOR_FROM_DATABASE=Texas Instruments
ID_MODEL_FROM_DATABASE=XIO2213A/B/XIO2221 IEEE-1394b OHCI Controller [Cheetah Express]
Unload module index
Unloaded link configuration context.
```

The aim of my workaround is to avoid using ID_VENDOR_FROM_DATABASE and
ID_MODEL_FROM_DATABASE for sound card associated to unit in IEEE 1394
bus. Instead, ID_VENDOR and ID_MODEL properties are used.

However, it has another issue. For the properties, the udev rule for
sound card has the other lines for sound card associated to unit in
IEEE 1394 bus, below:

```
SUBSYSTEMS=="firewire", ATTRS{guid}=="?*", \
  ENV{ID_BUS}="firewire", ENV{ID_SERIAL}="$attr{guid}", ENV{ID_SERIAL_SHORT}="$attr{guid}", \
  ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{model}", \
  ENV{ID_VENDOR}="$attr{vendor_name}", ENV{ID_MODEL}="$attr{model_name}"
SUBSYSTEMS=="firewire", GOTO="skip_pci"
```

The values of ID_VENDOR and ID_MODEL properties come from vendor_name and
model_name attributes in parent instance of the sound card, therefore
they come from audio and music units in IEEE 1394 bus. Unfortunately
these attributes are not available always.

All of nodes in IEEE 1394 bus should have configuration ROM in place
according to IEEE 1212 and Linux FireWire subsystem parses the content of
ROM to detect units in the node. At the same time, the subsystem manages
to detect information about vendor and model according to standard layout
defined by 1394 Trading Association[1].

When the content of ROM is against the standard, the subsystem is
discouraged the name detection.  In the case, vendor_name and model_name
attributes are not available, and supplemental information should be from
software implementation.

The new hwdb (hwdb.d/80-ieee1394-unit-function.hwdb) added to systemd v249
can solve the above issues. The prepared names for vendor and model in
hwdb are assigned to both node and unit. The udev hwdb builtin can find
the vendor and model names for the unit according to modalias before
arriving at pci-device. Regardless of standard or non-standard
configuration ROM, the hwdb gives prepared names of vendor and model.

This is an example of Mark of the Unicorn (MOTU) Traveler. The search
finishes at instance for unit in IEEE 1394 bus expectedly:

```
$ udevadm test-builtin hwdb /sys/class/sound/card2
Load module index
Parsed configuration file /usr/lib/systemd/network/99-default.link
Parsed configuration file /usr/lib/systemd/network/73-usb-net-by-mac.link
Created link configuration context.
ID_MODEL_FROM_DATABASE=Traveler
ID_VENDOR_FROM_DATABASE=MOTU
IEEE1394_UNIT_FUNCTION_AUDIO=1
IEEE1394_UNIT_FUNCTION_MIDI=1
Unload module index
Unloaded link configuration context.
```

[1] Configuration ROM for AV/C Devices 1.0 (Dec. 12, 2000, 1394 Trading
    Association, TA Document 1999027)

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/610>
2021-08-11 15:04:07 +00:00

299 lines
7.9 KiB
C

/***
This file is part of PulseAudio.
Copyright 2009 Lennart Poettering
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <libudev.h>
#include <pulse/xmalloc.h>
#include <pulse/proplist.h>
#include <pulsecore/log.h>
#include <pulsecore/core-util.h>
#include "udev-util.h"
static int read_id(struct udev_device *d, const char *n) {
const char *v;
unsigned u;
pa_assert(d);
pa_assert(n);
if (!(v = udev_device_get_property_value(d, n)))
return -1;
if (pa_startswith(v, "0x"))
v += 2;
if (!*v)
return -1;
if (sscanf(v, "%04x", &u) != 1)
return -1;
if (u > 0xFFFFU)
return -1;
return u;
}
static int dehex(char x) {
if (x >= '0' && x <= '9')
return x - '0';
if (x >= 'A' && x <= 'F')
return x - 'A' + 10;
if (x >= 'a' && x <= 'f')
return x - 'a' + 10;
return -1;
}
static void proplist_sets_unescape(pa_proplist *p, const char *prop, const char *s) {
const char *f;
char *t, *r;
int c = 0;
enum {
TEXT,
BACKSLASH,
EX,
FIRST
} state = TEXT;
/* The resulting string is definitely shorter than the source string */
r = pa_xnew(char, strlen(s)+1);
for (f = s, t = r; *f; f++) {
switch (state) {
case TEXT:
if (*f == '\\')
state = BACKSLASH;
else
*(t++) = *f;
break;
case BACKSLASH:
if (*f == 'x')
state = EX;
else {
*(t++) = '\\';
*(t++) = *f;
state = TEXT;
}
break;
case EX:
c = dehex(*f);
if (c < 0) {
*(t++) = '\\';
*(t++) = 'x';
*(t++) = *f;
state = TEXT;
} else
state = FIRST;
break;
case FIRST: {
int d = dehex(*f);
if (d < 0) {
*(t++) = '\\';
*(t++) = 'x';
*(t++) = *(f-1);
*(t++) = *f;
} else
*(t++) = (char) (c << 4) | d;
state = TEXT;
break;
}
}
}
switch (state) {
case TEXT:
break;
case BACKSLASH:
*(t++) = '\\';
break;
case EX:
*(t++) = '\\';
*(t++) = 'x';
break;
case FIRST:
*(t++) = '\\';
*(t++) = 'x';
*(t++) = *(f-1);
break;
}
*t = 0;
pa_proplist_sets(p, prop, r);
pa_xfree(r);
}
int pa_udev_get_info(int card_idx, pa_proplist *p) {
int r = -1;
struct udev *udev;
struct udev_device *card = NULL;
char *t;
const char *v;
int id;
pa_assert(p);
pa_assert(card_idx >= 0);
if (!(udev = udev_new())) {
pa_log_error("Failed to allocate udev context.");
goto finish;
}
t = pa_sprintf_malloc("/sys/class/sound/card%i", card_idx);
card = udev_device_new_from_syspath(udev, t);
pa_xfree(t);
if (!card) {
pa_log_error("Failed to get card object.");
goto finish;
}
if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS_PATH))
if (((v = udev_device_get_property_value(card, "ID_PATH")) && *v) ||
(v = udev_device_get_devpath(card)))
pa_proplist_sets(p, PA_PROP_DEVICE_BUS_PATH, v);
if (!pa_proplist_contains(p, "sysfs.path"))
if ((v = udev_device_get_devpath(card)))
pa_proplist_sets(p, "sysfs.path", v);
if (!pa_proplist_contains(p, "udev.id"))
if ((v = udev_device_get_property_value(card, "ID_ID")) && *v)
pa_proplist_sets(p, "udev.id", v);
if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS))
if ((v = udev_device_get_property_value(card, "ID_BUS")) && *v)
pa_proplist_sets(p, PA_PROP_DEVICE_BUS, v);
if (!pa_proplist_contains(p, PA_PROP_DEVICE_VENDOR_ID))
if ((id = read_id(card, "ID_VENDOR_ID")) > 0)
pa_proplist_setf(p, PA_PROP_DEVICE_VENDOR_ID, "%04x", id);
if (!pa_proplist_contains(p, PA_PROP_DEVICE_VENDOR_NAME)) {
if ((v = udev_device_get_property_value(card, "ID_VENDOR_FROM_DATABASE")) && *v)
pa_proplist_sets(p, PA_PROP_DEVICE_VENDOR_NAME, v);
else if ((v = udev_device_get_property_value(card, "ID_VENDOR_ENC")) && *v)
proplist_sets_unescape(p, PA_PROP_DEVICE_VENDOR_NAME, v);
else if ((v = udev_device_get_property_value(card, "ID_VENDOR")) && *v)
pa_proplist_sets(p, PA_PROP_DEVICE_VENDOR_NAME, v);
}
if (!pa_proplist_contains(p, PA_PROP_DEVICE_PRODUCT_ID))
if ((id = read_id(card, "ID_MODEL_ID")) >= 0)
pa_proplist_setf(p, PA_PROP_DEVICE_PRODUCT_ID, "%04x", id);
if (!pa_proplist_contains(p, PA_PROP_DEVICE_PRODUCT_NAME)) {
if ((v = udev_device_get_property_value(card, "ID_MODEL_FROM_DATABASE")) && *v)
pa_proplist_sets(p, PA_PROP_DEVICE_PRODUCT_NAME, v);
else if ((v = udev_device_get_property_value(card, "ID_MODEL_ENC")) && *v)
proplist_sets_unescape(p, PA_PROP_DEVICE_PRODUCT_NAME, v);
else if ((v = udev_device_get_property_value(card, "ID_MODEL")) && *v)
pa_proplist_sets(p, PA_PROP_DEVICE_PRODUCT_NAME, v);
}
if (!pa_proplist_contains(p, PA_PROP_DEVICE_SERIAL))
if ((v = udev_device_get_property_value(card, "ID_SERIAL")) && *v)
pa_proplist_sets(p, PA_PROP_DEVICE_SERIAL, v);
if (!pa_proplist_contains(p, PA_PROP_DEVICE_CLASS))
if ((v = udev_device_get_property_value(card, "SOUND_CLASS")) && *v)
pa_proplist_sets(p, PA_PROP_DEVICE_CLASS, v);
if (!pa_proplist_contains(p, PA_PROP_DEVICE_FORM_FACTOR))
if ((v = udev_device_get_property_value(card, "SOUND_FORM_FACTOR")) && *v)
pa_proplist_sets(p, PA_PROP_DEVICE_FORM_FACTOR, v);
/* This is normally not set by the udev rules but may be useful to
* allow administrators to overwrite the device description.*/
if (!pa_proplist_contains(p, PA_PROP_DEVICE_DESCRIPTION))
if ((v = udev_device_get_property_value(card, "SOUND_DESCRIPTION")) && *v)
pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, v);
r = 0;
finish:
if (card)
udev_device_unref(card);
if (udev)
udev_unref(udev);
return r;
}
char* pa_udev_get_property(int card_idx, const char *name) {
struct udev *udev;
struct udev_device *card = NULL;
char *t, *r = NULL;
const char *v;
pa_assert(card_idx >= 0);
pa_assert(name);
if (!(udev = udev_new())) {
pa_log_error("Failed to allocate udev context.");
goto finish;
}
t = pa_sprintf_malloc("/sys/class/sound/card%i", card_idx);
card = udev_device_new_from_syspath(udev, t);
pa_xfree(t);
if (!card) {
pa_log_error("Failed to get card object.");
goto finish;
}
if ((v = udev_device_get_property_value(card, name)) && *v)
r = pa_xstrdup(v);
finish:
if (card)
udev_device_unref(card);
if (udev)
udev_unref(udev);
return r;
}