From patchwork Sat Dec 26 00:15:36 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: William Breathitt Gray X-Patchwork-Id: 11990225 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.7 required=3.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED,DKIM_SIGNED,DKIM_VALID,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EA59CC433E9 for ; Sat, 26 Dec 2020 00:17:56 +0000 (UTC) Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id A5100207AD for ; Sat, 26 Dec 2020 00:17:56 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org A5100207AD Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=merlin.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:Cc:List-Subscribe:List-Help:List-Post:List-Archive: List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To:Message-Id:Date: Subject:To:From:Reply-To:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=cuLmR4WSpHIcbYGpvddyzXrSgG4I8+3KX8bJgO3Jv8o=; b=hGdfcIaoPPiL7eRR+90TQHiSJ OMfUOL8mTB1dAbfLh1FHTAsH40LZnADgLsC8Q0cTRWtE84itqhvRVTCODuC0JGE8QwP7gv7pyDHTX Buugv7mpqjuefgCthUQ8XQys3HweOmd1UUMzGxCMQTsOdTCnsz3qC7AMbYKcrfEk626Pf2HoRsZs1 MKIKDljYsNELBWQb8au6Gtom/YY+hxzWSlJUFxYkNbC/VxvxMrj3ya8HFQB/0CW163EYHY5BqHdPS 4uYoXTUi8B/JioM8AYeBoXss8kpHp5Xn5RX3cEc1TbcVW3pLrqMviguG/n0lALCjaO0NCzmIsQDtC Y146IYjtQ==; Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1ksxFb-00035W-3n; Sat, 26 Dec 2020 00:16:03 +0000 Received: from mail-qt1-x831.google.com ([2607:f8b0:4864:20::831]) by merlin.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1ksxFO-00033K-K2 for linux-arm-kernel@lists.infradead.org; Sat, 26 Dec 2020 00:15:57 +0000 Received: by mail-qt1-x831.google.com with SMTP id z20so3648525qtq.3 for ; Fri, 25 Dec 2020 16:15:49 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=maG8Vo6vY76syppH9bsMrHrBimLjyeyjZ1ikqnb14tI=; b=Yle9U7PSm+2PkQi86e+tNoRkK5YfITx+0gMelBQCp0iM8wSiTWEpqZeU8SSi3PoCvK 8MTa6g4wqGjW4QuJGIPJjr417pf1CmoU2RHgzDS/jTuAL94zw21z8dRU5lPR/sgPlLK+ 6f9P0IvGgSyOLLG1WoQ1ZDB3w3RIQPIm+BFw5R6YBySlCpYd7xN0OTCU4Chk4p8+tQPW g9UQ4OZkYzO2wK/9IxJqCAAA4slEvkv8xyBPUUjpV9LPjbY1frw+hAa7+vyNidexB4dg PnECYHAKAh8+N4zb6D5OIAUVHzbQjm5YfLNUJzJhhmWqUdCYBoJNcLel2P9uIplemGHi Ng5Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=maG8Vo6vY76syppH9bsMrHrBimLjyeyjZ1ikqnb14tI=; b=ovKtcKk5sCKOf1Z5nvy7FHdiQf6A43mXJUkdbid1xKL69ynyHi44O7Q4uTx3hNY1wU Ho4JusQ1v7kUcV4B1N7xaA+5c+4mvsEDT0I2pEvRnh0srLFuybCljQz56CGcmb1ua8dn pWz8DmwOdvC3DKcO5noBDcGoR7QGkmLwdYBnchejT5mvxnDrv+ktAa1V9WUeJv3tTSad 58S8zx+hfnF15iH6xYQn6S+TN6aHQNUih2SeedrEvwjZYCOWSKzPTENO3Dm6mzeexkwj 84UnkdN7WbUDG2o6ChPMjNR0HMgMLE1OxEBpDRifHbsSmGouvVWL7y/qIVdvPjTmExbx qbug== X-Gm-Message-State: AOAM5306MfR5WeQ6UtZdD/nz9Vn3BHXClBK+h1JDyN2BqalYMHf8BJ5V x2xazEBdNNMp8laXdRY07Cs= X-Google-Smtp-Source: ABdhPJwI4w3EPhH7FCOrhIXHAVJTArztXdXwWlo7CScTAxTzZKJ9fGKvpGvT/FFBh2+bwUvJ9IdeUA== X-Received: by 2002:ac8:74c7:: with SMTP id j7mr34550565qtr.102.1608941747946; Fri, 25 Dec 2020 16:15:47 -0800 (PST) Received: from localhost.localdomain (072-189-064-225.res.spectrum.com. [72.189.64.225]) by smtp.gmail.com with ESMTPSA id b14sm18349966qtx.36.2020.12.25.16.15.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 25 Dec 2020 16:15:47 -0800 (PST) From: William Breathitt Gray To: jic23@kernel.org Subject: [PATCH v7 3/5] counter: Add character device interface Date: Fri, 25 Dec 2020 19:15:36 -0500 Message-Id: <57bc509273bf288d74835e6ebdaebf27b4991888.1608935587.git.vilhelm.gray@gmail.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: References: MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20201225_191550_810557_19A20DEF X-CRM114-Status: GOOD ( 29.29 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kamel.bouhara@bootlin.com, gwendal@chromium.org, a.fatoum@pengutronix.de, david@lechnology.com, linux-iio@vger.kernel.org, patrick.havelange@essensium.com, alexandre.belloni@bootlin.com, mcoquelin.stm32@gmail.com, linux-kernel@vger.kernel.org, Dan Carpenter , kernel@pengutronix.de, William Breathitt Gray , fabrice.gasnier@st.com, syednwaris@gmail.com, linux-stm32@st-md-mailman.stormreply.com, linux-arm-kernel@lists.infradead.org, alexandre.torgue@st.com Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org This patch introduces a character device interface for the Counter subsystem. Device data is exposed through standard character device read operations. Device data is gathered when a Counter event is pushed by the respective Counter device driver. Configuration is handled via ioctl operations on the respective Counter character device node. Cc: David Lechner Cc: Gwendal Grignou Cc: Dan Carpenter Signed-off-by: William Breathitt Gray --- MAINTAINERS | 1 + drivers/counter/Makefile | 2 +- drivers/counter/counter-chrdev.c | 490 +++++++++++++++++++++++++++++++ drivers/counter/counter-chrdev.h | 16 + drivers/counter/counter-core.c | 37 ++- drivers/counter/counter-sysfs.c | 51 +++- include/linux/counter.h | 87 +++--- include/uapi/linux/counter.h | 123 ++++++++ 8 files changed, 751 insertions(+), 56 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 diff --git a/MAINTAINERS b/MAINTAINERS index b64fa49d5796..3a240f70bdd4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4494,6 +4494,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 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..61b11989546a --- /dev/null +++ b/drivers/counter/counter-chrdev.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic Counter character device interface + * Copyright (C) 2020 William Breathitt Gray + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "counter-chrdev.h" + +struct counter_comp_node { + struct list_head l; + struct counter_component component; + struct counter_comp comp; + void *parent; +}; + +static ssize_t counter_chrdev_read(struct file *filp, char __user *buf, + size_t len, loff_t *f_ps) +{ + struct counter_device *const counter = filp->private_data; + int err; + unsigned int copied; + + if (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 < 0) + return err; + } + + if (mutex_lock_interruptible(&counter->events_lock)) + return -ERESTARTSYS; + err = kfifo_to_user(&counter->events, buf, len, &copied); + mutex_unlock(&counter->events_lock); + if (err < 0) + return err; + } while (!copied); + + return copied; +} + +static __poll_t counter_chrdev_poll(struct file *filp, + struct poll_table_struct *pollt) +{ + struct counter_device *const counter = filp->private_data; + __poll_t events = 0; + + 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; + 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) { + /* Free event node if no one else is watching */ + if (list_empty(&event_node->comp_list)) { + list_del(&event_node->l); + kfree(event_node); + } + return -ENOMEM; + } + *comp_node = *cfg; + + /* Add component node to event node */ + list_add_tail(&comp_node->l, &event_node->comp_list); + + return 0; +} + +static int counter_clear_watches(struct counter_device *const counter) +{ + unsigned long flags; + int err = 0; + + raw_spin_lock_irqsave(&counter->events_list_lock, flags); + + counter_events_list_free(&counter->events_list); + + if (counter->ops->events_configure) + err = counter->ops->events_configure(counter); + + raw_spin_unlock_irqrestore(&counter->events_list_lock, flags); + + counter_events_list_free(&counter->next_events_list); + + return err; +} + +static int counter_add_watch(struct counter_device *const counter, + const unsigned long arg) +{ + void __user *const uwatch = (void __user *)arg; + struct counter_watch watch; + struct counter_comp_node comp_node = {0}; + size_t parent, id; + struct counter_comp *ext; + size_t num_ext; + int err; + + if (copy_from_user(&watch, uwatch, sizeof(watch))) + return -EFAULT; + + if (watch.component.type == COUNTER_COMPONENT_NONE) + goto no_component; + + parent = watch.component.parent; + + /* Configure parent component info for comp node */ + switch (watch.component.scope) { + case COUNTER_SCOPE_DEVICE: + ext = counter->ext; + num_ext = counter->num_ext; + break; + case COUNTER_SCOPE_SIGNAL: + if (parent >= counter->num_signals) + return -EINVAL; + parent = array_index_nospec(parent, counter->num_signals); + + comp_node.parent = counter->signals + parent; + + ext = counter->signals[parent].ext; + num_ext = counter->signals[parent].num_ext; + break; + case COUNTER_SCOPE_COUNT: + if (parent >= counter->num_counts) + return -EINVAL; + parent = array_index_nospec(parent, counter->num_counts); + + comp_node.parent = counter->counts + parent; + + ext = counter->counts[parent].ext; + num_ext = counter->counts[parent].num_ext; + break; + } + + id = watch.component.id; + + /* Configure component info for comp node */ + switch (watch.component.type) { + case COUNTER_COMPONENT_SIGNAL: + if (watch.component.scope != COUNTER_SCOPE_SIGNAL) + return -EINVAL; + + comp_node.comp.type = COUNTER_COMP_SIGNAL_LEVEL; + comp_node.comp.signal_u32_read = counter->ops->signal_read; + break; + case COUNTER_COMPONENT_COUNT: + if (watch.component.scope != COUNTER_SCOPE_COUNT) + return -EINVAL; + + comp_node.comp.type = COUNTER_COMP_U64; + comp_node.comp.count_u64_read = counter->ops->count_read; + break; + case COUNTER_COMPONENT_FUNCTION: + if (watch.component.scope != COUNTER_SCOPE_COUNT) + return -EINVAL; + + comp_node.comp.type = COUNTER_COMP_FUNCTION; + comp_node.comp.count_u32_read = counter->ops->function_read; + break; + case COUNTER_COMPONENT_SYNAPSE_ACTION: + if (watch.component.scope != COUNTER_SCOPE_COUNT) + return -EINVAL; + if (id >= counter->counts[parent].num_synapses) + return -EINVAL; + id = array_index_nospec(id, counter->counts[parent].num_synapses); + + comp_node.comp.type = COUNTER_COMP_SYNAPSE_ACTION; + comp_node.comp.action_read = counter->ops->action_read; + comp_node.comp.priv = counter->counts[parent].synapses + id; + break; + case COUNTER_COMPONENT_EXTENSION: + if (id >= num_ext) + return -EINVAL; + id = array_index_nospec(id, num_ext); + + comp_node.comp = ext[id]; + break; + default: + return -EINVAL; + } + /* Check if any read callback is set; this is part of a union */ + if (!comp_node.comp.count_u8_read) + return -EOPNOTSUPP; + +no_component: + if (counter->ops->watch_validate) { + err = counter->ops->watch_validate(counter, &watch); + if (err < 0) + return err; + } + + 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; + unsigned long flags; + int err = 0; + + switch (cmd) { + case COUNTER_CLEAR_WATCHES_IOCTL: + return counter_clear_watches(counter); + case COUNTER_ADD_WATCH_IOCTL: + return counter_add_watch(counter, arg); + case COUNTER_LOAD_WATCHES_IOCTL: + raw_spin_lock_irqsave(&counter->events_list_lock, flags); + + counter_events_list_free(&counter->events_list); + list_replace_init(&counter->next_events_list, + &counter->events_list); + + if (counter->ops->events_configure) + err = counter->ops->events_configure(counter); + + raw_spin_unlock_irqrestore(&counter->events_list_lock, flags); + break; + default: + return -ENOIOCTLCMD; + } + + return err; +} + +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; + int err; + + err = counter_clear_watches(counter); + if (err < 0) + return err; + + put_device(&counter->dev); + + 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_list_lock); + + /* Initialize Counter events queue */ + INIT_KFIFO(counter->events); + init_waitqueue_head(&counter->events_wait); + mutex_init(&counter->events_lock); + + /* 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, + u64 *const value) +{ + const struct counter_comp *const comp = &comp_node->comp; + void *const parent = comp_node->parent; + int err = 0; + u8 value_u8 = 0; + u32 value_u32 = 0; + + if (comp_node->component.type == COUNTER_COMPONENT_NONE) + return 0; + + switch (comp->type) { + case COUNTER_COMP_U8: + case COUNTER_COMP_BOOL: + switch (comp_node->component.scope) { + case COUNTER_SCOPE_DEVICE: + err = comp->device_u8_read(counter, &value_u8); + break; + case COUNTER_SCOPE_SIGNAL: + err = comp->signal_u8_read(counter, parent, &value_u8); + break; + case COUNTER_SCOPE_COUNT: + err = comp->count_u8_read(counter, parent, &value_u8); + break; + } + *value = value_u8; + break; + 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: + err = comp->device_u32_read(counter, &value_u32); + break; + case COUNTER_SCOPE_SIGNAL: + err = comp->signal_u32_read(counter, parent, + &value_u32); + break; + case COUNTER_SCOPE_COUNT: + err = comp->count_u32_read(counter, parent, &value_u32); + break; + } + *value = value_u32; + 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: + err = comp->action_read(counter, parent, comp->priv, + &value_u32); + *value = value_u32; + break; + } + + return err; +} + +/** + * counter_push_event - queue event for userspace reading + * @counter: pointer to Counter structure + * @event: triggered event + * @channel: event channel + * + * Note: If no one is watching for the respective event, it is silently + * discarded. + */ +void counter_push_event(struct counter_device *const counter, const u8 event, + const u8 channel) +{ + struct counter_event ev = {0}; + unsigned int copied = 0; + unsigned long flags; + struct counter_event_node *event_node; + struct counter_comp_node *comp_node; + + ev.timestamp = ktime_get_ns(); + ev.watch.event = event; + ev.watch.channel = channel; + + raw_spin_lock_irqsave(&counter->events_list_lock, flags); + + /* Search for event in the list */ + list_for_each_entry(event_node, &counter->events_list, l) + if (event_node->event == event && + event_node->channel == channel) + break; + + /* If event is not in the list */ + if (&event_node->l == &counter->events_list) + goto exit_early; + + /* Read and queue relevant comp for userspace */ + list_for_each_entry(comp_node, &event_node->comp_list, l) { + ev.watch.component = comp_node->component; + ev.errno = -counter_get_data(counter, comp_node, &ev.value); + + copied += kfifo_put(&counter->events, ev); + } + + if (copied) + wake_up_poll(&counter->events_wait, EPOLLIN); + +exit_early: + raw_spin_unlock_irqrestore(&counter->events_list_lock, flags); +} +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 +#include + +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 78e07588717b..1334e2f4ce4e 100644 --- a/drivers/counter/counter-core.c +++ b/drivers/counter/counter-core.c @@ -5,12 +5,16 @@ */ #include #include +#include #include +#include #include #include #include #include +#include +#include "counter-chrdev.h" #include "counter-sysfs.h" /* Provides a unique ID for each counter device */ @@ -33,6 +37,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 @@ -51,7 +57,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) { @@ -62,18 +67,25 @@ 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 < 0) - goto err_free_id; + goto err_remove_chrdev; /* Add device to system */ err = device_add(dev); if (err < 0) - goto err_free_id; + goto err_remove_chrdev; return 0; +err_remove_chrdev: + counter_chrdev_remove(counter); err_free_id: put_device(dev); return err; @@ -135,13 +147,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 654afa91ae9f..4d97d6e50d48 100644 --- a/drivers/counter/counter-sysfs.c +++ b/drivers/counter/counter-sysfs.c @@ -499,6 +499,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; @@ -513,7 +514,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; @@ -524,6 +525,18 @@ static int counter_name_attr_create(struct device *const dev, return 0; } +static int counter_ext_name_attr_create(struct device *const dev, + struct counter_attribute_group *const group, const size_t i, + const char *const name) +{ + const char *attr_name; + + attr_name = devm_kasprintf(dev, GFP_KERNEL, "extension%zu_name", i); + if (!attr_name) + return -ENOMEM; + + return counter_name_attr_create(dev, group, attr_name, name); +} static struct counter_comp counter_signal_comp = { .type = COUNTER_COMP_SIGNAL_LEVEL, @@ -539,6 +552,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; @@ -548,14 +562,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 < 0) 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 < 0) + return err; + + err = counter_ext_name_attr_create(dev, group, i, ext->name); if (err < 0) return err; } @@ -640,6 +659,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; @@ -650,7 +670,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 < 0) return err; @@ -664,8 +684,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 < 0) + return err; + + err = counter_ext_name_attr_create(dev, group, i, ext->name); if (err < 0) return err; } @@ -729,6 +754,7 @@ static int counter_sysfs_attr_add(struct counter_device *const counter, struct device *const dev = &counter->dev; int err; size_t i; + struct counter_comp *ext; /* Add Signals sysfs attributes */ err = counter_sysfs_signals_add(counter, group); @@ -743,7 +769,7 @@ static int counter_sysfs_attr_add(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 < 0) return err; @@ -761,8 +787,13 @@ static int counter_sysfs_attr_add(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 < 0) + return err; + + err = counter_ext_name_attr_create(dev, group, i, ext->name); if (err < 0) return err; } diff --git a/include/linux/counter.h b/include/linux/counter.h index 2f01e1fec857..ce79e62a2e58 100644 --- a/include/linux/counter.h +++ b/include/linux/counter.h @@ -6,9 +6,15 @@ #ifndef _COUNTER_H_ #define _COUNTER_H_ +#include #include #include +#include +#include +#include #include +#include +#include struct counter_device; struct counter_count; @@ -27,47 +33,6 @@ enum counter_comp_type { COUNTER_COMP_COUNT_MODE, }; -enum counter_scope { - COUNTER_SCOPE_DEVICE, - COUNTER_SCOPE_SIGNAL, - COUNTER_SCOPE_COUNT, -}; - -enum counter_count_direction { - COUNTER_COUNT_DIRECTION_FORWARD, - COUNTER_COUNT_DIRECTION_BACKWARD, -}; - -enum counter_count_mode { - COUNTER_COUNT_MODE_NORMAL, - COUNTER_COUNT_MODE_RANGE_LIMIT, - COUNTER_COUNT_MODE_NON_RECYCLE, - COUNTER_COUNT_MODE_MODULO_N, -}; - -enum counter_function { - COUNTER_FUNCTION_INCREASE, - COUNTER_FUNCTION_DECREASE, - COUNTER_FUNCTION_PULSE_DIRECTION, - COUNTER_FUNCTION_QUADRATURE_X1_A, - COUNTER_FUNCTION_QUADRATURE_X1_B, - COUNTER_FUNCTION_QUADRATURE_X2_A, - COUNTER_FUNCTION_QUADRATURE_X2_B, - COUNTER_FUNCTION_QUADRATURE_X4, -}; - -enum counter_signal_level { - COUNTER_SIGNAL_LEVEL_LOW, - COUNTER_SIGNAL_LEVEL_HIGH, -}; - -enum counter_synapse_action { - COUNTER_SYNAPSE_ACTION_NONE, - COUNTER_SYNAPSE_ACTION_RISING_EDGE, - COUNTER_SYNAPSE_ACTION_FALLING_EDGE, - COUNTER_SYNAPSE_ACTION_BOTH_EDGES, -}; - /** * struct counter_comp - Counter component node * @type: Counter component data type @@ -239,6 +204,20 @@ struct counter_count { size_t num_ext; }; +/** + * struct counter_event_node - Counter Event node + * @l: list of current watching Counter events + * @event: event that triggers + * @channel: event channel + * @comp_list: list of components to watch when event triggers + */ +struct counter_event_node { + struct list_head l; + u8 event; + u8 channel; + struct list_head comp_list; +}; + /** * struct counter_ops - Callbacks from driver * @signal_read: read callback for Signals. The read level of the @@ -261,6 +240,13 @@ struct counter_count { * @action_write: write callback for Synapse action modes. The action mode * to write for the respective Synapse is passed in via the * action parameter. + * @events_configure: write callback to configure events. The list of struct + * counter_event_node may be accessed via the events_list + * member of the counter parameter. + * @watch_validate: callback to validate a watch. The Counter component + * watch configuration is passed in via the watch + * parameter. A return value of 0 indicates a valid Counter + * component watch configuration. */ struct counter_ops { int (*signal_read)(struct counter_device *counter, @@ -284,6 +270,9 @@ struct counter_ops { struct counter_count *count, struct counter_synapse *synapse, enum counter_synapse_action action); + int (*events_configure)(struct counter_device *counter); + int (*watch_validate)(struct counter_device *counter, + const struct counter_watch *watch); }; /** @@ -300,6 +289,13 @@ 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_list: list of current watching Counter events + * @events_list_lock: lock to protect Counter events list operations + * @next_events_list: list of next watching Counter events + * @events: queue of detected Counter events + * @events_wait: wait queue to allow blocking reads of Counter events + * @events_lock: lock to protect Counter events queue read operations */ struct counter_device { const char *name; @@ -319,12 +315,21 @@ struct counter_device { int id; struct device dev; + struct cdev chrdev; + struct list_head events_list; + raw_spinlock_t events_list_lock; + struct list_head next_events_list; + DECLARE_KFIFO(events, struct counter_event, 64); + wait_queue_head_t events_wait; + struct mutex events_lock; }; int counter_register(struct counter_device *const counter); void counter_unregister(struct counter_device *const counter); int devm_counter_register(struct device *dev, struct counter_device *const counter); +void counter_push_event(struct counter_device *const counter, const u8 event, + const u8 channel); #define COUNTER_COMP_DEVICE_U8(_name, _read, _write) \ { \ diff --git a/include/uapi/linux/counter.h b/include/uapi/linux/counter.h new file mode 100644 index 000000000000..7585dc9db19d --- /dev/null +++ b/include/uapi/linux/counter.h @@ -0,0 +1,123 @@ +/* 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 +#include + +/* Component type definitions */ +enum counter_component_type { + COUNTER_COMPONENT_NONE, + COUNTER_COMPONENT_SIGNAL, + COUNTER_COMPONENT_COUNT, + COUNTER_COMPONENT_FUNCTION, + COUNTER_COMPONENT_SYNAPSE_ACTION, + COUNTER_COMPONENT_EXTENSION, +}; + +/* Component scope definitions */ +enum counter_scope { + COUNTER_SCOPE_DEVICE, + COUNTER_SCOPE_SIGNAL, + COUNTER_SCOPE_COUNT, +}; + +/** + * struct counter_component - Counter component identification + * @type: component type (Count, extension, etc.) + * @scope: component scope (Device, Count, or Signal) + * @parent: parent component identification number + * @id: component identification number + */ +struct counter_component { + __u8 type; + __u8 scope; + __u8 parent; + __u8 id; +}; + +/* Event type definitions */ +enum counter_event_type { + COUNTER_EVENT_OVERFLOW, + COUNTER_EVENT_UNDERFLOW, + COUNTER_EVENT_OVERFLOW_UNDERFLOW, + COUNTER_EVENT_THRESHOLD, + COUNTER_EVENT_INDEX, +}; + +/** + * struct counter_watch - Counter component watch configuration + * @component: component to watch when event triggers + * @event: event that triggers + * @channel: event channel + */ +struct counter_watch { + struct counter_component component; + __u8 event; + __u8 channel; +}; + +/* ioctl commands */ +#define COUNTER_CLEAR_WATCHES_IOCTL _IO(0x3E, 0x00) +#define COUNTER_ADD_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 + * @value: component value + * @watch: component watch configuration + * @errno: system error number + */ +struct counter_event { + __aligned_u64 timestamp; + __aligned_u64 value; + struct counter_watch watch; + __u8 errno; +}; + +/* Count direction values */ +enum counter_count_direction { + COUNTER_COUNT_DIRECTION_FORWARD, + COUNTER_COUNT_DIRECTION_BACKWARD, +}; + +/* Count mode values */ +enum counter_count_mode { + COUNTER_COUNT_MODE_NORMAL, + COUNTER_COUNT_MODE_RANGE_LIMIT, + COUNTER_COUNT_MODE_NON_RECYCLE, + COUNTER_COUNT_MODE_MODULO_N, +}; + +/* Count function values */ +enum counter_function { + COUNTER_FUNCTION_INCREASE, + COUNTER_FUNCTION_DECREASE, + COUNTER_FUNCTION_PULSE_DIRECTION, + COUNTER_FUNCTION_QUADRATURE_X1_A, + COUNTER_FUNCTION_QUADRATURE_X1_B, + COUNTER_FUNCTION_QUADRATURE_X2_A, + COUNTER_FUNCTION_QUADRATURE_X2_B, + COUNTER_FUNCTION_QUADRATURE_X4, +}; + +/* Signal values */ +enum counter_signal_level { + COUNTER_SIGNAL_LEVEL_LOW, + COUNTER_SIGNAL_LEVEL_HIGH, +}; + +/* Action mode values */ +enum counter_synapse_action { + COUNTER_SYNAPSE_ACTION_NONE, + COUNTER_SYNAPSE_ACTION_RISING_EDGE, + COUNTER_SYNAPSE_ACTION_FALLING_EDGE, + COUNTER_SYNAPSE_ACTION_BOTH_EDGES, +}; + +#endif /* _UAPI_COUNTER_H_ */