/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2006-2008 Richard Hughes * * Based on hid-ups.c: Copyright (c) 2001 Vojtech Pavlik * Copyright (c) 2001 Paul Stewart * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "config.h" #include #include #include #include #include #include #include #include /* asm/types.h required for __s32 in linux/hiddev.h */ #include #include #include #include #include #include #include #include #include #include #include "up-common.h" #include "up-device-hid.h" #include "up-constants.h" #define UP_DEVICE_HID_REFRESH_TIMEOUT 30l #define UP_DEVICE_HID_USAGE 0x840000 #define UP_DEVICE_HID_SERIAL 0x8400fe #define UP_DEVICE_HID_CHEMISTRY 0x850089 #define UP_DEVICE_HID_CAPACITY_MODE 0x85002c #define UP_DEVICE_HID_BATTERY_VOLTAGE 0x840030 #define UP_DEVICE_HID_BELOW_RCL 0x840042 #define UP_DEVICE_HID_SHUTDOWN_IMMINENT 0x840069 #define UP_DEVICE_HID_PRODUCT 0x8400fe #define UP_DEVICE_HID_SERIAL_NUMBER 0x8400ff #define UP_DEVICE_HID_CHARGING 0x850044 #define UP_DEVICE_HID_DISCHARGING 0x850045 #define UP_DEVICE_HID_REMAINING_CAPACITY 0x850066 #define UP_DEVICE_HID_RUNTIME_TO_EMPTY 0x850068 #define UP_DEVICE_HID_AC_PRESENT 0x8500d0 #define UP_DEVICE_HID_BATTERY_PRESENT 0x8500d1 #define UP_DEVICE_HID_DESIGN_CAPACITY 0x850083 #define UP_DEVICE_HID_DEVICE_NAME 0x850088 #define UP_DEVICE_HID_DEVICE_CHEMISTRY 0x850089 #define UP_DEVICE_HID_RECHARGEABLE 0x85008b #define UP_DEVICE_HID_OEM_INFORMATION 0x85008f #define UP_DEVICE_HID_PAGE_GENERIC_DESKTOP 0x01 #define UP_DEVICE_HID_PAGE_CONSUMER_PRODUCT 0x0c #define UP_DEVICE_HID_PAGE_USB_MONITOR 0x80 #define UP_DEVICE_HID_PAGE_USB_ENUMERATED_VALUES 0x81 #define UP_DEVICE_HID_PAGE_VESA_VIRTUAL_CONTROLS 0x82 #define UP_DEVICE_HID_PAGE_RESERVED_MONITOR 0x83 #define UP_DEVICE_HID_PAGE_POWER_DEVICE 0x84 #define UP_DEVICE_HID_PAGE_BATTERY_SYSTEM 0x85 struct UpDeviceHidPrivate { int fd; gboolean fake_device; }; G_DEFINE_TYPE_WITH_PRIVATE (UpDeviceHid, up_device_hid, UP_TYPE_DEVICE) static gboolean up_device_hid_refresh (UpDevice *device, UpRefreshReason reason); /** * up_device_hid_is_ups: **/ static gboolean up_device_hid_is_ups (UpDeviceHid *hid) { guint i; int retval; gboolean ret = FALSE; struct hiddev_devinfo device_info; /* get device info */ retval = ioctl (hid->priv->fd, HIDIOCGDEVINFO, &device_info); if (retval < 0) { g_debug ("HIDIOCGDEVINFO failed: %s", strerror (errno)); goto out; } /* can we use the hid device as a UPS? */ for (i = 0; i < device_info.num_applications; i++) { retval = ioctl (hid->priv->fd, HIDIOCAPPLICATION, i); if (retval >> 16 == UP_DEVICE_HID_PAGE_POWER_DEVICE) { ret = TRUE; goto out; } } out: return ret; } /** * up_device_hid_get_string: **/ static const gchar * up_device_hid_get_string (UpDeviceHid *hid, int sindex) { static struct hiddev_string_descriptor sdesc; /* nothing to get */ if (sindex == 0) return ""; sdesc.index = sindex; /* failed */ if (ioctl (hid->priv->fd, HIDIOCGSTRING, &sdesc) < 0) return ""; g_debug ("value: '%s'", sdesc.value); return sdesc.value; } /** * up_device_hid_set_values: **/ static gboolean up_device_hid_set_values (UpDeviceHid *hid, guint32 code, gint32 value) { const gchar *type; gboolean ret = TRUE; UpDevice *device = UP_DEVICE (hid); switch (code) { case UP_DEVICE_HID_REMAINING_CAPACITY: g_object_set (device, "percentage", (gfloat) CLAMP (value, 0, 100), NULL); break; case UP_DEVICE_HID_RUNTIME_TO_EMPTY: g_object_set (device, "time-to-empty", (gint64) value, NULL); break; case UP_DEVICE_HID_CHARGING: if (value != 0) g_object_set (device, "state", UP_DEVICE_STATE_CHARGING, NULL); break; case UP_DEVICE_HID_DISCHARGING: if (value != 0) g_object_set (device, "state", UP_DEVICE_STATE_DISCHARGING, NULL); break; case UP_DEVICE_HID_BATTERY_PRESENT: g_object_set (device, "is-present", (value != 0), NULL); break; case UP_DEVICE_HID_DEVICE_NAME: g_object_set (device, "device-name", up_device_hid_get_string (hid, value), NULL); break; case UP_DEVICE_HID_CHEMISTRY: type = up_device_hid_get_string (hid, value); g_object_set (device, "technology", up_convert_device_technology (type), NULL); break; case UP_DEVICE_HID_RECHARGEABLE: g_object_set (device, "is-rechargeable", (value != 0), NULL); break; case UP_DEVICE_HID_OEM_INFORMATION: g_object_set (device, "vendor", up_device_hid_get_string (hid, value), NULL); break; case UP_DEVICE_HID_PRODUCT: g_object_set (device, "model", up_device_hid_get_string (hid, value), NULL); break; case UP_DEVICE_HID_SERIAL_NUMBER: g_object_set (device, "serial", up_device_hid_get_string (hid, value), NULL); break; case UP_DEVICE_HID_DESIGN_CAPACITY: g_object_set (device, "energy-full-design", (gfloat) value, NULL); break; default: ret = FALSE; break; } return ret; } /** * up_device_hid_get_all_data: **/ static gboolean up_device_hid_get_all_data (UpDeviceHid *hid) { struct hiddev_report_info rinfo; struct hiddev_field_info finfo; struct hiddev_usage_ref uref; int rtype; guint i, j; gboolean ret = FALSE; /* get all results */ for (rtype = HID_REPORT_TYPE_MIN; rtype <= HID_REPORT_TYPE_MAX; rtype++) { rinfo.report_type = rtype; rinfo.report_id = HID_REPORT_ID_FIRST; while (ioctl (hid->priv->fd, HIDIOCGREPORTINFO, &rinfo) >= 0) { for (i = 0; i < rinfo.num_fields; i++) { memset (&finfo, 0, sizeof (finfo)); finfo.report_type = rinfo.report_type; finfo.report_id = rinfo.report_id; finfo.field_index = i; ioctl (hid->priv->fd, HIDIOCGFIELDINFO, &finfo); memset (&uref, 0, sizeof (uref)); for (j = 0; j < finfo.maxusage; j++) { uref.report_type = finfo.report_type; uref.report_id = finfo.report_id; uref.field_index = i; uref.usage_index = j; ioctl (hid->priv->fd, HIDIOCGUCODE, &uref); ioctl (hid->priv->fd, HIDIOCGUSAGE, &uref); /* process each */ up_device_hid_set_values (hid, uref.usage_code, uref.value); /* we got some data */ ret = TRUE; } } rinfo.report_id |= HID_REPORT_ID_NEXT; } } return ret; } /** * up_device_hid_fixup_state: **/ static void up_device_hid_fixup_state (UpDevice *device) { gdouble percentage; /* map states the UPS cannot express */ g_object_get (device, "percentage", &percentage, NULL); if (percentage < UP_DAEMON_EPSILON) g_object_set (device, "state", UP_DEVICE_STATE_EMPTY, NULL); if (percentage > (100.0 - UP_DAEMON_EPSILON)) g_object_set (device, "state", UP_DEVICE_STATE_FULLY_CHARGED, NULL); } /** * up_device_hid_coldplug: * * Return %TRUE on success, %FALSE if we failed to get data and should be removed **/ static gboolean up_device_hid_coldplug (UpDevice *device) { UpDeviceHid *hid = UP_DEVICE_HID (device); GUdevDevice *native; gboolean ret = FALSE; const gchar *device_file; const gchar *type; const gchar *vendor; /* detect what kind of device we are */ native = G_UDEV_DEVICE (up_device_get_native (device)); type = g_udev_device_get_property (native, "UPOWER_BATTERY_TYPE"); if (type == NULL || g_strcmp0 (type, "ups") != 0) goto out; /* get the device file */ device_file = g_udev_device_get_device_file (native); if (device_file == NULL) { g_debug ("could not get device file for HID device"); goto out; } /* first check that we are an UPS */ hid->priv->fake_device = g_udev_device_has_property (native, "UPOWER_FAKE_DEVICE"); if (!hid->priv->fake_device) { /* connect to the device */ g_debug ("using device: %s", device_file); hid->priv->fd = open (device_file, O_RDONLY | O_NONBLOCK); if (hid->priv->fd < 0) { g_debug ("cannot open device file %s", device_file); goto out; } ret = up_device_hid_is_ups (hid); if (!ret) { g_debug ("not a HID device: %s", device_file); goto out; } } /* prefer UPOWER names */ vendor = g_udev_device_get_property (native, "UPOWER_VENDOR"); if (vendor == NULL) vendor = g_udev_device_get_property (native, "ID_VENDOR"); /* hardcode some values */ g_object_set (device, "type", UP_DEVICE_KIND_UPS, "is-rechargeable", TRUE, "power-supply", TRUE, "is-present", TRUE, "vendor", vendor, "has-history", TRUE, "has-statistics", TRUE, NULL); /* coldplug everything */ if (hid->priv->fake_device) { ret = TRUE; if (g_udev_device_get_property_as_boolean (native, "UPOWER_FAKE_HID_CHARGING")) up_device_hid_set_values (hid, UP_DEVICE_HID_CHARGING, 1); else up_device_hid_set_values (hid, UP_DEVICE_HID_DISCHARGING, 1); up_device_hid_set_values (hid, UP_DEVICE_HID_REMAINING_CAPACITY, g_udev_device_get_property_as_int (native, "UPOWER_FAKE_HID_PERCENTAGE")); } else { ret = up_device_hid_get_all_data (hid); if (!ret) { g_debug ("failed to coldplug UPS: %s", device_file); goto out; } } /* fix up device states */ up_device_hid_fixup_state (device); g_object_set (device, "poll-timeout", UP_DEVICE_HID_REFRESH_TIMEOUT, NULL); out: return ret; } /** * up_device_hid_refresh: * * Return %TRUE on success, %FALSE if we failed to refresh or no data **/ static gboolean up_device_hid_refresh (UpDevice *device, UpRefreshReason reason) { gboolean set = FALSE; gboolean ret = FALSE; guint i; struct hiddev_event ev[64]; int rd; UpDeviceHid *hid = UP_DEVICE_HID (device); if (hid->priv->fake_device) goto update_time; /* read any data */ rd = read (hid->priv->fd, ev, sizeof (ev)); /* it's okay if there's nothing as we are non-blocking */ if (rd == -1) { g_debug ("no data"); ret = FALSE; goto out; } /* did we read enough data? */ if (rd < (int) sizeof (ev[0])) { g_warning ("incomplete read (%i<%i)", rd, (int) sizeof (ev[0])); goto out; } /* process each event */ for (i=0; i < rd / sizeof (ev[0]); i++) { set = up_device_hid_set_values (hid, ev[i].hid, ev[i].value); /* if only takes one match to make refresh a success */ if (set) ret = TRUE; } /* fix up device states */ up_device_hid_fixup_state (device); update_time: /* reset time */ g_object_set (device, "update-time", (guint64) g_get_real_time () / G_USEC_PER_SEC, NULL); out: return ret; } /** * up_device_hid_get_on_battery: **/ static gboolean up_device_hid_get_on_battery (UpDevice *device, gboolean *on_battery) { UpDeviceHid *hid = UP_DEVICE_HID (device); UpDeviceKind type; UpDeviceState state; gboolean is_present; g_return_val_if_fail (UP_IS_DEVICE_HID (hid), FALSE); g_return_val_if_fail (on_battery != NULL, FALSE); g_object_get (device, "type", &type, "state", &state, "is-present", &is_present, NULL); if (type != UP_DEVICE_KIND_UPS) return FALSE; if (state == UP_DEVICE_STATE_UNKNOWN) return FALSE; if (!is_present) return FALSE; *on_battery = (state == UP_DEVICE_STATE_DISCHARGING); return TRUE; } /** * up_device_hid_init: **/ static void up_device_hid_init (UpDeviceHid *hid) { hid->priv = up_device_hid_get_instance_private (hid); hid->priv->fd = -1; } /** * up_device_hid_finalize: **/ static void up_device_hid_finalize (GObject *object) { UpDeviceHid *hid; g_return_if_fail (object != NULL); g_return_if_fail (UP_IS_DEVICE_HID (object)); hid = UP_DEVICE_HID (object); g_return_if_fail (hid->priv != NULL); if (hid->priv->fd > 0) close (hid->priv->fd); G_OBJECT_CLASS (up_device_hid_parent_class)->finalize (object); } /** * up_device_hid_class_init: **/ static void up_device_hid_class_init (UpDeviceHidClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); UpDeviceClass *device_class = UP_DEVICE_CLASS (klass); object_class->finalize = up_device_hid_finalize; device_class->coldplug = up_device_hid_coldplug; device_class->get_on_battery = up_device_hid_get_on_battery; device_class->refresh = up_device_hid_refresh; }