lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <1458127687-25366-1-git-send-email-kernel@kempniu.pl>
Date:	Wed, 16 Mar 2016 12:28:07 +0100
From:	Michał Kępień <kernel@...pniu.pl>
To:	Jonathan Woithe <jwoithe@...t42.net>,
	Darren Hart <dvhart@...radead.org>
Cc:	platform-driver-x86@...r.kernel.org, linux-kernel@...r.kernel.org
Subject: [PATCH] fujitsu-laptop: Support radio LED

Lifebook E734/E744/E754 has a LED which the manual calls "radio
components indicator".  It should be lit when any radio transmitter is
enabled.  Its state can be read and set using ACPI (FUNC interface,
RFKILL method).

Signed-off-by: Michał Kępień <kernel@...pniu.pl>
---
First of all, this patch raises a couple of checkpatch warnings.  I
opted for consistency with existing code, which checkpatch doesn't like
as well.  Please let me know if you'd like me to fix the warnings (if
that's desired, I could also clean up the whole driver to make
checkpatch happy).

Now, about the LED itself.  As Lifebook E734/E744/E754 only has a button
(as compared to a slider) for enabling/disabling radio transmitters, I
believe the LED in question is meant to indicate whether all radio
transmitters are currently on or off.  However, pressing the radio
toggle button doesn't change the hardware state of the transmitters.
Consider the following scenario:

 1. Power the machine up.  Radio LED is on.
 2. Press the radio toggle button before the bootloader kicks in.
 3. Radio LED is turned off.
 4. Wait for Linux to boot.

Reading /sys/devices/platform/fujitsu-laptop/radios then yields
"killed", but you can still connect to Bluetooth devices and wireless
networks.  So it looks like this machine only relies on soft rfkill.

As for detecting whether the LED is present on a given machine, I had to
resort to educated guesswork.  I assumed this LED is present on all
devices which have a radio toggle button instead of a slider.  My
Lifebook E744 holds 0x01010001 in BTNI.  By comparing the bits and
buttons with those of a Lifebook E8420 (BTNI=0x000F0101, has a slider),
I put my money on bit 24 as the indicator of the radio toggle button
being present.  I might be completely wrong and this needs testing on a
broader set of devices.  See also three paragraphs below for an
alternative.

Figuring out how the LED is controlled was more deterministic as all it
took was decompiling the DSDT and taking a look at method S000 (the
RFKILL method of the FUNC interface).  Here is the relevant part:

    Method (S000, 3, Serialized)
    {
        Name (_T_0, Zero)  // _T_x: Emitted by ASL Compiler
        Local0 = Zero
        While (One)
        {
            _T_0 = Arg0
            If ((_T_0 == Zero))
            {
                Local0 |= 0x00020000
                Local0 |= 0x0200
                Local0 |= 0x0100
                Local0 |= 0x20
            }
	    ...
            ElseIf ((_T_0 == 0x04))
            {
                Arg1 = ((Arg2 << 0x10) | (Arg1 & 0xFFFF))
                Local0 = FSMI (0x91, Arg0, Arg1)
                If ((Local0 & 0x20))
                {
                    RFSW = One
                }
                Else
                {
                    RFSW = Zero
                }
    
                Local0 |= (RFSW << 0x05)
                Local0 |= (DKON << 0x09)
                Local0 |= (^^LID._LID () << 0x08)
            }
            ElseIf ((_T_0 == 0x05))
            {
                If ((Arg1 & 0x20))
                {
                    If ((Arg2 & 0x20))
                    {
                        RFSW = One
                    }
                    Else
                    {
                        RFSW = Zero
                    }
                }
    
                Arg1 = ((Arg2 << 0x10) | (Arg1 & 0xFFFF))
                Local0 = FSMI (0x91, Arg0, Arg1)
            }
            ...
            Break
        }
    
        Return (Local0)
    }

When Arg0 is 0x04, current hardware state is queried and returned (this
is already done by the driver).  When Arg0 is 0x05 and Arg1 is 0x20, one
can change the contents of RFSW (RF SWitch?), which eventually results
in turning the LED on or off (depending on the value of Arg2).  The 0x05
branch is not present in a DSDT dump from a Lifebook E8420, which has a
slider instead of a radio toggle button.

Note that when called with Arg0 set to 0x00, S000 returns 0x00020320 on
a Lifebook E744 (this is the value saved in the rfkill_supported field
of struct fujitsu_hotkey_t).  Bit 16 is not set on a Lifebook E8420, so
it might mean that this is an indicator of a radio toggle button being
present. 

Sadly, this implementation is unsuitable for use with "heavy" LED
triggers, like phy0rx.  Once blinking frequency achieves a certain
level, the system hangs.  I'm not sure how much of an issue this is as
I'm pretty sure other LEDs registered by fujitsu-laptop would also cause
a hang when assigned to a similar trigger as they are also controlled
using ACPI.

While it's not essential, it would be nice to initialize soft rfkill
state of all radio transmitters to the value of RFSW upon boot.  Note
that this value is persisted between reboots until the battery is
removed, but can be changed before the kernel is booted.  I haven't
found an rfkill function which would enable one to set all rfkill
devices to a chosen initial soft state (note that fujitsu-laptop doesn't
create any rfkill devices on its own).  Is this possible/desired or
should this task simply be delegated to userspace?

One last remark is that I think this LED would best be driven by an
inverted airplane mode LED trigger (as proposed by João Paulo Rechi
Vita).  As the code for that trigger is not yet merged, I refrained from
setting the default_trigger field in struct led_classdev radio_led.
Perhaps it's a candidate for a follow-up patch in the future.

And finally, perhaps some of the remarks above belong in the commit
message for future reference.  Please advise.

 drivers/platform/x86/fujitsu-laptop.c |   45 +++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c
index ffc84cc..7813e482 100644
--- a/drivers/platform/x86/fujitsu-laptop.c
+++ b/drivers/platform/x86/fujitsu-laptop.c
@@ -107,6 +107,7 @@
 #define KEYBOARD_LAMPS	0x100
 #define LOGOLAMP_POWERON 0x2000
 #define LOGOLAMP_ALWAYS  0x4000
+#define RADIO_LED_ON	0x20
 #endif
 
 /* Hotkey details */
@@ -174,6 +175,7 @@ struct fujitsu_hotkey_t {
 	int rfkill_state;
 	int logolamp_registered;
 	int kblamps_registered;
+	int radio_led_registered;
 };
 
 static struct fujitsu_hotkey_t *fujitsu_hotkey;
@@ -200,6 +202,16 @@ static struct led_classdev kblamps_led = {
  .brightness_get = kblamps_get,
  .brightness_set = kblamps_set
 };
+
+static enum led_brightness radio_led_get(struct led_classdev *cdev);
+static void radio_led_set(struct led_classdev *cdev,
+			       enum led_brightness brightness);
+
+static struct led_classdev radio_led = {
+ .name = "fujitsu::radio_led",
+ .brightness_get = radio_led_get,
+ .brightness_set = radio_led_set
+};
 #endif
 
 #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
@@ -275,6 +287,15 @@ static void kblamps_set(struct led_classdev *cdev,
 		call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_OFF);
 }
 
+static void radio_led_set(struct led_classdev *cdev,
+				enum led_brightness brightness)
+{
+	if (brightness >= LED_FULL)
+		call_fext_func(FUNC_RFKILL, 0x5, RADIO_LED_ON, RADIO_LED_ON);
+	else
+		call_fext_func(FUNC_RFKILL, 0x5, RADIO_LED_ON, 0x0);
+}
+
 static enum led_brightness logolamp_get(struct led_classdev *cdev)
 {
 	enum led_brightness brightness = LED_OFF;
@@ -299,6 +320,16 @@ static enum led_brightness kblamps_get(struct led_classdev *cdev)
 
 	return brightness;
 }
+
+static enum led_brightness radio_led_get(struct led_classdev *cdev)
+{
+	enum led_brightness brightness = LED_OFF;
+
+	if (call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0) & RADIO_LED_ON)
+		brightness = LED_FULL;
+
+	return brightness;
+}
 #endif
 
 /* Hardware access for LCD brightness control */
@@ -895,6 +926,17 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device)
 			       result);
 		}
 	}
+
+	if (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) {
+		result = led_classdev_register(&fujitsu->pf_device->dev,
+						&radio_led);
+		if (result == 0) {
+			fujitsu_hotkey->radio_led_registered = 1;
+		} else {
+			pr_err("Could not register LED handler for radio LED, error %i\n",
+			       result);
+		}
+	}
 #endif
 
 	return result;
@@ -921,6 +963,9 @@ static int acpi_fujitsu_hotkey_remove(struct acpi_device *device)
 
 	if (fujitsu_hotkey->kblamps_registered)
 		led_classdev_unregister(&kblamps_led);
+
+	if (fujitsu_hotkey->radio_led_registered)
+		led_classdev_unregister(&radio_led);
 #endif
 
 	input_unregister_device(input);
-- 
1.7.10.4

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