cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

ledtrig-pattern.c (11132B)


      1// SPDX-License-Identifier: GPL-2.0
      2
      3/*
      4 * LED pattern trigger
      5 *
      6 * Idea discussed with Pavel Machek. Raphael Teysseyre implemented
      7 * the first version, Baolin Wang simplified and improved the approach.
      8 */
      9
     10#include <linux/kernel.h>
     11#include <linux/leds.h>
     12#include <linux/module.h>
     13#include <linux/mutex.h>
     14#include <linux/slab.h>
     15#include <linux/timer.h>
     16
     17#define MAX_PATTERNS		1024
     18/*
     19 * When doing gradual dimming, the led brightness will be updated
     20 * every 50 milliseconds.
     21 */
     22#define UPDATE_INTERVAL		50
     23
     24struct pattern_trig_data {
     25	struct led_classdev *led_cdev;
     26	struct led_pattern patterns[MAX_PATTERNS];
     27	struct led_pattern *curr;
     28	struct led_pattern *next;
     29	struct mutex lock;
     30	u32 npatterns;
     31	int repeat;
     32	int last_repeat;
     33	int delta_t;
     34	bool is_indefinite;
     35	bool is_hw_pattern;
     36	struct timer_list timer;
     37};
     38
     39static void pattern_trig_update_patterns(struct pattern_trig_data *data)
     40{
     41	data->curr = data->next;
     42	if (!data->is_indefinite && data->curr == data->patterns)
     43		data->repeat--;
     44
     45	if (data->next == data->patterns + data->npatterns - 1)
     46		data->next = data->patterns;
     47	else
     48		data->next++;
     49
     50	data->delta_t = 0;
     51}
     52
     53static int pattern_trig_compute_brightness(struct pattern_trig_data *data)
     54{
     55	int step_brightness;
     56
     57	/*
     58	 * If current tuple's duration is less than the dimming interval,
     59	 * we should treat it as a step change of brightness instead of
     60	 * doing gradual dimming.
     61	 */
     62	if (data->delta_t == 0 || data->curr->delta_t < UPDATE_INTERVAL)
     63		return data->curr->brightness;
     64
     65	step_brightness = abs(data->next->brightness - data->curr->brightness);
     66	step_brightness = data->delta_t * step_brightness / data->curr->delta_t;
     67
     68	if (data->next->brightness > data->curr->brightness)
     69		return data->curr->brightness + step_brightness;
     70	else
     71		return data->curr->brightness - step_brightness;
     72}
     73
     74static void pattern_trig_timer_function(struct timer_list *t)
     75{
     76	struct pattern_trig_data *data = from_timer(data, t, timer);
     77
     78	for (;;) {
     79		if (!data->is_indefinite && !data->repeat)
     80			break;
     81
     82		if (data->curr->brightness == data->next->brightness) {
     83			/* Step change of brightness */
     84			led_set_brightness(data->led_cdev,
     85					   data->curr->brightness);
     86			mod_timer(&data->timer,
     87				  jiffies + msecs_to_jiffies(data->curr->delta_t));
     88			if (!data->next->delta_t) {
     89				/* Skip the tuple with zero duration */
     90				pattern_trig_update_patterns(data);
     91			}
     92			/* Select next tuple */
     93			pattern_trig_update_patterns(data);
     94		} else {
     95			/* Gradual dimming */
     96
     97			/*
     98			 * If the accumulation time is larger than current
     99			 * tuple's duration, we should go next one and re-check
    100			 * if we repeated done.
    101			 */
    102			if (data->delta_t > data->curr->delta_t) {
    103				pattern_trig_update_patterns(data);
    104				continue;
    105			}
    106
    107			led_set_brightness(data->led_cdev,
    108					   pattern_trig_compute_brightness(data));
    109			mod_timer(&data->timer,
    110				  jiffies + msecs_to_jiffies(UPDATE_INTERVAL));
    111
    112			/* Accumulate the gradual dimming time */
    113			data->delta_t += UPDATE_INTERVAL;
    114		}
    115
    116		break;
    117	}
    118}
    119
    120static int pattern_trig_start_pattern(struct led_classdev *led_cdev)
    121{
    122	struct pattern_trig_data *data = led_cdev->trigger_data;
    123
    124	if (!data->npatterns)
    125		return 0;
    126
    127	if (data->is_hw_pattern) {
    128		return led_cdev->pattern_set(led_cdev, data->patterns,
    129					     data->npatterns, data->repeat);
    130	}
    131
    132	/* At least 2 tuples for software pattern. */
    133	if (data->npatterns < 2)
    134		return -EINVAL;
    135
    136	data->delta_t = 0;
    137	data->curr = data->patterns;
    138	data->next = data->patterns + 1;
    139	data->timer.expires = jiffies;
    140	add_timer(&data->timer);
    141
    142	return 0;
    143}
    144
    145static ssize_t repeat_show(struct device *dev, struct device_attribute *attr,
    146			   char *buf)
    147{
    148	struct led_classdev *led_cdev = dev_get_drvdata(dev);
    149	struct pattern_trig_data *data = led_cdev->trigger_data;
    150	int repeat;
    151
    152	mutex_lock(&data->lock);
    153
    154	repeat = data->last_repeat;
    155
    156	mutex_unlock(&data->lock);
    157
    158	return scnprintf(buf, PAGE_SIZE, "%d\n", repeat);
    159}
    160
    161static ssize_t repeat_store(struct device *dev, struct device_attribute *attr,
    162			    const char *buf, size_t count)
    163{
    164	struct led_classdev *led_cdev = dev_get_drvdata(dev);
    165	struct pattern_trig_data *data = led_cdev->trigger_data;
    166	int err, res;
    167
    168	err = kstrtos32(buf, 10, &res);
    169	if (err)
    170		return err;
    171
    172	/* Number 0 and negative numbers except -1 are invalid. */
    173	if (res < -1 || res == 0)
    174		return -EINVAL;
    175
    176	mutex_lock(&data->lock);
    177
    178	del_timer_sync(&data->timer);
    179
    180	if (data->is_hw_pattern)
    181		led_cdev->pattern_clear(led_cdev);
    182
    183	data->last_repeat = data->repeat = res;
    184	/* -1 means repeat indefinitely */
    185	if (data->repeat == -1)
    186		data->is_indefinite = true;
    187	else
    188		data->is_indefinite = false;
    189
    190	err = pattern_trig_start_pattern(led_cdev);
    191
    192	mutex_unlock(&data->lock);
    193	return err < 0 ? err : count;
    194}
    195
    196static DEVICE_ATTR_RW(repeat);
    197
    198static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data,
    199					  char *buf, bool hw_pattern)
    200{
    201	ssize_t count = 0;
    202	int i;
    203
    204	mutex_lock(&data->lock);
    205
    206	if (!data->npatterns || (data->is_hw_pattern ^ hw_pattern))
    207		goto out;
    208
    209	for (i = 0; i < data->npatterns; i++) {
    210		count += scnprintf(buf + count, PAGE_SIZE - count,
    211				   "%d %u ",
    212				   data->patterns[i].brightness,
    213				   data->patterns[i].delta_t);
    214	}
    215
    216	buf[count - 1] = '\n';
    217
    218out:
    219	mutex_unlock(&data->lock);
    220	return count;
    221}
    222
    223static int pattern_trig_store_patterns_string(struct pattern_trig_data *data,
    224					      const char *buf, size_t count)
    225{
    226	int ccount, cr, offset = 0;
    227
    228	while (offset < count - 1 && data->npatterns < MAX_PATTERNS) {
    229		cr = 0;
    230		ccount = sscanf(buf + offset, "%u %u %n",
    231				&data->patterns[data->npatterns].brightness,
    232				&data->patterns[data->npatterns].delta_t, &cr);
    233
    234		if (ccount != 2 ||
    235		    data->patterns[data->npatterns].brightness > data->led_cdev->max_brightness) {
    236			data->npatterns = 0;
    237			return -EINVAL;
    238		}
    239
    240		offset += cr;
    241		data->npatterns++;
    242	}
    243
    244	return 0;
    245}
    246
    247static int pattern_trig_store_patterns_int(struct pattern_trig_data *data,
    248					   const u32 *buf, size_t count)
    249{
    250	unsigned int i;
    251
    252	for (i = 0; i < count; i += 2) {
    253		data->patterns[data->npatterns].brightness = buf[i];
    254		data->patterns[data->npatterns].delta_t = buf[i + 1];
    255		data->npatterns++;
    256	}
    257
    258	return 0;
    259}
    260
    261static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev,
    262					   const char *buf, const u32 *buf_int,
    263					   size_t count, bool hw_pattern)
    264{
    265	struct pattern_trig_data *data = led_cdev->trigger_data;
    266	int err = 0;
    267
    268	mutex_lock(&data->lock);
    269
    270	del_timer_sync(&data->timer);
    271
    272	if (data->is_hw_pattern)
    273		led_cdev->pattern_clear(led_cdev);
    274
    275	data->is_hw_pattern = hw_pattern;
    276	data->npatterns = 0;
    277
    278	if (buf)
    279		err = pattern_trig_store_patterns_string(data, buf, count);
    280	else
    281		err = pattern_trig_store_patterns_int(data, buf_int, count);
    282	if (err)
    283		goto out;
    284
    285	err = pattern_trig_start_pattern(led_cdev);
    286	if (err)
    287		data->npatterns = 0;
    288
    289out:
    290	mutex_unlock(&data->lock);
    291	return err < 0 ? err : count;
    292}
    293
    294static ssize_t pattern_show(struct device *dev, struct device_attribute *attr,
    295			    char *buf)
    296{
    297	struct led_classdev *led_cdev = dev_get_drvdata(dev);
    298	struct pattern_trig_data *data = led_cdev->trigger_data;
    299
    300	return pattern_trig_show_patterns(data, buf, false);
    301}
    302
    303static ssize_t pattern_store(struct device *dev, struct device_attribute *attr,
    304			     const char *buf, size_t count)
    305{
    306	struct led_classdev *led_cdev = dev_get_drvdata(dev);
    307
    308	return pattern_trig_store_patterns(led_cdev, buf, NULL, count, false);
    309}
    310
    311static DEVICE_ATTR_RW(pattern);
    312
    313static ssize_t hw_pattern_show(struct device *dev,
    314			       struct device_attribute *attr, char *buf)
    315{
    316	struct led_classdev *led_cdev = dev_get_drvdata(dev);
    317	struct pattern_trig_data *data = led_cdev->trigger_data;
    318
    319	return pattern_trig_show_patterns(data, buf, true);
    320}
    321
    322static ssize_t hw_pattern_store(struct device *dev,
    323				struct device_attribute *attr,
    324				const char *buf, size_t count)
    325{
    326	struct led_classdev *led_cdev = dev_get_drvdata(dev);
    327
    328	return pattern_trig_store_patterns(led_cdev, buf, NULL, count, true);
    329}
    330
    331static DEVICE_ATTR_RW(hw_pattern);
    332
    333static umode_t pattern_trig_attrs_mode(struct kobject *kobj,
    334				       struct attribute *attr, int index)
    335{
    336	struct device *dev = kobj_to_dev(kobj);
    337	struct led_classdev *led_cdev = dev_get_drvdata(dev);
    338
    339	if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr)
    340		return attr->mode;
    341	else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set)
    342		return attr->mode;
    343
    344	return 0;
    345}
    346
    347static struct attribute *pattern_trig_attrs[] = {
    348	&dev_attr_pattern.attr,
    349	&dev_attr_hw_pattern.attr,
    350	&dev_attr_repeat.attr,
    351	NULL
    352};
    353
    354static const struct attribute_group pattern_trig_group = {
    355	.attrs = pattern_trig_attrs,
    356	.is_visible = pattern_trig_attrs_mode,
    357};
    358
    359static const struct attribute_group *pattern_trig_groups[] = {
    360	&pattern_trig_group,
    361	NULL,
    362};
    363
    364static void pattern_init(struct led_classdev *led_cdev)
    365{
    366	unsigned int size = 0;
    367	u32 *pattern;
    368	int err;
    369
    370	pattern = led_get_default_pattern(led_cdev, &size);
    371	if (!pattern)
    372		return;
    373
    374	if (size % 2) {
    375		dev_warn(led_cdev->dev, "Expected pattern of tuples\n");
    376		goto out;
    377	}
    378
    379	err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, false);
    380	if (err < 0)
    381		dev_warn(led_cdev->dev,
    382			 "Pattern initialization failed with error %d\n", err);
    383
    384out:
    385	kfree(pattern);
    386}
    387
    388static int pattern_trig_activate(struct led_classdev *led_cdev)
    389{
    390	struct pattern_trig_data *data;
    391
    392	data = kzalloc(sizeof(*data), GFP_KERNEL);
    393	if (!data)
    394		return -ENOMEM;
    395
    396	if (!!led_cdev->pattern_set ^ !!led_cdev->pattern_clear) {
    397		dev_warn(led_cdev->dev,
    398			 "Hardware pattern ops validation failed\n");
    399		led_cdev->pattern_set = NULL;
    400		led_cdev->pattern_clear = NULL;
    401	}
    402
    403	data->is_indefinite = true;
    404	data->last_repeat = -1;
    405	mutex_init(&data->lock);
    406	data->led_cdev = led_cdev;
    407	led_set_trigger_data(led_cdev, data);
    408	timer_setup(&data->timer, pattern_trig_timer_function, 0);
    409	led_cdev->activated = true;
    410
    411	if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
    412		pattern_init(led_cdev);
    413		/*
    414		 * Mark as initialized even on pattern_init() error because
    415		 * any consecutive call to it would produce the same error.
    416		 */
    417		led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
    418	}
    419
    420	return 0;
    421}
    422
    423static void pattern_trig_deactivate(struct led_classdev *led_cdev)
    424{
    425	struct pattern_trig_data *data = led_cdev->trigger_data;
    426
    427	if (!led_cdev->activated)
    428		return;
    429
    430	if (led_cdev->pattern_clear)
    431		led_cdev->pattern_clear(led_cdev);
    432
    433	del_timer_sync(&data->timer);
    434
    435	led_set_brightness(led_cdev, LED_OFF);
    436	kfree(data);
    437	led_cdev->activated = false;
    438}
    439
    440static struct led_trigger pattern_led_trigger = {
    441	.name = "pattern",
    442	.activate = pattern_trig_activate,
    443	.deactivate = pattern_trig_deactivate,
    444	.groups = pattern_trig_groups,
    445};
    446
    447static int __init pattern_trig_init(void)
    448{
    449	return led_trigger_register(&pattern_led_trigger);
    450}
    451
    452static void __exit pattern_trig_exit(void)
    453{
    454	led_trigger_unregister(&pattern_led_trigger);
    455}
    456
    457module_init(pattern_trig_init);
    458module_exit(pattern_trig_exit);
    459
    460MODULE_AUTHOR("Raphael Teysseyre <rteysseyre@gmail.com>");
    461MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>");
    462MODULE_DESCRIPTION("LED Pattern trigger");
    463MODULE_LICENSE("GPL v2");