2023-11-06 12:35:04

by Florian Eckert

[permalink] [raw]
Subject: [Patch v7 0/6] ledtrig-tty: add additional tty state evaluation

Changes in v7:
==============
- Patch 1/7 is no longer included from the previous patch set v6, as it has
already been merged into the master branch [5].
- As requested by Maarten, I have added a 'Fixes:' tag to patch 2/6 of
this patch set, so that this commit should also be backported to the
stable branches, as it is a memory leak.
- As requested by Maarten, I added an invert flag on LED blink, so that
the LED blinks in the correct order.
* LED was 'on' in the previous round, then it should first go 'off' and
then 'on' again when it should blink (data has been transferred).
* LED was 'off' in the previous round, then it should first go 'on' and
then 'off' again when it should blink (data has been transferred).

Links:
[5] https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/drivers/tty/tty_io.c?h=next-20231106&id=838eb763c3e939a8de8d4c55a17ddcce737685c1

Changes in v6:
==============
v6: https://lore.kernel.org/linux-leds/[email protected]/
This is a paritial rewrite of the changes to make the function for
setting the tty evaluation configurable. This change was requested and
comment by Greg K-H at the last review [1]. The main changes are.
- Split the changes into smaller commits to make reviewing easier.
- Use a completion to sync the sysfs and the delay scheduler work on
shared variables.
- Adding the base-commit to this overview, that reviewer know which base
commit I am using.
Base branch is:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Patch [1/7]:
This patch is already included in the tty branch [2], but it is still
included in this patchsset, so that the following patches of the
base-commit branch could be applied correctly.
Patch [2/7]:
Add a new helper function tty_get_tiocm(). This got already a
'Acked-by: Greg Kroah-Hartman' [3] and is not changed.
Patch [3/7]:
Add missing of freeing an allocated ttyname buffer on trigger
deactivation. This is a memory leak fix patch and should also be
backported to the 'stable' branches.
Patch [4/7]:
As requested by greg k-h this is more a 'dev_warn' instead of a
'dev_info'. This could also be backported to the 'stable' branches if
needed.
Patch [5/7]:
Use a completion to sync for sysfs read/write and the delay scheduler
work. I hope I am using the completion correctly. I wasn't sure if I
should secure the sysfs read and write access at the same time via a
mutex. With this change, the work is also not stopped as it was before
when no ttyname was set via sysfs. A tty should always be set when this
trigger is used. And is therefore not a problem from my point of view.
Patch [6/7]:
Make rx tx activitate configurable. In the previous implementation,
there was still the ttytrigger flag variable. This flag variable was
replaced by individual variables in the data struct. Now these variables
can be accessed without masking. The commit was rebased and cleaned up
to use the completion implementation.
Patch [7/7]:
Adding additional trigger line state sources. The commit was also
rebased and cleaned up to use the completion implementation.

Changes in v5:
==============
v5: https://lore.kernel.org/linux-leds/[email protected]/
- Update commit message as request by greg k-h, to make the commit
message more generic and not focusing on my use case [2].
- Removing PATCH v4 1/3 from previous set. This has been already applied
to tty-testing [3] by greg k-h.
- As requested by greq k-h. I have also made the following changes to
PATCH v4 3/3 [4].
* Add a comment to the enum that this is already used for bit
evaluation and sysfs read and write.
* Renaming the variable 'unsigned long mode' to
'unsigned long ttytrigger' in the ledtrig_tty_data structure to make
it clearer that the selected triggers are stored there.
* Using sysfs_emit() function to dump the requestd ttytrigger to
userland.
* Also using the kstrtobool() function to write the selected
ttytrigger via the sysfs. This values are booleans.
- I also removed the function ledtrig_tty_evaluate() from my last
patchses PATCH v4 3/3 [4]. The new API tty_get_tiocm() function
is only called once now and checked for each ttytrigger bit.
Previously this function was called for each bit, which is not
necessary.

Links:
[2] https://lore.kernel.org/linux-leds/2023102115-stock-scrambled-f7d5@gregkh/
[3] https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git/commit/?h=tty-testing&id=838eb763c3e939a8de8d4c55a17ddcce737685c1
[4] https://lore.kernel.org/linux-leds/[email protected]/

Changes in v4:
==============
v4: https://lore.kernel.org/linux-leds/[email protected]/
- Merging patch 3/4 into patch number 4/4 from previous series, because
it fixes a problem that does not exist upstream. This was a note from
the build robot regarding my change that I added with previous series.
This change was never upstream and therefore this is not relevant.
- Update the commit message of patch 1/3 of this series, that this
commit
also changes the 'ndashes' to simple dashes. There were no changes, so
I add the 'Reviewed-by' that the commit received before.
- With this patchset version I have reworked my implementation for the
evaluation of the additional line state, so that this changes becomes
smaller. As basis I have used the staged commits from Christian Marangi
that makes this changes to the netdev trigger. This has already been
applied to 'for-leds-next-next' by Lee Jones. I adapted this to the
tty trigger.
Convert device attr to macro:
https://git.kernel.org/pub/scm/linux/kernel/git/lee/leds.git/commit/drivers/leds/trigger?h=for-leds-next-next&id=509412749002f4bac4c29f2012fff90c08d8afca
Unify sysfs and state handling:
https://git.kernel.org/pub/scm/linux/kernel/git/lee/leds.git/commit/drivers/leds/trigger?h=for-leds-next-next&id=0fd93ac8582627bee9a3c824489f302dff722881

Changes in v3:
==============
v3: https://lore.kernel.org/linux-leds/[email protected]/
- Add missing 'kernel test robot' information to the commit message.
- Additional information added to the commit message

Changes in v2:
==============
v2: https://lore.kernel.org/linux-leds/[email protected]/
- rename new function from tty_get_mget() to tty_get_tiocm() as
requested by 'Jiri Slaby'.
- As suggested by 'Jiri Slaby', fixed tabs in function documentation
throughout the file '/drivers/tty/tty_io.c' in a separate commit.
- Move the variable definition to the top in function
'ledtrig_tty_work()'.
This was reported by the 'kernel test robot' after my change in v1.
- Also set the 'max_brightness' to 'blink_brightness' if no
'blink_brightness' was set. This fixes a problem at startup when the
brightness is still set to 0 and only 'line_*' is evaluated. I looked
in the netdev trigger and that's exactly how it's done there.

Changes in v1:
==============
v1: https://lore.kernel.org/linux-leds/[email protected]/
This is a follow-up patchset, based on the mailing list discussion from
March 2023 based on the old patchset v8 [1]. I have changed, the LED
trigger handling via the sysfs interfaces as suggested by Uwe
Kleine-König.
Links:
[1] https://lore.kernel.org/linux-leds/[email protected]/


Florian Eckert (6):
tty: add new helper function tty_get_tiocm
leds: ledtrig-tty: free allocated ttyname buffer on deactivate
leds: ledtrig-tty: change logging if get icount failed
leds: ledtrig-tty: replace mutex with completion
leds: ledtrig-tty: make rx tx activitate configurable
leds: ledtrig-tty: add additional line state evaluation

.../ABI/testing/sysfs-class-led-trigger-tty | 56 ++++
drivers/leds/trigger/ledtrig-tty.c | 263 +++++++++++++++---
drivers/tty/tty_io.c | 28 +-
include/linux/tty.h | 1 +
4 files changed, 306 insertions(+), 42 deletions(-)


base-commit: d2f51b3516dade79269ff45eae2a7668ae711b25
--
2.30.2


2023-11-06 12:36:11

by Florian Eckert

[permalink] [raw]
Subject: [Patch v7 3/6] leds: ledtrig-tty: change logging if get icount failed

Change the log level from info to warn, because there is something
wrong. That is more a warn message than an info message.

While we are at it, the device prefix is also changed, as this is the
led device and not the tty device that generates this message.

Signed-off-by: Florian Eckert <[email protected]>
---
drivers/leds/trigger/ledtrig-tty.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c
index 3e69a7bde928..86595add72cd 100644
--- a/drivers/leds/trigger/ledtrig-tty.c
+++ b/drivers/leds/trigger/ledtrig-tty.c
@@ -83,6 +83,7 @@ static void ledtrig_tty_work(struct work_struct *work)
struct ledtrig_tty_data *trigger_data =
container_of(work, struct ledtrig_tty_data, dwork.work);
struct serial_icounter_struct icount;
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
int ret;

mutex_lock(&trigger_data->mutex);
@@ -117,7 +118,7 @@ static void ledtrig_tty_work(struct work_struct *work)

ret = tty_get_icount(trigger_data->tty, &icount);
if (ret) {
- dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n");
+ dev_warn(led_cdev->dev, "Failed to get icount, stop polling\n");
mutex_unlock(&trigger_data->mutex);
return;
}
@@ -126,8 +127,7 @@ static void ledtrig_tty_work(struct work_struct *work)
icount.tx != trigger_data->tx) {
unsigned long interval = LEDTRIG_TTY_INTERVAL;

- led_blink_set_oneshot(trigger_data->led_cdev, &interval,
- &interval, 0);
+ led_blink_set_oneshot(led_cdev, &interval, &interval, 0);

trigger_data->rx = icount.rx;
trigger_data->tx = icount.tx;
--
2.30.2

2023-11-06 12:36:11

by Florian Eckert

[permalink] [raw]
Subject: [Patch v7 5/6] leds: ledtrig-tty: make rx tx activitate configurable

Until now, the LED blinks when data is sent via the tty (rx/tx).
This is not configurable.

This change adds the possibility to make the indication for the direction
of the transmitted data independently controllable via the new rx and
tx sysfs entries.

- rx:
Signal reception (rx) of data on the named tty device.
If set to 0, the LED will not blink on reception.
If set to 1 (default), the LED will blink on reception.

- tx:
Signal transmission (tx) of data on the named tty device.
If set to 0, the LED will not blink on transmission.
If set to 1 (default), the LED will blink on transmission.

This new sysfs entry are on by default. Thus the trigger behaves as
before this change.

Signed-off-by: Florian Eckert <[email protected]>
---
.../ABI/testing/sysfs-class-led-trigger-tty | 16 +++
drivers/leds/trigger/ledtrig-tty.c | 124 ++++++++++++++++--
2 files changed, 130 insertions(+), 10 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-tty b/Documentation/ABI/testing/sysfs-class-led-trigger-tty
index 2bf6b24e781b..504dece151b8 100644
--- a/Documentation/ABI/testing/sysfs-class-led-trigger-tty
+++ b/Documentation/ABI/testing/sysfs-class-led-trigger-tty
@@ -4,3 +4,19 @@ KernelVersion: 5.10
Contact: [email protected]
Description:
Specifies the tty device name of the triggering tty
+
+What: /sys/class/leds/<led>/rx
+Date: February 2024
+KernelVersion: 6.8
+Description:
+ Signal reception (rx) of data on the named tty device.
+ If set to 0, the LED will not blink on reception.
+ If set to 1 (default), the LED will blink on reception.
+
+What: /sys/class/leds/<led>/tx
+Date: February 2024
+KernelVersion: 6.8
+Description:
+ Signal transmission (tx) of data on the named tty device.
+ If set to 0, the LED will not blink on transmission.
+ If set to 1 (default), the LED will blink on transmission.
diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c
index 3badf74fa420..1a40a78bf1ee 100644
--- a/drivers/leds/trigger/ledtrig-tty.c
+++ b/drivers/leds/trigger/ledtrig-tty.c
@@ -17,6 +17,19 @@ struct ledtrig_tty_data {
const char *ttyname;
struct tty_struct *tty;
int rx, tx;
+ bool mode_rx;
+ bool mode_tx;
+};
+
+/* Indicates which state the LED should now display */
+enum led_trigger_tty_state {
+ TTY_LED_BLINK,
+ TTY_LED_DISABLE,
+};
+
+enum led_trigger_tty_modes {
+ TRIGGER_TTY_RX = 0,
+ TRIGGER_TTY_TX,
};

static int ledtrig_tty_waitforcompletion(struct device *dev)
@@ -86,12 +99,82 @@ static ssize_t ttyname_store(struct device *dev,
}
static DEVICE_ATTR_RW(ttyname);

+static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf,
+ enum led_trigger_tty_modes attr)
+{
+ struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
+ int completion;
+ bool state;
+
+ reinit_completion(&trigger_data->sysfs);
+ completion = ledtrig_tty_waitforcompletion(dev);
+ if (completion < 0)
+ return completion;
+
+ switch (attr) {
+ case TRIGGER_TTY_RX:
+ state = trigger_data->mode_rx;
+ break;
+ case TRIGGER_TTY_TX:
+ state = trigger_data->mode_tx;
+ break;
+ }
+
+ return sysfs_emit(buf, "%u\n", state);
+}
+
+static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf,
+ size_t size, enum led_trigger_tty_modes attr)
+{
+ struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
+ int completion;
+ bool state;
+ int ret;
+
+ ret = kstrtobool(buf, &state);
+ if (ret)
+ return ret;
+
+ reinit_completion(&trigger_data->sysfs);
+ completion = ledtrig_tty_waitforcompletion(dev);
+ if (completion < 0)
+ return completion;
+
+ switch (attr) {
+ case TRIGGER_TTY_RX:
+ trigger_data->mode_rx = state;
+ break;
+ case TRIGGER_TTY_TX:
+ trigger_data->mode_tx = state;
+ break;
+ }
+
+ return size;
+}
+
+#define DEFINE_TTY_TRIGGER(trigger_name, trigger) \
+ static ssize_t trigger_name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ return ledtrig_tty_attr_show(dev, buf, trigger); \
+ } \
+ static ssize_t trigger_name##_store(struct device *dev, \
+ struct device_attribute *attr, const char *buf, size_t size) \
+ { \
+ return ledtrig_tty_attr_store(dev, buf, size, trigger); \
+ } \
+ static DEVICE_ATTR_RW(trigger_name)
+
+DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX);
+DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX);
+
static void ledtrig_tty_work(struct work_struct *work)
{
struct ledtrig_tty_data *trigger_data =
container_of(work, struct ledtrig_tty_data, dwork.work);
- struct serial_icounter_struct icount;
struct led_classdev *led_cdev = trigger_data->led_cdev;
+ enum led_trigger_tty_state state = TTY_LED_DISABLE;
+ unsigned long interval = LEDTRIG_TTY_INTERVAL;
int ret;

if (!trigger_data->ttyname)
@@ -119,21 +202,36 @@ static void ledtrig_tty_work(struct work_struct *work)
trigger_data->tty = tty;
}

- ret = tty_get_icount(trigger_data->tty, &icount);
- if (ret)
- goto out;
+ if (trigger_data->mode_rx || trigger_data->mode_tx) {
+ struct serial_icounter_struct icount;

- if (icount.rx != trigger_data->rx ||
- icount.tx != trigger_data->tx) {
- unsigned long interval = LEDTRIG_TTY_INTERVAL;
+ ret = tty_get_icount(trigger_data->tty, &icount);
+ if (ret)
+ goto out;

- led_blink_set_oneshot(led_cdev, &interval, &interval, 0);
+ if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) {
+ trigger_data->tx = icount.tx;
+ state = TTY_LED_BLINK;
+ }

- trigger_data->rx = icount.rx;
- trigger_data->tx = icount.tx;
+ if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) {
+ trigger_data->rx = icount.rx;
+ state = TTY_LED_BLINK;
+ }
}

