Message ID | 00be1fccc672c5207f3b04fe4cc09c29e22641f4.1601170670.git.vilhelm.gray@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Introduce the Counter character device interface | expand |
On 9/26/20 9:18 PM, William Breathitt Gray wrote: > 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. > Probably don't need to duplicate the full documentation in the commit message. > diff --git a/drivers/counter/counter-chrdev.c b/drivers/counter/counter-chrdev.c > new file mode 100644 > index 000000000000..2be3846e4105 > --- /dev/null > +++ b/drivers/counter/counter-chrdev.c > + > +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; > + 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_ATOMIC); > + 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 && > + comp_node->comp.count_u8_read == cfg->comp.count_u8_read) > + return -EINVAL; There are already enough things that could cause EINVAL, we could probably skip this duplicate check to make troubleshooting easier. > + > + /* Allocate component node */ > + comp_node = kmalloc(sizeof(*comp_node), GFP_ATOMIC); > + if (!comp_node) { > + err = -ENOMEM; > + goto err_comp_node; Since there is only one goto, we could just handle the error and return here instead. > + } > + *comp_node = *cfg; > + > + /* Add component node to event node */ > + list_add_tail(&comp_node->l, &event_node->comp_list); > + > + return 0; > + > +err_comp_node: A comment explaining the list_empty() check would be nice. It makes sense if you think about it, but it is not super obvious. > + if (list_empty(&event_node->comp_list)) { > + list_del(&event_node->l); > + kfree(event_node); > + } > + return err; > +} > + > +static int counter_set_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; > + > + if (copy_from_user(&watch, uwatch, sizeof(watch))) > + return -EFAULT; > + parent = watch.component.parent; > + id = watch.component.id; > + > + /* Configure parent component info for comp node */ > + switch (watch.component.scope) { > + case COUNTER_SCOPE_DEVICE: > + comp_node.parent = NULL; > + > + ext = counter->ext; > + num_ext = counter->num_ext; > + break; > + case COUNTER_SCOPE_SIGNAL: > + if (counter->num_signals < parent + 1) I think it would be more conventional this way: if (parent >= counter->num_signals) > + return -EINVAL; > + > + comp_node.parent = counter->signals + parent; > + > + ext = counter->signals[parent].ext; > + num_ext = counter->signals[parent].num_ext; > + break; > + case COUNTER_SCOPE_COUNT: > + if (counter->num_counts < parent + 1) Same here. > + return -EINVAL; > + > + comp_node.parent = counter->counts + parent; > + > + ext = counter->counts[parent].ext; > + num_ext = counter->counts[parent].num_ext; > + break; > + default: > + return -EINVAL; > + } > + > + /* 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_u8_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_u8_read = counter->ops->function_read; > + break; > + case COUNTER_COMPONENT_SYNAPSE_ACTION: > + if (watch.component.scope != COUNTER_SCOPE_COUNT) > + return -EINVAL; > + if (counter->counts[parent].num_synapses < id + 1) if (id >= counter->counts[parent].num_synapses) > + return -EINVAL; > + > + 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 (num_ext < id + 1) if (id >= num_ext) > + return -EINVAL; > + > + comp_node.comp = ext[id]; > + break; > + default: > + return -EINVAL; > + } > + if (!comp_node.comp.count_u8_read) A comment reminding us that count_u8_read is part of a union would be helpful here since this is checking that any read function was set, not just count_u8_read. > + return -EFAULT; Would EOPNOTSUPP be a better error choice? > + comp_node.component = watch.component; > + > + return counter_set_event_node(counter, &watch, &comp_node); > +} > + > +static long counter_chrdev_ioctl(struct file *filp, unsigned int cmd, > + unsigned long arg) > +{ > + struct counter_device *const counter = filp->private_data; > + raw_spinlock_t *const events_lock = &counter->events_lock; > + unsigned long flags; > + struct list_head *const events_list = &counter->events_list; > + struct list_head *const next_events_list = &counter->next_events_list; > + > + switch (cmd) { > + case COUNTER_CLEAR_WATCHES_IOCTL: > + raw_spin_lock_irqsave(events_lock, flags); > + counter_events_list_free(events_list); > + raw_spin_unlock_irqrestore(events_lock, flags); > + counter_events_list_free(next_events_list); I think this ioctl is doing too much. If we have to use it for both stopping events and clearing the list accumulated by COUNTER_SET_WATCH_IOCTL, then we have a race condition of no events after clearing watches during the time we are adding new ones and until we load the new ones. It would probably make more sense to call this ioctl COUNTER_STOP_WATCHES_IOCTL and move counter_events_list_free( next_events_list) to the end of COUNTER_LOAD_WATCHES_IOCTL. > + break; > + case COUNTER_SET_WATCH_IOCTL: > + return counter_set_watch(counter, arg); Would it make more sense to call this COUNTER_ADD_WATCH_IOCTL since it adds a watch to the list that will be loaded/enabled later? > + case COUNTER_LOAD_WATCHES_IOCTL: > + raw_spin_lock_irqsave(events_lock, flags); > + counter_events_list_free(events_list); > + list_replace_init(next_events_list, events_list); > + raw_spin_unlock_irqrestore(events_lock, flags); For the eQEP driver, we will need a callback here to configure interrupts for the events. Likewise a callback would be needed here (for the case when we are replacing already loaded watches) and in COUNTER_STOP_WATCHES_IOCTL to unconfigure them. > + break; > + default: > + return -ENOIOCTLCMD; > + } > + > + return 0; > +} > + > +static int counter_chrdev_open(struct inode *inode, struct file *filp) > +{ > + struct counter_device *const counter = container_of(inode->i_cdev, > + typeof(*counter), > + chrdev); > + > + 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; > + unsigned long flags; > + > + put_device(&counter->dev); put_device() should be at the end of the function in case it is the last reference. > + > + raw_spin_lock_irqsave(&counter->events_lock, flags); > + counter_events_list_free(&counter->events_list); > + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > + counter_events_list_free(&counter->next_events_list); > + > + return 0; > +} > + > +int counter_push_event(struct counter_device *const counter, const u8 event, > + const u8 channel) > +{ > + struct counter_event ev = {0}; > + unsigned int copied = 0; > + unsigned long flags; > + struct counter_event_node *event_node; > + struct counter_comp_node *comp_node; > + int err; > + > + ev.timestamp = ktime_get_ns(); > + ev.watch.event = event; > + ev.watch.channel = channel; > + > + raw_spin_lock_irqsave(&counter->events_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) { > + err = counter_get_data(counter, comp_node, &ev.value_u8); > + if (err) > + goto err_counter_get_data; > + > + ev.watch.component = comp_node->component; > + > + copied += kfifo_put(&counter->events, ev); > + } > + > + if (copied) > + wake_up_poll(&counter->events_wait, EPOLLIN); > + > +exit_early: > + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > + > + return 0; > + > +err_counter_get_data: > + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > + return err; This seems slightly redundant. Could just initialize err to 0 and combine the two labels. > +} > +EXPORT_SYMBOL_GPL(counter_push_event); > diff --git a/drivers/counter/counter-sysfs.c b/drivers/counter/counter-sysfs.c > index e66ed99dd5ea..cefef61f170d 100644 > --- a/drivers/counter/counter-sysfs.c > +++ b/drivers/counter/counter-sysfs.c Not sure why sysfs changes are in the chrdev patch. Are these changes related somehow? > diff --git a/include/uapi/linux/counter.h b/include/uapi/linux/counter.h > new file mode 100644 > index 000000000000..ad9a8686b2b0 > --- /dev/null > +++ b/include/uapi/linux/counter.h > @@ -0,0 +1,99 @@ > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ > +/* > + * Userspace ABI for Counter character devices > + * Copyright (C) 2020 William Breathitt Gray > + */ > +#ifndef _UAPI_COUNTER_H_ > +#define _UAPI_COUNTER_H_ > + > +#include <linux/ioctl.h> > +#include <linux/types.h> It would be very helpful to have documentation comments for all of the defines in this file. > + > +#define COUNTER_SCOPE_DEVICE 0 > +#define COUNTER_SCOPE_SIGNAL 1 > +#define COUNTER_SCOPE_COUNT 2 > + > +#define COUNTER_COMPONENT_SIGNAL 0 > +#define COUNTER_COMPONENT_COUNT 1 > +#define COUNTER_COMPONENT_FUNCTION 2 > +#define COUNTER_COMPONENT_SYNAPSE_ACTION 3 > +#define COUNTER_COMPONENT_EXTENSION 4 There are probably some cases where we will want to just have an event without reading a component value, so we could use something like COUNTER_SCOPE_NONE and COUNTER_COMPONENT_NONE here. > + > +/** > + * struct counter_component - Counter component identification > + * @scope: component scope (Device, Count, or Signal) > + * @parent: parent component identification number > + * @type: component type (Count, extension, etc.) > + * @id: component identification number > + */ > +struct counter_component { > + __u8 scope; > + __u64 parent; > + __u8 type; > + __u64 id; > +}; This will have alignment issues, e.g. when 64-bit kernel is used with a 32-bit userspace. It looks like there is a __aligned_u64 type to help with this. But, unless we expect more that 4 billion counter devices, there doesn't seem to be a reason for parent and id to be 64-bit. > + > +#define COUNTER_EVENT_OVERFLOW 0 > +#define COUNTER_EVENT_UNDERFLOW 1 > +#define COUNTER_EVENT_OVERFLOW_UNDERFLOW 2 Would it make sense to just have individual overflow and underflow events? For example, the Carry-Borrow interrupt on 104-quad-8 could just call counter_push_event() twice. > +#define COUNTER_EVENT_THRESHOLD 3 > +#define COUNTER_EVENT_INDEX 4 > + > +/** > + * struct counter_watch - Counter component watch configuration > + * @event: event that triggers > + * @channel: event channel How is channel intended to be used? (The device I am working with I assume would ignore the channel since it is just a single counter.) > + * @component: component to watch when event triggers > + */ > +struct counter_watch { > + __u8 event; > + __u8 channel; > + struct counter_component component; > +}; To get the most compact layout, usually fields are ordered largest to smallest. > + > +#define COUNTER_CLEAR_WATCHES_IOCTL _IO(0x3E, 0x00) > +#define COUNTER_SET_WATCH_IOCTL _IOW(0x3E, 0x01, struct counter_watch) > +#define COUNTER_LOAD_WATCHES_IOCTL _IO(0x3E, 0x02) > + > +/** > + * struct counter_event - Counter event data > + * @timestamp: best estimate of time of event occurrence, in nanoseconds > + * @watch: component watch configuration > + * @value_u8: component value as __u8 data type > + * @value_u64: component value as __u64 data type > + */ > +struct counter_event { > + __u64 timestamp; > + struct counter_watch watch; > + union { > + __u8 value_u8; > + __u64 value_u64; > + }; > +}; Should probably use __aligned_u64 here too. > + > +#define COUNTER_COUNT_DIRECTION_FORWARD 0 > +#define COUNTER_COUNT_DIRECTION_BACKWARD 1 > + > +#define COUNTER_COUNT_MODE_NORMAL 0 > +#define COUNTER_COUNT_MODE_RANGE_LIMIT 1 > +#define COUNTER_COUNT_MODE_NON_RECYCLE 2 > +#define COUNTER_COUNT_MODE_MODULO_N 3 > + > +#define COUNTER_FUNCTION_INCREASE 0 > +#define COUNTER_FUNCTION_DECREASE 1 > +#define COUNTER_FUNCTION_PULSE_DIRECTION 2 > +#define COUNTER_FUNCTION_QUADRATURE_X1_A 3 > +#define COUNTER_FUNCTION_QUADRATURE_X1_B 4 > +#define COUNTER_FUNCTION_QUADRATURE_X2_A 5 > +#define COUNTER_FUNCTION_QUADRATURE_X2_B 6 > +#define COUNTER_FUNCTION_QUADRATURE_X4 7 > + > +#define COUNTER_SIGNAL_LEVEL_LOW 0 > +#define COUNTER_SIGNAL_LEVEL_HIGH 1 > + > +#define COUNTER_SYNAPSE_ACTION_NONE 0 > +#define COUNTER_SYNAPSE_ACTION_RISING_EDGE 1 > +#define COUNTER_SYNAPSE_ACTION_FALLING_EDGE 2 > +#define COUNTER_SYNAPSE_ACTION_BOTH_EDGES 3 > + > +#endif /* _UAPI_COUNTER_H_ */ > Overall, I think we are getting close. I've started adding events to the TI eQEP driver. You can find the code at [1]. I still have a bit of work to do to add the timer and edge capture events. [1]: https://github.com/dlech/linux/tree/bone-counter
On 9/26/20 9:18 PM, William Breathitt Gray wrote: > diff --git a/drivers/counter/counter-chrdev.c b/drivers/counter/counter-chrdev.c > new file mode 100644 > index 000000000000..2be3846e4105 > --- /dev/null > +++ b/drivers/counter/counter-chrdev.c > +/** > + * 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. > + * > + * RETURNS: > + * 0 on success, negative error number on failure. > + */ > +int counter_push_event(struct counter_device *const counter, const u8 event, > + const u8 channel) > +{ > + struct counter_event ev = {0}; > + unsigned int copied = 0; > + unsigned long flags; > + struct counter_event_node *event_node; > + struct counter_comp_node *comp_node; > + int err; > + > + ev.timestamp = ktime_get_ns(); > + ev.watch.event = event; > + ev.watch.channel = channel; > + > + raw_spin_lock_irqsave(&counter->events_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) { > + err = counter_get_data(counter, comp_node, &ev.value_u8); Currently all counter devices are memory mapped devices so calling counter_get_data() here with interrupts disabled is probably OK, but if any counter drivers are added that use I2C/SPI/etc. that will take a long time to read, it would cause problems leaving interrupts disabled here. Brainstorming: Would it make sense to separate the event from the component value being read? As I mentioned in one of my previous reviews, I think there are some cases where we would just want to know when an event happened and not read any additional data anyway. In the case of a slow communication bus, this would also let us queue the event in the kfifo and notify poll right away and then defer the reads in a workqueue for later. > + if (err) > + goto err_counter_get_data; > + > + ev.watch.component = comp_node->component; > + > + copied += kfifo_put(&counter->events, ev); > + } > + > + if (copied) > + wake_up_poll(&counter->events_wait, EPOLLIN); > + > +exit_early: > + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > + > + return 0; > + > +err_counter_get_data: > + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > + return err; > +}
On Wed, Oct 14, 2020 at 12:43:08PM -0500, David Lechner wrote: > On 9/26/20 9:18 PM, William Breathitt Gray wrote: > > diff --git a/drivers/counter/counter-chrdev.c b/drivers/counter/counter-chrdev.c > > new file mode 100644 > > index 000000000000..2be3846e4105 > > --- /dev/null > > +++ b/drivers/counter/counter-chrdev.c > > > > +/** > > + * 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. > > + * > > + * RETURNS: > > + * 0 on success, negative error number on failure. > > + */ > > +int counter_push_event(struct counter_device *const counter, const u8 event, > > + const u8 channel) > > +{ > > + struct counter_event ev = {0}; > > + unsigned int copied = 0; > > + unsigned long flags; > > + struct counter_event_node *event_node; > > + struct counter_comp_node *comp_node; > > + int err; > > + > > + ev.timestamp = ktime_get_ns(); > > + ev.watch.event = event; > > + ev.watch.channel = channel; > > + > > + raw_spin_lock_irqsave(&counter->events_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) { > > + err = counter_get_data(counter, comp_node, &ev.value_u8); > > Currently all counter devices are memory mapped devices so calling > counter_get_data() here with interrupts disabled is probably OK, but > if any counter drivers are added that use I2C/SPI/etc. that will take > a long time to read, it would cause problems leaving interrupts > disabled here. > > Brainstorming: Would it make sense to separate the event from the > component value being read? As I mentioned in one of my previous > reviews, I think there are some cases where we would just want to > know when an event happened and not read any additional data anyway. > In the case of a slow communication bus, this would also let us > queue the event in the kfifo and notify poll right away and then > defer the reads in a workqueue for later. I don't see any problems with reporting just an event without any component value attached (e.g. userspace could handle the component reads via sysfs at a later point). We would just need a way to inform userspace that the struct counter_component in the struct counter_watch returned should be ignored. Perhaps we can add an additional member to struct counter_watch indicating whether it's an empty watch; or alternatively, add a new component scope define to differentiate between an actual component and an empty one (e.g. COUNTER_SCOPE_EVENT). What do you think? William Breathitt Gray
On 10/14/20 2:05 PM, William Breathitt Gray wrote: > On Wed, Oct 14, 2020 at 12:43:08PM -0500, David Lechner wrote: >> On 9/26/20 9:18 PM, William Breathitt Gray wrote: >>> diff --git a/drivers/counter/counter-chrdev.c b/drivers/counter/counter-chrdev.c >>> new file mode 100644 >>> index 000000000000..2be3846e4105 >>> --- /dev/null >>> +++ b/drivers/counter/counter-chrdev.c >> >> >>> +/** >>> + * 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. >>> + * >>> + * RETURNS: >>> + * 0 on success, negative error number on failure. >>> + */ >>> +int counter_push_event(struct counter_device *const counter, const u8 event, >>> + const u8 channel) >>> +{ >>> + struct counter_event ev = {0}; >>> + unsigned int copied = 0; >>> + unsigned long flags; >>> + struct counter_event_node *event_node; >>> + struct counter_comp_node *comp_node; >>> + int err; >>> + >>> + ev.timestamp = ktime_get_ns(); >>> + ev.watch.event = event; >>> + ev.watch.channel = channel; >>> + >>> + raw_spin_lock_irqsave(&counter->events_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) { >>> + err = counter_get_data(counter, comp_node, &ev.value_u8); >> >> Currently all counter devices are memory mapped devices so calling >> counter_get_data() here with interrupts disabled is probably OK, but >> if any counter drivers are added that use I2C/SPI/etc. that will take >> a long time to read, it would cause problems leaving interrupts >> disabled here. >> >> Brainstorming: Would it make sense to separate the event from the >> component value being read? As I mentioned in one of my previous >> reviews, I think there are some cases where we would just want to >> know when an event happened and not read any additional data anyway. >> In the case of a slow communication bus, this would also let us >> queue the event in the kfifo and notify poll right away and then >> defer the reads in a workqueue for later. > > I don't see any problems with reporting just an event without any > component value attached (e.g. userspace could handle the component > reads via sysfs at a later point). We would just need a way to inform > userspace that the struct counter_component in the struct counter_watch > returned should be ignored. > > Perhaps we can add an additional member to struct counter_watch > indicating whether it's an empty watch; or alternatively, add a new > component scope define to differentiate between an actual component and > an empty one (e.g. COUNTER_SCOPE_EVENT). What do you think? > > William Breathitt Gray > I made the same suggestion in one of my other replies - except I called it COUNTER_SCOPE_NONE.
On 9/26/20 9:18 PM, William Breathitt Gray wrote: > +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 long flags; > + unsigned int copied; > + > + 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)); > + if (err) > + return err; > + } > + > + raw_spin_lock_irqsave(&counter->events_lock, flags); > + err = kfifo_to_user(&counter->events, buf, len, &copied); > + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > + if (err) > + return err; > + } while (!copied); > + > + return copied; > +} All other uses of kfifo_to_user() I saw use a mutex instead of spin lock. I don't see a reason for disabling interrupts here. Example: static ssize_t iio_event_chrdev_read(struct file *filep, char __user *buf, size_t count, loff_t *f_ps) { struct iio_dev *indio_dev = filep->private_data; struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); struct iio_event_interface *ev_int = iio_dev_opaque->event_interface; unsigned int copied; int ret; if (!indio_dev->info) return -ENODEV; if (count < sizeof(struct iio_event_data)) return -EINVAL; do { if (kfifo_is_empty(&ev_int->det_events)) { if (filep->f_flags & O_NONBLOCK) return -EAGAIN; ret = wait_event_interruptible(ev_int->wait, !kfifo_is_empty(&ev_int->det_events) || indio_dev->info == NULL); if (ret) return ret; if (indio_dev->info == NULL) return -ENODEV; } if (mutex_lock_interruptible(&ev_int->read_lock)) return -ERESTARTSYS; ret = kfifo_to_user(&ev_int->det_events, buf, count, &copied); mutex_unlock(&ev_int->read_lock); if (ret) return ret; /* * If we couldn't read anything from the fifo (a different * thread might have been faster) we either return -EAGAIN if * the file descriptor is non-blocking, otherwise we go back to * sleep and wait for more data to arrive. */ if (copied == 0 && (filep->f_flags & O_NONBLOCK)) return -EAGAIN; } while (copied == 0); return copied; }
On Tue, Oct 13, 2020 at 08:40:18PM -0500, David Lechner wrote: > On 9/26/20 9:18 PM, William Breathitt Gray wrote: > > 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. > > > > Probably don't need to duplicate the full documentation in the commit > message. Ack. > > diff --git a/drivers/counter/counter-chrdev.c b/drivers/counter/counter-chrdev.c > > new file mode 100644 > > index 000000000000..2be3846e4105 > > --- /dev/null > > +++ b/drivers/counter/counter-chrdev.c > > > > + > > +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; > > + 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_ATOMIC); > > + 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 && > > + comp_node->comp.count_u8_read == cfg->comp.count_u8_read) > > + return -EINVAL; > > There are already enough things that could cause EINVAL, we could > probably skip this duplicate check to make troubleshooting easier. I'm not sure it would be safe to remove this check. This prevents duplicate watches from being allocated for the same component -- the rationale is that there's no benefit in repeating a component value grab for the same event. Although a typical user will not have the motivation to do this, a bad actor could purposely keep allocating duplicate watches in order to starve all the available memory. > > + > > + /* Allocate component node */ > > + comp_node = kmalloc(sizeof(*comp_node), GFP_ATOMIC); > > + if (!comp_node) { > > + err = -ENOMEM; > > + goto err_comp_node; > > Since there is only one goto, we could just handle the error and > return here instead. Ack. > > + } > > + *comp_node = *cfg; > > + > > + /* Add component node to event node */ > > + list_add_tail(&comp_node->l, &event_node->comp_list); > > + > > + return 0; > > + > > +err_comp_node: > > A comment explaining the list_empty() check would be nice. > It makes sense if you think about it, but it is not super > obvious. Ack. > > + if (list_empty(&event_node->comp_list)) { > > + list_del(&event_node->l); > > + kfree(event_node); > > + } > > + return err; > > +} > > + > > +static int counter_set_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; > > + > > + if (copy_from_user(&watch, uwatch, sizeof(watch))) > > + return -EFAULT; > > + parent = watch.component.parent; > > + id = watch.component.id; > > + > > + /* Configure parent component info for comp node */ > > + switch (watch.component.scope) { > > + case COUNTER_SCOPE_DEVICE: > > + comp_node.parent = NULL; > > + > > + ext = counter->ext; > > + num_ext = counter->num_ext; > > + break; > > + case COUNTER_SCOPE_SIGNAL: > > + if (counter->num_signals < parent + 1) > > I think it would be more conventional this way: > > if (parent >= counter->num_signals) Ack. > > + return -EINVAL; > > + > > + comp_node.parent = counter->signals + parent; > > + > > + ext = counter->signals[parent].ext; > > + num_ext = counter->signals[parent].num_ext; > > + break; > > + case COUNTER_SCOPE_COUNT: > > + if (counter->num_counts < parent + 1) > > Same here. Ack. > > + return -EINVAL; > > + > > + comp_node.parent = counter->counts + parent; > > + > > + ext = counter->counts[parent].ext; > > + num_ext = counter->counts[parent].num_ext; > > + break; > > + default: > > + return -EINVAL; > > + } > > + > > + /* 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_u8_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_u8_read = counter->ops->function_read; > > + break; > > + case COUNTER_COMPONENT_SYNAPSE_ACTION: > > + if (watch.component.scope != COUNTER_SCOPE_COUNT) > > + return -EINVAL; > > + if (counter->counts[parent].num_synapses < id + 1) > > if (id >= counter->counts[parent].num_synapses) Ack. > > + return -EINVAL; > > + > > + 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 (num_ext < id + 1) > > if (id >= num_ext) Ack. > > + return -EINVAL; > > + > > + comp_node.comp = ext[id]; > > + break; > > + default: > > + return -EINVAL; > > + } > > + if (!comp_node.comp.count_u8_read) > > A comment reminding us that count_u8_read is part of a union > would be helpful here since this is checking that any read > function was set, not just count_u8_read. Ack. > > + return -EFAULT; > > Would EOPNOTSUPP be a better error choice? You're right, that would be a more apt return. > > + comp_node.component = watch.component; > > + > > + return counter_set_event_node(counter, &watch, &comp_node); > > +} > > + > > +static long counter_chrdev_ioctl(struct file *filp, unsigned int cmd, > > + unsigned long arg) > > +{ > > + struct counter_device *const counter = filp->private_data; > > + raw_spinlock_t *const events_lock = &counter->events_lock; > > + unsigned long flags; > > + struct list_head *const events_list = &counter->events_list; > > + struct list_head *const next_events_list = &counter->next_events_list; > > + > > + switch (cmd) { > > + case COUNTER_CLEAR_WATCHES_IOCTL: > > + raw_spin_lock_irqsave(events_lock, flags); > > + counter_events_list_free(events_list); > > + raw_spin_unlock_irqrestore(events_lock, flags); > > + counter_events_list_free(next_events_list); > > I think this ioctl is doing too much. If we have to use it for both > stopping events and clearing the list accumulated by > COUNTER_SET_WATCH_IOCTL, then we have a race condition of no events > after clearing watches during the time we are adding new ones and > until we load the new ones. > > It would probably make more sense to call this ioctl > COUNTER_STOP_WATCHES_IOCTL and move counter_events_list_free( > next_events_list) to the end of COUNTER_LOAD_WATCHES_IOCTL. I don't think we will necessarily have a race condition here. COUNTER_CLEAR_WATCHES_IOCTL is intended to just clear the watches; e.g. bring us back to a clear state when some sort of job has completely finished and the user is no longer going to watch events for a while (maybe they're adjusting the conveyor for the next job or some similar operation). I think the scenario you're concerned about is when you need to swap watches in the middle of a job without losing events. In this case, you wouldn't need to use COUNTER_CLEAR_WATCHES_IOCTL at all. Instead, you would just set up the watches via COUNTER_SET_WATCH_IOCTL, and then use COUNTER_LOAD_WATCHES_IOCTL to perform the swap; after COUNTER_LOAD_WATCHES_IOCTL completes, next_events_list is empty (thanks to list_replace_init()) and you're ready for the next set of watches. > > + break; > > + case COUNTER_SET_WATCH_IOCTL: > > + return counter_set_watch(counter, arg); > > > Would it make more sense to call this COUNTER_ADD_WATCH_IOCTL since it > adds a watch to the list that will be loaded/enabled later? That's a good point because "set" might easily be confused for a configuration operation. I'll rename this to be clearer. > > + case COUNTER_LOAD_WATCHES_IOCTL: > > + raw_spin_lock_irqsave(events_lock, flags); > > + counter_events_list_free(events_list); > > + list_replace_init(next_events_list, events_list); > > + raw_spin_unlock_irqrestore(events_lock, flags); > > For the eQEP driver, we will need a callback here to configure > interrupts for the events. Likewise a callback would be needed > here (for the case when we are replacing already loaded watches) > and in COUNTER_STOP_WATCHES_IOCTL to unconfigure them. Ack, I'll add in a callback for interrupt configuration where needed. > > + break; > > + default: > > + return -ENOIOCTLCMD; > > + } > > + > > + return 0; > > +} > > + > > +static int counter_chrdev_open(struct inode *inode, struct file *filp) > > +{ > > + struct counter_device *const counter = container_of(inode->i_cdev, > > + typeof(*counter), > > + chrdev); > > + > > + 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; > > + unsigned long flags; > > + > > + put_device(&counter->dev); > > put_device() should be at the end of the function in case it is the last > reference. put_device() shouldn't affect the counter_device events members, so I don't think there's a difference in this case if it's called at the beginning or end of the counter_chrdev_release function. > > + > > + raw_spin_lock_irqsave(&counter->events_lock, flags); > > + counter_events_list_free(&counter->events_list); > > + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > > + counter_events_list_free(&counter->next_events_list); > > + > > + return 0; > > +} > > + > > > +int counter_push_event(struct counter_device *const counter, const u8 event, > > + const u8 channel) > > +{ > > + struct counter_event ev = {0}; > > + unsigned int copied = 0; > > + unsigned long flags; > > + struct counter_event_node *event_node; > > + struct counter_comp_node *comp_node; > > + int err; > > + > > + ev.timestamp = ktime_get_ns(); > > + ev.watch.event = event; > > + ev.watch.channel = channel; > > + > > + raw_spin_lock_irqsave(&counter->events_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) { > > + err = counter_get_data(counter, comp_node, &ev.value_u8); > > + if (err) > > + goto err_counter_get_data; > > + > > + ev.watch.component = comp_node->component; > > + > > + copied += kfifo_put(&counter->events, ev); > > + } > > + > > + if (copied) > > + wake_up_poll(&counter->events_wait, EPOLLIN); > > + > > +exit_early: > > + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > > + > > + return 0; > > + > > +err_counter_get_data: > > + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > > + return err; > > This seems slightly redundant. Could just initialize err to 0 > and combine the two labels. Ack. > > +} > > +EXPORT_SYMBOL_GPL(counter_push_event); > > > > diff --git a/drivers/counter/counter-sysfs.c b/drivers/counter/counter-sysfs.c > > index e66ed99dd5ea..cefef61f170d 100644 > > --- a/drivers/counter/counter-sysfs.c > > +++ b/drivers/counter/counter-sysfs.c > > > Not sure why sysfs changes are in the chrdev patch. Are these > changes related somehow? Sorry, I forgot to explain this in the cover letter. The changes here are only useful for the character device interface. These changes introduce the extensionZ_name and extensionZ_width sysfs attributes. In the character device interface, extensions are selected by their id number, and the value returned depends on the type of data. The new sysfs attributes introduced here allow users to match the id of an extension with its name, as well as the bit width of the value returned so that the user knows whether to use the value_u8 or value_u64 union member in struct counter_event. > > diff --git a/include/uapi/linux/counter.h b/include/uapi/linux/counter.h > > new file mode 100644 > > index 000000000000..ad9a8686b2b0 > > --- /dev/null > > +++ b/include/uapi/linux/counter.h > > @@ -0,0 +1,99 @@ > > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ > > +/* > > + * Userspace ABI for Counter character devices > > + * Copyright (C) 2020 William Breathitt Gray > > + */ > > +#ifndef _UAPI_COUNTER_H_ > > +#define _UAPI_COUNTER_H_ > > + > > +#include <linux/ioctl.h> > > +#include <linux/types.h> > > It would be very helpful to have documentation comments for all of > the defines in this file. Ack. > > + > > +#define COUNTER_SCOPE_DEVICE 0 > > +#define COUNTER_SCOPE_SIGNAL 1 > > +#define COUNTER_SCOPE_COUNT 2 > > + > > +#define COUNTER_COMPONENT_SIGNAL 0 > > +#define COUNTER_COMPONENT_COUNT 1 > > +#define COUNTER_COMPONENT_FUNCTION 2 > > +#define COUNTER_COMPONENT_SYNAPSE_ACTION 3 > > +#define COUNTER_COMPONENT_EXTENSION 4 > > There are probably some cases where we will want to just > have an event without reading a component value, so we > could use something like COUNTER_SCOPE_NONE and > COUNTER_COMPONENT_NONE here. I'll come up with a solution for this use-case. > > + > > +/** > > + * struct counter_component - Counter component identification > > + * @scope: component scope (Device, Count, or Signal) > > + * @parent: parent component identification number > > + * @type: component type (Count, extension, etc.) > > + * @id: component identification number > > + */ > > +struct counter_component { > > + __u8 scope; > > + __u64 parent; > > + __u8 type; > > + __u64 id; > > +}; > > > This will have alignment issues, e.g. when 64-bit kernel is used > with a 32-bit userspace. It looks like there is a __aligned_u64 > type to help with this. But, unless we expect more that 4 billion > counter devices, there doesn't seem to be a reason for parent > and id to be 64-bit. Fair point, I'll redefine these id as __u8 since it's unlikely we'll ever have a device that needs more than 255 components. > > + > > +#define COUNTER_EVENT_OVERFLOW 0 > > +#define COUNTER_EVENT_UNDERFLOW 1 > > +#define COUNTER_EVENT_OVERFLOW_UNDERFLOW 2 > > Would it make sense to just have individual overflow and > underflow events? For example, the Carry-Borrow interrupt > on 104-quad-8 could just call counter_push_event() twice. I can see what you mean, but I think a dedicated OVERFLOW_UNDERFLOW event is useful because of the directional implications of an OVERFLOW or UNDERFLOW event by itself: if we see an OVERFLOW event we can be certain that the counter changed conceptually from a ceiling value to floor value (and vice-versa for an UNDERFLOW event). By providing the OVERFLOW_UNDERFLOW event we have an explicit way to indicate that the device is not concerned with the direction of the change that trigger the event. I think that's useful information to have for a user. > > +#define COUNTER_EVENT_THRESHOLD 3 > > +#define COUNTER_EVENT_INDEX 4 > > + > > +/** > > + * struct counter_watch - Counter component watch configuration > > + * @event: event that triggers > > + * @channel: event channel > > How is channel intended to be used? (The device I am working with > I assume would ignore the channel since it is just a single counter.) Yes, this is for physical devices that have multiple counters ("channels") such as the 104-QUAD-8 which has 8 available on a single PC104 card. For the device you're working on, you would probably just set channel to 0 for all your uses since you only have one counter. > > + * @component: component to watch when event triggers > > + */ > > +struct counter_watch { > > + __u8 event; > > + __u8 channel; > > + struct counter_component component; > > +}; > > To get the most compact layout, usually fields are ordered largest to smallest. Ack. > > + > > +#define COUNTER_CLEAR_WATCHES_IOCTL _IO(0x3E, 0x00) > > +#define COUNTER_SET_WATCH_IOCTL _IOW(0x3E, 0x01, struct counter_watch) > > +#define COUNTER_LOAD_WATCHES_IOCTL _IO(0x3E, 0x02) > > + > > +/** > > + * struct counter_event - Counter event data > > + * @timestamp: best estimate of time of event occurrence, in nanoseconds > > + * @watch: component watch configuration > > + * @value_u8: component value as __u8 data type > > + * @value_u64: component value as __u64 data type > > + */ > > +struct counter_event { > > + __u64 timestamp; > > + struct counter_watch watch; > > + union { > > + __u8 value_u8; > > + __u64 value_u64; > > + }; > > +}; > > Should probably use __aligned_u64 here too. Ack. > > + > > +#define COUNTER_COUNT_DIRECTION_FORWARD 0 > > +#define COUNTER_COUNT_DIRECTION_BACKWARD 1 > > + > > +#define COUNTER_COUNT_MODE_NORMAL 0 > > +#define COUNTER_COUNT_MODE_RANGE_LIMIT 1 > > +#define COUNTER_COUNT_MODE_NON_RECYCLE 2 > > +#define COUNTER_COUNT_MODE_MODULO_N 3 > > + > > +#define COUNTER_FUNCTION_INCREASE 0 > > +#define COUNTER_FUNCTION_DECREASE 1 > > +#define COUNTER_FUNCTION_PULSE_DIRECTION 2 > > +#define COUNTER_FUNCTION_QUADRATURE_X1_A 3 > > +#define COUNTER_FUNCTION_QUADRATURE_X1_B 4 > > +#define COUNTER_FUNCTION_QUADRATURE_X2_A 5 > > +#define COUNTER_FUNCTION_QUADRATURE_X2_B 6 > > +#define COUNTER_FUNCTION_QUADRATURE_X4 7 > > + > > +#define COUNTER_SIGNAL_LEVEL_LOW 0 > > +#define COUNTER_SIGNAL_LEVEL_HIGH 1 > > + > > +#define COUNTER_SYNAPSE_ACTION_NONE 0 > > +#define COUNTER_SYNAPSE_ACTION_RISING_EDGE 1 > > +#define COUNTER_SYNAPSE_ACTION_FALLING_EDGE 2 > > +#define COUNTER_SYNAPSE_ACTION_BOTH_EDGES 3 > > + > > +#endif /* _UAPI_COUNTER_H_ */ > > > > > Overall, I think we are getting close. I've started adding > events to the TI eQEP driver. You can find the code at [1]. > I still have a bit of work to do to add the timer and edge > capture events. > > [1]: https://github.com/dlech/linux/tree/bone-counter I just want to mention again that I appreciate all the reviews you send for these patchsets, it really helps to have someone to bounce ideas off of as well an extra set of eyes to catch all the silly bugs that I miss during the revisions. :-) Thanks, William Breathitt Gray
On Wed, Oct 14, 2020 at 05:40:44PM -0500, David Lechner wrote: > On 9/26/20 9:18 PM, William Breathitt Gray wrote: > > +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 long flags; > > + unsigned int copied; > > + > > + 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)); > > + if (err) > > + return err; > > + } > > + > > + raw_spin_lock_irqsave(&counter->events_lock, flags); > > + err = kfifo_to_user(&counter->events, buf, len, &copied); > > + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > > + if (err) > > + return err; > > + } while (!copied); > > + > > + return copied; > > +} > > All other uses of kfifo_to_user() I saw use a mutex instead of spin > lock. I don't see a reason for disabling interrupts here. The Counter character device interface is special in this case because counter->events could be accessed from an interrupt context. This is possible if counter_push_event() is called for an interrupt (as is the case for the 104_quad_8 driver). In this case, we can't use mutex because we can't sleep in an interrupt context, so our only option is to use spin lock. William Breathitt Gray
>>> +static long counter_chrdev_ioctl(struct file *filp, unsigned int cmd, >>> + unsigned long arg) >>> +{ >>> + struct counter_device *const counter = filp->private_data; >>> + raw_spinlock_t *const events_lock = &counter->events_lock; >>> + unsigned long flags; >>> + struct list_head *const events_list = &counter->events_list; >>> + struct list_head *const next_events_list = &counter->next_events_list; >>> + >>> + switch (cmd) { >>> + case COUNTER_CLEAR_WATCHES_IOCTL: >>> + raw_spin_lock_irqsave(events_lock, flags); >>> + counter_events_list_free(events_list); >>> + raw_spin_unlock_irqrestore(events_lock, flags); >>> + counter_events_list_free(next_events_list); >> >> I think this ioctl is doing too much. If we have to use it for both >> stopping events and clearing the list accumulated by >> COUNTER_SET_WATCH_IOCTL, then we have a race condition of no events >> after clearing watches during the time we are adding new ones and >> until we load the new ones. >> >> It would probably make more sense to call this ioctl >> COUNTER_STOP_WATCHES_IOCTL and move counter_events_list_free( >> next_events_list) to the end of COUNTER_LOAD_WATCHES_IOCTL. > > I don't think we will necessarily have a race condition here. > COUNTER_CLEAR_WATCHES_IOCTL is intended to just clear the watches; e.g. > bring us back to a clear state when some sort of job has completely > finished and the user is no longer going to watch events for a while > (maybe they're adjusting the conveyor for the next job or some similar > operation). > > I think the scenario you're concerned about is when you need to swap > watches in the middle of a job without losing events. In this case, you > wouldn't need to use COUNTER_CLEAR_WATCHES_IOCTL at all. Instead, you > would just set up the watches via COUNTER_SET_WATCH_IOCTL, and then use > COUNTER_LOAD_WATCHES_IOCTL to perform the swap; after > COUNTER_LOAD_WATCHES_IOCTL completes, next_events_list is empty (thanks > to list_replace_init()) and you're ready for the next set of watches. > Got it. I think I missed the fact that list_replace_init() clears next_events_list. >>> + >>> +static int counter_chrdev_release(struct inode *inode, struct file *filp) >>> +{ >>> + struct counter_device *const counter = filp->private_data; >>> + unsigned long flags; >>> + >>> + put_device(&counter->dev); >> >> put_device() should be at the end of the function in case it is the last >> reference. > > put_device() shouldn't affect the counter_device events members, so I > don't think there's a difference in this case if it's called at the > beginning or end of the counter_chrdev_release function. > It isn't possible the some memory allocated with devm_kalloc() could be be referenced after calling put_device() now or in the future? >>> +} >>> +EXPORT_SYMBOL_GPL(counter_push_event); >> >> >>> diff --git a/drivers/counter/counter-sysfs.c b/drivers/counter/counter-sysfs.c >>> index e66ed99dd5ea..cefef61f170d 100644 >>> --- a/drivers/counter/counter-sysfs.c >>> +++ b/drivers/counter/counter-sysfs.c >> >> >> Not sure why sysfs changes are in the chrdev patch. Are these >> changes related somehow? > > Sorry, I forgot to explain this in the cover letter. The changes here > are only useful for the character device interface. These changes > introduce the extensionZ_name and extensionZ_width sysfs attributes. > > In the character device interface, extensions are selected by their id > number, and the value returned depends on the type of data. The new > sysfs attributes introduced here allow users to match the id of an > extension with its name, as well as the bit width of the value returned > so that the user knows whether to use the value_u8 or value_u64 union > member in struct counter_event. > Are we sure that all value types will always be CPU-endian unsigned integers? Or should we make an enum to describe the data type instead of just the width?
On 10/18/20 11:58 AM, William Breathitt Gray wrote: > On Wed, Oct 14, 2020 at 05:40:44PM -0500, David Lechner wrote: >> On 9/26/20 9:18 PM, William Breathitt Gray wrote: >>> +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 long flags; >>> + unsigned int copied; >>> + >>> + 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)); >>> + if (err) >>> + return err; >>> + } >>> + >>> + raw_spin_lock_irqsave(&counter->events_lock, flags); >>> + err = kfifo_to_user(&counter->events, buf, len, &copied); >>> + raw_spin_unlock_irqrestore(&counter->events_lock, flags); >>> + if (err) >>> + return err; >>> + } while (!copied); >>> + >>> + return copied; >>> +} >> >> All other uses of kfifo_to_user() I saw use a mutex instead of spin >> lock. I don't see a reason for disabling interrupts here. > > The Counter character device interface is special in this case because > counter->events could be accessed from an interrupt context. This is > possible if counter_push_event() is called for an interrupt (as is the > case for the 104_quad_8 driver). In this case, we can't use mutex > because we can't sleep in an interrupt context, so our only option is to > use spin lock. > The way I understand it, locking is only needed for concurrent readers and locking between reader and writer is not needed.
On Tue, Oct 20, 2020 at 10:53:32AM -0500, David Lechner wrote: > >>> + > >>> +static int counter_chrdev_release(struct inode *inode, struct file *filp) > >>> +{ > >>> + struct counter_device *const counter = filp->private_data; > >>> + unsigned long flags; > >>> + > >>> + put_device(&counter->dev); > >> > >> put_device() should be at the end of the function in case it is the last > >> reference. > > > > put_device() shouldn't affect the counter_device events members, so I > > don't think there's a difference in this case if it's called at the > > beginning or end of the counter_chrdev_release function. > > > > It isn't possible the some memory allocated with devm_kalloc() could be > be referenced after calling put_device() now or in the future? You're right, if put_device() is called before then we could end up in a garbage state where the device memory is released but events list has not yet been freed. I'll move put_device() to after the events list is freed then. > >>> diff --git a/drivers/counter/counter-sysfs.c b/drivers/counter/counter-sysfs.c > >>> index e66ed99dd5ea..cefef61f170d 100644 > >>> --- a/drivers/counter/counter-sysfs.c > >>> +++ b/drivers/counter/counter-sysfs.c > >> > >> > >> Not sure why sysfs changes are in the chrdev patch. Are these > >> changes related somehow? > > > > Sorry, I forgot to explain this in the cover letter. The changes here > > are only useful for the character device interface. These changes > > introduce the extensionZ_name and extensionZ_width sysfs attributes. > > > > In the character device interface, extensions are selected by their id > > number, and the value returned depends on the type of data. The new > > sysfs attributes introduced here allow users to match the id of an > > extension with its name, as well as the bit width of the value returned > > so that the user knows whether to use the value_u8 or value_u64 union > > member in struct counter_event. > > > > Are we sure that all value types will always be CPU-endian unsigned > integers? Or should we make an enum to describe the data type instead > of just the width? It should be safe to assume that the character device interface will only ever return CPU-endian unsigned integers. The device driver should handle the conversion of any strange endianness from the device before the character device interface, while userspace is the one responsible for interpreting the meaning of count in the context of the application. Let's create a scenario for the sake of example. Suppose we want to use a counter device to track the vertical position of a component moved by a linear actuator. The operator considers some vertical position as the horizon, where anything above would be a positive position and anything below a negative position. The counter device stores its count in big-endian format; but the system CPU expects little-endian. The flow of data for this scenario would look like the following (where BE = big-endian, LE = little-endian): +----------+ +---------------+ +--------+ | Raw data | - BE -> | Device driver | -> LE -> | chrdev | - u64 -> +----------+ +---------------+ +--------+ At this point, the userspace application would read the unsigned integer from the character device and determine how to interpret the position -- whether the count be converted to a signed value to represent a negative physical position. Whether or not a position should be considered negative is dependent on the user application and context. Because the character device does not know the context of the user application, it should only provide unsigned integers in order to ensure a standard interface for counter devices; userspace will be responsible for interpreting those counts to something meaningful for the context of their applications. William Breathitt Gray
On Tue, Oct 20, 2020 at 11:06:42AM -0500, David Lechner wrote: > On 10/18/20 11:58 AM, William Breathitt Gray wrote: > > On Wed, Oct 14, 2020 at 05:40:44PM -0500, David Lechner wrote: > >> On 9/26/20 9:18 PM, William Breathitt Gray wrote: > >>> +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 long flags; > >>> + unsigned int copied; > >>> + > >>> + 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)); > >>> + if (err) > >>> + return err; > >>> + } > >>> + > >>> + raw_spin_lock_irqsave(&counter->events_lock, flags); > >>> + err = kfifo_to_user(&counter->events, buf, len, &copied); > >>> + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > >>> + if (err) > >>> + return err; > >>> + } while (!copied); > >>> + > >>> + return copied; > >>> +} > >> > >> All other uses of kfifo_to_user() I saw use a mutex instead of spin > >> lock. I don't see a reason for disabling interrupts here. > > > > The Counter character device interface is special in this case because > > counter->events could be accessed from an interrupt context. This is > > possible if counter_push_event() is called for an interrupt (as is the > > case for the 104_quad_8 driver). In this case, we can't use mutex > > because we can't sleep in an interrupt context, so our only option is to > > use spin lock. > > > > > The way I understand it, locking is only needed for concurrent readers > and locking between reader and writer is not needed. You're right, it does say in the kfifo.h comments that with only one concurrent reader and one current write, we don't need extra locking to use these macros. Because we only have one kfifo_to_user() operating on counter->events, does that mean we don't need locking at all here for the counter_chrdev_read() function? William Breathitt Gray
On 10/25/20 8:18 AM, William Breathitt Gray wrote: > On Tue, Oct 20, 2020 at 11:06:42AM -0500, David Lechner wrote: >> On 10/18/20 11:58 AM, William Breathitt Gray wrote: >>> On Wed, Oct 14, 2020 at 05:40:44PM -0500, David Lechner wrote: >>>> On 9/26/20 9:18 PM, William Breathitt Gray wrote: >>>>> +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 long flags; >>>>> + unsigned int copied; >>>>> + >>>>> + 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)); >>>>> + if (err) >>>>> + return err; >>>>> + } >>>>> + >>>>> + raw_spin_lock_irqsave(&counter->events_lock, flags); >>>>> + err = kfifo_to_user(&counter->events, buf, len, &copied); >>>>> + raw_spin_unlock_irqrestore(&counter->events_lock, flags); >>>>> + if (err) >>>>> + return err; >>>>> + } while (!copied); >>>>> + >>>>> + return copied; >>>>> +} >>>> >>>> All other uses of kfifo_to_user() I saw use a mutex instead of spin >>>> lock. I don't see a reason for disabling interrupts here. >>> >>> The Counter character device interface is special in this case because >>> counter->events could be accessed from an interrupt context. This is >>> possible if counter_push_event() is called for an interrupt (as is the >>> case for the 104_quad_8 driver). In this case, we can't use mutex >>> because we can't sleep in an interrupt context, so our only option is to >>> use spin lock. >>> >> >> >> The way I understand it, locking is only needed for concurrent readers >> and locking between reader and writer is not needed. > > You're right, it does say in the kfifo.h comments that with only one > concurrent reader and one current write, we don't need extra locking to > use these macros. Because we only have one kfifo_to_user() operating on > counter->events, does that mean we don't need locking at all here for > the counter_chrdev_read() function? > > William Breathitt Gray > Even if we have the policy that only one file handle to the chrdev can be open at a time, it is still possible that the it could be read from multiple threads. So it I think it makes sense to keep it just to be safe.
>>>>> diff --git a/drivers/counter/counter-sysfs.c b/drivers/counter/counter-sysfs.c >>>>> index e66ed99dd5ea..cefef61f170d 100644 >>>>> --- a/drivers/counter/counter-sysfs.c >>>>> +++ b/drivers/counter/counter-sysfs.c >>>> >>>> >>>> Not sure why sysfs changes are in the chrdev patch. Are these >>>> changes related somehow? >>> >>> Sorry, I forgot to explain this in the cover letter. The changes here >>> are only useful for the character device interface. These changes >>> introduce the extensionZ_name and extensionZ_width sysfs attributes. >>> >>> In the character device interface, extensions are selected by their id >>> number, and the value returned depends on the type of data. The new >>> sysfs attributes introduced here allow users to match the id of an >>> extension with its name, as well as the bit width of the value returned >>> so that the user knows whether to use the value_u8 or value_u64 union >>> member in struct counter_event. >>> >> >> Are we sure that all value types will always be CPU-endian unsigned >> integers? Or should we make an enum to describe the data type instead >> of just the width? > > It should be safe to assume that the character device interface will > only ever return CPU-endian unsigned integers. The device driver should > handle the conversion of any strange endianness from the device before > the character device interface, while userspace is the one responsible > for interpreting the meaning of count in the context of the application. > > Let's create a scenario for the sake of example. Suppose we want to use > a counter device to track the vertical position of a component moved by > a linear actuator. The operator considers some vertical position as the > horizon, where anything above would be a positive position and anything > below a negative position. The counter device stores its count in > big-endian format; but the system CPU expects little-endian. > > The flow of data for this scenario would look like the following (where > BE = big-endian, LE = little-endian): > > +----------+ +---------------+ +--------+ > | Raw data | - BE -> | Device driver | -> LE -> | chrdev | - u64 -> > +----------+ +---------------+ +--------+ > > At this point, the userspace application would read the unsigned integer > from the character device and determine how to interpret the position -- > whether the count be converted to a signed value to represent a negative > physical position. > > Whether or not a position should be considered negative is dependent on > the user application and context. Because the character device does not > know the context of the user application, it should only provide > unsigned integers in order to ensure a standard interface for counter > devices; userspace will be responsible for interpreting those counts to > something meaningful for the context of their applications. > > William Breathitt Gray > Sounds reasonable to me.
On Sun, Oct 25, 2020 at 11:34:43AM -0500, David Lechner wrote: > On 10/25/20 8:18 AM, William Breathitt Gray wrote: > > On Tue, Oct 20, 2020 at 11:06:42AM -0500, David Lechner wrote: > >> On 10/18/20 11:58 AM, William Breathitt Gray wrote: > >>> On Wed, Oct 14, 2020 at 05:40:44PM -0500, David Lechner wrote: > >>>> On 9/26/20 9:18 PM, William Breathitt Gray wrote: > >>>>> +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 long flags; > >>>>> + unsigned int copied; > >>>>> + > >>>>> + 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)); > >>>>> + if (err) > >>>>> + return err; > >>>>> + } > >>>>> + > >>>>> + raw_spin_lock_irqsave(&counter->events_lock, flags); > >>>>> + err = kfifo_to_user(&counter->events, buf, len, &copied); > >>>>> + raw_spin_unlock_irqrestore(&counter->events_lock, flags); > >>>>> + if (err) > >>>>> + return err; > >>>>> + } while (!copied); > >>>>> + > >>>>> + return copied; > >>>>> +} > >>>> > >>>> All other uses of kfifo_to_user() I saw use a mutex instead of spin > >>>> lock. I don't see a reason for disabling interrupts here. > >>> > >>> The Counter character device interface is special in this case because > >>> counter->events could be accessed from an interrupt context. This is > >>> possible if counter_push_event() is called for an interrupt (as is the > >>> case for the 104_quad_8 driver). In this case, we can't use mutex > >>> because we can't sleep in an interrupt context, so our only option is to > >>> use spin lock. > >>> > >> > >> > >> The way I understand it, locking is only needed for concurrent readers > >> and locking between reader and writer is not needed. > > > > You're right, it does say in the kfifo.h comments that with only one > > concurrent reader and one current write, we don't need extra locking to > > use these macros. Because we only have one kfifo_to_user() operating on > > counter->events, does that mean we don't need locking at all here for > > the counter_chrdev_read() function? > > > > William Breathitt Gray > > > > Even if we have the policy that only one file handle to the chrdev > can be open at a time, it is still possible that the it could be > read from multiple threads. So it I think it makes sense to keep > it just to be safe. All right, I'll keep the locks in the code for now to keep it safe in case we have multiple threads reading. William Breathitt Gray
diff --git a/MAINTAINERS b/MAINTAINERS index 4969e997061a..de6685a683c8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4459,6 +4459,7 @@ F: Documentation/ABI/testing/sysfs-bus-counter* F: Documentation/driver-api/generic-counter.rst F: drivers/counter/ F: include/linux/counter.h +F: include/uapi/linux/counter.h CPMAC ETHERNET DRIVER M: Florian Fainelli <f.fainelli@gmail.com> diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile index cbe1d06af6a9..c4870eb5b1dd 100644 --- a/drivers/counter/Makefile +++ b/drivers/counter/Makefile @@ -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_STM32_TIMER_CNT) += stm32-timer-cnt.o diff --git a/drivers/counter/counter-chrdev.c b/drivers/counter/counter-chrdev.c new file mode 100644 index 000000000000..2be3846e4105 --- /dev/null +++ b/drivers/counter/counter-chrdev.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic Counter character device interface + * Copyright (C) 2020 William Breathitt Gray + */ + +#include <linux/cdev.h> +#include <linux/counter.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/fs.h> +#include <linux/list.h> +#include <linux/poll.h> +#include <linux/kdev_t.h> +#include <linux/kfifo.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/timekeeping.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/uaccess.h> + +#include "counter-chrdev.h" + +struct counter_comp_node { + struct list_head l; + struct counter_component component; + struct counter_comp comp; + void *parent; +}; + +struct counter_event_node { + struct list_head l; + u8 event; + u8 channel; + struct list_head comp_list; +}; + +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 long flags; + unsigned int copied; + + 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)); + if (err) + return err; + } + + raw_spin_lock_irqsave(&counter->events_lock, flags); + err = kfifo_to_user(&counter->events, buf, len, &copied); + raw_spin_unlock_irqrestore(&counter->events_lock, flags); + if (err) + 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; + + 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; + 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_ATOMIC); + 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 && + comp_node->comp.count_u8_read == cfg->comp.count_u8_read) + return -EINVAL; + + /* Allocate component node */ + comp_node = kmalloc(sizeof(*comp_node), GFP_ATOMIC); + if (!comp_node) { + err = -ENOMEM; + goto err_comp_node; + } + *comp_node = *cfg; + + /* Add component node to event node */ + list_add_tail(&comp_node->l, &event_node->comp_list); + + return 0; + +err_comp_node: + if (list_empty(&event_node->comp_list)) { + list_del(&event_node->l); + kfree(event_node); + } + return err; +} + +static int counter_set_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; + + if (copy_from_user(&watch, uwatch, sizeof(watch))) + return -EFAULT; + parent = watch.component.parent; + id = watch.component.id; + + /* Configure parent component info for comp node */ + switch (watch.component.scope) { + case COUNTER_SCOPE_DEVICE: + comp_node.parent = NULL; + + ext = counter->ext; + num_ext = counter->num_ext; + break; + case COUNTER_SCOPE_SIGNAL: + if (counter->num_signals < parent + 1) + return -EINVAL; + + comp_node.parent = counter->signals + parent; + + ext = counter->signals[parent].ext; + num_ext = counter->signals[parent].num_ext; + break; + case COUNTER_SCOPE_COUNT: + if (counter->num_counts < parent + 1) + return -EINVAL; + + comp_node.parent = counter->counts + parent; + + ext = counter->counts[parent].ext; + num_ext = counter->counts[parent].num_ext; + break; + default: + return -EINVAL; + } + + /* 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_u8_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_u8_read = counter->ops->function_read; + break; + case COUNTER_COMPONENT_SYNAPSE_ACTION: + if (watch.component.scope != COUNTER_SCOPE_COUNT) + return -EINVAL; + if (counter->counts[parent].num_synapses < id + 1) + return -EINVAL; + + 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 (num_ext < id + 1) + return -EINVAL; + + comp_node.comp = ext[id]; + break; + default: + return -EINVAL; + } + if (!comp_node.comp.count_u8_read) + return -EFAULT; + comp_node.component = watch.component; + + return counter_set_event_node(counter, &watch, &comp_node); +} + +static long counter_chrdev_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct counter_device *const counter = filp->private_data; + raw_spinlock_t *const events_lock = &counter->events_lock; + unsigned long flags; + struct list_head *const events_list = &counter->events_list; + struct list_head *const next_events_list = &counter->next_events_list; + + switch (cmd) { + case COUNTER_CLEAR_WATCHES_IOCTL: + raw_spin_lock_irqsave(events_lock, flags); + counter_events_list_free(events_list); + raw_spin_unlock_irqrestore(events_lock, flags); + counter_events_list_free(next_events_list); + break; + case COUNTER_SET_WATCH_IOCTL: + return counter_set_watch(counter, arg); + case COUNTER_LOAD_WATCHES_IOCTL: + raw_spin_lock_irqsave(events_lock, flags); + counter_events_list_free(events_list); + list_replace_init(next_events_list, events_list); + raw_spin_unlock_irqrestore(events_lock, flags); + break; + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +static int counter_chrdev_open(struct inode *inode, struct file *filp) +{ + struct counter_device *const counter = container_of(inode->i_cdev, + typeof(*counter), + chrdev); + + 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; + unsigned long flags; + + put_device(&counter->dev); + + raw_spin_lock_irqsave(&counter->events_lock, flags); + counter_events_list_free(&counter->events_list); + raw_spin_unlock_irqrestore(&counter->events_lock, flags); + counter_events_list_free(&counter->next_events_list); + + return 0; +} + +static const struct file_operations counter_fops = { + .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, + const dev_t counter_devt) +{ + struct device *const dev = &counter->dev; + struct cdev *const chrdev = &counter->chrdev; + + /* Initialize Counter events lists */ + INIT_LIST_HEAD(&counter->events_list); + INIT_LIST_HEAD(&counter->next_events_list); + raw_spin_lock_init(&counter->events_lock); + + /* Initialize Counter events queue */ + INIT_KFIFO(counter->events); + init_waitqueue_head(&counter->events_wait); + + /* Initialize character device */ + cdev_init(chrdev, &counter_fops); + dev->devt = MKDEV(MAJOR(counter_devt), counter->id); + cdev_set_parent(chrdev, &dev->kobj); + + return cdev_add(chrdev, dev->devt, 1); +} + +void counter_chrdev_remove(struct counter_device *const counter) +{ + cdev_del(&counter->chrdev); +} + +static int counter_get_data(struct counter_device *const counter, + const struct counter_comp_node *const comp_node, + void *const value) +{ + const struct counter_comp *const comp = &comp_node->comp; + void *const parent = comp_node->parent; + + switch (comp->type) { + case COUNTER_COMP_U8: + case COUNTER_COMP_BOOL: + 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: + return comp->device_u8_read(counter, value); + case COUNTER_SCOPE_SIGNAL: + return comp->signal_u8_read(counter, parent, value); + case COUNTER_SCOPE_COUNT: + return comp->count_u8_read(counter, parent, value); + } + break; + 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); + } + break; + case COUNTER_COMP_SYNAPSE_ACTION: + return comp->action_read(counter, parent, comp->priv, value); + } + + return 0; +} + +/** + * 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. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int counter_push_event(struct counter_device *const counter, const u8 event, + const u8 channel) +{ + struct counter_event ev = {0}; + unsigned int copied = 0; + unsigned long flags; + struct counter_event_node *event_node; + struct counter_comp_node *comp_node; + int err; + + ev.timestamp = ktime_get_ns(); + ev.watch.event = event; + ev.watch.channel = channel; + + raw_spin_lock_irqsave(&counter->events_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) { + err = counter_get_data(counter, comp_node, &ev.value_u8); + if (err) + goto err_counter_get_data; + + ev.watch.component = comp_node->component; + + copied += kfifo_put(&counter->events, ev); + } + + if (copied) + wake_up_poll(&counter->events_wait, EPOLLIN); + +exit_early: + raw_spin_unlock_irqrestore(&counter->events_lock, flags); + + return 0; + +err_counter_get_data: + raw_spin_unlock_irqrestore(&counter->events_lock, flags); + return err; +} +EXPORT_SYMBOL_GPL(counter_push_event); diff --git a/drivers/counter/counter-chrdev.h b/drivers/counter/counter-chrdev.h new file mode 100644 index 000000000000..cf5a318fe540 --- /dev/null +++ b/drivers/counter/counter-chrdev.h @@ -0,0 +1,16 @@ +/* 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> +#include <linux/types.h> + +int counter_chrdev_add(struct counter_device *const counter, + const dev_t counter_devt); +void counter_chrdev_remove(struct counter_device *const counter); + +#endif /* _COUNTER_CHRDEV_H_ */ diff --git a/drivers/counter/counter-core.c b/drivers/counter/counter-core.c index 987c6e8277eb..e9c01de36fcf 100644 --- a/drivers/counter/counter-core.c +++ b/drivers/counter/counter-core.c @@ -5,12 +5,16 @@ */ #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/module.h> +#include <linux/types.h> +#include "counter-chrdev.h" #include "counter-sysfs.h" /* Provides a unique ID for each counter device */ @@ -32,6 +36,8 @@ static struct bus_type counter_bus_type = { .name = "counter" }; +static dev_t counter_devt; + /** * counter_register - register Counter to the system * @counter: pointer to Counter to register @@ -50,7 +56,6 @@ int counter_register(struct counter_device *const counter) if (counter->id < 0) return counter->id; - /* Configure device structure for Counter */ dev->type = &counter_device_type; dev->bus = &counter_bus_type; if (counter->parent) { @@ -61,20 +66,27 @@ int counter_register(struct counter_device *const counter) device_initialize(dev); dev_set_drvdata(dev, counter); + /* Add Counter character device */ + err = counter_chrdev_add(counter, counter_devt); + if (err) + goto err_free_id; + /* Add Counter sysfs attributes */ err = counter_sysfs_add(counter); if (err) - goto err_free_id; + goto err_remove_chrdev; /* Add device to system */ err = device_add(dev); if (err) { put_device(dev); - goto err_free_id; + goto err_remove_chrdev; } return 0; +err_remove_chrdev: + counter_chrdev_remove(counter); err_free_id: /* get_device/put_device combo used to free managed resources */ get_device(dev); @@ -96,6 +108,7 @@ void counter_unregister(struct counter_device *const counter) return; device_unregister(&counter->dev); + counter_chrdev_remove(counter); } EXPORT_SYMBOL_GPL(counter_unregister); @@ -142,13 +155,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); } diff --git a/drivers/counter/counter-sysfs.c b/drivers/counter/counter-sysfs.c index e66ed99dd5ea..cefef61f170d 100644 --- a/drivers/counter/counter-sysfs.c +++ b/drivers/counter/counter-sysfs.c @@ -434,6 +434,7 @@ static ssize_t counter_comp_name_show(struct device *dev, static int counter_name_attr_create(struct device *const dev, struct counter_attribute_group *const group, + const char *const attr_name, const char *const name) { struct counter_attribute *counter_attr; @@ -448,7 +449,7 @@ static int counter_name_attr_create(struct device *const dev, /* Configure device attribute */ sysfs_attr_init(&counter_attr->dev_attr.attr); - counter_attr->dev_attr.attr.name = "name"; + counter_attr->dev_attr.attr.name = attr_name; counter_attr->dev_attr.attr.mode = 0444; counter_attr->dev_attr.show = counter_comp_name_show; @@ -459,6 +460,76 @@ static int counter_name_attr_create(struct device *const dev, return 0; } +static ssize_t counter_comp_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + switch (to_counter_attribute(attr)->comp.type) { + case COUNTER_COMP_U8: + case COUNTER_COMP_BOOL: + case COUNTER_COMP_SIGNAL_LEVEL: + case COUNTER_COMP_FUNCTION: + case COUNTER_COMP_SYNAPSE_ACTION: + case COUNTER_COMP_ENUM: + case COUNTER_COMP_COUNT_DIRECTION: + case COUNTER_COMP_COUNT_MODE: + return sprintf(buf, "8\n"); + case COUNTER_COMP_U64: + return sprintf(buf, "64\n"); + } + + return 0; +} + +static int counter_ext_width_attr_create(struct device *const dev, + struct counter_attribute_group *const group, const size_t i, + const struct counter_comp *const ext) +{ + struct counter_attribute *counter_attr; + const char *name; + + /* Allocate Counter attribute */ + counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL); + if (!counter_attr) + return -ENOMEM; + + /* Generate attribute name */ + name = devm_kasprintf(dev, GFP_KERNEL, "extension%zu_width", i); + if (!name) + return -ENOMEM; + + /* Configure Counter attribute */ + counter_attr->comp.type = ext->type; + + /* Configure device attribute */ + sysfs_attr_init(&counter_attr->dev_attr.attr); + counter_attr->dev_attr.attr.name = name; + counter_attr->dev_attr.attr.mode = 0444; + counter_attr->dev_attr.show = counter_comp_width_show; + + /* Store list node */ + list_add(&counter_attr->l, &group->attr_list); + group->num_attr++; + + return 0; +} + +static int counter_ext_meta_attrs_create(struct device *const dev, + struct counter_attribute_group *const group, const size_t i, + const struct counter_comp *const ext) +{ + const char *attr_name; + int err; + + attr_name = devm_kasprintf(dev, GFP_KERNEL, "extension%zu_name", i); + if (!attr_name) + return -ENOMEM; + + err = counter_name_attr_create(dev, group, attr_name, ext->name); + if (err) + return err; + + return counter_ext_width_attr_create(dev, group, i, ext); +} static struct counter_comp counter_signal_comp = { .type = COUNTER_COMP_SIGNAL_LEVEL, @@ -474,6 +545,7 @@ static int counter_signal_attrs_create(struct counter_device *const counter, int err; struct counter_comp comp; size_t i; + struct counter_comp *ext; /* Create main Signal attribute */ comp = counter_signal_comp; @@ -483,14 +555,19 @@ static int counter_signal_attrs_create(struct counter_device *const counter, return err; /* Create Signal name attribute */ - err = counter_name_attr_create(dev, group, signal->name); + err = counter_name_attr_create(dev, group, "name", signal->name); if (err) return err; /* Create an attribute for each extension */ for (i = 0; i < signal->num_ext; i++) { - err = counter_attr_create(dev, group, signal->ext + i, scope, - signal); + ext = signal->ext + i; + + err = counter_attr_create(dev, group, ext, scope, signal); + if (err) + return err; + + err = counter_ext_meta_attrs_create(dev, group, i, ext); if (err) return err; } @@ -575,6 +652,7 @@ static int counter_count_attrs_create(struct counter_device *const counter, int err; struct counter_comp comp; size_t i; + struct counter_comp *ext; /* Create main Count attribute */ comp = counter_count_comp; @@ -585,7 +663,7 @@ static int counter_count_attrs_create(struct counter_device *const counter, return err; /* Create Count name attribute */ - err = counter_name_attr_create(dev, group, count->name); + err = counter_name_attr_create(dev, group, "name", count->name); if (err) return err; @@ -599,8 +677,13 @@ static int counter_count_attrs_create(struct counter_device *const counter, /* Create an attribute for each extension */ for (i = 0; i < count->num_ext; i++) { - err = counter_attr_create(dev, group, count->ext + i, scope, - count); + ext = count->ext + i; + + err = counter_attr_create(dev, group, ext, scope, count); + if (err) + return err; + + err = counter_ext_meta_attrs_create(dev, group, i, ext); if (err) return err; } @@ -664,6 +747,7 @@ static int counter_device_register(struct counter_device *const counter, struct device *const dev = &counter->dev; int err; size_t i; + struct counter_comp *ext; /* Register Signals */ err = counter_signals_register(counter, group); @@ -678,7 +762,7 @@ static int counter_device_register(struct counter_device *const counter, group += counter->num_counts; /* Create name attribute */ - err = counter_name_attr_create(dev, group, counter->name); + err = counter_name_attr_create(dev, group, "name", counter->name); if (err) return err; @@ -696,8 +780,13 @@ static int counter_device_register(struct counter_device *const counter, /* Create an attribute for each extension */ for (i = 0; i < counter->num_ext; i++) { - err = counter_attr_create(dev, group, counter->ext + i, scope, - NULL); + ext = counter->ext + i; + + err = counter_attr_create(dev, group, ext, scope, NULL); + if (err) + return err; + + err = counter_ext_meta_attrs_create(dev, group, i, ext); if (err) return err; } diff --git a/include/linux/counter.h b/include/linux/counter.h index 132bfecca5c3..25d79047c9fc 100644 --- a/include/linux/counter.h +++ b/include/linux/counter.h @@ -6,10 +6,15 @@ #ifndef _COUNTER_H_ #define _COUNTER_H_ +#include <linux/cdev.h> #include <linux/device.h> #include <linux/kernel.h> +#include <linux/kfifo.h> #include <linux/list.h> +#include <linux/spinlock_types.h> #include <linux/types.h> +#include <linux/wait.h> +#include <uapi/linux/counter.h> struct counter_device; struct counter_count; @@ -28,35 +33,6 @@ enum counter_comp_type { COUNTER_COMP_COUNT_MODE, }; -#define COUNTER_SCOPE_DEVICE 0 -#define COUNTER_SCOPE_SIGNAL 1 -#define COUNTER_SCOPE_COUNT 2 - -#define COUNTER_COUNT_DIRECTION_FORWARD 0 -#define COUNTER_COUNT_DIRECTION_BACKWARD 1 - -#define COUNTER_COUNT_MODE_NORMAL 0 -#define COUNTER_COUNT_MODE_RANGE_LIMIT 1 -#define COUNTER_COUNT_MODE_NON_RECYCLE 2 -#define COUNTER_COUNT_MODE_MODULO_N 3 - -#define COUNTER_FUNCTION_INCREASE 0 -#define COUNTER_FUNCTION_DECREASE 1 -#define COUNTER_FUNCTION_PULSE_DIRECTION 2 -#define COUNTER_FUNCTION_QUADRATURE_X1_A 3 -#define COUNTER_FUNCTION_QUADRATURE_X1_B 4 -#define COUNTER_FUNCTION_QUADRATURE_X2_A 5 -#define COUNTER_FUNCTION_QUADRATURE_X2_B 6 -#define COUNTER_FUNCTION_QUADRATURE_X4 7 - -#define COUNTER_SIGNAL_LEVEL_LOW 0 -#define COUNTER_SIGNAL_LEVEL_HIGH 1 - -#define COUNTER_SYNAPSE_ACTION_NONE 0 -#define COUNTER_SYNAPSE_ACTION_RISING_EDGE 1 -#define COUNTER_SYNAPSE_ACTION_FALLING_EDGE 2 -#define COUNTER_SYNAPSE_ACTION_BOTH_EDGES 3 - struct counter_comp { enum counter_comp_type type; const char *name; @@ -205,6 +181,12 @@ struct counter_ops { * @priv: optional private data supplied by driver * @id: unique ID used to identify the Counter * @dev: internal device structure + * @chrdev: internal character device structure + * @events_lock: synchronization lock for Counter events + * @events_list: list of current watching Counter events + * @next_events_list: list of next watching Counter events + * @events: queue of detected Counter events + * @events_wait: wait queue to allow blocking reads of Counter events */ struct counter_device { const char *name; @@ -224,6 +206,12 @@ struct counter_device { int id; struct device dev; + struct cdev chrdev; + raw_spinlock_t events_lock; + struct list_head events_list; + struct list_head next_events_list; + DECLARE_KFIFO(events, struct counter_event, 64); + wait_queue_head_t events_wait; }; int counter_register(struct counter_device *const counter); @@ -232,6 +220,8 @@ int devm_counter_register(struct device *dev, struct counter_device *const counter); void devm_counter_unregister(struct device *dev, struct counter_device *const counter); +int counter_push_event(struct counter_device *const counter, const u8 event, + const u8 channel); #define COUNTER_COMP_DEVICE_U8(_name, _read, _write) \ { \ diff --git a/include/uapi/linux/counter.h b/include/uapi/linux/counter.h new file mode 100644 index 000000000000..ad9a8686b2b0 --- /dev/null +++ b/include/uapi/linux/counter.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Userspace ABI for Counter character devices + * Copyright (C) 2020 William Breathitt Gray + */ +#ifndef _UAPI_COUNTER_H_ +#define _UAPI_COUNTER_H_ + +#include <linux/ioctl.h> +#include <linux/types.h> + +#define COUNTER_SCOPE_DEVICE 0 +#define COUNTER_SCOPE_SIGNAL 1 +#define COUNTER_SCOPE_COUNT 2 + +#define COUNTER_COMPONENT_SIGNAL 0 +#define COUNTER_COMPONENT_COUNT 1 +#define COUNTER_COMPONENT_FUNCTION 2 +#define COUNTER_COMPONENT_SYNAPSE_ACTION 3 +#define COUNTER_COMPONENT_EXTENSION 4 + +/** + * struct counter_component - Counter component identification + * @scope: component scope (Device, Count, or Signal) + * @parent: parent component identification number + * @type: component type (Count, extension, etc.) + * @id: component identification number + */ +struct counter_component { + __u8 scope; + __u64 parent; + __u8 type; + __u64 id; +}; + +#define COUNTER_EVENT_OVERFLOW 0 +#define COUNTER_EVENT_UNDERFLOW 1 +#define COUNTER_EVENT_OVERFLOW_UNDERFLOW 2 +#define COUNTER_EVENT_THRESHOLD 3 +#define COUNTER_EVENT_INDEX 4 + +/** + * struct counter_watch - Counter component watch configuration + * @event: event that triggers + * @channel: event channel + * @component: component to watch when event triggers + */ +struct counter_watch { + __u8 event; + __u8 channel; + struct counter_component component; +}; + +#define COUNTER_CLEAR_WATCHES_IOCTL _IO(0x3E, 0x00) +#define COUNTER_SET_WATCH_IOCTL _IOW(0x3E, 0x01, struct counter_watch) +#define COUNTER_LOAD_WATCHES_IOCTL _IO(0x3E, 0x02) + +/** + * struct counter_event - Counter event data + * @timestamp: best estimate of time of event occurrence, in nanoseconds + * @watch: component watch configuration + * @value_u8: component value as __u8 data type + * @value_u64: component value as __u64 data type + */ +struct counter_event { + __u64 timestamp; + struct counter_watch watch; + union { + __u8 value_u8; + __u64 value_u64; + }; +}; + +#define COUNTER_COUNT_DIRECTION_FORWARD 0 +#define COUNTER_COUNT_DIRECTION_BACKWARD 1 + +#define COUNTER_COUNT_MODE_NORMAL 0 +#define COUNTER_COUNT_MODE_RANGE_LIMIT 1 +#define COUNTER_COUNT_MODE_NON_RECYCLE 2 +#define COUNTER_COUNT_MODE_MODULO_N 3 + +#define COUNTER_FUNCTION_INCREASE 0 +#define COUNTER_FUNCTION_DECREASE 1 +#define COUNTER_FUNCTION_PULSE_DIRECTION 2 +#define COUNTER_FUNCTION_QUADRATURE_X1_A 3 +#define COUNTER_FUNCTION_QUADRATURE_X1_B 4 +#define COUNTER_FUNCTION_QUADRATURE_X2_A 5 +#define COUNTER_FUNCTION_QUADRATURE_X2_B 6 +#define COUNTER_FUNCTION_QUADRATURE_X4 7 + +#define COUNTER_SIGNAL_LEVEL_LOW 0 +#define COUNTER_SIGNAL_LEVEL_HIGH 1 + +#define COUNTER_SYNAPSE_ACTION_NONE 0 +#define COUNTER_SYNAPSE_ACTION_RISING_EDGE 1 +#define COUNTER_SYNAPSE_ACTION_FALLING_EDGE 2 +#define COUNTER_SYNAPSE_ACTION_BOTH_EDGES 3 + +#endif /* _UAPI_COUNTER_H_ */
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. A high-level view of how a count value is passed down from a counter driver is exemplified by the following. The driver callbacks are first registered to the Counter core component for use by the Counter userspace interface components: +----------------------------+ | Counter device driver | +----------------------------+ | Processes data from device | +----------------------------+ | ------------------- / driver callbacks / ------------------- | V +----------------------+ | Counter core | +----------------------+ | Routes device driver | | callbacks to the | | userspace interfaces | +----------------------+ | ------------------- / driver callbacks / ------------------- | +---------------+---------------+ | | V V +--------------------+ +---------------------+ | Counter sysfs | | Counter chrdev | +--------------------+ +---------------------+ | Translates to the | | Translates to the | | standard Counter | | standard Counter | | sysfs output | | character device | +--------------------+ +---------------------+ Thereafter, data can be transferred directly between the Counter device driver and Counter userspace interface: ---------------------- / Counter device \ +----------------------+ | Count register: 0x28 | +----------------------+ | ----------------- / raw count data / ----------------- | V +----------------------------+ | Counter device driver | +----------------------------+ | Processes data from device | |----------------------------| | Type: u64 | | Value: 42 | +----------------------------+ | ---------- / u64 / ---------- | +---------------+---------------+ | | V V +--------------------+ +---------------------+ | Counter sysfs | | Counter chrdev | +--------------------+ +---------------------+ | Translates to the | | Translates to the | | standard Counter | | standard Counter | | sysfs output | | character device | |--------------------| |---------------------| | Type: const char * | | Type: u64 | | Value: "42" | | Value: 42 | +--------------------+ +---------------------+ | | --------------- ----------------------- / const char * / / struct counter_event / --------------- ----------------------- | | | V | +-----------+ | | read | | +-----------+ | \ Count: 42 / | ----------- | V +--------------------------------------------------+ | `/sys/bus/counter/devices/counterX/countY/count` | +--------------------------------------------------+ \ Count: "42" / -------------------------------------------------- Counter character device nodes are created under the `/dev` directory as `counterX`, where `X` is the respective counter device id. Defines for the standard Counter data types are exposed via the userspace `include/uapi/linux/counter.h` file. Counter events -------------- Counter device drivers can support Counter events by utilizing the `counter_push_event` function: int counter_push_event(struct counter_device *const counter, const u8 event, const u8 channel); The event id is specified by the `event` parameter; the event channel id is specified by the `channel` parameter. When this function is called, the Counter data associated with the respective event is gathered, and a `struct counter_event` is generated for each datum and pushed to userspace. Counter events can be configured by users to report various Counter data of interest. This can be conceptualized as a list of Counter component read calls to perform. For example: +~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~+ | COUNTER_EVENT_OVERFLOW | COUNTER_EVENT_INDEX | +~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~+ | Channel 0 | Channel 0 | +------------------------+------------------------+ | * Count 0 | * Signal 0 | | * Count 1 | * Signal 0 Extension 0 | | * Signal 3 | * Extension 4 | | * Count 4 Extension 2 +------------------------+ | * Signal 5 Extension 0 | Channel 1 | | +------------------------+ | | * Signal 4 | | | * Signal 4 Extension 0 | | | * Count 7 | +------------------------+------------------------+ When `counter_push_event(counter, COUNTER_EVENT_INDEX, 1)` is called for example, it will go down the list for the `COUNTER_EVENT_INDEX` event channel 1 and execute the read callbacks for Signal 4, Signal 4 Extension 0, and Count 4 -- the data returned for each is pushed to a kfifo as a `struct counter_event`, which userspace can retrieve via a standard read operation on the respective character device node. Userspace --------- Userspace applications can configure Counter events via ioctl operations on the Counter character device node. There following ioctl codes are supported and provided by the `linux/counter.h` userspace header file: * COUNTER_CLEAR_WATCHES_IOCTL: Clear all Counter watches from all events * COUNTER_SET_WATCH_IOCTL: Set a Counter watch for the specified event * COUNTER_LOAD_WATCHES_IOCTL: Activates the Counter watches set earlier To configure events to gather Counter data, users first populate a `struct counter_watch` with the relevant event id, event channel id, and the information for the desired Counter component from which to read, and then pass it via the `COUNTER_SET_WATCH_IOCTL` ioctl command. The `COUNTER_SET_WATCH_IOCTL` command will buffer these Counter watches. When ready, the `COUNTER_LOAD_WATCHES_IOCTL` ioctl command may be used to activate these Counter watches. Userspace applications can then execute a `read` operation (optionally calling `poll` first) on the Counter character device node to retrieve `struct counter_event` elements with the desired data. For example, the following userspace code opens `/dev/counter0`, configures the `COUNTER_EVENT_INDEX` event channel 0 to gather Count 0 and Count 1, and prints out the data as it becomes available on the character device node: #include <fcntl.h> #include <linux/counter.h> #include <poll.h> #include <stdio.h> #include <sys/ioctl.h> #include <unistd.h> struct counter_watch watches[2] = { { .event = COUNTER_EVENT_INDEX, .channel = 0, .component.scope = COUNTER_SCOPE_COUNT, .component.parent = 0, .component.type = COUNTER_COMPONENT_COUNT, }, { .event = COUNTER_EVENT_INDEX, .channel = 0, .component.scope = COUNTER_SCOPE_COUNT, .component.parent = 1, .component.type = COUNTER_COMPONENT_COUNT, }, }; int main(void) { struct pollfd pfd = { .events = POLLIN }; struct counter_event event_data[2]; pfd.fd = open("/dev/counter0", O_RDWR); ioctl(pfd.fd, COUNTER_SET_WATCH_IOCTL, watches); ioctl(pfd.fd, COUNTER_SET_WATCH_IOCTL, watches + 1); ioctl(pfd.fd, COUNTER_LOAD_WATCHES_IOCTL); for (;;) { poll(&pfd, 1, -1); read(pfd.fd, event_data, sizeof(event_data)); printf("Timestamp 0: %llu\nCount 0: %llu\n" "Timestamp 1: %llu\nCount 1: %llu\n", (unsigned long long)event_data[0].timestamp, (unsigned long long)event_data[0].value_u64, (unsigned long long)event_data[1].timestamp, (unsigned long long)event_data[1].value_u64); } return 0; } Cc: David Lechner <david@lechnology.com> Cc: Gwendal Grignou <gwendal@chromium.org> Signed-off-by: William Breathitt Gray <vilhelm.gray@gmail.com> --- MAINTAINERS | 1 + drivers/counter/Makefile | 2 +- drivers/counter/counter-chrdev.c | 451 +++++++++++++++++++++++++++++++ drivers/counter/counter-chrdev.h | 16 ++ drivers/counter/counter-core.c | 38 ++- drivers/counter/counter-sysfs.c | 109 +++++++- include/linux/counter.h | 48 ++-- include/uapi/linux/counter.h | 99 +++++++ 8 files changed, 720 insertions(+), 44 deletions(-) create mode 100644 drivers/counter/counter-chrdev.c create mode 100644 drivers/counter/counter-chrdev.h create mode 100644 include/uapi/linux/counter.h