From cfff3d242c11dc3ebfa70e7771ee1c094bf2f368 Mon Sep 17 00:00:00 2001
From: Stephen Horvath <s.horvath@outlook.com.au>
Date: Tue, 7 May 2024 04:31:28 +1000
Subject: [PATCH] Add privacy switches to sysfs (#14)

I've added querying the mic and camera privacy switches
through sysfs
(`/sys/devices/platform/framework_laptop/framework_privacy`).

There doesn't really seem to be a proper standard for this, but I've
kept to the same [format as the `dell-privacy`
driver](https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-platform-dell-privacy-wmi),
so if there is scripts or something floating around for dell laptops, it
should be a one or two line change for framework laptops too.

Also don't feel pressured to merge this or anything, it seems way less
useful than fan control, and I mostly just did it to procrastinate doing
an assignment haha.

I've also added a few basically trivial changes to do with my previous
PR.
---
 README.md          |  7 ++++++-
 framework_laptop.c | 48 +++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 49 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index ae8b386..fc0e013 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,12 @@ This driver supports up to 4 fans, and creates a HWMON interface with the name `
 - `fan[1-4]_alarm` - Fan stall indicator (read-only)
 - `pwm[1-4]` - Fan speed control in percent 0-100 (write-only)
 - `pwm[1-4]_enable` - Enable automatic fan control (write-only)
-  - Currently you can write anything to enable, but I recommend writing `2` in case the driver is updated to support disabling automatic fan control.
+  - Currently you can write anything to enable, but writing `2` is recommended in case the driver is updated to support disabling automatic fan control.
   - Writing to the other interfaces will disable automatic fan control.
 - `pwm[1-4]_min` - returns 0 (read-only)
 - `pwm[1-4]_max` - returns 100 (read-only)
+
+### Privacy Switches
+
+This driver exposes the privacy switches as a custom SysFS interface under `/sys/devices/platform/framework_laptop/framework_privacy`.
+It follows the [existing format of the `dell-privacy` driver](https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-platform-dell-privacy-wmi).
diff --git a/framework_laptop.c b/framework_laptop.c
index acfa8ce..1d1d64e 100644
--- a/framework_laptop.c
+++ b/framework_laptop.c
@@ -65,6 +65,13 @@ struct ec_response_chg_limit_control {
 	uint8_t min_percentage;
 } __ec_align1;
 
+#define EC_CMD_PRIVACY_SWITCHES_CHECK_MODE 0x3E14
+
+struct ec_response_privacy_switches_check {
+	uint8_t microphone;
+	uint8_t camera;
+} __ec_align1;
+
 static int charge_limit_control(enum ec_chg_limit_control_modes modes, uint8_t max_percentage) {
 	struct {
 		struct cros_ec_command msg;
@@ -521,8 +528,32 @@ static ssize_t ec_count_fans(size_t *val)
 	return 0;
 }
 
+// --- framework_privacy ---
+static ssize_t framework_privacy_show(struct device *dev,
+				      struct device_attribute *attr, char *buf)
+{
+	int ret;
+	if (!ec_device)
+		return -ENODEV;
+
+	struct cros_ec_device *ec = dev_get_drvdata(ec_device);
+
+	struct ec_response_privacy_switches_check resp;
+
+	ret = cros_ec_cmd(ec, 0, EC_CMD_PRIVACY_SWITCHES_CHECK_MODE, NULL, 0,
+			  &resp, sizeof(resp));
+	if (ret < 0)
+		return -EIO;
+
+	// Output following dell-privacy's format
+	return sysfs_emit(buf, "[Microphone] [%s]\n[Camera] [%s]\n",
+			  resp.microphone ? "unmuted" : "muted",
+			  resp.camera ? "unmuted" : "muted");
+}
+
 #define FW_ATTRS_PER_FAN 8
 
+// --- hwmon sysfs attributes ---
 // clang-format off
 static SENSOR_DEVICE_ATTR_RO(fan1_input, fw_fan_speed, 0); // Fan Reading
 static SENSOR_DEVICE_ATTR_RW(fan1_target, fw_fan_target, 0); // Target RPM (RW on fan 0 only)
@@ -532,6 +563,7 @@ static SENSOR_DEVICE_ATTR_WO(pwm1_enable, fw_pwm_enable, 0); // Set Fan Control
 static SENSOR_DEVICE_ATTR_WO(pwm1, fw_pwm, 0); // Set Fan Speed
 static SENSOR_DEVICE_ATTR_RO(pwm1_min, fw_pwm_min, 0); // Min Fan Speed
 static SENSOR_DEVICE_ATTR_RO(pwm1_max, fw_pwm_max, 0); // Max Fan Speed
+// clang-format on
 
 static SENSOR_DEVICE_ATTR_RO(fan2_input, fw_fan_speed, 1);
 static SENSOR_DEVICE_ATTR_WO(fan2_target, fw_fan_target, 1);
@@ -559,7 +591,6 @@ static SENSOR_DEVICE_ATTR_WO(pwm4_enable, fw_pwm_enable, 3);
 static SENSOR_DEVICE_ATTR_WO(pwm4, fw_pwm, 3);
 static SENSOR_DEVICE_ATTR_RO(pwm4_min, fw_pwm_min, 3);
 static SENSOR_DEVICE_ATTR_RO(pwm4_max, fw_pwm_max, 3);
-// clang-format on
 
 static struct attribute
 	*fw_hwmon_attrs[(EC_FAN_SPEED_ENTRIES * FW_ATTRS_PER_FAN) + 1] = {
@@ -602,13 +633,19 @@ static struct attribute
 		NULL,
 	};
 
-static const struct attribute_group fw_hwmon_group = {
-	.attrs = fw_hwmon_attrs,
+ATTRIBUTE_GROUPS(fw_hwmon);
+
+// --- generic sysfs attributes ---
+static DEVICE_ATTR_RO(framework_privacy);
+
+static struct attribute *framework_laptop_attrs[] = {
+	&dev_attr_framework_privacy.attr,
+	NULL,
 };
 
-static const struct attribute_group *fw_hwmon_groups[] = { &fw_hwmon_group,
-							   NULL };
+ATTRIBUTE_GROUPS(framework_laptop);
 
+// --- platform driver ---
 static struct acpi_battery_hook framework_laptop_battery_hook = {
 	.add_battery = framework_laptop_battery_add,
 	.remove_battery = framework_laptop_battery_remove,
@@ -734,6 +771,7 @@ static struct platform_driver framework_driver = {
 	.driver = {
 		.name = DRV_NAME,
 		.acpi_match_table = device_ids,
+		.dev_groups = framework_laptop_groups,
 	},
 	.probe = framework_probe,
 	.remove = framework_remove,
-- 
GitLab