Common Problem: namespace race conditions

From MythTV Official Wiki
Jump to: navigation, search

The Symptom

If you're here it's because you're experiencing a problem where your various tuners don't always seem to wind up with the same device name each time the machine boots and it's causing your backend to fail to use the correct tuner (or any tuner at all) to record with.

This is most likely to occur if you have multiple tuner devices on separate buses (meaning one or more USB devices and/or one or more PCI devices), although it can potentially happen under rarer circumstances like the cards being moved around inside the machine, or the USB devices being plugged into a different hub or different ports.

Solving this problem can be done semi-permanently with little risk to the rest of your configuration but will require a small amount a reading, a few shell commands, and the ability to cut and paste short strings without making mistakes.

The Problem

An old problem that originally occurred when udev/HAL were first being deployed has come back to haunt you.

Prior to the developments of udev/HAL, if you had two of the same piece of hardware in your machine, it was up to you to find a way to be certain that either they couldn't swap places between reboots or that it didn't matter if it did. At that time, the kernel searched for these devices in a specific order which avoided many potential problems.

The change to having udev enumerate (in short, identify them, load their respective driver, and give them their /dev node if applicable) devices on USB and PCI buses brought this problem back with a vengeance. Initially udev loaded driver modules based on whichever devices on the bus identified themselves first, but this is not guaranteed to happen in the same order very time. The changes to udev to ensure that devices are enumerated consistently in the same order applies to each individual bus and under certain circumstances may still fail to do what you expect if these devices are on different buses. This may be the case if you have both PCI and USB tuner devices.

During one boot, a USB device may be assigned /dev/video0 while a PCI device be given /dev/video1. During the the next boot, this situation can actually reverse itself. Particularly pressing is the issue of a Hauppauge PVR-500, which while having two tuners on the same card can actually wind up with it's second /dev/video[1-9] node not immediately following the first, should a USB tuner device announce itself at the right time.

The Solution

The solution is relatively simple. If your MythTV box is to host more than one sufficiently different type of tuner or video input, we'll need to add a custom udev rule that gives these devices separate and unique namespaces by which MythTV can access them. To do this you'll need to find a unique bit of information that udev can reliably recognize about each device, and the name of a new symlink for it to create.

Identifying the device

Important.png Note: The "udevinfo" utility mentioned here has somewhat recently undergone a name change and may or may not be present on your machine as "udevinfo". If this is the case, substitute "udevadm info" any time you need to type a udevinfo command. All the other options for the utility have remained the same.

In order for udev to be able to handle rules which match our device, we need to first find out something relatively unique about what udev will know about the device the next time it is initialized. The first step to doing this is to find out the sysfs path to the device. Assuming for the moment your device is currently connected and functioning properly as /dev/video0, run the following command to query udev about the sysfs path used by the device at /dev/video0.

udevinfo -q path -n /dev/video0

Udevinfo will tell you the device uses a sysfs path of something like /class/video4linux/video0. Next, using that sysfs path you just discovered, you'll run the udevinfo command again to tell it to show you practically everything it knows about the device, what it's connected to, and what that's connected to. The output from this can be relatively long and winding, so you'll probably want to pipe it through less.

user@mythtv:~$ udevinfo -a -p /class/video4linux/video0 | less

Udevinfo starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

 looking at device '/class/video4linux/video0':
   KERNEL=="video0"
   SUBSYSTEM=="video4linux"
   DRIVER==""
   ATTR{dev}=="81:0"
   ATTR{name}=="Hauppauge HD PVR"
   ATTR{index}=="0"

 looking at parent device '/devices/pci0000:00/0000:00:04.1/usb3/3-1/3-1:1.0':
   KERNELS=="3-1:1.0"
   SUBSYSTEMS=="usb"
   DRIVERS=="hdpvr"
   ATTRS{bInterfaceNumber}=="00"
   ATTRS{bAlternateSetting}==" 0"
   ATTRS{bNumEndpoints}=="02"
   ATTRS{bInterfaceClass}=="ff"
   ATTRS{bInterfaceSubClass}=="02"
   ATTRS{bInterfaceProtocol}=="00"
   ATTRS{modalias}=="usb:v2040p4902d0000dc00dsc00dp00icFFisc02ip00"
   ATTRS{supports_autosuspend}=="0"

 looking at parent device '/devices/pci0000:00/0000:00:04.1/usb3/3-1':
   KERNELS=="3-1"
   SUBSYSTEMS=="usb"
   DRIVERS=="usb"
   ATTRS{dev}=="189:263"
   ATTRS{configuration}==""
   ATTRS{bNumInterfaces}==" 1"
   ATTRS{bConfigurationValue}=="1"
   ATTRS{bmAttributes}=="c0"
   ATTRS{bMaxPower}=="  4mA"
   ATTRS{urbnum}=="1403294"
   ATTRS{idVendor}=="2040"
   ATTRS{idProduct}=="4902"
   ATTRS{bcdDevice}=="0000"
   ATTRS{bDeviceClass}=="00"
   ATTRS{bDeviceSubClass}=="00"
   ATTRS{bDeviceProtocol}=="00"
   ATTRS{bNumConfigurations}=="1"
   ATTRS{bMaxPacketSize0}=="64"
   ATTRS{speed}=="480"
   ATTRS{busnum}=="3"
   ATTRS{devnum}=="8"
   ATTRS{version}==" 2.00"
   ATTRS{maxchild}=="0"
   ATTRS{quirks}=="0x0"
   ATTRS{authorized}=="1"
   ATTRS{manufacturer}=="AMBA"
   ATTRS{product}=="Hauppauge HD PVR"
   ATTRS{serial}=="FEEBDAED"

