blob: fa30fde475f0b34d54a4be016ce39194b03e9eae [file]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2020 Google LLC
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdbool.h>
#include <stdint.h>
#include <glib.h>
#include "gdbus/gdbus.h"
#include "bluetooth/bluetooth.h"
#include "src/shared/battery.h"
#include "src/shared/queue.h"
#include "src/shared/util.h"
#include "battery.h"
#include "dbus-common.h"
#include "adapter.h"
#include "device.h"
#include "log.h"
#include "error.h"
#define BATTERY_INTERFACE "org.bluez.Battery1"
#define BATTERY_PROVIDER_INTERFACE "org.bluez.BatteryProvider1"
#define BATTERY_PROVIDER_MANAGER_INTERFACE "org.bluez.BatteryProviderManager1"
#define BATTERY_MAX_PERCENTAGE 100
struct btd_battery {
char *path; /* D-Bus object path */
uint8_t percentage; /* valid between 0 to 100 inclusively */
char *source; /* Descriptive source of the battery info */
char *provider_path; /* The provider root path, if any */
struct bt_battery *filter;
};
struct btd_battery_provider_manager {
struct btd_adapter *adapter; /* Does not own pointer */
struct queue *battery_providers;
};
struct battery_provider {
struct btd_battery_provider_manager *manager; /* Does not own pointer */
char *owner; /* Owner D-Bus address */
char *path; /* D-Bus object path */
GDBusClient *client;
};
static struct queue *batteries = NULL;
static void provider_disconnect_cb(DBusConnection *conn, void *user_data);
static void battery_add(struct btd_battery *battery)
{
if (!batteries)
batteries = queue_new();
queue_push_head(batteries, battery);
}
static void battery_remove(struct btd_battery *battery)
{
queue_remove(batteries, battery);
if (queue_isempty(batteries)) {
queue_destroy(batteries, NULL);
batteries = NULL;
}
}
static bool match_path(const void *data, const void *user_data)
{
const struct btd_battery *battery = data;
const char *path = user_data;
return g_strcmp0(battery->path, path) == 0;
}
static struct btd_battery *battery_new(const char *path, const char *source,
const char *provider_path)
{
struct btd_battery *battery;
battery = new0(struct btd_battery, 1);
battery->path = g_strdup(path);
battery->percentage = UINT8_MAX;
if (source)
battery->source = g_strdup(source);
if (provider_path)
battery->provider_path = g_strdup(provider_path);
battery->filter = bt_battery_new();
return battery;
}
static void battery_free(struct btd_battery *battery)
{
if (battery->path)
g_free(battery->path);
if (battery->source)
g_free(battery->source);
if (battery->filter) {
bt_battery_free(battery->filter);
free(battery->filter);
}
free(battery);
}
static gboolean property_percentage_get(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_battery *battery = data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE,
&battery->percentage);
return TRUE;
}
static gboolean property_percentage_exists(const GDBusPropertyTable *property,
void *data)
{
struct btd_battery *battery = data;
return battery->percentage <= BATTERY_MAX_PERCENTAGE;
}
static gboolean property_source_get(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_battery *battery = data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
&battery->source);
return TRUE;
}
static gboolean property_source_exists(const GDBusPropertyTable *property,
void *data)
{
struct btd_battery *battery = data;
return battery->source != NULL;
}
static const GDBusPropertyTable battery_properties[] = {
{ "Percentage", "y", property_percentage_get, NULL,
property_percentage_exists },
{ "Source", "s", property_source_get, NULL, property_source_exists },
{}
};
struct btd_battery *btd_battery_register(const char *path, const char *source,
const char *provider_path)
{
struct btd_battery *battery;
DBG("path = %s", path);
if (queue_find(batteries, match_path, path)) {
error("error registering battery: path exists");
return NULL;
}
if (!g_str_has_prefix(path, "/")) {
error("error registering battery: invalid D-Bus object path");
return NULL;
}
battery = battery_new(path, source, provider_path);
battery_add(battery);
if (!g_dbus_register_interface(btd_get_dbus_connection(), battery->path,
BATTERY_INTERFACE, NULL, NULL,
battery_properties, battery, NULL)) {
error("error registering D-Bus interface for %s",
battery->path);
battery_remove(battery);
battery_free(battery);
return NULL;
}
DBG("registered Battery object: %s", battery->path);
return battery;
}
bool btd_battery_unregister(struct btd_battery *battery)
{
DBG("path = %s", battery->path);
if (!queue_find(batteries, NULL, battery)) {
error("error unregistering battery: "
"battery %s is not registered",
battery->path);
return false;
}
if (!g_dbus_unregister_interface(btd_get_dbus_connection(),
battery->path, BATTERY_INTERFACE)) {
error("error unregistering battery %s from D-Bus interface",
battery->path);
return false;
}
battery_remove(battery);
battery_free(battery);
return true;
}
bool btd_battery_update(struct btd_battery *battery, uint8_t percentage)
{
DBG("path = %s", battery->path);
if (!queue_find(batteries, NULL, battery)) {
error("error updating battery: battery is not registered");
return false;
}
if (percentage > BATTERY_MAX_PERCENTAGE) {
error("error updating battery: percentage is not valid");
return false;
}
if (battery->percentage == percentage)
return true;
battery->percentage = bt_battery_charge(battery->filter, percentage);
g_dbus_emit_property_changed(btd_get_dbus_connection(), battery->path,
BATTERY_INTERFACE, "Percentage");
return true;
}
static struct btd_battery *find_battery_by_path(const char *path)
{
return queue_find(batteries, match_path, path);
}
static void provided_battery_property_changed_cb(GDBusProxy *proxy,
const char *name,
DBusMessageIter *iter,
void *user_data)
{
uint8_t percentage = 0;
const char *export_path;
DBusMessageIter dev_iter;
if (g_dbus_proxy_get_property(proxy, "Device", &dev_iter) == FALSE)
return;
dbus_message_iter_get_basic(&dev_iter, &export_path);
if (strcmp(name, "Percentage") != 0)
return;
if (iter) {
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BYTE)
return;
dbus_message_iter_get_basic(iter, &percentage);
}
DBG("battery percentage changed on %s, percentage = %d",
g_dbus_proxy_get_path(proxy), percentage);
btd_battery_update(find_battery_by_path(export_path), percentage);
}
static void provided_battery_added_cb(GDBusProxy *proxy, void *user_data)
{
struct battery_provider *provider = user_data;
struct btd_battery *battery;
struct btd_device *device;
const char *path = g_dbus_proxy_get_path(proxy);
const char *export_path;
const char *source = NULL;
uint8_t percentage;
DBusMessageIter iter;
if (strcmp(g_dbus_proxy_get_interface(proxy),
BATTERY_PROVIDER_INTERFACE) != 0)
return;
if (g_dbus_proxy_get_property(proxy, "Device", &iter) == FALSE) {
warn("Battery object %s does not specify device path", path);
return;
}
dbus_message_iter_get_basic(&iter, &export_path);
device = btd_adapter_find_device_by_path(provider->manager->adapter,
export_path);
if (!device || device_is_temporary(device)) {
warn("Ignoring non-existent device path for battery %s",
export_path);
return;
}
if (find_battery_by_path(export_path)) {
DBG("Battery for %s is already provided, ignoring the new one",
export_path);
return;
}
g_dbus_proxy_set_property_watch(
proxy, provided_battery_property_changed_cb, provider);
if (g_dbus_proxy_get_property(proxy, "Source", &iter) == TRUE)
dbus_message_iter_get_basic(&iter, &source);
battery = btd_battery_register(export_path, source, provider->path);
DBG("provided battery added %s", path);
/* Percentage property may not be immediately available, that's okay
* since we monitor changes to this property.
*/
if (g_dbus_proxy_get_property(proxy, "Percentage", &iter) == FALSE)
return;
dbus_message_iter_get_basic(&iter, &percentage);
btd_battery_update(battery, percentage);
}
static void provided_battery_removed_cb(GDBusProxy *proxy, void *user_data)
{
struct battery_provider *provider = user_data;
struct btd_battery *battery;
const char *export_path;
DBusMessageIter iter;
if (strcmp(g_dbus_proxy_get_interface(proxy),
BATTERY_PROVIDER_INTERFACE) != 0)
return;
if (g_dbus_proxy_get_property(proxy, "Device", &iter) == FALSE)
return;
dbus_message_iter_get_basic(&iter, &export_path);
DBG("provided battery removed %s", g_dbus_proxy_get_path(proxy));
battery = find_battery_by_path(export_path);
if (!battery)
return;
if (g_strcmp0(battery->provider_path, provider->path) != 0)
return;
g_dbus_proxy_set_property_watch(proxy, NULL, NULL);
btd_battery_unregister(battery);
}
static bool match_provider_path(const void *data, const void *user_data)
{
const struct battery_provider *provider = data;
const char *path = user_data;
return strcmp(provider->path, path) == 0;
}
static void unregister_if_path_has_prefix(void *data, void *user_data)
{
struct btd_battery *battery = data;
struct battery_provider *provider = user_data;
if (g_strcmp0(battery->provider_path, provider->path) == 0)
btd_battery_unregister(battery);
}
static void battery_provider_free(gpointer data)
{
struct battery_provider *provider = data;
/* Unregister batteries under the root path of provider->path */
queue_foreach(batteries, unregister_if_path_has_prefix, provider);
if (provider->owner)
g_free(provider->owner);
if (provider->path)
g_free(provider->path);
if (provider->client) {
g_dbus_client_set_disconnect_watch(provider->client, NULL,
NULL);
g_dbus_client_set_proxy_handlers(provider->client, NULL, NULL,
NULL, NULL);
g_dbus_client_unref(provider->client);
}
free(provider);
}
static struct battery_provider *
battery_provider_new(DBusConnection *conn,
struct btd_battery_provider_manager *manager,
const char *path, const char *sender)
{
struct battery_provider *provider;
provider = new0(struct battery_provider, 1);
provider->manager = manager;
provider->owner = g_strdup(sender);
provider->path = g_strdup(path);
provider->client = g_dbus_client_new_full(conn, sender, path, path);
if (!provider->client) {
error("error creating D-Bus client %s", path);
battery_provider_free(provider);
return NULL;
}
g_dbus_client_set_disconnect_watch(provider->client,
provider_disconnect_cb, provider);
g_dbus_client_set_proxy_handlers(provider->client,
provided_battery_added_cb,
provided_battery_removed_cb, NULL,
provider);
return provider;
}
static void provider_disconnect_cb(DBusConnection *conn, void *user_data)
{
struct battery_provider *provider = user_data;
struct btd_battery_provider_manager *manager = provider->manager;
DBG("battery provider client disconnected %s root path %s",
provider->owner, provider->path);
if (!queue_find(manager->battery_providers, NULL, provider)) {
warn("Disconnection on a non-existing provider %s",
provider->path);
return;
}
queue_remove(manager->battery_providers, provider);
battery_provider_free(provider);
}
static DBusMessage *register_battery_provider(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct btd_battery_provider_manager *manager = user_data;
const char *sender = dbus_message_get_sender(msg);
DBusMessageIter args;
const char *path;
struct battery_provider *provider;
if (!dbus_message_iter_init(msg, &args))
return btd_error_invalid_args(msg);
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&args, &path);
DBG("register battery provider path = %s", path);
if (!g_str_has_prefix(path, "/"))
return btd_error_invalid_args(msg);
if (queue_find(manager->battery_providers, match_provider_path, path)) {
return dbus_message_new_error(msg,
ERROR_INTERFACE ".AlreadyExists",
"Provider already exists");
}
provider = battery_provider_new(conn, manager, path, sender);
queue_push_head(manager->battery_providers, provider);
return dbus_message_new_method_return(msg);
}
static DBusMessage *unregister_battery_provider(DBusConnection *conn,
DBusMessage *msg,
void *user_data)
{
struct btd_battery_provider_manager *manager = user_data;
const char *sender = dbus_message_get_sender(msg);
DBusMessageIter args;
const char *path;
struct battery_provider *provider;
if (!dbus_message_iter_init(msg, &args))
return btd_error_invalid_args(msg);
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&args, &path);
DBG("unregister battery provider path = %s", path);
provider = queue_find(manager->battery_providers, match_provider_path,
path);
if (!provider || strcmp(provider->owner, sender) != 0) {
return dbus_message_new_error(msg,
ERROR_INTERFACE ".DoesNotExist",
"Provider does not exist");
}
queue_remove(manager->battery_providers, provider);
battery_provider_free(provider);
return dbus_message_new_method_return(msg);
}
static const GDBusMethodTable methods[] = {
{ GDBUS_METHOD("RegisterBatteryProvider",
GDBUS_ARGS({ "provider", "o" }), NULL,
register_battery_provider) },
{ GDBUS_METHOD("UnregisterBatteryProvider",
GDBUS_ARGS({ "provider", "o" }), NULL,
unregister_battery_provider) },
{}
};
static struct btd_battery_provider_manager *
manager_new(struct btd_adapter *adapter)
{
struct btd_battery_provider_manager *manager;
DBG("");
manager = new0(struct btd_battery_provider_manager, 1);
manager->adapter = adapter;
manager->battery_providers = queue_new();
return manager;
}
static void manager_free(struct btd_battery_provider_manager *manager)
{
if (!manager)
return;
DBG("");
queue_destroy(manager->battery_providers, battery_provider_free);
free(manager);
}
struct btd_battery_provider_manager *
btd_battery_provider_manager_create(struct btd_adapter *adapter)
{
struct btd_battery_provider_manager *manager;
if (!adapter)
return NULL;
manager = manager_new(adapter);
if (!manager)
return NULL;
if (!g_dbus_register_interface(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
BATTERY_PROVIDER_MANAGER_INTERFACE,
methods, NULL, NULL, manager, NULL)) {
error("error registering " BATTERY_PROVIDER_MANAGER_INTERFACE
" interface");
manager_free(manager);
return NULL;
}
info("Battery Provider Manager created");
return manager;
}
void btd_battery_provider_manager_destroy(
struct btd_battery_provider_manager *manager)
{
if (!manager)
return;
g_dbus_unregister_interface(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
BATTERY_PROVIDER_MANAGER_INTERFACE);
info("Battery Provider Manager destroyed");
manager_free(manager);
}