Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH BlueZ v9 12/15] unit: Add integration tests
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

Add integration tests that will start btvirt if no Bluetooth adapters
are available, start bluetoothd if not already started, and run some
of our tools in conditions where they would have caused problems.

If a Bluetooth adapter is plugged in and bluetoothd started, the tests
will use those to run the tests against, making it easy to run tests on
a developer's machine.

If there are no available Bluetooth adapters and bluetoothd isn't
running, the tests will need to run as root, as is common on CI setups.
---
 meson.build                      |  11 ++
 unit/integration-test-skipped.sh |   3 +
 unit/integration-test.py         | 271 +++++++++++++++++++++++++++++++
 unit/meson.build                 |  30 ++++
 unit/output_checker.py           | 214 ++++++++++++++++++++++++
 unit/unittest_inspector.py       |  46 ++++++
 6 files changed, 575 insertions(+)
 create mode 100755 unit/integration-test-skipped.sh
 create mode 100755 unit/integration-test.py
 create mode 100644 unit/output_checker.py
 create mode 100755 unit/unittest_inspector.py

diff --git a/meson.build b/meson.build
index e034cdf80fb9..91042e437438 100644
--- a/meson.build
+++ b/meson.build
@@ -258,6 +258,14 @@ configure_file(
   configuration: config_h
 )
 
+python = import('python')
+python3 = python.find_installation('python3')
+has_pythonmodules = false
+script = 'import dbusmock\nimport pexpect'
+if run_command(python3, '-c', script, check: false).returncode() == 0
+  has_pythonmodules = true
+endif
+
 subdir('lib')
 if not get_option('tools').disabled() or get_option('client').enabled() or get_option('obex').enabled() or not get_option('tests').disabled() or not get_option('daemon').disabled()
   subdir('gdbus')
@@ -306,3 +314,6 @@ endif
 if get_option('mesh').enabled()
   meson.add_install_script(sh, '-c', 'install -dm700 ${DESTDIR}/' + meshstoragedir)
 endif
+
+summary({'Integration tests': has_pythonmodules},
+  section: 'General')
diff --git a/unit/integration-test-skipped.sh b/unit/integration-test-skipped.sh
new file mode 100755
index 000000000000..ac1449442020
--- /dev/null
+++ b/unit/integration-test-skipped.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exit 77
diff --git a/unit/integration-test.py b/unit/integration-test.py
new file mode 100755
index 000000000000..7d5f8d51ea17
--- /dev/null
+++ b/unit/integration-test.py
@@ -0,0 +1,271 @@
+#!/usr/bin/python3
+
+# Copyright: (C) 2025 Bastien Nocera <hadess@hadess.net>
+# Copyright: (C) 2023 cagney, for the AsciiDecoder
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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.
+
+import os
+import sys
+import subprocess
+import unittest
+import time
+import re
+from output_checker import OutputChecker
+
+try:
+    import gi
+    from gi.repository import GLib
+except ImportError as e:
+    sys.stderr.write('Skipping tests, PyGobject not available for Python 3, or missing GI typelibs: %s\n' % str(e))
+    sys.exit(77)
+
+builddir = os.getenv('top_builddir', '.')
+
+try:
+    import dbusmock
+    import dbus
+except ImportError:
+    sys.stderr.write('Skipping tests, python-dbusmock not available (https://pypi.python.org/pypi/python-dbusmock).\n')
+    sys.exit(77)
+
+try:
+    import pexpect
+except ImportError:
+    sys.stderr.write('Skipping tests, pexpect not available (https://pypi.org/project/pexpect/).\n')
+    sys.exit(77)
+
+def count_adapters():
+    try:
+        for entry in os.walk('/sys/class/bluetooth'):
+            return len(entry[1])
+        return 0
+    except OSError:
+        return 0
+
+def is_root():
+    return os.geteuid() == 0
+
+# https://stackoverflow.com/a/76543345
+# CC-BY-SA 4.0 relicensed under GPLv2 or later
+# see https://web.archive.org/web/20200805075926/http://ascii-table.com/ansi-escape-sequences.php
+class AsciiDecoder(object):
+    def __init__(self):
+        self.buf = b''
+    def encode(self, b, final=False):
+        return b
+    def decode(self, b, final=False):
+        # escape sequences can be split so
+        # work on lines
+        self.buf = self.buf + b
+        i = self.buf.find(b'\n')
+        if i >= 0:
+            c = self.buf[0:i+1]
+            self.buf = self.buf[i+1:]
+            d = re.sub(rb'\x1b\[[0-9;=?]*[HfABCDsuJKmhlr]', b'*', c)
+            e = re.sub(rb'\x1b', b'<ESC>', d)
+            if e != e:
+                print(">", e, "<")
+            return e
+        return b''
+
+class Tests(dbusmock.DBusTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        cls.dbus_con = cls.get_dbus(True)
+
+    @classmethod
+    def tearDownClass(cls):
+        super().tearDownClass()
+
+    def setUp(self):
+        super().setUp()
+        self.daemon_log = OutputChecker()
+
+    def stop_virt(self):
+        if self.virt:
+            try:
+                self.virt.terminate()
+            except OSError:
+                pass
+            self.assertEqual(self.virt.wait(timeout=3000), 0)
+
+        self.virt = None
+
+    def start_virt(self):
+        if not is_root():
+            self.skipTest('btvirt cannot start as we are not root')
+
+        num_adapters = count_adapters()
+
+        self.virt = subprocess.Popen(
+            [ self.btvirt_path(), '-l1' ],
+        )
+        self.addCleanup(self.stop_virt)
+
+        self.assert_eventually(lambda: count_adapters() > num_adapters)
+
+    def start_virt_if_needed(self):
+        if count_adapters() == 0:
+            self.start_virt()
+
+    def stop_daemon(self):
+        if self.daemon:
+            try:
+                self.daemon.terminate()
+            except OSError:
+                pass
+            self.assertEqual(self.daemon.wait(timeout=3000), 0)
+
+        self.daemon = None
+
+    def daemon_check(self):
+        if not self.dbus_con.name_has_owner('org.bluez'):
+            return False
+
+        try:
+            p = dbus.Interface(self.dbus_con.get_object('org.bluez', '/'),
+                               dbus_interface=dbus.INTROSPECTABLE_IFACE)
+            p.Introspect()
+            return True
+        except dbus.exceptions.DBusException as e:
+            last_exc = e
+            if '.UnknownInterface' in str(e):
+                return False
+
+    def start_daemon(self):
+        if not is_root():
+            self.skipTest('bluetoothd cannot start as we are not root')
+
+        self.daemon = subprocess.Popen(
+            [ self.bluetoothd_path(), '--nodetach', '-f', self.bluetooth_config_path() ],
+            stdout=subprocess.DEVNULL, stderr=self.daemon_log.fd
+        )
+        self.daemon_log.writer_attached()
+        self.addCleanup(self.stop_daemon)
+
+        self.assert_eventually(lambda: self.daemon_check())
+
+    def start_daemon_if_needed(self):
+        # Only start daemon if one is not already available
+        if not self.daemon_check():
+            self.start_daemon()
+
+    def binary_path(self, binary):
+        builddir = os.getenv('top_builddir', '.')
+        return os.path.join(builddir, binary)
+
+    def bluetoothctl_path(self):
+        return self.binary_path('client/bluetoothctl')
+
+    def btmgmt_path(self):
+        return self.binary_path('tools/btmgmt')
+
+    def btvirt_path(self):
+        return self.binary_path('emulator/btvirt')
+
+    def bluetoothd_path(self):
+        return self.binary_path('src/bluetoothd')
+
+    def bluetooth_config_path(self):
+        srcdir = os.getenv('top_srcdir', '.')
+        return os.path.join(srcdir, 'src/main.conf')
+
+    def assert_eventually(self, condition, message=None, timeout=5000, keep_checking=0):
+        """Assert that condition function eventually returns True.
+
+        Timeout is in milliseconds, defaulting to 5000 (5 seconds). message is
+        printed on failure.
+        """
+        if not keep_checking:
+            if condition():
+                return
+
+        done = False
+
+        def on_timeout_reached():
+            nonlocal done
+            done = True
+
+        source = GLib.timeout_add(timeout, on_timeout_reached)
+        while not done:
+            if condition():
+                GLib.source_remove(source)
+                if keep_checking > 0:
+                    self.assert_condition_persists(
+                        condition, message, timeout=keep_checking
+                    )
+                return
+            GLib.MainContext.default().iteration(False)
+
+        self.fail(message() if message else f"timed out waiting for {condition}")
+
+    def test_bluetoothctl_no_output(self):
+        self.start_virt_if_needed()
+        self.start_daemon_if_needed()
+
+        num_adapters = count_adapters()
+
+        cmd = subprocess.run([ self.bluetoothctl_path(), 'list' ], check=False, capture_output=True)
+        output = cmd.stdout.decode('UTF-8')
+        self.assertEqual(len(output.strip().split('\n')), num_adapters)
+        self.assertRegex(output, '.*\\[default\\]')
+
+    def test_btmgmt_stdin_hang(self):
+        self.start_virt_if_needed()
+
+        num_adapters = count_adapters()
+
+        cmd = subprocess.run([ self.btmgmt_path(), 'info' ], stdin=subprocess.DEVNULL, check=False, capture_output=True)
+        output = cmd.stdout.decode('UTF-8')
+        self.assertRegex(output, '.*Index list with ' + str(num_adapters) + ' item')
+
+    def test_btmgmt_help_hang(self):
+        devnull = open(os.devnull, 'w')
+        cmd_log = OutputChecker(out=devnull)
+        cmd = subprocess.Popen([ self.btmgmt_path(), '--help' ], stdout=cmd_log.fd)
+        cmd_log.writer_attached()
+
+        # Check that btmgmt --help doesn't hang
+        self.assertEqual(cmd.wait(timeout=1), 0)
+        devnull.close()
+
+        # Check that it contains the help output
+        cmd_log.check_line('Send HCI Command and wait for Event', timeout=1)
+
+    def test_bluetoothd_failed_to_set_default_config(self):
+        if self.daemon_check():
+            # We need to start bluetoothd ourselves, so skip the test
+            self.skipTest('Test requires that we start bluetoothd ourselves')
+
+        self.start_virt_if_needed()
+        self.start_daemon()
+
+        self.daemon_log.check_line('Battery Provider Manager created', timeout=1)
+        self.daemon_log.check_no_line('Failed to set default system config for',
+                                      failmsg='Error output when using stock configuration')
+
+    def test_bluetoothctl_completion_crash(self):
+        child = pexpect.spawn(self.bluetoothctl_path())
+        child._decoder = AsciiDecoder()
+        child.expect(r'\[bluetoothctl\]> ')
+        child.sendline(' \t')
+        child.expect(r'\[bluetoothctl\]> ')
+
+        time.sleep(0.5)
+
+        self.assertTrue(child.isalive())
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/unit/meson.build b/unit/meson.build
index 92e1c6f66caa..1fea15766897 100644
--- a/unit/meson.build
+++ b/unit/meson.build
@@ -162,3 +162,33 @@ add_test_setup(
   # default timeout in meson is 30s
   timeout_multiplier: 4,
 )
+
+if not has_pythonmodules
+  test('integration-test-skipped',
+    find_program('integration-test-skipped.sh'),
+  )
+else
+  integration_test = find_program('integration-test.py')
+
+  envs = environment()
+  envs.set ('top_builddir', meson.global_build_root())
+  envs.set ('top_srcdir', meson.global_source_root())
+
+  test_deps = []
+
+  python3 = find_program('python3')
+  unittest_inspector = find_program('unittest_inspector.py')
+  r = run_command(unittest_inspector, files('integration-test.py'), check: true)
+  unit_tests = r.stdout().strip().split('\n')
+
+  foreach ut: unit_tests
+    ut_args = files('integration-test.py')
+    ut_args += ut
+    test(ut,
+      python3,
+      args: ut_args,
+      env: envs,
+      depends: test_deps
+    )
+  endforeach
+endif
diff --git a/unit/output_checker.py b/unit/output_checker.py
new file mode 100644
index 000000000000..037e248b9311
--- /dev/null
+++ b/unit/output_checker.py
@@ -0,0 +1,214 @@
+#! /usr/bin/env python3
+# Copyright © 2020, RedHat Inc.
+#
+# This program 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.
+#
+# This program 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+# Authors:
+#       Benjamin Berg <bberg@redhat.com>
+
+import os
+import sys
+import fcntl
+import io
+import re
+import time
+import threading
+import select
+import errno
+
+
+class OutputChecker(object):
+    def __init__(self, out=sys.stdout):
+        self._output = out
+        self._pipe_fd_r, self._pipe_fd_w = os.pipe()
+        self._partial_buf = b""
+        self._lines_sem = threading.Semaphore()
+        self._lines = []
+        self._reader_io = io.StringIO()
+
+        # Just to be sure, shouldn't be a problem even if we didn't set it
+        fcntl.fcntl(
+            self._pipe_fd_r,
+            fcntl.F_SETFL,
+            fcntl.fcntl(self._pipe_fd_r, fcntl.F_GETFL) | os.O_CLOEXEC | os.O_NONBLOCK,
+        )
+        fcntl.fcntl(
+            self._pipe_fd_w,
+            fcntl.F_SETFL,
+            fcntl.fcntl(self._pipe_fd_w, fcntl.F_GETFL) | os.O_CLOEXEC,
+        )
+
+        # Start copier thread
+        self._thread = threading.Thread(target=self._copy, daemon=True)
+        self._thread.start()
+
+    def _copy(self):
+        p = select.poll()
+        p.register(self._pipe_fd_r)
+        while True:
+            try:
+                # Be lazy and wake up occasionally in case _pipe_fd_r became invalid
+                # The reason to do this is because os.read() will *not* return if the
+                # FD is forcefully closed.
+                p.poll(0.1)
+
+                r = os.read(self._pipe_fd_r, 1024)
+                if not r:
+                    os.close(self._pipe_fd_r)
+                    self._pipe_fd_r = -1
+                    self._lines_sem.release()
+                    return
+            except OSError as e:
+                if e.errno == errno.EWOULDBLOCK:
+                    continue
+
+                # We get a bad file descriptor error when the outside closes the FD
+                if self._pipe_fd_r >= 0:
+                    os.close(self._pipe_fd_r)
+                self._pipe_fd_r = -1
+                self._lines_sem.release()
+                return
+
+            l = r.split(b"\n")
+            l[0] = self._partial_buf + l[0]
+            self._lines.extend(l[:-1])
+            self._partial_buf = l[-1]
+
+            self._lines_sem.release()
+
+            os.write(self._output.fileno(), r)
+
+    def check_line_re(self, needle_re, timeout=0, failmsg=None):
+        deadline = time.time() + timeout
+
+        if isinstance(needle_re, str):
+            needle_re = needle_re.encode("ascii")
+
+        r = re.compile(needle_re)
+        ret = []
+
+        while True:
+            try:
+                l = self._lines.pop(0)
+            except IndexError:
+                # EOF, throw error
+                if self._pipe_fd_r == -1:
+                    if failmsg:
+                        raise AssertionError(
+                            "No further messages: " % failmsg
+                        ) from None
+                    else:
+                        raise AssertionError(
+                            "No client waiting for needle %s" % (str(needle_re))
+                        ) from None
+
+                # Check if should wake up
+                if not self._lines_sem.acquire(timeout=deadline - time.time()):
+                    if failmsg:
+                        raise AssertionError(failmsg) from None
+                    else:
+                        raise AssertionError(
+                            "Timed out waiting for needle %s (timeout: %0.2f)"
+                            % (str(needle_re), timeout)
+                        ) from None
+                continue
+
+            ret.append(l)
+            if r.search(l):
+                return ret
+
+    def check_line(self, needle, timeout=0, failmsg=None):
+        if isinstance(needle, str):
+            needle = needle.encode("ascii")
+
+        needle_re = re.escape(needle)
+
+        return self.check_line_re(needle_re, timeout=timeout, failmsg=failmsg)
+
+    def check_no_line_re(self, needle_re, wait=0, failmsg=None):
+        deadline = time.time() + wait
+
+        if isinstance(needle_re, str):
+            needle_re = needle_re.encode("ascii")
+
+        r = re.compile(needle_re)
+        ret = []
+
+        while True:
+            try:
+                l = self._lines.pop(0)
+            except IndexError:
+                # EOF, so everything good
+                if self._pipe_fd_r == -1:
+                    break
+
+                # Check if should wake up
+                if not self._lines_sem.acquire(timeout=deadline - time.time()):
+                    # Timed out, so everything is good
+                    break
+                continue
+
+            ret.append(l)
+            if r.search(l):
+                if failmsg:
+                    raise AssertionError(failmsg)
+                else:
+                    raise AssertionError(
+                        "Found needle %s but shouldn't have been there (timeout: %0.2f)"
+                        % (str(needle_re), wait)
+                    )
+
+        return ret
+
+    def check_no_line(self, needle, wait=0, failmsg=None):
+        if isinstance(needle, str):
+            needle = needle.encode("ascii")
+
+        needle_re = re.escape(needle)
+
+        return self.check_no_line_re(needle_re, wait=wait, failmsg=failmsg)
+
+    def clear(self):
+        ret = self._lines
+        self._lines = []
+        return ret
+
+    def assert_closed(self, timeout=1):
+        self._thread.join(timeout)
+
+        if self._thread.is_alive() != False:
+            raise AssertionError("OutputCheck: Write side has not been closed yet!")
+
+    def force_close(self):
+        fd = self._pipe_fd_r
+        self._pipe_fd_r = -1
+        if fd >= 0:
+            os.close(fd)
+
+        self._thread.join()
+
+    @property
+    def fd(self):
+        return self._pipe_fd_w
+
+    def writer_attached(self):
+        os.close(self._pipe_fd_w)
+        self._pipe_fd_w = -1
+
+    def __del__(self):
+        if self._pipe_fd_r >= 0:
+            os.close(self._pipe_fd_r)
+            self._pipe_fd_r = -1
+        if self._pipe_fd_w >= 0:
+            os.close(self._pipe_fd_w)
+            self._pipe_fd_w = -1
diff --git a/unit/unittest_inspector.py b/unit/unittest_inspector.py
new file mode 100755
index 000000000000..fe8304686da7
--- /dev/null
+++ b/unit/unittest_inspector.py
@@ -0,0 +1,46 @@
+#! /usr/bin/env python3
+# Copyright © 2020, Canonical Ltd
+#
+# This program 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.
+#
+# This program 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+# Authors:
+#       Marco Trevisan <marco.trevisan@canonical.com>
+
+import argparse
+import importlib.util
+import inspect
+import os
+import unittest
+
+def list_tests(module):
+    tests = []
+    for name, obj in inspect.getmembers(module):
+        if inspect.isclass(obj) and issubclass(obj, unittest.TestCase) and not name.startswith('OopTests'):
+            cases = unittest.defaultTestLoader.getTestCaseNames(obj)
+            tests += [ (obj, '{}.{}'.format(name, t)) for t in cases ]
+    return tests
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument('unittest_source', type=argparse.FileType('r'))
+
+    args = parser.parse_args()
+    source_path = args.unittest_source.name
+    spec = importlib.util.spec_from_file_location(
+        os.path.basename(source_path), source_path)
+    module = importlib.util.module_from_spec(spec)
+    spec.loader.exec_module(module)
+
+    for machine, human in list_tests(module):
+        print(human)
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 13/15] doc: Add HTML documentation
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

Based off the XDG desktop portal documentation at:
https://github.com/flatpak/xdg-desktop-portal/commits/main/doc
---
 doc/_static/bluez.css      | 452 +++++++++++++++++++++++++++++++++++++
 doc/_static/bluez.png      | Bin 0 -> 18354 bytes
 doc/_static/bullet.svg     |   1 +
 doc/_static/card.png       | Bin 0 -> 35387 bytes
 doc/_static/figtree.woff2  | Bin 0 -> 14364 bytes
 doc/_static/toc_bullet.svg |  39 ++++
 doc/bluetoothctl-cmd.rst   |  34 +++
 doc/conf.py                |  39 ++++
 doc/copy-subdir.py         |  14 ++
 doc/daemons.rst            |  12 +
 doc/dbus-gatt.rst          |  18 ++
 doc/dbus-obex.rst          |  33 +++
 doc/dbus.rst               |  76 +++++++
 doc/deprecated-tools.rst   |  22 ++
 doc/dev-maint.rst          |  17 ++
 doc/favicon.ico            | Bin 0 -> 29152 bytes
 doc/index.rst              |  47 ++++
 doc/meson.build            | 119 ++++++++++
 doc/protocols.rst          |  22 ++
 doc/qualifications.rst     |  76 +++++++
 doc/tools.rst              |  24 ++
 meson.build                |   6 +-
 meson_options.txt          |   1 +
 23 files changed, 1051 insertions(+), 1 deletion(-)
 create mode 100644 doc/_static/bluez.css
 create mode 100644 doc/_static/bluez.png
 create mode 100644 doc/_static/bullet.svg
 create mode 100644 doc/_static/card.png
 create mode 100644 doc/_static/figtree.woff2
 create mode 100644 doc/_static/toc_bullet.svg
 create mode 100644 doc/bluetoothctl-cmd.rst
 create mode 100644 doc/conf.py
 create mode 100644 doc/copy-subdir.py
 create mode 100644 doc/daemons.rst
 create mode 100644 doc/dbus-gatt.rst
 create mode 100644 doc/dbus-obex.rst
 create mode 100644 doc/dbus.rst
 create mode 100644 doc/deprecated-tools.rst
 create mode 100644 doc/dev-maint.rst
 create mode 100644 doc/favicon.ico
 create mode 100644 doc/index.rst
 create mode 100644 doc/protocols.rst
 create mode 100644 doc/qualifications.rst
 create mode 100644 doc/tools.rst

diff --git a/doc/_static/bluez.css b/doc/_static/bluez.css
new file mode 100644
index 000000000000..e0e281d79cdc
--- /dev/null
+++ b/doc/_static/bluez.css
@@ -0,0 +1,452 @@
+/* XDG CSS theme for Sphinx + Furo
+
+https://www.sphinx-doc.org
+https://pypi.org/project/furo/
+*/
+
+/* FONTS */
+
+@font-face {
+  font-family: "Figtree var";
+  font-weight: 100 900;
+  font-display: swap;
+  font-style: normal;
+  font-named-instance: "Regular";
+  src: url("figtree.woff2") format("woff2");
+}
+
+:root {
+  --rounded-corner: 12px;
+  --blue1: rgb(153,193,241);
+  --blue2: rgb(98,160,234);
+  --blue3: rgb(53,132,228);
+  --blue4: rgb(28,113,216);
+  --blue5: rgb(26,95,180);
+  --green1: rgb(143,240,164);
+  --green2: rgb(87,227,137);
+  --green3: rgb(51,209,122);
+  --green4: rgb(46,194,126);
+  --green5: rgb(38,162,105);
+  --yellow1: rgb(249,240,107);
+  --yellow2: rgb(248,228,92);
+  --yellow3: rgb(246,211,45);
+  --yellow4: rgb(245,194,17);
+  --yellow5: rgb(229,165,10);
+  --orange1: rgb(255,190,111);
+  --orange2: rgb(255,163,72);
+  --orange3: rgb(255,120,0);
+  --orange4: rgb(230,97,0);
+  --orange5: rgb(198,70,0);
+  --red1: rgb(246,97,81);
+  --red2: rgb(237,51,59);
+  --red3: rgb(224,27,36);
+  --red4: rgb(192,28,40);
+  --red5: rgb(165,29,45);
+  --purple1: rgb(220,138,221);
+  --purple2: rgb(192,97,203);
+  --purple3: rgb(145,65,172);
+  --purple4: rgb(129,61,156);
+  --purple5: rgb(97,53,131);
+  --brown1: rgb(205,171,143);
+  --brown2: rgb(181,131,90);
+  --brown3: rgb(152,106,68);
+  --brown4: rgb(134,94,60);
+  --brown5: rgb(99,69,44);
+  --light1: rgb(255,255,255);
+  --light2: rgb(246,245,244);
+  --light3: rgb(222,221,218);
+  --light4: rgb(192,191,188);
+  --light5: rgb(154,153,150);
+  --dark1: rgb(119,118,123);
+  --dark2: rgb(94,92,100);
+  --dark3: rgb(61,56,70);
+  --dark4: rgb(36,31,49);
+  --dark5: rgb(0,0,0);
+  --text: #f6f5f4;
+  --tocbg: var(--light2);
+  --tocfg: var(--dark3);
+  --borders: var(--light3);
+  --link: var(--blue5);
+  --link-underline: var(--blue1);
+}
+
+::selection {
+	background-color:  rgba(153,193,241,0.5);
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --tocbg: var(--dark5);
+    --tocfg: var(--light5);
+    --borders: var(--dark3);
+    --link: var(--blue1);
+    --link-underline: var(--blue5);
+  }
+	::selection {
+		background-color:  rgba(26,95,180,0.4);
+	}
+}
+
+* {
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+}
+
+html, body {
+  margin: 0;
+  padding: 0;
+  font-size: 16px;
+  font-family: "Figtree var", sans-serif;
+  font-weight: 400;
+  line-height: 1.6;
+}
+  @media (min-width: 700px) {
+    html, body {
+      font-size: 18px;
+    }
+  }
+
+a {
+  color: var(--link);
+  text-decoration-color: var(--link-underline);
+}
+
+p strong {
+  font-weight: 600;
+  opacity: 0.9;
+}
+
+h1 {
+   font-weight: 500;
+   font-size: 2rem;
+   margin-top: '0';
+   margin-bottom: 1.5rem;
+   font-family: "Figtree var", sans-serif;
+}
+h2 {
+   font-weight: 800;
+   font-size: 1.7rem;
+   margin-top: 2.5rem;
+   margin-bottom: 1.2rem;
+   font-family: "Figtree var", sans-serif;
+}
+h3 {
+   font-weight: 800;
+   font-size: 1.3rem;
+   margin-top: 2.2rem;
+   margin-bottom: 1rem;
+   font-family: "Figtree var", sans-serif;
+}
+h4 {
+   font-weight: 700;
+   font-size: 1.1rem;
+   margin-top: 1rem;
+   margin-bottom: 1rem;
+   font-family: "Figtree var", sans-serif;
+}
+h5 {
+   font-weight: 700;
+   font-size: 1rem;
+   margin-top: 1rem;
+   margin-bottom: 1rem;
+   font-family: "Figtree var", sans-serif;
+}
+  h2,h3,h4,h5 { opacity: 0.7; }
+
+ul.simple {
+  margin: 1rem 1rem 3rem;
+}
+
+  ul.simple li {
+    list-style-image: url(bullet.svg);
+    margin-inline-end: 1ch;
+    margin-bottom: 1rem;
+  }
+	ul.simple li:hover::marker {	}
+
+.related-pages {
+  font-size: 10pt;
+  font-weight: 800;
+  display: flex;
+  flex-direction: row-reverse;
+  align-items: stretch;
+  justify-content: space-between;
+}
+
+  .related-pages a .title {
+    color: var(--dark1);
+  }
+  .related-pages a svg {
+    width: 32px; height: 32px;
+  }
+
+  .prev-page {
+    border-radius: 8px;
+    padding: 8px 24px 8px 8px;
+
+  }
+  .next-page {
+    border-radius: 8px;
+    padding: 8px 8px 8px 24px;
+  }
+  .related-pages a.prev-page,
+  .related-pages a.next-page {
+    float: none;
+    max-width: inherit; /* cancel furo's 50% */
+  }
+  .related-pages a.prev-page:hover, .related-pages a.next-page:hover {
+    background-color: var(--tocbg);
+  }
+
+  @media (min-width: 700px) {
+    .related-pages {
+      font-size: 14pt;
+    }
+    .prev-page {
+      padding: 16px 48px 16px 16px;
+      border-radius: 24px;
+
+    }
+    .next-page {
+      padding: 16px 16px 16px 48px;
+      border-radius: 24px;
+    }
+  }
+  .related-pages a.prev-page:only-child,
+  .related-pages a.next-page:only-child,
+  .page-info {
+      /* align-self: flex-end; i wish */
+      width: 100%; /* big buttons instead */
+  }
+
+.toctree-wrapper ul {
+	margin: 0; padding: 0;
+}
+
+  .toctree-wrapper li {
+		list-style: none;
+    margin: 0 0 1rem 0;
+  }
+  .toctree-wrapper li.toctree-l1 {
+    font-size: 14pt;
+    font-weight: 600;
+
+  }
+    .toctree-wrapper li.toctree-l1 > a { color: var(--tocfg); }
+
+  .toctree-wrapper li.toctree-l2 {
+		list-style-image: url(toc_bullet.svg);
+		margin: 0;
+    font-size: 12pt;
+    font-weight: 400;
+    padding: 0.2rem;
+  }
+    .toctree-wrapper > ul ul {
+      margin-top: 1rem;
+      display: grid;
+      grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
+      gap: .2rem;
+    }
+    .toctree-wrapper > ul ul a {
+      background-color: transparent;
+    }
+/* tiled TOC with images */
+
+ul.tiled-toc {
+  display: grid;
+  gap: 1rem;
+  grid-auto-flow: row;
+  margin: 1rem 0; padding: 0;
+  text-align: center;
+  margin-top: 2rem;
+  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+}
+  ul.tiled-toc li {
+    display: block;
+    list-style: none;
+  }
+  ul.tiled-toc li img {
+    display: block;
+    width: 100%;
+    image-rendering: crisp-edges;
+    image-rendering: pixelated;
+  }
+  ul.tiled-toc li img::after {
+    content: "";
+  }
+
+  ul.tiled-toc a {
+    color: black;
+    text-decoration: none;
+    font-weight: 600;
+  }
+
+  @media (prefers-color-scheme: dark) {
+    ul.tiled-toc a { color: white; }
+  }
+
+
+table.docutils {
+  font-size: 90%;
+  box-shadow: none;
+  border-collapse: collapse;
+  border-spacing: 0;
+  border-radius: 0;
+  text-align: left;
+  border-width: 0;
+  box-sizing: border-box;
+}
+  /* reset crazy */
+  table.docutils th, table.docutils tr, table.docutils td {
+    background-color: transparent;
+    text-align: left;
+  }
+
+  table.docutils td, table.docutils th {
+    border-width: 0;
+    padding: .25rem;
+  }
+  table.docutils tr {
+    border-width: 0;
+    border-bottom: 1px solid var(--borders);
+  }
+
+  table.docutils thead tr {
+    font-weight: 800;
+    border-width: 0;
+    box-sizing: inherit;
+    border-bottom: 2px solid var(--borders);
+  }
+
+  .hig-palette-table.align-default {
+    display: table;
+    width: 100%;
+  }
+
+  img.hig-palette-swatch {
+    display: block;
+    width: 42px;
+    aspect-ratio: 1 / 1;
+    border-radius: 50%;
+  }
+
+  .hig-palette-table colgroup { display: none; }
+  .hig-palette-table .stub,
+  .hig-palette-table .head.stub {
+    width: 50px;
+  }
+
+video {
+  width: 100%;
+  height: auto;
+}
+
+footer {
+  margin-bottom: 3rem;
+}
+
+.sidebar-drawer {
+
+}
+
+.sidebar-brand-text {
+  font-family: Cantarell, -apple-system, BlinkMacSystemFont, "Helvetica", sans-serif;
+  font-size: 14pt;
+  font-weight: 800;
+}
+
+.sidebar-search-container,
+.sidebar-search {
+	border-radius: 99999px;
+	border: none;
+}
+.sidebar-search-container {
+	margin: 0 var(--sidebar-item-spacing-horizontal);
+	transition: background-color 0.2s linear;
+	background-color: rgba(0,0,0,0.06);
+}
+.sidebar-search-container:hover {
+  background-color: rgba(0,0,0,0.09);
+}
+.sidebar-search-container:focus-within {
+  background-color: rgba(0,0,0,0.12);
+}
+
+@media (prefers-color-scheme: dark) {
+  .sidebar-search-container {
+    background-color: rgba(255,255,255,0.06);
+  }
+  .sidebar-search-container:hover {
+    background-color: rgba(255,255,255,0.09);
+  }
+  .sidebar-search-container:focus-within {
+    background-color: rgba(255,255,255,0.12);
+  }
+}
+
+.sidebar-tree .toctree-l1,
+.sidebar-tree .toctree-l2 {
+	margin: 1px 12px;
+	border-radius: 6px;
+  color: var(--link);
+}
+
+.sidebar-tree .toctree-l1 > .reference {
+  color: var(--link);
+}
+
+.sidebar-tree .toctree-l2 {
+	margin: 1px 0;
+	font-size: 90%;
+	text-wrap: balance;
+	overflow-wrap: break-word;
+	hyphens: manual;
+}
+
+.sidebar-tree li > ul {
+	margin-left: 0;
+}
+.sidebar-tree li > ul .reference {
+	padding-left: calc(1.5*var(--sidebar-item-spacing-horizontal));
+}
+
+.sidebar-tree label[for^="toctree-checkbox"] {
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+.sidebar-tree li.toctree-l3 a {
+  margin: 1px 0;
+  padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)
+           var(--sidebar-item-spacing-vertical) calc(2*var(--sidebar-item-spacing-horizontal)) ; /* extra indentation */
+}
+
+.sidebar-tree .reference {
+	background: rgba(0,0,0,0);
+	transition: background 0.2s linear;
+	border-radius: 6px;
+	padding: var(--sidebar-item-spacing-vertical) calc(0.75*var(--sidebar-item-spacing-horizontal));
+}
+.sidebar-tree .reference:hover,
+.sidebar-tree .current > .reference:hover {
+	/* background: var(--color-background-hover); */
+	background: rgba(0,0,0,0.03);
+}
+
+.sidebar-tree .reference.current {
+	background: rgba(0,0,0,0.06);
+}
+.sidebar-tree .reference.current:hover {
+	background: rgba(0,0,0,0.09);
+}
+
+.sidebar-tree .current-page > .reference {
+	font-weight: 600;
+}
+
+.theme-toggle {
+  display: none; /* just go with auto */
+}
diff --git a/doc/_static/bluez.png b/doc/_static/bluez.png
new file mode 100644
index 0000000000000000000000000000000000000000..105aac0e1d857e0ced2b931a54153ff1eb8a285e
GIT binary patch
literal 18354
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
QL_|a!?Om@jpaH4>2jhNBdjJ3c

