3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering This file is part of systemd.
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering Copyright 2013 Lennart Poettering
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering systemd is free software; you can redistribute it and/or modify it
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering under the terms of the GNU Lesser General Public License as published by
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering (at your option) any later version.
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering systemd is distributed in the hope that it will be useful, but
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering Lesser General Public License for more details.
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering You should have received a copy of the GNU Lesser General Public License
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poetteringstatic struct udev_device *find_pci_or_platform_parent(struct udev_device *device) {
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering subsystem = udev_device_get_subsystem(parent);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering sysname = udev_device_get_sysname(parent);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering const char *c;
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering if (*c == '-') {
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering /* A connector DRM device, let's ignore all but LVDS and eDP! */
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering !startswith(c, "-Embedded DisplayPort-"))
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering value = udev_device_get_sysattr_value(parent, "class");
938d2699d2e818bd996614e89ea3d668200ad2a8Zbigniew Jędrzejewski-Szmek log_warning("Cannot parse PCI class %s of device %s:%s.",
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering /* Graphics card */
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering return find_pci_or_platform_parent(parent);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poetteringstatic bool same_device(struct udev_device *a, struct udev_device *b) {
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering if (!streq_ptr(udev_device_get_subsystem(a), udev_device_get_subsystem(b)))
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering if (!streq_ptr(udev_device_get_sysname(a), udev_device_get_sysname(b)))
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poetteringstatic bool validate_device(struct udev *udev, struct udev_device *device) {
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering _cleanup_udev_enumerate_unref_ struct udev_enumerate *enumerate = NULL;
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering struct udev_list_entry *item = NULL, *first = NULL;
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering /* Verify whether we should actually care for a specific
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * backlight device. For backlight devices there might be
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * multiple ways to access the same control: "firmware"
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * (i.e. ACPI), "platform" (i.e. via the machine's EC) and
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * "raw" (via the graphics card). In general we should prefer
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * "firmware" (i.e. ACPI) or "platform" access over "raw"
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * access, in order not to confuse the BIOS/EC, and
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * compatibility with possible low-level hotkey handling of
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * screen brightness. The kernel will already make sure to
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * expose only one of "firmware" and "platform" for the same
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * device to userspace. However, we still need to make sure
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * that we use "raw" only if no "firmware" or "platform"
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * device for the same device exists. */
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering subsystem = udev_device_get_subsystem(device);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering v = udev_device_get_sysattr_value(device, "type");
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering parent = find_pci_or_platform_parent(device);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering subsystem = udev_device_get_subsystem(parent);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering r = udev_enumerate_add_match_subsystem(enumerate, "backlight");
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering r = udev_enumerate_scan_devices(enumerate);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering first = udev_enumerate_get_list_entry(enumerate);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering _cleanup_udev_device_unref_ struct udev_device *other;
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering other = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering v = udev_device_get_sysattr_value(other, "type");
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering if (!streq_ptr(v, "platform") && !streq_ptr(v, "firmware"))
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering /* OK, so there's another backlight device, and it's a
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * platform or firmware device, so, let's see if we
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * can verify it belongs to the same device as
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering other_parent = find_pci_or_platform_parent(other);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering /* Both have the same PCI parent, that means
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * we are out. */
938d2699d2e818bd996614e89ea3d668200ad2a8Zbigniew Jędrzejewski-Szmek log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
938d2699d2e818bd996614e89ea3d668200ad2a8Zbigniew Jędrzejewski-Szmek udev_device_get_sysname(device),
938d2699d2e818bd996614e89ea3d668200ad2a8Zbigniew Jędrzejewski-Szmek udev_device_get_sysname(other));
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering other_subsystem = udev_device_get_subsystem(other_parent);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering if (streq_ptr(other_subsystem, "platform") && streq_ptr(subsystem, "pci")) {
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering /* The other is connected to the platform bus
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * and we are a PCI device, that also means we
938d2699d2e818bd996614e89ea3d668200ad2a8Zbigniew Jędrzejewski-Szmek log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
938d2699d2e818bd996614e89ea3d668200ad2a8Zbigniew Jędrzejewski-Szmek udev_device_get_sysname(device),
938d2699d2e818bd996614e89ea3d668200ad2a8Zbigniew Jędrzejewski-Szmek udev_device_get_sysname(other));
3cadce7d33e263ec7a6a83c00c11144930258b22Thomas Bächlerstatic unsigned get_max_brightness(struct udev_device *device) {
7b909d7407965c03caaba30daae7aee113627a83Josh Triplett max_brightness_str = udev_device_get_sysattr_value(device, "max_brightness");
c7fdf44d08e1217d40dc092fb90a65978a0f541fLennart Poettering log_warning("Failed to read 'max_brightness' attribute.");
3cadce7d33e263ec7a6a83c00c11144930258b22Thomas Bächler r = safe_atou(max_brightness_str, &max_brightness);
da927ba997d68401563b927f92e6e40e021a8e5cMichal Schmidt log_warning_errno(r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str);
c7fdf44d08e1217d40dc092fb90a65978a0f541fLennart Poettering log_warning("Maximum brightness is 0, ignoring device.");
3cadce7d33e263ec7a6a83c00c11144930258b22Thomas Bächler/* Some systems turn the backlight all the way off at the lowest levels.
3cadce7d33e263ec7a6a83c00c11144930258b22Thomas Bächler * clamp_brightness clamps the saved brightness to at least 1 or 5% of
4cd2b2cf8ca585d15ebc859701b346658262b5bbDenis Tikhomirov * max_brightness in case of 'backlight' subsystem. This avoids preserving
4cd2b2cf8ca585d15ebc859701b346658262b5bbDenis Tikhomirov * an unreadably dim screen, which would otherwise force the user to
4cd2b2cf8ca585d15ebc859701b346658262b5bbDenis Tikhomirov * disable state restoration. */
3cadce7d33e263ec7a6a83c00c11144930258b22Thomas Bächlerstatic void clamp_brightness(struct udev_device *device, char **value, unsigned max_brightness) {
0c9d8f1d4b5018199cb5a9b57580dc1480a7f915Jani Nikula unsigned brightness, new_brightness, min_brightness;
da927ba997d68401563b927f92e6e40e021a8e5cMichal Schmidt log_warning_errno(r, "Failed to parse brightness \"%s\": %m", *value);
4cd2b2cf8ca585d15ebc859701b346658262b5bbDenis Tikhomirov subsystem = udev_device_get_subsystem(device);
4cd2b2cf8ca585d15ebc859701b346658262b5bbDenis Tikhomirov min_brightness = MAX(1U, max_brightness/20);
0c9d8f1d4b5018199cb5a9b57580dc1480a7f915Jani Nikula new_brightness = CLAMP(brightness, min_brightness, max_brightness);
0c9d8f1d4b5018199cb5a9b57580dc1480a7f915Jani Nikula log_info("Saved brightness %s %s to %s.", old_value,
1ca208fb4f93e5869704af1812cbff7130a2fc03Zbigniew Jędrzejewski-Szmek _cleanup_udev_unref_ struct udev *udev = NULL;
1ca208fb4f93e5869704af1812cbff7130a2fc03Zbigniew Jędrzejewski-Szmek _cleanup_udev_device_unref_ struct udev_device *device = NULL;
be3f52f4ed02a9256b1577719677b32a17b525acLennart Poettering _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering log_error("This program requires two arguments.");
ef5bfcf668e6029faa78534dfeb2591df854cdefLennart Poettering r = mkdir_p("/var/lib/systemd/backlight", 0755);
c33b329709ebe2755181980a050d02ec7c81ed87Michal Schmidt log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
938d2699d2e818bd996614e89ea3d668200ad2a8Zbigniew Jędrzejewski-Szmek log_error("Requires a subsystem and sysname pair specifying a backlight device.");
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering ss = strndup(argv[2], sysname - argv[2]);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering if (!streq(ss, "backlight") && !streq(ss, "leds")) {
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering log_error("Not a backlight or LED device: '%s:%s'", ss, sysname);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
56f64d95763a799ba4475daf44d8e9f72a1bd474Michal Schmidt log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
3cadce7d33e263ec7a6a83c00c11144930258b22Thomas Bächler /* If max_brightness is 0, then there is no actual backlight
3cadce7d33e263ec7a6a83c00c11144930258b22Thomas Bächler * device. This happens on desktops with Asus mainboards
3cadce7d33e263ec7a6a83c00c11144930258b22Thomas Bächler * that load the eeepc-wmi module.
be3f52f4ed02a9256b1577719677b32a17b525acLennart Poettering path_id = udev_device_get_property_value(device, "ID_PATH");
be3f52f4ed02a9256b1577719677b32a17b525acLennart Poettering saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname, NULL);
be3f52f4ed02a9256b1577719677b32a17b525acLennart Poettering saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname, NULL);
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering /* If there are multiple conflicting backlight devices, then
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * their probing at boot-time might happen in any order. This
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * means the validity checking of the device then is not
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * reliable, since it might not see other devices conflicting
73e231abde39f22097df50542c745e01de879836Jan Engelhardt * with a specific backlight. To deal with this, we will
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * actively delete backlight state files at shutdown (where
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * device probing should be complete), so that the validity
0f4ba83c397e807939a4eb0b2cbd04ad4ab548ccLennart Poettering * check at boot time doesn't have to be reliable. */
da927ba997d68401563b927f92e6e40e021a8e5cMichal Schmidt log_error_errno(r, "Failed to read %s: %m", saved);
bca81be7755d15e7369d764bfa94a7ca6c595c76Topi Miettinen clamp = udev_device_get_property_value(device, "ID_BACKLIGHT_CLAMP");
bca81be7755d15e7369d764bfa94a7ca6c595c76Topi Miettinen if (!clamp || parse_boolean(clamp) != 0) /* default to clamping */
bca81be7755d15e7369d764bfa94a7ca6c595c76Topi Miettinen clamp_brightness(device, &value, max_brightness);
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering r = udev_device_set_sysattr_value(device, "brightness", value);
c33b329709ebe2755181980a050d02ec7c81ed87Michal Schmidt log_error_errno(r, "Failed to write system 'brightness' attribute: %m");
3731acf1acfb4a6eb68374a5b137f3b368f63381Lennart Poettering value = udev_device_get_sysattr_value(device, "brightness");
938d2699d2e818bd996614e89ea3d668200ad2a8Zbigniew Jędrzejewski-Szmek log_error("Failed to read system 'brightness' attribute");
4c1fc3e404d648c70bd2f50ac50aeac6ece8872eDaniel Mack r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE);
da927ba997d68401563b927f92e6e40e021a8e5cMichal Schmidt log_error_errno(r, "Failed to write %s: %m", saved);