out:
+ switch (state) {
+ case TTY_LED_BLINK:
+ led_blink_set_oneshot(led_cdev, &interval, &interval, 0);
+ break;
+ case TTY_LED_DISABLE:
+ fallthrough;
+ default:
+ led_set_brightness(led_cdev, LED_OFF);
+ break;
+ }
+
complete_all(&trigger_data->sysfs);
schedule_delayed_work(&trigger_data->dwork,
msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2));
@@ -141,6 +239,8 @@ static void ledtrig_tty_work(struct work_struct *work)

static struct attribute *ledtrig_tty_attrs[] = {
&dev_attr_ttyname.attr,
+ &dev_attr_rx.attr,
+ &dev_attr_tx.attr,
NULL
};
ATTRIBUTE_GROUPS(ledtrig_tty);
@@ -153,6 +253,10 @@ static int ledtrig_tty_activate(struct led_classdev *led_cdev)
if (!trigger_data)
return -ENOMEM;

+ /* Enable default rx/tx mode */
+ trigger_data->mode_rx = true;
+ trigger_data->mode_tx = true;
+
led_set_trigger_data(led_cdev, trigger_data);

INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work);
--
2.30.2

2023-11-06 12:36:25

by Florian Eckert

[permalink] [raw]
Subject: [Patch v7 4/6] leds: ledtrig-tty: replace mutex with completion

With this commit, the mutex handling is replaced by the completion
handling. When handling mutex, it must always be ensured that the held
mutex is also released again. This is more error-prone should the number
of code paths increase.

This is a preparatory commit to make the trigger more configurable via
additional sysfs parameters. With this change, the worker always runs and
is no longer stopped if no ttyname is set.

Signed-off-by: Florian Eckert <[email protected]>
---
drivers/leds/trigger/ledtrig-tty.c | 60 +++++++++++++++---------------
1 file changed, 31 insertions(+), 29 deletions(-)

diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c
index 86595add72cd..3badf74fa420 100644
--- a/drivers/leds/trigger/ledtrig-tty.c
+++ b/drivers/leds/trigger/ledtrig-tty.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0

+#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/leds.h>
#include <linux/module.h>
@@ -12,15 +13,24 @@
struct ledtrig_tty_data {
struct led_classdev *led_cdev;
struct delayed_work dwork;
- struct mutex mutex;
+ struct completion sysfs;
const char *ttyname;
struct tty_struct *tty;
int rx, tx;
};

-static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data)
+static int ledtrig_tty_waitforcompletion(struct device *dev)
{
- schedule_delayed_work(&trigger_data->dwork, 0);
+ struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
+ int ret;
+
+ ret = wait_for_completion_timeout(&trigger_data->sysfs,
+ msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 20));
+
+ if (ret == 0)
+ return -ETIMEDOUT;
+
+ return ret;
}

static ssize_t ttyname_show(struct device *dev,
@@ -28,14 +38,16 @@ static ssize_t ttyname_show(struct device *dev,
{
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
ssize_t len = 0;
+ int completion;

- mutex_lock(&trigger_data->mutex);
+ reinit_completion(&trigger_data->sysfs);
+ completion = ledtrig_tty_waitforcompletion(dev);
+ if (completion < 0)
+ return completion;

if (trigger_data->ttyname)
len = sprintf(buf, "%s\n", trigger_data->ttyname);

- mutex_unlock(&trigger_data->mutex);
-
return len;
}

@@ -46,7 +58,7 @@ static ssize_t ttyname_store(struct device *dev,
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
char *ttyname;
ssize_t ret = size;
- bool running;
+ int completion;

if (size > 0 && buf[size - 1] == '\n')
size -= 1;
@@ -59,9 +71,10 @@ static ssize_t ttyname_store(struct device *dev,
ttyname = NULL;
}

- mutex_lock(&trigger_data->mutex);
-
- running = trigger_data->ttyname != NULL;
+ reinit_completion(&trigger_data->sysfs);
+ completion = ledtrig_tty_waitforcompletion(dev);
+ if (completion < 0)
+ return completion;

kfree(trigger_data->ttyname);
tty_kref_put(trigger_data->tty);
@@ -69,11 +82,6 @@ static ssize_t ttyname_store(struct device *dev,

trigger_data->ttyname = ttyname;

- mutex_unlock(&trigger_data->mutex);
-
- if (ttyname && !running)
- ledtrig_tty_restart(trigger_data);
-
return ret;
}
static DEVICE_ATTR_RW(ttyname);
@@ -86,13 +94,8 @@ static void ledtrig_tty_work(struct work_struct *work)
struct led_classdev *led_cdev = trigger_data->led_cdev;
int ret;

- mutex_lock(&trigger_data->mutex);
-
- if (!trigger_data->ttyname) {
- /* exit without rescheduling */
- mutex_unlock(&trigger_data->mutex);
- return;
- }
+ if (!trigger_data->ttyname)
+ goto out;

/* try to get the tty corresponding to $ttyname */
if (!trigger_data->tty) {
@@ -117,11 +120,8 @@ static void ledtrig_tty_work(struct work_struct *work)
}

ret = tty_get_icount(trigger_data->tty, &icount);
- if (ret) {
- dev_warn(led_cdev->dev, "Failed to get icount, stop polling\n");
- mutex_unlock(&trigger_data->mutex);
- return;
- }
+ if (ret)
+ goto out;

if (icount.rx != trigger_data->rx ||
icount.tx != trigger_data->tx) {
@@ -134,7 +134,7 @@ static void ledtrig_tty_work(struct work_struct *work)
}

out:
- mutex_unlock(&trigger_data->mutex);
+ complete_all(&trigger_data->sysfs);
schedule_delayed_work(&trigger_data->dwork,
msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2));
}
@@ -157,7 +157,9 @@ static int ledtrig_tty_activate(struct led_classdev *led_cdev)

INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work);
trigger_data->led_cdev = led_cdev;
- mutex_init(&trigger_data->mutex);
+ init_completion(&trigger_data->sysfs);
+
+ schedule_delayed_work(&trigger_data->dwork, 0);

return 0;
}
--
2.30.2

2023-11-06 12:36:25

by Florian Eckert

[permalink] [raw]
Subject: [Patch v7 2/6] leds: ledtrig-tty: free allocated ttyname buffer on deactivate

The ttyname buffer for the ledtrig_tty_data struct is allocated in the
sysfs ttyname_store() function. This buffer must be released on trigger
deactivation. This was missing and is thus a memory leak.

While we are at it, the tty handler in the ledtrig_tty_data struct should
also be returned in case of the trigger deactivation call.

Fixes: fd4a641ac88f ("leds: trigger: implement a tty trigger")
Signed-off-by: Florian Eckert <[email protected]>
---
drivers/leds/trigger/ledtrig-tty.c | 4 ++++
1 file changed, 4 insertions(+)

diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c
index 8ae0d2d284af..3e69a7bde928 100644
--- a/drivers/leds/trigger/ledtrig-tty.c
+++ b/drivers/leds/trigger/ledtrig-tty.c
@@ -168,6 +168,10 @@ static void ledtrig_tty_deactivate(struct led_classdev *led_cdev)

cancel_delayed_work_sync(&trigger_data->dwork);

+ kfree(trigger_data->ttyname);
+ tty_kref_put(trigger_data->tty);
+ trigger_data->tty = NULL;
+
kfree(trigger_data);
}

--
2.30.2

2023-11-06 12:36:26

by Florian Eckert

[permalink] [raw]
Subject: [Patch v7 6/6] leds: ledtrig-tty: add additional line state evaluation

The serial tty interface also supports additional input signals,that
can also be evaluated within this trigger. This change is adding the
following additional input sources, which could be controlled
via the '/sys/class/<leds>/' sysfs interface.

Explanation:
DCE = Data Communication Equipment (Modem)
DTE = Data Terminal Equipment (Computer)

- cts:
DCE is ready to accept data from the DTE (CTS = Clear To Send). If
the line state is detected, the LED is switched on.
If set to 0 (default), the LED will not evaluate CTS.
If set to 1, the LED will evaluate CTS.

- dsr:
DCE is ready to receive and send data (DSR = Data Set Ready). If the
line state is detected, the LED is switched on.
If set to 0 (default), the LED will not evaluate DSR.
If set to 1, the LED will evaluate DSR.

- dcd:
DTE is receiving a carrier from the DCE (DCD = Data Carrier Detect).
If the line state is detected, the LED is switched on.
If set to 0 (default), the LED will not evaluate DCD.
If set to 1, the LED will evaluate DCD.

- rng:
DCE has detected an incoming ring signal on the telephone line
(RNG = Ring Indicator). If the line state is detected, the LED is
switched on.
If set to 0 (default), the LED will not evaluate RNG.
If set to 1, the LED will evaluate RNG.

Add an invert flag on LED blink, so that the LED blinks in the correct order.
* LED was 'on' in the previous round, then it should first go 'off' and
then 'on' again when it should blink (data has been transferred).
* LED was 'off' in the previous round, then it should first go 'on' and
then 'off' again when it should blink (data has been transferred).

In order to also evaluate the LED 'state' form the previous round, so we
could blink in the correct order, the 'state' must be saved in the trigger
data struct.

Signed-off-by: Florian Eckert <[email protected]>
---
.../ABI/testing/sysfs-class-led-trigger-tty | 40 ++++++++
drivers/leds/trigger/ledtrig-tty.c | 91 ++++++++++++++++++-
2 files changed, 126 insertions(+), 5 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-tty b/Documentation/ABI/testing/sysfs-class-led-trigger-tty
index 504dece151b8..30cef9ac0f49 100644
--- a/Documentation/ABI/testing/sysfs-class-led-trigger-tty
+++ b/Documentation/ABI/testing/sysfs-class-led-trigger-tty
@@ -20,3 +20,43 @@ Description:
Signal transmission (tx) of data on the named tty device.
If set to 0, the LED will not blink on transmission.
If set to 1 (default), the LED will blink on transmission.
+
+What: /sys/class/leds/<led>/cts
+Date: February 2024
+KernelVersion: 6.8
+Description:
+ CTS = Clear To Send
+ DCE is ready to accept data from the DTE.
+ If the line state is detected, the LED is switched on.
+ If set to 0 (default), the LED will not evaluate CTS.
+ If set to 1, the LED will evaluate CTS.
+
+What: /sys/class/leds/<led>/dsr
+Date: February 2024
+KernelVersion: 6.8
+Description:
+ DSR = Data Set Ready
+ DCE is ready to receive and send data.
+ If the line state is detected, the LED is switched on.
+ If set to 0 (default), the LED will not evaluate DSR.
+ If set to 1, the LED will evaluate DSR.
+
+What: /sys/class/leds/<led>/dcd
+Date: February 2024
+KernelVersion: 6.8
+Description:
+ DCD = Data Carrier Detect
+ DTE is receiving a carrier from the DCE.
+ If the line state is detected, the LED is switched on.
+ If set to 0 (default), the LED will not evaluate CAR (DCD).
+ If set to 1, the LED will evaluate CAR (DCD).
+
+What: /sys/class/leds/<led>/rng
+Date: February 2024
+KernelVersion: 6.8
+Description:
+ RNG = Ring Indicator
+ DCE has detected an incoming ring signal on the telephone
+ line. If the line state is detected, the LED is switched on.
+ If set to 0 (default), the LED will not evaluate RNG.
+ If set to 1, the LED will evaluate RNG.
diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c
index 1a40a78bf1ee..107fbbca96de 100644
--- a/drivers/leds/trigger/ledtrig-tty.c
+++ b/drivers/leds/trigger/ledtrig-tty.c
@@ -17,19 +17,29 @@ struct ledtrig_tty_data {
const char *ttyname;
struct tty_struct *tty;
int rx, tx;
+ int state;
bool mode_rx;
bool mode_tx;
+ bool mode_cts;
+ bool mode_dsr;
+ bool mode_dcd;
+ bool mode_rng;
};

/* Indicates which state the LED should now display */
enum led_trigger_tty_state {
TTY_LED_BLINK,
+ TTY_LED_ENABLE,
TTY_LED_DISABLE,
};

enum led_trigger_tty_modes {
TRIGGER_TTY_RX = 0,
TRIGGER_TTY_TX,
+ TRIGGER_TTY_CTS,
+ TRIGGER_TTY_DSR,
+ TRIGGER_TTY_DCD,
+ TRIGGER_TTY_RNG,
};

static int ledtrig_tty_waitforcompletion(struct device *dev)
@@ -118,6 +128,18 @@ static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf,
case TRIGGER_TTY_TX:
state = trigger_data->mode_tx;
break;
+ case TRIGGER_TTY_CTS:
+ state = trigger_data->mode_cts;
+ break;
+ case TRIGGER_TTY_DSR:
+ state = trigger_data->mode_dsr;
+ break;
+ case TRIGGER_TTY_DCD:
+ state = trigger_data->mode_dcd;
+ break;
+ case TRIGGER_TTY_RNG:
+ state = trigger_data->mode_rng;
+ break;
}

return sysfs_emit(buf, "%u\n", state);
@@ -147,6 +169,18 @@ static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf,
case TRIGGER_TTY_TX:
trigger_data->mode_tx = state;
break;
+ case TRIGGER_TTY_CTS:
+ trigger_data->mode_cts = state;
+ break;
+ case TRIGGER_TTY_DSR:
+ trigger_data->mode_dsr = state;
+ break;
+ case TRIGGER_TTY_DCD:
+ trigger_data->mode_dcd = state;
+ break;
+ case TRIGGER_TTY_RNG:
+ trigger_data->mode_rng = state;
+ break;
}

return size;
@@ -167,16 +201,27 @@ static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf,

DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX);
DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX);
+DEFINE_TTY_TRIGGER(cts, TRIGGER_TTY_CTS);
+DEFINE_TTY_TRIGGER(dsr, TRIGGER_TTY_DSR);
+DEFINE_TTY_TRIGGER(dcd, TRIGGER_TTY_DCD);
+DEFINE_TTY_TRIGGER(rng, TRIGGER_TTY_RNG);

static void ledtrig_tty_work(struct work_struct *work)
{
struct ledtrig_tty_data *trigger_data =
container_of(work, struct ledtrig_tty_data, dwork.work);
struct led_classdev *led_cdev = trigger_data->led_cdev;
- enum led_trigger_tty_state state = TTY_LED_DISABLE;
unsigned long interval = LEDTRIG_TTY_INTERVAL;
+ int invert = 0;
+ int status;
int ret;

+ if (trigger_data->state == TTY_LED_ENABLE)
+ invert = 1;
+
+ /* Always disable the LED if no evaluation could be done */
+ trigger_data->state = TTY_LED_DISABLE;
+
if (!trigger_data->ttyname)
goto out;

@@ -202,6 +247,33 @@ static void ledtrig_tty_work(struct work_struct *work)
trigger_data->tty = tty;
}

+ status = tty_get_tiocm(trigger_data->tty);
+ if (status > 0) {
+ if (trigger_data->mode_cts) {
+ if (status & TIOCM_CTS)
+ trigger_data->state = TTY_LED_ENABLE;
+ }
+
+ if (trigger_data->mode_dsr) {
+ if (status & TIOCM_DSR)
+ trigger_data->state = TTY_LED_ENABLE;
+ }
+
+ if (trigger_data->mode_dcd) {
+ if (status & TIOCM_CAR)
+ trigger_data->state = TTY_LED_ENABLE;
+ }
+
+ if (trigger_data->mode_rng) {
+ if (status & TIOCM_RNG)
+ trigger_data->state = TTY_LED_ENABLE;
+ }
+ }
+
+ /*
+ * The evaluation of rx/tx must be done after the evaluation
+ * of TIOCM_*, because rx/tx has priority.
+ */
if (trigger_data->mode_rx || trigger_data->mode_tx) {
struct serial_icounter_struct icount;

@@ -211,19 +283,22 @@ static void ledtrig_tty_work(struct work_struct *work)

if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) {
trigger_data->tx = icount.tx;
- state = TTY_LED_BLINK;
+ trigger_data->state = TTY_LED_BLINK;
}

if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) {
trigger_data->rx = icount.rx;
- state = TTY_LED_BLINK;
+ trigger_data->state = TTY_LED_BLINK;
}
}

out:
- switch (state) {
+ switch (trigger_data->state) {
case TTY_LED_BLINK:
- led_blink_set_oneshot(led_cdev, &interval, &interval, 0);
+ led_blink_set_oneshot(led_cdev, &interval, &interval, invert);
+ break;
+ case TTY_LED_ENABLE:
+ led_set_brightness(led_cdev, led_cdev->blink_brightness);
break;
case TTY_LED_DISABLE:
fallthrough;
@@ -241,6 +316,10 @@ static struct attribute *ledtrig_tty_attrs[] = {
&dev_attr_ttyname.attr,
&dev_attr_rx.attr,
&dev_attr_tx.attr,
+ &dev_attr_cts.attr,
+ &dev_attr_dsr.attr,
+ &dev_attr_dcd.attr,
+ &dev_attr_rng.attr,
NULL
};
ATTRIBUTE_GROUPS(ledtrig_tty);
@@ -257,6 +336,8 @@ static int ledtrig_tty_activate(struct led_classdev *led_cdev)
trigger_data->mode_rx = true;
trigger_data->mode_tx = true;

+ trigger_data->state = TTY_LED_DISABLE;
+
led_set_trigger_data(led_cdev, trigger_data);

INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work);
--
2.30.2

2023-11-06 12:57:10

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [Patch v7 2/6] leds: ledtrig-tty: free allocated ttyname buffer on deactivate

On Mon, Nov 06, 2023 at 01:34:11PM +0100, Florian Eckert wrote:
> The ttyname buffer for the ledtrig_tty_data struct is allocated in the
> sysfs ttyname_store() function. This buffer must be released on trigger
> deactivation. This was missing and is thus a memory leak.
>
> While we are at it, the tty handler in the ledtrig_tty_data struct should
> also be returned in case of the trigger deactivation call.
>
> Fixes: fd4a641ac88f ("leds: trigger: implement a tty trigger")
> Signed-off-by: Florian Eckert <[email protected]>
> ---
> drivers/leds/trigger/ledtrig-tty.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c
> index 8ae0d2d284af..3e69a7bde928 100644
> --- a/drivers/leds/trigger/ledtrig-tty.c
> +++ b/drivers/leds/trigger/ledtrig-tty.c
> @@ -168,6 +168,10 @@ static void ledtrig_tty_deactivate(struct led_classdev *led_cdev)
>
> cancel_delayed_work_sync(&trigger_data->dwork);
>
> + kfree(trigger_data->ttyname);
> + tty_kref_put(trigger_data->tty);
> + trigger_data->tty = NULL;
> +
> kfree(trigger_data);
> }
>
> --
> 2.30.2
>

This should be sent independent of your new changes please, as it is a
bugfix for all kernels.

thanks,

greg k-h

2023-11-06 12:57:14

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [Patch v7 2/6] leds: ledtrig-tty: free allocated ttyname buffer on deactivate

On Mon, Nov 06, 2023 at 01:34:11PM +0100, Florian Eckert wrote:
> The ttyname buffer for the ledtrig_tty_data struct is allocated in the
> sysfs ttyname_store() function. This buffer must be released on trigger
> deactivation. This was missing and is thus a memory leak.
>
> While we are at it, the tty handler in the ledtrig_tty_data struct should
> also be returned in case of the trigger deactivation call.
>
> Fixes: fd4a641ac88f ("leds: trigger: implement a tty trigger")
> Signed-off-by: Florian Eckert <[email protected]>
> ---
> drivers/leds/trigger/ledtrig-tty.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c
> index 8ae0d2d284af..3e69a7bde928 100644
> --- a/drivers/leds/trigger/ledtrig-tty.c
> +++ b/drivers/leds/trigger/ledtrig-tty.c
> @@ -168,6 +168,10 @@ static void ledtrig_tty_deactivate(struct led_classdev *led_cdev)
>
> cancel_delayed_work_sync(&trigger_data->dwork);
>
> + kfree(trigger_data->ttyname);
> + tty_kref_put(trigger_data->tty);
> + trigger_data->tty = NULL;
> +
> kfree(trigger_data);
> }
>
> --
> 2.30.2
>

Hi,

This is the friendly patch-bot of Greg Kroah-Hartman. You have sent him
a patch that has triggered this response. He used to manually respond
to these common problems, but in order to save his sanity (he kept
writing the same thing over and over, yet to different people), I was
created. Hopefully you will not take offence and will fix the problem
in your patch and resubmit it so that it can be accepted into the Linux
kernel tree.

You are receiving this message because of the following common error(s)
as indicated below:

- You have marked a patch with a "Fixes:" tag for a commit that is in an
older released kernel, yet you do not have a cc: stable line in the
signed-off-by area at all, which means that the patch will not be
applied to any older kernel releases. To properly fix this, please
follow the documented rules in the
Documentation/process/stable-kernel-rules.rst file for how to resolve
this.

If you wish to discuss this problem further, or you have questions about
how to resolve this issue, please feel free to respond to this email and
Greg will reply once he has dug out from the pending patches received
from other developers.

thanks,

greg k-h's patch email bot

2023-11-06 13:27:52

by Maarten Brock

[permalink] [raw]
Subject: Re: [Patch v7 6/6] leds: ledtrig-tty: add additional line state evaluation

Florian Eckert wrote on 2023-11-06 13:34:
> Add an invert flag on LED blink, so that the LED blinks in the correct
> order.
> * LED was 'on' in the previous round, then it should first go 'off' and
> then 'on' again when it should blink (data has been transferred).
> * LED was 'off' in the previous round, then it should first go 'on' and
> then 'off' again when it should blink (data has been transferred).
>
> In order to also evaluate the LED 'state' form the previous round, so
> we
> could blink in the correct order, the 'state' must be saved in the
> trigger
> data struct.
>
> Signed-off-by: Florian Eckert <[email protected]>
> ---
> diff --git a/drivers/leds/trigger/ledtrig-tty.c
> b/drivers/leds/trigger/ledtrig-tty.c
> index 1a40a78bf1ee..107fbbca96de 100644
> --- a/drivers/leds/trigger/ledtrig-tty.c
> +++ b/drivers/leds/trigger/ledtrig-tty.c
> @@ -17,19 +17,29 @@ struct ledtrig_tty_data {
> const char *ttyname;
> struct tty_struct *tty;
> int rx, tx;
> + int state;

I don't think you need to remember the state here.

> bool mode_rx;
> bool mode_tx;
> + bool mode_cts;
> + bool mode_dsr;
> + bool mode_dcd;
> + bool mode_rng;
> };
> @@ -167,16 +201,27 @@ static ssize_t ledtrig_tty_attr_store(struct
> device *dev, const char *buf,
>
> DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX);
> DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX);
> +DEFINE_TTY_TRIGGER(cts, TRIGGER_TTY_CTS);
> +DEFINE_TTY_TRIGGER(dsr, TRIGGER_TTY_DSR);
> +DEFINE_TTY_TRIGGER(dcd, TRIGGER_TTY_DCD);
> +DEFINE_TTY_TRIGGER(rng, TRIGGER_TTY_RNG);
>
> static void ledtrig_tty_work(struct work_struct *work)
> {
> struct ledtrig_tty_data *trigger_data =
> container_of(work, struct ledtrig_tty_data, dwork.work);
> struct led_classdev *led_cdev = trigger_data->led_cdev;
> - enum led_trigger_tty_state state = TTY_LED_DISABLE;

Keep this one.

> unsigned long interval = LEDTRIG_TTY_INTERVAL;
> + int invert = 0;

bool invert = false;

> + int status;
> int ret;
>
> + if (trigger_data->state == TTY_LED_ENABLE)
> + invert = 1;

Drop the above.

> +
> + /* Always disable the LED if no evaluation could be done */
> + trigger_data->state = TTY_LED_DISABLE;
> +
> if (!trigger_data->ttyname)
> goto out;
>
> @@ -202,6 +247,33 @@ static void ledtrig_tty_work(struct work_struct
> *work)
> trigger_data->tty = tty;
> }
>
> + status = tty_get_tiocm(trigger_data->tty);
> + if (status > 0) {
> + if (trigger_data->mode_cts) {
> + if (status & TIOCM_CTS)
> + trigger_data->state = TTY_LED_ENABLE;
> + }
> +
> + if (trigger_data->mode_dsr) {
> + if (status & TIOCM_DSR)
> + trigger_data->state = TTY_LED_ENABLE;
> + }
> +
> + if (trigger_data->mode_dcd) {
> + if (status & TIOCM_CAR)
> + trigger_data->state = TTY_LED_ENABLE;
> + }
> +
> + if (trigger_data->mode_rng) {
> + if (status & TIOCM_RNG)
> + trigger_data->state = TTY_LED_ENABLE;
> + }
> + }
> +
> + /*
> + * The evaluation of rx/tx must be done after the evaluation
> + * of TIOCM_*, because rx/tx has priority.
> + */
> if (trigger_data->mode_rx || trigger_data->mode_tx) {
> struct serial_icounter_struct icount;
>
> @@ -211,19 +283,22 @@ static void ledtrig_tty_work(struct work_struct
> *work)
>
> if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) {
> trigger_data->tx = icount.tx;

invert = state == TTY_LED_ENABLE;

> - state = TTY_LED_BLINK;

Keep this line.

> + trigger_data->state = TTY_LED_BLINK;

And drop this one.

> }
>
> if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) {
> trigger_data->rx = icount.rx;

invert = state == TTY_LED_ENABLE;

> - state = TTY_LED_BLINK;
> + trigger_data->state = TTY_LED_BLINK;
> }
> }
>
> out:
> - switch (state) {
> + switch (trigger_data->state) {
> case TTY_LED_BLINK:
> - led_blink_set_oneshot(led_cdev, &interval, &interval, 0);
> + led_blink_set_oneshot(led_cdev, &interval, &interval, invert);
> + break;
> + case TTY_LED_ENABLE:
> + led_set_brightness(led_cdev, led_cdev->blink_brightness);
> break;
> case TTY_LED_DISABLE:
> fallthrough;

Maarten