Comment 5 for bug 1191898

Revision history for this message
Simon (eierfrucht) wrote :

Currently I am running Kubuntu x64 14.04 LTS on Asus G750JX laptop and just until recently had been plagued by exactly the same bug with my Sound Blaster HD USB. Upgrading to a newer kernel and/or installing the latest unstable pulseaudio proved to decrease the probability of triggering the bug, but it just lingered on and didn't want to go.

Luckily I was able to find and test a workaround involving four simple components: a custom udev rule; a script to be launched upon triggering this rule; another script to properly load pulseaudio's equalizer modules; finally a session cleanup script to prevent device lockup on warm reboot.

After much error and trial I found that the USB Sound Blaster HD would hang when:

1. ...its corresponding ALSA sink is discovered by pulseaudio while pulseaudio simultaneously loads various modules. This causes the device randomly hang on system start.

2. ...its corresponding kernel module snd_usb_audio remains in use at the moment when the device receives an unbind command. This is even worse because once triggered, the device preserves its zombie state across warm reboots (unfortunately few PCs and laptops cycle USB power during a warm reboot)

3. Physical replugging recovers the device, but at least on a few occasions I observed the USB hub itself entering an abnormal state after triggering this bug. Because of this, even physical replugging does not always help. I had to power down the laptop completely.

I found that the best way to keep the device from hanging is to ensure that: 1. it remains unmounted until pulseaudio is up and running 2. its module is unloaded (immediately followed by an unbind command) early during system shutdown

THE FIX:

1. Create and make executable the following script in your user script folder:

#######################################

#!/bin/bash

# The following line makes udev behave differently
# depending on whether the rule is triggered before
# or after pulseaudio has started:

if ! $(ps ax | grep [p]ulseaudio > /dev/null )

# So if it's the first time the rule is triggered, let's unload
# the device's kernel module to ensure safe device removal

then

modprobe -r snd_usb_audio

# Now the module is no longer running, so
# we can safely issue an unbind command

echo $1 | tee /sys/bus/usb/drivers/usb/unbind

# Let's wait for pulseaudio to start...

while ! $(ps ax | grep [p]ulseaudio > /dev/null)

do
sleep 1
done

# ...then one extra second to let finish loading modules:

sleep 1

# Now we can safely bind the device:

echo $1 | tee /sys/bus/usb/drivers/usb/bind
fi

# At this point the rule is triggered again and the script
# has a second run, but does nothing so udev just proceeds
# to automatically load the snd_usb_audio module

#######################################

2. Create a udev rule that launches the script upon device discovery, passing its USB address as an argument:

#######################################

ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="041e", ATTRS{idProduct}=="30d7", ATTRS{manufacturer}=="Creative Technology", ATTRS{product}=="USB Sound Blaster HD", RUN+="/home/user/.scripts/load-usb-audio.sh $attr{busnum}-$attr{devpath}"

#######################################

3. If you are using pulseaudio equalizer, remove these two lines from /etc/pulse/default.pa:

#######################################

pacmd load-module module-dbus-protocol
pacmd load-module module-equalizer-sink sink_name=equalized

#######################################

Then create and make executable the following script in your user script folder:

#######################################

#!/bin/bash

# Let's wait until we can say for sure that pulseaudio is running
# and the USB device has been given time to initialize gracefully

while ! $(lsmod | grep [s]nd_usb_audio > /dev/null); do sleep 1; done

# Then another second, otherwise there's
# a slim chance of triggering the bug

sleep 1

# Now we can safely load equalizer modules:

pacmd load-module module-dbus-protocol
pacmd load-module module-equalizer-sink sink_name=equalized

#######################################

Make the above script execute as user upon login. The script must be aware of the user's pulseaudio session, otherwise it will fail (e.g. plainly starting it from ~/.profile won't work) In KDE, the best way to achieve this is to add the script as a 'desktop file' to the Autorun section in System Settings. Anything added as a 'desktop file' acts the same as if it were started by the user from their desktop environment, while 'script file' entries are not aware of user's dbus / pulseaudio sessions, etc. Other distros may employ different methods so please make sure pacmd has access to the user's pulseaudio session during script execution.

4. Lastly, create and make executable the following script in your user script folder:

#######################################

# let's find the device's USB port:

for f in $(ls -1L /sys/bus/usb/drivers/usb/*/product)

do
if [ "$(cat $f)" == "USB Sound Blaster HD" ]

then
SB_USB="$(echo $f | sed 's/\// /g' | awk '{print $6}')"
fi
done

# Otherwise no graceful ALSA shudown:
killall pulseaudio

# Otherwise no permission to unload driver module:
alsa force-unload

# Doing this before unbinding device saves the day:
modprobe -r snd_usb_audio

# For a safer world better do this:
echo $SB_USB | tee /sys/bus/usb/drivers/usb/unbind

#######################################

Make this run with root privileges as early during shutdown as possible. With Kubuntu's default session manager, LightDM, the best way to achieve this is to call the script from a session cleanup script defined in /etc/lightdm/lightdm.conf (google 'lightdm.conf session-cleanup-script' if unfamiliar)