2013-08-14 01:57:02 +02:00
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd .
Copyright 2013 Lennart Poettering
systemd is free software ; you can redistribute it and / or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation ; either version 2.1 of the License , or
( at your option ) any later version .
systemd 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
Lesser General Public License for more details .
You should have received a copy of the GNU Lesser General Public License
along with systemd ; If not , see < http : //www.gnu.org/licenses/>.
* * */
# include "util.h"
# include "mkdir.h"
# include "fileio.h"
2013-10-13 02:28:21 +02:00
# include "libudev.h"
# include "udev-util.h"
2013-08-14 01:57:02 +02:00
2013-10-14 02:12:52 +02:00
static struct udev_device * find_pci_or_platform_parent ( struct udev_device * device ) {
struct udev_device * parent ;
const char * subsystem , * sysname ;
assert ( device ) ;
parent = udev_device_get_parent ( device ) ;
if ( ! parent )
return NULL ;
subsystem = udev_device_get_subsystem ( parent ) ;
if ( ! subsystem )
return NULL ;
sysname = udev_device_get_sysname ( parent ) ;
if ( ! sysname )
return NULL ;
if ( streq ( subsystem , " drm " ) ) {
const char * c ;
c = startswith ( sysname , " card " ) ;
if ( ! c )
return NULL ;
c + = strspn ( c , " 0123456789 " ) ;
if ( * c = = ' - ' ) {
/* A connector DRM device, let's ignore all but LVDS and eDP! */
if ( ! startswith ( c , " -LVDS- " ) & &
! startswith ( c , " -Embedded DisplayPort- " ) )
return NULL ;
}
} else if ( streq ( subsystem , " pci " ) ) {
const char * value ;
value = udev_device_get_sysattr_value ( parent , " class " ) ;
if ( value ) {
unsigned long class ;
if ( safe_atolu ( value , & class ) < 0 ) {
log_warning ( " Cannot parse PCI class %s of device %s:%s. " , value , subsystem , sysname ) ;
return NULL ;
}
/* Graphics card */
if ( class = = 0x30000 )
return parent ;
}
} else if ( streq ( subsystem , " platform " ) )
return parent ;
return find_pci_or_platform_parent ( parent ) ;
}
static bool same_device ( struct udev_device * a , struct udev_device * b ) {
assert ( a ) ;
assert ( b ) ;
if ( ! streq_ptr ( udev_device_get_subsystem ( a ) , udev_device_get_subsystem ( b ) ) )
return false ;
if ( ! streq_ptr ( udev_device_get_sysname ( a ) , udev_device_get_sysname ( b ) ) )
return false ;
return true ;
}
static bool validate_device ( struct udev * udev , struct udev_device * device ) {
_cleanup_udev_enumerate_unref_ struct udev_enumerate * enumerate = NULL ;
struct udev_list_entry * item = NULL , * first = NULL ;
struct udev_device * parent ;
const char * v , * subsystem ;
int r ;
assert ( udev ) ;
assert ( device ) ;
/* Verify whether we should actually care for a specific
* backlight device . For backlight devices there might be
* multiple ways to access the same control : " firmware "
* ( i . e . ACPI ) , " platform " ( i . e . via the machine ' s EC ) and
* " raw " ( via the graphics card ) . In general we should prefer
* " firmware " ( i . e . ACPI ) or " platform " access over " raw "
* access , in order not to confuse the BIOS / EC , and
* compatibility with possible low - level hotkey handling of
* screen brightness . The kernel will already make sure to
* expose only one of " firmware " and " platform " for the same
* device to userspace . However , we still need to make sure
* that we use " raw " only if no " firmware " or " platform "
* device for the same device exists . */
subsystem = udev_device_get_subsystem ( device ) ;
if ( ! streq_ptr ( subsystem , " backlight " ) )
return true ;
v = udev_device_get_sysattr_value ( device , " type " ) ;
if ( ! streq_ptr ( v , " raw " ) )
return true ;
parent = find_pci_or_platform_parent ( device ) ;
if ( ! parent )
return true ;
subsystem = udev_device_get_subsystem ( parent ) ;
if ( ! subsystem )
return true ;
enumerate = udev_enumerate_new ( udev ) ;
if ( ! enumerate )
return true ;
r = udev_enumerate_add_match_subsystem ( enumerate , " backlight " ) ;
if ( r < 0 )
return true ;
r = udev_enumerate_scan_devices ( enumerate ) ;
if ( r < 0 )
return true ;
first = udev_enumerate_get_list_entry ( enumerate ) ;
udev_list_entry_foreach ( item , first ) {
_cleanup_udev_device_unref_ struct udev_device * other ;
struct udev_device * other_parent ;
const char * other_subsystem ;
other = udev_device_new_from_syspath ( udev , udev_list_entry_get_name ( item ) ) ;
if ( ! other )
return true ;
if ( same_device ( device , other ) )
continue ;
v = udev_device_get_sysattr_value ( other , " type " ) ;
if ( ! streq_ptr ( v , " platform " ) & & ! streq_ptr ( v , " firmware " ) )
continue ;
/* OK, so there's another backlight device, and it's a
* platform or firmware device , so , let ' s see if we
* can verify it belongs to the same device as
* ours . */
other_parent = find_pci_or_platform_parent ( other ) ;
if ( ! other_parent )
continue ;
if ( same_device ( parent , other_parent ) ) {
/* Both have the same PCI parent, that means
* we are out . */
log_debug ( " Skipping backlight device %s, since backlight device %s is on same PCI device and, takes precedence. " , udev_device_get_sysname ( device ) , udev_device_get_sysname ( other ) ) ;
return false ;
}
other_subsystem = udev_device_get_subsystem ( other_parent ) ;
if ( streq_ptr ( other_subsystem , " platform " ) & & streq_ptr ( subsystem , " pci " ) ) {
/* The other is connected to the platform bus
* and we are a PCI device , that also means we
* are out . */
log_debug ( " Skipping backlight device %s, since backlight device %s is a platform device and takes precedence. " , udev_device_get_sysname ( device ) , udev_device_get_sysname ( other ) ) ;
return false ;
}
}
return true ;
}
2013-08-14 01:57:02 +02:00
int main ( int argc , char * argv [ ] ) {
2013-10-13 02:28:21 +02:00
_cleanup_udev_unref_ struct udev * udev = NULL ;
_cleanup_udev_device_unref_ struct udev_device * device = NULL ;
2013-10-14 02:12:52 +02:00
_cleanup_free_ char * saved = NULL , * ss = NULL ;
const char * sysname ;
2013-08-14 01:57:02 +02:00
int r ;
if ( argc ! = 3 ) {
log_error ( " This program requires two arguments. " ) ;
return EXIT_FAILURE ;
}
log_set_target ( LOG_TARGET_AUTO ) ;
log_parse_environment ( ) ;
log_open ( ) ;
umask ( 0022 ) ;
2013-09-18 00:28:35 +02:00
r = mkdir_p ( " /var/lib/systemd/backlight " , 0755 ) ;
2013-08-14 01:57:02 +02:00
if ( r < 0 ) {
log_error ( " Failed to create backlight directory: %s " , strerror ( - r ) ) ;
2013-10-13 02:28:21 +02:00
return EXIT_FAILURE ;
2013-08-14 01:57:02 +02:00
}
udev = udev_new ( ) ;
if ( ! udev ) {
2013-10-13 02:28:21 +02:00
log_oom ( ) ;
return EXIT_FAILURE ;
2013-08-14 01:57:02 +02:00
}
2013-10-14 02:12:52 +02:00
sysname = strchr ( argv [ 2 ] , ' : ' ) ;
if ( ! sysname ) {
log_error ( " Requires pair of subsystem and sysname for specifying backlight device. " ) ;
return EXIT_FAILURE ;
}
ss = strndup ( argv [ 2 ] , sysname - argv [ 2 ] ) ;
if ( ! ss ) {
log_oom ( ) ;
return EXIT_FAILURE ;
}
sysname + + ;
if ( ! streq ( ss , " backlight " ) & & ! streq ( ss , " leds " ) ) {
log_error ( " Not a backlight or LED device: '%s:%s' " , ss , sysname ) ;
return EXIT_FAILURE ;
}
2013-08-14 02:55:57 +02:00
errno = 0 ;
2013-10-14 02:12:52 +02:00
device = udev_device_new_from_subsystem_sysname ( udev , ss , sysname ) ;
2013-08-14 01:57:02 +02:00
if ( ! device ) {
2013-10-13 02:28:21 +02:00
if ( errno ! = 0 )
2013-10-14 02:12:52 +02:00
log_error ( " Failed to get backlight or LED device '%s:%s': %m " , ss , sysname ) ;
2013-10-13 02:28:21 +02:00
else
2013-10-14 02:12:52 +02:00
log_oom ( ) ;
2013-08-14 01:57:02 +02:00
2013-10-13 02:28:21 +02:00
return EXIT_FAILURE ;
2013-08-14 01:57:02 +02:00
}
2013-10-14 02:12:52 +02:00
saved = strjoin ( " /var/lib/systemd/backlight/ " , ss , " : " , sysname , NULL ) ;
2013-08-14 01:57:02 +02:00
if ( ! saved ) {
2013-10-13 02:28:21 +02:00
log_oom ( ) ;
return EXIT_FAILURE ;
2013-08-14 01:57:02 +02:00
}
2013-10-14 02:12:52 +02:00
/* If there are multiple conflicting backlight devices, then
* their probing at boot - time might happen in any order . This
* means the validity checking of the device then is not
* reliable , since it might not see other devices conflicting
* with a specific backlight . To deal with this we will
* actively delete backlight state files at shutdown ( where
* device probing should be complete ) , so that the validity
* check at boot time doesn ' t have to be reliable . */
2013-08-14 01:57:02 +02:00
if ( streq ( argv [ 1 ] , " load " ) ) {
_cleanup_free_ char * value = NULL ;
2013-10-14 02:12:52 +02:00
if ( ! validate_device ( udev , device ) )
return EXIT_SUCCESS ;
2013-08-14 01:57:02 +02:00
r = read_one_line_file ( saved , & value ) ;
if ( r < 0 ) {
2013-10-13 02:28:21 +02:00
if ( r = = - ENOENT )
return EXIT_SUCCESS ;
2013-08-14 01:57:02 +02:00
log_error ( " Failed to read %s: %s " , saved , strerror ( - r ) ) ;
2013-10-13 02:28:21 +02:00
return EXIT_FAILURE ;
2013-08-14 01:57:02 +02:00
}
r = udev_device_set_sysattr_value ( device , " brightness " , value ) ;
if ( r < 0 ) {
log_error ( " Failed to write system attribute: %s " , strerror ( - r ) ) ;
2013-10-13 02:28:21 +02:00
return EXIT_FAILURE ;
2013-08-14 01:57:02 +02:00
}
} else if ( streq ( argv [ 1 ] , " save " ) ) {
const char * value ;
2013-10-14 02:12:52 +02:00
if ( ! validate_device ( udev , device ) ) {
unlink ( saved ) ;
return EXIT_SUCCESS ;
}
2013-08-14 01:57:02 +02:00
value = udev_device_get_sysattr_value ( device , " brightness " ) ;
if ( ! value ) {
log_error ( " Failed to read system attribute: %s " , strerror ( - r ) ) ;
2013-10-13 02:28:21 +02:00
return EXIT_FAILURE ;
2013-08-14 01:57:02 +02:00
}
r = write_string_file ( saved , value ) ;
if ( r < 0 ) {
log_error ( " Failed to write %s: %s " , saved , strerror ( - r ) ) ;
2013-10-13 02:28:21 +02:00
return EXIT_FAILURE ;
2013-08-14 01:57:02 +02:00
}
} else {
log_error ( " Unknown verb %s. " , argv [ 1 ] ) ;
2013-10-13 02:28:21 +02:00
return EXIT_FAILURE ;
2013-08-14 01:57:02 +02:00
}
2013-10-13 02:28:21 +02:00
return EXIT_SUCCESS ;
2013-08-14 01:57:02 +02:00
}