Commit b6c50aff authored by William Breathitt Gray's avatar William Breathitt Gray Committed by Jonathan Cameron

counter: Add character device interface

This patch introduces a character device interface for the Counter
subsystem. Device data is exposed through standard character device read
operations. Device data is gathered when a Counter event is pushed by
the respective Counter device driver. Configuration is handled via ioctl
operations on the respective Counter character device node.

Cc: David Lechner <david@lechnology.com>
Cc: Gwendal Grignou <gwendal@chromium.org>
Cc: Dan Carpenter <dan.carpenter@oracle.com>
Cc: Oleksij Rempel <o.rempel@pengutronix.de>
Signed-off-by: default avatarWilliam Breathitt Gray <vilhelm.gray@gmail.com>
Link: https://lore.kernel.org/r/b8b8c64b4065aedff43699ad1f0e2f8d1419c15b.1632884256.git.vilhelm.gray@gmail.comSigned-off-by: default avatarJonathan Cameron <Jonathan.Cameron@huawei.com>
parent e65c26f4
......@@ -4,7 +4,7 @@
#
obj-$(CONFIG_COUNTER) += counter.o
counter-y := counter-core.o counter-sysfs.o
counter-y := counter-core.o counter-sysfs.o counter-chrdev.o
obj-$(CONFIG_104_QUAD_8) += 104-quad-8.o
obj-$(CONFIG_INTERRUPT_CNT) += interrupt-cnt.o
......
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Counter character device interface
* Copyright (C) 2020 William Breathitt Gray
*/
#ifndef _COUNTER_CHRDEV_H_
#define _COUNTER_CHRDEV_H_
#include <linux/counter.h>
int counter_chrdev_add(struct counter_device *const counter);
void counter_chrdev_remove(struct counter_device *const counter);
#endif /* _COUNTER_CHRDEV_H_ */
......@@ -3,14 +3,22 @@
* Generic Counter interface
* Copyright (C) 2020 William Breathitt Gray
*/
#include <linux/cdev.h>
#include <linux/counter.h>
#include <linux/device.h>
#include <linux/device/bus.h>
#include <linux/export.h>
#include <linux/fs.h>
#include <linux/gfp.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/kdev_t.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/wait.h>
#include "counter-chrdev.h"
#include "counter-sysfs.h"
/* Provides a unique ID for each counter device */
......@@ -18,6 +26,9 @@ static DEFINE_IDA(counter_ida);
static void counter_device_release(struct device *dev)
{
struct counter_device *const counter = dev_get_drvdata(dev);
counter_chrdev_remove(counter);
ida_free(&counter_ida, dev->id);
}
......@@ -31,6 +42,8 @@ static struct bus_type counter_bus_type = {
.dev_name = "counter",
};
static dev_t counter_devt;
/**
* counter_register - register Counter to the system
* @counter: pointer to Counter to register
......@@ -53,10 +66,13 @@ int counter_register(struct counter_device *const counter)
if (id < 0)
return id;
mutex_init(&counter->ops_exist_lock);
/* Configure device structure for Counter */
dev->id = id;
dev->type = &counter_device_type;
dev->bus = &counter_bus_type;
dev->devt = MKDEV(MAJOR(counter_devt), id);
if (counter->parent) {
dev->parent = counter->parent;
dev->of_node = counter->parent->of_node;
......@@ -64,18 +80,22 @@ int counter_register(struct counter_device *const counter)
device_initialize(dev);
dev_set_drvdata(dev, counter);
/* Add Counter sysfs attributes */
err = counter_sysfs_add(counter);
if (err < 0)
goto err_free_id;
/* Add device to system */
err = device_add(dev);
err = counter_chrdev_add(counter);
if (err < 0)
goto err_free_id;
err = cdev_device_add(&counter->chrdev, dev);
if (err < 0)
goto err_remove_chrdev;
return 0;
err_remove_chrdev:
counter_chrdev_remove(counter);
err_free_id:
put_device(dev);
return err;
......@@ -93,7 +113,16 @@ void counter_unregister(struct counter_device *const counter)
if (!counter)
return;
device_unregister(&counter->dev);
cdev_device_del(&counter->chrdev, &counter->dev);
mutex_lock(&counter->ops_exist_lock);
counter->ops = NULL;
wake_up(&counter->events_wait);
mutex_unlock(&counter->ops_exist_lock);
put_device(&counter->dev);
}
EXPORT_SYMBOL_GPL(counter_unregister);
......@@ -127,13 +156,30 @@ int devm_counter_register(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_counter_register);
#define COUNTER_DEV_MAX 256
static int __init counter_init(void)
{
return bus_register(&counter_bus_type);
int err;
err = bus_register(&counter_bus_type);
if (err < 0)
return err;
err = alloc_chrdev_region(&counter_devt, 0, COUNTER_DEV_MAX, "counter");
if (err < 0)
goto err_unregister_bus;
return 0;
err_unregister_bus:
bus_unregister(&counter_bus_type);
return err;
}
static void __exit counter_exit(void)
{
unregister_chrdev_region(counter_devt, COUNTER_DEV_MAX);
bus_unregister(&counter_bus_type);
}
......
......@@ -6,9 +6,14 @@
#ifndef _COUNTER_H_
#define _COUNTER_H_
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/kfifo.h>
#include <linux/mutex.h>
#include <linux/spinlock_types.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <uapi/linux/counter.h>
struct counter_device;
......@@ -199,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: optional read callback for Signals. The read level of
......@@ -222,6 +241,13 @@ struct counter_count {
* @action_write: optional write callback for Synapse action modes. The
* action mode to write for the respective Synapse is
* passed in via the action parameter.
* @events_configure: optional write callback to configure events. The list of
* struct counter_event_node may be accessed via the
* events_list member of the counter parameter.
* @watch_validate: optional callback to validate a watch. The Counter
* component watch configuration is passed in via the watch
* parameter. A return value of 0 indicates a valid Counter
* component watch configuration.
*/
struct counter_ops {
int (*signal_read)(struct counter_device *counter,
......@@ -245,6 +271,9 @@ struct counter_ops {
struct counter_count *count,
struct counter_synapse *synapse,
enum counter_synapse_action action);
int (*events_configure)(struct counter_device *counter);
int (*watch_validate)(struct counter_device *counter,
const struct counter_watch *watch);
};
/**
......@@ -260,6 +289,16 @@ struct counter_ops {
* @num_ext: number of Counter device extensions specified in @ext
* @priv: optional private data supplied by driver
* @dev: internal device structure
* @chrdev: internal character device structure
* @events_list: list of current watching Counter events
* @events_list_lock: lock to protect Counter events list operations
* @next_events_list: list of next watching Counter events
* @n_events_list_lock: lock to protect Counter next events list operations
* @events: queue of detected Counter events
* @events_wait: wait queue to allow blocking reads of Counter events
* @events_lock: lock to protect Counter events queue read operations
* @chrdev_lock: lock to limit chrdev to a single open at a time
* @ops_exist_lock: lock to prevent use during removal
*/
struct counter_device {
const char *name;
......@@ -278,12 +317,29 @@ struct counter_device {
void *priv;
struct device dev;
struct cdev chrdev;
struct list_head events_list;
spinlock_t events_list_lock;
struct list_head next_events_list;
struct mutex n_events_list_lock;
DECLARE_KFIFO_PTR(events, struct counter_event);
wait_queue_head_t events_wait;
struct mutex events_lock;
/*
* chrdev_lock is locked by counter_chrdev_open() and unlocked by
* counter_chrdev_release(), so a mutex is not possible here because
* chrdev_lock will invariably be held when returning to user space
*/
atomic_t chrdev_lock;
struct mutex ops_exist_lock;
};
int counter_register(struct counter_device *const counter);
void counter_unregister(struct counter_device *const counter);
int devm_counter_register(struct device *dev,
struct counter_device *const counter);
void counter_push_event(struct counter_device *const counter, const u8 event,
const u8 channel);
#define COUNTER_COMP_DEVICE_U8(_name, _read, _write) \
{ \
......
......@@ -6,6 +6,19 @@
#ifndef _UAPI_COUNTER_H_
#define _UAPI_COUNTER_H_
#include <linux/ioctl.h>
#include <linux/types.h>
/* Component type definitions */
enum counter_component_type {
COUNTER_COMPONENT_NONE,
COUNTER_COMPONENT_SIGNAL,
COUNTER_COMPONENT_COUNT,
COUNTER_COMPONENT_FUNCTION,
COUNTER_COMPONENT_SYNAPSE_ACTION,
COUNTER_COMPONENT_EXTENSION,
};
/* Component scope definitions */
enum counter_scope {
COUNTER_SCOPE_DEVICE,
......@@ -13,6 +26,91 @@ enum counter_scope {
COUNTER_SCOPE_COUNT,
};
/**
* struct counter_component - Counter component identification
* @type: component type (one of enum counter_component_type)
* @scope: component scope (one of enum counter_scope)
* @parent: parent ID (matching the ID suffix of the respective parent sysfs
* path as described by the ABI documentation file
* Documentation/ABI/testing/sysfs-bus-counter)
* @id: component ID (matching the ID provided by the respective *_component_id
* sysfs attribute of the desired component)
*
* For example, if the Count 2 ceiling extension of Counter device 4 is desired,
* set type equal to COUNTER_COMPONENT_EXTENSION, scope equal to
* COUNTER_COUNT_SCOPE, parent equal to 2, and id equal to the value provided by
* the respective /sys/bus/counter/devices/counter4/count2/ceiling_component_id
* sysfs attribute.
*/
struct counter_component {
__u8 type;
__u8 scope;
__u8 parent;
__u8 id;
};
/* Event type definitions */
enum counter_event_type {
/* Count value increased past ceiling */
COUNTER_EVENT_OVERFLOW,
/* Count value decreased past floor */
COUNTER_EVENT_UNDERFLOW,
/* Count value increased past ceiling, or decreased past floor */
COUNTER_EVENT_OVERFLOW_UNDERFLOW,
/* Count value reached threshold */
COUNTER_EVENT_THRESHOLD,
/* Index signal detected */
COUNTER_EVENT_INDEX,
};
/**
* struct counter_watch - Counter component watch configuration
* @component: component to watch when event triggers
* @event: event that triggers (one of enum counter_event_type)
* @channel: event channel (typically 0 unless the device supports concurrent
* events of the same type)
*/
struct counter_watch {
struct counter_component component;
__u8 event;
__u8 channel;
};
/*
* Queues a Counter watch for the specified event.
*
* The queued watches will not be applied until COUNTER_ENABLE_EVENTS_IOCTL is
* called.
*/
#define COUNTER_ADD_WATCH_IOCTL _IOW(0x3E, 0x00, struct counter_watch)
/*
* Enables monitoring the events specified by the Counter watches that were
* queued by COUNTER_ADD_WATCH_IOCTL.
*
* If events are already enabled, the new set of watches replaces the old one.
* Calling this ioctl also has the effect of clearing the queue of watches added
* by COUNTER_ADD_WATCH_IOCTL.
*/
#define COUNTER_ENABLE_EVENTS_IOCTL _IO(0x3E, 0x01)
/*
* Stops monitoring the previously enabled events.
*/
#define COUNTER_DISABLE_EVENTS_IOCTL _IO(0x3E, 0x02)
/**
* struct counter_event - Counter event data
* @timestamp: best estimate of time of event occurrence, in nanoseconds
* @value: component value
* @watch: component watch configuration
* @status: return status (system error number)
*/
struct counter_event {
__aligned_u64 timestamp;
__aligned_u64 value;
struct counter_watch watch;
__u8 status;
};
/* Count direction values */
enum counter_count_direction {
COUNTER_COUNT_DIRECTION_FORWARD,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment