On some modern distros (tested: Ubuntu-based, BlueZ 5.85, kernel 7.x) the built-in BlueZ
sixaxis plugin silently does nothing — plug the controller in over USB and it never sets the
master address, never registers the device, never writes an SDP record. The result is either no
connection at all, or a controller that connects and instantly disconnects (js0 flaps), with this
in journalctl -u bluetooth:
profiles/input/device.c:hidp_add_connection() Could not parse HID SDP record: No such file or directory (2)
This guide does the entire pairing manually — the five things the plugin was supposed to do. Everything here persists across reboots.
The DualShock 3 does not implement standard Bluetooth pairing. You must connect it by USB once to write the host adapter's address into the controller ("the master"), then it connects wirelessly.
# Host Bluetooth adapter MAC
bluetoothctl show | grep Controller # e.g. 30:89:4A:11:7E:38
# Plug the DS3 in via USB. Confirm the kernel sees it:
lsusb | grep 054c:0268 # Sony Corp. PlayStation 3 Controller
ls /dev/input/js0 # wired joystick node should existNote your two MACs — referred to below as:
HOST= your laptop's BT adapter (e.g.30:89:4A:11:7E:38)CTRL= the controller's own MAC (read in step 1)
This is what the sixpair tool does, implemented here in pure Python over hidraw (no libusb,
no compiling). Controller must be plugged in via USB. Adjust /dev/hidrawN if needed
(grep -l 'PS3 Controller' /sys/class/hidraw/*/device/uevent).
# ds3pair.py — run: sudo python3 ds3pair.py (read only)
# sudo python3 ds3pair.py set (write host MAC as master)
import fcntl, sys
DEV = "/dev/hidraw3"
HOST = "30:89:4A:11:7E:38" # <-- your adapter MAC
def _IOC(d,t,nr,size): return (d<<30)|(size<<16)|(ord(t)<<8)|nr
def GFEATURE(size): return _IOC(3,'H',0x07,size)
def SFEATURE(size): return _IOC(3,'H',0x06,size)
f = open(DEV, "rb+", buffering=0)
buf = bytearray(17); buf[0]=0xf2 # report 0xf2 = controller identity
fcntl.ioctl(f, GFEATURE(len(buf)), buf, True)
print("CONTROLLER_MAC=" + ":".join("%02X"%b for b in buf[4:10]))
buf2 = bytearray(8); buf2[0]=0xf5 # report 0xf5 = current master
fcntl.ioctl(f, GFEATURE(len(buf2)), buf2, True)
print("CURRENT_MASTER=" + ":".join("%02X"%b for b in buf2[2:8]))
if len(sys.argv)>1 and sys.argv[1]=="set":
m = [int(x,16) for x in HOST.split(":")]
out = bytearray([0xf5,0x01]+m)
fcntl.ioctl(f, SFEATURE(len(out)), out, True)
print("MASTER_SET_TO=" + HOST)sudo python3 ds3pair.py # note CONTROLLER_MAC
sudo python3 ds3pair.py set # writes your adapter as the masterHOST=30:89:4A:11:7E:38
CTRL=A0:5A:5F:33:8D:F0 # from step 1
sudo mkdir -p /var/lib/bluetooth/$HOST/$CTRL
sudo tee /var/lib/bluetooth/$HOST/$CTRL/info >/dev/null <<EOF
[General]
Name=PLAYSTATION(R)3 Controller
Class=0x000508
SupportedTechnologies=BR/EDR;
Trusted=true
Blocked=false
Services=00001124-0000-1000-8000-00805f9b34fb;
[DeviceID]
Source=1
Vendor=1356
Product=616
Version=257
EOFWithout this you get Could not parse HID SDP record and the controller flaps. The DS3 doesn't
serve its own SDP record over Bluetooth, so BlueZ must have it cached up front. This is the standard
DS3 HID record:
SDP="3601920900000A000100000900013503191124090004350D35061901000900113503190011090006350909656E09006A0901000900093508350619112409010009000D350F350D350619010009001335031900110901002513576972656C65737320436F6E74726F6C6C65720901012513576972656C65737320436F6E74726F6C6C6572090102251B536F6E7920436F6D707574657220456E7465727461696E6D656E740902000901000902010901000902020800090203082109020428010902052801090206359A35980822259405010904A101A102850175089501150026FF00810375019513150025013500450105091901291381027501950D0600FF8103150026FF0005010901A10075089504350046FF0009300931093209358102C0050175089527090181027508953009019102750895300901B102C0A1028502750895300901B102C0A10285EE750895300901B102C0A10285EF750895300901B102C0C0090207350835060904090901000902082800090209280109020A280109020B09010009020C093E8009020D280009020E2800"
sudo mkdir -p /var/lib/bluetooth/$HOST/cache
printf '[General]\nName=PLAYSTATION(R)3 Controller\n\n[ServiceRecords]\n0x00010000=%s\n' "$SDP" \
| sudo tee /var/lib/bluetooth/$HOST/cache/$CTRL >/dev/null(SDP record courtesy of https://github.com/debiangamer/ps3controller.)
The DS3 is trusted but never bonds (no link key). Modern BlueZ refuses HID from unbonded devices by default. Flip one switch:
sudo sed -i 's/^#*\s*ClassicBondedOnly.*/ClassicBondedOnly=false/I' /etc/bluetooth/input.conf
# if the line doesn't exist, add ClassicBondedOnly=false under [General]DualShock 3 controllers choke on L2CAP Enhanced Re-Transmission Mode. This is the single most common cause of "it connects then drops instantly":
# runtime
echo 1 | sudo tee /sys/module/bluetooth/parameters/disable_ertm
# permanent
echo "options bluetooth disable_ertm=1" | sudo tee /etc/modprobe.d/bluetooth-ds3.confsudo systemctl restart bluetoothThen unplug the USB cable and press the PS (center) button. The light settles to one solid LED,
/dev/input/js0 appears and stays. It reconnects on the PS button from now on.
ls /dev/input/js0
bluetoothctl info $CTRL | grep -E 'Name|Connected|Trusted'| Symptom | Cause | Fix |
|---|---|---|
| Plugging USB does nothing | BlueZ sixaxis plugin inert on this build |
do it all manually (steps 1–3) |
Could not parse HID SDP record |
no SDP record cached | step 3 |
| Connects then instantly drops | requires bonding / ERTM incompatible | steps 4 & 5 |
Tested on Ubuntu-based distro, BlueZ 5.85, kernel 7.x, ThinkPad (Intel AX211), genuine Sony DS3.