diff mbox series

[RFC,BlueZ] plugins: Add new plugin to manage wake policy

Message ID 20240426132342.732682-1-frederic.danis@collabora.com (mailing list archive)
State New, archived
Headers show
Series [RFC,BlueZ] plugins: Add new plugin to manage wake policy | expand

Checks

Context Check Description
tedd_an/pre-ci_am success Success
tedd_an/CheckPatch success CheckPatch PASS
tedd_an/GitLint success Gitlint PASS
tedd_an/BuildEll success Build ELL PASS
tedd_an/BluezMake success Bluez Make PASS
tedd_an/MakeCheck success Bluez Make Check PASS
tedd_an/MakeDistcheck success Make Distcheck PASS
tedd_an/CheckValgrind success Check Valgrind PASS
tedd_an/CheckSmatch success CheckSparse PASS
tedd_an/bluezmakeextell success Make External ELL PASS
tedd_an/IncrementalBuild success Incremental Build PASS
tedd_an/ScanBuild success Scan Build PASS

Commit Message

Frédéric Danis April 26, 2024, 1:23 p.m. UTC
By default all class of devices providing the correct service (HID, HoG)
are allowed to wake up the host.
This plugin allows to choose which class of devices are allowed or not to
do it.

E.g. only the Peripheral class, or more specifically the Peripheral
Joystick and Gamepad devices, could be allowed but not the other type of
devices like e.g. the headsets.
For the first case, all Major classes in /etc/bluetooth/wake-policy.conf
except Peripheral should be uncommented and set to 'false', while for the
second case Peripheral should also be uncommented and set to '01;02'.
---

I send this as an RFC as I'm not sure creating a wake policy plugin is the
correct way to get it merged upstream.

Could it be better to move the device_set_wake_support() call from
profiles/input/{device,hog}.c files to src/device.c:device_svc_resolved()
which could perform Service and Class of Device checks?

Any advice much appreciated.

 Makefile.plugins         |   4 +
 plugins/wake-policy.c    | 180 +++++++++++++++++++++++++++++++++++++++
 plugins/wake-policy.conf |  21 +++++
 src/device.c             |   2 +-
 src/device.h             |   1 +
 5 files changed, 207 insertions(+), 1 deletion(-)
 create mode 100644 plugins/wake-policy.c
 create mode 100644 plugins/wake-policy.conf

Comments

bluez.test.bot@gmail.com April 26, 2024, 2:58 p.m. UTC | #1
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=848254

---Test result---

Test Summary:
CheckPatch                    PASS      0.47 seconds
GitLint                       PASS      0.22 seconds
BuildEll                      PASS      24.42 seconds
BluezMake                     PASS      1618.74 seconds
MakeCheck                     PASS      12.85 seconds
MakeDistcheck                 PASS      175.22 seconds
CheckValgrind                 PASS      244.56 seconds
CheckSmatch                   PASS      346.87 seconds
bluezmakeextell               PASS      117.63 seconds
IncrementalBuild              PASS      1382.31 seconds
ScanBuild                     PASS      974.63 seconds



---
Regards,
Linux Bluetooth
Luiz Augusto von Dentz April 26, 2024, 3:05 p.m. UTC | #2
Hi Frédéric,

On Fri, Apr 26, 2024 at 9:23 AM Frédéric Danis
<frederic.danis@collabora.com> wrote:
>
> By default all class of devices providing the correct service (HID, HoG)
> are allowed to wake up the host.
> This plugin allows to choose which class of devices are allowed or not to
> do it.
>
> E.g. only the Peripheral class, or more specifically the Peripheral
> Joystick and Gamepad devices, could be allowed but not the other type of
> devices like e.g. the headsets.
> For the first case, all Major classes in /etc/bluetooth/wake-policy.conf
> except Peripheral should be uncommented and set to 'false', while for the
> second case Peripheral should also be uncommented and set to '01;02'.
> ---
>
> I send this as an RFC as I'm not sure creating a wake policy plugin is the
> correct way to get it merged upstream.
>
> Could it be better to move the device_set_wake_support() call from
> profiles/input/{device,hog}.c files to src/device.c:device_svc_resolved()
> which could perform Service and Class of Device checks?
>
> Any advice much appreciated.

At first I didn't get what you were trying to do since we do already
allow to overwrite via Device.WakeAllowed property, but I guess you
want to generalize it so other classes, other than just input, could
wake up the host?