literal 0
HcmV?d00001

diff --git a/doc/_static/bullet.svg b/doc/_static/bullet.svg
new file mode 100644
index 000000000000..e75341131991
--- /dev/null
+++ b/doc/_static/bullet.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M6.662 4.586a1.143 1.143 0 00-.838.334l-.531.531c-.458.458-.442 1.21.037 1.69l2.873 2.87-2.873 2.874c-.479.479-.495 1.231-.037 1.69l.531.53c.458.458 1.213.442 1.692-.037l4.068-4.07a1.22 1.22 0 00.348-.986 1.22 1.22 0 00-.348-.985l-4.068-4.07a1.249 1.249 0 00-.854-.371z" fill="#9a9996"/></svg>
\ No newline at end of file
diff --git a/doc/_static/card.png b/doc/_static/card.png
new file mode 100644
index 0000000000000000000000000000000000000000..60383d7e792ae320f226fc5540dd10a232e92243
GIT binary patch
literal 35387
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

literal 0
HcmV?d00001

diff --git a/doc/_static/figtree.woff2 b/doc/_static/figtree.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..857341f7c678b1c5d85729ff9ea8d486fe2b7a04
GIT binary patch
literal 14364
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
XnJx~kuFKepMr;1_w*ej>008_K_<*wS

literal 0
HcmV?d00001

diff --git a/doc/_static/toc_bullet.svg b/doc/_static/toc_bullet.svg
new file mode 100644
index 000000000000..d4a77331c98f
--- /dev/null
+++ b/doc/_static/toc_bullet.svg
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   height="16"
+   width="16"
+   version="1.1"
+   id="svg4"
+   sodipodi:docname="toc_bullet.svg"
+   inkscape:version="1.1-beta1 (77e7b44db3, 2021-03-28)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs8" />
+  <sodipodi:namedview
+     id="namedview6"
+     pagecolor="#505050"
+     bordercolor="#ffffff"
+     borderopacity="1"
+     objecttolerance="10.0"
+     gridtolerance="10.0"
+     guidetolerance="10.0"
+     inkscape:pageshadow="0"
+     inkscape:pageopacity="0"
+     inkscape:pagecheckerboard="1"
+     showgrid="true"
+     inkscape:current-layer="svg4">
+    <inkscape:grid
+       type="xygrid"
+       id="grid49" />
+  </sodipodi:namedview>
+  <path
+     id="path2"
+     sodipodi:type="inkscape:offset"
+     inkscape:radius="-0.81220347"
+     inkscape:original="M 6.6621094 4.5859375 A 1.143 1.143 0 0 0 5.8242188 4.9199219 L 5.2929688 5.4511719 C 4.8349687 5.9091719 4.8510781 6.660625 5.3300781 7.140625 L 8.203125 10.011719 L 5.3300781 12.884766 C 4.8510781 13.363766 4.8349688 14.115219 5.2929688 14.574219 L 5.8242188 15.105469 C 6.2822188 15.563469 7.036625 15.547359 7.515625 15.068359 L 11.583984 10.998047 A 1.22 1.22 0 0 0 11.931641 10.011719 A 1.22 1.22 0 0 0 11.583984 9.0273438 L 7.515625 4.9570312 A 1.249 1.249 0 0 0 6.6621094 4.5859375 z "
+     d="m 6.6386719,5.3984375 c -0.088798,-0.00181 -0.1773948,0.032925 -0.2402344,0.095703 l -0.53125,0.53125 C 5.7300188,6.1625593 5.7182537,6.3799747 5.9042969,6.5664062 L 8.7773438,9.4375 a 0.81228469,0.81228469 0 0 1 0,1.148438 L 5.9042969,13.458984 C 5.719186,13.644095 5.7289844,13.861495 5.8671875,14 l 0.53125,0.53125 c 0.135934,0.135934 0.3590378,0.146822 0.5429687,-0.03711 l 4.0664058,-4.06836 0.002,-0.002 c 0.08366,-0.0854 0.125702,-0.209206 0.113281,-0.328125 a 0.81228469,0.81228469 0 0 1 0,-0.1679686 c 0.01222,-0.1187076 -0.0298,-0.2409206 -0.113281,-0.3261719 l -0.002,-0.00195 L 6.9414062,5.53125 A 0.81228469,0.81228469 0 0 1 6.9375,5.5273438 C 6.8598316,5.4486568 6.7511214,5.4020564 6.640625,5.3984375 Z"
+     style="fill:#deddda" />
+</svg>
diff --git a/doc/bluetoothctl-cmd.rst b/doc/bluetoothctl-cmd.rst
new file mode 100644
index 000000000000..3fa9b4c984e7
--- /dev/null
+++ b/doc/bluetoothctl-cmd.rst
@@ -0,0 +1,34 @@
+bluetoothctl
+============
+
+.. toctree::
+   :hidden:
+   :maxdepth: 2
+
+   bluetoothctl
+   bluetoothctl-mgmt
+   bluetoothctl-monitor
+   bluetoothctl-admin
+   bluetoothctl-advertise
+   bluetoothctl-endpoint
+   bluetoothctl-gatt
+   bluetoothctl-player
+   bluetoothctl-scan
+   bluetoothctl-transport
+   bluetoothctl-assistant
+   bluetoothctl-hci
+   bluetoothctl-telephony
+
+* :doc:`bluetoothctl <bluetoothctl>`
+* :doc:`bluetoothctl-mgmt <bluetoothctl-mgmt>`
+* :doc:`bluetoothctl-monitor <bluetoothctl-monitor>`
+* :doc:`bluetoothctl-admin <bluetoothctl-admin>`
+* :doc:`bluetoothctl-advertise <bluetoothctl-advertise>`
+* :doc:`bluetoothctl-endpoint <bluetoothctl-endpoint>`
+* :doc:`bluetoothctl-gatt <bluetoothctl-gatt>`
+* :doc:`bluetoothctl-player <bluetoothctl-player>`
+* :doc:`bluetoothctl-scan <bluetoothctl-scan>`
+* :doc:`bluetoothctl-transport <bluetoothctl-transport>`
+* :doc:`bluetoothctl-assistant <bluetoothctl-assistant>`
+* :doc:`bluetoothctl-hci <bluetoothctl-hci>`
+* :doc:`bluetoothctl-telephony <bluetoothctl-telephony>`
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644
index 000000000000..07f87510be6f
--- /dev/null
+++ b/doc/conf.py
@@ -0,0 +1,39 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = "BlueZ"
+copyright = "2026, BlueZ authors"
+author = "BlueZ authors"
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+html_favicon = "favicon.ico"
+
+extensions = [
+    "sphinxext.opengraph",
+    "sphinx_copybutton",
+]
+
+templates_path = ["_templates"]
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = "furo"
+# add custom files that are stored in _static
+html_css_files = ["bluez.css"]
+html_static_path = ["_static"]
+
+# -- Options for OpenGraph ---------------------------------------------------
+
+ogp_site_url = "https://bluez.github.io/docs/"
+ogp_image = "_static/card.png"
+
+html_permalinks_icon = "🔗"
diff --git a/doc/copy-subdir.py b/doc/copy-subdir.py
new file mode 100644
index 000000000000..8cb64f54bf9b
--- /dev/null
+++ b/doc/copy-subdir.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+import shutil
+
+subdir = os.environ["MESON_SUBDIR"]
+
+input_path = os.path.join(os.environ["MESON_SOURCE_ROOT"], subdir, sys.argv[1])
+output_path = os.path.join(os.environ["MESON_BUILD_ROOT"], subdir, sys.argv[2])
+
+os.makedirs(os.path.dirname(output_path), exist_ok=True)
+
+shutil.copyfile(input_path, output_path)
diff --git a/doc/daemons.rst b/doc/daemons.rst
new file mode 100644
index 000000000000..7c5f61f74c79
--- /dev/null
+++ b/doc/daemons.rst
@@ -0,0 +1,12 @@
+Daemons
+=======
+
+.. toctree::
+   :hidden:
+   :maxdepth: 2
+
+   bluetoothd
+   bluetooth-meshd
+
+* :doc:`bluetoothd <bluetoothd>`
+* :doc:`bluetooth-meshd <bluetooth-meshd>`
diff --git a/doc/dbus-gatt.rst b/doc/dbus-gatt.rst
new file mode 100644
index 000000000000..fb026e9f9ede
--- /dev/null
+++ b/doc/dbus-gatt.rst
@@ -0,0 +1,18 @@
+D-Bus GATT APIs
+===============
+
+.. toctree::
+   :hidden:
+   :maxdepth: 2
+
+   org.bluez.GattManager
+   org.bluez.GattProfile
+   org.bluez.GattService
+   org.bluez.GattCharacteristic
+   org.bluez.GattDescriptor
+
+* :doc:`org.bluez.GattManager <org.bluez.GattManager>`
+* :doc:`org.bluez.GattProfile <org.bluez.GattProfile>`
+* :doc:`org.bluez.GattService <org.bluez.GattService>`
+* :doc:`org.bluez.GattCharacteristic <org.bluez.GattCharacteristic>`
+* :doc:`org.bluez.GattDescriptor <org.bluez.GattDescriptor>`
diff --git a/doc/dbus-obex.rst b/doc/dbus-obex.rst
new file mode 100644
index 000000000000..b3d0e5c90ada
--- /dev/null
+++ b/doc/dbus-obex.rst
@@ -0,0 +1,33 @@
+OBEX D-Bus APIs
+===============
+
+.. toctree::
+   :hidden:
+   :maxdepth: 2
+
+   org.bluez.obex.Agent
+   org.bluez.obex.Image
+   org.bluez.obex.Client
+   org.bluez.obex.Session
+   org.bluez.obex.Transfer
+   org.bluez.obex.ObjectPush
+   org.bluez.obex.FileTransfer
+   org.bluez.obex.Synchronization
+   org.bluez.obex.PhonebookAccess
+   org.bluez.obex.MessageAccess
+   org.bluez.obex.Message
+   org.bluez.obex.AgentManager
+
+* :doc:`org.bluez.obex.Client </org.bluez.obex.Client>`
+* :doc:`org.bluez.obex.Session <org.bluez.obex.Session>`
+* :doc:`org.bluez.obex.Transfer <org.bluez.obex.Transfer>`
+* :doc:`org.bluez.obex.ObjectPush <org.bluez.obex.ObjectPush>`
+* :doc:`org.bluez.obex.FileTransfer <org.bluez.obex.FileTransfer>`
+* :doc:`org.bluez.obex.Synchronization <org.bluez.obex.Synchronization>`
+* :doc:`org.bluez.obex.PhonebookAccess <org.bluez.obex.PhonebookAccess>`
+* :doc:`org.bluez.obex.MessageAccess <org.bluez.obex.MessageAccess>`
+* :doc:`org.bluez.obex.Message <org.bluez.obex.Message>`
+* :doc:`org.bluez.obex.AgentManager <org.bluez.obex.AgentManager>`
+* :doc:`org.bluez.obex.Agent <org.bluez.obex.Agent>`
+* :doc:`org.bluez.obex.Image <org.bluez.obex.Image>`
+
diff --git a/doc/dbus.rst b/doc/dbus.rst
new file mode 100644
index 000000000000..3bed4711f56d
--- /dev/null
+++ b/doc/dbus.rst
@@ -0,0 +1,76 @@
+D-Bus APIs
+==========
+
+.. toctree::
+   :hidden:
+   :maxdepth: 2
+
+   org.bluez.Adapter
+   org.bluez.AdminPolicySet
+   org.bluez.AdminPolicyStatus
+   org.bluez.Agent
+   org.bluez.AgentManager
+   org.bluez.Battery
+   org.bluez.BatteryProvider
+   org.bluez.BatteryProviderManager
+   org.bluez.Bearer.BREDR
+   org.bluez.Bearer.LE
+   org.bluez.LEAdvertisingManager
+   org.bluez.LEAdvertisement
+   org.bluez.AdvertisementMonitorManager
+   org.bluez.AdvertisementMonitor
+   org.bluez.Device
+   org.bluez.DeviceSet
+   org.bluez.Input
+   org.bluez.Media
+   org.bluez.MediaAssistant
+   org.bluez.MediaControl
+   org.bluez.MediaEndpoint
+   org.bluez.MediaItem
+   org.bluez.MediaTransport
+   org.bluez.MediaFolder
+   org.bluez.MediaPlayer
+   org.bluez.Network
+   org.bluez.NetworkServer
+   org.bluez.Profile
+   org.bluez.ProfileManager
+   org.bluez.Telephony
+   org.bluez.Call
+   org.bluez.ThermometerManager
+   org.bluez.Thermometer
+   org.bluez.ThermometerWatcher
+
+* :doc:`org.bluez.Adapter <org.bluez.Adapter>`
+* :doc:`org.bluez.AdminPolicySet <org.bluez.AdminPolicySet>`
+* :doc:`org.bluez.AdminPolicyStatus <org.bluez.AdminPolicyStatus>`
+* :doc:`org.bluez.Agent <org.bluez.Agent>`
+* :doc:`org.bluez.AgentManager <org.bluez.AgentManager>`
+* :doc:`org.bluez.Battery <org.bluez.Battery>`
+* :doc:`org.bluez.BatteryProvider <org.bluez.BatteryProvider>`
+* :doc:`org.bluez.BatteryProviderManager <org.bluez.BatteryProviderManager>`
+* :doc:`org.bluez.Bearer.BREDR <org.bluez.Bearer.BREDR>`
+* :doc:`org.bluez.Bearer.LE <org.bluez.Bearer.LE>`
+* :doc:`org.bluez.LEAdvertisingManager <org.bluez.LEAdvertisingManager>`
+* :doc:`org.bluez.LEAdvertisement <org.bluez.LEAdvertisement>`
+* :doc:`org.bluez.AdvertisementMonitorManager <org.bluez.AdvertisementMonitorManager>`
+* :doc:`org.bluez.AdvertisementMonitor <org.bluez.AdvertisementMonitor>`
+* :doc:`org.bluez.Device <org.bluez.Device>`
+* :doc:`org.bluez.DeviceSet <org.bluez.DeviceSet>`
+* :doc:`org.bluez.Input <org.bluez.Input>`
+* :doc:`org.bluez.Media <org.bluez.Media>`
+* :doc:`org.bluez.MediaAssistant <org.bluez.MediaAssistant>`
+* :doc:`org.bluez.MediaControl <org.bluez.MediaControl>`
+* :doc:`org.bluez.MediaEndpoint <org.bluez.MediaEndpoint>`
+* :doc:`org.bluez.MediaItem <org.bluez.MediaItem>`
+* :doc:`org.bluez.MediaTransport <org.bluez.MediaTransport>`
+* :doc:`org.bluez.MediaFolder <org.bluez.MediaFolder>`
+* :doc:`org.bluez.MediaPlayer <org.bluez.MediaPlayer>`
+* :doc:`org.bluez.Network <org.bluez.Network>`
+* :doc:`org.bluez.NetworkServer <org.bluez.NetworkServer>`
+* :doc:`org.bluez.Profile <org.bluez.Profile>`
+* :doc:`org.bluez.ProfileManager <org.bluez.ProfileManager>`
+* :doc:`org.bluez.Telephony <org.bluez.Telephony>`
+* :doc:`org.bluez.Call <org.bluez.Call>`
+* :doc:`org.bluez.ThermometerManager <org.bluez.ThermometerManager>`
+* :doc:`org.bluez.Thermometer <org.bluez.Thermometer>`
+* :doc:`org.bluez.ThermometerWatcher <org.bluez.ThermometerWatcher>`
diff --git a/doc/deprecated-tools.rst b/doc/deprecated-tools.rst
new file mode 100644
index 000000000000..0c86f5265751
--- /dev/null
+++ b/doc/deprecated-tools.rst
@@ -0,0 +1,22 @@
+Deprecated Tools
+================
+
+.. toctree::
+   :hidden:
+   :maxdepth: 2
+
+   hciattach
+   hciconfig
+   hcitool
+   hcidump
+   rfcomm
+   sdptool
+   ciptool
+
+* :doc:`hciattach <hciattach>`
+* :doc:`hciconfig <hciconfig>`
+* :doc:`hcitool <hcitool>`
+* :doc:`hcidump <hcidump>`
+* :doc:`rfcomm <rfcomm>`
+* :doc:`sdptool <sdptool>`
+* :doc:`ciptool <ciptool>`
diff --git a/doc/dev-maint.rst b/doc/dev-maint.rst
new file mode 100644
index 000000000000..d92e892ba352
--- /dev/null
+++ b/doc/dev-maint.rst
@@ -0,0 +1,17 @@
+Development and Maintenance
+===========================
+
+
+.. toctree::
+   :hidden:
+
+   test-runner
+   security-bugs
+   coding-style
+   maintainer-guidelines
+
+* :doc:`Test kernel changes to the Bluetooth subsystem <test-runner>`
+* :doc:`Security bugs handling <security-bugs>`
+* :doc:`Coding style <coding-style>`
+* :doc:`Maintainers guidelines <maintainer-guidelines>`
+
diff --git a/doc/favicon.ico b/doc/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..1e136f240e08be84dc0e214daf84715a98bcb307
GIT binary patch
literal 29152
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
Pmn=m&HQ550*YExp%bT+2

literal 0
HcmV?d00001

diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 000000000000..f7b35ee0925a
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,47 @@
+
+.. image:: _static/bluez.png
+
+BlueZ
+=====
+
+BlueZ overview lives here.
+
+Here's a link to the website `BlueZ <http://www.bluez.org>`_.
+
+* :doc:`Development and Maintenance <dev-maint>`
+* :doc:`Low-level Protocol and Management APIs <protocols>`
+
+Man pages
+---------
+
+* :doc:`Daemons <daemons>`
+* :doc:`bluetoothctl <bluetoothctl-cmd>`
+* :doc:`Tools <tools>`
+* :doc:`Deprecated tools <deprecated-tools>`
+
+D-Bus APIs
+----------
+
+* :doc:`D-Bus APIs <dbus>`
+* :doc:`D-Bus GATT APIs <dbus>`
+* :doc:`OBEX D-Bus APIs <dbus-obex>`
+
+Qualifications
+--------------
+
+* :doc:`Qualifications <qualifications>`
+
+.. toctree::
+   :maxdepth: 2
+   :hidden:
+
+   dev-maint
+   protocols
+   daemons
+   bluetoothctl-cmd
+   tools
+   deprecated-tools
+   dbus
+   dbus-gatt
+   dbus-obex
+   qualifications
diff --git a/doc/meson.build b/doc/meson.build
index 498b6a479067..722f1407f51b 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -42,7 +42,11 @@ man_pages = [
   'org.bluez.AdvertisementMonitor.5',
   'org.bluez.Telephony.5',
   'org.bluez.Call.5',
+  'org.bluez.ThermometerManager.5',
+  'org.bluez.Thermometer.5',
+  'org.bluez.ThermometerWatcher.5',
 ]
+doc_man_pages = man_pages
 
 obex_man_pages = [
   'org.bluez.obex.Client.5',
@@ -58,12 +62,14 @@ obex_man_pages = [
   'org.bluez.obex.Agent.5',
   'org.bluez.obex.Image.5',
 ]
+doc_man_pages += obex_man_pages
 
 if get_option('obex').enabled()
   man_pages += obex_man_pages
 endif
 
 monitor_man_pages = [ 'btmon.1' ]
+doc_man_pages += monitor_man_pages
 
 if get_option('monitor').allowed()
   man_pages += monitor_man_pages
@@ -76,8 +82,10 @@ tools_man_pages = [
   'isotest.1',
   'bdaddr.1',
 ]
+doc_man_pages += tools_man_pages
 
 noinst_tools_man_pages = [ 'btmgmt.1' ]
+doc_man_pages += noinst_tools_man_pages
 
 if get_option('tools').allowed()
   man_pages += tools_man_pages
@@ -92,12 +100,14 @@ deprecated_tools_man_pages = [
   'sdptool.1',
   'ciptool.1',
 ]
+doc_man_pages += deprecated_tools_man_pages
 
 if get_option('deprecated').allowed()
   man_pages += deprecated_tools_man_pages
 endif
 
 hid2hci_man_pages = [ 'hid2hci.1' ]
+doc_man_pages += hid2hci_man_pages
 
 if get_option('hid2hci').enabled()
   man_pages += 'hid2hci.1'
@@ -118,6 +128,7 @@ client_man_pages = [
   'bluetoothctl-hci.1',
   'bluetoothctl-telephony.1'
 ]
+doc_man_pages += client_man_pages
 
 if get_option('client').enabled()
   man_pages += client_man_pages
@@ -141,6 +152,43 @@ if rst2man.found()
   endforeach
 endif
 
+qualifications_docs = [
+  'qualification/a2dp-pics.rst',
+  'qualification/a2dp-pts.rst',
+  'qualification/avctp-pics.rst',
+  'qualification/avctp-pts.rst',
+  'qualification/avdtp-pics.rst',
+  'qualification/avdtp-pts.rst',
+  'qualification/avrcp-pics.rst',
+  'qualification/avrcp-pts.rst',
+  'qualification/gap-pics.rst',
+  'qualification/gap-pts.rst',
+  'qualification/gatt-pics.rst',
+  'qualification/gatt-pts.rst',
+  'qualification/gavdp-pics.rst',
+  'qualification/gavdp-pts.rst',
+  'qualification/hid-pics.rst',
+  'qualification/hid-pts.rst',
+  'qualification/hogp-pics.rst',
+  'qualification/hogp-pts.rst',
+  'qualification/hsp-pics.rst',
+  'qualification/hsp-pts.rst',
+  'qualification/iopt-pics.rst',
+  'qualification/iopt-pts.rst',
+  'qualification/l2cap-pics.rst',
+  'qualification/l2cap-pts.rst',
+  'qualification/rfcomm-pics.rst',
+  'qualification/rfcomm-pts.rst',
+  'qualification/scpp-pics.rst',
+  'qualification/scpp-pts.rst',
+  'qualification/sdp-pics.rst',
+  'qualification/sdp-pts.rst',
+  'qualification/sm-pics.rst',
+  'qualification/sm-pts.rst',
+  'qualification/spp-pics.rst',
+  'qualification/spp-pts.rst',
+]
+
 bluetoothd_rst = configure_file(
   input: 'bluetoothd.rst.in',
   output: 'bluetoothd.rst',
@@ -171,3 +219,74 @@ if get_option('mesh').enabled() and rst2man.found()
     install_dir: get_option('mandir') / 'man8'
   )
 endif
+
+if get_option('documentation').enabled()
+  docs_sources = [
+    'conf.py',
+    'favicon.ico',
+
+    'index.rst',
+    'dev-maint.rst',
+    'test-runner.rst',
+    'assigned-numbers.rst',
+    'security-bugs.rst',
+    'coding-style.rst',
+    'maintainer-guidelines.rst',
+    'protocols.rst',
+    'daemons.rst',
+    'bluetoothctl-cmd.rst',
+    'tools.rst',
+    'deprecated-tools.rst',
+    'dbus.rst',
+    'dbus-gatt.rst',
+    'dbus-obex.rst',
+    'qualifications.rst',
+  ]
+  docs_sources += qualifications_docs
+
+  foreach man_page: doc_man_pages
+    man_page_name = man_page.substring(0, -2)
+    man_section = man_page.substring(-1)
+    if man_section.to_int() == 7
+      man_page_name = man_page_name + '-protocol'
+    endif
+    docs_sources += man_page_name + '.rst'
+  endforeach
+
+  copied_docs_sources = []
+  foreach d: docs_sources
+    copied_docs_sources += configure_file(input: d, output: fs.name(d), copy: true)
+  endforeach
+  copied_docs_sources += [ bluetooth_meshd_rst, bluetoothd_rst ]
+
+  docs_inputs = copied_docs_sources
+
+  # Copy static files to the appropriate output subdir
+  copy = find_program('copy-subdir.py')
+
+  static_files = [
+    'bullet.svg',
+    'card.png',
+    'figtree.woff2',
+    'toc_bullet.svg',
+    'bluez.css',
+    'bluez.png',
+  ]
+
+  foreach f: static_files
+    run_command(copy, '_static' / f, '_static' / f, check: true)
+  endforeach
+
+  sphinx_command = [ sphinx_build ]
+
+  if get_option('werror')
+    sphinx_command += [ '-W', '--keep-going' ]
+  endif
+
+  custom_target('docs',
+    input: docs_inputs,
+    output: 'html',
+    command: sphinx_command + [ meson.current_build_dir(), '@OUTPUT@' ],
+    build_by_default: true,
+  )
+endif
diff --git a/doc/protocols.rst b/doc/protocols.rst
new file mode 100644
index 000000000000..1b61a72dd605
--- /dev/null
+++ b/doc/protocols.rst
@@ -0,0 +1,22 @@
+Protocol and Management References
+==================================
+
+.. toctree::
+   :hidden:
+
+   hci-protocol
+   mgmt-protocol
+   l2cap-protocol
+   rfcomm-protocol
+   assigned-numbers
+   sco-protocol
+   iso-protocol
+   btsnoop-protocol
+
+* :doc:`HCI protocol <hci-protocol>`
+* :doc:`MGMT protocol <mgmt-protocol>`
+* :doc:`L2CAP protocol <l2cap-protocol>`
+* :doc:`RFCOMM protocol <rfcomm-protocol>` and :doc:`channel numbers <assigned-numbers>`
+* :doc:`SCO transport <sco-protocol>`
+* :doc:`ISO transport <iso-protocol>`
+* :doc:`BTSnoop/Monitor protocol <btsnoop-protocol>`
diff --git a/doc/qualifications.rst b/doc/qualifications.rst
new file mode 100644
index 000000000000..d7eb6b803f01
--- /dev/null
+++ b/doc/qualifications.rst
@@ -0,0 +1,76 @@
+Qualifications
+--------------
+
+.. toctree::
+   :hidden:
+   :maxdepth: 2
+
+   a2dp-pics
+   a2dp-pts
+   avctp-pics
+   avctp-pts
+   avdtp-pics
+   avdtp-pts
+   avrcp-pics
+   avrcp-pts
+   gap-pics
+   gap-pts
+   gatt-pics
+   gatt-pts
+   gavdp-pics
+   gavdp-pts
+   hid-pics
+   hid-pts
+   hogp-pics
+   hogp-pts
+   hsp-pics
+   hsp-pts
+   iopt-pics
+   iopt-pts
+   l2cap-pics
+   l2cap-pts
+   rfcomm-pics
+   rfcomm-pts
+   scpp-pics
+   scpp-pts
+   sdp-pics
+   sdp-pts
+   sm-pics
+   sm-pts
+   spp-pics
+   spp-pts
+
+* :doc:`a2dp-pics <a2dp-pics>`
+* :doc:`a2dp-pts <a2dp-pts>`
+* :doc:`avctp-pics <avctp-pics>`
+* :doc:`avctp-pts <avctp-pts>`
+* :doc:`avdtp-pics <avdtp-pics>`
+* :doc:`avdtp-pts <avdtp-pts>`
+* :doc:`avrcp-pics <avrcp-pics>`
+* :doc:`avrcp-pts <avrcp-pts>`
+* :doc:`gap-pics <gap-pics>`
+* :doc:`gap-pts <gap-pts>`
+* :doc:`gatt-pics <gatt-pics>`
+* :doc:`gatt-pts <gatt-pts>`
+* :doc:`gavdp-pics <gavdp-pics>`
+* :doc:`gavdp-pts <gavdp-pts>`
+* :doc:`hid-pics <hid-pics>`
+* :doc:`hid-pts <hid-pts>`
+* :doc:`hogp-pics <hogp-pics>`
+* :doc:`hogp-pts <hogp-pts>`
+* :doc:`hsp-pics <hsp-pics>`
+* :doc:`hsp-pts <hsp-pts>`
+* :doc:`iopt-pics <iopt-pics>`
+* :doc:`iopt-pts <iopt-pts>`
+* :doc:`l2cap-pics <l2cap-pics>`
+* :doc:`l2cap-pts <l2cap-pts>`
+* :doc:`rfcomm-pics <rfcomm-pics>`
+* :doc:`rfcomm-pts <rfcomm-pts>`
+* :doc:`scpp-pics <scpp-pics>`
+* :doc:`scpp-pts <scpp-pts>`
+* :doc:`sdp-pics <sdp-pics>`
+* :doc:`sdp-pts <sdp-pts>`
+* :doc:`sm-pics <sm-pics>`
+* :doc:`sm-pts <sm-pts>`
+* :doc:`spp-pics <spp-pics>`
+* :doc:`spp-pts <spp-pts>`
diff --git a/doc/tools.rst b/doc/tools.rst
new file mode 100644
index 000000000000..6da8ca58f6fc
--- /dev/null
+++ b/doc/tools.rst
@@ -0,0 +1,24 @@
+Tools
+=====
+
+.. toctree::
+   :hidden:
+   :maxdepth: 2
+
+   btmon
+   rctest
+   l2ping
+   btattach
+   isotest
+   btmgmt
+   hid2hci
+   bdaddr
+
+* :doc:`btmon <btmon>`
+* :doc:`rctest <rctest>`
+* :doc:`l2ping <l2ping>`
+* :doc:`btattach <btattach>`
+* :doc:`isotest <isotest>`
+* :doc:`btmgmt <btmgmt>`
+* :doc:`hid2hci <hid2hci>`
+* :doc:`bdaddr <bdaddr>`
diff --git a/meson.build b/meson.build
index 91042e437438..c7971430d340 100644
--- a/meson.build
+++ b/meson.build
@@ -159,6 +159,10 @@ ell_mesh_dep = dependency('ell',
   required: get_option('mesh')
 )
 
+pymod = import('python')
+sphinx_build = find_program('sphinx-build', native: true, required: get_option('documentation'))
+sphinx_modules = pymod.find_installation(modules: ['sphinxext.opengraph', 'sphinx_copybutton', 'furo'], required: get_option('documentation'))
+
 rst2man = find_program('rst2man', 'rst2man.py', required: get_option('manpages'))
 if rst2man.found()
   rst2man_command = [
@@ -294,7 +298,7 @@ endif
 if get_option('mesh').enabled()
   subdir('mesh')
 endif
-if rst2man.found()
+if get_option('manpages').enabled() or get_option('documentation').enabled()
   subdir('doc')
 endif
 sh = find_program('sh', required: true)
diff --git a/meson_options.txt b/meson_options.txt
index 79e75e33ddaf..d097a7c91fcf 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -12,6 +12,7 @@ option('logind', type: 'feature', value: 'enabled', description: 'Enable logind
 option('udev', type: 'feature', value: 'enabled', description: 'Enable udev device support in tools')
 option('datafiles', type: 'feature', value: 'enabled', description: 'Install configuration and data files')
 option('manpages', type: 'feature', value: 'auto', description: 'Enable building of manual pages')
+option('documentation', type: 'feature', value: 'auto', description: 'Enable building of online documentation')
 option('experimental', type: 'feature', value: 'disabled', description: 'Enable experimental tools')
 option('deprecated', type: 'feature', value: 'disabled', description: 'Enable deprecated tools')
 option('external-plugins', type: 'feature', value: 'enabled', description: 'Enable support for external plugins')
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 15/15] doc: Add introduction to GATT
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Paul Otto
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

From: Paul Otto <potto@ieee.org>

As originally written for the bluetoothctl gatt submenu at:
https://github.com/potto216/bluez/blob/master/client/bluetoothctl-gatt.rst

Closes: https://github.com/bluez/bluez/issues/747
---
 doc/dbus-gatt.rst  |   2 +
 doc/gatt-intro.rst | 141 +++++++++++++++++++++++++++++++++++++++++++++
 doc/meson.build    |   1 +
 3 files changed, 144 insertions(+)
 create mode 100644 doc/gatt-intro.rst

diff --git a/doc/dbus-gatt.rst b/doc/dbus-gatt.rst
index fb026e9f9ede..264fb12bae11 100644
--- a/doc/dbus-gatt.rst
+++ b/doc/dbus-gatt.rst
@@ -5,12 +5,14 @@ D-Bus GATT APIs
    :hidden:
    :maxdepth: 2
 
+   gatt-intro
    org.bluez.GattManager
    org.bluez.GattProfile
    org.bluez.GattService
    org.bluez.GattCharacteristic
    org.bluez.GattDescriptor
 
+* :doc:`Intro to GATT (Generic Attribute Profile) <gatt-intro>`
 * :doc:`org.bluez.GattManager <org.bluez.GattManager>`
 * :doc:`org.bluez.GattProfile <org.bluez.GattProfile>`
 * :doc:`org.bluez.GattService <org.bluez.GattService>`
diff --git a/doc/gatt-intro.rst b/doc/gatt-intro.rst
new file mode 100644
index 000000000000..271f9bac08ce
--- /dev/null
+++ b/doc/gatt-intro.rst
@@ -0,0 +1,141 @@
+=========================================
+Intro to GATT (Generic Attribute Profile)
+=========================================
+
+The Generic Attribute Profile (GATT) defines how two connected Bluetooth
+LE devices can share data using concepts called Services and Characteristics.
+Data is orgnaized into a hierarchical structure of profiles, services, and
+characteristics in the GATT database. The device containing the GATT database
+is known as the server device and the device accessing the GATT database is
+the client device. Only the database's services and characteristics are
+documented here.
+
+BlueZ provides D-Bus interfaces for accessing GATT services and profiles which
+are documented in the :doc:`D-Bus GATT APIs <dbus>` document.
+
+One-off usage and prototyping can also be done using the bluetoothctl
+command-line utility.
+
+UUIDs
+-----
+
+Services and characteristics are differentiated by Universally Unique
+Identifiers (UUIDs). For GATT there are several formats of UUIDs: 128-bit,
+32-bit, and 16-bit.
+
+128-bit UUIDs: Displayed in five groups of 32 hexadecimal digits separated
+by hyphens (e.g., 123e4567-e89b-12d3-a456-426614174000). 128-bit UUIDs allow
+for custom services and characteristics that do not conflict with those of
+other vendors or standard profiles defined by the Bluetooth SIG.
+
+32-bit UUIDs: A subset of the 128-bit UUID, where only 32 bits are used to
+represent the UUID. The UUID values fall within a certain range defined by
+the Bluetooth specification. The 32-bit value is inserted into a base UUID
+(UUUUUUUU-0000-1000-8000-00805F9B34FB) to form a full 128-bit UUID. The
+UUUUUUUU in the base UUID is replaced by the 32-bit value. This format is
+used within the Bluetooth protocol for standardized services and profiles.
+
+16-bit UUIDs: Similar to the 32-bit UUID, but more common. This is also
+used for standardized services, characteristics, and descriptors defined by
+the Bluetooth SIG. The 16-bit value is inserted into a base UUID
+(0000UUUU-0000-1000-8000-00805F9B34FB) to form a full 128-bit UUID.
+
+Services and Characteristics
+----------------------------
+
+Services: Collections of related data that together perform a specific
+function or feature of the device. Each service is identified by a
+UUID and contains Characteristics that define the actual data and behaviors
+of the service.
+
+Characteristics: Pieces of data under a service, representing a specific
+piece of information or a function of the device. Each characteristic can
+have its own unique value, associated properties and access permissions that
+define how it can be interacted with. These properties include read, write,
+notify, indicate, and more. A Characteristic may include one or more
+Descriptors that provide additional metadata or control specific aspects
+of the Characteristic's behavior.
+
+Properties
+----------
+
+Read Property: Allows a connected device to read the value of a characteristic.
+Information meant to be consumed but not altered by the client device, such
+as the device's battery level or sensor readings. Characteristics marked with
+the read property are essentially broadcasting their data to be accessed by
+other devices, ensuring that the information can be consumed without
+being modified.
+
+Write Property: Enables a connected device to change the value of a
+characteristic. An example of when a client device needs to alter data on the
+server device is a Bluetooth controlled light bulb, where the characteristic
+controlling the color or brightness of the light would have the write property,
+allowing external devices to modify these settings.
+
+Read/Write Property: Characteristics can also be configured to support both
+read and write properties simultaneously. This dual capability is useful for
+characteristics that hold information which not only needs to be displayed to
+external devices but also can be updated by them. For example, a Bluetooth
+thermostat may have a characteristic with read/write properties for the target
+temperature, enabling devices to both monitor the current setting and adjust
+it as needed.
+
+Notifications and Indications
+-----------------------------
+
+In addition to read and write properties,
+Bluetooth characteristics can also be configured to notify or indicate to
+connected devices when their value has changed. This is particularly useful
+for dynamic data that changes over time, such as sensor measurements or
+device status updates.
+
+Notifications: When a characteristic's notify property is enabled, it can
+send an update to connected devices whenever its value changes. This allows
+for real-time data streaming without the client having to continuously read
+the characteristic's value. Notifications are unacknowledged, meaning the
+sender does not receive confirmation that the message has been
+received successfully.
+
+Indications: Similar to notifications, indications inform connected devices
+about changes in a characteristic's value. The key difference is that
+indications are acknowledged; the receiver sends back a confirmation to the
+sender upon successfully receiving the update. This ensures reliable delivery
+of important data changes but at the cost of higher power consumption and
+potentially lower throughput due to the additional confirmation step.
+
+Organization of a GATT database The Bluetooth GATT database is a structured
+collection of values that are used to share information between two Bluetooth
+Low Energy (LE) devices. The GATT database is organized as a hierarchical
+structure composed of Services, Characteristics, and Descriptors. The two LE
+devices share the database information through read, writes, and subscription
+to notifications or indications of data values. The two LE devices perform
+this sharing by referencing 16-bit indexes known as "handles" which are
+numerical identifiers for each element in the GATT database. This avoids
+the need to use the UUIDs to reference the elements in the database.
+
+Service: A GATT database service is a collection of related functionalities
+that a device offers. It groups together one or more Characteristics that are
+logically related to perform a specific function or feature of the server
+device. Each service is identified by a UUID and contains Characteristics
+that define the actual data and behaviors of the service.
+
+Characteristic: Each characteristic can have its own unique value, associated
+properties, and access permissions that define how it can be interacted with.
+These properties include read, write, notify, indicate, and more. A
+Characteristic may include one or more Descriptors that provide additional
+metadata or control specific aspects of the Characteristic's behavior.
+
+Descriptors: A descriptor is an optional attribute that describes or controls
+certain aspects of a Characteristic's behavior. They provide additional context
+or metadata about a Characteristic. Some common types of Descriptors include the
+Characteristic User Description (provides human-readable description), the
+Characteristic Presentation Format (defines the format of the Characteristic
+value), and the Client Characteristic Configuration (used to enable or
+disable notifications or indications for a Characteristic).
+
+Handles: Every element in the GATT database (Service, Characteristic,
+Descriptor) is assigned a unique handle. Handles are 16-bit numerical
+identifiers that are used to reference these elements in GATT operations.
+The handles are used to avoid the need to use the UUIDs to reference the
+elements in the database.
+
diff --git a/doc/meson.build b/doc/meson.build
index 722f1407f51b..658980bda3f9 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -238,6 +238,7 @@ if get_option('documentation').enabled()
     'tools.rst',
     'deprecated-tools.rst',
     'dbus.rst',
+    'gatt-intro.rst',
     'dbus-gatt.rst',
     'dbus-obex.rst',
     'qualifications.rst',
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 11/15] build: Only build gdbus library if there is a user
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

---
 meson.build | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/meson.build b/meson.build
index b00e378d04ca..e034cdf80fb9 100644
--- a/meson.build
+++ b/meson.build
@@ -259,7 +259,9 @@ configure_file(
 )
 
 subdir('lib')
-subdir('gdbus')
+if not get_option('tools').disabled() or get_option('client').enabled() or get_option('obex').enabled() or not get_option('tests').disabled() or not get_option('daemon').disabled()
+  subdir('gdbus')
+endif
 subdir('attrib')
 subdir('btio')
 subdir('plugins')
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 14/15] github: Add docs deployment
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

