162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Generic Counter character device interface
462306a36Sopenharmony_ci * Copyright (C) 2020 William Breathitt Gray
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include <linux/cdev.h>
762306a36Sopenharmony_ci#include <linux/counter.h>
862306a36Sopenharmony_ci#include <linux/err.h>
962306a36Sopenharmony_ci#include <linux/errno.h>
1062306a36Sopenharmony_ci#include <linux/export.h>
1162306a36Sopenharmony_ci#include <linux/fs.h>
1262306a36Sopenharmony_ci#include <linux/kfifo.h>
1362306a36Sopenharmony_ci#include <linux/list.h>
1462306a36Sopenharmony_ci#include <linux/mutex.h>
1562306a36Sopenharmony_ci#include <linux/nospec.h>
1662306a36Sopenharmony_ci#include <linux/poll.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci#include <linux/spinlock.h>
1962306a36Sopenharmony_ci#include <linux/timekeeping.h>
2062306a36Sopenharmony_ci#include <linux/types.h>
2162306a36Sopenharmony_ci#include <linux/uaccess.h>
2262306a36Sopenharmony_ci#include <linux/wait.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include "counter-chrdev.h"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistruct counter_comp_node {
2762306a36Sopenharmony_ci	struct list_head l;
2862306a36Sopenharmony_ci	struct counter_component component;
2962306a36Sopenharmony_ci	struct counter_comp comp;
3062306a36Sopenharmony_ci	void *parent;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define counter_comp_read_is_equal(a, b) \
3462306a36Sopenharmony_ci	(a.action_read == b.action_read || \
3562306a36Sopenharmony_ci	a.device_u8_read == b.device_u8_read || \
3662306a36Sopenharmony_ci	a.count_u8_read == b.count_u8_read || \
3762306a36Sopenharmony_ci	a.signal_u8_read == b.signal_u8_read || \
3862306a36Sopenharmony_ci	a.device_u32_read == b.device_u32_read || \
3962306a36Sopenharmony_ci	a.count_u32_read == b.count_u32_read || \
4062306a36Sopenharmony_ci	a.signal_u32_read == b.signal_u32_read || \
4162306a36Sopenharmony_ci	a.device_u64_read == b.device_u64_read || \
4262306a36Sopenharmony_ci	a.count_u64_read == b.count_u64_read || \
4362306a36Sopenharmony_ci	a.signal_u64_read == b.signal_u64_read || \
4462306a36Sopenharmony_ci	a.signal_array_u32_read == b.signal_array_u32_read || \
4562306a36Sopenharmony_ci	a.device_array_u64_read == b.device_array_u64_read || \
4662306a36Sopenharmony_ci	a.count_array_u64_read == b.count_array_u64_read || \
4762306a36Sopenharmony_ci	a.signal_array_u64_read == b.signal_array_u64_read)
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci#define counter_comp_read_is_set(comp) \
5062306a36Sopenharmony_ci	(comp.action_read || \
5162306a36Sopenharmony_ci	comp.device_u8_read || \
5262306a36Sopenharmony_ci	comp.count_u8_read || \
5362306a36Sopenharmony_ci	comp.signal_u8_read || \
5462306a36Sopenharmony_ci	comp.device_u32_read || \
5562306a36Sopenharmony_ci	comp.count_u32_read || \
5662306a36Sopenharmony_ci	comp.signal_u32_read || \
5762306a36Sopenharmony_ci	comp.device_u64_read || \
5862306a36Sopenharmony_ci	comp.count_u64_read || \
5962306a36Sopenharmony_ci	comp.signal_u64_read || \
6062306a36Sopenharmony_ci	comp.signal_array_u32_read || \
6162306a36Sopenharmony_ci	comp.device_array_u64_read || \
6262306a36Sopenharmony_ci	comp.count_array_u64_read || \
6362306a36Sopenharmony_ci	comp.signal_array_u64_read)
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic ssize_t counter_chrdev_read(struct file *filp, char __user *buf,
6662306a36Sopenharmony_ci				   size_t len, loff_t *f_ps)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	struct counter_device *const counter = filp->private_data;
6962306a36Sopenharmony_ci	int err;
7062306a36Sopenharmony_ci	unsigned int copied;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	if (!counter->ops)
7362306a36Sopenharmony_ci		return -ENODEV;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	if (len < sizeof(struct counter_event))
7662306a36Sopenharmony_ci		return -EINVAL;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	do {
7962306a36Sopenharmony_ci		if (kfifo_is_empty(&counter->events)) {
8062306a36Sopenharmony_ci			if (filp->f_flags & O_NONBLOCK)
8162306a36Sopenharmony_ci				return -EAGAIN;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci			err = wait_event_interruptible(counter->events_wait,
8462306a36Sopenharmony_ci					!kfifo_is_empty(&counter->events) ||
8562306a36Sopenharmony_ci					!counter->ops);
8662306a36Sopenharmony_ci			if (err < 0)
8762306a36Sopenharmony_ci				return err;
8862306a36Sopenharmony_ci			if (!counter->ops)
8962306a36Sopenharmony_ci				return -ENODEV;
9062306a36Sopenharmony_ci		}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci		if (mutex_lock_interruptible(&counter->events_out_lock))
9362306a36Sopenharmony_ci			return -ERESTARTSYS;
9462306a36Sopenharmony_ci		err = kfifo_to_user(&counter->events, buf, len, &copied);
9562306a36Sopenharmony_ci		mutex_unlock(&counter->events_out_lock);
9662306a36Sopenharmony_ci		if (err < 0)
9762306a36Sopenharmony_ci			return err;
9862306a36Sopenharmony_ci	} while (!copied);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	return copied;
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic __poll_t counter_chrdev_poll(struct file *filp,
10462306a36Sopenharmony_ci				    struct poll_table_struct *pollt)
10562306a36Sopenharmony_ci{
10662306a36Sopenharmony_ci	struct counter_device *const counter = filp->private_data;
10762306a36Sopenharmony_ci	__poll_t events = 0;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (!counter->ops)
11062306a36Sopenharmony_ci		return events;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	poll_wait(filp, &counter->events_wait, pollt);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	if (!kfifo_is_empty(&counter->events))
11562306a36Sopenharmony_ci		events = EPOLLIN | EPOLLRDNORM;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	return events;
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic void counter_events_list_free(struct list_head *const events_list)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	struct counter_event_node *p, *n;
12362306a36Sopenharmony_ci	struct counter_comp_node *q, *o;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	list_for_each_entry_safe(p, n, events_list, l) {
12662306a36Sopenharmony_ci		/* Free associated component nodes */
12762306a36Sopenharmony_ci		list_for_each_entry_safe(q, o, &p->comp_list, l) {
12862306a36Sopenharmony_ci			list_del(&q->l);
12962306a36Sopenharmony_ci			kfree(q);
13062306a36Sopenharmony_ci		}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci		/* Free event node */
13362306a36Sopenharmony_ci		list_del(&p->l);
13462306a36Sopenharmony_ci		kfree(p);
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic int counter_set_event_node(struct counter_device *const counter,
13962306a36Sopenharmony_ci				  struct counter_watch *const watch,
14062306a36Sopenharmony_ci				  const struct counter_comp_node *const cfg)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	struct counter_event_node *event_node;
14362306a36Sopenharmony_ci	int err = 0;
14462306a36Sopenharmony_ci	struct counter_comp_node *comp_node;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	/* Search for event in the list */
14762306a36Sopenharmony_ci	list_for_each_entry(event_node, &counter->next_events_list, l)
14862306a36Sopenharmony_ci		if (event_node->event == watch->event &&
14962306a36Sopenharmony_ci		    event_node->channel == watch->channel)
15062306a36Sopenharmony_ci			break;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	/* If event is not already in the list */
15362306a36Sopenharmony_ci	if (&event_node->l == &counter->next_events_list) {
15462306a36Sopenharmony_ci		/* Allocate new event node */
15562306a36Sopenharmony_ci		event_node = kmalloc(sizeof(*event_node), GFP_KERNEL);
15662306a36Sopenharmony_ci		if (!event_node)
15762306a36Sopenharmony_ci			return -ENOMEM;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci		/* Configure event node and add to the list */
16062306a36Sopenharmony_ci		event_node->event = watch->event;
16162306a36Sopenharmony_ci		event_node->channel = watch->channel;
16262306a36Sopenharmony_ci		INIT_LIST_HEAD(&event_node->comp_list);
16362306a36Sopenharmony_ci		list_add(&event_node->l, &counter->next_events_list);
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	/* Check if component watch has already been set before */
16762306a36Sopenharmony_ci	list_for_each_entry(comp_node, &event_node->comp_list, l)
16862306a36Sopenharmony_ci		if (comp_node->parent == cfg->parent &&
16962306a36Sopenharmony_ci		    counter_comp_read_is_equal(comp_node->comp, cfg->comp)) {
17062306a36Sopenharmony_ci			err = -EINVAL;
17162306a36Sopenharmony_ci			goto exit_free_event_node;
17262306a36Sopenharmony_ci		}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	/* Allocate component node */
17562306a36Sopenharmony_ci	comp_node = kmalloc(sizeof(*comp_node), GFP_KERNEL);
17662306a36Sopenharmony_ci	if (!comp_node) {
17762306a36Sopenharmony_ci		err = -ENOMEM;
17862306a36Sopenharmony_ci		goto exit_free_event_node;
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci	*comp_node = *cfg;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	/* Add component node to event node */
18362306a36Sopenharmony_ci	list_add_tail(&comp_node->l, &event_node->comp_list);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ciexit_free_event_node:
18662306a36Sopenharmony_ci	/* Free event node if no one else is watching */
18762306a36Sopenharmony_ci	if (list_empty(&event_node->comp_list)) {
18862306a36Sopenharmony_ci		list_del(&event_node->l);
18962306a36Sopenharmony_ci		kfree(event_node);
19062306a36Sopenharmony_ci	}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	return err;
19362306a36Sopenharmony_ci}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic int counter_enable_events(struct counter_device *const counter)
19662306a36Sopenharmony_ci{
19762306a36Sopenharmony_ci	unsigned long flags;
19862306a36Sopenharmony_ci	int err = 0;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	mutex_lock(&counter->n_events_list_lock);
20162306a36Sopenharmony_ci	spin_lock_irqsave(&counter->events_list_lock, flags);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	counter_events_list_free(&counter->events_list);
20462306a36Sopenharmony_ci	list_replace_init(&counter->next_events_list,
20562306a36Sopenharmony_ci			  &counter->events_list);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	if (counter->ops->events_configure)
20862306a36Sopenharmony_ci		err = counter->ops->events_configure(counter);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	spin_unlock_irqrestore(&counter->events_list_lock, flags);
21162306a36Sopenharmony_ci	mutex_unlock(&counter->n_events_list_lock);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	return err;
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic int counter_disable_events(struct counter_device *const counter)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	unsigned long flags;
21962306a36Sopenharmony_ci	int err = 0;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	spin_lock_irqsave(&counter->events_list_lock, flags);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	counter_events_list_free(&counter->events_list);
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	if (counter->ops->events_configure)
22662306a36Sopenharmony_ci		err = counter->ops->events_configure(counter);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	spin_unlock_irqrestore(&counter->events_list_lock, flags);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	mutex_lock(&counter->n_events_list_lock);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	counter_events_list_free(&counter->next_events_list);
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	mutex_unlock(&counter->n_events_list_lock);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	return err;
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_cistatic int counter_get_ext(const struct counter_comp *const ext,
24062306a36Sopenharmony_ci			   const size_t num_ext, const size_t component_id,
24162306a36Sopenharmony_ci			   size_t *const ext_idx, size_t *const id)
24262306a36Sopenharmony_ci{
24362306a36Sopenharmony_ci	struct counter_array *element;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	*id = 0;
24662306a36Sopenharmony_ci	for (*ext_idx = 0; *ext_idx < num_ext; (*ext_idx)++) {
24762306a36Sopenharmony_ci		if (*id == component_id)
24862306a36Sopenharmony_ci			return 0;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci		if (ext[*ext_idx].type == COUNTER_COMP_ARRAY) {
25162306a36Sopenharmony_ci			element = ext[*ext_idx].priv;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci			if (component_id - *id < element->length)
25462306a36Sopenharmony_ci				return 0;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci			*id += element->length;
25762306a36Sopenharmony_ci		} else
25862306a36Sopenharmony_ci			(*id)++;
25962306a36Sopenharmony_ci	}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	return -EINVAL;
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cistatic int counter_add_watch(struct counter_device *const counter,
26562306a36Sopenharmony_ci			     const unsigned long arg)
26662306a36Sopenharmony_ci{
26762306a36Sopenharmony_ci	void __user *const uwatch = (void __user *)arg;
26862306a36Sopenharmony_ci	struct counter_watch watch;
26962306a36Sopenharmony_ci	struct counter_comp_node comp_node = {};
27062306a36Sopenharmony_ci	size_t parent, id;
27162306a36Sopenharmony_ci	struct counter_comp *ext;
27262306a36Sopenharmony_ci	size_t num_ext;
27362306a36Sopenharmony_ci	size_t ext_idx, ext_id;
27462306a36Sopenharmony_ci	int err = 0;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	if (copy_from_user(&watch, uwatch, sizeof(watch)))
27762306a36Sopenharmony_ci		return -EFAULT;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	if (watch.component.type == COUNTER_COMPONENT_NONE)
28062306a36Sopenharmony_ci		goto no_component;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	parent = watch.component.parent;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	/* Configure parent component info for comp node */
28562306a36Sopenharmony_ci	switch (watch.component.scope) {
28662306a36Sopenharmony_ci	case COUNTER_SCOPE_DEVICE:
28762306a36Sopenharmony_ci		ext = counter->ext;
28862306a36Sopenharmony_ci		num_ext = counter->num_ext;
28962306a36Sopenharmony_ci		break;
29062306a36Sopenharmony_ci	case COUNTER_SCOPE_SIGNAL:
29162306a36Sopenharmony_ci		if (parent >= counter->num_signals)
29262306a36Sopenharmony_ci			return -EINVAL;
29362306a36Sopenharmony_ci		parent = array_index_nospec(parent, counter->num_signals);
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci		comp_node.parent = counter->signals + parent;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci		ext = counter->signals[parent].ext;
29862306a36Sopenharmony_ci		num_ext = counter->signals[parent].num_ext;
29962306a36Sopenharmony_ci		break;
30062306a36Sopenharmony_ci	case COUNTER_SCOPE_COUNT:
30162306a36Sopenharmony_ci		if (parent >= counter->num_counts)
30262306a36Sopenharmony_ci			return -EINVAL;
30362306a36Sopenharmony_ci		parent = array_index_nospec(parent, counter->num_counts);
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci		comp_node.parent = counter->counts + parent;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci		ext = counter->counts[parent].ext;
30862306a36Sopenharmony_ci		num_ext = counter->counts[parent].num_ext;
30962306a36Sopenharmony_ci		break;
31062306a36Sopenharmony_ci	default:
31162306a36Sopenharmony_ci		return -EINVAL;
31262306a36Sopenharmony_ci	}
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	id = watch.component.id;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	/* Configure component info for comp node */
31762306a36Sopenharmony_ci	switch (watch.component.type) {
31862306a36Sopenharmony_ci	case COUNTER_COMPONENT_SIGNAL:
31962306a36Sopenharmony_ci		if (watch.component.scope != COUNTER_SCOPE_SIGNAL)
32062306a36Sopenharmony_ci			return -EINVAL;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci		comp_node.comp.type = COUNTER_COMP_SIGNAL_LEVEL;
32362306a36Sopenharmony_ci		comp_node.comp.signal_u32_read = counter->ops->signal_read;
32462306a36Sopenharmony_ci		break;
32562306a36Sopenharmony_ci	case COUNTER_COMPONENT_COUNT:
32662306a36Sopenharmony_ci		if (watch.component.scope != COUNTER_SCOPE_COUNT)
32762306a36Sopenharmony_ci			return -EINVAL;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci		comp_node.comp.type = COUNTER_COMP_U64;
33062306a36Sopenharmony_ci		comp_node.comp.count_u64_read = counter->ops->count_read;
33162306a36Sopenharmony_ci		break;
33262306a36Sopenharmony_ci	case COUNTER_COMPONENT_FUNCTION:
33362306a36Sopenharmony_ci		if (watch.component.scope != COUNTER_SCOPE_COUNT)
33462306a36Sopenharmony_ci			return -EINVAL;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci		comp_node.comp.type = COUNTER_COMP_FUNCTION;
33762306a36Sopenharmony_ci		comp_node.comp.count_u32_read = counter->ops->function_read;
33862306a36Sopenharmony_ci		break;
33962306a36Sopenharmony_ci	case COUNTER_COMPONENT_SYNAPSE_ACTION:
34062306a36Sopenharmony_ci		if (watch.component.scope != COUNTER_SCOPE_COUNT)
34162306a36Sopenharmony_ci			return -EINVAL;
34262306a36Sopenharmony_ci		if (id >= counter->counts[parent].num_synapses)
34362306a36Sopenharmony_ci			return -EINVAL;
34462306a36Sopenharmony_ci		id = array_index_nospec(id, counter->counts[parent].num_synapses);
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci		comp_node.comp.type = COUNTER_COMP_SYNAPSE_ACTION;
34762306a36Sopenharmony_ci		comp_node.comp.action_read = counter->ops->action_read;
34862306a36Sopenharmony_ci		comp_node.comp.priv = counter->counts[parent].synapses + id;
34962306a36Sopenharmony_ci		break;
35062306a36Sopenharmony_ci	case COUNTER_COMPONENT_EXTENSION:
35162306a36Sopenharmony_ci		err = counter_get_ext(ext, num_ext, id, &ext_idx, &ext_id);
35262306a36Sopenharmony_ci		if (err < 0)
35362306a36Sopenharmony_ci			return err;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci		comp_node.comp = ext[ext_idx];
35662306a36Sopenharmony_ci		break;
35762306a36Sopenharmony_ci	default:
35862306a36Sopenharmony_ci		return -EINVAL;
35962306a36Sopenharmony_ci	}
36062306a36Sopenharmony_ci	if (!counter_comp_read_is_set(comp_node.comp))
36162306a36Sopenharmony_ci		return -EOPNOTSUPP;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_cino_component:
36462306a36Sopenharmony_ci	mutex_lock(&counter->n_events_list_lock);
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	if (counter->ops->watch_validate) {
36762306a36Sopenharmony_ci		err = counter->ops->watch_validate(counter, &watch);
36862306a36Sopenharmony_ci		if (err < 0)
36962306a36Sopenharmony_ci			goto err_exit;
37062306a36Sopenharmony_ci	}
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	comp_node.component = watch.component;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	err = counter_set_event_node(counter, &watch, &comp_node);
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_cierr_exit:
37762306a36Sopenharmony_ci	mutex_unlock(&counter->n_events_list_lock);
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	return err;
38062306a36Sopenharmony_ci}
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_cistatic long counter_chrdev_ioctl(struct file *filp, unsigned int cmd,
38362306a36Sopenharmony_ci				 unsigned long arg)
38462306a36Sopenharmony_ci{
38562306a36Sopenharmony_ci	struct counter_device *const counter = filp->private_data;
38662306a36Sopenharmony_ci	int ret = -ENODEV;
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	mutex_lock(&counter->ops_exist_lock);
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	if (!counter->ops)
39162306a36Sopenharmony_ci		goto out_unlock;
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	switch (cmd) {
39462306a36Sopenharmony_ci	case COUNTER_ADD_WATCH_IOCTL:
39562306a36Sopenharmony_ci		ret = counter_add_watch(counter, arg);
39662306a36Sopenharmony_ci		break;
39762306a36Sopenharmony_ci	case COUNTER_ENABLE_EVENTS_IOCTL:
39862306a36Sopenharmony_ci		ret = counter_enable_events(counter);
39962306a36Sopenharmony_ci		break;
40062306a36Sopenharmony_ci	case COUNTER_DISABLE_EVENTS_IOCTL:
40162306a36Sopenharmony_ci		ret = counter_disable_events(counter);
40262306a36Sopenharmony_ci		break;
40362306a36Sopenharmony_ci	default:
40462306a36Sopenharmony_ci		ret = -ENOIOCTLCMD;
40562306a36Sopenharmony_ci		break;
40662306a36Sopenharmony_ci	}
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ciout_unlock:
40962306a36Sopenharmony_ci	mutex_unlock(&counter->ops_exist_lock);
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	return ret;
41262306a36Sopenharmony_ci}
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_cistatic int counter_chrdev_open(struct inode *inode, struct file *filp)
41562306a36Sopenharmony_ci{
41662306a36Sopenharmony_ci	struct counter_device *const counter = container_of(inode->i_cdev,
41762306a36Sopenharmony_ci							    typeof(*counter),
41862306a36Sopenharmony_ci							    chrdev);
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	get_device(&counter->dev);
42162306a36Sopenharmony_ci	filp->private_data = counter;
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	return nonseekable_open(inode, filp);
42462306a36Sopenharmony_ci}
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_cistatic int counter_chrdev_release(struct inode *inode, struct file *filp)
42762306a36Sopenharmony_ci{
42862306a36Sopenharmony_ci	struct counter_device *const counter = filp->private_data;
42962306a36Sopenharmony_ci	int ret = 0;
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	mutex_lock(&counter->ops_exist_lock);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	if (!counter->ops) {
43462306a36Sopenharmony_ci		/* Free any lingering held memory */
43562306a36Sopenharmony_ci		counter_events_list_free(&counter->events_list);
43662306a36Sopenharmony_ci		counter_events_list_free(&counter->next_events_list);
43762306a36Sopenharmony_ci		ret = -ENODEV;
43862306a36Sopenharmony_ci		goto out_unlock;
43962306a36Sopenharmony_ci	}
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	ret = counter_disable_events(counter);
44262306a36Sopenharmony_ci	if (ret < 0) {
44362306a36Sopenharmony_ci		mutex_unlock(&counter->ops_exist_lock);
44462306a36Sopenharmony_ci		return ret;
44562306a36Sopenharmony_ci	}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ciout_unlock:
44862306a36Sopenharmony_ci	mutex_unlock(&counter->ops_exist_lock);
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	put_device(&counter->dev);
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	return ret;
45362306a36Sopenharmony_ci}
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_cistatic const struct file_operations counter_fops = {
45662306a36Sopenharmony_ci	.owner = THIS_MODULE,
45762306a36Sopenharmony_ci	.llseek = no_llseek,
45862306a36Sopenharmony_ci	.read = counter_chrdev_read,
45962306a36Sopenharmony_ci	.poll = counter_chrdev_poll,
46062306a36Sopenharmony_ci	.unlocked_ioctl = counter_chrdev_ioctl,
46162306a36Sopenharmony_ci	.open = counter_chrdev_open,
46262306a36Sopenharmony_ci	.release = counter_chrdev_release,
46362306a36Sopenharmony_ci};
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ciint counter_chrdev_add(struct counter_device *const counter)
46662306a36Sopenharmony_ci{
46762306a36Sopenharmony_ci	/* Initialize Counter events lists */
46862306a36Sopenharmony_ci	INIT_LIST_HEAD(&counter->events_list);
46962306a36Sopenharmony_ci	INIT_LIST_HEAD(&counter->next_events_list);
47062306a36Sopenharmony_ci	spin_lock_init(&counter->events_list_lock);
47162306a36Sopenharmony_ci	mutex_init(&counter->n_events_list_lock);
47262306a36Sopenharmony_ci	init_waitqueue_head(&counter->events_wait);
47362306a36Sopenharmony_ci	spin_lock_init(&counter->events_in_lock);
47462306a36Sopenharmony_ci	mutex_init(&counter->events_out_lock);
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	/* Initialize character device */
47762306a36Sopenharmony_ci	cdev_init(&counter->chrdev, &counter_fops);
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	/* Allocate Counter events queue */
48062306a36Sopenharmony_ci	return kfifo_alloc(&counter->events, 64, GFP_KERNEL);
48162306a36Sopenharmony_ci}
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_civoid counter_chrdev_remove(struct counter_device *const counter)
48462306a36Sopenharmony_ci{
48562306a36Sopenharmony_ci	kfifo_free(&counter->events);
48662306a36Sopenharmony_ci}
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_cistatic int counter_get_array_data(struct counter_device *const counter,
48962306a36Sopenharmony_ci				  const enum counter_scope scope,
49062306a36Sopenharmony_ci				  void *const parent,
49162306a36Sopenharmony_ci				  const struct counter_comp *const comp,
49262306a36Sopenharmony_ci				  const size_t idx, u64 *const value)
49362306a36Sopenharmony_ci{
49462306a36Sopenharmony_ci	const struct counter_array *const element = comp->priv;
49562306a36Sopenharmony_ci	u32 value_u32 = 0;
49662306a36Sopenharmony_ci	int ret;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	switch (element->type) {
49962306a36Sopenharmony_ci	case COUNTER_COMP_SIGNAL_POLARITY:
50062306a36Sopenharmony_ci		if (scope != COUNTER_SCOPE_SIGNAL)
50162306a36Sopenharmony_ci			return -EINVAL;
50262306a36Sopenharmony_ci		ret = comp->signal_array_u32_read(counter, parent, idx,
50362306a36Sopenharmony_ci						  &value_u32);
50462306a36Sopenharmony_ci		*value = value_u32;
50562306a36Sopenharmony_ci		return ret;
50662306a36Sopenharmony_ci	case COUNTER_COMP_U64:
50762306a36Sopenharmony_ci		switch (scope) {
50862306a36Sopenharmony_ci		case COUNTER_SCOPE_DEVICE:
50962306a36Sopenharmony_ci			return comp->device_array_u64_read(counter, idx, value);
51062306a36Sopenharmony_ci		case COUNTER_SCOPE_SIGNAL:
51162306a36Sopenharmony_ci			return comp->signal_array_u64_read(counter, parent, idx,
51262306a36Sopenharmony_ci							   value);
51362306a36Sopenharmony_ci		case COUNTER_SCOPE_COUNT:
51462306a36Sopenharmony_ci			return comp->count_array_u64_read(counter, parent, idx,
51562306a36Sopenharmony_ci							  value);
51662306a36Sopenharmony_ci		default:
51762306a36Sopenharmony_ci			return -EINVAL;
51862306a36Sopenharmony_ci		}
51962306a36Sopenharmony_ci	default:
52062306a36Sopenharmony_ci		return -EINVAL;
52162306a36Sopenharmony_ci	}
52262306a36Sopenharmony_ci}
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_cistatic int counter_get_data(struct counter_device *const counter,
52562306a36Sopenharmony_ci			    const struct counter_comp_node *const comp_node,
52662306a36Sopenharmony_ci			    u64 *const value)
52762306a36Sopenharmony_ci{
52862306a36Sopenharmony_ci	const struct counter_comp *const comp = &comp_node->comp;
52962306a36Sopenharmony_ci	const enum counter_scope scope = comp_node->component.scope;
53062306a36Sopenharmony_ci	const size_t id = comp_node->component.id;
53162306a36Sopenharmony_ci	struct counter_signal *const signal = comp_node->parent;
53262306a36Sopenharmony_ci	struct counter_count *const count = comp_node->parent;
53362306a36Sopenharmony_ci	u8 value_u8 = 0;
53462306a36Sopenharmony_ci	u32 value_u32 = 0;
53562306a36Sopenharmony_ci	const struct counter_comp *ext;
53662306a36Sopenharmony_ci	size_t num_ext;
53762306a36Sopenharmony_ci	size_t ext_idx, ext_id;
53862306a36Sopenharmony_ci	int ret;
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	if (comp_node->component.type == COUNTER_COMPONENT_NONE)
54162306a36Sopenharmony_ci		return 0;
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	switch (comp->type) {
54462306a36Sopenharmony_ci	case COUNTER_COMP_U8:
54562306a36Sopenharmony_ci	case COUNTER_COMP_BOOL:
54662306a36Sopenharmony_ci		switch (scope) {
54762306a36Sopenharmony_ci		case COUNTER_SCOPE_DEVICE:
54862306a36Sopenharmony_ci			ret = comp->device_u8_read(counter, &value_u8);
54962306a36Sopenharmony_ci			break;
55062306a36Sopenharmony_ci		case COUNTER_SCOPE_SIGNAL:
55162306a36Sopenharmony_ci			ret = comp->signal_u8_read(counter, signal, &value_u8);
55262306a36Sopenharmony_ci			break;
55362306a36Sopenharmony_ci		case COUNTER_SCOPE_COUNT:
55462306a36Sopenharmony_ci			ret = comp->count_u8_read(counter, count, &value_u8);
55562306a36Sopenharmony_ci			break;
55662306a36Sopenharmony_ci		default:
55762306a36Sopenharmony_ci			return -EINVAL;
55862306a36Sopenharmony_ci		}
55962306a36Sopenharmony_ci		*value = value_u8;
56062306a36Sopenharmony_ci		return ret;
56162306a36Sopenharmony_ci	case COUNTER_COMP_SIGNAL_LEVEL:
56262306a36Sopenharmony_ci	case COUNTER_COMP_FUNCTION:
56362306a36Sopenharmony_ci	case COUNTER_COMP_ENUM:
56462306a36Sopenharmony_ci	case COUNTER_COMP_COUNT_DIRECTION:
56562306a36Sopenharmony_ci	case COUNTER_COMP_COUNT_MODE:
56662306a36Sopenharmony_ci	case COUNTER_COMP_SIGNAL_POLARITY:
56762306a36Sopenharmony_ci		switch (scope) {
56862306a36Sopenharmony_ci		case COUNTER_SCOPE_DEVICE:
56962306a36Sopenharmony_ci			ret = comp->device_u32_read(counter, &value_u32);
57062306a36Sopenharmony_ci			break;
57162306a36Sopenharmony_ci		case COUNTER_SCOPE_SIGNAL:
57262306a36Sopenharmony_ci			ret = comp->signal_u32_read(counter, signal,
57362306a36Sopenharmony_ci						    &value_u32);
57462306a36Sopenharmony_ci			break;
57562306a36Sopenharmony_ci		case COUNTER_SCOPE_COUNT:
57662306a36Sopenharmony_ci			ret = comp->count_u32_read(counter, count, &value_u32);
57762306a36Sopenharmony_ci			break;
57862306a36Sopenharmony_ci		default:
57962306a36Sopenharmony_ci			return -EINVAL;
58062306a36Sopenharmony_ci		}
58162306a36Sopenharmony_ci		*value = value_u32;
58262306a36Sopenharmony_ci		return ret;
58362306a36Sopenharmony_ci	case COUNTER_COMP_U64:
58462306a36Sopenharmony_ci		switch (scope) {
58562306a36Sopenharmony_ci		case COUNTER_SCOPE_DEVICE:
58662306a36Sopenharmony_ci			return comp->device_u64_read(counter, value);
58762306a36Sopenharmony_ci		case COUNTER_SCOPE_SIGNAL:
58862306a36Sopenharmony_ci			return comp->signal_u64_read(counter, signal, value);
58962306a36Sopenharmony_ci		case COUNTER_SCOPE_COUNT:
59062306a36Sopenharmony_ci			return comp->count_u64_read(counter, count, value);
59162306a36Sopenharmony_ci		default:
59262306a36Sopenharmony_ci			return -EINVAL;
59362306a36Sopenharmony_ci		}
59462306a36Sopenharmony_ci	case COUNTER_COMP_SYNAPSE_ACTION:
59562306a36Sopenharmony_ci		ret = comp->action_read(counter, count, comp->priv, &value_u32);
59662306a36Sopenharmony_ci		*value = value_u32;
59762306a36Sopenharmony_ci		return ret;
59862306a36Sopenharmony_ci	case COUNTER_COMP_ARRAY:
59962306a36Sopenharmony_ci		switch (scope) {
60062306a36Sopenharmony_ci		case COUNTER_SCOPE_DEVICE:
60162306a36Sopenharmony_ci			ext = counter->ext;
60262306a36Sopenharmony_ci			num_ext = counter->num_ext;
60362306a36Sopenharmony_ci			break;
60462306a36Sopenharmony_ci		case COUNTER_SCOPE_SIGNAL:
60562306a36Sopenharmony_ci			ext = signal->ext;
60662306a36Sopenharmony_ci			num_ext = signal->num_ext;
60762306a36Sopenharmony_ci			break;
60862306a36Sopenharmony_ci		case COUNTER_SCOPE_COUNT:
60962306a36Sopenharmony_ci			ext = count->ext;
61062306a36Sopenharmony_ci			num_ext = count->num_ext;
61162306a36Sopenharmony_ci			break;
61262306a36Sopenharmony_ci		default:
61362306a36Sopenharmony_ci			return -EINVAL;
61462306a36Sopenharmony_ci		}
61562306a36Sopenharmony_ci		ret = counter_get_ext(ext, num_ext, id, &ext_idx, &ext_id);
61662306a36Sopenharmony_ci		if (ret < 0)
61762306a36Sopenharmony_ci			return ret;
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_ci		return counter_get_array_data(counter, scope, comp_node->parent,
62062306a36Sopenharmony_ci					      comp, id - ext_id, value);
62162306a36Sopenharmony_ci	default:
62262306a36Sopenharmony_ci		return -EINVAL;
62362306a36Sopenharmony_ci	}
62462306a36Sopenharmony_ci}
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ci/**
62762306a36Sopenharmony_ci * counter_push_event - queue event for userspace reading
62862306a36Sopenharmony_ci * @counter:	pointer to Counter structure
62962306a36Sopenharmony_ci * @event:	triggered event
63062306a36Sopenharmony_ci * @channel:	event channel
63162306a36Sopenharmony_ci *
63262306a36Sopenharmony_ci * Note: If no one is watching for the respective event, it is silently
63362306a36Sopenharmony_ci * discarded.
63462306a36Sopenharmony_ci */
63562306a36Sopenharmony_civoid counter_push_event(struct counter_device *const counter, const u8 event,
63662306a36Sopenharmony_ci			const u8 channel)
63762306a36Sopenharmony_ci{
63862306a36Sopenharmony_ci	struct counter_event ev;
63962306a36Sopenharmony_ci	unsigned int copied = 0;
64062306a36Sopenharmony_ci	unsigned long flags;
64162306a36Sopenharmony_ci	struct counter_event_node *event_node;
64262306a36Sopenharmony_ci	struct counter_comp_node *comp_node;
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci	ev.timestamp = ktime_get_ns();
64562306a36Sopenharmony_ci	ev.watch.event = event;
64662306a36Sopenharmony_ci	ev.watch.channel = channel;
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	/* Could be in an interrupt context, so use a spin lock */
64962306a36Sopenharmony_ci	spin_lock_irqsave(&counter->events_list_lock, flags);
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	/* Search for event in the list */
65262306a36Sopenharmony_ci	list_for_each_entry(event_node, &counter->events_list, l)
65362306a36Sopenharmony_ci		if (event_node->event == event &&
65462306a36Sopenharmony_ci		    event_node->channel == channel)
65562306a36Sopenharmony_ci			break;
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_ci	/* If event is not in the list */
65862306a36Sopenharmony_ci	if (&event_node->l == &counter->events_list)
65962306a36Sopenharmony_ci		goto exit_early;
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	/* Read and queue relevant comp for userspace */
66262306a36Sopenharmony_ci	list_for_each_entry(comp_node, &event_node->comp_list, l) {
66362306a36Sopenharmony_ci		ev.watch.component = comp_node->component;
66462306a36Sopenharmony_ci		ev.status = -counter_get_data(counter, comp_node, &ev.value);
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci		copied += kfifo_in_spinlocked_noirqsave(&counter->events, &ev,
66762306a36Sopenharmony_ci							1, &counter->events_in_lock);
66862306a36Sopenharmony_ci	}
66962306a36Sopenharmony_ci
67062306a36Sopenharmony_ciexit_early:
67162306a36Sopenharmony_ci	spin_unlock_irqrestore(&counter->events_list_lock, flags);
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci	if (copied)
67462306a36Sopenharmony_ci		wake_up_poll(&counter->events_wait, EPOLLIN);
67562306a36Sopenharmony_ci}
67662306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(counter_push_event, COUNTER);
677