/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (c) 2016, Eric Koegel * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_LINUX_INPUT_H #include #endif #ifdef HAVE_LIBDRM #include #endif #ifdef HAVE_SYS_SYSMACROS_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_LIBUDEV_H #include #endif #ifdef HAVE_DEVATTR_H #include #endif #ifdef HAVE_LIBPROP_PROPLIB_H #include #endif #ifdef HAVE_SYS_MKDEV_H #include #endif #ifdef HAVE_LIBEVDEV_H #include "libevdev.h" #endif /* For TRACE */ #include "ck-sysdeps.h" #include "ck-device.h" /* We may not have the evdev header because of license issues, so * we'll define the symbol here */ #ifndef EVIOCREVOKE #define EVIOCREVOKE _IOW('E', 0x91, int) #endif static void ck_device_finalize (GObject *object); struct _CkDevice { GObject parent_instance; guint major; guint minor; struct udev_device *udevice; gchar *devnode; CkDeviceCategory category; gint fd; gboolean state; }; static struct udev *dev = NULL; G_DEFINE_TYPE (CkDevice, ck_device, G_TYPE_OBJECT) static void ck_device_class_init (CkDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = ck_device_finalize; } static void ck_device_init (CkDevice *device) { device->fd = -1; } static void ck_device_finalize (GObject *object) { CkDevice *device = CK_DEVICE (object); /* Always revoke/drop master before we are removed */ ck_device_set_active (device, FALSE); if (device->udevice != NULL) { udev_device_unref (device->udevice); } if (device->fd >= 0) { g_close (device->fd, NULL); } g_free (device->devnode); G_OBJECT_CLASS (ck_device_parent_class)->finalize (object); } static gboolean ck_device_evdev_revoke (gint fd) { errno = 0; if (fd < 0) { return FALSE; } if (ioctl (fd, EVIOCREVOKE, NULL) == -1) { g_warning ("failed to revoke access. Error: %s", g_strerror(errno)); return FALSE; } return TRUE; } static gboolean change_drm_master (gint fd, gulong request) { errno = 0; if (fd < 0) { return FALSE; } if (ioctl (fd, request, NULL) == -1) { g_warning ("failed to change drm master setting. Error: %s", g_strerror(errno)); return FALSE; } return TRUE; } static gint ck_device_drm_drop_master (gint fd) { TRACE (); return change_drm_master (fd, DRM_IOCTL_DROP_MASTER); } static gboolean ck_device_drm_set_master (gint fd) { TRACE (); return change_drm_master (fd, DRM_IOCTL_SET_MASTER); } static gint ck_device_open (CkDevice *device, gboolean active) { gint fd = -1; TRACE (); if (device == NULL || device->udevice == NULL || device->devnode == NULL) { g_debug ("invalid device"); return -1; } g_debug ("opening devnode: %s", device->devnode); fd = g_open (device->devnode, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK, S_IRUSR|S_IWUSR); if (fd < 0) { g_debug ("failed to open device"); return -1; } switch (device->category) { case DEVICE_DRM: if (active) { if (!ck_device_drm_set_master (fd)) { g_debug ("failed to call drm set master"); g_close (fd, NULL); return -1; } } else { ck_device_drm_drop_master (fd); } break; case DEVICE_EVDEV: if (active == FALSE) { ck_device_evdev_revoke (fd); } break; default: g_debug ("ck_device_open: Device type not supported"); break; } return fd; } static struct udev_device* ck_device_get_udevice_from_devnum (guint major, guint minor) { #if defined(HAVE_DEVATTR_H) struct udev_enumerate *enumerate; struct udev_list_entry *current; struct udev_device *udevice = NULL; prop_dictionary_t dict; gchar majorbuf[16]; gchar minorbuf[16]; enumerate = udev_enumerate_new (dev); if (enumerate == NULL) { g_warning ("ck_device_get_udevice_from_devnum: failed to call udev_enumerate_new"); return NULL; } memset (majorbuf, 0, sizeof(majorbuf)); memset (minorbuf, 0, sizeof(minorbuf)); g_snprintf (majorbuf, 15, "%d", major); g_snprintf (minorbuf, 15, "%d", minor); udev_enumerate_add_match_expr (enumerate, "major", majorbuf); udev_enumerate_add_match_expr (enumerate, "minor", minorbuf); if (udev_enumerate_scan_devices (enumerate) != 0) { /* failed to scan devices */ udev_enumerate_unref (enumerate); return NULL; } current = udev_enumerate_get_list_entry (enumerate); if (current == NULL) { /* No device with that major/minor */ udev_enumerate_unref (enumerate); return NULL; } udev_list_entry_foreach (current, current) { udevice = udev_list_entry_get_device (current); if (udevice == NULL) { continue; } dict = udev_device_get_dictionary (udevice); if (dict == NULL) { /* Why is there a device with no properties? */ udevice = NULL; continue; } /* found our device, create our ref */ udev_device_ref (udevice); break; } udev_enumerate_unref (enumerate); return udevice; #else return udev_device_new_from_devnum (dev, 'c', makedev (major, minor)); #endif } static gchar* udevice_get_device_name (struct udev_device *udevice) { #if defined(HAVE_DEVATTR_H) gchar *name; prop_dictionary_t dict; if (udevice == NULL) { return NULL; } dict = udev_device_get_dictionary (udevice); if (dict == NULL) { return NULL; } prop_dictionary_get_cstring (dict, "driver", &name); return name; #else return g_strdup (udev_device_get_sysname (udevice)); #endif } CkDevice* ck_device_new (guint major, guint minor, gboolean active) { CkDevice *device; const gchar *subsystem; const gchar *sysname; TRACE (); /* We create a single udev instance */ if (dev == NULL) { dev = udev_new (); if (dev == NULL) { return NULL; } } device = g_object_new (CK_TYPE_DEVICE, NULL); device->major = major; device->minor = minor; device->state = active; device->udevice = ck_device_get_udevice_from_devnum (major, minor); if (device->udevice == NULL) { g_warning ("failed to get a udev device, it probably doesn't exist"); g_object_unref (device); return NULL; } subsystem = udev_device_get_subsystem (device->udevice); sysname = udevice_get_device_name (device->udevice); device->devnode = g_strdup (udev_device_get_devnode (device->udevice)); g_debug ("major %d minor %d subsystem %s sysname %s devnode %s active ? %s", major, minor, subsystem, sysname, device->devnode, active ? "TRUE" : "FALSE"); /* Start with other device as a default, we have special things * we do with DRM and EVDEV devices so find and tag them */ device->category = DEVICE_OTHER; if (g_strcmp0 (subsystem, "drm") == 0) { if (g_str_has_prefix (sysname, "card")) { g_debug ("DEVICE_DRM"); device->category = DEVICE_DRM; } } else if (g_strcmp0 (subsystem, "input") == 0) { if (g_str_has_prefix (sysname, "event")) { g_debug ("DEVICE_EVDEV"); device->category = DEVICE_EVDEV; } } if (device->category != DEVICE_OTHER) { /* we don't do device management for anything but * DRM/EVDEV */ device->fd = ck_device_open (device, active); if (device->fd < 0 && active) { /* try again in an inactive state */ device->fd = ck_device_open (device, FALSE); } } return device; } void ck_device_set_active (CkDevice *device, gboolean active) { TRACE (); if (device == NULL) return; if (device->state == active) return; device->state = active; switch (device->category) { case DEVICE_DRM: if (active) { ck_device_drm_set_master (device->fd); } else { ck_device_drm_drop_master (device->fd); } break; case DEVICE_EVDEV: if (active) { /* close the old fd since it was revoked when * inactive */ g_close (device->fd, NULL); device->fd = ck_device_open (device, active); } else { ck_device_evdev_revoke (device->fd); } break; default: g_warning ("ck_device_set_active: Device type not supported"); break; } } gboolean ck_device_get_active (CkDevice *device) { return device->state; } guint ck_device_get_major (CkDevice *device) { return device->major; } guint ck_device_get_minor (CkDevice *device) { return device->minor; } gint ck_device_get_fd (CkDevice *device) { return device->fd; } CkDeviceCategory ck_device_get_category (CkDevice *device) { return device->category; } gboolean ck_device_compare_devices (CkDevice *device1, CkDevice *device2) { if (device1 == NULL || device2 == NULL) { return FALSE; } if (device1->major != device2->major) { return FALSE; } if (device1->minor != device2->minor) { return FALSE; } return TRUE; } gboolean ck_device_compare (CkDevice *device, guint major, guint minor) { if (device == NULL) { return FALSE; } if (device->major != major) { return FALSE; } if (device->minor != minor) { return FALSE; } return TRUE; } gboolean ck_device_is_server_managed (void) { return TRUE; }