---
 .github/workflows/docs.yml | 54 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)
 create mode 100644 .github/workflows/docs.yml

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 000000000000..07169d0bc5c9
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,54 @@
+name: Deploy Docs
+
+on:
+  push:
+    branches:
+      - master
+  workflow_dispatch:
+
+permissions:
+  contents: read
+  pages: write
+  id-token: write
+
+concurrency:
+  group: pages
+  cancel-in-progress: true
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: "3.12"
+
+      - name: Install deps
+        run: |
+          sudo apt-get update
+          sudo apt install -y libglib2.0-dev libdbus-1-dev meson python3-docutils furo python3-sphinx python3-sphinx-copybutton python3-sphinxext-opengraph
+
+      - name: Build site
+        run: |
+          meson setup --prefix /tmp/bluez-install --libdir lib -Dudev=disabled -Dauto_features=disabled -Dtools=disabled -Dclient=disabled -Ddaemon=disabled -Dmonitor=disabled -Ddeprecated=disabled -Dexperimental=disabled -Ddocumentation=enabled -Dlocalstatedir=/var _build
+          meson compile -C _build
+
+      - name: Upload Pages artifact
+        uses: actions/upload-pages-artifact@v3
+        with:
+          path: _build/doc/html/
+
+  deploy:
+    runs-on: ubuntu-latest
+    needs: build
+    environment:
+      name: github-pages
+      url: ${{ steps.deployment.outputs.page_url }}
+    steps:
+      - name: Deploy to GitHub Pages
+        id: deployment
+        uses: actions/deploy-pages@v4
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 10/15] build: Only build profiles if the daemon is built
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

---
 meson.build | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/meson.build b/meson.build
index 586b69459d8a..b00e378d04ca 100644
--- a/meson.build
+++ b/meson.build
@@ -263,7 +263,9 @@ subdir('gdbus')
 subdir('attrib')
 subdir('btio')
 subdir('plugins')
-subdir('profiles')
+if get_option('daemon').enabled()
+  subdir('profiles')
+endif
 subdir('src')
 subdir('client')
 if get_option('monitor').allowed()
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 09/15] unit: Make gobex-transfer tests run concurrently
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

---
 unit/meson.build | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/unit/meson.build b/unit/meson.build
index 1bd3b7a85992..92e1c6f66caa 100644
--- a/unit/meson.build
+++ b/unit/meson.build
@@ -71,15 +71,18 @@ tests3 = [
   [ 'test-avctp.c', '../src/log.c', 'avctp.c' ],
   [ 'test-util.c' ],
 ]
+tests4 = []
 
 if ical_dep.found()
   tests3 += [
     [ 'test-gobex.c', 'util.c', gobex_sources ],
     [ 'test-gobex-packet.c', 'util.c', gobex_sources ],
     [ 'test-gobex-header.c', 'util.c', gobex_sources ],
-    [ 'test-gobex-transfer.c', 'util.c', gobex_sources ],
     [ 'test-gobex-apparam.c', 'util.c', gobex_sources ],
   ]
+  tests4 = [
+    [ 'test-gobex-transfer.c', 'util.c', gobex_sources ],
+  ]
 endif
 
 foreach t: tests3
@@ -93,6 +96,22 @@ foreach t: tests3
   test(test_name, exe)
 endforeach
 
+foreach t: tests4
+  test_name = t[0].substring(0, -2)
+  exe = executable(test_name,
+    sources: t,
+    c_args: [ '-DSTORAGEDIR="@0"'.format(storagedir) ],
+    include_directories: '../lib',
+    dependencies: libshared_glib_dep
+  )
+
+  r = run_command(sh, unittest_list, t, check: true)
+  unit_tests = r.stdout().strip().split(' ')
+  foreach ut: unit_tests
+    test(ut, exe, args: [ '-p', ut ])
+  endforeach
+endforeach
+
 exe = executable('test-gdbus-client',
   sources: 'test-gdbus-client.c',
   dependencies: [ libgdbus_internal_dep, libshared_glib_dep ]
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 07/15] build: Add option to allow disabling bluetoothd
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

This makes it possible to build, for example, just the library, or the
command-line client, without also building and installing bluetoothd.
---
 doc/meson.build   |   3 +-
 meson.build       |   8 ++-
 meson_options.txt |   1 +
 src/meson.build   | 164 +++++++++++++++++++++++-----------------------
 4 files changed, 92 insertions(+), 84 deletions(-)

diff --git a/doc/meson.build b/doc/meson.build
index 3d5ccd7d3034..498b6a479067 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -146,7 +146,8 @@ bluetoothd_rst = configure_file(
   output: 'bluetoothd.rst',
   configuration: man_conf
 )
-if rst2man.found()
+
+if get_option('daemon').enabled() and rst2man.found()
   custom_target(
     input: bluetoothd_rst,
     output: 'bluetoothd.8',
diff --git a/meson.build b/meson.build
index 0b5cc0b7ce69..f716eae1f063 100644
--- a/meson.build
+++ b/meson.build
@@ -293,8 +293,12 @@ endif
 # Fix permissions on install
 install = find_program('install')
 sh = find_program('sh')
-meson.add_install_script(sh, '-c', 'install -dm755 ${DESTDIR}/' + configdir)
-meson.add_install_script(sh, '-c', 'install -dm700 ${DESTDIR}/' + storagedir)
+if get_option('daemon').enabled()
+  meson.add_install_script(sh, '-c', 'install -dm755 ${DESTDIR}/' + configdir)
+endif
+if get_option('daemon').enabled() or get_option('mesh').enabled()
+  meson.add_install_script(sh, '-c', 'install -dm700 ${DESTDIR}/' + storagedir)
+endif
 if get_option('mesh').enabled()
   meson.add_install_script(sh, '-c', 'install -dm700 ${DESTDIR}/' + meshstoragedir)
 endif
diff --git a/meson_options.txt b/meson_options.txt
index 54cfdbb0d7c1..79e75e33ddaf 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -6,6 +6,7 @@ option('tests', type: 'feature', value: 'auto', description: 'Enable testing too
 option('tools', type: 'feature', value: 'enabled', description: 'Enable Bluetooth tools')
 option('monitor', type: 'feature', value: 'enabled', description: 'Enable Bluetooth monitor')
 option('client', type: 'feature', value: 'enabled', description: 'Enable command line client')
+option('daemon', type: 'feature', value: 'enabled', description: 'Enable bluetoothd daemon')
 option('systemd', type: 'feature', value: 'enabled', description: 'Install systemd service files')
 option('logind', type: 'feature', value: 'enabled', description: 'Enable logind integration in obexd')
 option('udev', type: 'feature', value: 'enabled', description: 'Enable udev device support in tools')
diff --git a/src/meson.build b/src/meson.build
index 65b7131c86b4..4962b782a7b6 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,88 +1,90 @@
 subdir('shared')
 
-if get_option('datafiles').allowed()
-  install_data('bluetooth.conf',
-    install_dir: dbusconfdir / 'dbus-1/system.d'
-  )
-  install_data('main.conf',
-    install_dir: configdir
-  )
-endif
+if get_option('daemon').allowed()
+  if get_option('datafiles').allowed()
+    install_data('bluetooth.conf',
+      install_dir: dbusconfdir / 'dbus-1/system.d'
+    )
+    install_data('main.conf',
+      install_dir: configdir
+    )
+  endif
 
-if systemd_dep.found()
-  configure_file(
-    input: 'bluetooth.service.in',
-    output: 'bluetooth.service',
-    install: true,
-    install_dir: systemdsystemunitdir,
-    configuration: service_conf
+  if systemd_dep.found()
+    configure_file(
+      input: 'bluetooth.service.in',
+      output: 'bluetooth.service',
+      install: true,
+      install_dir: systemdsystemunitdir,
+      configuration: service_conf
+    )
+
+    install_data('org.bluez.service',
+      install_dir: dbussystembusdir
+    )
+  endif
+
+  genbuiltin = find_program('genbuiltin')
+  builtin_h = custom_target('src-builtin.h',
+    output: 'builtin.h',
+    capture: true,
+    command: [ genbuiltin, builtin_modules ]
   )
 
-  install_data('org.bluez.service',
-    install_dir: dbussystembusdir
+  executable('bluetoothd',
+    sources: [ builtin_sources,
+      attrib_sources,
+      btio_sources,
+      builtin_h,
+      'main.c',
+      'log.c',
+      'backtrace.c',
+      'rfkill.c',
+      'sdpd-server.c',
+      'sdpd-request.c',
+      'sdpd-service.c',
+      'sdpd-database.c',
+      'gatt-database.c',
+      'sdp-xml.c',
+      'sdp-client.c',
+      'textfile.c',
+      'uuid-helper.c',
+      'plugin.c',
+      'storage.c',
+      'advertising.c',
+      'agent.c',
+      'bearer.c',
+      'error.c',
+      'adapter.c',
+      'profile.c',
+      'service.c',
+      'gatt-client.c',
+      'device.c',
+      'dbus-common.c',
+      'eir.c',
+      'adv_monitor.c',
+      'battery.c',
+      'settings.c',
+      'set.c' ],
+    dependencies: [
+      libbluetooth_internal_dep,
+      libgdbus_internal_dep,
+      libshared_glib_dep,
+      backtrace_dep,
+      glib_dep,
+      dbus_dep,
+      udevlib_dep,
+      builtin_deps
+    ],
+    override_options: [ 'b_lundef=false' ],
+    c_args: [
+      '-DBLUETOOTH_PLUGIN_BUILTIN',
+      '-DPLUGINDIR="@0@"'.format(plugindir),
+      '-DSTORAGEDIR="@0@"'.format(storagedir),
+      '-DCONFIGDIR="@0@"'.format(configdir)
+    ],
+    link_args: [ '-Wl,--gc-sections', external_plugins_link_args ],
+    install: true,
+    install_dir: pkglibexecdir
   )
 endif
-
-genbuiltin = find_program('genbuiltin')
-builtin_h = custom_target('src-builtin.h',
-  output: 'builtin.h',
-  capture: true,
-  command: [ genbuiltin, builtin_modules ]
-)
-
-executable('bluetoothd',
-  sources: [ builtin_sources,
-    attrib_sources,
-    btio_sources,
-    builtin_h,
-    'main.c',
-    'log.c',
-    'backtrace.c',
-    'rfkill.c',
-    'sdpd-server.c',
-    'sdpd-request.c',
-    'sdpd-service.c',
-    'sdpd-database.c',
-    'gatt-database.c',
-    'sdp-xml.c',
-    'sdp-client.c',
-    'textfile.c',
-    'uuid-helper.c',
-    'plugin.c',
-    'storage.c',
-    'advertising.c',
-    'agent.c',
-    'bearer.c',
-    'error.c',
-    'adapter.c',
-    'profile.c',
-    'service.c',
-    'gatt-client.c',
-    'device.c',
-    'dbus-common.c',
-    'eir.c',
-    'adv_monitor.c',
-    'battery.c',
-    'settings.c',
-    'set.c' ],
-  dependencies: [
-    libbluetooth_internal_dep,
-    libgdbus_internal_dep,
-    libshared_glib_dep,
-    backtrace_dep,
-    glib_dep,
-    dbus_dep,
-    udevlib_dep,
-    builtin_deps
-  ],
-  override_options: [ 'b_lundef=false' ],
-  c_args: [
-    '-DBLUETOOTH_PLUGIN_BUILTIN',
-    '-DPLUGINDIR="@0@"'.format(plugindir),
-    '-DSTORAGEDIR="@0@"'.format(storagedir),
-    '-DCONFIGDIR="@0@"'.format(configdir)
-  ],
-  link_args: [ '-Wl,--gc-sections', external_plugins_link_args ],
-  install: true,
-  install_dir: pkglibexecdir
-)
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 06/15] emulator: Install the emulator if built
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

It's useful for testing.
---
 emulator/meson.build | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/emulator/meson.build b/emulator/meson.build
index 40544567e4e4..9da91b16671b 100644
--- a/emulator/meson.build
+++ b/emulator/meson.build
@@ -18,6 +18,8 @@ if get_option('tests').allowed()
       'phy.c',
       'le.c' ],
     dependencies: [ libbluetooth_internal_dep, libshared_mainloop_dep ]
+    install: true,
+    install_dir: pkglibexecdir
   )
 
   executable('b1ee',
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 08/15] unit: Run test-bap tests concurrently
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

---
 meson.build           |  2 +-
 unit/meson.build      | 25 ++++++++++++++++++++++---
 unit/unittest_list.sh | 15 +++++++++++++++
 3 files changed, 38 insertions(+), 4 deletions(-)
 create mode 100644 unit/unittest_list.sh

diff --git a/meson.build b/meson.build
index f716eae1f063..586b69459d8a 100644
--- a/meson.build
+++ b/meson.build
@@ -285,6 +285,7 @@ endif
 if rst2man.found()
   subdir('doc')
 endif
+sh = find_program('sh', required: true)
 if get_option('tests').allowed()
   subdir('test')
   subdir('unit')
@@ -292,7 +293,6 @@ endif
 
 # Fix permissions on install
 install = find_program('install')
-sh = find_program('sh')
 if get_option('daemon').enabled()
   meson.add_install_script(sh, '-c', 'install -dm755 ${DESTDIR}/' + configdir)
 endif
diff --git a/unit/meson.build b/unit/meson.build
index c8198574147d..1bd3b7a85992 100644
--- a/unit/meson.build
+++ b/unit/meson.build
@@ -7,7 +7,6 @@ tests1 = [
   [ 'test-avrcp.c', '../src/log.c', 'avctp.c', 'avrcp-lib.c' ],
   [ 'test-lib.c' ],
   [ 'test-gatt.c' ],
-  [ 'test-bap.c' ],
   [ 'test-micp.c' ],
   [ 'test-bass.c' ],
   [ 'test-vcp.c' ],
@@ -39,6 +38,26 @@ foreach t: tests1
 endforeach
 
 tests2 = [
+  [ 'test-bap.c' ],
+]
+
+unittest_list = find_program('unittest_list.sh')
+
+foreach t: tests2
+  test_name = t[0].substring(0, -2)
+  exe = executable(test_name,
+    sources: t,
+    dependencies: [ libshared_glib_dep, libbluetooth_internal_dep ],
+  )
+
+  r = run_command(sh, unittest_list, t, check: true)
+  unit_tests = r.stdout().strip().split(' ')
+  foreach ut: unit_tests
+    test(ut, exe, args: [ '-p', ut ])
+  endforeach
+endforeach
+
+tests3 = [
   [ 'test-textfile.c', '../src/textfile.c' ],
   [ 'test-crc.c', '../monitor/crc.c' ],
   [ 'test-crypto.c' ],
@@ -54,7 +73,7 @@ tests2 = [
 ]
 
 if ical_dep.found()
-  tests2 += [
+  tests3 += [
     [ 'test-gobex.c', 'util.c', gobex_sources ],
     [ 'test-gobex-packet.c', 'util.c', gobex_sources ],
     [ 'test-gobex-header.c', 'util.c', gobex_sources ],
@@ -63,7 +82,7 @@ if ical_dep.found()
   ]
 endif
 
-foreach t: tests2
+foreach t: tests3
   test_name = t[0].substring(0, -2)
   exe = executable(test_name,
     sources: t,
diff --git a/unit/unittest_list.sh b/unit/unittest_list.sh
new file mode 100644
index 000000000000..c478edc9d3e4
--- /dev/null
+++ b/unit/unittest_list.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+if [ $# -lt 1 ] ; then
+	echo "Usage: $0 FILE..."
+	exit 1
+fi
+
+TESTS=$(grep '^[[:space:]]*define_test[_a-z]*("' $* | cut -d \" -f 2 | cut -d " " -f 1)
+if [ -z "$TESTS" ] ; then
+	TESTS=$(grep '^[[:space:]]*tester_add("' $* | cut -d \" -f 2 | cut -d " " -f 1)
+fi
+if [ -z "$TESTS" ] ; then
+	TESTS=$(grep '^[[:space:]]*g_test_add_*func("' $* | cut -d \" -f 2 | cut -d " " -f 1)
+fi
+echo $TESTS
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 02/15] build: Add meson build system
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

The options default were selected to try and match the existing
autotools default as best as possible. Further commits will likely
change those defaults, but at least it should be clear what defaults
were changed from.

However, a number of options were removed:

--enable-pie is replaced by the meson base option "b_pie":
https://mesonbuild.com/Builtin-options.html#base-options

--enable-maintainer-mode is replaced by the "debug" build-types:
https://mesonbuild.com/Builtin-options.html#core-options

--enable-debug and --disable-optimization are replaced by the
debug build type:
https://mesonbuild.com/Builtin-options.html#details-for-buildtype

Each of the sanitisers have their own b_sanitize option:
- asan: b_sanitize=address
- lsan: b_sanitize=leak
- ubsan: b_sanitize=address,undefined
https://mesonbuild.com/Builtin-options.html#base-options

--enable-coverage is replaced by the b_coverage option:
https://mesonbuild.com/Builtin-options.html#base-options

--enable-valgrind can be replicated by running the test suite with:
meson test --setup valgrind
---
 attrib/meson.build           |  17 ++
 btio/meson.build             |   1 +
 client/btpclient/meson.build |  19 +++
 client/meson.build           |  33 ++++
 completion/meson.build       |   3 +
 doc/meson.build              | 172 ++++++++++++++++++++
 emulator/meson.build         |  34 ++++
 gdbus/meson.build            |  19 +++
 gobex/meson.build            |   8 +
 lib/meson.build              |  53 +++++++
 mesh/meson.build             |  60 +++++++
 meson.build                  | 299 +++++++++++++++++++++++++++++++++++
 meson_options.txt            |  52 ++++++
 monitor/meson.build          |  37 +++++
 obexd/meson.build            | 121 ++++++++++++++
 peripheral/meson.build       |  13 ++
 plugins/meson.build          |  29 ++++
 profiles/meson.build         | 167 +++++++++++++++++++
 src/meson.build              |  88 +++++++++++
 src/shared/meson.build       |  85 ++++++++++
 test/meson.build             |  37 +++++
 tools/mesh/meson.build       |  16 ++
 tools/meson.build            | 261 ++++++++++++++++++++++++++++++
 tools/mpris-proxy.service.in |   2 +-
 unit/meson.build             | 126 +++++++++++++++
 25 files changed, 1751 insertions(+), 1 deletion(-)
 create mode 100644 attrib/meson.build
 create mode 100644 btio/meson.build
 create mode 100644 client/btpclient/meson.build
 create mode 100644 client/meson.build
 create mode 100644 completion/meson.build
 create mode 100644 doc/meson.build
 create mode 100644 emulator/meson.build
 create mode 100644 gdbus/meson.build
 create mode 100644 gobex/meson.build
 create mode 100644 lib/meson.build
 create mode 100644 mesh/meson.build
 create mode 100644 meson.build
 create mode 100644 meson_options.txt
 create mode 100644 monitor/meson.build
 create mode 100644 obexd/meson.build
 create mode 100644 peripheral/meson.build
 create mode 100644 plugins/meson.build
 create mode 100644 profiles/meson.build
 create mode 100644 src/meson.build
 create mode 100644 src/shared/meson.build
 create mode 100644 test/meson.build
 create mode 100644 tools/mesh/meson.build
 create mode 100644 tools/meson.build
 create mode 100644 unit/meson.build

diff --git a/attrib/meson.build b/attrib/meson.build
new file mode 100644
index 000000000000..0b7bc97933db
--- /dev/null
+++ b/attrib/meson.build
@@ -0,0 +1,17 @@
+attrib_sources = files(
+  'att.c',
+  'gatt.c',
+  'gattrib.c'
+)
+
+gatttool_sources = files(
+  'gatttool.c',
+  'att.c',
+  'gatt.c',
+  'gattrib.c',
+  'interactive.c',
+  'utils.c',
+  '../client/display.c',
+  '../btio/btio.c',
+  '../src/log.c'
+)
diff --git a/btio/meson.build b/btio/meson.build
new file mode 100644
index 000000000000..46ae5691f1d8
--- /dev/null
+++ b/btio/meson.build
@@ -0,0 +1 @@
+btio_sources = files('btio.c')
diff --git a/client/btpclient/meson.build b/client/btpclient/meson.build
new file mode 100644
index 000000000000..6c5ae11b8dd2
--- /dev/null
+++ b/client/btpclient/meson.build
@@ -0,0 +1,19 @@
+executable('btpclient',
+  sources: [
+    'btpclient.c',
+    'core.c',
+    'gap.c',
+    'gatt.c',
+    '../../src/shared/btp.c' ],
+  include_directories: '..',
+  dependencies: [ libbluetooth_internal_dep, libshared_ell_dep ],
+)
+executable('btpclientctl',
+  sources: [ 'btpclientctl.c', '../../client/display.c' ],
+  dependencies: [
+    libshared_mainloop_dep,
+    libshared_glib_dep,
+    libbluetooth_internal_dep,
+    readline_dep
+  ]
+)
diff --git a/client/meson.build b/client/meson.build
new file mode 100644
index 000000000000..a7642d13fd8b
--- /dev/null
+++ b/client/meson.build
@@ -0,0 +1,33 @@
+if get_option('client').enabled()
+  executable('bluetoothctl',
+    sources: [
+      'main.c',
+      'print.c',
+      'display.c',
+      'agent.c',
+      'advertising.c',
+      'adv_monitor.c',
+      'gatt.c',
+      'admin.c',
+      'player.c',
+      'mgmt.c',
+      'assistant.c',
+      'hci.c',
+      'telephony.c'
+      ],
+    dependencies: [
+      libbluetooth_internal_dep,
+      libgdbus_internal_dep,
+      libshared_glib_dep,
+      glib_dep,
+      udevlib_dep,
+      dbus_dep,
+      readline_dep
+      ],
+    install: true,
+  )
+endif
+
+if get_option('btpclient').allowed() and ell_dep.found()
+  subdir('btpclient')
+endif
diff --git a/completion/meson.build b/completion/meson.build
new file mode 100644
index 000000000000..24b71518c67d
--- /dev/null
+++ b/completion/meson.build
@@ -0,0 +1,3 @@
+install_data('zsh/_bluetoothctl',
+  install_dir: zshcompletiondir
+)
diff --git a/doc/meson.build b/doc/meson.build
new file mode 100644
index 000000000000..3d5ccd7d3034
--- /dev/null
+++ b/doc/meson.build
@@ -0,0 +1,172 @@
+man_pages = [
+  'hci.7',
+  'iso.7',
+  'mgmt.7',
+  'l2cap.7',
+  'rfcomm.7',
+  'sco.7',
+  'btsnoop.7',
+  'org.bluez.Adapter.5',
+  'org.bluez.Bearer.LE.5',
+  'org.bluez.Bearer.BREDR.5',
+  'org.bluez.Device.5',
+  'org.bluez.DeviceSet.5',
+  'org.bluez.AgentManager.5',
+  'org.bluez.Agent.5',
+  'org.bluez.ProfileManager.5',
+  'org.bluez.Profile.5',
+  'org.bluez.NetworkServer.5',
+  'org.bluez.Network.5',
+  'org.bluez.Input.5',
+  'org.bluez.BatteryProviderManager.5',
+  'org.bluez.BatteryProvider.5',
+  'org.bluez.Battery.5',
+  'org.bluez.AdminPolicySet.5',
+  'org.bluez.AdminPolicyStatus.5',
+  'org.bluez.Media.5',
+  'org.bluez.MediaControl.5',
+  'org.bluez.MediaPlayer.5',
+  'org.bluez.MediaFolder.5',
+  'org.bluez.MediaItem.5',
+  'org.bluez.MediaEndpoint.5',
+  'org.bluez.MediaTransport.5',
+  'org.bluez.MediaAssistant.5',
+  'org.bluez.GattManager.5',
+  'org.bluez.GattProfile.5',
+  'org.bluez.GattService.5',
+  'org.bluez.GattCharacteristic.5',
+  'org.bluez.GattDescriptor.5',
+  'org.bluez.LEAdvertisingManager.5',
+  'org.bluez.LEAdvertisement.5',
+  'org.bluez.AdvertisementMonitorManager.5',
+  'org.bluez.AdvertisementMonitor.5',
+  'org.bluez.Telephony.5',
+  'org.bluez.Call.5',
+]
+
+obex_man_pages = [
+  'org.bluez.obex.Client.5',
+  'org.bluez.obex.Session.5',
+  'org.bluez.obex.Transfer.5',
+  'org.bluez.obex.ObjectPush.5',
+  'org.bluez.obex.FileTransfer.5',
+  'org.bluez.obex.Synchronization.5',
+  'org.bluez.obex.PhonebookAccess.5',
+  'org.bluez.obex.MessageAccess.5',
+  'org.bluez.obex.Message.5',
+  'org.bluez.obex.AgentManager.5',
+  'org.bluez.obex.Agent.5',
+  'org.bluez.obex.Image.5',
+]
+
+if get_option('obex').enabled()
+  man_pages += obex_man_pages
+endif
+
+monitor_man_pages = [ 'btmon.1' ]
+
+if get_option('monitor').allowed()
+  man_pages += monitor_man_pages
+endif
+
+tools_man_pages = [
+  'rctest.1',
+  'l2ping.1',
+  'btattach.1',
+  'isotest.1',
+  'bdaddr.1',
+]
+
+noinst_tools_man_pages = [ 'btmgmt.1' ]
+
+if get_option('tools').allowed()
+  man_pages += tools_man_pages
+  man_pages += noinst_tools_man_pages
+endif
+
+deprecated_tools_man_pages = [
+  'hciattach.1',
+  'hciconfig.1',
+  'hcitool.1',
+  'rfcomm.1',
+  'sdptool.1',
+  'ciptool.1',
+]
+
+if get_option('deprecated').allowed()
+  man_pages += deprecated_tools_man_pages
+endif
+
+hid2hci_man_pages = [ 'hid2hci.1' ]
+
+if get_option('hid2hci').enabled()
+  man_pages += 'hid2hci.1'
+endif
+
+client_man_pages = [
+  'bluetoothctl.1',
+  'bluetoothctl-mgmt.1',
+  'bluetoothctl-monitor.1',
+  'bluetoothctl-admin.1',
+  'bluetoothctl-advertise.1',
+  'bluetoothctl-endpoint.1',
+  'bluetoothctl-gatt.1',
+  'bluetoothctl-player.1',
+  'bluetoothctl-scan.1',
+  'bluetoothctl-transport.1',
+  'bluetoothctl-assistant.1',
+  'bluetoothctl-hci.1',
+  'bluetoothctl-telephony.1'
+]
+
+if get_option('client').enabled()
+  man_pages += client_man_pages
+endif
+
+if rst2man.found()
+  foreach man_page: man_pages
+    man_page_name = man_page.substring(0, -2)
+    man_section = man_page.substring(-1)
+    if man_section.to_int() == 7
+      man_page_name = man_page_name + '-protocol'
+    endif
+    install = not noinst_tools_man_pages.contains(man_page)
+    custom_target(man_page,
+      input: man_page_name + '.rst',
+      output: man_page,
+      command: rst2man_command,
+      install: install,
+      install_dir: get_option('mandir') / 'man@0@'.format(man_section)
+    )
+  endforeach
+endif
+
+bluetoothd_rst = configure_file(
+  input: 'bluetoothd.rst.in',
+  output: 'bluetoothd.rst',
+  configuration: man_conf
+)
+if rst2man.found()
+  custom_target(
+    input: bluetoothd_rst,
+    output: 'bluetoothd.8',
+    command: rst2man_command,
+    install: true,
+    install_dir: get_option('mandir') / 'man8'
+  )
+endif
+
+bluetooth_meshd_rst = configure_file(
+  input: 'bluetooth-meshd.rst.in',
+  output: 'bluetooth-meshd.rst',
+  configuration: man_conf
+)
+if get_option('mesh').enabled() and rst2man.found()
+  custom_target(
+    input: bluetooth_meshd_rst,
+    output: 'bluetooth-meshd.8',
+    command: rst2man_command,
+    install: true,
+    install_dir: get_option('mandir') / 'man8'
+  )
+endif
diff --git a/emulator/meson.build b/emulator/meson.build
new file mode 100644
index 000000000000..40544567e4e4
--- /dev/null
+++ b/emulator/meson.build
@@ -0,0 +1,34 @@
+emulator_sources = files(
+  'hciemu.c',
+  'vhci.c',
+  'btdev.c',
+  'bthost.c',
+  'smp.c',
+)
+
+if get_option('tests').allowed()
+  executable('btvirt',
+    sources: [ 'main.c',
+      'serial.c',
+      'server.c',
+      'vhci.c',
+      'btdev.c',
+      'bthost.c',
+      'smp.c',
+      'phy.c',
+      'le.c' ],
+    dependencies: [ libbluetooth_internal_dep, libshared_mainloop_dep ]
+  )
+
+  executable('b1ee',
+    sources: 'b1ee.c',
+    include_directories: '../lib',
+    dependencies: libshared_mainloop_dep
+  )
+
+  executable('hfp',
+    sources: 'hfp.c',
+    dependencies: libshared_mainloop_dep
+  )
+endif
+
diff --git a/gdbus/meson.build b/gdbus/meson.build
new file mode 100644
index 000000000000..dc7f2887a60f
--- /dev/null
+++ b/gdbus/meson.build
@@ -0,0 +1,19 @@
+gdbus_sources = [
+  'gdbus.h',
+  'mainloop.c',
+  'watch.c',
+  'object.c',
+  'client.c',
+  'polkit.c'
+]
+
+libgdbus_internal = static_library('gdbus_internal',
+  sources: gdbus_sources,
+  dependencies: [ glib_dep, dbus_dep ],
+  install: false
+)
+
+libgdbus_internal_dep = declare_dependency(
+  dependencies: dbus_dep,
+  link_with: libgdbus_internal
+)
diff --git a/gobex/meson.build b/gobex/meson.build
new file mode 100644
index 000000000000..0ff6238a7b8c
--- /dev/null
+++ b/gobex/meson.build
@@ -0,0 +1,8 @@
+gobex_sources = files(
+  'gobex.c',
+  'gobex-defs.c',
+  'gobex-packet.c',
+  'gobex-header.c',
+  'gobex-transfer.c',
+  'gobex-apparam.c'
+)
diff --git a/lib/meson.build b/lib/meson.build
new file mode 100644
index 000000000000..e8d11f342bd8
--- /dev/null
+++ b/lib/meson.build
@@ -0,0 +1,53 @@
+lib_sources = [
+  'bluetooth/bluetooth.c',
+  'bluetooth/hci.c',
+  'bluetooth/sdp.c',
+]
+
+lib_extra_sources = [
+  'bluetooth/uuid.c'
+]
+
+lib_headers = [
+  'bluetooth/bluetooth.h',
+  'bluetooth/hci.h',
+  'bluetooth/hci_lib.h',
+  'bluetooth/sco.h',
+  'bluetooth/l2cap.h',
+  'bluetooth/sdp.h',
+  'bluetooth/sdp_lib.h',
+  'bluetooth/rfcomm.h',
+  'bluetooth/bnep.h',
+  'bluetooth/cmtp.h',
+  'bluetooth/hidp.h'
+  ]
+
+if get_option('library').allowed()
+  lib_bluetooth = shared_library('bluetooth',
+    sources: lib_headers + lib_sources,
+    version: '3.19.15',
+    install: true
+  )
+
+  install_headers(lib_headers,
+    subdir: 'bluetooth'
+  )
+
+  pkgconfig = import('pkgconfig')
+  pkgconfig.generate(
+    name: 'bluez',
+    description: 'Bluetooth protocol stack for Linux',
+    version: meson.project_version(),
+    libraries: lib_bluetooth,
+  )
+endif
+
+libbluetooth_internal = static_library('bluetooth_internal',
+  sources: lib_headers + lib_sources + lib_extra_sources,
+  install: false
+)
+
+libbluetooth_internal_dep = declare_dependency(
+  link_with: libbluetooth_internal,
+  include_directories: '.'
+)
diff --git a/mesh/meson.build b/mesh/meson.build
new file mode 100644
index 000000000000..cb4c9012300d
--- /dev/null
+++ b/mesh/meson.build
@@ -0,0 +1,60 @@
+if get_option('datafiles').allowed()
+  install_data('bluetooth-mesh.conf',
+    install_dir: dbusdir)
+  install_data('mesh-main.conf',
+    install_dir: configdir)
+endif
+
+if systemd_dep.found()
+  configure_file(
+    input: 'bluetooth-mesh.service.in',
+    output: 'bluetooth-mesh.service',
+    install: true,
+    install_dir: systemdsystemunitdir,
+    configuration: service_conf
+  )
+  install_data('org.bluez.mesh.service',
+    install_dir: dbussystembusdir
+  )
+endif
+
+executable('bluetooth-meshd',
+  sources: [
+    builtin_h,
+    'main.c',
+    'mesh.c',
+    'net-keys.c',
+    'mesh-io.c',
+    'mesh-mgmt.c',
+    'mesh-io-unit.c',
+    'mesh-io-mgmt.c',
+    'mesh-io-generic.c',
+    'net.c',
+    'crypto.c',
+    'friend.c',
+    'appkey.c',
+    'node.c',
+    'model.c',
+    'cfgmod-server.c',
+    'remprv-server.c',
+    'mesh-config-json.c',
+    'util.c',
+    'dbus.c',
+    'agent.c',
+    'prov-acceptor.c',
+    'prov-initiator.c',
+    'manager.c',
+    'pb-adv.c',
+    'keyring.c',
+    'rpl.c',
+    'prvbeac-server.c'
+  ],
+  include_directories: '../lib',
+  dependencies: [ libshared_ell_dep, jsonc_dep ],
+  c_args: [
+    '-DCONFIGDIR="@0@"'.format(configdir),
+    '-DMESH_STORAGEDIR="@0@"'.format(meshstoragedir)
+  ],
+  install: true,
+  install_dir: pkglibexecdir
+)
diff --git a/meson.build b/meson.build
new file mode 100644
index 000000000000..d3c43d79141f
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,299 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+project(
+  'bluez', 'c',
+  version: '5.86',
+  license: 'LGPL-2.1-or-later AND GPL-2.0-or-later',
+  meson_version: '>= 1.3.0'
+)
+
+datadir = get_option('datadir')
+storagedir = '/' / get_option('localstatedir') / 'lib/bluetooth'
+configdir = get_option('sysconfdir') / 'bluetooth'
+pkglibdir = get_option('libdir') / 'bluetooth'
+pkglibexecdir = get_option('libexecdir') / 'bluetooth'
+meshstoragedir = storagedir / 'mesh'
+
+fs = import('fs')
+service_conf = configuration_data()
+if not fs.is_absolute(pkglibexecdir)
+  pkglibexecdir = get_option('prefix') / pkglibexecdir
+endif
+service_conf.set('PKGLIBEXECDIR', pkglibexecdir)
+
+add_project_arguments('-DHAVE_CONFIG_H', language: 'c')
+config_h = configuration_data()
+
+cc = meson.get_compiler('c')
+
+required_funcs = [
+  'explicit_bzero',
+  'getrandom',
+  'rawmemchr',
+  'signalfd',
+]
+
+foreach func: required_funcs
+  cc.has_function(func, required: true)
+endforeach
+
+required_lib_funcs = [
+  [ 'clock_gettime', 'rt' ],
+  [ 'pthread_create', 'pthread' ],
+  [ 'dlopen', 'dl' ]
+]
+
+foreach func: required_lib_funcs
+  dep = cc.find_library(func[1], required: true)
+  cc.has_function(func[0], dependencies: dep, required: true)
+endforeach
+
+required_headers = [
+  'stdarg.h',
+  'stdio.h',
+  'string.h',
+  'linux/types.h',
+  'linux/if_alg.h',
+  'linux/uinput.h',
+  'linux/uhid.h',
+  'sys/random.h'
+]
+
+foreach header: required_headers
+  cc.has_header(header, required: true)
+endforeach
+
+# basename may be only available in libgen.h with the POSIX behavior,
+# not desired here
+config_h.set10('HAVE_DECL_BASENAME',
+  cc.has_function('basename', prefix: '#define _GNU_SOURCE 1\n#include <string.h>'))
+
+# To combine with -Wl,--gc-sections
+add_project_arguments([ '-ffunction-sections', '-fdata-sections' ] , language: 'c')
+
+# Maintainer mode
+if get_option('buildtype').contains('debug')
+  test_cflags = [
+    '-Wno-unused-parameter',
+    '-Wno-missing-field-initializers',
+    '-Wdeclaration-after-statement',
+    '-Wmissing-declarations',
+    '-Wredundant-decls',
+    '-Wcast-align',
+    '-Wswitch-enum',
+    '-Wformat',
+    '-Wformat-security',
+    '-Wstringop-overflow',
+  ]
+
+  if get_option('buildtype') == 'debugoptimized'
+    test_cflags += '-D_FORTIFY_SOURCE=3'
+  endif
+
+  common_flags = [
+    '-DG_DISABLE_DEPRECATED',
+    '-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_36',
+    '-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_36'
+  ]
+
+  common_flags += cc.get_supported_arguments(test_cflags)
+
+  add_project_arguments(common_flags, language: 'c')
+
+  config_h.set('HAVE_VALGRIND_MEMCHECK_H',
+    cc.has_header('valgrind/memcheck.h', required: get_option('valgrind')))
+endif
+
+test_cflags = [
+  '-ffunction-sections',
+  '-fdata-sections'
+]
+
+add_project_arguments(cc.get_supported_arguments(test_cflags), language: 'c')
+add_project_arguments('-I' + meson.project_build_root(), language: 'c')
+add_project_arguments('-I' + meson.project_source_root(), language: 'c')
+add_project_link_arguments('-Wl,--gc-sections', language: 'c')
+
+dummy_dep = dependency('dummy', required: false)
+glib_dep = dependency('glib-2.0', version: '>= 2.36')
+dbus_dep = dependency('dbus-1', version:'>= 1.10')
+
+cc.check_header('elfutils/libdwfl.h', required: get_option('backtrace'))
+backtrace_dep = cc.find_library('dw', required: get_option('backtrace'))
+config_h.set('HAVE_BACKTRACE_SUPPORT', backtrace_dep.found())
+
+config_h.set10('HAVE_DECL_SOF_TIMESTAMPING_TX_COMPLETION',
+  cc.has_header_symbol('linux/net_tstamp.h', 'SOF_TIMESTAMPING_TX_COMPLETION'))
+config_h.set10('HAVE_DECL_SCM_TSTAMP_COMPLETION',
+  cc.has_header_symbol('linux/errqueue.h', 'SCM_TSTAMP_COMPLETION', prefix: '#include <time.h>'))
+
+udevlib_dep = dependency('libudev', required: get_option('udev'))
+udevlib_sixaxis_dep = dependency('libudev', required: get_option('sixaxis'))
+udevlib_hid2hci_dep = dependency('libudev', required: get_option('hid2hci'))
+
+cups_dep = dependency('cups', required: get_option('cups'))
+jsonc_dep = dependency('json-c', version: '>= 0.13', required: get_option('mesh'))
+alsa_dep = dependency('alsa', required: get_option('midi'))
+ical_dep = dependency('libical', required: get_option('obex'))
+readline_dep = dependency('readline',
+  required: get_option('client').enabled() or
+    get_option('mesh').enabled())
+systemd_dep = dependency('systemd', required: get_option('systemd'))
+libsystemd_dep = dependency('libsystemd', required: get_option('systemd'))
+
+ell_args = {
+  'version': '>= 0.39',
+  'fallback' : ['ell', 'libell_dep'],
+  'default_options': [
+    'default_library=static',
+    'tests=false',
+    'cert-tests=false'
+  ]
+}
+ell_dep = dependency('ell',
+  kwargs: ell_args,
+  required: get_option('btpclient')
+)
+ell_mesh_dep = dependency('ell',
+  kwargs: ell_args,
+  required: get_option('mesh')
+)
+
+rst2man = find_program('rst2man', 'rst2man.py', required: get_option('manpages'))
+if rst2man.found()
+  rst2man_command = [
+    rst2man,
+    '--strict',
+    '--no-raw',
+    '--no-generator',
+    '--no-datestamp',
+    '@INPUT@',
+    '@OUTPUT@'
+  ]
+endif
+
+man_conf = configuration_data()
+man_conf.set('CONFIGDIR', get_option('prefix') / configdir)
+man_conf.set('MESH_STORAGEDIR', get_option('prefix') / meshstoragedir)
+
+if get_option('phonebook') == 'ebook'
+  ebook_dep = dependency('libebook-1.2', '>= 3.3', required: true)
+  edataserver_dep = dependency('libedataserver-1.2', '>= 3.3', required: true)
+else
+  ebook_dep = dummy_dep
+  edataserver_dep = dummy_dep
+endif
+
+plugindir = get_option('libdir') / 'bluetooth/plugins'
+
+dbusconfdir = get_option('dbusconfdir')
+if dbusconfdir == ''
+  dbusconfdir = dbus_dep.get_variable('datadir')
+endif
+dbusdir = dbusconfdir / 'dbus-1/system.d'
+
+dbussystembusdir = get_option('dbussystembusdir')
+if dbussystembusdir == ''
+  dbussystembusdir = dbus_dep.get_variable('system_bus_services_dir')
+endif
+
+dbussessionbusdir = get_option('dbussessionbusdir')
+if dbussessionbusdir == ''
+  dbussessionbusdir = dbus_dep.get_variable('session_bus_services_dir')
+endif
+
+service_in = configuration_data()
+service_in.set_quoted('PKGLIBEXECDIR', get_option('prefix') / pkglibexecdir)
+
+if systemd_dep.found()
+  systemdsystemunitdir = get_option('systemdsystemunitdir')
+  if systemdsystemunitdir == ''
+    systemdsystemunitdir = systemd_dep.get_variable('systemdsystemunitdir')
+  endif
+  systemduserunitdir = get_option('systemduserunitdir')
+  if systemduserunitdir == ''
+    systemduserunitdir = systemd_dep.get_variable('systemduserunitdir')
+  endif
+endif
+
+zshcompletiondir = get_option('zsh-completion-dir')
+if zshcompletiondir == ''
+  zshcompletiondir = datadir / 'zsh/site-functions'
+endif
+
+zsh_completions = fs.is_dir(zshcompletiondir)
+
+udev_dep = dependency('udev', version:'>= 196', required: udevlib_hid2hci_dep.found())
+if udev_dep.found()
+  udevdir = get_option('udevdir')
+  if udevdir == ''
+    udevdir = udev_dep.get_variable('udevdir')
+  endif
+endif
+
+if cups_dep.found()
+  cups_serverbin = get_option('cups_serverbin')
+  if cups_serverbin == ''
+    cups_serverbin = cups_dep.get_variable('cups_serverbin')
+  endif
+endif
+
+external_plugins_link_args = ''
+if get_option('external-plugins').enabled()
+  external_plugins_link_args = [
+    '-Wl,--export-dynamic',
+    '-Wl,--version-script=' + meson.project_source_root() / 'src/bluetooth.ver'
+  ]
+endif
+
+config_h.set('HAVE_A2DP', get_option('a2dp').allowed())
+config_h.set('HAVE_AVRCP', get_option('avrcp').allowed())
+config_h.set('HAVE_ASHA', get_option('asha').allowed())
+config_h.set('HAVE_VCP', get_option('vcp').allowed())
+config_h.set10('EXTERNAL_PLUGINS', get_option('external-plugins').enabled())
+config_h.set_quoted('VERSION', meson.project_version())
+
+configure_file(
+  output: 'config.h',
+  configuration: config_h
+)
+
+subdir('lib')
+subdir('gdbus')
+subdir('attrib')
+subdir('btio')
+subdir('plugins')
+subdir('profiles')
+subdir('src')
+subdir('client')
+if get_option('monitor').allowed()
+  subdir('monitor')
+endif
+subdir('emulator')
+subdir('gobex')
+subdir('tools')
+subdir('peripheral')
+if get_option('obex').enabled()
+  subdir('obexd')
+endif
+if zsh_completions
+  subdir('completion')
+endif
+if get_option('mesh').enabled()
+  subdir('mesh')
+endif
+if rst2man.found()
+  subdir('doc')
+endif
+if get_option('tests').allowed()
+  subdir('test')
+  subdir('unit')
+endif
+
+# Fix permissions on install
+install = find_program('install')
+sh = find_program('sh')
+meson.add_install_script(sh, '-c', 'install -dm755 ${DESTDIR}/' + configdir)
+meson.add_install_script(sh, '-c', 'install -dm700 ${DESTDIR}/' + storagedir)
+if get_option('mesh').enabled()
+  meson.add_install_script(sh, '-c', 'install -dm700 ${DESTDIR}/' + meshstoragedir)
+endif
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 000000000000..4a0c726c2098
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,52 @@
+option('valgrind', type: 'feature', value: 'disabled', description: 'Enable valgrind memory checks')
+option('backtrace', type: 'feature', value: 'disabled', description: 'Enable backtrace support')
+option('library', type: 'feature', value: 'enabled', description: 'Install Bluetooth library')
+option('examples', type: 'feature', value: 'auto', description: 'Enable test/example scripts')
+option('tests', type: 'feature', value: 'auto', description: 'Enable testing tools')
+option('tools', type: 'feature', value: 'enabled', description: 'Enable Bluetooth tools')
+option('monitor', type: 'feature', value: 'enabled', description: 'Enable Bluetooth monitor')
+option('client', type: 'feature', value: 'enabled', description: 'Enable command line client')
+option('systemd', type: 'feature', value: 'enabled', description: 'Enable systemd integration')
+option('udev', type: 'feature', value: 'enabled', description: 'Enable udev device support in tools')
+option('datafiles', type: 'feature', value: 'enabled', description: 'Install configuration and data files')
+option('manpages', type: 'feature', value: 'auto', description: 'Enable building of manual pages')
+option('experimental', type: 'feature', value: 'disabled', description: 'Enable experimental tools')
+option('deprecated', type: 'feature', value: 'disabled', description: 'Enable deprecated tools')
+option('external-plugins', type: 'feature', value: 'enabled', description: 'Enable support for external plugins')
+
+option('dbusconfdir', type: 'string', value: '', description: 'D-Bus configuration directory')
+option('dbussystembusdir', type: 'string', value: '', description: 'D-Bus system bus services dir')
+option('dbussessionbusdir', type:'string', value: '', description: 'D-Bus session bus services dir')
+option('systemdsystemunitdir', type: 'string', value: '', description: 'path to systemd system unit directory')
+option('systemduserunitdir', type: 'string', value: '', description: 'systemd user unit dir')
+option('zsh-completion-dir', type:'string', value: '', description: 'Path to install zsh completions')
+option('udevdir', type: 'string', value: '', description: 'udev directory')
+option('cups_serverbin', type: 'string', value: '', description: 'CUPS directory')
+
+option('nfc', type: 'feature', value: 'disabled', description: 'Enable NFC pairing')
+option('a2dp', type: 'feature', value: 'auto', description: 'Enable A2DP profile')
+option('avrcp', type: 'feature', value: 'auto', description: 'Enable AVRCP profile')
+option('network', type: 'feature', value: 'auto', description: 'Enable network profiles')
+option('hid', type: 'feature', value: 'auto', description: 'Enable HID profile')
+option('hog', type: 'feature', value: 'auto', description: 'Enable HoG profile')
+option('bap', type: 'feature', value: 'auto', description: 'Enable BAP profile')
+option('bass', type: 'feature', value: 'auto', description: 'Enable BASS service')
+option('mcp', type: 'feature', value: 'auto', description: 'Enable MCP profile')
+option('ccp', type: 'feature', value: 'auto', description: 'Enable CCP profile')
+option('vcp', type: 'feature', value: 'auto', description: 'Enable VCP profile')
+option('micp', type: 'feature', value: 'auto', description: 'Enable MICP profile')
+option('csip', type: 'feature', value: 'auto', description: 'Enable CSIP profile')
+option('asha', type: 'feature', value: 'auto', description: 'Enable ASHA support')
+option('cups', type: 'feature', value: 'auto', description: 'Enable CUPS printer support')
+option('mesh', type: 'feature', value: 'auto', description: 'Enable Mesh profile support')
+option('midi', type: 'feature', value: 'auto', description: 'Enable MIDI support')
+option('obex', type: 'feature', value: 'auto', description: 'Enable OBEX profile support')
+option('tmap', type: 'feature', value: 'auto', description: 'Enable TMAP profile support')
+option('gmap', type: 'feature', value: 'auto', description: 'Enable GMAP profile support')
+option('hfp', type: 'feature', value: 'auto', description: 'Enable HFP profile support')
+option('btpclient', type: 'feature', value: 'auto', description: 'Enable BTP client')
+option('sixaxis', type: 'feature', value: 'auto', description: 'Enable sixaxis plugin')
+option('hid2hci', type: 'feature', value: 'auto', description: 'Enable hid2hci tool')
+option('logger', type: 'feature', value: 'disabled', description: 'Enable HCI logger service')
+option('admin', type: 'feature', value: 'auto', description: 'Enable admin policy plugin')
+option('phonebook', type: 'combo', choices: [ 'dummy', 'ebook' ], value: 'dummy', description: 'Obexd phonebook plugin')
diff --git a/monitor/meson.build b/monitor/meson.build
new file mode 100644
index 000000000000..44197f2d3ba2
--- /dev/null
+++ b/monitor/meson.build
@@ -0,0 +1,37 @@
+executable('btmon',
+  sources: [
+    'main.c',
+    'display.c',
+    'hcidump.c',
+    'ellisys.c',
+    'control.c',
+    'packet.c',
+    'vendor.c',
+    'lmp.c',
+    'crc.c',
+    'll.c',
+    'l2cap.c',
+    'sdp.c',
+    'avctp.c',
+    'avdtp.c',
+    'a2dp.c',
+    'rfcomm.c',
+    'bnep.c',
+    'hwdb.c',
+    'keys.c',
+    'analyze.c',
+    'intel.c',
+    'broadcom.c',
+    'msft.c',
+    'jlink.c',
+    'att.c',
+    '../src/log.c',
+    '../src/textfile.c',
+    '../src/settings.c'
+  ],
+  c_args: [
+    '-DSTORAGEDIR="@0"'.format(storagedir)
+  ],
+  dependencies: [ libbluetooth_internal_dep, libshared_mainloop_dep, udevlib_dep, glib_dep ],
+  install: true
+)
diff --git a/obexd/meson.build b/obexd/meson.build
new file mode 100644
index 000000000000..baddafcd8873
--- /dev/null
+++ b/obexd/meson.build
@@ -0,0 +1,121 @@
+if get_option('datafiles').allowed()
+  install_data('src/obex.conf',
+    install_dir: dbusconfdir / 'dbus-1/system.d'
+  )
+endif
+
+if systemd_dep.found()
+  configure_file(
+    input: 'src/obex.service.in',
+    output: 'obex.service',
+    install: true,
+    install_dir: systemduserunitdir,
+    configuration: service_conf
+  )
+  configure_file(
+    input: 'src/org.bluez.obex.service.in',
+    output: 'org.bluez.obex.service',
+    install: true,
+    install_dir: dbussessionbusdir,
+    configuration: service_conf
+  )
+  install_symlink('dbus-org.bluez.obex.service',
+    pointing_to: dbussessionbusdir / 'org.bluez.obex.service',
+    install_dir: systemduserunitdir
+  )
+endif
+
+obexd_builtin_modules = [
+  'filesystem',
+  'bluetooth',
+  'opp',
+  'ftp',
+  'irmc',
+  'pbap',
+  'mas',
+  'mns'
+]
+
+obexd_builtin_sources = [
+  'plugins/filesystem.c',
+  'plugins/bluetooth.c',
+  'plugins/opp.c',
+  'plugins/ftp.c',
+  'plugins/irmc.c',
+  'plugins/pbap.c',
+  'plugins/vcard.c',
+  'plugins/phonebook-@0@.c'.format(get_option('phonebook')),
+  'plugins/mas.c',
+  'plugins/messages-dummy.c',
+  'client/mns.c',
+]
+
+if get_option('experimental').allowed()
+  obexd_builtin_modules += 'pcsuite'
+  obexd_builtin_sources += 'plugins/pcsuite.c'
+endif
+
+obex_plugindir = get_option('libdir') / 'obex/plugins'
+
+obexd_genbuiltin = find_program('src/genbuiltin')
+obexd_builtin_h = custom_target('obexd-builtin.h',
+  output: 'builtin.h',
+  capture: true,
+  command: [ obexd_genbuiltin, obexd_builtin_modules ]
+)
+
+executable('obexd',
+  sources: [
+    btio_sources,
+    gobex_sources,
+    obexd_builtin_sources,
+    obexd_builtin_h,
+    'src/main.c',
+    'src/plugin.c',
+    'src/log.c',
+    'src/logind.c',
+    'src/manager.c',
+    'src/obex.c',
+    'src/mimetype.c',
+    'src/service.c',
+    'src/transport.c',
+    'src/server.c',
+    'client/manager.c',
+    'client/session.c',
+    'client/bluetooth.c',
+    'client/sync.c',
+    'client/pbap.c',
+    'client/ftp.c',
+    'client/opp.c',
+    'client/map.c',
+    'client/bip.c',
+    'client/bip-common.c',
+    'client/map-event.c',
+    'client/transfer.c',
+    'client/transport.c',
+    'client/driver.c'
+  ],
+  dependencies: [
+    libbluetooth_internal_dep,
+    libgdbus_internal_dep,
+    libshared_glib_dep,
+    ical_dep,
+    dbus_dep,
+    ebook_dep,
+    edataserver_dep,
+    glib_dep,
+    libsystemd_dep.found() ? libsystemd_dep : dummy_dep
+  ],
+  c_args: [
+    '-DOBEX_PLUGIN_BUILTIN',
+    '-DPLUGINDIR="@0@"'.format(obex_plugindir),
+    '-D_FILE_OFFSET_BITS=64',
+    '-DCONFIGDIR="@0@"'.format(configdir),
+    libsystemd_dep.found() ? '-DSYSTEMD' : ''
+  ],
+  link_args: external_plugins_link_args,
+  install: true,
+  install_dir: pkglibexecdir,
+)
+
+
diff --git a/peripheral/meson.build b/peripheral/meson.build
new file mode 100644
index 000000000000..6a9fdb4cbc66
--- /dev/null
+++ b/peripheral/meson.build
@@ -0,0 +1,13 @@
+if get_option('tests').allowed()
+  executable('btsensor',
+    sources: [
+      'main.c',
+      'efivars.c',
+      'attach.c',
+      'log.c',
+      'gap.c',
+      'gatt.c',
+    ],
+    dependencies: [ libshared_mainloop_dep, libbluetooth_internal_dep ]
+  )
+endif
diff --git a/plugins/meson.build b/plugins/meson.build
new file mode 100644
index 000000000000..d02fb8530af0
--- /dev/null
+++ b/plugins/meson.build
@@ -0,0 +1,29 @@
+builtin_modules = [
+  'hostname',
+  'autopair',
+  'policy',
+]
+
+builtin_sources = files(
+  'hostname.c',
+  'autopair.c',
+  'policy.c'
+)
+
+builtin_deps = []
+
+if get_option('admin').allowed()
+  builtin_modules += 'admin'
+  builtin_sources += files('admin.c')
+endif
+
+if get_option('nfc').allowed()
+  builtin_modules += 'neard'
+  builtin_sources += files('neard.c')
+endif
+
+if udevlib_sixaxis_dep.found()
+  builtin_modules += 'sixaxis'
+  builtin_sources += files('sixaxis.c')
+  builtin_deps += udevlib_sixaxis_dep
+endif
diff --git a/profiles/meson.build b/profiles/meson.build
new file mode 100644
index 000000000000..7e33af9932d8
--- /dev/null
+++ b/profiles/meson.build
@@ -0,0 +1,167 @@
+builtin_modules += [
+  'gap',
+  'scanparam',
+  'deviceinfo',
+  'battery',
+  'rap',
+]
+
+builtin_sources += files(
+  'audio/media.c',
+  'audio/transport.c',
+  'audio/player.c',
+  'audio/avrcp-player.c',
+  'gap/gas.c',
+  'scanparam/scan.c',
+  'deviceinfo/deviceinfo.c',
+  'battery/battery.c',
+  'ranging/rap.c',
+)
+
+if get_option('a2dp').allowed()
+  builtin_modules += 'a2dp'
+  builtin_sources += files(
+    'audio/source.c',
+    'audio/sink.c',
+    'audio/a2dp.c',
+    'audio/avdtp.c'
+  )
+endif
+
+if get_option('avrcp').allowed()
+  builtin_modules += 'avrcp'
+  builtin_sources += files(
+    'audio/control.c',
+    'audio/avctp.c',
+    'audio/avctp.c',
+    'audio/avrcp.c'
+  )
+endif
+
+if get_option('network').allowed()
+  builtin_modules += 'network'
+  builtin_sources += files(
+    'network/manager.c',
+    'network/bnep.c',
+    'network/server.c',
+    'network/connection.c'
+  )
+  install_data('network/network.conf',
+    install_dir: configdir
+  )
+endif
+
+if get_option('hid').allowed()
+  builtin_modules += 'input'
+  builtin_sources += files(
+    'input/manager.c',
+    'input/server.c',
+    'input/device.c'
+  )
+
+  install_data('input/input.conf',
+    install_dir: configdir
+  )
+endif
+
+if get_option('hog').allowed()
+  builtin_modules += 'hog'
+  builtin_sources += files(
+    'input/hog.c',
+    'input/hog-lib.c',
+    'deviceinfo/dis.c',
+    'battery/bas.c',
+    'scanparam/scpp.c',
+    'input/suspend-none.c'
+  )
+endif
+
+if alsa_dep.found()
+  builtin_modules += 'midi'
+  builtin_sources += files(
+    'midi/midi.c',
+    'midi/libmidi.c'
+  )
+  builtin_deps += alsa_dep
+endif
+
+if get_option('bap').allowed()
+  builtin_modules += 'bap'
+  builtin_sources += files('audio/bap.c')
+endif
+
+if get_option('bass').allowed()
+  builtin_modules += 'bass'
+  builtin_sources += files('audio/bass.c')
+endif
+
+if get_option('mcp').allowed()
+  builtin_modules += 'mcp'
+  builtin_sources += files('audio/mcp.c')
+endif
+
+if get_option('vcp').allowed()
+  builtin_modules += 'vcp'
+  builtin_sources += files('audio/vcp.c')
+endif
+
+if get_option('micp').allowed()
+  builtin_modules += 'micp'
+  builtin_sources += files('audio/micp.c')
+endif
+
+if get_option('ccp').allowed()
+  builtin_modules += 'ccp'
+  builtin_sources += files('audio/ccp.c')
+endif
+
+if get_option('csip').allowed()
+  builtin_modules += 'csip'
+  builtin_sources += files('audio/csip.c')
+endif
+
+if get_option('asha').allowed()
+  builtin_modules += 'asha'
+  builtin_sources += files('audio/asha.c')
+endif
+
+if get_option('tmap').allowed()
+  builtin_modules += 'tmap'
+  builtin_sources += files('audio/tmap.c')
+endif
+
+if get_option('gmap').allowed()
+  builtin_modules += 'gmap'
+  builtin_sources += files('audio/gmap.c')
+endif
+
+if get_option('hfp').allowed()
+  builtin_modules += 'hfp'
+  builtin_sources += files('audio/telephony.c', 'audio/hfp-hf.c')
+endif
+
+if get_option('tools').allowed()
+  executable('iapd',
+    sources: 'iap/main.c',
+    dependencies: [ libgdbus_internal_dep, glib_dep, dbus_dep ]
+  )
+endif
+
+if cups_dep.found()
+  executable('bluetooth',
+    sources: [
+      'cups/main.c',
+      'cups/sdp.c',
+      'cups/spp.c',
+      'cups/hcrp.c'
+    ],
+    dependencies: [
+      libbluetooth_internal_dep,
+      libgdbus_internal_dep,
+      glib_dep,
+      dbus_dep
+    ],
+    install: true,
+    install_dir: cups_serverbin / 'backend'
+  )
+endif
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 000000000000..65b7131c86b4
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,88 @@
+subdir('shared')
+
+if get_option('datafiles').allowed()
+  install_data('bluetooth.conf',
+    install_dir: dbusconfdir / 'dbus-1/system.d'
+  )
+  install_data('main.conf',
+    install_dir: configdir
+  )
+endif
+
+if systemd_dep.found()
+  configure_file(
+    input: 'bluetooth.service.in',
+    output: 'bluetooth.service',
+    install: true,
+    install_dir: systemdsystemunitdir,
+    configuration: service_conf
+  )
+
+  install_data('org.bluez.service',
+    install_dir: dbussystembusdir
+  )
+endif
+
+genbuiltin = find_program('genbuiltin')
+builtin_h = custom_target('src-builtin.h',
+  output: 'builtin.h',
+  capture: true,
+  command: [ genbuiltin, builtin_modules ]
+)
+
+executable('bluetoothd',
+  sources: [ builtin_sources,
+    attrib_sources,
+    btio_sources,
+    builtin_h,
+    'main.c',
+    'log.c',
+    'backtrace.c',
+    'rfkill.c',
+    'sdpd-server.c',
+    'sdpd-request.c',
+    'sdpd-service.c',
+    'sdpd-database.c',
+    'gatt-database.c',
+    'sdp-xml.c',
+    'sdp-client.c',
+    'textfile.c',
+    'uuid-helper.c',
+    'plugin.c',
+    'storage.c',
+    'advertising.c',
+    'agent.c',
+    'bearer.c',
+    'error.c',
+    'adapter.c',
+    'profile.c',
+    'service.c',
+    'gatt-client.c',
+    'device.c',
+    'dbus-common.c',
+    'eir.c',
+    'adv_monitor.c',
+    'battery.c',
+    'settings.c',
+    'set.c' ],
+  dependencies: [
+    libbluetooth_internal_dep,
+    libgdbus_internal_dep,
+    libshared_glib_dep,
+    backtrace_dep,
+    glib_dep,
+    dbus_dep,
+    udevlib_dep,
+    builtin_deps
+  ],
+  override_options: [ 'b_lundef=false' ],
+  c_args: [
+    '-DBLUETOOTH_PLUGIN_BUILTIN',
+    '-DPLUGINDIR="@0@"'.format(plugindir),
+    '-DSTORAGEDIR="@0@"'.format(storagedir),
+    '-DCONFIGDIR="@0@"'.format(configdir)
+  ],
+  link_args: [ '-Wl,--gc-sections', external_plugins_link_args ],
+  install: true,
+  install_dir: pkglibexecdir
+)
diff --git a/src/shared/meson.build b/src/shared/meson.build
new file mode 100644
index 000000000000..ae9a65e1b5c6
--- /dev/null
+++ b/src/shared/meson.build
@@ -0,0 +1,85 @@
+shared_sources = [
+  'queue.c',
+  'util.c',
+  'mgmt.c',
+  'crypto.c',
+  'ecc.c',
+  'ringbuf.c',
+  'hci.c',
+  'hci-crypto.c',
+  'hfp.c',
+  'uhid.c',
+  'pcap.c',
+  'btsnoop.c',
+  'ad.c',
+  'att.c',
+  'gatt-helpers.c',
+  'gatt-client.c',
+  'gatt-server.c',
+  'gatt-db.c',
+  'gap.c',
+  'log.c',
+  'bap.c',
+  'bap-debug.c',
+  'mcp.c',
+  'vcp.c',
+  'micp.c',
+  'csip.c',
+  'bass.c',
+  'ccp.c',
+  'asha.c',
+  'battery.c',
+  'uinput.c',
+  'tmap.c',
+  'gmap.c',
+  'rap.c',
+]
+
+if readline_dep.found()
+  shared_sources += [ 'shell.c' ]
+endif
+
+libshared_glib = static_library('libshared-glib',
+  sources: [ shared_sources,
+    'io-glib.c',
+    'timeout-glib.c',
+    'mainloop-glib.c',
+    'mainloop-notify.c',
+    'tester.c' ],
+  include_directories: '../../lib',
+  dependencies: glib_dep
+)
+
+libshared_glib_dep = declare_dependency(
+  link_with: libshared_glib,
+  dependencies: glib_dep
+)
+
+libshared_mainloop = static_library('libshared-mainloop',
+  sources: [ shared_sources,
+    'io-mainloop.c',
+    'timeout-mainloop.c',
+    'mainloop.c',
+    'mainloop-notify.c' ],
+  include_directories: '../../lib',
+  dependencies: glib_dep
+)
+
+libshared_mainloop_dep = declare_dependency(
+  link_with: libshared_mainloop
+)
+
+if ell_dep.found()
+  libshared_ell = static_library('libshared-ell',
+    sources: [ shared_sources,
+      'io-ell.c',
+      'timeout-ell.c',
+      'mainloop-ell.c' ],
+    include_directories: [ '../..', '../../lib' ],
+    dependencies: [ glib_dep, ell_dep ]
+  )
+  libshared_ell_dep = declare_dependency(
+    link_with: libshared_ell,
+    dependencies: ell_dep
+  )
+endif
diff --git a/test/meson.build b/test/meson.build
new file mode 100644
index 000000000000..6f93cf8a36e0
--- /dev/null
+++ b/test/meson.build
@@ -0,0 +1,37 @@
+test_scripts = [
+  'bluezutils.py',
+  'dbusdef.py',
+  'monitor-bluetooth',
+  'list-devices',
+  'test-discovery',
+  'test-manager',
+  'test-adapter',
+  'test-device',
+  'simple-agent',
+  'simple-endpoint',
+  'test-network',
+  'test-profile',
+  'service-record.dtd',
+  'service-did.xml',
+  'service-spp.xml',
+  'service-opp.xml',
+  'service-ftp.xml',
+  'simple-player',
+  'test-nap',
+  'test-hfp',
+  'opp-client',
+  'ftp-client',
+  'pbap-client',
+  'map-client',
+  'example-advertisement',
+  'example-gatt-server',
+  'example-gatt-client',
+  'test-gatt-profile',
+  'test-mesh',
+  'agent.py'
+]
+
+install_data(
+  sources: test_scripts,
+  install_dir: pkglibdir / 'tests'
+)
diff --git a/tools/mesh/meson.build b/tools/mesh/meson.build
new file mode 100644
index 000000000000..ae1fbca6f6b6
--- /dev/null
+++ b/tools/mesh/meson.build
@@ -0,0 +1,16 @@
+if get_option('tools').allowed() and readline_dep.found()
+  executable('mesh-cfgclient',
+    sources: [ '../mesh-cfgclient.c',
+      'cfgcli.c',
+      'keys.c',
+      'util.c',
+      'remote.c',
+      'agent.c',
+      'mesh-db.c',
+      '../../mesh/util.c',
+      '../../mesh/crypto.c',
+    ],
+    dependencies: [ libbluetooth_internal_dep, libshared_mainloop_dep, ell_dep, dbus_dep, jsonc_dep, readline_dep ],
+    install: true
+  )
+endif
diff --git a/tools/meson.build b/tools/meson.build
new file mode 100644
index 000000000000..8fc0dc1d2fb5
--- /dev/null
+++ b/tools/meson.build
@@ -0,0 +1,261 @@
+if get_option('logger').allowed()
+  executable('btmon-logger',
+    [ 'btmon-logger.c' ],
+    dependencies: libshared_mainloop_dep,
+    include_directories: '../lib/',
+    install_dir: pkglibexecdir
+  )
+
+  if systemd_dep.found()
+    configure_file(
+      input: 'bluetooth-logger.service.in',
+      output: 'bluetooth-logger.service',
+      configuration: service_in,
+      install: true,
+      install_dir: systemdsystemunitdir
+    )
+  endif
+endif
+
+executable('3dsp',
+  sources: '3dsp.c',
+  dependencies: libshared_mainloop_dep
+)
+
+tester_tools = [
+  'mgmt',
+  'mesh',
+  'l2cap',
+  'rfcomm',
+  'bnep',
+  'smp',
+  'gap',
+  'sco',
+  'userchan',
+  'iso',
+  'ioctl',
+  'hci',
+  '6lowpan',
+]
+
+if get_option('tests').allowed()
+  foreach tool: tester_tools
+    executable(tool + '-tester',
+      sources: [ tool + '-tester.c', emulator_sources ],
+      dependencies: [ libbluetooth_internal_dep, libshared_glib_dep, libgdbus_internal_dep, glib_dep ]
+    )
+  endforeach
+endif
+
+if udevlib_hid2hci_dep.found()
+  executable('hid2hci',
+    sources: 'hid2hci.c',
+    dependencies: udevlib_hid2hci_dep,
+    install: true,
+    install_dir: udevdir
+  )
+  install_data('hid2hci.rules',
+    install_dir: udevdir / 'rules.d',
+    rename: '97-hid2hci.rules'
+  )
+endif
+
+tools_names = [
+  'avinfo',
+  'avtest',
+  'scotest',
+  'hwdb',
+  'hcieventmask',
+  'btinfo',
+  'btconfig',
+  'btsnoop',
+  'btproxy',
+  'cltest',
+  'oobtest',
+  'advtest',
+  'seq2bseq',
+  'nokfw',
+  'rtlfw',
+  'create-image',
+  'eddystone',
+  'ibeacon',
+]
+
+inst_tools_names = [
+  'rctest',
+  'l2test',
+  'l2ping',
+  'bluemoon',
+  'hex2hcd',
+  'mpris-proxy',
+  'btattach',
+  'isotest'
+]
+
+if get_option('tools').allowed()
+  foreach tool: tools_names
+    executable(tool,
+      sources: tool + '.c',
+      dependencies: [ libbluetooth_internal_dep, libshared_mainloop_dep ]
+    )
+  endforeach
+
+  foreach tool: inst_tools_names
+    executable(tool,
+      sources: tool + '.c',
+      dependencies: [ libbluetooth_internal_dep, libshared_mainloop_dep, libgdbus_internal_dep, dbus_dep, glib_dep ],
+      install: true
+    )
+  endforeach
+
+  if udevlib_dep.found()
+    executable('bdaddr',
+      sources: [ 'bdaddr.c', '../src/oui.c' ],
+      dependencies: [ libbluetooth_internal_dep, udevlib_dep ]
+    )
+  endif
+  executable('btiotest',
+    sources: [ 'btiotest.c', btio_sources ],
+    dependencies: [ libbluetooth_internal_dep, glib_dep ]
+  )
+  executable('bneptest',
+    sources: [ 'bneptest.c', btio_sources, '../src/log.c', '../profiles/network/bnep.c' ],
+    dependencies: [ libbluetooth_internal_dep, libshared_mainloop_dep, glib_dep ]
+  )
+  executable('btgatt-client',
+    sources: [ 'btgatt-client.c', '../src/uuid-helper.c' ],
+    dependencies: [ libbluetooth_internal_dep, libshared_mainloop_dep ]
+  )
+  executable('btgatt-server',
+    sources: [ 'btgatt-server.c', '../src/uuid-helper.c' ],
+    dependencies: [ libbluetooth_internal_dep, libshared_mainloop_dep ]
+  )
+  if udevlib_dep.found()
+    executable('gatt-service',
+      sources: 'gatt-service.c',
+      dependencies: [ libbluetooth_internal_dep, libshared_mainloop_dep, glib_dep, libgdbus_internal_dep, udevlib_dep ]
+    )
+  endif
+  executable('test-runner',
+    sources: 'test-runner.c',
+    dependencies: [ libbluetooth_internal_dep ]
+  )
+
+  if readline_dep.found()
+    executable('btmgmt',
+      sources: [ 'btmgmt.c', '../src/uuid-helper.c', '../client/display.c', '../client/mgmt.c' ],
+      dependencies: [ libbluetooth_internal_dep, libshared_glib_dep, readline_dep ]
+    )
+    executable('obex-client-tool',
+      sources: [ 'obex-client-tool.c', gobex_sources, btio_sources ],
+      dependencies: [ libbluetooth_internal_dep, libshared_glib_dep, readline_dep, glib_dep ]
+    )
+    executable('obex-server-tool',
+      sources: [ 'obex-server-tool.c', gobex_sources, btio_sources ],
+      dependencies: [ libbluetooth_internal_dep, libshared_glib_dep, glib_dep ]
+    )
+    executable('bluetooth-player',
+      sources: [ 'bluetooth-player.c', '../client/print.c', '../client/player.c' ],
+      dependencies: [ libbluetooth_internal_dep, libshared_glib_dep, libgdbus_internal_dep, readline_dep ]
+    )
+    executable('obexctl',
+      sources: [ 'obexctl.c' ],
+      dependencies: [ libbluetooth_internal_dep, libshared_glib_dep, libgdbus_internal_dep, readline_dep ]
+    )
+    if get_option('mesh').enabled()
+      executable('mesh-cfgtest',
+        sources: 'mesh-cfgtest.c',
+        dependencies: [ libbluetooth_internal_dep, dbus_dep, ell_dep ],
+        include_directories: '..',
+        install: true
+      )
+      if get_option('deprecated').allowed()
+        executable('meshctl',
+          sources: [
+            'meshctl.c',
+            'mesh/agent.c',
+            'mesh-gatt/node.c',
+            'mesh-gatt/gatt.c',
+            'mesh-gatt/crypto.c',
+            'mesh-gatt/net.c',
+            'mesh-gatt/prov.c',
+            'mesh-gatt/util.c',
+            'mesh-gatt/prov-db.c',
+            'mesh-gatt/config-client.c',
+            'mesh-gatt/config-server.c',
+            'mesh-gatt/onoff-model.c'
+            ],
+          dependencies: [
+            libgdbus_internal_dep,
+            libshared_glib_dep,
+            libbluetooth_internal_dep,
+            glib_dep,
+            dbus_dep,
+            jsonc_dep,
+            readline_dep
+            ],
+          install: true
+        )
+      endif
+    endif
+  endif
+
+  if get_option('deprecated').allowed()
+    executable('gatttool',
+      sources: gatttool_sources,
+      dependencies: [ libbluetooth_internal_dep, libshared_glib_dep, readline_dep ],
+    )
+    executable('hciattach',
+      sources: [
+        'hciattach.c',
+        'hciattach_st.c',
+        'hciattach_ti.c',
+        'hciattach_tialt.c',
+        'hciattach_ath3k.c',
+        'hciattach_qualcomm.c',
+        'hciattach_intel.c',
+        'hciattach_bcm43xx.c'
+      ],
+      dependencies: libbluetooth_internal_dep,
+      install: true
+    )
+    if udevlib_dep.found()
+      executable('hcitool',
+        sources: [ 'hcitool.c', '../src/oui.c' ],
+        dependencies: [ libbluetooth_internal_dep, udevlib_dep ],
+        install: true
+      )
+    endif
+    executable('sdptool',
+      sources: [ 'sdptool.c', '../src/sdp-xml.c' ],
+      dependencies: [ libbluetooth_internal_dep, glib_dep ],
+      install: true
+    )
+    deprecated_tools = [
+      'ciptool',
+      'hciconfig',
+      'rfcomm'
+    ]
+    foreach tool: deprecated_tools
+      executable(tool,
+        sources: tool + '.c',
+        dependencies: libbluetooth_internal_dep,
+        install: true
+      )
+    endforeach
+  endif
+
+  if systemd_dep.found()
+    configure_file(
+      input: 'mpris-proxy.service.in',
+      output: 'mpris-proxy.service',
+      configuration: service_in,
+      install: true,
+      install_dir: systemduserunitdir
+    )
+  endif
+endif
+
+if get_option('mesh').enabled()
+  subdir('mesh')
+endif
diff --git a/tools/mpris-proxy.service.in b/tools/mpris-proxy.service.in
index 6ae56c6720b9..a55f0254014a 100644
--- a/tools/mpris-proxy.service.in
+++ b/tools/mpris-proxy.service.in
@@ -8,7 +8,7 @@ ConditionUser=!@system
 
 [Service]
 Type=simple
-ExecStart=@PKGBINDIR@/mpris-proxy
+ExecStart=@PKGLIBEXECDIR@/mpris-proxy
 
 [Install]
 WantedBy=default.target
diff --git a/unit/meson.build b/unit/meson.build
new file mode 100644
index 000000000000..c8198574147d
--- /dev/null
+++ b/unit/meson.build
@@ -0,0 +1,126 @@
+
+tests1 = [
+  [ 'test-tester.c' ],
+  [ 'test-uuid.c' ],
+  [ 'test-eir.c', '../src/eir.c', '../src/uuid-helper.c' ],
+  [ 'test-sdp.c', '../src/sdpd-database.c', '../src/log.c', '../src/sdpd-service.c', '../src/sdpd-request.c' ],
+  [ 'test-avrcp.c', '../src/log.c', 'avctp.c', 'avrcp-lib.c' ],
+  [ 'test-lib.c' ],
+  [ 'test-gatt.c' ],
+  [ 'test-bap.c' ],
+  [ 'test-micp.c' ],
+  [ 'test-bass.c' ],
+  [ 'test-vcp.c' ],
+  [ 'test-battery.c' ],
+  [ 'test-tmap.c' ],
+  [ 'test-gmap.c' ],
+  [ 'test-rap.c' ],
+  [ 'test-mcp.c' ],
+  [ 'test-hog.c',
+    btio_sources,
+    '../profiles/input/hog-lib.c',
+    '../profiles/scanparam/scpp.c',
+    '../profiles/battery/bas.c',
+    '../profiles/deviceinfo/dis.c',
+    '../src/log.c',
+    '../attrib/att.c',
+    '../attrib/gatt.c',
+    '../attrib/gattrib.c'
+  ],
+]
+
+foreach t: tests1
+  test_name = t[0].substring(0, -2)
+  exe = executable(test_name,
+    sources: t,
+    dependencies: [ libshared_glib_dep, libbluetooth_internal_dep ],
+  )
+  test(test_name, exe)
+endforeach
+
+tests2 = [
+  [ 'test-textfile.c', '../src/textfile.c' ],
+  [ 'test-crc.c', '../monitor/crc.c' ],
+  [ 'test-crypto.c' ],
+  [ 'test-ecc.c' ],
+  [ 'test-ringbuf.c' ],
+  [ 'test-queue.c' ],
+  [ 'test-mgmt.c' ],
+  [ 'test-uhid.c' ],
+  [ 'test-hfp.c' ],
+  [ 'test-avdtp.c', '../src/log.c', 'avdtp.c' ],
+  [ 'test-avctp.c', '../src/log.c', 'avctp.c' ],
+  [ 'test-util.c' ],
+]
+
+if ical_dep.found()
+  tests2 += [
+    [ 'test-gobex.c', 'util.c', gobex_sources ],
+    [ 'test-gobex-packet.c', 'util.c', gobex_sources ],
+    [ 'test-gobex-header.c', 'util.c', gobex_sources ],
+    [ 'test-gobex-transfer.c', 'util.c', gobex_sources ],
+    [ 'test-gobex-apparam.c', 'util.c', gobex_sources ],
+  ]
+endif
+
+foreach t: tests2
+  test_name = t[0].substring(0, -2)
+  exe = executable(test_name,
+    sources: t,
+    c_args: [ '-DSTORAGEDIR="@0"'.format(storagedir) ],
+    include_directories: '../lib',
+    dependencies: libshared_glib_dep
+  )
+  test(test_name, exe)
+endforeach
+
+exe = executable('test-gdbus-client',
+  sources: 'test-gdbus-client.c',
+  dependencies: [ libgdbus_internal_dep, libshared_glib_dep ]
+)
+dbus_run_session = find_program('dbus-run-session')
+if dbus_run_session.found()
+  test('test-gdbus-client', dbus_run_session, args: [ '--', exe ])
+else
+  test('test-gdbus-client', exe)
+endif
+
+exe = executable('test-gattrib',
+  sources: [ 'test-gattrib.c', '../attrib/gattrib.c', btio_sources, '../src/log.c' ],
+  dependencies: [ libshared_glib_dep, libbluetooth_internal_dep ]
+)
+test('test-gattrib', exe)
+
+exe = executable('test-profile',
+  sources: [ 'test-profile.c', 'btd.c', '../src/profile.c' ],
+  dependencies: [ dbus_dep, libshared_glib_dep, libbluetooth_internal_dep ]
+)
+test('test-profile', exe)
+
+if alsa_dep.found()
+  exe = executable('test-midi',
+    sources: [ 'test-midi.c', '../profiles/midi/libmidi.c' ],
+    c_args: [ '-DMIDI_TEST' ],
+    dependencies: [ libshared_glib_dep, alsa_dep ]
+  )
+  test('test-midi', exe)
+endif
+
+if get_option('mesh').enabled()
+  exe = executable('test-mesh-crypto',
+    sources: [ 'test-mesh-crypto.c' ],
+    dependencies: [ ell_dep ]
+  )
+  test('test-mesh-crypto', exe)
+endif
+
+add_test_setup(
+  'valgrind',
+  exe_wrapper: [
+    'valgrind', '--quiet', '--leak-check=full', '--trace-children=yes',
+    '--suppressions=@0@'.format(meson.current_source_dir() / 'valgrind.supp'),
+    '--num-callers=30', '--show-reachable=no', '--error-exitcode=1',
+  ],
+  # default timeout in meson is 30s
+  timeout_multiplier: 4,
+)
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 03/15] build: Separate systemd and libsystemd dependencies
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

One is to install systemd service files, the other to enable deeper
logind integration into obexd.
---
 meson.build       | 3 ++-
 meson_options.txt | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/meson.build b/meson.build
index d3c43d79141f..0b5cc0b7ce69 100644
--- a/meson.build
+++ b/meson.build
@@ -138,7 +138,8 @@ readline_dep = dependency('readline',
   required: get_option('client').enabled() or
     get_option('mesh').enabled())
 systemd_dep = dependency('systemd', required: get_option('systemd'))
-libsystemd_dep = dependency('libsystemd', required: get_option('systemd'))
+libsystemd_dep = dependency('libsystemd',
+  required: get_option('obex').enabled() and get_option('logind').enabled())
 
 ell_args = {
   'version': '>= 0.39',
diff --git a/meson_options.txt b/meson_options.txt
index 4a0c726c2098..54cfdbb0d7c1 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -6,7 +6,8 @@ option('tests', type: 'feature', value: 'auto', description: 'Enable testing too
 option('tools', type: 'feature', value: 'enabled', description: 'Enable Bluetooth tools')
 option('monitor', type: 'feature', value: 'enabled', description: 'Enable Bluetooth monitor')
 option('client', type: 'feature', value: 'enabled', description: 'Enable command line client')
-option('systemd', type: 'feature', value: 'enabled', description: 'Enable systemd integration')
+option('systemd', type: 'feature', value: 'enabled', description: 'Install systemd service files')
+option('logind', type: 'feature', value: 'enabled', description: 'Enable logind integration in obexd')
 option('udev', type: 'feature', value: 'enabled', description: 'Enable udev device support in tools')
 option('datafiles', type: 'feature', value: 'enabled', description: 'Install configuration and data files')
 option('manpages', type: 'feature', value: 'auto', description: 'Enable building of manual pages')
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 04/15] tools: Install gatttool if deprecated tools are enabled
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

Fixes:
https://bugzilla.redhat.com/show_bug.cgi?id=1141909
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=720486
---
 tools/meson.build | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tools/meson.build b/tools/meson.build
index 8fc0dc1d2fb5..5f092e7e1b5c 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -204,6 +204,7 @@ if get_option('tools').allowed()
     executable('gatttool',
       sources: gatttool_sources,
       dependencies: [ libbluetooth_internal_dep, libshared_glib_dep, readline_dep ],
+      install: true
     )
     executable('hciattach',
       sources: [
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 05/15] tools: Install avinfo tool by default
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

It's used for checking which audio codecs are supported by a Bluetooth
audio device, which is more useful now that PulseAudio/PipeWire support
alternative codecs like LDAC or aptX).

Fixes:
https://bugzilla.redhat.com/show_bug.cgi?id=1699680
---
 tools/meson.build | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/meson.build b/tools/meson.build
index 5f092e7e1b5c..1c2595a31f77 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -61,7 +61,6 @@ if udevlib_hid2hci_dep.found()
 endif
 
 tools_names = [
-  'avinfo',
   'avtest',
   'scotest',
   'hwdb',
@@ -82,6 +81,7 @@ tools_names = [
 ]
 
 inst_tools_names = [
+  'avinfo',
   'rctest',
   'l2test',
   'l2ping',
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 01/15] build: Add meson wrap for libell
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <20260424091324.3097084-1-hadess@hadess.net>

Rather than relying on libell being able to build with meson from
upstream, apply the meson build patches on top of the latest upstream
release.

The build should still fallback to the system libell if available, and
will fail to build if a system libell is not available, and no network
access is available, as in many build systems.
---
 subprojects/ell.wrap                          |  11 +
 .../0001-build-Add-meson-build-system.patch   | 922 ++++++++++++++++++
 2 files changed, 933 insertions(+)
 create mode 100644 subprojects/ell.wrap
 create mode 100644 subprojects/packagefiles/0001-build-Add-meson-build-system.patch

diff --git a/subprojects/ell.wrap b/subprojects/ell.wrap
new file mode 100644
index 000000000000..f2ff070085f2
--- /dev/null
+++ b/subprojects/ell.wrap
@@ -0,0 +1,11 @@
+[wrap-file]
+directory = ell-0.82
+source_url = https://www.kernel.org/pub/linux/libs/ell/ell-0.82.tar.xz
+source_filename = ell-0.82.tar.xz
+source_hash = 133a9273151090b4cc37676063cc00ff350a093032ba20a2aaf8b3c5fd6f8b6d
+
+# Maintained in https://github.com/hadess/ell/tree/wip/hadess/add-meson
+diff_files = 0001-build-Add-meson-build-system.patch
+
+[provide]
+dependency_names = ell
diff --git a/subprojects/packagefiles/0001-build-Add-meson-build-system.patch b/subprojects/packagefiles/0001-build-Add-meson-build-system.patch
new file mode 100644
index 000000000000..c06896515795
--- /dev/null
+++ b/subprojects/packagefiles/0001-build-Add-meson-build-system.patch
@@ -0,0 +1,922 @@
+From 84f74ee4f0e199a6e80719f9eb365c45be405d96 Mon Sep 17 00:00:00 2001
+From: Bastien Nocera <hadess@hadess.net>
+Date: Thu, 17 Jul 2025 11:31:22 +0200
+Subject: [PATCH] build: Add meson build system
+
+--enable-pie is replaced by the meson base option "b_pie":
+https://mesonbuild.com/Builtin-options.html#base-options
+
+--enable-maintainer-mode is replaced by the "debug" build-types:
+https://mesonbuild.com/Builtin-options.html#core-options
+
+--enable-debug and --disable-optimization are replaced by the
+debug build type:
+https://mesonbuild.com/Builtin-options.html#details-for-buildtype
+
+Each of the sanitisers have their own b_sanitize option:
+- asan: b_sanitize=address
+- lsan: b_sanitize=leak
+- ubsan: b_sanitize=address,undefined
+https://mesonbuild.com/Builtin-options.html#base-options
+
+--enable-coverage is replaced by the b_coverage option:
+https://mesonbuild.com/Builtin-options.html#base-options
+---
+ ell/meson.build              | 187 +++++++++++++
+ examples/meson.build         |  32 +++
+ meson.build                  |  69 +++++
+ meson_options.txt            |   5 +
+ tools/meson.build            |  18 ++
+ unit/gen-cert-expired-pem.sh |  14 +
+ unit/meson.build             | 501 +++++++++++++++++++++++++++++++++++
+ unit/xxd.sh                  |   3 +
+ 8 files changed, 829 insertions(+)
+ create mode 100644 ell/meson.build
+ create mode 100644 examples/meson.build
+ create mode 100644 meson.build
+ create mode 100644 meson_options.txt
+ create mode 100644 tools/meson.build
+ create mode 100755 unit/gen-cert-expired-pem.sh
+ create mode 100644 unit/meson.build
+ create mode 100755 unit/xxd.sh
+
+diff --git a/ell/meson.build b/ell/meson.build
+new file mode 100644
+index 000000000000..ba61a39a8cc3
+--- /dev/null
++++ b/ell/meson.build
+@@ -0,0 +1,187 @@
++lib_headers = [
++  'ell.h',
++  'util.h',
++  'test.h',
++  'strv.h',
++  'utf8.h',
++  'queue.h',
++  'hashmap.h',
++  'string.h',
++  'settings.h',
++  'main.h',
++  'idle.h',
++  'signal.h',
++  'timeout.h',
++  'io.h',
++  'ringbuf.h',
++  'log.h',
++  'checksum.h',
++  'netlink.h',
++  'genl.h',
++  'rtnl.h',
++  'dbus.h',
++  'dbus-service.h',
++  'dbus-client.h',
++  'hwdb.h',
++  'cipher.h',
++  'random.h',
++  'uintset.h',
++  'base64.h',
++  'pem.h',
++  'tls.h',
++  'uuid.h',
++  'key.h',
++  'file.h',
++  'dir.h',
++  'net.h',
++  'dhcp.h',
++  'dhcp6.h',
++  'cert.h',
++  'ecc.h',
++  'ecdh.h',
++  'time.h',
++  'gpio.h',
++  'path.h',
++  'icmp6.h',
++  'acd.h',
++  'tester.h',
++  'cleanup.h',
++  'netconfig.h',
++  'sysctl.h',
++  'minheap.h',
++  'notifylist.h'
++]
++
++lib_sources = [
++  'private.h',
++  'useful.h',
++  'missing.h',
++  'util.c',
++  'test-private.h',
++  'test.c',
++  'test-dbus.c',
++  'strv.c',
++  'utf8.c',
++  'queue.c',
++  'hashmap.c',
++  'string.c',
++  'settings.c',
++  'main-private.h',
++  'main.c',
++  'idle.c',
++  'signal.c',
++  'timeout.c',
++  'io.c',
++  'ringbuf.c',
++  'log.c',
++  'checksum.c',
++  'netlink-private.h',
++  'netlink.c',
++  'genl.c',
++  'rtnl-private.h',
++  'rtnl.c',
++  'dbus-private.h',
++  'dbus.c',
++  'dbus-message.c',
++  'dbus-util.c',
++  'dbus-service.c',
++  'dbus-client.c',
++  'dbus-name-cache.c',
++  'dbus-filter.c',
++  'gvariant-private.h',
++  'gvariant-util.c',
++  'siphash-private.h',
++  'siphash.c',
++  'hwdb.c',
++  'cipher.c',
++  'random.c',
++  'uintset.c',
++  'base64.c',
++  'asn1-private.h',
++  'pem-private.h',
++  'pem.c',
++  'tls-private.h',
++  'tls.c',
++  'tls-record.c',
++  'tls-extensions.c',
++  'tls-suites.c',
++  'uuid.c',
++  'key.c',
++  'file.c',
++  'dir.c',
++  'net-private.h',
++  'net.c',
++  'dhcp-private.h',
++  'dhcp.c',
++  'dhcp-transport.c',
++  'dhcp-lease.c',
++  'dhcp6-private.h',
++  'dhcp6.c',
++  'dhcp6-transport.c',
++  'dhcp6-lease.c',
++  'dhcp-util.c',
++  'dhcp-server.c',
++  'cert-private.h',
++  'cert.c',
++  'cert-crypto.c',
++  'ecc-private.h',
++  'ecc.h',
++  'ecc-external.c',
++  'ecc.c',
++  'ecdh.c',
++  'time.c',
++  'time-private.h',
++  'gpio.c',
++  'path.c',
++  'icmp6.c',
++  'icmp6-private.h',
++  'acd.c',
++  'tester.c',
++  'netconfig.c',
++  'sysctl.c',
++  'minheap.c',
++  'notifylist.c'
++]
++
++linux_headers = [
++  '../linux/gpio.h'
++]
++
++libell = library('ell',
++  sources: lib_headers + lib_sources + linux_headers,
++  include_directories: include_directories('..'),
++  implicit_include_directories: false,
++  gnu_symbol_visibility: 'hidden',
++  version: '0.2.0',
++  install: get_option('default_library') != 'static'
++)
++
++libell_dep = declare_dependency(include_directories: include_directories('..'),
++  link_with: libell)
++
++if get_option('default_library') != 'static'
++  install_headers(lib_headers,
++    subdir: 'ell'
++  )
++
++  pkgconfig = import('pkgconfig')
++  pkgconfig.generate(
++    name: 'ell',
++    description: 'Embedded Linux library',
++    version: meson.project_version(),
++    libraries: libell,
++    # FIXME requires meson 1.9.0
++    # license: meson.project_license()
++  )
++endif
++
++libell_private = static_library('ell_private',
++  sources: lib_headers + lib_sources + linux_headers,
++  include_directories: include_directories('..'),
++  implicit_include_directories: false,
++  install: false
++)
++
++libell_private_dep = declare_dependency(
++  link_with: libell_private
++)
+diff --git a/examples/meson.build b/examples/meson.build
+new file mode 100644
+index 000000000000..831abb58be46
+--- /dev/null
++++ b/examples/meson.build
+@@ -0,0 +1,32 @@
++examples = [
++  'dbus-service',
++  'https-client-test',
++  'https-server-test',
++  'dbus-client',
++  'dhcp-client',
++  'dhcp6-client',
++  'dhcp-server',
++  'acd-client',
++  'netconfig-test',
++]
++
++if get_option('examples')
++  foreach example: examples
++    exe = executable(example,
++      '@0@.c'.format(example),
++      include_directories: include_directories('..'),
++      dependencies: libell_private_dep,
++      install: false
++    )
++  endforeach
++endif
++
++if get_option('glib') and get_option('examples')
++  exe = executable('glib-eventloop',
++    'glib-eventloop.c',
++    include_directories: include_directories('..'),
++    dependencies: [ libell_private_dep, glib_dep ],
++    install: false
++  )
++endif
++
+diff --git a/meson.build b/meson.build
+new file mode 100644
+index 000000000000..8427d4fe6ec9
+--- /dev/null
++++ b/meson.build
+@@ -0,0 +1,69 @@
++# SPDX-License-Identifier: LGPL-2.1-or-later
++project(
++  'ell', 'c',
++  version: '0.82',
++  license: 'LGPL-2.1-or-later',
++  default_options: [
++    'buildtype=debugoptimized',
++    'default_library=shared'
++  ],
++  meson_version: '>= 1.3.0'
++)
++
++cc = meson.get_compiler('c')
++find_program('awk', required: true)
++config_h = configuration_data()
++
++checked_funcs = [
++  'explicit_bzero',
++  'rawmemchr',
++]
++
++foreach func: checked_funcs
++  config_h.set('HAVE_' + func.to_upper(), cc.has_function(func))
++endforeach
++
++required_funcs = [
++  'getrandom',
++  'signalfd',
++  'timerfd_create',
++  'epoll_create'
++]
++
++foreach func: required_funcs
++  cc.has_function(func, required: true)
++endforeach
++
++required_headers = [
++  'linux/types.h',
++  'linux/if_alg.h'
++]
++
++foreach header: required_headers
++  cc.has_header(header, required: true)
++endforeach
++
++glib_dep = dependency('glib-2.0', version: '>= 2.32', required: get_option('glib'))
++openssl = find_program('openssl', required: get_option('cert-tests'))
++sh = find_program('sh', required: get_option('cert-tests'))
++xxd = find_program('xxd', required: get_option('cert-tests'))
++
++if openssl.found()
++  r = run_command(openssl, 'list', '-providers', check: false)
++  if r.returncode() == 0
++    openssl_legacy = [ '-provider', 'legacy', '-provider', 'default' ]
++  else
++    openssl_legacy = ''
++  endif
++endif
++
++add_project_arguments('-DHAVE_CONFIG_H', language: 'c')
++configure_file(
++  output: 'config.h',
++  configuration: config_h
++)
++
++subdir('ell')
++subdir('tools')
++subdir('unit')
++subdir('examples')
+diff --git a/meson_options.txt b/meson_options.txt
+new file mode 100644
+index 000000000000..5effa15916b4
+--- /dev/null
++++ b/meson_options.txt
+@@ -0,0 +1,5 @@
++option('glib', type: 'boolean', value: true, description: 'Enable ell/glib main loop example')
++option('tests', type: 'boolean', value: true, description: 'Enable unit tests compilation')
++option('cert-tests', type: 'boolean', value: true, description: 'Enable OpenSSL cert tests')
++option('tools', type: 'boolean', value: true, description: 'Enable extra tools compilation')
++option('examples', type: 'boolean', value: true, description: 'Enable code examples compilation')
+diff --git a/tools/meson.build b/tools/meson.build
+new file mode 100644
+index 000000000000..01505f266631
+--- /dev/null
++++ b/tools/meson.build
+@@ -0,0 +1,18 @@
++tools = [
++  'certchain-verify',
++  'genl-discover',
++  'genl-watch',
++  'genl-request',
++  'gpio'
++]
++
++if get_option('tools')
++  foreach tool: tools
++      exe = executable(tool,
++        '@0@.c'.format(tool),
++        include_directories: include_directories('..'),
++        dependencies: libell_private_dep,
++        install: false
++      )
++  endforeach
++endif
+diff --git a/unit/gen-cert-expired-pem.sh b/unit/gen-cert-expired-pem.sh
+new file mode 100755
+index 000000000000..f3e0b9d28424
+--- /dev/null
++++ b/unit/gen-cert-expired-pem.sh
+@@ -0,0 +1,14 @@
++#!/bin/sh -e
++
++echo -n > cert-ca-index.txt
++OUTDIR=`mktemp -d`
++openssl ca -batch \
++	-config "$4" -name example \
++	-cert "$2" \
++	-keyfile "$3" \
++	-outdir $OUTDIR \
++	-rand_serial -extensions cert_ext \
++	-extfile "$5" -md sha256 \
++	-startdate 000101120000Z -enddate 010101120000Z \
++	-preserveDN -notext -in "$1" -out "$6"
++rm -rf $OUTDIR cert-ca-index.txt*
+diff --git a/unit/meson.build b/unit/meson.build
+new file mode 100644
+index 000000000000..be9f2655f010
+--- /dev/null
++++ b/unit/meson.build
+@@ -0,0 +1,501 @@
++tests = [
++  'test-unit',
++  'test-queue',
++  'test-hashmap',
++  'test-endian',
++  'test-string',
++  'test-utf8',
++  'test-main',
++  'test-io',
++  'test-ringbuf',
++  'test-checksum',
++  'test-settings',
++  'test-netlink',
++  'test-genl-msg',
++  'test-rtnl',
++  'test-siphash',
++  'test-cipher',
++  'test-random',
++  'test-util',
++  'test-uintset',
++  'test-base64',
++  'test-uuid',
++  'test-pbkdf2',
++  'test-dhcp',
++  'test-dhcp6',
++  'test-dir-watch',
++  'test-ecc',
++  'test-ecdh',
++  'test-time',
++  'test-path',
++  'test-net',
++  'test-sysctl',
++  'test-minheap',
++  'test-notifylist',
++  'test-hwdb',
++  'test-dbus',
++  'test-dbus-util',
++  'test-dbus-message',
++  'test-dbus-message-fds',
++  'test-dbus-properties',
++  'test-dbus-service',
++  'test-dbus-watch',
++  'test-gvariant-util',
++  'test-gvariant-message'
++]
++
++gen_headers = []
++test_pem_cert_deps = []
++
++if get_option('cert-tests')
++  tests += [
++    'test-pem',
++    'test-tls',
++    'test-key'
++  ]
++
++  cert_server_key_pem_tgt = custom_target('cert-server-key.pem',
++    output: 'cert-server-key.pem',
++    command: [ openssl, 'genrsa', '-out', '@OUTPUT@' ]
++  )
++
++  ec_cert_server_key_pem_tgt = custom_target('ec-cert-server-key.pem',
++    output: 'ec-cert-server-key.pem',
++    command: [ openssl, 'ecparam', '-out', '@OUTPUT@', '-name', 'secp384r1', '-genkey' ]
++  )
++
++  cert_server_key_pkcs8_pem_tgt = custom_target('cert-server-key-pkcs8.pem',
++    input: cert_server_key_pem_tgt,
++    output: 'cert-server-key-pkcs8.pem',
++    command: [ openssl, 'pkcs8', '-topk8', '-nocrypt', '-in', '@INPUT@', '-out', '@OUTPUT@' ]
++  )
++
++  cert_server_csr_tgt = custom_target('cert-server.csr',
++    input: cert_server_key_pem_tgt,
++    output: 'cert-server.csr',
++    command: [ openssl, 'req', '-new', '-extensions', 'cert_ext',
++      '-config', files('gencerts.cnf'),
++      '-subj', '/O=Foo Example Organization/CN=Foo Example Organization/emailAddress=foo@mail.example',
++      '-key', '@INPUT@', '-out', '@OUTPUT@' ]
++  )
++
++  ec_cert_server_csr_tgt = custom_target('ec-cert-server.csr',
++    input: ec_cert_server_key_pem_tgt,
++    output: 'ec-cert-server.csr',
++    command: [ openssl, 'req', '-new', '-extensions', 'cert_ext',
++      '-config', files('gencerts.cnf'),
++      '-subj', '/O=Foo Example Organization/CN=Foo Example Organization/emailAddress=foo@mail.example',
++      '-key', '@INPUT@', '-out', '@OUTPUT@' ]
++  )
++
++  cert_ca_key_pem_tgt = custom_target('cert-ca-key.pem',
++    output: 'cert-ca-key.pem',
++    command: [ openssl, 'genrsa', '-out', '@OUTPUT@', '2048' ]
++  )
++
++  cert_ca_pem_tgt = custom_target('cert-ca.pem',
++    input: cert_ca_key_pem_tgt,
++    output: 'cert-ca.pem',
++    command: [ openssl, 'req', '-x509', '-new', '-nodes', '-extensions', 'ca_ext',
++      '-config', files('gencerts.cnf'), '-subj', '/O=International Union of Example Organizations/CN=Certificate issuer guy/emailAddress=ca@mail.example',
++      '-key', '@INPUT@', '-sha256', '-days', '10000', '-out', '@OUTPUT@' ],
++  )
++
++  cert_server_pem_tgt = custom_target('cert-server.pem',
++    input: [ cert_server_csr_tgt, cert_ca_pem_tgt, cert_ca_key_pem_tgt ],
++    output: 'cert-server.pem',
++    command: [ openssl, 'x509', '-req', '-extensions', 'server_ext',
++      '-extfile', files('gencerts.cnf'),
++      '-in', '@INPUT0@', '-CA', '@INPUT1@',
++      '-CAkey', '@INPUT2@',
++      '-CAserial', 'cert-ca.srl',
++      '-CAcreateserial', '-sha256', '-days', '10000', '-out', '@OUTPUT@' ]
++  )
++
++  ec_cert_ca_key_pem_tgt = custom_target('ec-cert-ca-key.pem',
++    output: 'ec-cert-ca-key.pem',
++    command: [ openssl, 'ecparam', '-out', '@OUTPUT@', '-name', 'secp384r1', '-genkey' ]
++  )
++
++  ec_cert_ca_pem_tgt = custom_target('ec-cert-ca.pem',
++  input: ec_cert_ca_key_pem_tgt,
++  output: 'ec-cert-ca.pem',
++  command: [ openssl, 'req', '-x509', '-new', '-nodes', '-extensions', 'ca_ext',
++    '-config', files('gencerts.cnf'), '-subj', '/O=International Union of Example Organizations/CN=Certificate issuer guy/emailAddress=ca@mail.example',
++    '-key', '@INPUT@', '-sha256', '-days', '10000', '-out', '@OUTPUT@' ]
++  )
++
++  ec_cert_server_pem_tgt = custom_target('ec-cert-server.pem',
++    input: [ ec_cert_server_csr_tgt, ec_cert_ca_pem_tgt, ec_cert_ca_key_pem_tgt ],
++    output: 'ec-cert-server.pem',
++    command: [ openssl, 'x509', '-req', '-extensions', 'server_ext',
++      '-extfile', files('gencerts.cnf'),
++      '-in', '@INPUT0@', '-CA', '@INPUT1@',
++      '-CAkey', '@INPUT2@',
++      '-CAserial', 'cert-ca.srl',
++      '-CAcreateserial', '-sha256', '-days', '10000', '-out', '@OUTPUT@' ]
++  )
++
++  cert_client_key_pkcs1_pem_tgt = custom_target('cert-client-key-pkcs1.pem',
++    output: 'cert-client-key-pkcs1.pem',
++    command: [ openssl, 'genrsa', '-out', '@OUTPUT@' ]
++  )
++
++  cert_client_key_pkcs8_pem_tgt = custom_target('cert-client-key-pkcs8.pem',
++    input: cert_client_key_pkcs1_pem_tgt,
++    output: 'cert-client-key-pkcs8.pem',
++    command: [ openssl, 'pkcs8', '-topk8', '-nocrypt', '-in', '@INPUT@', '-out', '@OUTPUT@' ]
++  )
++
++  cert_client_key_pkcs8_md5_des_pem_tgt = custom_target('cert-client-key-pkcs8-md5-des.pem',
++    input: cert_client_key_pkcs8_pem_tgt,
++    output: 'cert-client-key-pkcs8-md5-des.pem',
++    command: [ openssl, 'pkcs8', '-in', '@INPUT@', '-out', '@OUTPUT@',
++      '-topk8', '-v1', 'PBE-MD5-DES', '-passout', 'pass:abc', openssl_legacy ]
++  )
++
++  cert_client_key_pkcs8_v2_des_pem_tgt = custom_target('cert-client-key-pkcs8-v2-des.pem',
++    input: cert_client_key_pkcs8_pem_tgt,
++    output: 'cert-client-key-pkcs8-v2-des.pem',
++    command: [ openssl, 'pkcs8', '-in', '@INPUT@', '-out', '@OUTPUT@',
++      '-topk8', '-v2', 'des-cbc', '-v2prf', 'hmacWithSHA1', '-passout', 'pass:abc',
++      openssl_legacy ]
++  )
++
++  cert_client_key_pkcs8_v2_des_ede3_pem_tgt = custom_target('cert-client-key-pkcs8-v2-des-ede3.pem',
++    input: cert_client_key_pkcs8_pem_tgt,
++    output: 'cert-client-key-pkcs8-v2-des-ede3.pem',
++    command: [ openssl, 'pkcs8', '-in', '@INPUT@', '-out', '@OUTPUT@',
++        '-topk8', '-v2', 'des-ede3-cbc', '-v2prf', 'hmacWithSHA224', '-passout', 'pass:abc' ]
++  )
++
++  cert_client_key_pkcs8_v2_aes128_pem_tgt = custom_target('cert-client-key-pkcs8-v2-aes128.pem',
++    input: cert_client_key_pkcs8_pem_tgt,
++    output: 'cert-client-key-pkcs8-v2-aes128.pem',
++    command: [ openssl, 'pkcs8', '-in', '@INPUT@', '-out', '@OUTPUT@',
++      '-topk8', '-v2', 'aes128', '-v2prf', 'hmacWithSHA256', '-passout', 'pass:abc' ]
++  )
++
++  cert_client_key_pkcs8_sha1_des_pem_tgt = custom_target('cert-client-key-pkcs8-sha1-des.pem',
++    input: cert_client_key_pkcs8_pem_tgt,
++    output: 'cert-client-key-pkcs8-sha1-des.pem',
++    command: [ openssl, 'pkcs8', '-in', '@INPUT@', '-out', '@OUTPUT@',
++      '-topk8', '-v1', 'PBE-SHA1-DES', '-passout', 'pass:abc', openssl_legacy ]
++  )
++
++  cert_client_key_pkcs8_v2_aes256_pem_tgt = custom_target('cert-client-key-pkcs8-v2-aes256.pem',
++    input: cert_client_key_pkcs8_pem_tgt,
++    output: 'cert-client-key-pkcs8-v2-aes256.pem',
++    command: [ openssl, 'pkcs8', '-in', '@INPUT@', '-out', '@OUTPUT@',
++      '-topk8', '-v2', 'aes256', '-v2prf', 'hmacWithSHA512', '-passout', 'pass:abc' ]
++  )
++
++  cert_client_csr_tgt = custom_target('cert-client.csr',
++    input: cert_client_key_pkcs1_pem_tgt,
++    output: 'cert-client.csr',
++    command: [ openssl, 'req', '-new', '-extensions', 'cert_ext',
++      '-config', files('gencerts.cnf'),
++      '-subj', '/O=Bar Example Organization/CN=Bar Example Organization/emailAddress=bar@mail.example',
++      '-key', '@INPUT@', '-out', '@OUTPUT@'
++    ]
++  )
++
++  cert_client_pem_tgt = custom_target('cert-client.pem',
++    input: [ cert_client_csr_tgt, cert_ca_pem_tgt, cert_ca_key_pem_tgt ],
++    output: 'cert-client.pem',
++    command: [ openssl, 'x509', '-req', '-extensions', 'cert_ext',
++      '-extfile', files('gencerts.cnf'),
++      '-in', '@INPUT0@', '-CA', '@INPUT1@',
++      '-CAkey', '@INPUT2@',
++      '-CAserial', 'cert-ca.srl',
++      '-CAcreateserial', '-sha256', '-days', '10000', '-out', '@OUTPUT@' ]
++  )
++
++  cert_client_crt_tgt = custom_target('cert-client.crt',
++    input: cert_client_pem_tgt,
++    output: 'cert-client.crt',
++    command: [ openssl, 'x509', '-in', '@INPUT@', '-out', '@OUTPUT@', '-outform', 'der' ]
++  )
++
++  cert_intca_key_pem_tgt = custom_target('cert-intca-key.pem',
++    output: 'cert-intca-key.pem',
++    command: [ openssl, 'genrsa', '-out', '@OUTPUT@' ]
++  )
++
++  cert_intca_csr_tgt = custom_target('cert-intca.csr',
++    input: cert_intca_key_pem_tgt,
++    output: 'cert-intca.csr',
++    command: [ openssl, 'req', '-new', '-extensions', 'int_ext',
++      '-config', files('gencerts.cnf'),
++      '-subj', '/O=International Union of Example Organizations/CN=Certificate issuer guy/emailAddress=ca@mail.example',
++      '-key', '@INPUT@', '-out', '@OUTPUT@' ]
++  )
++
++  cert_intca_pem_tgt = custom_target('cert-intca.pem',
++    input: [ cert_intca_csr_tgt, cert_ca_pem_tgt, cert_ca_key_pem_tgt ],
++    output: 'cert-intca.pem',
++    command: [ openssl, 'x509', '-req', '-extensions', 'int_ext',
++      '-extfile', files('gencerts.cnf'),
++      '-in', '@INPUT0@', '-CA', '@INPUT1@',
++      '-CAkey', '@INPUT2@',
++      '-CAserial', 'cert-ca.srl',
++      '-CAcreateserial', '-sha256', '-days', '10000', '-out', '@OUTPUT@' ]
++  )
++
++  cat = find_program('cat')
++
++  cert_chain_pem_tgt = custom_target('cert-chain.pem',
++    input: [ cert_intca_pem_tgt, cert_ca_pem_tgt ],
++    output: 'cert-chain.pem',
++    capture: true,
++    command: [ cat, '@INPUT@' ]
++  )
++
++  cert_entity_int_key_pem_tgt = custom_target('cert-entity-int-key.pem',
++    output: 'cert-entity-int-key.pem',
++    command: [ openssl, 'genrsa', '-out', '@OUTPUT@' ]
++  )
++
++  cert_entity_int_csr_tgt = custom_target('cert-entity-int.csr',
++    input: cert_entity_int_key_pem_tgt,
++    output: 'cert-entity-int.csr',
++    command: [ openssl, 'req', '-new', '-extensions', 'cert_ext',
++      '-config', files('gencerts.cnf'),
++      '-subj', '/O=Baz Example Organization/CN=Baz Example Organization/emailAddress=baz@mail.example',
++      '-key', '@INPUT@', '-out', '@OUTPUT@' ]
++  )
++
++  cert_entity_int_pem_tgt = custom_target('cert-entity-int.pem',
++    input: [ cert_entity_int_csr_tgt, cert_intca_pem_tgt, cert_intca_key_pem_tgt ],
++    output: 'cert-entity-int.pem',
++    command: [ openssl, 'x509', '-req', '-extensions', 'cert_ext',
++      '-extfile', files('gencerts.cnf'),
++      '-in', '@INPUT0@', '-CA', '@INPUT1@',
++      '-CAkey', '@INPUT2@',
++      '-CAserial', 'cert-intca.srl',
++      '-CAcreateserial', '-sha256', '-days', '10000', '-out', '@OUTPUT@' ]
++  )
++
++  cert_ca2_pem_tgt = custom_target('cert-ca2.pem',
++    input: cert_ca_key_pem_tgt,
++    output: 'cert-ca2.pem',
++    command: [ openssl, 'req', '-x509', '-new', '-nodes', '-extensions', 'ca_no_akid_ext',
++      '-config', files('gencerts.cnf'),
++      '-subj', '/O=International Union of Example Organizations/CN=Certificate issuer guy/emailAddress=ca-no-akid@mail.example',
++      '-key', '@INPUT@', '-sha256', '-days', '10000', '-out', '@OUTPUT@' ]
++  )
++
++  cert_client_key_pkcs1_des_pem_tgt = custom_target('cert-client-key-pkcs1-des.pem',
++    input: cert_client_key_pkcs1_pem_tgt,
++    output: 'cert-client-key-pkcs1-des.pem',
++    command: [ openssl, 'rsa', '-in', '@INPUT@', '-out', '@OUTPUT@', '-des', '-passout', 'pass:abc', openssl_legacy ],
++  )
++
++  cert_no_keyid_csr_tgt = custom_target('cert-no-keyid.csr',
++    input: cert_client_key_pkcs1_pem_tgt,
++    output: 'cert-no-keyid.csr',
++    command: [ openssl, 'req', '-new',
++      '-config', files('gencerts.cnf'),
++      '-subj', '/O=Baz Example Organization/CN=Baz Example Organization/emailAddress=baz@mail.example',
++      '-key', '@INPUT@', '-out', '@OUTPUT@' ]
++  )
++
++  cert_no_keyid_pem_tgt = custom_target('cert-no-keyid.pem',
++    input: [ cert_no_keyid_csr_tgt, cert_ca2_pem_tgt, cert_ca_key_pem_tgt ],
++    output: 'cert-no-keyid.pem',
++    command: [ openssl, 'x509', '-req', '-extensions', 'no_keyid_ext',
++      '-extfile', files('gencerts.cnf'),
++      '-in', '@INPUT0@', '-CA', '@INPUT1@',
++      '-CAkey', '@INPUT2@',
++      '-CAserial', 'cert-ca2.srl',
++      '-CAcreateserial', '-sha256', '-days', '10000', '-out', '@OUTPUT@' ]
++  )
++
++  cert_expired_csr_tgt = custom_target('cert-expired.csr',
++    input: cert_client_key_pkcs1_pem_tgt,
++    output: 'cert-expired.csr',
++    command: [ openssl, 'req', '-new', '-extensions', 'cert_ext',
++      '-config', files('gencerts.cnf'),
++      '-subj', '/O=Bar Example Organization/CN=Bar Example Organization/emailAddress=bar@mail.example',
++      '-key', '@INPUT@', '-out', '@OUTPUT@' ]
++  )
++
++  echo = find_program('echo')
++
++  cert_ca_cnf_tgt = custom_target('cert-ca.cnf',
++    output: 'cert-ca.cnf',
++    capture: true,
++    command: [ echo, '-e',
++      '[example]\ndatabase=cert-ca-index.txt\nserial=cert-ca.srl\npolicy=dummy\n[dummy]' ]
++  )
++
++  cert_entity_pkcs12_rc2_sha1_p12_tgt = custom_target('cert-entity-pkcs12-rc2-sha1.p12',
++    input: [ cert_entity_int_key_pem_tgt, cert_entity_int_pem_tgt, cert_chain_pem_tgt ],
++    output: 'cert-entity-pkcs12-rc2-sha1.p12',
++    command: [ openssl, 'pkcs12', '-inkey', '@INPUT0@', '-in', '@INPUT1@', '-certfile', '@INPUT2@', '-out', '@OUTPUT@', '-export',
++      '-passout', 'pass:abc', '-certpbe', 'PBE-SHA1-RC2-40', '-keypbe', 'PBE-SHA1-RC2-128', '-macalg', 'sha1', openssl_legacy ]
++  )
++
++  cert_entity_combined_pem_tgt = custom_target('cert-entity-combined.pem',
++    input: cert_entity_pkcs12_rc2_sha1_p12_tgt,
++    output: 'cert-entity-combined.pem',
++    command: [ openssl, 'pkcs12', '-in', '@INPUT@', '-out', '@OUTPUT@', '-passin', 'pass:abc', '-passout', 'pass:abc', openssl_legacy ]
++  )
++
++  gen_cert_expired_pem_sh = find_program('gen-cert-expired-pem.sh')
++  cert_expired_pem_tgt = custom_target('cert-expired.pem',
++    input: [ cert_expired_csr_tgt, cert_ca_pem_tgt, cert_ca_key_pem_tgt, cert_ca_cnf_tgt, files('gencerts.cnf') ],
++    output: 'cert-expired.pem',
++    command: [ gen_cert_expired_pem_sh, '@INPUT0@', '@INPUT1@', '@INPUT2@', '@INPUT3@', '@INPUT4@', '@OUTPUT@' ]
++  )
++
++  cert_entity_pkcs12_nomac_p12_tgt = custom_target('cert-entity-pkcs12-nomac.p12',
++    input: [ cert_entity_int_key_pem_tgt, cert_entity_int_pem_tgt ],
++    output: 'cert-entity-pkcs12-nomac.p12',
++    command: [ openssl, 'pkcs12', '-inkey', '@INPUT0@', '-in', '@INPUT1@', '-out', '@OUTPUT@', '-export', '-passout', 'pass:abc', '-nomac' ]
++  )
++
++  cert_entity_pkcs12_des_sha256_p12_tgt = custom_target('cert-entity-pkcs12-des-sha256.p12',
++    input: [ cert_entity_int_key_pem_tgt, cert_entity_int_pem_tgt, cert_chain_pem_tgt ],
++    output: 'cert-entity-pkcs12-des-sha256.p12',
++    command: [ openssl, 'pkcs12', '-inkey', '@INPUT0@', '-in', '@INPUT1@', '-certfile', '@INPUT2@', '-out', '@OUTPUT@', '-export',
++      '-passout', 'pass:abc', '-certpbe', 'PBE-SHA1-3DES', '-keypbe', 'PBE-SHA1-2DES', '-macalg', 'sha256' ]
++  )
++
++  cert_entity_pkcs12_rc4_sha384_p12_tgt = custom_target('cert-entity-pkcs12-rc4-sha384.p12',
++    input: [ cert_entity_int_key_pem_tgt, cert_entity_int_pem_tgt, cert_chain_pem_tgt ],
++    output: 'cert-entity-pkcs12-rc4-sha384.p12',
++    command: [ openssl, 'pkcs12', '-inkey', '@INPUT0@', '-in', '@INPUT1@', '-certfile', '@INPUT2@', '-out', '@OUTPUT@', '-export',
++      '-passout', 'pass:abc', '-certpbe', 'PBE-SHA1-RC4-128', '-keypbe', 'PBE-SHA1-RC2-40', '-macalg', 'sha384', openssl_legacy ]
++  )
++
++  cert_entity_pkcs12_pkcs5_sha512_p12_tgt = custom_target('cert-entity-pkcs12-pkcs5-sha512.p12',
++    input: [ cert_entity_int_key_pem_tgt, cert_entity_int_pem_tgt, cert_chain_pem_tgt ],
++    output: 'cert-entity-pkcs12-pkcs5-sha512.p12',
++    command: [ openssl, 'pkcs12', '-inkey', '@INPUT0@', '-in', '@INPUT1@', '-certfile', '@INPUT2@', '-out', '@OUTPUT@', '-export',
++      '-passout', 'pass:abc', '-certpbe', 'des-cbc', '-keypbe', 'des-cbc', '-macalg', 'sha512', openssl_legacy ]
++  )
++
++  key_ciphertext_dat_tgt = custom_target('key-ciphertext.dat',
++    input: [ 'plaintext.txt', cert_client_pem_tgt ],
++    output: 'key-ciphertext.dat',
++    command: [ openssl, 'rsautl', '-encrypt', '-pkcs', '-in', '@INPUT0@', '-certin',
++      '-inkey', '@INPUT1@', '-out', '@OUTPUT@' ]
++  )
++
++  key_signature_dat_tgt = custom_target('key-signature.dat',
++    input: 'plaintext.txt',
++    output: 'key-signature.dat',
++    command: [ openssl, 'rsautl', '-sign', '-pkcs', '-in', '@INPUT@',
++      '-inkey', cert_client_key_pkcs1_pem_tgt, '-out', '@OUTPUT@' ]
++  )
++
++  pem_rsa_files = [
++    [ 'cert-client-key-pkcs1-des3.pem', '-des3' ],
++    [ 'cert-client-key-pkcs1-aes128.pem', '-aes128' ],
++    [ 'cert-client-key-pkcs1-aes192.pem', '-aes192' ],
++    [ 'cert-client-key-pkcs1-aes256.pem', '-aes256' ],
++  ]
++
++  foreach file: pem_rsa_files
++    test_pem_cert_deps += custom_target(file[0],
++      input: cert_client_key_pkcs1_pem_tgt,
++      output: file[0],
++      command: [ openssl, 'rsa', '-in', '@INPUT@', '-out', '@OUTPUT@', file[1], '-passout', 'pass:abc' ]
++    )
++  endforeach
++
++  built_headers = [
++    [ 'plaintext.txt', 'key-plaintext.h' ],
++    [ key_ciphertext_dat_tgt, 'key-ciphertext.h' ],
++    [ key_signature_dat_tgt, 'key-signature.h' ]
++  ]
++
++  xxd_sh = find_program('xxd.sh')
++
++  foreach header: built_headers
++    gen_headers += custom_target(header[1],
++      input: header[0],
++      output: header[1],
++      command: [ xxd_sh,'@INPUT@', '@OUTPUT@' ]
++    )
++  endforeach
++
++  cert_verifs = [
++    [ cert_server_pem_tgt, cert_ca_pem_tgt ],
++    [ ec_cert_server_pem_tgt, ec_cert_ca_pem_tgt ],
++    [ cert_client_pem_tgt, cert_ca_pem_tgt ],
++    [ cert_intca_pem_tgt, cert_ca_pem_tgt ],
++    [ cert_entity_int_pem_tgt, cert_chain_pem_tgt ],
++    [ cert_no_keyid_pem_tgt, cert_ca2_pem_tgt ]
++  ]
++  foreach cert_verif: cert_verifs
++    test(cert_verif[0].full_path() + '-verify',
++      openssl,
++      args: [ 'verify', '-CAfile', cert_verif[1].full_path(), cert_verif[0].full_path() ]
++    )
++  endforeach
++
++  test_pem_cert_deps += [
++    # test_pem
++    cert_client_key_pkcs8_md5_des_pem_tgt,
++    cert_client_key_pkcs8_sha1_des_pem_tgt,
++    cert_client_key_pkcs8_v2_des_pem_tgt,
++    cert_client_key_pkcs8_v2_des_ede3_pem_tgt,
++    cert_client_key_pkcs8_v2_aes128_pem_tgt,
++    cert_client_key_pkcs8_v2_aes256_pem_tgt,
++    cert_client_key_pkcs1_des_pem_tgt,
++    cert_client_key_pkcs1_pem_tgt,
++    cert_client_key_pkcs1_des_pem_tgt,
++    cert_client_key_pkcs8_pem_tgt,
++    cert_client_key_pkcs8_sha1_des_pem_tgt,
++    cert_client_pem_tgt,
++    cert_client_crt_tgt,
++    cert_entity_combined_pem_tgt,
++    cert_entity_pkcs12_nomac_p12_tgt,
++    cert_entity_pkcs12_rc2_sha1_p12_tgt,
++    cert_entity_pkcs12_des_sha256_p12_tgt,
++    cert_entity_pkcs12_rc4_sha384_p12_tgt,
++    cert_entity_pkcs12_pkcs5_sha512_p12_tgt,
++    # test_key
++    cert_ca_pem_tgt,
++    cert_server_pem_tgt,
++    cert_intca_pem_tgt,
++    cert_entity_int_pem_tgt,
++    cert_client_pem_tgt,
++    cert_client_key_pkcs8_pem_tgt,
++    # test_tls
++    cert_chain_pem_tgt,
++    cert_expired_pem_tgt,
++    cert_ca2_pem_tgt,
++    cert_no_keyid_pem_tgt,
++    ec_cert_ca_pem_tgt,
++    ec_cert_server_pem_tgt,
++    cert_server_key_pkcs8_pem_tgt
++  ]
++endif
++
++# Mocking l_getrandom() to get a stable random seed
++libell_private_ecdh_dep = declare_dependency(
++  link_with: libell_private,
++  link_args: '-Wl,-wrap,l_getrandom'
++)
++
++if get_option('tests') or get_option('cert-tests')
++  foreach test_name: tests
++    dep = test_name == 'test-ecdh' ? libell_private_ecdh_dep : libell_private_dep
++    exe = executable(test_name,
++      [ '@0@.c'.format(test_name), gen_headers, test_pem_cert_deps ],
++      include_directories: include_directories('..'),
++      c_args: [
++        '-DUNITDIR="@0@/unit/"'.format(meson.project_source_root()),
++        '-DCERTDIR="@0@/unit/"'.format(meson.project_build_root())
++      ],
++      dependencies: dep,
++      install: false
++    )
++
++    test(test_name, exe,
++      protocol: 'tap')
++  endforeach
++endif
+diff --git a/unit/xxd.sh b/unit/xxd.sh
+new file mode 100755
+index 000000000000..3fd26d2bce90
+--- /dev/null
++++ b/unit/xxd.sh
+@@ -0,0 +1,3 @@
++#!/bin/sh
++
++xxd -i < "$1" > "$2"
+-- 
+2.52.0
+
-- 
2.53.0


^ permalink raw reply related

* [PATCH BlueZ v9 00/15] Add meson build system and HTML docs
From: Bastien Nocera @ 2026-04-24  9:11 UTC (permalink / raw)
  To: linux-bluetooth

Changes since v8:
- add btsnoop protocol to the docs and generate man page
- fix duplicated labels when linking to SCO and ISO docs
- add test-util to the test suite
- remove hcidump
- fix btpclient build
- remove glib dependency from btvirt
- add bluetoothctl completion crash regression test
- add config warning regression test
- link btmgmt against glib

Bastien Nocera (14):
  build: Add meson wrap for libell
  build: Add meson build system
  build: Separate systemd and libsystemd dependencies
  tools: Install gatttool if deprecated tools are enabled
  tools: Install avinfo tool by default
  emulator: Install the emulator if built
  build: Add option to allow disabling bluetoothd
  unit: Run test-bap tests concurrently
  unit: Make gobex-transfer tests run concurrently
  build: Only build profiles if the daemon is built
  build: Only build gdbus library if there is a user
  unit: Add integration tests
  doc: Add HTML documentation
  github: Add docs deployment

Paul Otto (1):
  doc: Add introduction to GATT

 .github/workflows/docs.yml                    |  54 +
 attrib/meson.build                            |  17 +
 btio/meson.build                              |   1 +
 client/btpclient/meson.build                  |  19 +
 client/meson.build                            |  33 +
 completion/meson.build                        |   3 +
 doc/_static/bluez.css                         | 452 +++++++++
 doc/_static/bluez.png                         | Bin 0 -> 18354 bytes
 doc/_static/bullet.svg                        |   1 +
 doc/_static/card.png                          | Bin 0 -> 35387 bytes
 doc/_static/figtree.woff2                     | Bin 0 -> 14364 bytes
 doc/_static/toc_bullet.svg                    |  39 +
 doc/bluetoothctl-cmd.rst                      |  34 +
 doc/conf.py                                   |  39 +
 doc/copy-subdir.py                            |  14 +
 doc/daemons.rst                               |  12 +
 doc/dbus-gatt.rst                             |  20 +
 doc/dbus-obex.rst                             |  33 +
 doc/dbus.rst                                  |  76 ++
 doc/deprecated-tools.rst                      |  22 +
 doc/dev-maint.rst                             |  17 +
 doc/favicon.ico                               | Bin 0 -> 29152 bytes
 doc/gatt-intro.rst                            | 141 +++
 doc/index.rst                                 |  47 +
 doc/meson.build                               | 293 ++++++
 doc/protocols.rst                             |  22 +
 doc/qualifications.rst                        |  76 ++
 doc/tools.rst                                 |  24 +
 emulator/meson.build                          |  36 +
 gdbus/meson.build                             |  19 +
 gobex/meson.build                             |   8 +
 lib/meson.build                               |  53 +
 mesh/meson.build                              |  60 ++
 meson.build                                   | 323 ++++++
 meson_options.txt                             |  55 ++
 monitor/meson.build                           |  37 +
 obexd/meson.build                             | 121 +++
 peripheral/meson.build                        |  13 +
 plugins/meson.build                           |  29 +
 profiles/meson.build                          | 167 ++++
 src/meson.build                               |  90 ++
 src/shared/meson.build                        |  85 ++
 subprojects/ell.wrap                          |  11 +
 .../0001-build-Add-meson-build-system.patch   | 922 ++++++++++++++++++
 test/meson.build                              |  37 +
 tools/mesh/meson.build                        |  16 +
 tools/meson.build                             | 262 +++++
 tools/mpris-proxy.service.in                  |   2 +-
 unit/integration-test-skipped.sh              |   3 +
 unit/integration-test.py                      | 271 +++++
 unit/meson.build                              | 194 ++++
 unit/output_checker.py                        | 214 ++++
 unit/unittest_inspector.py                    |  46 +
 unit/unittest_list.sh                         |  15 +
 54 files changed, 4577 insertions(+), 1 deletion(-)
 create mode 100644 .github/workflows/docs.yml
 create mode 100644 attrib/meson.build
 create mode 100644 btio/meson.build
 create mode 100644 client/btpclient/meson.build
 create mode 100644 client/meson.build
 create mode 100644 completion/meson.build
 create mode 100644 doc/_static/bluez.css
 create mode 100644 doc/_static/bluez.png
 create mode 100644 doc/_static/bullet.svg
 create mode 100644 doc/_static/card.png
 create mode 100644 doc/_static/figtree.woff2
 create mode 100644 doc/_static/toc_bullet.svg
 create mode 100644 doc/bluetoothctl-cmd.rst
 create mode 100644 doc/conf.py
 create mode 100644 doc/copy-subdir.py
 create mode 100644 doc/daemons.rst
 create mode 100644 doc/dbus-gatt.rst
 create mode 100644 doc/dbus-obex.rst
 create mode 100644 doc/dbus.rst
 create mode 100644 doc/deprecated-tools.rst
 create mode 100644 doc/dev-maint.rst
 create mode 100644 doc/favicon.ico
 create mode 100644 doc/gatt-intro.rst
 create mode 100644 doc/index.rst
 create mode 100644 doc/meson.build
 create mode 100644 doc/protocols.rst
 create mode 100644 doc/qualifications.rst
 create mode 100644 doc/tools.rst
 create mode 100644 emulator/meson.build
 create mode 100644 gdbus/meson.build
 create mode 100644 gobex/meson.build
 create mode 100644 lib/meson.build
 create mode 100644 mesh/meson.build
 create mode 100644 meson.build
 create mode 100644 meson_options.txt
 create mode 100644 monitor/meson.build
 create mode 100644 obexd/meson.build
 create mode 100644 peripheral/meson.build
 create mode 100644 plugins/meson.build
 create mode 100644 profiles/meson.build
 create mode 100644 src/meson.build
 create mode 100644 src/shared/meson.build
 create mode 100644 subprojects/ell.wrap
 create mode 100644 subprojects/packagefiles/0001-build-Add-meson-build-system.patch
 create mode 100644 test/meson.build
 create mode 100644 tools/mesh/meson.build
 create mode 100644 tools/meson.build
 create mode 100755 unit/integration-test-skipped.sh
 create mode 100755 unit/integration-test.py
 create mode 100644 unit/meson.build
 create mode 100644 unit/output_checker.py
 create mode 100755 unit/unittest_inspector.py
 create mode 100644 unit/unittest_list.sh

-- 
2.53.0


^ permalink raw reply

* Re: [PATCH 00/12] Fixes/improvements for the PCI M.2 power sequencing driver
From: Wei Deng @ 2026-04-24  6:25 UTC (permalink / raw)
  To: manivannan.sadhasivam, Bartosz Golaszewski, Manivannan Sadhasivam,
	Marcel Holtmann, Luiz Augusto von Dentz, Shuai Zhang
  Cc: linux-pm, linux-kernel, linux-pci, linux-arm-msm, linux-bluetooth,
	Luiz Augusto von Dentz, Bartosz Golaszewski, stable+noautosel
In-Reply-To: <20260422-pwrseq-m2-bt-v1-0-720d02545a64@oss.qualcomm.com>

Hi Mani,

Tested with WCN685x on both M.2 card (pwrseq-pcie-m2) and direct attach
(pwrseq-qcom-wcn). BT works correctly in both cases.

Tested-by: Wei Deng <wei.deng@oss.qualcomm.com>

On 4/22/2026 7:24 PM, Manivannan Sadhasivam via B4 Relay wrote:
> Hi,
> 
> This series has several key improvements and fixes to the M.2 power sequencing
> driver and also the BT HCI_QCA driver. Notably, this series allows the M.2 power
> sequencing driver to work with more M.2 cards, not just WCN7850. It also allows
> the BT HCI_QCA driver to detect whether it can control BT_EN (or W_DISABLE2#)
> signal on the connector and set the HCI_QUIRK_NON_PERSISTENT_SETUP quirk.
> 
> Testing
> =======
> 
> This series was tested on Lenovo Thinkpad T14s together with the below DTS
> patches:
> https://github.com/Mani-Sadhasivam/linux/commit/29534d15307551b2355eb254601dec511169f0aa
> https://github.com/Mani-Sadhasivam/linux/commit/f4eaacfe647674be200847092b43cdef2194fc55
> 
> Merge Strategy
> ==============
> 
> Since the BT HCI_QCA changes depend on the pwrseq changes, it would be good to
> merge the whole series through pwrseq tree or through an immutable branch.
> 
> Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
> ---
> Manivannan Sadhasivam (12):
>       power: sequencing: Introduce an API to check whether the pwrseq is fixed or controllable
>       power: sequencing: pcie-m2: Add support for 'is_fixed()' callback to 'uart' target
>       power: sequencing: qcom-wcn: Add support for 'is_fixed()' callback to 'bluetooth' target
>       power: sequencing: pcie-m2: Fix inconsistent function prefixes
>       power: sequencing: pcie-m2: Allow creating serdev for multiple PCI devices
>       power: sequencing: pcie-m2: Improve PCI device ID check
>       power: sequencing: pcie-m2: Create serdev for PCI devices present before probe
>       power: sequencing: pcie-m2: Create BT node based on the pci_device_id[] table
>       Bluetooth: hci_qca: Add M.2 Bluetooth device support using pwrseq
>       Bluetooth: hci_qca: Rename 'power_ctrl_enabled' to 'bt_en_available'
>       Bluetooth: hci_qca: Check whether the M.2 UART interface is fixed or not
>       Bluetooth: hci_qca: Fix the broken BT_EN GPIO detection for Qcom WCN devices
> 
>  drivers/bluetooth/hci_qca.c                |  40 +++--
>  drivers/power/sequencing/core.c            |  33 ++++
>  drivers/power/sequencing/pwrseq-pcie-m2.c  | 242 ++++++++++++++++++++++-------
>  drivers/power/sequencing/pwrseq-qcom-wcn.c |   9 ++
>  include/linux/pwrseq/consumer.h            |   6 +
>  include/linux/pwrseq/provider.h            |   2 +
>  6 files changed, 262 insertions(+), 70 deletions(-)
> ---
> base-commit: 6596a02b207886e9e00bb0161c7fd59fea53c081
> change-id: 20260422-pwrseq-m2-bt-abdaa71094eb
> 
> Best regards,
> --  
> Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
> 
> 

-- 
Best Regards,
Wei Deng


^ permalink raw reply

* RE: [BlueZ,v2] src/shared: add custom CCC callbacks
From: bluez.test.bot @ 2026-04-24  6:22 UTC (permalink / raw)
  To: linux-bluetooth, prathibha.madugonde
In-Reply-To: <20260424042830.2374708-1-prathm@qti.qualcomm.com>

[-- Attachment #1: Type: text/plain, Size: 989 bytes --]

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1085008

---Test result---

Test Summary:
CheckPatch                    PASS      0.48 seconds
GitLint                       PASS      0.28 seconds
BuildEll                      PASS      20.67 seconds
BluezMake                     PASS      648.34 seconds
MakeCheck                     PASS      1.02 seconds
MakeDistcheck                 PASS      249.68 seconds
CheckValgrind                 PASS      225.71 seconds
CheckSmatch                   PASS      352.21 seconds
bluezmakeextell               PASS      183.36 seconds
IncrementalBuild              PASS      640.32 seconds
ScanBuild                     PASS      1032.72 seconds



https://github.com/bluez/bluez/pull/2068

---
Regards,
Linux Bluetooth


^ permalink raw reply

* [bluez/bluez] f8f5c8: src/shared: add custom CCC callbacks
From: prathibhamadugonde @ 2026-04-24  5:25 UTC (permalink / raw)
  To: linux-bluetooth

  Branch: refs/heads/1085008
  Home:   https://github.com/bluez/bluez
  Commit: f8f5c8829c8ee2b41d70f4fcdb71b44009b6afba
      https://github.com/bluez/bluez/commit/f8f5c8829c8ee2b41d70f4fcdb71b44009b6afba
  Author: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>
  Date:   2026-04-24 (Fri, 24 Apr 2026)

  Changed paths:
    M src/shared/gatt-db.c
    M src/shared/gatt-db.h

  Log Message:
  -----------
  src/shared: add custom CCC callbacks

Introduce custom CCC callbacks to allow interception of
client notification subscription changes. This enables
stack-level handling of CCC writes.



To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications

^ permalink raw reply

* [PATCH BlueZ v2] src/shared: add custom CCC callbacks
From: Prathibha Madugonde @ 2026-04-24  4:28 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg

From: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>

Introduce custom CCC callbacks to allow interception of
client notification subscription changes. This enables
stack-level handling of CCC writes.
---
 src/shared/gatt-db.c | 119 ++++++++++++++++++++++++++++++++++++++++++-
 src/shared/gatt-db.h |   4 ++
 2 files changed, 122 insertions(+), 1 deletion(-)

diff --git a/src/shared/gatt-db.c b/src/shared/gatt-db.c
index d0e149d6f..87cc61cf3 100644
--- a/src/shared/gatt-db.c
+++ b/src/shared/gatt-db.c
@@ -1214,6 +1214,112 @@ gatt_db_service_add_ccc(struct gatt_db_attribute *attrib, uint32_t permissions)
 	return ccc;
 }
 
+static void ccc_custom_read(struct gatt_db_attribute *attrib,
+					unsigned int id, uint16_t offset,
+					uint8_t opcode, struct bt_att *att,
+					void *user_data)
+{
+	struct gatt_db *db = attrib->service->db;
+
+	db->ccc->read_func(attrib, id, offset, opcode, att, db->ccc->user_data);
+}
+
+static void custom_write_result(struct gatt_db_attribute *attr, int err,
+							void *user_data)
+{
+	int *result = user_data;
+
+	*result = err;
+}
+
+static void ccc_custom_write(struct gatt_db_attribute *attrib,
+					unsigned int id, uint16_t offset,
+					const uint8_t *value, size_t len,
+					uint8_t opcode, struct bt_att *att,
+					void *user_data)
+{
+	struct gatt_db_ccc *data = user_data;
+	struct gatt_db *db = attrib->service->db;
+	struct pending_write *p;
+	int err = 0;
+
+	/* Create another pending write to handle results from custom write
+	 * function.
+	 */
+	p = new0(struct pending_write, 1);
+	p->attrib = attrib;
+	p->id = ++attrib->write_id;
+	p->func = custom_write_result;
+	p->user_data = &err;
+
+	queue_push_tail(attrib->pending_writes, p);
+
+	/* Call custom write function first */
+	data->write_func(attrib, p->id, offset, value, len, opcode, att,
+							data->user_data);
+
+	if (err) {
+		gatt_db_attribute_write_result(attrib, id, err);
+		return;
+	}
+
+	/* If custom write function did not return error proceed to call the
+	 * default CCC write function.
+	 */
+	db->ccc->write_func(attrib, id, offset, value, len, opcode, att,
+							db->ccc->user_data);
+}
+
+struct gatt_db_attribute *
+gatt_db_service_add_ccc_custom(struct gatt_db_attribute *attrib,
+				uint32_t permissions,
+				gatt_db_write_t write_func, void *user_data)
+{
+	struct gatt_db *db;
+	struct gatt_db_attribute *ccc;
+	struct gatt_db_attribute *value;
+	uint16_t handle = 0;
+	struct gatt_db_ccc *data;
+
+	if (!attrib || !permissions)
+		return NULL;
+
+	db = attrib->service->db;
+
+	if (!db->ccc)
+		return NULL;
+
+	/* Locate value handle */
+	gatt_db_service_foreach_char(attrib, find_ccc_value, &handle);
+
+	if (!handle)
+		return NULL;
+
+	value = gatt_db_get_attribute(db, handle);
+	if (!value || value->notify_func)
+		return NULL;
+
+	data = new0(struct gatt_db_ccc, 1);
+	data->write_func = write_func;
+	data->user_data = user_data;
+
+	ccc = service_insert_descriptor(attrib->service, 0, &ccc_uuid,
+					permissions,
+					ccc_custom_read,
+					ccc_custom_write,
+					data);
+	if (!ccc) {
+		free(data);
+		return NULL;
+	}
+
+	gatt_db_attribute_set_fixed_length(ccc, 2);
+	ccc->notify_func = db->ccc->notify_func;
+	value->notify_func = db->ccc->notify_func;
+
+	return ccc;
+}
+
 void gatt_db_ccc_register(struct gatt_db *db, gatt_db_read_t read_func,
 				gatt_db_write_t write_func,
 				gatt_db_notify_t notify_func,
@@ -2338,6 +2444,8 @@ bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
 					struct bt_att *att)
 {
 	struct gatt_db_attribute *ccc;
+	struct gatt_db *db;
+	void *notify_user_data;
 
 	if (!attrib || !attrib->notify_func)
 		return false;
@@ -2350,7 +2458,16 @@ bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
 	if (!ccc)
 		return false;
 
-	attrib->notify_func(attrib, ccc, value, len, att, ccc->user_data);
+	/* For custom CCC descriptors, use the database user_data for
+	 * notify_func. For regular CCC descriptors, use the CCC's user_data.
+	 */
+	db = attrib->service->db;
+	if (ccc->write_func == ccc_custom_write && db && db->ccc)
+		notify_user_data = db->ccc->user_data;
+	else
+		notify_user_data = ccc->user_data;
+
+	attrib->notify_func(attrib, ccc, value, len, att, notify_user_data);
 
 	return true;
 }
diff --git a/src/shared/gatt-db.h b/src/shared/gatt-db.h
index dc2daf7fc..da0600c01 100644
--- a/src/shared/gatt-db.h
+++ b/src/shared/gatt-db.h
@@ -112,6 +112,10 @@ gatt_db_service_insert_descriptor(struct gatt_db_attribute *attrib,
 struct gatt_db_attribute *
 gatt_db_service_add_ccc(struct gatt_db_attribute *attrib, uint32_t permissions);
 
+struct gatt_db_attribute *
+gatt_db_service_add_ccc_custom(struct gatt_db_attribute *attrib,
+				uint32_t permissions,
+				gatt_db_write_t write_func, void *user_data);
 struct gatt_db_attribute *
 gatt_db_insert_included(struct gatt_db *db, uint16_t handle,
 			struct gatt_db_attribute *include);
-- 
2.34.1


^ permalink raw reply related

* RE: [v2] Bluetooth: RFCOMM: pull credit byte with skb_pull_data()
From: bluez.test.bot @ 2026-04-23 23:29 UTC (permalink / raw)
  To: linux-bluetooth, pengpeng
In-Reply-To: <20260424070102.1-rfcomm-v2-pengpeng@iscas.ac.cn>

[-- Attachment #1: Type: text/plain, Size: 935 bytes --]

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1084930

---Test result---

Test Summary:
CheckPatch                    PASS      0.52 seconds
GitLint                       PASS      0.23 seconds
SubjectPrefix                 PASS      0.09 seconds
BuildKernel                   PASS      21.35 seconds
CheckAllWarning               PASS      23.64 seconds
CheckSparse                   PASS      23.81 seconds
BuildKernel32                 PASS      20.22 seconds
TestRunnerSetup               PASS      415.26 seconds
TestRunner_rfcomm-tester      PASS      7.45 seconds
IncrementalBuild              PASS      20.39 seconds



https://github.com/bluez/bluetooth-next/pull/118

---
Regards,
Linux Bluetooth


^ permalink raw reply

* [PATCH v2] Bluetooth: RFCOMM: pull credit byte with skb_pull_data()
From: Pengpeng Hou @ 2026-04-23 15:31 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz
  Cc: Kees Cook, Jakub Kicinski, Ingo Molnar, Bastien Nocera,
	Thomas Gleixner, linux-bluetooth, linux-kernel, stable, pengpeng
In-Reply-To: <20260417073446.95494-1-pengpeng@iscas.ac.cn>

rfcomm_recv_data() treats the first payload byte as a credit field when
the UIH frame carries PF and credit-based flow control is enabled.

After the header has been stripped, the PF/CFC path consumes that byte
with a direct skb->data dereference followed by skb_pull(). A malformed
short frame can reach this path without a byte available.

Use skb_pull_data() so the length check and pull happen together before
the returned credit byte is consumed.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
Changes since v1:
- use skb_pull_data() as suggested by Luiz Augusto von Dentz

diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c
index 611a9a94151e..d11bd5337d57 100644
--- a/net/bluetooth/rfcomm/core.c
+++ b/net/bluetooth/rfcomm/core.c
@@ -1715,9 +1715,12 @@ static int rfcomm_recv_data(struct rfcomm_session *s, u8 dlci, int pf, struct sk
 	}
 
 	if (pf && d->cfc) {
-		u8 credits = *(u8 *) skb->data; skb_pull(skb, 1);
+		u8 *credits = skb_pull_data(skb, 1);
 
-		d->tx_credits += credits;
+		if (!credits)
+			goto drop;
+
+		d->tx_credits += *credits;
 		if (d->tx_credits)
 			clear_bit(RFCOMM_TX_THROTTLED, &d->flags);
 	}
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* Re: [PATCH BlueZ v2 1/3] audio: Add ability to force CIS transport Links property
From: Frédéric Danis @ 2026-04-23 20:32 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
In-Reply-To: <CABBYNZKiNU88wK+oMT2Q7nKOr7NsngFD4+C35-agcbntJXsJ2g@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 11866 bytes --]

Hi Luiz,

On 23/04/2026 19:08, Luiz Augusto von Dentz wrote:
> Hi Frederic,
>
> On Thu, Apr 23, 2026 at 3:53 AM Frédéric Danis
> <frederic.danis@collabora.com> wrote:
>> If bluetoothd is started in testing mode the Links property for CIS
>> is readwrite and can be used to force transport objects Links.
>> This can used to unlink transport objects by sending an empty array.
>>
>> For unlinked transport objects, each transports needs to be acquired
>> separately.
>>
>> This allows to pass PTS tests BAP/UCL/STR/BV-543-C and BV-546-C.
>> ---
>> v1 -> v2:
>>    - Add testing mode check in bap_connect_io_cb()
>>    - Replace stream state check by checking that the stream is not linked
>>      before passing the connect event to all streams belonging to same
>>      CIG/CIS.
>>
>>   profiles/audio/bap.c       | 46 ++++++++++++++++++++++++++++++
>>   profiles/audio/transport.c | 57 +++++++++++++++++++++++++++++++-------
>>   2 files changed, 95 insertions(+), 10 deletions(-)
>>
>> diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
>> index 5333267f7..e20456436 100644
>> --- a/profiles/audio/bap.c
>> +++ b/profiles/audio/bap.c
>> @@ -2655,13 +2655,61 @@ static void bap_connect_bcast_io_cb(GIOChannel *chan, GError *err,
>>          iso_connect_bcast_cb(chan, err, setup->stream);
>>   }
>>
>> +struct connect_io_data {
>> +       GIOChannel *chan;
>> +       GError *err;
>> +       uint8_t cig_id;
>> +       uint8_t cis_id;
>> +};
>> +
>> +static void connect_stream(void *data, void *user_data)
>> +{
>> +       struct bap_setup *setup = data;
>> +       struct connect_io_data *d = user_data;
>> +       uint8_t state;
>> +
>> +       /* Check stream state to only pass the connect event managed
>> +        * by bap_stream_set_io() */
>> +       state = bt_bap_stream_get_state(setup->stream);
>> +       if ((state == BT_BAP_STREAM_STATE_ENABLING ||
>> +                       state == BT_BAP_STREAM_STATE_DISABLING) &&
>> +                       setup->qos.ucast.cig_id == d->cig_id &&
>> +                       setup->qos.ucast.cis_id == d->cis_id)
>> +               iso_connect_cb(d->chan, d->err, setup->stream);
>  From the discussion we had Ive assume we wouldn't be checking the
> state here, since we don't check on bap_connect_io_cb either.
If the state is not checked this prevents to complete the transport 
acquire, ending with:
- for BV-543-C: profiles/audio/bap.c:setup_connect_io() setup 
0x50f000007a20 stream 0x50e000010dc0 io already up
- for BV-546-C: profiles/audio/bap.c:bap_state() stream 0x50e00000a5a0: 
enabling(3) -> disabling(5)
see attached files.
>
>> +}
>> +
>> +static void connect_ep(void *data, void *user_data)
>> +{
>> +       struct bap_ep *ep = data;
>> +
>> +       if (ep->setups)
>> +               queue_foreach(ep->setups, connect_stream, user_data);
>> +}
>> +
>>   static void bap_connect_io_cb(GIOChannel *chan, GError *err, gpointer user_data)
>>   {
>>          struct bap_setup *setup = user_data;
>> +       struct connect_io_data data;
>>
>>          if (!setup->stream)
>>                  return;
>>
>> +       if (queue_isempty(bt_bap_stream_io_get_links(setup->stream)) &&
>> +               btd_opts.testing) {
>> +               /* The stream may have manually been unliked for PTS tests,
>> +                * e.g. BAP/UCL/STR/BV-543-C or BAP/UCL/STR/BV-546-C,
>> +                * in this case send the connect event to all streams
>> +                * belonging to the same CIG/CIS.
>> +                */
>> +               data.chan = chan;
>> +               data.err = err;
>> +               data.cig_id = setup->qos.ucast.cig_id;
>> +               data.cis_id = setup->qos.ucast.cis_id;
>> +               queue_foreach(setup->ep->data->snks, connect_ep, &data);
>> +               queue_foreach(setup->ep->data->srcs, connect_ep, &data);
>> +               return;
>> +       }
>> +
>>          iso_connect_cb(chan, err, setup->stream);
>>   }
>>
>> diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
>> index 5c2a2777e..b6a5dd1fd 100644
>> --- a/profiles/audio/transport.c
>> +++ b/profiles/audio/transport.c
>> @@ -37,6 +37,7 @@
>>   #include "src/shared/bap.h"
>>   #include "src/shared/bass.h"
>>   #include "src/shared/io.h"
>> +#include "src/btd.h"
>>
>>   #ifdef HAVE_A2DP
>>   #include "avdtp.h"
>> @@ -114,6 +115,7 @@ struct bap_transport {
>>   struct media_transport_ops {
>>          const char *uuid;
>>          const GDBusPropertyTable *properties;
>> +       const GDBusPropertyTable *test_properties;
>>          void (*set_owner)(struct media_transport *transport,
>>                                  struct media_owner *owner);
>>          void (*remove_owner)(struct media_transport *transport,
>> @@ -1419,6 +1421,9 @@ static struct media_transport *find_transport_by_path(const char *path)
>>          return NULL;
>>   }
>>
>> +static void bap_update_links(const struct media_transport *transport);
>> +static void transport_unlink(void *data, void *user_data);
>> +
>>   static void set_links(const GDBusPropertyTable *property,
>>                                  DBusMessageIter *iter,
>>                                  GDBusPendingPropertySet id, void *user_data)
>> @@ -1436,6 +1441,16 @@ static void set_links(const GDBusPropertyTable *property,
>>
>>          dbus_message_iter_recurse(iter, &array);
>>
>> +       if (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_INVALID) {
>> +               struct queue *links = bt_bap_stream_io_get_links(bap->stream);
>> +
>> +               /* Unlink stream from all its links */
>> +               queue_foreach(links, transport_unlink, bap->stream);
>> +
>> +               bt_bap_stream_io_unlink(bap->stream, NULL);
>> +               bap_update_links(transport);
>> +       }
>> +
>>          while (dbus_message_iter_get_arg_type(&array) ==
>>                                                  DBUS_TYPE_OBJECT_PATH) {
>>                  struct media_transport *link;
>> @@ -1486,6 +1501,21 @@ static const GDBusPropertyTable transport_bap_uc_properties[] = {
>>          { }
>>   };
>>
>> +static const GDBusPropertyTable transport_bap_uc_test_properties[] = {
>> +       { "Device", "o", get_device },
>> +       { "UUID", "s", get_uuid },
>> +       { "Codec", "y", get_codec },
>> +       { "Configuration", "ay", get_configuration },
>> +       { "State", "s", get_state },
>> +       { "QoS", "a{sv}", get_ucast_qos, NULL, qos_ucast_exists },
>> +       { "Endpoint", "o", get_endpoint, NULL, endpoint_exists },
>> +       { "Location", "u", get_location },
>> +       { "Metadata", "ay", get_metadata, set_metadata },
>> +       { "Links", "ao", get_links, set_links, links_exists },
>> +       { "Volume", "q", get_volume, set_volume, volume_exists },
>> +       { }
>> +};
>> +
>>   static gboolean get_bcast_qos(const GDBusPropertyTable *property,
>>                                          DBusMessageIter *iter, void *data)
>>   {
>> @@ -1884,8 +1914,6 @@ static void bap_resume_complete(struct media_transport *transport)
>>          transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
>>   }
>>
>> -static void bap_update_links(const struct media_transport *transport);
>> -
>>   static bool match_link_transport(const void *data, const void *user_data)
>>   {
>>          const struct bt_bap_stream *stream = data;
>> @@ -2540,10 +2568,11 @@ static void *transport_asha_init(struct media_transport *transport, void *data)
>>   #define TRANSPORT_OPS(_uuid, _props, _set_owner, _remove_owner, _init, \
>>                        _resume, _suspend, _cancel, _set_state, _get_stream, \
>>                        _get_volume, _set_volume, _set_delay, _update_links, \
>> -                     _destroy) \
>> +                     _destroy, _test_props) \
>>   { \
>>          .uuid = _uuid, \
>>          .properties = _props, \
>> +       .test_properties = _test_props, \
>>          .set_owner = _set_owner, \
>>          .remove_owner = _remove_owner, \
>>          .init = _init, \
>> @@ -2565,26 +2594,28 @@ static void *transport_asha_init(struct media_transport *transport, void *data)
>>                          transport_a2dp_resume, transport_a2dp_suspend, \
>>                          transport_a2dp_cancel, NULL, \
>>                          transport_a2dp_get_stream, transport_a2dp_get_volume, \
>> -                       _set_volume, _set_delay, NULL, _destroy)
>> +                       _set_volume, _set_delay, NULL, _destroy, NULL)
>>
>>   #define BAP_OPS(_uuid, _props, _set_owner, _remove_owner, _update_links, \
>> -               _set_state) \
>> +               _set_state, _test_props) \
>>          TRANSPORT_OPS(_uuid, _props, _set_owner, _remove_owner,\
>>                          transport_bap_init, \
>>                          transport_bap_resume, transport_bap_suspend, \
>>                          transport_bap_cancel, _set_state, \
>>                          transport_bap_get_stream, transport_bap_get_volume, \
>>                          transport_bap_set_volume, NULL, \
>> -                       _update_links, transport_bap_destroy)
>> +                       _update_links, transport_bap_destroy, _test_props)
>>
>>   #define BAP_UC_OPS(_uuid) \
>>          BAP_OPS(_uuid, transport_bap_uc_properties, \
>>                          transport_bap_set_owner, transport_bap_remove_owner, \
>> -                       transport_bap_update_links_uc, transport_bap_set_state)
>> +                       transport_bap_update_links_uc, \
>> +                       transport_bap_set_state, \
>> +                       transport_bap_uc_test_properties)
>>
>>   #define BAP_BC_OPS(_uuid) \
>>          BAP_OPS(_uuid, transport_bap_bc_properties, NULL, NULL, \
>> -                       transport_bap_update_links_bc, NULL)
>> +                       transport_bap_update_links_bc, NULL, NULL)
>>
>>   #define ASHA_OPS(_uuid) \
>>          TRANSPORT_OPS(_uuid, transport_asha_properties, NULL, NULL, \
>> @@ -2592,7 +2623,7 @@ static void *transport_asha_init(struct media_transport *transport, void *data)
>>                          transport_asha_resume, transport_asha_suspend, \
>>                          transport_asha_cancel, NULL, NULL, \
>>                          transport_asha_get_volume, transport_asha_set_volume, \
>> -                       NULL, NULL, NULL)
>> +                       NULL, NULL, NULL, NULL)
>>
>>   static const struct media_transport_ops transport_ops[] = {
>>   #ifdef HAVE_A2DP
>> @@ -2647,6 +2678,7 @@ struct media_transport *media_transport_create(struct btd_device *device,
>>          struct media_transport *transport;
>>          const struct media_transport_ops *ops;
>>          int fd;
>> +       const GDBusPropertyTable *properties;
>>
>>          transport = g_new0(struct media_transport, 1);
>>          if (device)
>> @@ -2701,9 +2733,14 @@ struct media_transport *media_transport_create(struct btd_device *device,
>>                          goto fail;
>>          }
>>
>> +       if (btd_opts.testing && ops->test_properties)
>> +               properties = ops->test_properties;
>> +       else
>> +               properties = ops->properties;
>> +
>>          if (g_dbus_register_interface(btd_get_dbus_connection(),
>>                                  transport->path, MEDIA_TRANSPORT_INTERFACE,
>> -                               transport_methods, NULL, ops->properties,
>> +                               transport_methods, NULL, properties,
>>                                  transport, media_transport_free) == FALSE) {
>>                  error("Could not register transport %s", transport->path);
>>                  goto fail;
>> --
>> 2.43.0
>>
>>
>

-- 
Frédéric Danis
Senior Software Engineer

Collabora Ltd.
Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, United Kingdom
Registered in England & Wales, no. 5513718


[-- Attachment #2: no-state-check-543.log --]
[-- Type: text/x-log, Size: 11845 bytes --]

bluetoothd[487726]: [:1.5304:method_call] > org.bluez.MediaTransport1.Acquire [#101]
bluetoothd[487726]: profiles/audio/transport.c:media_owner_create() Owner created: sender=:1.5304
bluetoothd[487726]: profiles/audio/transport.c:media_transport_resume() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0/fd0 Owner :1.5304
bluetoothd[487726]: src/shared/bap.c:bap_queue_req() req 0x50600008c360 (op 0x03) queue 0x5030000bc650
bluetoothd[487726]: profiles/audio/transport.c:transport_set_state() State changed /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0/fd0: TRANSPORT_STATE_IDLE -> TRANSPORT_STATE_REQUESTING
bluetoothd[487726]: profiles/audio/transport.c:media_request_create() Request created: method=Acquire id=23
bluetoothd[487726]: profiles/audio/transport.c:media_owner_add() Owner :1.5304 Request Acquire
bluetoothd[487726]: profiles/audio/transport.c:media_transport_set_owner() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0/fd0 Owner :1.5304
bluetoothd[487726]: src/shared/bap.c:bap_process_queue() 
bluetoothd[487726]: src/shared/bap.c:bap_send() req 0x50600008c360 len 8
bluetoothd[487726]: src/shared/att.c:can_read_data() (chan 0x5080000162a0) ATT PDU received: 0x1b
bluetoothd[487726]: src/shared/bap.c:bap_process_queue() 
bluetoothd[487726]: src/shared/att.c:can_read_data() (chan 0x5080000162a0) ATT PDU received: 0x1b
bluetoothd[487726]: src/shared/bap.c:bap_ep_set_status() ASE status: ep 0x504000059690 id 0x01 handle 0x0121 state enabling len 7
bluetoothd[487726]: src/shared/bap.c:ep_status_metadata() CIS 0x00 CIG 0x00 metadata len 4
bluetoothd[487726]: profiles/audio/bap.c:bap_state() stream 0x50e000010ea0: qos(2) -> enabling(3)
bluetoothd[487726]: profiles/audio/transport.c:bap_state_changed() stream 0x50e000010ea0: qos(2) -> enabling(3)
bluetoothd[487726]: profiles/audio/bap.c:bap_update_cig() adapter 0x513000000200 CIG 0x0
bluetoothd[487726]: profiles/audio/bap.c:update_cig_setup_enable() 0x50f000007930
bluetoothd[487726]: profiles/audio/bap.c:setup_create_io() setup 0x50f000007930 stream 0x50e000010ea0 defer false
bluetoothd[487726]: src/shared/bap.c:bt_bap_stream_io_get_qos() in (nil) out 0x50e000010ed8
bluetoothd[487726]: profiles/audio/bap.c:setup_accept_io() stream 0x50e000010ea0 fd 26 defer true
bluetoothd[487726]: profiles/audio/bap.c:iso_connect_cb() ISO connected
bluetoothd[487726]: profiles/audio/bap.c:iso_connect_cb() ISO connected
bluetoothd[487726]: profiles/audio/bap.c:iso_connect_cb() Unable to set IO
bluetoothd[487726]: src/shared/bap.c:bap_stream_io_detach() stream 0x50e000010dc0
bluetoothd[487726]: src/shared/att.c:can_read_data() (chan 0x5080000162a0) ATT PDU received: 0x1b
bluetoothd[487726]: src/shared/bap.c:bap_ep_set_status() ASE status: ep 0x504000059690 id 0x01 handle 0x0121 state streaming len 7
bluetoothd[487726]: src/shared/bap.c:ep_status_metadata() CIS 0x00 CIG 0x00 metadata len 4
bluetoothd[487726]: profiles/audio/bap.c:bap_state() stream 0x50e000010ea0: enabling(3) -> streaming(4)
bluetoothd[487726]: profiles/audio/transport.c:bap_state_changed() stream 0x50e000010ea0: enabling(3) -> streaming(4)
bluetoothd[487726]: profiles/audio/transport.c:bap_metadata_changed() stream 0x50e000010ea0: metadata 0x5020000f4910 old (nil)
bluetoothd[487726]: /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0/fd0: fd(26) ready
bluetoothd[487726]: profiles/audio/transport.c:transport_update_playing() /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0/fd0 State=TRANSPORT_STATE_REQUESTING Playing=1
bluetoothd[487726]: profiles/audio/transport.c:bap_resume_complete() stream 0x50e000010ea0 owner 0x5030000d8700 resume complete
bluetoothd[487726]: [signal] org.freedesktop.DBus.Properties.PropertiesChanged
bluetoothd[487726]: [:1.5304:method_return] < [#101]
bluetoothd[487726]: profiles/audio/transport.c:media_owner_remove() Owner :1.5304 Request Acquire
bluetoothd[487726]: profiles/audio/transport.c:transport_set_state() State changed /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0/fd0: TRANSPORT_STATE_REQUESTING -> TRANSPORT_STATE_ACTIVE
bluetoothd[487726]: [signal] org.freedesktop.DBus.Properties.PropertiesChanged
bluetoothd[487726]: [:1.5304:method_call] > org.bluez.MediaTransport1.Acquire [#102]
bluetoothd[487726]: profiles/audio/transport.c:media_owner_create() Owner created: sender=:1.5304
bluetoothd[487726]: profiles/audio/transport.c:media_transport_resume() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1 Owner :1.5304
bluetoothd[487726]: src/shared/bap.c:bap_queue_req() req 0x50600008c8a0 (op 0x03) queue 0x5030000bc650
bluetoothd[487726]: profiles/audio/transport.c:transport_set_state() State changed /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1: TRANSPORT_STATE_IDLE -> TRANSPORT_STATE_REQUESTING
bluetoothd[487726]: profiles/audio/transport.c:media_request_create() Request created: method=Acquire id=24
bluetoothd[487726]: profiles/audio/transport.c:media_owner_add() Owner :1.5304 Request Acquire
bluetoothd[487726]: profiles/audio/transport.c:media_transport_set_owner() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1 Owner :1.5304
bluetoothd[487726]: src/shared/bap.c:bap_process_queue() 
bluetoothd[487726]: src/shared/bap.c:bap_send() req 0x50600008c8a0 len 8
bluetoothd[487726]: src/shared/att.c:can_read_data() (chan 0x5080000162a0) ATT PDU received: 0x1b
bluetoothd[487726]: src/shared/bap.c:bap_process_queue() 
bluetoothd[487726]: src/shared/att.c:can_read_data() (chan 0x5080000162a0) ATT PDU received: 0x1b
bluetoothd[487726]: src/shared/bap.c:bap_ep_set_status() ASE status: ep 0x504000059790 id 0x0a handle 0x0127 state enabling len 7
bluetoothd[487726]: src/shared/bap.c:ep_status_metadata() CIS 0x00 CIG 0x00 metadata len 4
bluetoothd[487726]: profiles/audio/bap.c:bap_state() stream 0x50e000010dc0: qos(2) -> enabling(3)
bluetoothd[487726]: profiles/audio/transport.c:bap_state_changed() stream 0x50e000010dc0: qos(2) -> enabling(3)
bluetoothd[487726]: profiles/audio/bap.c:bap_update_cig() adapter 0x513000000200 CIG 0x0
bluetoothd[487726]: profiles/audio/bap.c:bap_update_cig() setup 0x50f000007930 stream 0x50e000010ea0 busy
bluetoothd[487726]: profiles/audio/bap.c:update_cig_setup_enable() 0x50f000007a20
bluetoothd[487726]: profiles/audio/bap.c:setup_create_io() setup 0x50f000007a20 stream 0x50e000010dc0 defer false
bluetoothd[487726]: src/shared/bap.c:bt_bap_stream_io_get_qos() in 0x50e000010df8 out (nil)
bluetoothd[487726]: profiles/audio/bap.c:setup_connect_io() setup 0x50f000007a20 stream 0x50e000010dc0 io already up

bluetoothd[487726]: [:1.493:method_call] > org.bluez.Device1.Disconnect [#49547]
bluetoothd[487726]: src/device.c:device_set_auto_connect() 9C:47:2A:39:56:FE auto connect: 0
bluetoothd[487726]: src/service.c:change_state() 0x506000075d40: device 9C:47:2A:39:56:FE profile rap state changed: connected -> disconnecting (0)
bluetoothd[487726]: profiles/ranging/rap.c:rap_disconnect()  
bluetoothd[487726]: src/service.c:change_state() 0x506000075d40: device 9C:47:2A:39:56:FE profile rap state changed: disconnecting -> disconnected (0)
bluetoothd[487726]: src/service.c:change_state() 0x506000075f20: device 9C:47:2A:39:56:FE profile bap state changed: connected -> disconnecting (0)
bluetoothd[487726]: profiles/audio/bap.c:ep_unregister() ep 0x5060000891e0 path /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0
bluetoothd[487726]: profiles/audio/bap.c:setup_free() 0x50f000007930
bluetoothd[487726]: profiles/audio/bap.c:setup_io_close() setup 0x50f000007930
bluetoothd[487726]: src/shared/bap.c:bap_stream_io_detach() stream 0x50e000010ea0
bluetoothd[487726]: src/shared/bap.c:stream_io_free() fd 26
bluetoothd[487726]: src/shared/bap.c:bap_queue_req() req 0x50600008cc60 (op 0x08) queue 0x5030000bc650
bluetoothd[487726]: [signal] org.freedesktop.DBus.ObjectManager.InterfacesRemoved
bluetoothd[487726]: profiles/audio/bap.c:ep_unregister() ep 0x506000088f40 path /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0
bluetoothd[487726]: profiles/audio/bap.c:setup_free() 0x50f000007a20
bluetoothd[487726]: profiles/audio/bap.c:setup_io_close() setup 0x50f000007a20
bluetoothd[487726]: src/shared/bap.c:bap_queue_req() req 0x50600008cde0 (op 0x08) queue 0x5030000d96f0
bluetoothd[487726]: [signal] org.freedesktop.DBus.ObjectManager.InterfacesRemoved
bluetoothd[487726]: src/shared/bap.c:bt_bap_detach() 0x510000004240
bluetoothd[487726]: src/shared/bap.c:bap_ucast_detach() stream 0x50e000010dc0 ep 0x504000059790
bluetoothd[487726]: profiles/audio/media.c:pac_clear() endpoint 0x50f000005a40 stream 0x50e000010dc0
bluetoothd[487726]: [:1.5304:method_call] < org.bluez.MediaEndpoint1.ClearConfiguration
bluetoothd[487726]: profiles/audio/transport.c:media_transport_remove_owner() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1 Owner :1.5304
bluetoothd[487726]: profiles/audio/transport.c:media_request_reply() Request Acquire Reply Input/output error
bluetoothd[487726]: [:1.5304:error] < org.bluez.Error.Failed [#102]
bluetoothd[487726]: profiles/audio/transport.c:media_owner_free() Owner :1.5304
bluetoothd[487726]: profiles/audio/transport.c:media_owner_remove() Owner :1.5304 Request Acquire
bluetoothd[487726]: profiles/audio/transport.c:media_transport_suspend() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1 Owner 
bluetoothd[487726]: profiles/audio/transport.c:transport_set_state() State changed /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1: TRANSPORT_STATE_REQUESTING -> TRANSPORT_STATE_IDLE
bluetoothd[487726]: [signal] org.freedesktop.DBus.ObjectManager.InterfacesRemoved
bluetoothd[487726]: src/shared/bap.c:bap_ucast_detach() stream 0x50e000010ea0 ep 0x504000059690
bluetoothd[487726]: profiles/audio/media.c:pac_clear() endpoint 0x50f000005950 stream 0x50e000010ea0
bluetoothd[487726]: [:1.5304:method_call] < org.bluez.MediaEndpoint1.ClearConfiguration
bluetoothd[487726]: profiles/audio/transport.c:media_transport_remove_owner() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0/fd0 Owner :1.5304
bluetoothd[487726]: profiles/audio/transport.c:media_owner_free() Owner :1.5304
bluetoothd[487726]: profiles/audio/transport.c:media_transport_suspend() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0/fd0 Owner 
bluetoothd[487726]: profiles/audio/transport.c:transport_set_state() State changed /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0/fd0: TRANSPORT_STATE_ACTIVE -> TRANSPORT_STATE_IDLE
bluetoothd[487726]: [signal] org.freedesktop.DBus.ObjectManager.InterfacesRemoved
bluetoothd[487726]: profiles/audio/bass.c:bap_detached() 0x510000004240
bluetoothd[487726]: profiles/audio/bap.c:bap_detached() 0x510000004240
bluetoothd[487726]: src/service.c:change_state() 0x506000075f20: device 9C:47:2A:39:56:FE profile bap state changed: disconnecting -> disconnected (0)
bluetoothd[487726]: src/service.c:change_state() 0x506000076d60: device 9C:47:2A:39:56:FE profile bass state changed: connected -> disconnecting (0)
bluetoothd[487726]: profiles/audio/bass.c:bass_disconnect() 9C:47:2A:39:56:FE
bluetoothd[487726]: profiles/audio/bass.c:bass_detached() 0x50b0000474d0
bluetoothd[487726]: src/service.c:change_state() 0x506000076d60: device 9C:47:2A:39:56:FE profile bass state changed: disconnecting -> disconnected (0)
bluetoothd[487726]: src/service.c:change_state() 0x5060000771e0: device 9C:47:2A:39:56:FE profile gap-profile state changed: connected -> disconnecting (0)
bluetoothd[487726]: src/service.c:change_state() 0x5060000771e0: device 9C:47:2A:39:56:FE profile gap-profile state changed: disconnecting -> disconnected (0)
bluetoothd[487726]: src/service.c:change_state() 0x506000077480: device 9C:47:2A:39:56:FE profile deviceinfo state changed: connected -> disconnecting (0)
bluetoothd[487726]: src/service.c:change_state() 0x506000077480: device 9C:47:2A:39:56:FE profile deviceinfo state changed: disconnecting -> disconnected (0)


[-- Attachment #3: no-state-check-546.log --]
[-- Type: text/x-log, Size: 10241 bytes --]

bluetoothd[487726]: [:1.5289:method_call] > org.bluez.MediaTransport1.Acquire [#212]
bluetoothd[487726]: profiles/audio/transport.c:media_owner_create() Owner created: sender=:1.5289
bluetoothd[487726]: profiles/audio/transport.c:media_transport_resume() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1 Owner :1.5289
bluetoothd[487726]: src/shared/bap.c:bap_queue_req() req 0x506000065120 (op 0x03) queue 0x503000083d40
bluetoothd[487726]: profiles/audio/transport.c:transport_set_state() State changed /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1: TRANSPORT_STATE_IDLE -> TRANSPORT_STATE_REQUESTING
bluetoothd[487726]: profiles/audio/transport.c:media_request_create() Request created: method=Acquire id=14
bluetoothd[487726]: profiles/audio/transport.c:media_owner_add() Owner :1.5289 Request Acquire
bluetoothd[487726]: profiles/audio/transport.c:media_transport_set_owner() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1 Owner :1.5289
bluetoothd[487726]: src/shared/bap.c:bap_process_queue() 
bluetoothd[487726]: src/shared/bap.c:bap_send() req 0x506000065120 len 8
bluetoothd[487726]: src/shared/att.c:can_read_data() (chan 0x5080000106a0) ATT PDU received: 0x1b
bluetoothd[487726]: src/shared/bap.c:bap_process_queue() 
bluetoothd[487726]: src/shared/att.c:can_read_data() (chan 0x5080000106a0) ATT PDU received: 0x1b
bluetoothd[487726]: src/shared/bap.c:bap_ep_set_status() ASE status: ep 0x50400003eb10 id 0x0a handle 0x0127 state enabling len 7
bluetoothd[487726]: src/shared/bap.c:ep_status_metadata() CIS 0x00 CIG 0x00 metadata len 4
bluetoothd[487726]: profiles/audio/bap.c:bap_state() stream 0x50e00000a5a0: qos(2) -> enabling(3)
bluetoothd[487726]: profiles/audio/transport.c:bap_state_changed() stream 0x50e00000a5a0: qos(2) -> enabling(3)
bluetoothd[487726]: profiles/audio/bap.c:bap_update_cig() adapter 0x513000000200 CIG 0x0
bluetoothd[487726]: profiles/audio/bap.c:update_cig_setup_enable() 0x50f000005680
bluetoothd[487726]: profiles/audio/bap.c:setup_create_io() setup 0x50f000005680 stream 0x50e00000a5a0 defer false
bluetoothd[487726]: src/shared/bap.c:bt_bap_stream_io_get_qos() in 0x50e00000a5d8 out (nil)
bluetoothd[487726]: profiles/audio/bap.c:setup_accept_io() stream 0x50e00000a5a0 fd 26 defer true
bluetoothd[487726]: profiles/audio/bap.c:iso_connect_cb() ISO connected
bluetoothd[487726]: profiles/audio/bap.c:iso_connect_cb() ISO connected
bluetoothd[487726]: profiles/audio/bap.c:iso_connect_cb() Unable to set IO
bluetoothd[487726]: src/shared/bap.c:bap_stream_io_detach() stream 0x50e00000a5a0
bluetoothd[487726]: src/shared/bap.c:bap_queue_req() req 0x506000065480 (op 0x05) queue 0x503000083d40
bluetoothd[487726]: src/shared/bap.c:bap_process_queue() 
bluetoothd[487726]: src/shared/bap.c:bap_send() req 0x506000065480 len 3
bluetoothd[487726]: src/shared/att.c:can_read_data() (chan 0x5080000106a0) ATT PDU received: 0x1b
bluetoothd[487726]: src/shared/bap.c:bap_process_queue() 
bluetoothd[487726]: src/shared/att.c:can_read_data() (chan 0x5080000106a0) ATT PDU received: 0x1b
bluetoothd[487726]: src/shared/bap.c:bap_ep_set_status() ASE status: ep 0x50400003eb10 id 0x0a handle 0x0127 state disabling len 7
bluetoothd[487726]: src/shared/bap.c:ep_status_metadata() CIS 0x00 CIG 0x00 metadata len 4
bluetoothd[487726]: profiles/audio/bap.c:bap_state() stream 0x50e00000a5a0: enabling(3) -> disabling(5)
bluetoothd[487726]: profiles/audio/transport.c:bap_state_changed() stream 0x50e00000a5a0: enabling(3) -> disabling(5)
bluetoothd[487726]: src/shared/bap.c:bap_queue_req() req 0x5060000655a0 (op 0x06) queue 0x503000083d40
bluetoothd[487726]: src/shared/att.c:can_read_data() (chan 0x5080000106a0) ATT PDU received: 0x1b
bluetoothd[487726]: src/shared/bap.c:bap_ep_set_status() ASE status: ep 0x50400003eb10 id 0x0a handle 0x0127 state qos len 15
bluetoothd[487726]: src/shared/bap.c:ep_status_qos() CIG 0x00 CIS 0x00 interval 10000 framing 0x00 phys 0x02 rtn 2 latency 10 pd 40000
bluetoothd[487726]: src/shared/bap.c:bap_stream_update_io_links() stream 0x50e00000a5a0
bluetoothd[487726]: src/shared/bap.c:bap_ucast_io_link() stream 0x50e00000a680 link 0x50e00000a5a0
bluetoothd[487726]: profiles/audio/bap.c:bap_state() stream 0x50e00000a5a0: disabling(5) -> qos(2)
bluetoothd[487726]: profiles/audio/transport.c:bap_state_changed() stream 0x50e00000a5a0: disabling(5) -> qos(2)
bluetoothd[487726]: profiles/audio/bap.c:bap_update_cig() adapter 0x513000000200 CIG 0x0
bluetoothd[487726]: profiles/audio/bap.c:bap_update_cig() setup 0x50f000005680 stream 0x50e00000a5a0 busy
bluetoothd[487726]: src/shared/bap.c:bap_process_queue() 
bluetoothd[487726]: src/shared/bap.c:bap_send() req 0x5060000655a0 len 3
bluetoothd[487726]: src/shared/att.c:can_read_data() (chan 0x5080000106a0) ATT PDU received: 0x1b
bluetoothd[487726]: src/shared/bap.c:bap_process_queue() 

bluetoothd[487726]: [:1.493:method_call] > org.bluez.Device1.Disconnect [#49428]
bluetoothd[487726]: src/device.c:device_set_auto_connect() 9C:47:2A:39:56:FE auto connect: 0
bluetoothd[487726]: src/service.c:change_state() 0x506000051b00: device 9C:47:2A:39:56:FE profile rap state changed: connected -> disconnecting (0)
bluetoothd[487726]: profiles/ranging/rap.c:rap_disconnect()  
bluetoothd[487726]: src/service.c:change_state() 0x506000051b00: device 9C:47:2A:39:56:FE profile rap state changed: disconnecting -> disconnected (0)
bluetoothd[487726]: src/service.c:change_state() 0x506000051ce0: device 9C:47:2A:39:56:FE profile bap state changed: connected -> disconnecting (0)
bluetoothd[487726]: profiles/audio/bap.c:ep_unregister() ep 0x506000061fa0 path /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0
bluetoothd[487726]: profiles/audio/bap.c:setup_free() 0x50f000005590
bluetoothd[487726]: profiles/audio/bap.c:setup_io_close() setup 0x50f000005590
bluetoothd[487726]: src/shared/bap.c:bap_stream_io_detach() stream 0x50e00000a680
bluetoothd[487726]: src/shared/bap.c:bap_stream_io_detach() stream 0x50e00000a5a0
bluetoothd[487726]: src/shared/bap.c:stream_io_free() fd 26
bluetoothd[487726]: profiles/audio/transport.c:transport_bap_update_links_uc() stream 0x50e00000a5a0 linked true
bluetoothd[487726]: profiles/audio/transport.c:transport_bap_update_links_uc() stream 0x50e00000a680 linked true
bluetoothd[487726]: src/shared/bap.c:bap_queue_req() req 0x5060000659c0 (op 0x08) queue 0x503000083d40
bluetoothd[487726]: [signal] org.freedesktop.DBus.ObjectManager.InterfacesRemoved
bluetoothd[487726]: profiles/audio/bap.c:ep_unregister() ep 0x506000061d00 path /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0
bluetoothd[487726]: profiles/audio/bap.c:setup_free() 0x50f000005680
bluetoothd[487726]: profiles/audio/bap.c:setup_io_close() setup 0x50f000005680
bluetoothd[487726]: src/shared/bap.c:bap_queue_req() req 0x506000065b40 (op 0x08) queue 0x5030000a0a20
bluetoothd[487726]: [signal] org.freedesktop.DBus.ObjectManager.InterfacesRemoved
bluetoothd[487726]: src/shared/bap.c:bt_bap_detach() 0x510000003c40
bluetoothd[487726]: src/shared/bap.c:bap_ucast_detach() stream 0x50e00000a5a0 ep 0x50400003eb10
bluetoothd[487726]: profiles/audio/media.c:pac_clear() endpoint 0x50f0000035b0 stream 0x50e00000a5a0
bluetoothd[487726]: [signal] org.freedesktop.DBus.Properties.PropertiesChanged
bluetoothd[487726]: [signal] org.freedesktop.DBus.Properties.PropertiesChanged
bluetoothd[487726]: [:1.5289:method_call] < org.bluez.MediaEndpoint1.ClearConfiguration
bluetoothd[487726]: profiles/audio/transport.c:media_transport_remove_owner() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1 Owner :1.5289
bluetoothd[487726]: profiles/audio/transport.c:media_request_reply() Request Acquire Reply Input/output error
bluetoothd[487726]: [:1.5289:error] < org.bluez.Error.Failed [#212]
bluetoothd[487726]: profiles/audio/transport.c:linked_transport_remove_owner() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_sink0/fd0 Owner :1.5289
bluetoothd[487726]: profiles/audio/transport.c:media_owner_free() Owner :1.5289
bluetoothd[487726]: profiles/audio/transport.c:media_owner_remove() Owner :1.5289 Request Acquire
bluetoothd[487726]: profiles/audio/transport.c:media_transport_suspend() Transport /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1 Owner 
bluetoothd[487726]: profiles/audio/transport.c:transport_set_state() State changed /org/bluez/hci0/dev_9C_47_2A_39_56_FE/pac_source0/fd1: TRANSPORT_STATE_REQUESTING -> TRANSPORT_STATE_IDLE
bluetoothd[487726]: [signal] org.freedesktop.DBus.ObjectManager.InterfacesRemoved
bluetoothd[487726]: src/shared/bap.c:bap_ucast_detach() stream 0x50e00000a680 ep 0x50400003ea10
bluetoothd[487726]: profiles/audio/media.c:pac_clear() endpoint 0x50f0000034c0 stream 0x50e00000a680
bluetoothd[487726]: [:1.5289:method_call] < org.bluez.MediaEndpoint1.ClearConfiguration
bluetoothd[487726]: [signal] org.freedesktop.DBus.ObjectManager.InterfacesRemoved
bluetoothd[487726]: profiles/audio/bass.c:bap_detached() 0x510000003c40
bluetoothd[487726]: profiles/audio/bap.c:bap_detached() 0x510000003c40
bluetoothd[487726]: src/service.c:change_state() 0x506000051ce0: device 9C:47:2A:39:56:FE profile bap state changed: disconnecting -> disconnected (0)
bluetoothd[487726]: src/service.c:change_state() 0x506000052b20: device 9C:47:2A:39:56:FE profile bass state changed: connected -> disconnecting (0)
bluetoothd[487726]: profiles/audio/bass.c:bass_disconnect() 9C:47:2A:39:56:FE
bluetoothd[487726]: profiles/audio/bass.c:bass_detached() 0x50b00000b930
bluetoothd[487726]: src/service.c:change_state() 0x506000052b20: device 9C:47:2A:39:56:FE profile bass state changed: disconnecting -> disconnected (0)
bluetoothd[487726]: src/service.c:change_state() 0x506000052fa0: device 9C:47:2A:39:56:FE profile gap-profile state changed: connected -> disconnecting (0)
bluetoothd[487726]: src/service.c:change_state() 0x506000052fa0: device 9C:47:2A:39:56:FE profile gap-profile state changed: disconnecting -> disconnected (0)
bluetoothd[487726]: src/service.c:change_state() 0x506000053240: device 9C:47:2A:39:56:FE profile deviceinfo state changed: connected -> disconnecting (0)
bluetoothd[487726]: src/service.c:change_state() 0x506000053240: device 9C:47:2A:39:56:FE profile deviceinfo state changed: disconnecting -> disconnected (0)


^ permalink raw reply

* Re: [PATCH] Bluetooth: btusb: Add Mercusys MA530 for Realtek RTL8761BUV
From: patchwork-bot+bluetooth @ 2026-04-23 19:40 UTC (permalink / raw)
  To: Hrvoje Nuic; +Cc: marcel, luiz.dentz, linux-bluetooth, linux-kernel
In-Reply-To: <20260422212647.62497-1-hrvoje.nuic@gmail.com>

Hello:

This patch was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Wed, 22 Apr 2026 23:26:47 +0200 you wrote:
> Add the USB ID for the Mercusys MA530 Bluetooth adapter. The device uses
> a Realtek RTL8761BUV controller and works with the existing Realtek setup
> path.
> 
> The device reports vendor ID 0x2c4e and product ID 0x0115, and loads the
> rtl_bt/rtl8761bu_fw.bin firmware successfully with this quirk.
> 
> [...]

Here is the summary with links:
  - Bluetooth: btusb: Add Mercusys MA530 for Realtek RTL8761BUV
    https://git.kernel.org/bluetooth/bluetooth-next/c/0105c3e2a97e

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH v1] mediatek MT7925: update bluetooth firmware to 20260414153243
From: Sean Wang @ 2026-04-23 17:36 UTC (permalink / raw)
  To: David Ruth
  Cc: Chris Lu (陸稚泓), ben, dwmw2, johan.hedberg,
	jwboyer, linux-bluetooth, linux-firmware, linux-kernel,
	linux-mediatek, marcel, ss.wu, steve.lee, will-cy.Lee
In-Reply-To: <CAKHmtrSaDR4fBxN0mvEzYPHoOwr2rrtnOJW6fsrxzsBAeHTjQg@mail.gmail.com>

On Wed, Apr 22, 2026 at 5:19 PM David Ruth <druth@chromium.org> wrote:
>
> Tested-by: David Ruth <druth@chromium.org>
>

Merged
https://gitlab.com/kernel-firmware/linux-firmware/-/merge_requests/1015

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox