counter: Add character device interface
authorWilliam Breathitt Gray <vilhelm.gray@gmail.com>
Wed, 29 Sep 2021 03:15:59 +0000 (12:15 +0900)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Sun, 17 Oct 2021 09:53:52 +0000 (10:53 +0100)
This patch introduces a character device interface for the Counter
subsystem. Device data is exposed through standard character device read
operations. Device data is gathered when a Counter event is pushed by
the respective Counter device driver. Configuration is handled via ioctl
operations on the respective Counter character device node.

Cc: David Lechner <david@lechnology.com>
Cc: Gwendal Grignou <gwendal@chromium.org>
Cc: Dan Carpenter <dan.carpenter@oracle.com>
Cc: Oleksij Rempel <o.rempel@pengutronix.de>
Signed-off-by: William Breathitt Gray <vilhelm.gray@gmail.com>
Link: https://lore.kernel.org/r/b8b8c64b4065aedff43699ad1f0e2f8d1419c15b.1632884256.git.vilhelm.gray@gmail.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/counter/Makefile
drivers/counter/counter-chrdev.c [new file with mode: 0644]
drivers/counter/counter-chrdev.h [new file with mode: 0644]
drivers/counter/counter-core.c
include/linux/counter.h
include/uapi/linux/counter.h

index 1ab7e08..8fde6c1 100644 (file)
@@ -4,7 +4,7 @@
 #
 
 obj-$(CONFIG_COUNTER) += counter.o
-counter-y := counter-core.o counter-sysfs.o
+counter-y := counter-core.o counter-sysfs.o counter-chrdev.o
 
 obj-$(CONFIG_104_QUAD_8)       += 104-quad-8.o
 obj-$(CONFIG_INTERRUPT_CNT)            += interrupt-cnt.o
diff --git a/drivers/counter/counter-chrdev.c b/drivers/counter/counter-chrdev.c
new file mode 100644 (file)
index 0000000..967c94a
--- /dev/null
@@ -0,0 +1,578 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic Counter character device interface
+ * Copyright (C) 2020 William Breathitt Gray
+ */
+#include <linux/atomic.h>
+#include <linux/cdev.h>
+#include <linux/counter.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/fs.h>
+#include <linux/kfifo.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/nospec.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/timekeeping.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+
+#include "counter-chrdev.h"
+
+struct counter_comp_node {
+       struct list_head l;
+       struct counter_component component;
+       struct counter_comp comp;
+       void *parent;
+};
+
+#define counter_comp_read_is_equal(a, b) \
+       (a.action_read == b.action_read || \
+       a.device_u8_read == b.device_u8_read || \
+       a.count_u8_read == b.count_u8_read || \
+       a.signal_u8_read == b.signal_u8_read || \
+       a.device_u32_read == b.device_u32_read || \
+       a.count_u32_read == b.count_u32_read || \
+       a.signal_u32_read == b.signal_u32_read || \
+       a.device_u64_read == b.device_u64_read || \
+       a.count_u64_read == b.count_u64_read || \
+       a.signal_u64_read == b.signal_u64_read)
+
+#define counter_comp_read_is_set(comp) \
+       (comp.action_read || \
+       comp.device_u8_read || \
+       comp.count_u8_read || \
+       comp.signal_u8_read || \
+       comp.device_u32_read || \
+       comp.count_u32_read || \
+       comp.signal_u32_read || \
+       comp.device_u64_read || \
+       comp.count_u64_read || \
+       comp.signal_u64_read)
+
+static ssize_t counter_chrdev_read(struct file *filp, char __user *buf,
+                                  size_t len, loff_t *f_ps)
+{
+       struct counter_device *const counter = filp->private_data;
+       int err;
+       unsigned int copied;
+
+       if (!counter->ops)
+               return -ENODEV;
+
+       if (len < sizeof(struct counter_event))
+               return -EINVAL;
+
+       do {
+               if (kfifo_is_empty(&counter->events)) {
+                       if (filp->f_flags & O_NONBLOCK)
+                               return -EAGAIN;
+
+                       err = wait_event_interruptible(counter->events_wait,
+                                       !kfifo_is_empty(&counter->events) ||
+                                       !counter->ops);
+                       if (err < 0)
+                               return err;
+                       if (!counter->ops)
+                               return -ENODEV;
+               }
+
+               if (mutex_lock_interruptible(&counter->events_lock))
+                       return -ERESTARTSYS;
+               err = kfifo_to_user(&counter->events, buf, len, &copied);
+               mutex_unlock(&counter->events_lock);
+               if (err < 0)
+                       return err;
+       } while (!copied);
+
+       return copied;
+}
+
+static __poll_t counter_chrdev_poll(struct file *filp,
+                                   struct poll_table_struct *pollt)
+{
+       struct counter_device *const counter = filp->private_data;
+       __poll_t events = 0;
+
+       if (!counter->ops)
+               return events;
+
+       poll_wait(filp, &counter->events_wait, pollt);
+
+       if (!kfifo_is_empty(&counter->events))
+               events = EPOLLIN | EPOLLRDNORM;
+
+       return events;
+}
+
+static void counter_events_list_free(struct list_head *const events_list)
+{
+       struct counter_event_node *p, *n;
+       struct counter_comp_node *q, *o;
+
+       list_for_each_entry_safe(p, n, events_list, l) {
+               /* Free associated component nodes */
+               list_for_each_entry_safe(q, o, &p->comp_list, l) {
+                       list_del(&q->l);
+                       kfree(q);
+               }
+
+               /* Free event node */
+               list_del(&p->l);
+               kfree(p);
+       }
+}
+
+static int counter_set_event_node(struct counter_device *const counter,
+                                 struct counter_watch *const watch,
+                                 const struct counter_comp_node *const cfg)
+{
+       struct counter_event_node *event_node;
+       int err = 0;
+       struct counter_comp_node *comp_node;
+
+       /* Search for event in the list */
+       list_for_each_entry(event_node, &counter->next_events_list, l)
+               if (event_node->event == watch->event &&
+                   event_node->channel == watch->channel)
+                       break;
+
+       /* If event is not already in the list */
+       if (&event_node->l == &counter->next_events_list) {
+               /* Allocate new event node */
+               event_node = kmalloc(sizeof(*event_node), GFP_KERNEL);
+               if (!event_node)
+                       return -ENOMEM;
+
+               /* Configure event node and add to the list */
+               event_node->event = watch->event;
+               event_node->channel = watch->channel;
+               INIT_LIST_HEAD(&event_node->comp_list);
+               list_add(&event_node->l, &counter->next_events_list);
+       }
+
+       /* Check if component watch has already been set before */
+       list_for_each_entry(comp_node, &event_node->comp_list, l)
+               if (comp_node->parent == cfg->parent &&
+                   counter_comp_read_is_equal(comp_node->comp, cfg->comp)) {
+                       err = -EINVAL;
+                       goto exit_free_event_node;
+               }
+
+       /* Allocate component node */
+       comp_node = kmalloc(sizeof(*comp_node), GFP_KERNEL);
+       if (!comp_node) {
+               err = -ENOMEM;
+               goto exit_free_event_node;
+       }
+       *comp_node = *cfg;
+
+       /* Add component node to event node */
+       list_add_tail(&comp_node->l, &event_node->comp_list);
+
+exit_free_event_node:
+       /* Free event node if no one else is watching */
+       if (list_empty(&event_node->comp_list)) {
+               list_del(&event_node->l);
+               kfree(event_node);
+       }
+
+       return err;
+}
+
+static int counter_enable_events(struct counter_device *const counter)
+{
+       unsigned long flags;
+       int err = 0;
+
+       mutex_lock(&counter->n_events_list_lock);
+       spin_lock_irqsave(&counter->events_list_lock, flags);
+
+       counter_events_list_free(&counter->events_list);
+       list_replace_init(&counter->next_events_list,
+                         &counter->events_list);
+
+       if (counter->ops->events_configure)
+               err = counter->ops->events_configure(counter);
+
+       spin_unlock_irqrestore(&counter->events_list_lock, flags);
+       mutex_unlock(&counter->n_events_list_lock);
+
+       return err;
+}
+
+static int counter_disable_events(struct counter_device *const counter)
+{
+       unsigned long flags;
+       int err = 0;
+
+       spin_lock_irqsave(&counter->events_list_lock, flags);
+
+       counter_events_list_free(&counter->events_list);
+
+       if (counter->ops->events_configure)
+               err = counter->ops->events_configure(counter);
+
+       spin_unlock_irqrestore(&counter->events_list_lock, flags);
+
+       mutex_lock(&counter->n_events_list_lock);
+
+       counter_events_list_free(&counter->next_events_list);
+
+       mutex_unlock(&counter->n_events_list_lock);
+
+       return err;
+}
+
+static int counter_add_watch(struct counter_device *const counter,
+                            const unsigned long arg)
+{
+       void __user *const uwatch = (void __user *)arg;
+       struct counter_watch watch;
+       struct counter_comp_node comp_node = {};
+       size_t parent, id;
+       struct counter_comp *ext;
+       size_t num_ext;
+       int err = 0;
+
+       if (copy_from_user(&watch, uwatch, sizeof(watch)))
+               return -EFAULT;
+
+       if (watch.component.type == COUNTER_COMPONENT_NONE)
+               goto no_component;
+
+       parent = watch.component.parent;
+
+       /* Configure parent component info for comp node */
+       switch (watch.component.scope) {
+       case COUNTER_SCOPE_DEVICE:
+               ext = counter->ext;
+               num_ext = counter->num_ext;
+               break;
+       case COUNTER_SCOPE_SIGNAL:
+               if (parent >= counter->num_signals)
+                       return -EINVAL;
+               parent = array_index_nospec(parent, counter->num_signals);
+
+               comp_node.parent = counter->signals + parent;
+
+               ext = counter->signals[parent].ext;
+               num_ext = counter->signals[parent].num_ext;
+               break;
+       case COUNTER_SCOPE_COUNT:
+               if (parent >= counter->num_counts)
+                       return -EINVAL;
+               parent = array_index_nospec(parent, counter->num_counts);
+
+               comp_node.parent = counter->counts + parent;
+
+               ext = counter->counts[parent].ext;
+               num_ext = counter->counts[parent].num_ext;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       id = watch.component.id;
+
+       /* Configure component info for comp node */
+       switch (watch.component.type) {
+       case COUNTER_COMPONENT_SIGNAL:
+               if (watch.component.scope != COUNTER_SCOPE_SIGNAL)
+                       return -EINVAL;
+
+               comp_node.comp.type = COUNTER_COMP_SIGNAL_LEVEL;
+               comp_node.comp.signal_u32_read = counter->ops->signal_read;
+               break;
+       case COUNTER_COMPONENT_COUNT:
+               if (watch.component.scope != COUNTER_SCOPE_COUNT)
+                       return -EINVAL;
+
+               comp_node.comp.type = COUNTER_COMP_U64;
+               comp_node.comp.count_u64_read = counter->ops->count_read;
+               break;
+       case COUNTER_COMPONENT_FUNCTION:
+               if (watch.component.scope != COUNTER_SCOPE_COUNT)
+                       return -EINVAL;
+
+               comp_node.comp.type = COUNTER_COMP_FUNCTION;
+               comp_node.comp.count_u32_read = counter->ops->function_read;
+               break;
+       case COUNTER_COMPONENT_SYNAPSE_ACTION:
+               if (watch.component.scope != COUNTER_SCOPE_COUNT)
+                       return -EINVAL;
+               if (id >= counter->counts[parent].num_synapses)
+                       return -EINVAL;
+               id = array_index_nospec(id, counter->counts[parent].num_synapses);
+
+               comp_node.comp.type = COUNTER_COMP_SYNAPSE_ACTION;
+               comp_node.comp.action_read = counter->ops->action_read;
+               comp_node.comp.priv = counter->counts[parent].synapses + id;
+               break;
+       case COUNTER_COMPONENT_EXTENSION:
+               if (id >= num_ext)
+                       return -EINVAL;
+               id = array_index_nospec(id, num_ext);
+
+               comp_node.comp = ext[id];
+               break;
+       default:
+               return -EINVAL;
+       }
+       if (!counter_comp_read_is_set(comp_node.comp))
+               return -EOPNOTSUPP;
+
+no_component:
+       mutex_lock(&counter->n_events_list_lock);
+
+       if (counter->ops->watch_validate) {
+               err = counter->ops->watch_validate(counter, &watch);
+               if (err < 0)
+                       goto err_exit;
+       }
+
+       comp_node.component = watch.component;
+
+       err = counter_set_event_node(counter, &watch, &comp_node);
+
+err_exit:
+       mutex_unlock(&counter->n_events_list_lock);
+
+       return err;
+}
+
+static long counter_chrdev_ioctl(struct file *filp, unsigned int cmd,
+                                unsigned long arg)
+{
+       struct counter_device *const counter = filp->private_data;
+       int ret = -ENODEV;
+
+       mutex_lock(&counter->ops_exist_lock);
+
+       if (!counter->ops)
+               goto out_unlock;
+
+       switch (cmd) {
+       case COUNTER_ADD_WATCH_IOCTL:
+               ret = counter_add_watch(counter, arg);
+               break;
+       case COUNTER_ENABLE_EVENTS_IOCTL:
+               ret = counter_enable_events(counter);
+               break;
+       case COUNTER_DISABLE_EVENTS_IOCTL:
+               ret = counter_disable_events(counter);
+               break;
+       default:
+               ret = -ENOIOCTLCMD;
+               break;
+       }
+
+out_unlock:
+       mutex_unlock(&counter->ops_exist_lock);
+
+       return ret;
+}
+
+static int counter_chrdev_open(struct inode *inode, struct file *filp)
+{
+       struct counter_device *const counter = container_of(inode->i_cdev,
+                                                           typeof(*counter),
+                                                           chrdev);
+
+       /* Ensure chrdev is not opened more than 1 at a time */
+       if (!atomic_add_unless(&counter->chrdev_lock, 1, 1))
+               return -EBUSY;
+
+       get_device(&counter->dev);
+       filp->private_data = counter;
+
+       return nonseekable_open(inode, filp);
+}
+
+static int counter_chrdev_release(struct inode *inode, struct file *filp)
+{
+       struct counter_device *const counter = filp->private_data;
+       int ret = 0;
+
+       mutex_lock(&counter->ops_exist_lock);
+
+       if (!counter->ops) {
+               /* Free any lingering held memory */
+               counter_events_list_free(&counter->events_list);
+               counter_events_list_free(&counter->next_events_list);
+               ret = -ENODEV;
+               goto out_unlock;
+       }
+
+       ret = counter_disable_events(counter);
+       if (ret < 0) {
+               mutex_unlock(&counter->ops_exist_lock);
+               return ret;
+       }
+
+out_unlock:
+       mutex_unlock(&counter->ops_exist_lock);
+
+       put_device(&counter->dev);
+       atomic_dec(&counter->chrdev_lock);
+
+       return ret;
+}
+
+static const struct file_operations counter_fops = {
+       .owner = THIS_MODULE,
+       .llseek = no_llseek,
+       .read = counter_chrdev_read,
+       .poll = counter_chrdev_poll,
+       .unlocked_ioctl = counter_chrdev_ioctl,
+       .open = counter_chrdev_open,
+       .release = counter_chrdev_release,
+};
+
+int counter_chrdev_add(struct counter_device *const counter)
+{
+       /* Initialize Counter events lists */
+       INIT_LIST_HEAD(&counter->events_list);
+       INIT_LIST_HEAD(&counter->next_events_list);
+       spin_lock_init(&counter->events_list_lock);
+       mutex_init(&counter->n_events_list_lock);
+       init_waitqueue_head(&counter->events_wait);
+       mutex_init(&counter->events_lock);
+
+       /* Initialize character device */
+       atomic_set(&counter->chrdev_lock, 0);
+       cdev_init(&counter->chrdev, &counter_fops);
+
+       /* Allocate Counter events queue */
+       return kfifo_alloc(&counter->events, 64, GFP_KERNEL);
+}
+
+void counter_chrdev_remove(struct counter_device *const counter)
+{
+       kfifo_free(&counter->events);
+}
+
+static int counter_get_data(struct counter_device *const counter,
+                           const struct counter_comp_node *const comp_node,
+                           u64 *const value)
+{
+       const struct counter_comp *const comp = &comp_node->comp;
+       void *const parent = comp_node->parent;
+       u8 value_u8 = 0;
+       u32 value_u32 = 0;
+       int ret;
+
+       if (comp_node->component.type == COUNTER_COMPONENT_NONE)
+               return 0;
+
+       switch (comp->type) {
+       case COUNTER_COMP_U8:
+       case COUNTER_COMP_BOOL:
+               switch (comp_node->component.scope) {
+               case COUNTER_SCOPE_DEVICE:
+                       ret = comp->device_u8_read(counter, &value_u8);
+                       break;
+               case COUNTER_SCOPE_SIGNAL:
+                       ret = comp->signal_u8_read(counter, parent, &value_u8);
+                       break;
+               case COUNTER_SCOPE_COUNT:
+                       ret = comp->count_u8_read(counter, parent, &value_u8);
+                       break;
+               }
+               *value = value_u8;
+               return ret;
+       case COUNTER_COMP_SIGNAL_LEVEL:
+       case COUNTER_COMP_FUNCTION:
+       case COUNTER_COMP_ENUM:
+       case COUNTER_COMP_COUNT_DIRECTION:
+       case COUNTER_COMP_COUNT_MODE:
+               switch (comp_node->component.scope) {
+               case COUNTER_SCOPE_DEVICE:
+                       ret = comp->device_u32_read(counter, &value_u32);
+                       break;
+               case COUNTER_SCOPE_SIGNAL:
+                       ret = comp->signal_u32_read(counter, parent,
+                                                   &value_u32);
+                       break;
+               case COUNTER_SCOPE_COUNT:
+                       ret = comp->count_u32_read(counter, parent, &value_u32);
+                       break;
+               }
+               *value = value_u32;
+               return ret;
+       case COUNTER_COMP_U64:
+               switch (comp_node->component.scope) {
+               case COUNTER_SCOPE_DEVICE:
+                       return comp->device_u64_read(counter, value);
+               case COUNTER_SCOPE_SIGNAL:
+                       return comp->signal_u64_read(counter, parent, value);
+               case COUNTER_SCOPE_COUNT:
+                       return comp->count_u64_read(counter, parent, value);
+               default:
+                       return -EINVAL;
+               }
+       case COUNTER_COMP_SYNAPSE_ACTION:
+               ret = comp->action_read(counter, parent, comp->priv,
+                                       &value_u32);
+               *value = value_u32;
+               return ret;
+       default:
+               return -EINVAL;
+       }
+}
+
+/**
+ * counter_push_event - queue event for userspace reading
+ * @counter:   pointer to Counter structure
+ * @event:     triggered event
+ * @channel:   event channel
+ *
+ * Note: If no one is watching for the respective event, it is silently
+ * discarded.
+ */
+void counter_push_event(struct counter_device *const counter, const u8 event,
+                       const u8 channel)
+{
+       struct counter_event ev;
+       unsigned int copied = 0;
+       unsigned long flags;
+       struct counter_event_node *event_node;
+       struct counter_comp_node *comp_node;
+
+       ev.timestamp = ktime_get_ns();
+       ev.watch.event = event;
+       ev.watch.channel = channel;
+
+       /* Could be in an interrupt context, so use a spin lock */
+       spin_lock_irqsave(&counter->events_list_lock, flags);
+
+       /* Search for event in the list */
+       list_for_each_entry(event_node, &counter->events_list, l)
+               if (event_node->event == event &&
+                   event_node->channel == channel)
+                       break;
+
+       /* If event is not in the list */
+       if (&event_node->l == &counter->events_list)
+               goto exit_early;
+
+       /* Read and queue relevant comp for userspace */
+       list_for_each_entry(comp_node, &event_node->comp_list, l) {
+               ev.watch.component = comp_node->component;
+               ev.status = -counter_get_data(counter, comp_node, &ev.value);
+
+               copied += kfifo_in(&counter->events, &ev, 1);
+       }
+
+exit_early:
+       spin_unlock_irqrestore(&counter->events_list_lock, flags);
+
+       if (copied)
+               wake_up_poll(&counter->events_wait, EPOLLIN);
+}
+EXPORT_SYMBOL_GPL(counter_push_event);
diff --git a/drivers/counter/counter-chrdev.h b/drivers/counter/counter-chrdev.h
new file mode 100644 (file)
index 0000000..5529d16
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Counter character device interface
+ * Copyright (C) 2020 William Breathitt Gray
+ */
+#ifndef _COUNTER_CHRDEV_H_
+#define _COUNTER_CHRDEV_H_
+
+#include <linux/counter.h>
+
+int counter_chrdev_add(struct counter_device *const counter);
+void counter_chrdev_remove(struct counter_device *const counter);
+
+#endif /* _COUNTER_CHRDEV_H_ */
index 3cda2c4..5acc545 100644 (file)
@@ -3,14 +3,22 @@
  * Generic Counter interface
  * Copyright (C) 2020 William Breathitt Gray
  */
+#include <linux/cdev.h>
 #include <linux/counter.h>
 #include <linux/device.h>
+#include <linux/device/bus.h>
 #include <linux/export.h>
+#include <linux/fs.h>
 #include <linux/gfp.h>
 #include <linux/idr.h>
 #include <linux/init.h>
+#include <linux/kdev_t.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/wait.h>
 
+#include "counter-chrdev.h"
 #include "counter-sysfs.h"
 
 /* Provides a unique ID for each counter device */
@@ -18,6 +26,9 @@ static DEFINE_IDA(counter_ida);
 
 static void counter_device_release(struct device *dev)
 {
+       struct counter_device *const counter = dev_get_drvdata(dev);
+
+       counter_chrdev_remove(counter);
        ida_free(&counter_ida, dev->id);
 }
 
@@ -31,6 +42,8 @@ static struct bus_type counter_bus_type = {
        .dev_name = "counter",
 };
 
+static dev_t counter_devt;
+
 /**
  * counter_register - register Counter to the system
  * @counter:   pointer to Counter to register
@@ -53,10 +66,13 @@ int counter_register(struct counter_device *const counter)
        if (id < 0)
                return id;
 
+       mutex_init(&counter->ops_exist_lock);
+
        /* Configure device structure for Counter */
        dev->id = id;
        dev->type = &counter_device_type;
        dev->bus = &counter_bus_type;
+       dev->devt = MKDEV(MAJOR(counter_devt), id);
        if (counter->parent) {
                dev->parent = counter->parent;
                dev->of_node = counter->parent->of_node;
@@ -64,18 +80,22 @@ int counter_register(struct counter_device *const counter)
        device_initialize(dev);
        dev_set_drvdata(dev, counter);
 
-       /* Add Counter sysfs attributes */
        err = counter_sysfs_add(counter);
        if (err < 0)
                goto err_free_id;
 
-       /* Add device to system */
-       err = device_add(dev);
+       err = counter_chrdev_add(counter);
        if (err < 0)
                goto err_free_id;
 
+       err = cdev_device_add(&counter->chrdev, dev);
+       if (err < 0)
+               goto err_remove_chrdev;
+
        return 0;
 
+err_remove_chrdev:
+       counter_chrdev_remove(counter);
 err_free_id:
        put_device(dev);
        return err;
@@ -93,7 +113,16 @@ void counter_unregister(struct counter_device *const counter)
        if (!counter)
                return;
 
-       device_unregister(&counter->dev);
+       cdev_device_del(&counter->chrdev, &counter->dev);
+
+       mutex_lock(&counter->ops_exist_lock);
+
+       counter->ops = NULL;
+       wake_up(&counter->events_wait);
+
+       mutex_unlock(&counter->ops_exist_lock);
+
+       put_device(&counter->dev);
 }
 EXPORT_SYMBOL_GPL(counter_unregister);
 
@@ -127,13 +156,30 @@ int devm_counter_register(struct device *dev,
 }
 EXPORT_SYMBOL_GPL(devm_counter_register);
 
+#define COUNTER_DEV_MAX 256
+
 static int __init counter_init(void)
 {
-       return bus_register(&counter_bus_type);
+       int err;
+
+       err = bus_register(&counter_bus_type);
+       if (err < 0)
+               return err;
+
+       err = alloc_chrdev_region(&counter_devt, 0, COUNTER_DEV_MAX, "counter");
+       if (err < 0)
+               goto err_unregister_bus;
+
+       return 0;
+
+err_unregister_bus:
+       bus_unregister(&counter_bus_type);
+       return err;
 }
 
 static void __exit counter_exit(void)
 {
+       unregister_chrdev_region(counter_devt, COUNTER_DEV_MAX);
        bus_unregister(&counter_bus_type);
 }
 
index 7c9f7e2..22b14a5 100644 (file)
@@ -6,9 +6,14 @@
 #ifndef _COUNTER_H_
 #define _COUNTER_H_
 
+#include <linux/cdev.h>
 #include <linux/device.h>
 #include <linux/kernel.h>
+#include <linux/kfifo.h>
+#include <linux/mutex.h>
+#include <linux/spinlock_types.h>
 #include <linux/types.h>
+#include <linux/wait.h>
 #include <uapi/linux/counter.h>
 
 struct counter_device;
@@ -200,6 +205,20 @@ struct counter_count {
 };
 
 /**
+ * struct counter_event_node - Counter Event node
+ * @l:         list of current watching Counter events
+ * @event:     event that triggers
+ * @channel:   event channel
+ * @comp_list: list of components to watch when event triggers
+ */
+struct counter_event_node {
+       struct list_head l;
+       u8 event;
+       u8 channel;
+       struct list_head comp_list;
+};
+
+/**
  * struct counter_ops - Callbacks from driver
  * @signal_read:       optional read callback for Signals. The read level of
  *                     the respective Signal should be passed back via the
@@ -222,6 +241,13 @@ struct counter_count {
  * @action_write:      optional write callback for Synapse action modes. The
  *                     action mode to write for the respective Synapse is
  *                     passed in via the action parameter.
+ * @events_configure:  optional write callback to configure events. The list of
+ *                     struct counter_event_node may be accessed via the
+ *                     events_list member of the counter parameter.
+ * @watch_validate:    optional callback to validate a watch. The Counter
+ *                     component watch configuration is passed in via the watch
+ *                     parameter. A return value of 0 indicates a valid Counter
+ *                     component watch configuration.
  */
 struct counter_ops {
        int (*signal_read)(struct counter_device *counter,
@@ -245,6 +271,9 @@ struct counter_ops {
                            struct counter_count *count,
                            struct counter_synapse *synapse,
                            enum counter_synapse_action action);
+       int (*events_configure)(struct counter_device *counter);
+       int (*watch_validate)(struct counter_device *counter,
+                             const struct counter_watch *watch);
 };
 
 /**
@@ -260,6 +289,16 @@ struct counter_ops {
  * @num_ext:           number of Counter device extensions specified in @ext
  * @priv:              optional private data supplied by driver
  * @dev:               internal device structure
+ * @chrdev:            internal character device structure
+ * @events_list:       list of current watching Counter events
+ * @events_list_lock:  lock to protect Counter events list operations
+ * @next_events_list:  list of next watching Counter events
+ * @n_events_list_lock:        lock to protect Counter next events list operations
+ * @events:            queue of detected Counter events
+ * @events_wait:       wait queue to allow blocking reads of Counter events
+ * @events_lock:       lock to protect Counter events queue read operations
+ * @chrdev_lock:       lock to limit chrdev to a single open at a time
+ * @ops_exist_lock:    lock to prevent use during removal
  */
 struct counter_device {
        const char *name;
@@ -278,12 +317,29 @@ struct counter_device {
        void *priv;
 
        struct device dev;
+       struct cdev chrdev;
+       struct list_head events_list;
+       spinlock_t events_list_lock;
+       struct list_head next_events_list;
+       struct mutex n_events_list_lock;
+       DECLARE_KFIFO_PTR(events, struct counter_event);
+       wait_queue_head_t events_wait;
+       struct mutex events_lock;
+       /*
+        * chrdev_lock is locked by counter_chrdev_open() and unlocked by
+        * counter_chrdev_release(), so a mutex is not possible here because
+        * chrdev_lock will invariably be held when returning to user space
+        */
+       atomic_t chrdev_lock;
+       struct mutex ops_exist_lock;
 };
 
 int counter_register(struct counter_device *const counter);
 void counter_unregister(struct counter_device *const counter);
 int devm_counter_register(struct device *dev,
                          struct counter_device *const counter);
+void counter_push_event(struct counter_device *const counter, const u8 event,
+                       const u8 channel);
 
 #define COUNTER_COMP_DEVICE_U8(_name, _read, _write) \
 { \
index 6113938..d0aa95a 100644 (file)
@@ -6,6 +6,19 @@
 #ifndef _UAPI_COUNTER_H_
 #define _UAPI_COUNTER_H_
 
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/* Component type definitions */
+enum counter_component_type {
+       COUNTER_COMPONENT_NONE,
+       COUNTER_COMPONENT_SIGNAL,
+       COUNTER_COMPONENT_COUNT,
+       COUNTER_COMPONENT_FUNCTION,
+       COUNTER_COMPONENT_SYNAPSE_ACTION,
+       COUNTER_COMPONENT_EXTENSION,
+};
+
 /* Component scope definitions */
 enum counter_scope {
        COUNTER_SCOPE_DEVICE,
@@ -13,6 +26,91 @@ enum counter_scope {
        COUNTER_SCOPE_COUNT,
 };
 
+/**
+ * struct counter_component - Counter component identification
+ * @type: component type (one of enum counter_component_type)
+ * @scope: component scope (one of enum counter_scope)
+ * @parent: parent ID (matching the ID suffix of the respective parent sysfs
+ *          path as described by the ABI documentation file
+ *          Documentation/ABI/testing/sysfs-bus-counter)
+ * @id: component ID (matching the ID provided by the respective *_component_id
+ *      sysfs attribute of the desired component)
+ *
+ * For example, if the Count 2 ceiling extension of Counter device 4 is desired,
+ * set type equal to COUNTER_COMPONENT_EXTENSION, scope equal to
+ * COUNTER_COUNT_SCOPE, parent equal to 2, and id equal to the value provided by
+ * the respective /sys/bus/counter/devices/counter4/count2/ceiling_component_id
+ * sysfs attribute.
+ */
+struct counter_component {
+       __u8 type;
+       __u8 scope;
+       __u8 parent;
+       __u8 id;
+};
+
+/* Event type definitions */
+enum counter_event_type {
+       /* Count value increased past ceiling */
+       COUNTER_EVENT_OVERFLOW,
+       /* Count value decreased past floor */
+       COUNTER_EVENT_UNDERFLOW,
+       /* Count value increased past ceiling, or decreased past floor */
+       COUNTER_EVENT_OVERFLOW_UNDERFLOW,
+       /* Count value reached threshold */
+       COUNTER_EVENT_THRESHOLD,
+       /* Index signal detected */
+       COUNTER_EVENT_INDEX,
+};
+
+/**
+ * struct counter_watch - Counter component watch configuration
+ * @component: component to watch when event triggers
+ * @event: event that triggers (one of enum counter_event_type)
+ * @channel: event channel (typically 0 unless the device supports concurrent
+ *          events of the same type)
+ */
+struct counter_watch {
+       struct counter_component component;
+       __u8 event;
+       __u8 channel;
+};
+
+/*
+ * Queues a Counter watch for the specified event.
+ *
+ * The queued watches will not be applied until COUNTER_ENABLE_EVENTS_IOCTL is
+ * called.
+ */
+#define COUNTER_ADD_WATCH_IOCTL _IOW(0x3E, 0x00, struct counter_watch)
+/*
+ * Enables monitoring the events specified by the Counter watches that were
+ * queued by COUNTER_ADD_WATCH_IOCTL.
+ *
+ * If events are already enabled, the new set of watches replaces the old one.
+ * Calling this ioctl also has the effect of clearing the queue of watches added
+ * by COUNTER_ADD_WATCH_IOCTL.
+ */
+#define COUNTER_ENABLE_EVENTS_IOCTL _IO(0x3E, 0x01)
+/*
+ * Stops monitoring the previously enabled events.
+ */
+#define COUNTER_DISABLE_EVENTS_IOCTL _IO(0x3E, 0x02)
+
+/**
+ * struct counter_event - Counter event data
+ * @timestamp: best estimate of time of event occurrence, in nanoseconds
+ * @value: component value
+ * @watch: component watch configuration
+ * @status: return status (system error number)
+ */
+struct counter_event {
+       __aligned_u64 timestamp;
+       __aligned_u64 value;
+       struct counter_watch watch;
+       __u8 status;
+};
+
 /* Count direction values */
 enum counter_count_direction {
        COUNTER_COUNT_DIRECTION_FORWARD,