In that case I would suggest doing it directly via main.conf and
perhaps allow something using a UUIDs:

WakeAllowed=true/false/uuid1, uuid2...

That said we might need to introduce support for flagging driver that
do really support setting wakeallowed property otherwise
WakeAllowed=true may have a bad impact if users try to enable it for
everything including SDP for example, or we could just got with the
uuid list and default to HID and HoG for now and in case it is set
empty than it disables but there is no way to simply enable to
everything, thoughts?

>  Makefile.plugins         |   4 +
>  plugins/wake-policy.c    | 180 +++++++++++++++++++++++++++++++++++++++
>  plugins/wake-policy.conf |  21 +++++
>  src/device.c             |   2 +-
>  src/device.h             |   1 +
>  5 files changed, 207 insertions(+), 1 deletion(-)
>  create mode 100644 plugins/wake-policy.c
>  create mode 100644 plugins/wake-policy.conf
>
> diff --git a/Makefile.plugins b/Makefile.plugins
> index 4aa2c9c92..fbd4e4155 100644
> --- a/Makefile.plugins
> +++ b/Makefile.plugins
> @@ -11,6 +11,10 @@ builtin_sources += plugins/autopair.c
>  builtin_modules += policy
>  builtin_sources += plugins/policy.c
>
> +builtin_modules += wake_policy
> +builtin_sources += plugins/wake-policy.c
> +EXTRA_DIST += plugins/wake-policy.conf
> +
>  if ADMIN
>  builtin_modules += admin
>  builtin_sources += plugins/admin.c
> diff --git a/plugins/wake-policy.c b/plugins/wake-policy.c
> new file mode 100644
> index 000000000..34e77c615
> --- /dev/null
> +++ b/plugins/wake-policy.c
> @@ -0,0 +1,180 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + *
> + *  BlueZ - Bluetooth protocol stack for Linux
> + *
> + *  Copyright (C) 2024 Frédéric Danis <frederic.danis@collabora.com>
> + *
> + *
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#include <stdbool.h>
> +
> +#include <glib.h>
> +
> +#include "bluetooth/bluetooth.h"
> +
> +#include "src/plugin.h"
> +#include "src/adapter.h"
> +#include "src/device.h"
> +#include "src/log.h"
> +#include "src/textfile.h"
> +
> +static GKeyFile * key_file;
> +
> +static gboolean is_allowed_to_wake(const char *major, guint minor)
> +{
> +       guint *minor_list;
> +       gsize length;
> +       gboolean allowed = false;
> +       GError *gerr = NULL;
> +
> +       if (!g_key_file_has_key(key_file, "WakeAllowed", major, NULL))
> +               return true;
> +
> +       allowed = g_key_file_get_boolean(key_file, "WakeAllowed", major, &gerr);
> +       if (!gerr)
> +               return allowed;
> +
> +       g_error_free(gerr);
> +       gerr = NULL;
> +
> +       minor_list = (guint *)g_key_file_get_integer_list(key_file,
> +                                               "WakeAllowed",
> +                                               major, &length, &gerr);
> +       if (gerr) {
> +               DBG("Failed to get allowed minor list for %s", major);
> +               return false;
> +       }
> +
> +       for (gsize i = 0; i < length; i++) {
> +               if (minor_list[i] == minor) {
> +                       allowed = true;
> +                       break;
> +               }
> +       }
> +
> +       return allowed;
> +}
> +
> +static const struct {
> +       uint8_t val;
> +       const char *str;
> +} major_class_table[] = {
> +       { 0x00, "Miscellaneous" },
> +       { 0x01, "Computer"      },
> +       { 0x02, "Phone"         },
> +       { 0x03, "LAN/Network"   },
> +       { 0x04, "Audio/Video"   },
> +       { 0x05, "Peripheral"    },
> +       { 0x06, "Imaging"       },
> +       { 0x07, "Wearable"      },
> +       { 0x08, "Toy"           },
> +       { 0x09, "Health"        },
> +       { 0x1f, "Uncategorized" },
> +       { }
> +};
> +
> +static gboolean is_class_allowed_to_wake(uint32_t class)
> +{
> +       uint8_t major = (class & 0x1f00) >> 8;
> +       uint8_t minor = (class & 0x00fc) >> 2;
> +
> +       if ((major >= 0x01 && major <= 0x09) || major == 0x1f)
> +               return is_allowed_to_wake(major_class_table[major].str, minor);
> +
> +       return true;
> +}
> +
> +static void wake_policy_device_resolved(struct btd_adapter *adapter,
> +                                    struct btd_device *device)
> +{
> +       char *filename;
> +       GKeyFile *device_key_file;
> +       GError *gerr = NULL;
> +
> +       if (key_file == NULL)
> +               return;
> +
> +       // Does device support to wake the host?
> +       if (!device_get_wake_support(device))
> +               return;
> +
> +       // Check if WakeAllowed has already been stored,
> +       // if yes do not change it
> +       filename = btd_device_get_storage_path(device, "info");
> +       device_key_file = g_key_file_new();
> +       if (!g_key_file_load_from_file(device_key_file, filename, 0, &gerr)) {
> +               error("Unable to load key file from %s: (%s)", filename,
> +                                                       gerr->message);
> +               g_clear_error(&gerr);
> +       } else {
> +               if (g_key_file_has_key(device_key_file, "General",
> +                                       "WakeAllowed", NULL)) {
> +                       DBG("%s WakeAllowed already stored",
> +                               device_get_path(device));
> +                       return;
> +               }
> +       }
> +       g_key_file_free(device_key_file);
> +       g_free(filename);
> +
> +       // Check if Class of Device is allowed to wake up the host
> +       if (!is_class_allowed_to_wake(btd_device_get_class(device))) {
> +               DBG("%s Force WakeAllowed to false", device_get_path(device));
> +               device_set_wake_override(device, false);
> +               device_set_wake_allowed(device, false, -1U);
> +       }
> +}
> +
> +static int wake_policy_probe(struct btd_adapter *adapter)
> +{
> +       GError *gerr = NULL;
> +
> +       DBG("");
> +       key_file = g_key_file_new();
> +       if (!g_key_file_load_from_file(key_file,
> +                                       CONFIGDIR "/wake-policy.conf",
> +                                       0,
> +                                       &gerr)) {
> +               error("Unable to load key file from %s: (%s)",
> +                       CONFIGDIR "/wake-policy.conf",
> +                       gerr->message);
> +               g_clear_error(&gerr);
> +               g_key_file_free(key_file);
> +               key_file = NULL;
> +       }
> +
> +       return 0;
> +}
> +
> +static void wake_policy_remove(struct btd_adapter *adapter)
> +{
> +       DBG("");
> +       if (key_file)
> +               g_key_file_free(key_file);
> +}
> +
> +static struct btd_adapter_driver wake_policy_driver = {
> +       .name   = "wake-policy",
> +       .probe  = wake_policy_probe,
> +       .remove = wake_policy_remove,
> +       .device_resolved = wake_policy_device_resolved,
> +};
> +
> +static int wake_policy_init(void)
> +{
> +       return btd_register_adapter_driver(&wake_policy_driver);
> +}
> +
> +static void wake_policy_exit(void)
> +{
> +       btd_unregister_adapter_driver(&wake_policy_driver);
> +}
> +
> +BLUETOOTH_PLUGIN_DEFINE(wake_policy, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW,
> +                       wake_policy_init, wake_policy_exit)
> diff --git a/plugins/wake-policy.conf b/plugins/wake-policy.conf
> new file mode 100644
> index 000000000..e6220bad1
> --- /dev/null
> +++ b/plugins/wake-policy.conf
> @@ -0,0 +1,21 @@
> +[WakeAllowed]
> +# By default each Class of Devices providing the correct service (HID, HoG) is
> +# allowed to wake up the host.
> +# The following values are used to control the default WakeAllowed value based
> +# on the Class of Device.
> +# It is still possible for the user to override the WakeAllowed value per
> +# device afterwards.
> +#
> +# Possible values for each Major Class:
> +# true,false,<Minor Class allowed list (integer separated by ;)>
> +
> +#Computer=true
> +#Phone=true
> +#LAN/Network=true
> +#Audio/Video=true
> +#Peripheral=true
> +#Imaging=true
> +#Wearable=true
> +#Toy=true
> +#Health=true
> +#Uncategorized=true
> diff --git a/src/device.c b/src/device.c
> index 1d4b8ab36..d970745f5 100644
> --- a/src/device.c
> +++ b/src/device.c
> @@ -1500,7 +1500,7 @@ dev_property_advertising_data_exist(const GDBusPropertyTable *property,
>         return bt_ad_has_data(device->ad, NULL);
>  }
>
> -static bool device_get_wake_support(struct btd_device *device)
> +bool device_get_wake_support(struct btd_device *device)
>  {
>         return device->wake_support;
>  }
> diff --git a/src/device.h b/src/device.h
> index 5722ca9ca..f8c744baf 100644
> --- a/src/device.h
> +++ b/src/device.h
> @@ -149,6 +149,7 @@ void device_set_wake_support(struct btd_device *device, bool wake_support);
>  void device_set_wake_override(struct btd_device *device, bool wake_override);
>  void device_set_wake_allowed(struct btd_device *device, bool wake_allowed,
>                              guint32 id);
> +bool device_get_wake_support(struct btd_device *device);
>  void device_set_refresh_discovery(struct btd_device *dev, bool refresh);
>
>  typedef void (*disconnect_watch) (struct btd_device *device, gboolean removal,
> --
> 2.34.1
>
>
Luiz Augusto von Dentz April 26, 2024, 4:04 p.m. UTC | #3
Hi Frédéric,

On Fri, Apr 26, 2024 at 11:42 AM Frédéric Danis
<frederic.danis@collabora.com> wrote:
>
> Hi Luiz,
>
> On 26/04/2024 17:05, Luiz Augusto von Dentz wrote:
>
> Hi Frédéric,
>
> On Fri, Apr 26, 2024 at 9:23 AM Frédéric Danis
> <frederic.danis@collabora.com> wrote:
>
> By default all class of devices providing the correct service (HID, HoG)
> are allowed to wake up the host.
> This plugin allows to choose which class of devices are allowed or not to
> do it.
>
> E.g. only the Peripheral class, or more specifically the Peripheral
> Joystick and Gamepad devices, could be allowed but not the other type of
> devices like e.g. the headsets.
> For the first case, all Major classes in /etc/bluetooth/wake-policy.conf
> except Peripheral should be uncommented and set to 'false', while for the
> second case Peripheral should also be uncommented and set to '01;02'.
> ---
>
> I send this as an RFC as I'm not sure creating a wake policy plugin is the
> correct way to get it merged upstream.
>
> Could it be better to move the device_set_wake_support() call from
> profiles/input/{device,hog}.c files to src/device.c:device_svc_resolved()
> which could perform Service and Class of Device checks?
>
> Any advice much appreciated.
>
> At first I didn't get what you were trying to do since we do already
> allow to overwrite via Device.WakeAllowed property, but I guess you
> want to generalize it so other classes, other than just input, could
> wake up the host?
>
>
> I guess I was unclear, it's the other way round, I want to prevent other type of device like headsets or earphones which are providing the HID or HoG service from being able to wake up the host by default.
>
> In other words, I'm expecting that the default behavior for input devices (like keyboard or gamepad) is to be able to wake up the host (as currently) while other classes of device should not, unless user change it using Device.WakeAllowed property.
> E.g. controllers are allowed to wake, everything else defaults to WakeAllowed off.
>
> We found this with the Steamdeck where users accidentally turn on their deck when powering on some BT headsets, e.g. Sony XM5 headphones.

hmm, does it implement HoG to do that? That said I don't think it is a
good idea to break combo devices if they do that, so think this shall
be handled via user preference and then the user can disable
WakeAllowed, or perhaps we default to false in case of combo devices
so the user has to opt-in to enable. Btw, using the class alone may
not be enough to catch exactly what is the device behind it, beside
that doesn't apply to LE devices where we would probably need to look
into the appearance, still feel like the wrong thing to do to assume
anything just based on that.

Another idea would be perhaps add an authorization request to ask the
user to confirm setting wake_allowed flag when a driver request it,
that way the user can authorize, or not, during the first time it
connects to the device so we learn its preferences, etc.

> I hope I am a little bit more understandable :)
>
> In that case I would suggest doing it directly via main.conf and
> perhaps allow something using a UUIDs:
>
> WakeAllowed=true/false/uuid1, uuid2...
>
> That said we might need to introduce support for flagging driver that
> do really support setting wakeallowed property otherwise
> WakeAllowed=true may have a bad impact if users try to enable it for
> everything including SDP for example, or we could just got with the
> uuid list and default to HID and HoG for now and in case it is set
> empty than it disables but there is no way to simply enable to
> everything, thoughts?
>
>  Makefile.plugins         |   4 +
>  plugins/wake-policy.c    | 180 +++++++++++++++++++++++++++++++++++++++
>  plugins/wake-policy.conf |  21 +++++
>  src/device.c             |   2 +-
>  src/device.h             |   1 +
>  5 files changed, 207 insertions(+), 1 deletion(-)
>  create mode 100644 plugins/wake-policy.c
>  create mode 100644 plugins/wake-policy.conf
>
> diff --git a/Makefile.plugins b/Makefile.plugins
> index 4aa2c9c92..fbd4e4155 100644
> --- a/Makefile.plugins
> +++ b/Makefile.plugins
> @@ -11,6 +11,10 @@ builtin_sources += plugins/autopair.c
>  builtin_modules += policy
>  builtin_sources += plugins/policy.c
>
> +builtin_modules += wake_policy
> +builtin_sources += plugins/wake-policy.c
> +EXTRA_DIST += plugins/wake-policy.conf
> +
>  if ADMIN
>  builtin_modules += admin
>  builtin_sources += plugins/admin.c
> diff --git a/plugins/wake-policy.c b/plugins/wake-policy.c
> new file mode 100644
> index 000000000..34e77c615
> --- /dev/null
> +++ b/plugins/wake-policy.c
> @@ -0,0 +1,180 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + *
> + *  BlueZ - Bluetooth protocol stack for Linux
> + *
> + *  Copyright (C) 2024 Frédéric Danis <frederic.danis@collabora.com>
> + *
> + *
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#include <stdbool.h>
> +
> +#include <glib.h>
> +
> +#include "bluetooth/bluetooth.h"
> +
> +#include "src/plugin.h"
> +#include "src/adapter.h"
> +#include "src/device.h"
> +#include "src/log.h"
> +#include "src/textfile.h"
> +
> +static GKeyFile * key_file;
> +
> +static gboolean is_allowed_to_wake(const char *major, guint minor)
> +{
> +       guint *minor_list;
> +       gsize length;
> +       gboolean allowed = false;
> +       GError *gerr = NULL;
> +
> +       if (!g_key_file_has_key(key_file, "WakeAllowed", major, NULL))
> +               return true;
> +
> +       allowed = g_key_file_get_boolean(key_file, "WakeAllowed", major, &gerr);
> +       if (!gerr)
> +               return allowed;
> +
> +       g_error_free(gerr);
> +       gerr = NULL;
> +
> +       minor_list = (guint *)g_key_file_get_integer_list(key_file,
> +                                               "WakeAllowed",
> +                                               major, &length, &gerr);
> +       if (gerr) {
> +               DBG("Failed to get allowed minor list for %s", major);
> +               return false;
> +       }
> +
> +       for (gsize i = 0; i < length; i++) {
> +               if (minor_list[i] == minor) {
> +                       allowed = true;
> +                       break;
> +               }
> +       }
> +
> +       return allowed;
> +}
> +
> +static const struct {
> +       uint8_t val;
> +       const char *str;
> +} major_class_table[] = {
> +       { 0x00, "Miscellaneous" },
> +       { 0x01, "Computer"      },
> +       { 0x02, "Phone"         },
> +       { 0x03, "LAN/Network"   },
> +       { 0x04, "Audio/Video"   },
> +       { 0x05, "Peripheral"    },
> +       { 0x06, "Imaging"       },
> +       { 0x07, "Wearable"      },
> +       { 0x08, "Toy"           },
> +       { 0x09, "Health"        },
> +       { 0x1f, "Uncategorized" },
> +       { }
> +};
> +
> +static gboolean is_class_allowed_to_wake(uint32_t class)
> +{
> +       uint8_t major = (class & 0x1f00) >> 8;
> +       uint8_t minor = (class & 0x00fc) >> 2;
> +
> +       if ((major >= 0x01 && major <= 0x09) || major == 0x1f)
> +               return is_allowed_to_wake(major_class_table[major].str, minor);
> +
> +       return true;
> +}
> +
> +static void wake_policy_device_resolved(struct btd_adapter *adapter,
> +                                    struct btd_device *device)
> +{
> +       char *filename;
> +       GKeyFile *device_key_file;
> +       GError *gerr = NULL;
> +
> +       if (key_file == NULL)
> +               return;
> +
> +       // Does device support to wake the host?
> +       if (!device_get_wake_support(device))
> +               return;
> +
> +       // Check if WakeAllowed has already been stored,
> +       // if yes do not change it
> +       filename = btd_device_get_storage_path(device, "info");
> +       device_key_file = g_key_file_new();
> +       if (!g_key_file_load_from_file(device_key_file, filename, 0, &gerr)) {
> +               error("Unable to load key file from %s: (%s)", filename,
> +                                                       gerr->message);
> +               g_clear_error(&gerr);
> +       } else {
> +               if (g_key_file_has_key(device_key_file, "General",
> +                                       "WakeAllowed", NULL)) {
> +                       DBG("%s WakeAllowed already stored",
> +                               device_get_path(device));
> +                       return;
> +               }
> +       }
> +       g_key_file_free(device_key_file);
> +       g_free(filename);
> +
> +       // Check if Class of Device is allowed to wake up the host
> +       if (!is_class_allowed_to_wake(btd_device_get_class(device))) {
> +               DBG("%s Force WakeAllowed to false", device_get_path(device));
> +               device_set_wake_override(device, false);
> +               device_set_wake_allowed(device, false, -1U);
> +       }
> +}
> +
> +static int wake_policy_probe(struct btd_adapter *adapter)
> +{
> +       GError *gerr = NULL;
> +
> +       DBG("");
> +       key_file = g_key_file_new();
> +       if (!g_key_file_load_from_file(key_file,
> +                                       CONFIGDIR "/wake-policy.conf",
> +                                       0,
> +                                       &gerr)) {
> +               error("Unable to load key file from %s: (%s)",
> +                       CONFIGDIR "/wake-policy.conf",
> +                       gerr->message);
> +               g_clear_error(&gerr);
> +               g_key_file_free(key_file);
> +               key_file = NULL;
> +       }
> +
> +       return 0;
> +}
> +
> +static void wake_policy_remove(struct btd_adapter *adapter)
> +{
> +       DBG("");
> +       if (key_file)
> +               g_key_file_free(key_file);
> +}
> +
> +static struct btd_adapter_driver wake_policy_driver = {
> +       .name   = "wake-policy",
> +       .probe  = wake_policy_probe,
> +       .remove = wake_policy_remove,
> +       .device_resolved = wake_policy_device_resolved,
> +};
> +
> +static int wake_policy_init(void)
> +{
> +       return btd_register_adapter_driver(&wake_policy_driver);
> +}
> +
> +static void wake_policy_exit(void)
> +{
> +       btd_unregister_adapter_driver(&wake_policy_driver);
> +}
> +
> +BLUETOOTH_PLUGIN_DEFINE(wake_policy, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW,
> +                       wake_policy_init, wake_policy_exit)
> diff --git a/plugins/wake-policy.conf b/plugins/wake-policy.conf
> new file mode 100644
> index 000000000..e6220bad1
> --- /dev/null
> +++ b/plugins/wake-policy.conf
> @@ -0,0 +1,21 @@
> +[WakeAllowed]
> +# By default each Class of Devices providing the correct service (HID, HoG) is
> +# allowed to wake up the host.
> +# The following values are used to control the default WakeAllowed value based
> +# on the Class of Device.
> +# It is still possible for the user to override the WakeAllowed value per
> +# device afterwards.
> +#
> +# Possible values for each Major Class:
> +# true,false,<Minor Class allowed list (integer separated by ;)>
> +
> +#Computer=true
> +#Phone=true
> +#LAN/Network=true
> +#Audio/Video=true
> +#Peripheral=true
> +#Imaging=true
> +#Wearable=true
> +#Toy=true
> +#Health=true
> +#Uncategorized=true
> diff --git a/src/device.c b/src/device.c
> index 1d4b8ab36..d970745f5 100644
> --- a/src/device.c
> +++ b/src/device.c
> @@ -1500,7 +1500,7 @@ dev_property_advertising_data_exist(const GDBusPropertyTable *property,
>         return bt_ad_has_data(device->ad, NULL);
>  }
>
> -static bool device_get_wake_support(struct btd_device *device)
> +bool device_get_wake_support(struct btd_device *device)
>  {
>         return device->wake_support;
>  }
> diff --git a/src/device.h b/src/device.h
> index 5722ca9ca..f8c744baf 100644
> --- a/src/device.h
> +++ b/src/device.h
> @@ -149,6 +149,7 @@ void device_set_wake_support(struct btd_device *device, bool wake_support);
>  void device_set_wake_override(struct btd_device *device, bool wake_override);
>  void device_set_wake_allowed(struct btd_device *device, bool wake_allowed,
>                              guint32 id);
> +bool device_get_wake_support(struct btd_device *device);
>  void device_set_refresh_discovery(struct btd_device *dev, bool refresh);
>
>  typedef void (*disconnect_watch) (struct btd_device *device, gboolean removal,
> --
> 2.34.1
>
>
>
>
> --
> 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
diff mbox series

Patch

diff --git a/Makefile.plugins b/Makefile.plugins
index 4aa2c9c92..fbd4e4155 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -11,6 +11,10 @@  builtin_sources += plugins/autopair.c
 builtin_modules += policy
 builtin_sources += plugins/policy.c
 
+builtin_modules += wake_policy
+builtin_sources += plugins/wake-policy.c
+EXTRA_DIST += plugins/wake-policy.conf
+
 if ADMIN
 builtin_modules += admin
 builtin_sources += plugins/admin.c
diff --git a/plugins/wake-policy.c b/plugins/wake-policy.c
new file mode 100644
index 000000000..34e77c615
--- /dev/null
+++ b/plugins/wake-policy.c
@@ -0,0 +1,180 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2024 Frédéric Danis <frederic.danis@collabora.com>
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+
+#include <glib.h>
+
+#include "bluetooth/bluetooth.h"
+
+#include "src/plugin.h"
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/log.h"
+#include "src/textfile.h"
+
+static GKeyFile * key_file;
+
+static gboolean is_allowed_to_wake(const char *major, guint minor)
+{
+	guint *minor_list;
+	gsize length;
+	gboolean allowed = false;
+	GError *gerr = NULL;
+
+	if (!g_key_file_has_key(key_file, "WakeAllowed", major, NULL))
+		return true;
+
+	allowed = g_key_file_get_boolean(key_file, "WakeAllowed", major, &gerr);
+	if (!gerr)
+		return allowed;
+
+	g_error_free(gerr);
+	gerr = NULL;
+
+	minor_list = (guint *)g_key_file_get_integer_list(key_file,
+						"WakeAllowed",
+						major, &length, &gerr);
+	if (gerr) {
+		DBG("Failed to get allowed minor list for %s", major);
+		return false;
+	}
+
+	for (gsize i = 0; i < length; i++) {
+		if (minor_list[i] == minor) {
+			allowed = true;
+			break;
+		}
+	}
+
+	return allowed;
+}
+
+static const struct {
+	uint8_t val;
+	const char *str;
+} major_class_table[] = {
+	{ 0x00, "Miscellaneous"	},
+	{ 0x01, "Computer"	},
+	{ 0x02, "Phone"		},
+	{ 0x03, "LAN/Network"	},
+	{ 0x04, "Audio/Video"	},
+	{ 0x05, "Peripheral"	},
+	{ 0x06, "Imaging"	},
+	{ 0x07, "Wearable"	},
+	{ 0x08, "Toy"		},
+	{ 0x09, "Health"	},
+	{ 0x1f, "Uncategorized"	},
+	{ }
+};
+
+static gboolean is_class_allowed_to_wake(uint32_t class)
+{
+	uint8_t major = (class & 0x1f00) >> 8;
+	uint8_t minor = (class & 0x00fc) >> 2;
+
+	if ((major >= 0x01 && major <= 0x09) || major == 0x1f)
+		return is_allowed_to_wake(major_class_table[major].str, minor);
+
+	return true;
+}
+
+static void wake_policy_device_resolved(struct btd_adapter *adapter,
+				     struct btd_device *device)
+{
+	char *filename;
+	GKeyFile *device_key_file;
+	GError *gerr = NULL;
+
+	if (key_file == NULL)
+		return;
+
+	// Does device support to wake the host?
+	if (!device_get_wake_support(device))
+		return;
+
+	// Check if WakeAllowed has already been stored,
+	// if yes do not change it
+	filename = btd_device_get_storage_path(device, "info");
+	device_key_file = g_key_file_new();
+	if (!g_key_file_load_from_file(device_key_file, filename, 0, &gerr)) {
+		error("Unable to load key file from %s: (%s)", filename,
+							gerr->message);
+		g_clear_error(&gerr);
+	} else {
+		if (g_key_file_has_key(device_key_file, "General",
+					"WakeAllowed", NULL)) {
+			DBG("%s WakeAllowed already stored",
+				device_get_path(device));
+			return;
+		}
+	}
+	g_key_file_free(device_key_file);
+	g_free(filename);
+
+	// Check if Class of Device is allowed to wake up the host
+	if (!is_class_allowed_to_wake(btd_device_get_class(device))) {
+		DBG("%s Force WakeAllowed to false", device_get_path(device));
+		device_set_wake_override(device, false);
+		device_set_wake_allowed(device, false, -1U);
+	}
+}
+
+static int wake_policy_probe(struct btd_adapter *adapter)
+{
+	GError *gerr = NULL;
+
+	DBG("");
+	key_file = g_key_file_new();
+	if (!g_key_file_load_from_file(key_file,
+					CONFIGDIR "/wake-policy.conf",
+					0,
+					&gerr)) {
+		error("Unable to load key file from %s: (%s)",
+			CONFIGDIR "/wake-policy.conf",
+			gerr->message);
+		g_clear_error(&gerr);
+		g_key_file_free(key_file);
+		key_file = NULL;
+	}
+
+	return 0;
+}
+
+static void wake_policy_remove(struct btd_adapter *adapter)
+{
+	DBG("");
+	if (key_file)
+		g_key_file_free(key_file);
+}
+
+static struct btd_adapter_driver wake_policy_driver = {
+	.name	= "wake-policy",
+	.probe	= wake_policy_probe,
+	.remove	= wake_policy_remove,
+	.device_resolved = wake_policy_device_resolved,
+};
+
+static int wake_policy_init(void)
+{
+	return btd_register_adapter_driver(&wake_policy_driver);
+}
+
+static void wake_policy_exit(void)
+{
+	btd_unregister_adapter_driver(&wake_policy_driver);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(wake_policy, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW,
+			wake_policy_init, wake_policy_exit)
diff --git a/plugins/wake-policy.conf b/plugins/wake-policy.conf
new file mode 100644
index 000000000..e6220bad1
--- /dev/null
+++ b/plugins/wake-policy.conf
@@ -0,0 +1,21 @@ 
+[WakeAllowed]
+# By default each Class of Devices providing the correct service (HID, HoG) is
+# allowed to wake up the host.
+# The following values are used to control the default WakeAllowed value based
+# on the Class of Device.
+# It is still possible for the user to override the WakeAllowed value per
+# device afterwards.
+#
+# Possible values for each Major Class:
+# true,false,<Minor Class allowed list (integer separated by ;)>
+
+#Computer=true
+#Phone=true
+#LAN/Network=true
+#Audio/Video=true
+#Peripheral=true
+#Imaging=true
+#Wearable=true
+#Toy=true
+#Health=true
+#Uncategorized=true
diff --git a/src/device.c b/src/device.c
index 1d4b8ab36..d970745f5 100644
--- a/src/device.c
+++ b/src/device.c
@@ -1500,7 +1500,7 @@  dev_property_advertising_data_exist(const GDBusPropertyTable *property,
 	return bt_ad_has_data(device->ad, NULL);
 }
 
-static bool device_get_wake_support(struct btd_device *device)
+bool device_get_wake_support(struct btd_device *device)
 {
 	return device->wake_support;
 }
diff --git a/src/device.h b/src/device.h
index 5722ca9ca..f8c744baf 100644
--- a/src/device.h
+++ b/src/device.h
@@ -149,6 +149,7 @@  void device_set_wake_support(struct btd_device *device, bool wake_support);
 void device_set_wake_override(struct btd_device *device, bool wake_override);
 void device_set_wake_allowed(struct btd_device *device, bool wake_allowed,
 			     guint32 id);
+bool device_get_wake_support(struct btd_device *device);
 void device_set_refresh_discovery(struct btd_device *dev, bool refresh);
 
 typedef void (*disconnect_watch) (struct btd_device *device, gboolean removal,