/*************************************************************************** * * acpi.c : Main routines for setting battery, AC adapter, and lid properties * * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Licensed under the Academic Free License version 2.1 * **************************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "../hald/device_info.h" #include "../hald/hald_dbus.h" #include "../hald/logger.h" #include "../hald/util_pm.h" #include "acpi.h" static void my_dbus_error_free(DBusError *error) { if (dbus_error_is_set(error)) { dbus_error_free(error); } } gboolean laptop_panel_update(LibHalContext *ctx, const char *udi, int fd) { LibHalChangeSet *cs; DBusError error; struct acpi_drv_output_info inf; HAL_DEBUG(("laptop_panel_update() enter")); dbus_error_init(&error); if (!libhal_device_query_capability(ctx, udi, "laptop_panel", &error)) { bzero(&inf, sizeof (inf)); if ((ioctl(fd, ACPI_DRV_IOC_INFO, &inf) < 0) || (inf.nlev == 0)) { return (FALSE); } my_dbus_error_free(&error); libhal_device_add_capability(ctx, udi, "laptop_panel", &error); if ((cs = libhal_device_new_changeset(udi)) == NULL) { my_dbus_error_free(&error); return (FALSE); } libhal_changeset_set_property_string(cs, "info.product", "Generic Backlight Device"); libhal_changeset_set_property_string(cs, "info.category", "laptop_panel"); libhal_changeset_set_property_int(cs, "laptop_panel.num_levels", inf.nlev); my_dbus_error_free(&error); libhal_device_commit_changeset(ctx, cs, &error); libhal_device_free_changeset(cs); } my_dbus_error_free(&error); HAL_DEBUG(("ac_adapter_present() exit")); return (TRUE); } gboolean lid_update(LibHalContext *ctx, const char *udi, int fd) { LibHalChangeSet *cs; DBusError error; int lid_state; HAL_DEBUG(("lid_update() enter")); if ((cs = libhal_device_new_changeset(udi)) == NULL) { return (FALSE); } dbus_error_init(&error); if (!libhal_device_query_capability(ctx, udi, "button", &error)) { my_dbus_error_free(&error); libhal_device_add_capability(ctx, udi, "button", &error); my_dbus_error_free(&error); libhal_changeset_set_property_bool(cs, "button.has_state", TRUE); if (ioctl(fd, ACPI_DRV_IOC_LID_STATUS, &lid_state) < 0) { return (FALSE); } if (lid_state != 0) { /* lid open */ libhal_changeset_set_property_bool(cs, "button.state.value", FALSE); } else { /* lid closed */ libhal_changeset_set_property_bool(cs, "button.state.value", TRUE); } libhal_changeset_set_property_bool(cs, "button.workaround", TRUE); libhal_changeset_set_property_string(cs, "button.type", "lid"); libhal_changeset_set_property_string(cs, "info.product", "Lid Switch"); libhal_changeset_set_property_string(cs, "info.category", "button"); } else { my_dbus_error_free(&error); if (ioctl(fd, ACPI_DRV_IOC_LID_UPDATE, &lid_state) < 0) { return (FALSE); } if (lid_state != 0) { /* lid open */ libhal_changeset_set_property_bool(cs, "button.state.value", FALSE); } else { /* lid closed */ libhal_changeset_set_property_bool(cs, "button.state.value", TRUE); } } libhal_device_commit_changeset(ctx, cs, &error); libhal_device_free_changeset(cs); my_dbus_error_free(&error); HAL_DEBUG(("update_lid() exit")); return (TRUE); } static void ac_adapter_present(LibHalContext *ctx, const char *udi, int fd) { int pow; LibHalChangeSet *cs; DBusError error; HAL_DEBUG(("ac_adapter_present() enter")); if (ioctl(fd, ACPI_DRV_IOC_POWER_STATUS, &pow) < 0) { return; } if ((cs = libhal_device_new_changeset(udi)) == NULL) { return; } if (pow > 0) { libhal_changeset_set_property_bool(cs, "ac_adapter.present", TRUE); } else { libhal_changeset_set_property_bool(cs, "ac_adapter.present", FALSE); } dbus_error_init(&error); libhal_device_commit_changeset(ctx, cs, &error); libhal_device_free_changeset(cs); my_dbus_error_free(&error); HAL_DEBUG(("ac_adapter_present() exit")); } static void battery_remove(LibHalContext *ctx, const char *udi) { DBusError error; HAL_DEBUG(("battery_remove() enter")); dbus_error_init(&error); libhal_device_remove_property(ctx, udi, "battery.remaining_time", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.charge_level.percentage", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.charge_level.rate", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.charge_level.last_full", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.charge_level.current", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.voltage.present", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.reporting.rate", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.reporting.current", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.rechargeable.is_discharging", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.rechargeable.is_charging", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.is_rechargeable", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.charge_level.unit", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.charge_level.granularity_2", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.charge_level.granularity_1", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.charge_level.low", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.charge_level.warning", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.charge_level.design", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.voltage.design", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.reporting.granularity_2", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.reporting.granularity_1", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.reporting.low", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.reporting.warning", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.reporting.design", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.reporting.last_full", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.reporting.unit", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.technology", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.reporting.technology", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.serial", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.model", &error); my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.vendor", &error); my_dbus_error_free(&error); HAL_DEBUG(("battery_remove() exit")); } static void battery_last_full(LibHalChangeSet *cs, int fd) { acpi_bif_t bif; bzero(&bif, sizeof (bif)); if (ioctl(fd, ACPI_DRV_IOC_INFO, &bif) < 0) { return; } libhal_changeset_set_property_int(cs, "battery.reporting_last_full", bif.bif_last_cap); } static void battery_dynamic_update(LibHalContext *ctx, const char *udi, int fd) { int reporting_rate; int reporting_current; int reporting_lastfull; int design_voltage; int present_voltage; char *reporting_unit; int remaining_time; int remaining_percentage; gboolean charging; gboolean discharging; acpi_bst_t bst; LibHalChangeSet *cs; DBusError error; static int counter = 0; HAL_DEBUG(("battery_dynamic_update() enter")); bzero(&bst, sizeof (bst)); if (ioctl(fd, ACPI_DRV_IOC_STATUS, &bst) < 0) { return; } charging = bst.bst_state & ACPI_DRV_BST_CHARGING ? TRUE : FALSE; discharging = bst.bst_state & ACPI_DRV_BST_DISCHARGING ? TRUE : FALSE; /* No need to continue if battery is essentially idle. */ if (counter && !charging && !discharging) { return; } dbus_error_init(&error); libhal_device_set_property_bool(ctx, udi, "battery.is_rechargeable", TRUE, &error); my_dbus_error_free(&error); if (libhal_device_property_exists(ctx, udi, "battery.charge_level.percentage", &error)) { remaining_percentage = libhal_device_get_property_int(ctx, udi, "battery.charge_level.percentage", &error); if ((remaining_percentage == 100) && charging) { charging = FALSE; } } libhal_device_set_property_bool(ctx, udi, "battery.rechargeable.is_charging", charging, &error); my_dbus_error_free(&error); libhal_device_set_property_bool(ctx, udi, "battery.rechargeable.is_discharging", discharging, &error); my_dbus_error_free(&error); reporting_current = bst.bst_rem_cap; libhal_device_set_property_int(ctx, udi, "battery.reporting.current", bst.bst_rem_cap, &error); my_dbus_error_free(&error); reporting_rate = bst.bst_rate; libhal_device_set_property_int(ctx, udi, "battery.reporting.rate", bst.bst_rate, &error); my_dbus_error_free(&error); present_voltage = bst.bst_voltage; libhal_device_set_property_int(ctx, udi, "battery.voltage.present", bst.bst_voltage, &error); /* get all the data we know */ my_dbus_error_free(&error); reporting_unit = libhal_device_get_property_string(ctx, udi, "battery.reporting.unit", &error); my_dbus_error_free(&error); reporting_lastfull = libhal_device_get_property_int(ctx, udi, "battery.reporting.last_full", &error); /* * Convert mAh to mWh since util_compute_time_remaining() works * for mWh. */ if (reporting_unit && strcmp(reporting_unit, "mAh") == 0) { my_dbus_error_free(&error); design_voltage = libhal_device_get_property_int(ctx, udi, "battery.voltage.design", &error); /* * If the present_voltage is inaccurate, set it to the * design_voltage. */ if (((present_voltage * 10) < design_voltage) || (present_voltage <= 0) || (present_voltage > design_voltage)) { present_voltage = design_voltage; } reporting_rate = (reporting_rate * present_voltage) / 1000; reporting_lastfull = (reporting_lastfull * present_voltage) / 1000; reporting_current = (reporting_current * present_voltage) / 1000; } /* Make sure the current charge does not exceed the full charge */ if (reporting_current > reporting_lastfull) { reporting_current = reporting_lastfull; } if (!charging && !discharging) { counter++; reporting_rate = 0; } if ((cs = libhal_device_new_changeset(udi)) == NULL) { HAL_DEBUG(("Cannot allocate changeset")); libhal_free_string(reporting_unit); my_dbus_error_free(&error); return; } libhal_changeset_set_property_int(cs, "battery.charge_level.rate", reporting_rate); libhal_changeset_set_property_int(cs, "battery.charge_level.last_full", reporting_lastfull); libhal_changeset_set_property_int(cs, "battery.charge_level.current", reporting_current); remaining_percentage = util_compute_percentage_charge(udi, reporting_current, reporting_lastfull); remaining_time = util_compute_time_remaining(udi, reporting_rate, reporting_current, reporting_lastfull, discharging, charging, 0); /* * Some batteries give bad remaining_time estimates relative to * the charge level. */ if (charging && ((remaining_time < 30) || ((remaining_time < 300) && (remaining_percentage < 95)) || (remaining_percentage > 97))) { remaining_time = util_compute_time_remaining(udi, reporting_rate, reporting_current, reporting_lastfull, discharging, charging, 1); } if (remaining_percentage > 0) { libhal_changeset_set_property_int(cs, "battery.charge_level.percentage", remaining_percentage); } else { my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.charge_level.percentage", &error); } if ((remaining_percentage == 100) && charging) { battery_last_full(cs, fd); } /* * remaining_percentage is more accurate so we handle cases * where the remaining_time cannot be correct. */ if ((!charging && !discharging) || ((remaining_percentage == 100) && !discharging)) { remaining_time = 0; } if (remaining_time < 0) { my_dbus_error_free(&error); libhal_device_remove_property(ctx, udi, "battery.remaining_time", &error); } else if (remaining_time >= 0) { libhal_changeset_set_property_int(cs, "battery.remaining_time", remaining_time); } my_dbus_error_free(&error); libhal_device_commit_changeset(ctx, cs, &error); libhal_device_free_changeset(cs); libhal_free_string(reporting_unit); my_dbus_error_free(&error); HAL_DEBUG(("battery_dynamic_update() exit")); } static gboolean battery_static_update(LibHalContext *ctx, const char *udi, int fd) { const char *technology; int reporting_design; int reporting_warning; int reporting_low; int reporting_gran1; int reporting_gran2; int voltage_design; char reporting_unit[10]; acpi_bif_t bif; LibHalChangeSet *cs; DBusError error; HAL_DEBUG(("battery_static_update() enter")); bzero(&bif, sizeof (bif)); if (ioctl(fd, ACPI_DRV_IOC_INFO, &bif) < 0) { return (FALSE); } if ((cs = libhal_device_new_changeset(udi)) == NULL) { HAL_DEBUG(("Cannot allocate changeset")); return (FALSE); } libhal_changeset_set_property_string(cs, "battery.vendor", bif.bif_oem_info); technology = bif.bif_type; if (technology != NULL) { libhal_changeset_set_property_string(cs, "battery.reporting.technology", technology); libhal_changeset_set_property_string(cs, "battery.technology", util_get_battery_technology(technology)); } libhal_changeset_set_property_string(cs, "battery.serial", bif.bif_serial); libhal_changeset_set_property_string(cs, "battery.model", bif.bif_model); if (bif.bif_unit) { libhal_changeset_set_property_string(cs, "battery.reporting.unit", "mAh"); strlcpy(reporting_unit, "mAh", sizeof (reporting_unit)); } else { libhal_changeset_set_property_string(cs, "battery.reporting.unit", "mWh"); strlcpy(reporting_unit, "mWh", sizeof (reporting_unit)); } libhal_changeset_set_property_int(cs, "battery.reporting.last_full", bif.bif_last_cap); libhal_changeset_set_property_int(cs, "battery.reporting.design", bif.bif_design_cap); reporting_design = bif.bif_design_cap; libhal_changeset_set_property_int(cs, "battery.reporting.warning", bif.bif_warn_cap); reporting_warning = bif.bif_warn_cap; libhal_changeset_set_property_int(cs, "battery.reporting.low", bif.bif_low_cap); reporting_low = bif.bif_low_cap; libhal_changeset_set_property_int(cs, "battery.reporting.granularity_1", bif.bif_gran1_cap); reporting_gran1 = bif.bif_gran1_cap; libhal_changeset_set_property_int(cs, "battery.reporting.granularity_2", bif.bif_gran2_cap); reporting_gran2 = bif.bif_gran2_cap; libhal_changeset_set_property_int(cs, "battery.voltage.design", bif.bif_voltage); voltage_design = bif.bif_voltage; if (reporting_unit && strcmp(reporting_unit, "mAh") == 0) { /* convert to mWh */ libhal_changeset_set_property_string(cs, "battery.charge_level.unit", "mWh"); libhal_changeset_set_property_int(cs, "battery.charge_level.design", (reporting_design * voltage_design) / 1000); libhal_changeset_set_property_int(cs, "battery.charge_level.warning", (reporting_warning * voltage_design) / 1000); libhal_changeset_set_property_int(cs, "battery.charge_level.low", (reporting_low * voltage_design) / 1000); libhal_changeset_set_property_int(cs, "battery.charge_level.granularity_1", (reporting_gran1 * voltage_design) / 1000); libhal_changeset_set_property_int(cs, "battery.charge_level.granularity_2", (reporting_gran2 * voltage_design) / 1000); } else { if (reporting_unit && strcmp(reporting_unit, "mWh") == 0) { libhal_changeset_set_property_string(cs, "battery.charge_level.unit", "mWh"); } libhal_changeset_set_property_int(cs, "battery.charge_level.design", reporting_design); libhal_changeset_set_property_int(cs, "battery.charge_level.warning", reporting_warning); libhal_changeset_set_property_int(cs, "battery.charge_level.low", reporting_low); libhal_changeset_set_property_int(cs, "battery.charge_level.granularity_1", reporting_gran1); libhal_changeset_set_property_int(cs, "battery.charge_level.granularity_2", reporting_gran2); } dbus_error_init(&error); libhal_device_commit_changeset(ctx, cs, &error); libhal_device_free_changeset(cs); my_dbus_error_free(&error); HAL_DEBUG(("battery_static_update() exit")); return (TRUE); } gboolean battery_update(LibHalContext *ctx, const char *udi, int fd) { acpi_bst_t bst; DBusError error; HAL_DEBUG(("battery_update() enter")); dbus_error_init(&error); libhal_device_set_property_string(ctx, udi, "info.product", "Battery Bay", &error); my_dbus_error_free(&error); libhal_device_set_property_string(ctx, udi, "info.category", "battery", &error); bzero(&bst, sizeof (bst)); if (ioctl(fd, ACPI_DRV_IOC_STATUS, &bst) < 0) { if (errno == ENXIO) { my_dbus_error_free(&error); libhal_device_set_property_bool(ctx, udi, "battery.present", FALSE, &error); } else { my_dbus_error_free(&error); return (FALSE); } } else { my_dbus_error_free(&error); libhal_device_set_property_bool(ctx, udi, "battery.present", TRUE, &error); } my_dbus_error_free(&error); if (!libhal_device_get_property_bool(ctx, udi, "battery.present", &error)) { HAL_DEBUG(("battery_update(): battery is NOT present")); battery_remove(ctx, udi); } else { HAL_DEBUG(("battery_update(): battery is present")); my_dbus_error_free(&error); libhal_device_set_property_string(ctx, udi, "battery.type", "primary", &error); my_dbus_error_free(&error); libhal_device_add_capability(ctx, udi, "battery", &error); my_dbus_error_free(&error); if (libhal_device_get_property_type(ctx, udi, "battery.vendor", &error) == LIBHAL_PROPERTY_TYPE_INVALID) { battery_static_update(ctx, udi, fd); } battery_dynamic_update(ctx, udi, fd); } my_dbus_error_free(&error); HAL_DEBUG(("battery_update() exit")); return (TRUE); } static gboolean battery_update_all(LibHalContext *ctx) { int i; int num_devices; char **battery_devices; int fd; DBusError error; HAL_DEBUG(("battery_update_all() enter")); dbus_error_init(&error); if ((battery_devices = libhal_manager_find_device_string_match (ctx, "info.category", "battery", &num_devices, &error)) != NULL) { for (i = 0; i < num_devices; i++) { my_dbus_error_free(&error); if (libhal_device_get_property_bool(ctx, battery_devices[i], "battery.present", &error)) { if ((fd = open_device(ctx, battery_devices[i])) == -1) { continue; } battery_dynamic_update(ctx, battery_devices[i], fd); close(fd); } } libhal_free_string_array(battery_devices); } my_dbus_error_free(&error); HAL_DEBUG(("battery_update_all() exit")); return (TRUE); } gboolean ac_adapter_update(LibHalContext *ctx, const char *udi, int fd) { LibHalChangeSet *cs; DBusError error; HAL_DEBUG(("ac_adapter_update() enter")); dbus_error_init(&error); if (!libhal_device_query_capability(ctx, udi, "ac_adapter", &error)) { my_dbus_error_free(&error); libhal_device_add_capability(ctx, udi, "ac_adapter", &error); if ((cs = libhal_device_new_changeset(udi)) == NULL) { my_dbus_error_free(&error); return (FALSE); } libhal_changeset_set_property_string(cs, "info.product", "AC Adapter"); libhal_changeset_set_property_string(cs, "info.category", "ac_adapter"); my_dbus_error_free(&error); libhal_device_commit_changeset(ctx, cs, &error); libhal_device_free_changeset(cs); } ac_adapter_present(ctx, udi, fd); battery_update_all(ctx); my_dbus_error_free(&error); HAL_DEBUG(("ac_adapter_update() exit")); return (TRUE); } static gboolean ac_adapter_update_all(LibHalContext *ctx) { int i; int num_devices; char **ac_adapter_devices; int fd; DBusError error; HAL_DEBUG(("ac_adapter_update_all() enter")); dbus_error_init(&error); if ((ac_adapter_devices = libhal_manager_find_device_string_match( ctx, "info.category", "ac_adapter", &num_devices, &error)) != NULL) { for (i = 0; i < num_devices; i++) { if ((fd = open_device(ctx, ac_adapter_devices[i])) == -1) { continue; } ac_adapter_present(ctx, ac_adapter_devices[i], fd); close(fd); } libhal_free_string_array(ac_adapter_devices); } my_dbus_error_free(&error); HAL_DEBUG(("ac_adapter_update_all() exit")); return (TRUE); } gboolean update_devices(gpointer data) { LibHalContext *ctx = (LibHalContext *)data; HAL_DEBUG(("update_devices() enter")); ac_adapter_update_all(ctx); battery_update_all(ctx); HAL_DEBUG(("update_devices() exit")); return (TRUE); } int open_device(LibHalContext *ctx, char *udi) { char path[HAL_PATH_MAX] = "/devices"; char *devfs_path; DBusError error; dbus_error_init(&error); devfs_path = libhal_device_get_property_string(ctx, udi, "solaris.devfs_path", &error); my_dbus_error_free(&error); if (devfs_path == NULL) { return (-1); } strlcat(path, devfs_path, HAL_PATH_MAX); libhal_free_string(devfs_path); return (open(path, O_RDONLY | O_NONBLOCK)); }