Some explanation is in order about what these sections represent. The first section is about the device node udev created when it saw the device appear. The second section is the driver module that was invoked after udev looked up which driver belonged to this device. The third section is the USB device itself, the HD-PVR. The section after that would be it's position on the USB bus and so on and so on.

Creating a custom udev rule

When a new device is plugged in, udev takes all the information (like above) about the device and compares it to a set of rules that reside under /etc/udev/rules.d. These rules are stored in plain text files, one rule per line, and can be edited with any decent Unix text editor. They are read by udev sorted by their filename, so any decent Linux distribution will start the filenames for these rules with a number like 50-udev-default.rules or 70-persistent-net.rules. Within these files, rules are simply stated one per line, and look quite a bit like just a bunch of variable assignments separated by commas.

If you absolutely must know the full details about writing udev rules, more complete documentation exists at http://reactivated.net/writing_udev_rules.html but for now, we're only going to deal with three things.

  • Anything that looks like TOKEN=="value" is a test to see if the variable TOKEN is set to "value".
  • Anything that says TOKEN="value" (one equals sign) actually sets TOKEN to "value" if all the tests in that line are true.
  • Anything that says TOKEN+="value" appends value to TOKEN if all the tests in that line are true.

As a quick example, here is one of the default rules from 40-video.rules which is responsible for making the /dev/v4l/video* nodes have symlinks in /dev as /dev/video.

KERNEL=="video[0-9]*",  NAME="v4l/video%n", SYMLINK+="%k", GROUP="video"

The first argument is a test which is only true if what the kernel calls this device is "video[0-9]*" (yes it takes globmatches just like the shell). The second argument actually sets the name of the device node to "v4l/video%n" which is appended to the value of udev_root (defined in /etc/rules.d/udev.conf) before being created. The third part adds a symlink directly in udev_root whch points to this device node, and the last part sets the group ownership attribute of the new device node to the video group. The "%n" is simply the kernel number of the device (generally starting at 0 and going up for each device). The "%k" is another special variable which evaluates to be the exact value of KERNEL (hence, the first matching device's symlink becomes /dev/video0).

When you create your new, custom udev rules for your tuners, you will want to use your own rules file as the existing ones are likely to be replaced or overridden when you update your Linux installation. These files may be named almost anything, and when they happen in the processing order isn't even very important to us so aim for somewhere around the middle with a filename called something like "51-mythtvstuff.rules".

Renaming the device

A strong suggestion would be to create new device symlink names which closely follow the hardware's actual name, so that they may be accessed by MythTV through more descriptively-named symbolic links instead of as generic video4linux devices named /dev/video[0-9]. After having created these new rules when you configure MythTV in mythtv-setup use these new, custom device names instead of the generic /dev/video[0-9]* names.

  • Hauppauge PVR-nnn units may be named /dev/pvrnnn_[0-9].
  • Hauppauge HD-PVR units may be named /dev/hdpvr[0-9].
  • Tuners which are connected to specific sources should probably not be assigned using the same names that could be generated by automatically numbering rules like the above. Use devicename-first and devicename-second and so on instead.

Example rules

Define your new rules to be as strict or as relaxed as you see fit, but here are some use-case samples, one of which may show you exactly what you need to do.

As mentioned in the previous section, if you would like your Hauppauge HD-PVRs to be accessible as /dev/hdpvr[0-9]* you can use the rule below. This will make any device whose product name is "Hauppauge HD PVR" that is handled by the video4linux subsystem have an extra symlink created named /dev/hdpvr[0-9]*.

SUBSYSTEM=="video4linux", ATTRS{product}=="Hauppauge HD PVR", SYMLINK+="hdpvr%n"

If you have two Hauppauge HD-PVRs and they're each connected to different video sources so which one comes first matters to you, you can provide them each a specific symlink name by matching against their serial numbers.

Warning.png Warning: It is advisable to not use numbers on the end of a symlink name if the same name could possibly by created by some other rule. A slightly weak example of this would be if you assigned two HD-PVRs to hdpvr0 and hdpvr1 and then added a third HD-PVR later. Udev might see your newest device first and create the /dev/hdpvr0 symlink, which would promptly be overwritten when it later sees the unit you assigned explicitly to /dev/hdpvr0 (instead of /dev/hdpvr-first).

SUBSYSTEM=="video4linux", ATTRS{product}=="Hauppauge HD PVR", ATTRS{serial}=="00987654", SYMLINK+="hdpvr-first"
SUBSYSTEM=="video4linux", ATTRS{product}=="Hauppauge HD PVR", ATTRS{serial}=="00123456", SYMLINK+="hdpvr-second"

If you have two Hauppauge PVR-150's and they're each connected to different video sources as above, things must be done a little differently. They don't have a specific product name tied to them like the HD-PVR, but you can match against the PCI product and vendor codes (which should be just as exact). They don't have unique serial numbers stored in them either, so the next best thing will be to make the rule match which PCI socket they're plugged into. The PCI socket is identified by it's bus ID number (which is the KERNELS token in the second section udevinfo shows).

Information.png Tip: If you're confused about which set of numbers are the PCI product/vendor codes, `/sbin/lspci -nn` shows them in square brackets separated by a colon at the end of each line.

SUBSYSTEM=="video4linux", ATTRS{subsystem_vendor}=="0x0070", ATTRS{subsystem_device}=="0x8003", KERNELS=="0000:01:06.0", SYMLINK+="pvr150-first"
SUBSYSTEM=="video4linux", ATTRS{subsystem_vendor}=="0x0070", ATTRS{subsystem_device}=="0x8003", KERNELS=="0000:01:07.0", SYMLINK+="pvr150-second"

Be warned that tying a rule to the PCI socket a card is plugged into instead of the actual card will break if you move the card to a different socket, but in the above example we don't really have any other choice.