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