Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

platform/x86: thinkpad_acpi: Register a privacy-screen device

Register a privacy-screen device on laptops with a privacy-screen,
this exports the PrivacyGuard features to user-space using a
standardized vendor-agnostic sysfs interface. Note the sysfs interface
is read-only.

Registering a privacy-screen device with the new privacy-screen class
code will also allow the GPU driver to get a handle to it and export
the privacy-screen setting as a property on the DRM connector object
for the LCD panel. This DRM connector property is a new standardized
interface which all user-space code should use to query and control
the privacy-screen.

Reviewed-by: Emil Velikov <emil.l.velikov@gmail.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
Reviewed-by: Mark Pearson <markpearson@lenovo.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20211005202322.700909-9-hdegoede@redhat.com

+74 -25
+2
drivers/platform/x86/Kconfig
··· 501 501 depends on ACPI_VIDEO || ACPI_VIDEO = n 502 502 depends on BACKLIGHT_CLASS_DEVICE 503 503 depends on I2C 504 + depends on DRM 504 505 select ACPI_PLATFORM_PROFILE 506 + select DRM_PRIVACY_SCREEN 505 507 select HWMON 506 508 select NVRAM 507 509 select NEW_LEDS
+72 -25
drivers/platform/x86/thinkpad_acpi.c
··· 73 73 #include <linux/uaccess.h> 74 74 #include <acpi/battery.h> 75 75 #include <acpi/video.h> 76 + #include <drm/drm_privacy_screen_driver.h> 76 77 #include "dual_accel_detect.h" 77 78 78 79 /* ThinkPad CMOS commands */ ··· 158 157 TP_HKEY_EV_VOL_UP = 0x1015, /* Volume up or unmute */ 159 158 TP_HKEY_EV_VOL_DOWN = 0x1016, /* Volume down or unmute */ 160 159 TP_HKEY_EV_VOL_MUTE = 0x1017, /* Mixer output mute */ 160 + TP_HKEY_EV_PRIVACYGUARD_TOGGLE = 0x130f, /* Toggle priv.guard on/off */ 161 161 162 162 /* Reasons for waking up from S3/S4 */ 163 163 TP_HKEY_EV_WKUP_S3_UNDOCK = 0x2304, /* undock requested, S3 */ ··· 3890 3888 static bool hotkey_notify_extended_hotkey(const u32 hkey) 3891 3889 { 3892 3890 unsigned int scancode; 3891 + 3892 + switch (hkey) { 3893 + case TP_HKEY_EV_PRIVACYGUARD_TOGGLE: 3894 + tpacpi_driver_event(hkey); 3895 + return true; 3896 + } 3893 3897 3894 3898 /* Extended keycodes start at 0x300 and our offset into the map 3895 3899 * TP_ACPI_HOTKEYSCAN_EXTENDED_START. The calculated scancode ··· 9827 9819 * LCD Shadow subdriver, for the Lenovo PrivacyGuard feature 9828 9820 */ 9829 9821 9822 + static struct drm_privacy_screen *lcdshadow_dev; 9830 9823 static acpi_handle lcdshadow_get_handle; 9831 9824 static acpi_handle lcdshadow_set_handle; 9832 - static int lcdshadow_state; 9833 9825 9834 - static int lcdshadow_on_off(bool state) 9826 + static int lcdshadow_set_sw_state(struct drm_privacy_screen *priv, 9827 + enum drm_privacy_screen_status state) 9835 9828 { 9836 9829 int output; 9830 + 9831 + if (WARN_ON(!mutex_is_locked(&priv->lock))) 9832 + return -EIO; 9837 9833 9838 9834 if (!acpi_evalf(lcdshadow_set_handle, &output, NULL, "dd", (int)state)) 9839 9835 return -EIO; 9840 9836 9841 - lcdshadow_state = state; 9837 + priv->hw_state = priv->sw_state = state; 9842 9838 return 0; 9843 9839 } 9844 9840 9845 - static int lcdshadow_set(bool on) 9841 + static void lcdshadow_get_hw_state(struct drm_privacy_screen *priv) 9846 9842 { 9847 - if (lcdshadow_state < 0) 9848 - return lcdshadow_state; 9849 - if (lcdshadow_state == on) 9850 - return 0; 9851 - return lcdshadow_on_off(on); 9843 + int output; 9844 + 9845 + if (!acpi_evalf(lcdshadow_get_handle, &output, NULL, "dd", 0)) 9846 + return; 9847 + 9848 + priv->hw_state = priv->sw_state = output & 0x1; 9852 9849 } 9850 + 9851 + static const struct drm_privacy_screen_ops lcdshadow_ops = { 9852 + .set_sw_state = lcdshadow_set_sw_state, 9853 + .get_hw_state = lcdshadow_get_hw_state, 9854 + }; 9853 9855 9854 9856 static int tpacpi_lcdshadow_init(struct ibm_init_struct *iibm) 9855 9857 { ··· 9868 9850 9869 9851 status1 = acpi_get_handle(hkey_handle, "GSSS", &lcdshadow_get_handle); 9870 9852 status2 = acpi_get_handle(hkey_handle, "SSSS", &lcdshadow_set_handle); 9871 - if (ACPI_FAILURE(status1) || ACPI_FAILURE(status2)) { 9872 - lcdshadow_state = -ENODEV; 9853 + if (ACPI_FAILURE(status1) || ACPI_FAILURE(status2)) 9873 9854 return 0; 9874 - } 9875 9855 9876 - if (!acpi_evalf(lcdshadow_get_handle, &output, NULL, "dd", 0)) { 9877 - lcdshadow_state = -EIO; 9856 + if (!acpi_evalf(lcdshadow_get_handle, &output, NULL, "dd", 0)) 9878 9857 return -EIO; 9879 - } 9880 - if (!(output & 0x10000)) { 9881 - lcdshadow_state = -ENODEV; 9858 + 9859 + if (!(output & 0x10000)) 9882 9860 return 0; 9883 - } 9884 - lcdshadow_state = output & 0x1; 9861 + 9862 + lcdshadow_dev = drm_privacy_screen_register(&tpacpi_pdev->dev, 9863 + &lcdshadow_ops); 9864 + if (IS_ERR(lcdshadow_dev)) 9865 + return PTR_ERR(lcdshadow_dev); 9885 9866 9886 9867 return 0; 9887 9868 } 9888 9869 9870 + static void lcdshadow_exit(void) 9871 + { 9872 + drm_privacy_screen_unregister(lcdshadow_dev); 9873 + } 9874 + 9889 9875 static void lcdshadow_resume(void) 9890 9876 { 9891 - if (lcdshadow_state >= 0) 9892 - lcdshadow_on_off(lcdshadow_state); 9877 + if (!lcdshadow_dev) 9878 + return; 9879 + 9880 + mutex_lock(&lcdshadow_dev->lock); 9881 + lcdshadow_set_sw_state(lcdshadow_dev, lcdshadow_dev->sw_state); 9882 + mutex_unlock(&lcdshadow_dev->lock); 9893 9883 } 9894 9884 9895 9885 static int lcdshadow_read(struct seq_file *m) 9896 9886 { 9897 - if (lcdshadow_state < 0) { 9887 + if (!lcdshadow_dev) { 9898 9888 seq_puts(m, "status:\t\tnot supported\n"); 9899 9889 } else { 9900 - seq_printf(m, "status:\t\t%d\n", lcdshadow_state); 9890 + seq_printf(m, "status:\t\t%d\n", lcdshadow_dev->hw_state); 9901 9891 seq_puts(m, "commands:\t0, 1\n"); 9902 9892 } 9903 9893 ··· 9917 9891 char *cmd; 9918 9892 int res, state = -EINVAL; 9919 9893 9920 - if (lcdshadow_state < 0) 9894 + if (!lcdshadow_dev) 9921 9895 return -ENODEV; 9922 9896 9923 9897 while ((cmd = strsep(&buf, ","))) { ··· 9929 9903 if (state >= 2 || state < 0) 9930 9904 return -EINVAL; 9931 9905 9932 - return lcdshadow_set(state); 9906 + mutex_lock(&lcdshadow_dev->lock); 9907 + res = lcdshadow_set_sw_state(lcdshadow_dev, state); 9908 + mutex_unlock(&lcdshadow_dev->lock); 9909 + 9910 + drm_privacy_screen_call_notifier_chain(lcdshadow_dev); 9911 + 9912 + return res; 9933 9913 } 9934 9914 9935 9915 static struct ibm_struct lcdshadow_driver_data = { 9936 9916 .name = "lcdshadow", 9917 + .exit = lcdshadow_exit, 9937 9918 .resume = lcdshadow_resume, 9938 9919 .read = lcdshadow_read, 9939 9920 .write = lcdshadow_write, ··· 10749 10716 /* If we are already accessing DYTC then skip dytc update */ 10750 10717 if (!atomic_add_unless(&dytc_ignore_event, -1, 0)) 10751 10718 dytc_profile_refresh(); 10719 + } 10720 + 10721 + if (lcdshadow_dev && hkey_event == TP_HKEY_EV_PRIVACYGUARD_TOGGLE) { 10722 + enum drm_privacy_screen_status old_hw_state; 10723 + bool changed; 10724 + 10725 + mutex_lock(&lcdshadow_dev->lock); 10726 + old_hw_state = lcdshadow_dev->hw_state; 10727 + lcdshadow_get_hw_state(lcdshadow_dev); 10728 + changed = lcdshadow_dev->hw_state != old_hw_state; 10729 + mutex_unlock(&lcdshadow_dev->lock); 10730 + 10731 + if (changed) 10732 + drm_privacy_screen_call_notifier_chain(lcdshadow_dev); 10752 10733 } 10753 10734 } 10754 10735