diff mbox series

[v2,3/4] counter: Add character device interface

Message ID a722cbc1e59441cd477bf42fd0313eb7abd76e38.1589654470.git.vilhelm.gray@gmail.com (mailing list archive)
State New, archived
Headers show
Series Introduce the Counter character device interface | expand

Commit Message

William Breathitt Gray May 16, 2020, 7:20 p.m. UTC
This patch introduces a character device interface for the Counter
subsystem. Device control is exposed through standard character device
read and write operations.

The first 196095 bytes of the character device serve as a control
selection area where control exposure of desired Counter components and
extensions may be selected. Each byte serves as a boolean selection
indicator for a respective Counter component or extension. The format of
this area is as follows:

* For each device extension, a byte is required.
* For each Signal, a byte is reserved for the Signal component, and a
  byte is reserved for each Signal extension.
* For each Count, a byte is reserved for the Count component, a byte is
  reserved for the count function, a byte is reserved for each Synapse
  action, and byte is reserved for each Count extension.

The selected Counter components and extensions may then be interfaced
after the first 196095 bytes via standard character device read/write
operations. The number of bytes available for each component or
extension is dependent on their respective data type: u8 will have 1
byte available, u64 will have 8 bytes available, strings will have 64
bytes available, etc.

A high-level view of how a count value is passed down from a counter
driver can be exemplified by the following:

                 ----------------------
                / Counter device       \
                +----------------------+
                | Count register: 0x28 |
                +----------------------+
                        |
                 -----------------
                / raw count data /
                -----------------
                        |
                        V
                +----------------------------+
                | Counter device driver      |----------+
                +----------------------------+          |
                | Processes data from device |   -------------------
                |----------------------------|  / driver callbacks /
                | Type: u64                  |  -------------------
                | Value: 42                  |          |
                +----------------------------+          |
                        |                               |
                 ----------                             |
                / u64     /                             |
                ----------                              |
                        |                               |
                        |                               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    |
        |--------------------|     |---------------------+
        | Type: const char * |     | Type: u64           |
        | Value: "42"        |     | Value: 42           |
        +--------------------+     +---------------------+
                |                               |
         ---------------                 ----------
        / const char * /                / u64     /
        ---------------                 ----------
                |                               |
                |                               V
                |                       +-----------+
                |                       | read      |
                |                       +-----------+
                |                       \ Count: 42 /
                |                        -----------
                |
                V
        +--------------------------------------------------+
        | `/sys/bus/counter/devices/counterX/countY/count` |
        +--------------------------------------------------+
        \ Count: "42"                                      /
         --------------------------------------------------

Signed-off-by: William Breathitt Gray <vilhelm.gray@gmail.com>
---
 MAINTAINERS                      |   1 +
 drivers/counter/Makefile         |   2 +-
 drivers/counter/counter-chrdev.c | 656 +++++++++++++++++++++++++++++++
 drivers/counter/counter-chrdev.h |  16 +
 drivers/counter/counter-core.c   |  34 +-
 include/linux/counter.h          |  16 +
 6 files changed, 722 insertions(+), 3 deletions(-)
 create mode 100644 drivers/counter/counter-chrdev.c
 create mode 100644 drivers/counter/counter-chrdev.h

Comments

kernel test robot May 20, 2020, 3:07 p.m. UTC | #1
Hi William,

I love your patch! Perhaps something to improve:

[auto build test WARNING on linus/master]
[also build test WARNING on v5.7-rc6 next-20200519]
[cannot apply to stm32/stm32-next linux/master]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/William-Breathitt-Gray/Introduce-the-Counter-character-device-interface/20200517-032530
base:   https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 12bf0b632ed090358cbf03e323e5342212d0b2e4
config: arm64-randconfig-r026-20200519 (attached as .config)
compiler: clang version 11.0.0 (https://github.com/llvm/llvm-project 135b877874fae96b4372c8a3fbfaa8ff44ff86e3)
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install arm64 cross compiling tool for clang build
        # apt-get install binutils-aarch64-linux-gnu
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=arm64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kbuild test robot <lkp@intel.com>

All warnings (new ones prefixed by >>, old ones prefixed by <<):

>> drivers/counter/counter-chrdev.c:632:5: warning: no previous prototype for function 'counter_chrdev_add' [-Wmissing-prototypes]
int counter_chrdev_add(struct counter_device *const counter,
^
drivers/counter/counter-chrdev.c:632:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
int counter_chrdev_add(struct counter_device *const counter,
^
static
>> drivers/counter/counter-chrdev.c:652:6: warning: no previous prototype for function 'counter_chrdev_free' [-Wmissing-prototypes]
void counter_chrdev_free(struct counter_device *const counter)
^
drivers/counter/counter-chrdev.c:652:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
void counter_chrdev_free(struct counter_device *const counter)
^
static
2 warnings generated.

vim +/counter_chrdev_add +632 drivers/counter/counter-chrdev.c

   631	
 > 632	int counter_chrdev_add(struct counter_device *const counter,
   633			       const dev_t counter_devt)
   634	{
   635		struct device *const dev = &counter->dev;
   636		struct cdev *const chrdev = &counter->chrdev;
   637	
   638		/* Initialize Counter character device control selection */
   639		memset(counter->selection, 0, sizeof(counter->selection));
   640	
   641		/* Initialize Counter character device selected controls list */
   642		INIT_LIST_HEAD(&counter->control_list);
   643	
   644		/* Initialize character device */
   645		cdev_init(chrdev, &counter_fops);
   646		dev->devt = MKDEV(MAJOR(counter_devt), counter->id);
   647		cdev_set_parent(chrdev, &dev->kobj);
   648	
   649		return cdev_add(chrdev, dev->devt, 1);
   650	}
   651	
 > 652	void counter_chrdev_free(struct counter_device *const counter)

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index ef72b5755793..150ad8a9bb87 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4365,6 +4365,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
 F:	include/uapi/linux/counter-types.h
 
 CPMAC ETHERNET DRIVER
diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile
index f48e337cbd85..d219b9c058e7 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..7fd55bf71e47
--- /dev/null
+++ b/drivers/counter/counter-chrdev.c
@@ -0,0 +1,656 @@ 
+// 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/fs.h>
+#include <linux/kdev_t.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+
+enum counter_owner_type {
+	COUNTER_OWNER_TYPE_DEVICE,
+	COUNTER_OWNER_TYPE_SIGNAL,
+	COUNTER_OWNER_TYPE_COUNT,
+};
+
+struct counter_control {
+	loff_t offset;
+	size_t size;
+	struct list_head l;
+
+	struct counter_data data;
+	enum counter_owner_type type;
+	void *owner;
+};
+
+static int counter_data_u8_read(struct counter_device *const counter,
+				const struct counter_control *const control,
+				u8 __user *const buf)
+{
+	const struct counter_data *const data = &control->data;
+	int err;
+	u8 val;
+
+	switch (control->type) {
+	case COUNTER_OWNER_TYPE_DEVICE:
+		err = data->device_u8_read(counter, &val);
+		break;
+	case COUNTER_OWNER_TYPE_SIGNAL:
+		err = data->signal_u8_read(counter, control->owner, &val);
+		break;
+	case COUNTER_OWNER_TYPE_COUNT:
+		if (data->type == COUNTER_DATA_TYPE_SYNAPSE_ACTION)
+			err = data->action_read(counter, control->owner,
+						data->priv, &val);
+		else
+			err = data->count_u8_read(counter, control->owner,
+						  &val);
+		break;
+	default: return -EINVAL;
+	}
+	if (err)
+		return err;
+
+	return put_user(val, buf);
+}
+
+static int counter_data_u64_read(struct counter_device *const counter,
+				 const struct counter_control *const control,
+				 char __user *const buf, const size_t len,
+				 const size_t offset)
+{
+	const struct counter_data *const data = &control->data;
+	int err;
+	u64 val;
+
+	switch (control->type) {
+	case COUNTER_OWNER_TYPE_DEVICE:
+		err = data->device_u64_read(counter, &val);
+		break;
+	case COUNTER_OWNER_TYPE_SIGNAL:
+		err = data->signal_u64_read(counter, control->owner, &val);
+		break;
+	case COUNTER_OWNER_TYPE_COUNT:
+		err = data->count_u64_read(counter, control->owner, &val);
+		break;
+	default: return -EINVAL;
+	}
+	if (err)
+		return err;
+
+	return copy_to_user(buf, (const char *)&val + offset, len);
+}
+
+static int counter_data_string_read(struct counter_device *const counter,
+				    const struct counter_control *const control,
+				    char __user *const buf, const size_t len,
+				    const size_t offset)
+{
+	const struct counter_data *const data = &control->data;
+	int err;
+	char str[64] = "";
+
+	switch (control->type) {
+	case COUNTER_OWNER_TYPE_DEVICE:
+		err = data->device_string_read(counter, str, sizeof(str));
+		break;
+	case COUNTER_OWNER_TYPE_SIGNAL:
+		err = data->signal_string_read(counter, control->owner, str,
+					       sizeof(str));
+		break;
+	case COUNTER_OWNER_TYPE_COUNT:
+		err = data->count_string_read(counter, control->owner, str,
+					      sizeof(str));
+		break;
+	default: return -EINVAL;
+	}
+	if (err < 0)
+		return err;
+
+	return copy_to_user(buf, str + offset, len);
+}
+
+static int counter_control_read(struct counter_device *const counter,
+				const struct counter_control *const control,
+				char __user *const buf, const size_t len,
+				const size_t offset)
+{
+	/* Check if read operation is supported */
+	if (!control->data.device_u8_read)
+		return -EFAULT;
+
+	switch (control->data.type) {
+	case COUNTER_DATA_TYPE_U8:
+	case COUNTER_DATA_TYPE_BOOL:
+	case COUNTER_DATA_TYPE_SIGNAL:
+	case COUNTER_DATA_TYPE_COUNT_FUNCTION:
+	case COUNTER_DATA_TYPE_SYNAPSE_ACTION:
+	case COUNTER_DATA_TYPE_COUNT_DIRECTION:
+	case COUNTER_DATA_TYPE_COUNT_MODE:
+		return counter_data_u8_read(counter, control, buf);
+	case COUNTER_DATA_TYPE_U64:
+		return counter_data_u64_read(counter, control, buf, len,
+					     offset);
+	case COUNTER_DATA_TYPE_STRING:
+		return counter_data_string_read(counter, control, buf, len,
+						offset);
+	}
+
+	return -EINVAL;
+}
+
+static ssize_t counter_chrdev_read(struct file *filp, char __user *buf,
+				   size_t len, loff_t *f_ps)
+{
+	const loff_t start_ps = *f_ps;
+	struct counter_device *const counter = filp->private_data;
+	size_t read_size;
+	struct counter_control *control;
+	int err;
+	loff_t next_control_ps;
+
+	/* Handle Counter control selection */
+	if (*f_ps < COUNTER_SELECTION_SIZE) {
+		read_size = (*f_ps + len > COUNTER_SELECTION_SIZE) ?
+			    COUNTER_SELECTION_SIZE - *f_ps : len;
+
+		if (copy_to_user(buf, counter->selection + *f_ps, read_size))
+			return -EFAULT;
+
+		*f_ps += read_size;
+		buf += read_size;
+		len -= read_size;
+		if (!len)
+			goto exit_chrdev_read;
+	}
+
+	/* Handle controls */
+	list_for_each_entry(control, &counter->control_list, l) {
+		next_control_ps = control->offset + control->size;
+
+		/* Find first control item */
+		if (*f_ps >= next_control_ps)
+			continue;
+
+		read_size = (*f_ps + len > next_control_ps) ?
+			    next_control_ps - *f_ps : len;
+
+		err = counter_control_read(counter, control, buf, read_size,
+					   control->offset - *f_ps);
+		if (err)
+			return err;
+
+		*f_ps += read_size;
+		buf += read_size;
+		len -= read_size;
+		if (!len)
+			goto exit_chrdev_read;
+	}
+
+exit_chrdev_read:
+	return *f_ps - start_ps;
+}
+
+static int counter_control_ext_add(const u8 *const selection,
+				   const enum counter_owner_type type,
+				   void *const owner, const size_t num_ext,
+				   const struct counter_data *const ext,
+				   struct list_head *const cntrl_list,
+				   loff_t *const offset)
+{
+	struct counter_control *p = list_last_entry(cntrl_list, typeof(*p), l);
+	struct counter_control *n;
+	size_t i;
+	struct counter_control *control;
+	int err;
+
+	for (i = 0; i < num_ext; i++) {
+		/* Check if extension is selected */
+		if (!selection[i])
+			continue;
+
+		/* Generate control list item */
+		control = kzalloc(sizeof(*control), GFP_KERNEL);
+		if (!control) {
+			err = -ENOMEM;
+			goto err_control_ext_create;
+		}
+		list_add_tail(&control->l, cntrl_list);
+
+		/* Configure control list item */
+		control->data = ext[i],
+		control->type = type;
+		control->owner = owner;
+		control->offset = *offset;
+		switch (control->data.type) {
+		case COUNTER_DATA_TYPE_U8:
+		case COUNTER_DATA_TYPE_BOOL:
+		case COUNTER_DATA_TYPE_COUNT_DIRECTION:
+		case COUNTER_DATA_TYPE_COUNT_MODE:
+			control->size = sizeof(u8);
+			break;
+		case COUNTER_DATA_TYPE_U64:
+			control->size = sizeof(u64);
+			break;
+		case COUNTER_DATA_TYPE_STRING:
+			control->size = 64;
+			break;
+		default:
+			err = -EINVAL;
+			goto err_control_ext_create;
+		}
+		*offset += control->size;
+	}
+
+	return 0;
+
+err_control_ext_create:
+	list_for_each_entry_safe_continue(p, n, cntrl_list, l) {
+		list_del(&p->l);
+		kfree(p);
+	}
+	return err;
+}
+
+static void counter_control_list_free(struct list_head *const cntrl_list)
+{
+	struct counter_control *p, *n;
+
+	list_for_each_entry_safe(p, n, cntrl_list, l) {
+		list_del(&p->l);
+		kfree(p);
+	}
+}
+
+static int counter_control_list_create(struct counter_device *const counter)
+{
+	const u8 *selection = counter->selection;
+	loff_t offset = COUNTER_SELECTION_SIZE;
+	size_t i, j;
+	struct counter_signal *signal;
+	struct counter_count *count;
+	struct counter_control *control;
+	int err;
+
+	/* Clean up old list */
+	counter_control_list_free(&counter->control_list);
+
+	/* Handle device extensions */
+	err = counter_control_ext_add(selection, COUNTER_OWNER_TYPE_DEVICE,
+				      NULL, counter->num_ext, counter->ext,
+				      &counter->control_list, &offset);
+	if (err)
+		goto err_control_create;
+	selection += counter->num_ext;
+
+	/* Handle Signals */
+	for (i = 0; i < counter->num_signals; i++) {
+		signal = counter->signals + i;
+
+		/* Check if Signal is selected */
+		if (*selection++) {
+			/* Generate control list item */
+			control = kzalloc(sizeof(*control), GFP_KERNEL);
+			if (!control) {
+				err = -ENOMEM;
+				goto err_control_create;
+			}
+			list_add_tail(&control->l, &counter->control_list);
+
+			/* Configure control list item */
+			control->data.type = COUNTER_DATA_TYPE_SIGNAL,
+			control->data.signal_u8_read = counter->signal_read;
+			control->type = COUNTER_OWNER_TYPE_SIGNAL;
+			control->owner = signal;
+			control->offset = offset;
+			control->size = sizeof(u8);
+			offset += control->size;
+		}
+
+		/* Handle extensions */
+		err = counter_control_ext_add(selection,
+					      COUNTER_OWNER_TYPE_SIGNAL, signal,
+					      signal->num_ext, signal->ext,
+					      &counter->control_list, &offset);
+		if (err)
+			goto err_control_create;
+		selection += signal->num_ext;
+	}
+
+	/* Handle Counts */
+	for (i = 0; i < counter->num_counts; i++) {
+		count = counter->counts + i;
+
+		/* Check if Count is selected */
+		if (*selection++) {
+			/* Generate control list item */
+			control = kzalloc(sizeof(*control), GFP_KERNEL);
+			if (!control) {
+				err = -ENOMEM;
+				goto err_control_create;
+			}
+			list_add_tail(&control->l, &counter->control_list);
+
+			/* Configure control list item */
+			control->data.type = COUNTER_DATA_TYPE_U64,
+			control->data.count_u64_read = counter->count_read;
+			control->data.count_u64_write = counter->count_write;
+			control->type = COUNTER_OWNER_TYPE_COUNT;
+			control->owner = count;
+			control->offset = offset;
+			control->size = sizeof(u64);
+			offset += control->size;
+		}
+
+		/* Handle count function */
+		if (*selection++) {
+			/* Generate control list item */
+			control = kzalloc(sizeof(*control), GFP_KERNEL);
+			if (!control) {
+				err = -ENOMEM;
+				goto err_control_create;
+			}
+			list_add_tail(&control->l, &counter->control_list);
+
+			/* Configure control list item */
+			control->data.type = COUNTER_DATA_TYPE_COUNT_FUNCTION,
+			control->data.count_u8_read = counter->function_read;
+			control->data.count_u8_write = counter->function_write;
+			control->type = COUNTER_OWNER_TYPE_COUNT;
+			control->owner = count;
+			control->offset = offset;
+			control->size = sizeof(u8);
+			offset += control->size;
+		}
+
+		/* Handle Synapses */
+		for (j = 0; j < count->num_synapses; j++) {
+			/* Check if extension is selected */
+			if (!*selection++)
+				continue;
+
+			/* Generate control list item */
+			control = kzalloc(sizeof(*control), GFP_KERNEL);
+			if (!control) {
+				err = -ENOMEM;
+				goto err_control_create;
+			}
+			list_add_tail(&control->l, &counter->control_list);
+
+			/* Configure control list item */
+			control->data.type = COUNTER_DATA_TYPE_SYNAPSE_ACTION;
+			control->data.action_read = counter->action_read;
+			control->data.action_write = counter->action_write;
+			control->data.priv = count->synapses + j;
+			control->type = COUNTER_OWNER_TYPE_COUNT;
+			control->owner = count;
+			control->offset = offset;
+			control->size = sizeof(u8);
+			offset += control->size;
+		}
+
+		/* Handle extensions */
+		err = counter_control_ext_add(selection,
+					      COUNTER_OWNER_TYPE_COUNT, count,
+					      count->num_ext, count->ext,
+					      &counter->control_list, &offset);
+		if (err)
+			goto err_control_create;
+		selection += count->num_ext;
+	}
+
+	return 0;
+
+err_control_create:
+	counter_control_list_free(&counter->control_list);
+	return err;
+}
+
+static int counter_data_u8_write(struct counter_device *const counter,
+				 const struct counter_control *const control,
+				 const u8 __user *const buf)
+{
+	const struct counter_data *const data = &control->data;
+	int err;
+	u8 val;
+
+	err = get_user(val, buf);
+	if (err)
+		return err;
+
+	switch (control->type) {
+	case COUNTER_OWNER_TYPE_DEVICE:
+		err = data->device_u8_write(counter, val);
+		break;
+	case COUNTER_OWNER_TYPE_SIGNAL:
+		err = data->signal_u8_write(counter, control->owner, val);
+		break;
+	case COUNTER_OWNER_TYPE_COUNT:
+		if (data->type == COUNTER_DATA_TYPE_SYNAPSE_ACTION)
+			err = data->action_write(counter, control->owner,
+						 data->priv, val);
+		else
+			err = data->count_u8_write(counter, control->owner,
+						   val);
+		break;
+	default: return -EINVAL;
+	}
+
+	return err;
+}
+
+static int counter_data_u64_write(struct counter_device *const counter,
+				  const struct counter_control *const control,
+				  const char __user *const buf,
+				  const size_t len, const size_t offset)
+{
+	const struct counter_data *const data = &control->data;
+	int err;
+	u64 val = 0;
+
+	err = copy_from_user((char *)&val + offset, buf, len);
+	if (err)
+		return err;
+
+	switch (control->type) {
+	case COUNTER_OWNER_TYPE_DEVICE:
+		err = data->device_u64_write(counter, val);
+		break;
+	case COUNTER_OWNER_TYPE_SIGNAL:
+		err = data->signal_u64_write(counter, control->owner, val);
+		break;
+	case COUNTER_OWNER_TYPE_COUNT:
+		err = data->count_u64_write(counter, control->owner, val);
+		break;
+	default: return -EINVAL;
+	}
+
+	return err;
+}
+
+static int counter_data_string_write(struct counter_device *const counter,
+				     const struct counter_control *const control,
+				     const char __user *const buf,
+				     const size_t len, const size_t offset)
+{
+	const struct counter_data *const data = &control->data;
+	int err;
+	char str[64] = "";
+
+	err = copy_from_user(str + offset, buf, len);
+	if (err)
+		return err;
+
+	switch (control->type) {
+	case COUNTER_OWNER_TYPE_DEVICE:
+		err = data->device_string_write(counter, str, sizeof(str));
+		break;
+	case COUNTER_OWNER_TYPE_SIGNAL:
+		err = data->signal_string_write(counter, control->owner, str,
+						sizeof(str));
+		break;
+	case COUNTER_OWNER_TYPE_COUNT:
+		err = data->count_string_write(counter, control->owner, str,
+					       sizeof(str));
+		break;
+	default: return -EINVAL;
+	}
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int counter_control_write(struct counter_device *const counter,
+				 const struct counter_control *const control,
+				 const char __user *const buf, const size_t len,
+				 const size_t offset)
+{
+	/* Check if write operation is supported */
+	if (!control->data.device_u8_write)
+		return -EFAULT;
+
+	switch (control->data.type) {
+	case COUNTER_DATA_TYPE_U8:
+	case COUNTER_DATA_TYPE_BOOL:
+	case COUNTER_DATA_TYPE_SIGNAL:
+	case COUNTER_DATA_TYPE_COUNT_FUNCTION:
+	case COUNTER_DATA_TYPE_SYNAPSE_ACTION:
+	case COUNTER_DATA_TYPE_COUNT_DIRECTION:
+	case COUNTER_DATA_TYPE_COUNT_MODE:
+		return counter_data_u8_write(counter, control, buf);
+	case COUNTER_DATA_TYPE_U64:
+		return counter_data_u64_write(counter, control, buf, len,
+					      offset);
+	case COUNTER_DATA_TYPE_STRING:
+		return counter_data_string_write(counter, control, buf, len,
+						 offset);
+	}
+
+	return -EINVAL;
+}
+
+static ssize_t counter_chrdev_write(struct file *filp, const char __user *buf,
+				    size_t len, loff_t *f_ps)
+{
+	const loff_t start_ps = *f_ps;
+	struct counter_device *const counter = filp->private_data;
+	size_t write_size;
+	struct counter_control *control;
+	int err;
+	loff_t next_control_ps;
+
+	/* Handle Counter control selection */
+	if (*f_ps < COUNTER_SELECTION_SIZE) {
+		write_size = (*f_ps + len > COUNTER_SELECTION_SIZE) ?
+			    COUNTER_SELECTION_SIZE - *f_ps : len;
+		if (copy_from_user(counter->selection + *f_ps, buf, write_size))
+			return -EFAULT;
+
+		/* Create new list based on updated selection array */
+		err = counter_control_list_create(counter);
+		if (err)
+			return err;
+
+		*f_ps += write_size;
+		buf += write_size;
+		len -= write_size;
+		if (!len)
+			goto exit_chrdev_write;
+	}
+
+	/* Handle controls */
+	list_for_each_entry(control, &counter->control_list, l) {
+		next_control_ps = control->offset + control->size;
+
+		/* Find first control item */
+		if (*f_ps >= next_control_ps)
+			continue;
+
+		write_size = (*f_ps + len > next_control_ps) ?
+			     next_control_ps - *f_ps : len;
+
+		err = counter_control_write(counter, control, buf, write_size,
+					    control->offset - *f_ps);
+		if (err)
+			return err;
+
+		*f_ps += write_size;
+		buf += write_size;
+		len -= write_size;
+		if (!len)
+			goto exit_chrdev_write;
+	}
+
+	return -EFAULT;
+
+exit_chrdev_write:
+	return *f_ps - start_ps;
+}
+
+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 generic_file_open(inode, filp);
+}
+
+static int counter_chrdev_release(struct inode *inode, struct file *filp)
+{
+	struct counter_device *const counter = container_of(inode->i_cdev,
+							    typeof(*counter),
+							    chrdev);
+
+	put_device(&counter->dev);
+
+	return 0;
+}
+
+static const struct file_operations counter_fops = {
+	.llseek = generic_file_llseek,
+	.read = counter_chrdev_read,
+	.write = counter_chrdev_write,
+	.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 character device control selection */
+	memset(counter->selection, 0, sizeof(counter->selection));
+
+	/* Initialize Counter character device selected controls list */
+	INIT_LIST_HEAD(&counter->control_list);
+
+	/* 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_free(struct counter_device *const counter)
+{
+	cdev_del(&counter->chrdev);
+	counter_control_list_free(&counter->control_list);
+}
diff --git a/drivers/counter/counter-chrdev.h b/drivers/counter/counter-chrdev.h
new file mode 100644
index 000000000000..7ab0797d3857
--- /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_free(struct counter_device *const counter);
+
+#endif /* _COUNTER_CHRDEV_H_ */
diff --git a/drivers/counter/counter-core.c b/drivers/counter/counter-core.c
index 499664809c75..2d0886648201 100644
--- a/drivers/counter/counter-core.c
+++ b/drivers/counter/counter-core.c
@@ -6,11 +6,14 @@ 
 #include <linux/counter.h>
 #include <linux/device.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 */
@@ -33,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
@@ -62,10 +67,15 @@  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_free_chrdev;
 
 	/* Add device to system */
 	err = device_add(dev);
@@ -76,6 +86,8 @@  int counter_register(struct counter_device *const counter)
 
 err_free_sysfs:
 	counter_sysfs_free(counter);
+err_free_chrdev:
+	counter_chrdev_free(counter);
 err_free_id:
 	ida_simple_remove(&counter_ida, counter->id);
 	return err;
@@ -93,6 +105,7 @@  void counter_unregister(struct counter_device *const counter)
 	if (counter) {
 		device_del(&counter->dev);
 		counter_sysfs_free(counter);
+		counter_chrdev_free(counter);
 	}
 }
 EXPORT_SYMBOL_GPL(counter_unregister);
@@ -139,13 +152,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/include/linux/counter.h b/include/linux/counter.h
index e8abbaf47a4b..22e91d00a880 100644
--- a/include/linux/counter.h
+++ b/include/linux/counter.h
@@ -6,6 +6,7 @@ 
 #ifndef _COUNTER_H_
 #define _COUNTER_H_
 
+#include <linux/cdev.h>
 #include <linux/device.h>
 #include <linux/kernel.h>
 #include <linux/list.h>
@@ -170,6 +171,9 @@  struct counter_attribute_group {
  * @priv:		optional private data supplied by driver
  * @id:			unique ID used to identify the Counter
  * @dev:		internal device structure
+ * @chrdev:		internal character device structure
+ * @selection:		Counter character device control selection
+ * @control_list:	Counter character device selected controls
  * @dynamic_names_list:	List for dynamic names
  * @groups_list:	attribute groups list (for Signals, Counts, and ext)
  * @num_groups:		number of attribute groups containers
@@ -208,6 +212,18 @@  struct counter_device {
 
 	int id;
 	struct device dev;
+	struct cdev chrdev;
+
+#define MAX_EXT 255
+#define MAX_SIGNALS 255
+#define MAX_SYNAPSES 255
+#define MAX_COUNTS 255
+#define SIGNALS_SELECTION_SIZE ((1 + MAX_EXT) * MAX_SIGNALS)
+#define COUNTS_SELECTION_SIZE ((1 + 1 + MAX_SYNAPSES + MAX_EXT) * MAX_COUNTS)
+#define COUNTER_SELECTION_SIZE (MAX_EXT + SIGNALS_SELECTION_SIZE + COUNTS_SELECTION_SIZE)
+	u8 selection[COUNTER_SELECTION_SIZE];
+	struct list_head control_list;
+
 	struct list_head dynamic_names_list;
 	struct counter_attribute_group *groups_list;
 	size_t num_groups;