The ‘What’ and the ‘Why’
Introduction
I decided to write up my notes from my journey of getting a wireless controller to work as a Bluetooth human input device under FreeBSD 14.1-RELEASE, namely by patching bthidd(8), in the hope that it is useful or applicable for someone else.Effectively the same topic was already addressed, albeit inconclusively, here: Thread playstation-5-dualsense-controller-pairing.80786. Much of the knowledge gathered there was applicable to my case.
This is presented as two posts, the first containing relevant background information, and the second an actual guide/how-to.
Background
I had never owned or used a controller until two weeks ago; this was not least an exercise in demystification. I was looking in the first instance for an analogue input device for emulation (primarily PCSX2) via SDL. For lack of other knowledge, I settled on the DualShock 4 because a) it is readily available and replaceable, and b) already had robust support as a USB input device under FreeBSD >= 13.0 with wulf's ps4dshock(4) kernel module. While USB support is itself already quite satisfactory, sometimes it's more comfortable not to have an extra cable dangling around, and the experience offered by the controller seemed incomplete without at least rudimentary Bluetooth connectivity.Requirements
The sole objective was to capture analogue stick and button input events. Other features supported by the ps4dshock(4) driver, but not part of this project/how-to, are:- LED (‘light bar’) color changes
- Synaptics touchpad support
- Accelerometer/gyroscope events
- Haptic feedback (‘rumble’)
Preliminary Investigation
When plugged in via USB, the device worked OOTB with all expected features. However, when it was connected via Bluetooth (cf. Steps 1–2 below), even after thebthidd
service was started, no input device showed up, nor was it detected in any form by SDL. The key, as it turns out, is to add support for gamepad devices to the user-space daemon bthidd(8) – this is actually quite easy to do.Helpful Debugging Tools and Tips
The following tools were very helpful in the process of figuring this all out:- comms/hcidump – like tcpdump(1) for Bluetooth/L2CAP, useful for inspecting reports from connected Bluetooth devices (run with
-x
flag) cat /var/run/devd.pipe
– useful for seeing when devices are attached/detached- x11/evtest – for debugging
evdev
events generated by fromuinput
devices - x11/controllermap – for generating SDL-compatible controller button maps
The ‘How’
Step 1: Enabling Bluetooth
Unless you have a laptop or something else with a builtin Bluetooth chip, you will likely need a USB adapter.I used the USB-BT500 from ASUS (apparently with a Realtek chip inside), which works OOTB despite FreeBSD's nominally shaky support for Bluetooth 4.0/5.0. Their USB-BT400 model apparently also works well, as do adapters with Broadcom chips. No sponsorship/promotion here. YMMV.
The adapter was detected at boot and created a
ubt
device:
Code:
# dmesg | grep ubt
ubt0 on uhub1
ubt0: <Realtek ASUS USB-BT500, class 224/1, rev 1.10/2.00, addr 3> on usbus0
Code:
# kldstat | awk '/ng/ {print $5}'
ng_ubt.ko
ng_hci.ko
ng_bluetooth.ko
ng_l2cap.ko
ng_btsocket.ko
ng_socket.ko
bluetooth
service.
Code:
service bluetooth start ubt0
devd
(cf. /etc/devd/bluetooth.conf) in my case.Finally, start the hcsecd service. This is needed for manging the device link keys.
Code:
service hcsecd start
Step 2: Pairing the Controller
First, scan for Bluetooth devices:
Code:
hccontrol -n ubt0hci Inquiry
00:11:22:33:aa:bb
is the address of your device and ubt0hci
is the NetGraph HCI node corresponding to your Bluetooth adapter (replace as relevant).
Code:
Inquiry result #0
BD_ADDR: 00:11:22:33:aa:bb
Page Scan Rep. Mode: 0x1
Page Scan Period Mode: 0x2
Page Scan Mode: 00
Class: 00:25:08
Clock offset: 0x1c60
Code:
hccontrol -n ubt0hci Create_Connection 00:11:22:33:aa:bb
Code:
hccontrol -n ubt0hci Write_Authentication_Enable 1
Finally, you need to query and store the HID descriptor in /etc/bluetooth/bthidd.conf (this will become important later):
Code:
bthidcontrol -a 00:11:22:33:aa:bb query >> /etc/bluetooth/bthidd.conf
Code:
device {
bdaddr 00:11:22:33:aa:bb;
name "Wireless Controller";
vendor_id 0x054c;
product_id 0x09cc;
version 0x0100;
control_psm 0x11;
interrupt_psm 0x13;
reconnect_initiate true;
battery_power true;
normally_connectable false;
hid_descriptor {
0x05 0x01 0x09 0x05 0xa1 0x01 0x85 0x01
0x09 0x30 0x09 0x31 0x09 0x33 0x09 0x34
0x15 0x00 0x26 0xff 0x00 0x75 0x08 0x95
0x04 0x81 0x02 0x09 0x39 0x15 0x00 0x25
0x07 0x35 0x00 0x46 0x3b 0x01 0x65 0x14
0x75 0x04 0x95 0x01 0x81 0x42 0x65 0x00
0x45 0x00 0x05 0x09 0x19 0x01 0x29 0x0e
0x15 0x00 0x25 0x01 0x75 0x01 0x95 0x0e
0x81 0x02 0x06 0x00 0xff 0x09 0x20 0x75
0x06 0x95 0x01 0x15 0x00 0x25 0x3f 0x81
0x02 0x05 0x01 0x09 0x32 0x09 0x35 0x15
0x00 0x26 0xff 0x00 0x75 0x08 0x95 0x02
0x81 0x02 0xc0
};
}
This is the point where normally we'd start the
bthidd
service, as per most instructions about Bluetooth mice/keyboards, but it will need to be patched in order to get the controller to work.Step 3: Patching bthidd(8)
This is where things get a bit trickier.As established above, even though the DualShock 4 paired successfully, input was not being registered after starting bthidd(8).
At this point, it was tempting to give up – were it not for the tantalizing fact that, after starting bthidd(8), the output of hcidump(1) showed reports whose values, moreover, changed predictably with button presses or stick movements, e.g.:
Code:
# hcidump -x
HCIDump - HCI packet analyzer ver 1.5
device: any snap_len: 65535 filter: 0xffffffffffffffff
> ACL data: handle 0x0002 flags 0x02 dlen 15
L2CAP(d): cid 0x43 len 11 [psm 0]
A1 01 80 80 7A 81 08 00 00 00 00
> ACL data: handle 0x0002 flags 0x02 dlen 15
L2CAP(d): cid 0x43 len 11 [psm 0]
A1 01 00 8B 7B 81 08 00 00 00 00
An investigation of the bthidd(8) source code revealed that the daemon is only written to pick up input from devices classified as mice and keyboards. The device class is determined based on the HID device descriptor from the HID map saved in /etc/bluetooth/bthidd.conf.
As this is a user-space tool, it is relatively easy to patch and test in situ as you don't even need to recompile your kernel, etc. Moreover, wulf's work on uinput/evdev makes it relatively trivial to set up new types of input devices.
A patch is provided as an attachment (with a
*.txt
extension – why doesn't XenForo allow uploading *.diff
or *.patch
files?!). Download it and cd
to the directory where it is saved, and run the following commands. (Make sure bthidd
is not running.)
Code:
cp -rv /usr/src/usr.sbin/bluetooth/bthidd $PWD
cd bthidd/
# change file name as relevant
patch < ../bthidd.patch.txt
make
mv -v /usr/sbin/bthidd /usr/sbin/bthidd.old
cp -v bthidd /usr/sbin/bthidd
Now that this is done, we can finally start
bthidd
in one of two ways (functionally equivalent):
Code:
/usr/sbin/bthidd -u -c /etc/bluetooth/bthidd.conf
Code:
service bthidd start
Code:
# cat /var/run/devd.pipe
!system=DEVFS subsystem=CDEV type=CREATE cdev=input/event9
Code:
add path 'input/*' mode 0660 group operator
Code:
# evtest /dev/input/event9
Input driver version is 1.0.1
Input device ID: bus 0x5 vendor 0x54c product 0x9cc version 0x100
Input device name: "Wireless Controller, bdaddr 00:11:22:33:aa:bb"
Supported events:
Event type 1 (EV_KEY)
Event code 304 (BTN_SOUTH)
Event code 305 (BTN_EAST)
Event code 307 (BTN_NORTH)
Event code 308 (BTN_WEST)
Event code 309 (BTN_Z)
Event code 310 (BTN_TL)
Event code 311 (BTN_TR)
Event code 312 (BTN_TL2)
Event code 313 (BTN_TR2)
Event code 314 (BTN_SELECT)
Event code 315 (BTN_START)
Event code 316 (BTN_MODE)
Event code 317 (BTN_THUMBL)
Event code 318 (BTN_THUMBR)
Event type 3 (EV_ABS)
Event code 0 (ABS_X)
Value 124
Min 0
Max 255
Flat 15
Event code 1 (ABS_Y)
Value 128
Min 0
Max 255
Flat 15
Event code 2 (ABS_Z)
Value 0
Min 0
Max 255
Event code 3 (ABS_RX)
Value 122
Min 0
Max 255
Flat 15
Event code 4 (ABS_RY)
Value 129
Min 0
Max 255
Flat 15
Event code 5 (ABS_RZ)
Value 0
Min 0
Max 255
Event code 16 (ABS_HAT0X)
Value 0
Min -1
Max 1
Event code 17 (ABS_HAT0Y)
Value 0
Min -1
Max 1
Properties:
Property type 1 (INPUT_PROP_DIRECT)
Testing ... (interrupt to exit)
Event: time 1739922581.396298, type 3 (EV_ABS), code 3 (ABS_RX), value 123
Event: time 1739922581.396298, -------------- SYN_REPORT ------------
Event: time 1739922581.396300, type 3 (EV_ABS), code 4 (ABS_RY), value 128
Event: time 1739922581.396300, -------------- SYN_REPORT ------------
Event: time 1739922581.397844, type 3 (EV_ABS), code 3 (ABS_RX), value 122
Event: time 1739922581.397844, -------------- SYN_REPORT ------------
Step 4: Automating the Process
I created a small script that automates the pairing and connection events, assuming a patched bthidd(8) from Step 3. It also automatically handles the steps of disconnecting an already connected device.Save the contents of the code block (click 'Spoiler' to open below), e.g. as setup_connection.sh, and execute it as root while passing
BT_ADDR
as an environment variable, e.g. env BD_ADDR='00:11:22:33:aa:bb' sh setup_connection.sh
.
Code:
#!/bin/sh
if [ "$(id -u)" -ne 0 ]
then
printf '%s\n' 'Error: Please run this script as root.'
exit 1
fi
# this may not be necessary
hccontrol -n ubt0hci Write_Authentication_Enable 1
# either pass BD_ADDR as environent variable or set it here
#BD_ADDR='00:11:22:33:aa:bb'
if [ -z "$BD_ADDR" ]
then
printf '%s\n' 'Error: Please specify a BD_ADDR.'
exit 1
fi
CONN=$(hccontrol -n ubt0hci Read_Connection_List \
| grep $BD_ADDR | awk '{print $2}')
if [ ! -z "$CONN" ]
then
hccontrol -n ubt0hci Disconnect $CONN 1>/dev/null
fi
false
while [ $? -ne 0 ]
do hccontrol -n ubt0hci Inquiry \
&& hccontrol -n ubt0hci Create_Connection $BD_ADDR
done
bthidcontrol -a $BD_ADDR Forget
pgrep -q bthidd && pkill bthidd
/usr/sbin/bthidd -u -c /etc/bluetooth/bthidd.conf
Step 5: Using the Device With Applications
Note that SDL relies on a gamecontrollerdb.txt (alternatively: game_controller_db.txt, or similar), generally supplied with the resource files of each respective application, to determine the button mappings. A specific entry for the controller in question is required.The entries can be generated with x11/controllermap and added to the file. The following ought to work for the DualShock 4 (again, replace the value for
bdaddr
with your controller's address):
Code:
# FreeBSD Bluetooth
050000004c050000cc09000000010000,Wireless Controller bdaddr 00:11:22:33:aa:bb,platform:FreeBSD,crc:f21b,a:b0,b:b1,x:b3,y:b2,back:b9,guide:b11,start:b10,leftstick:b12,rightstick:b13,leftshoulder:b5,rightshoulder:b6,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,misc1:b4,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,
Results and Desirables
With the patch and the connection process documented above, I am able to establish a stable connection to the DualShock 4. The controller is picked up by all SDL applications I've tested; there is no noticeable latency in processing the input events; and the connection is maintained as long asbthidd
is running, terminating as soon as the process is killed.For now, this fix is specific to the DualShock 4. The patch is designed to be flexible/extensible, but it's definitely not production-grade. However, if the DualSense or other controllers are equally well-behaved, it should in theory carry over easily to them. I could try to add support for the DualSense to the patch if someone would provide an accurate HID report descriptor for it. In general, I'd be happy to help anyone in the community who feels sufficiently confident about the above steps, to get a Bluetooth controller working on FreeBSD.
Of course, there are other features (LEDs, gyro, rumble, touchpad) that fall outside of the scope of this post. Since haptic feedback and setting the LEDs would require sending events TO the peripheral, implementing these functions could be less trivial. They may have to wait for a fuller integration of the Bluetooth stack with
iichid
and the new HID stack built up around it. This is not out of the question, but I am not aware of any definite timeline for it.Attachments
Last edited: