2021-09-29 12:15:59 +09:00
// 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/err.h>
# include <linux/errno.h>
# include <linux/export.h>
# include <linux/fs.h>
# include <linux/kfifo.h>
# include <linux/list.h>
# include <linux/mutex.h>
# include <linux/nospec.h>
# include <linux/poll.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/timekeeping.h>
# include <linux/types.h>
# include <linux/uaccess.h>
# include <linux/wait.h>
# include "counter-chrdev.h"
struct counter_comp_node {
struct list_head l ;
struct counter_component component ;
struct counter_comp comp ;
void * parent ;
} ;
# define counter_comp_read_is_equal(a, b) \
( a . action_read = = b . action_read | | \
a . device_u8_read = = b . device_u8_read | | \
a . count_u8_read = = b . count_u8_read | | \
a . signal_u8_read = = b . signal_u8_read | | \
a . device_u32_read = = b . device_u32_read | | \
a . count_u32_read = = b . count_u32_read | | \
a . signal_u32_read = = b . signal_u32_read | | \
a . device_u64_read = = b . device_u64_read | | \
a . count_u64_read = = b . count_u64_read | | \
a . signal_u64_read = = b . signal_u64_read )
# define counter_comp_read_is_set(comp) \
( comp . action_read | | \
comp . device_u8_read | | \
comp . count_u8_read | | \
comp . signal_u8_read | | \
comp . device_u32_read | | \
comp . count_u32_read | | \
comp . signal_u32_read | | \
comp . device_u64_read | | \
comp . count_u64_read | | \
comp . signal_u64_read )
static ssize_t counter_chrdev_read ( struct file * filp , char __user * buf ,
size_t len , loff_t * f_ps )
{
struct counter_device * const counter = filp - > private_data ;
int err ;
unsigned int copied ;
if ( ! counter - > ops )
return - ENODEV ;
if ( len < sizeof ( struct counter_event ) )
return - EINVAL ;
do {
if ( kfifo_is_empty ( & counter - > events ) ) {
if ( filp - > f_flags & O_NONBLOCK )
return - EAGAIN ;
err = wait_event_interruptible ( counter - > events_wait ,
! kfifo_is_empty ( & counter - > events ) | |
! counter - > ops ) ;
if ( err < 0 )
return err ;
if ( ! counter - > ops )
return - ENODEV ;
}
2021-10-21 19:35:40 +09:00
if ( mutex_lock_interruptible ( & counter - > events_out_lock ) )
2021-09-29 12:15:59 +09:00
return - ERESTARTSYS ;
err = kfifo_to_user ( & counter - > events , buf , len , & copied ) ;
2021-10-21 19:35:40 +09:00
mutex_unlock ( & counter - > events_out_lock ) ;
2021-09-29 12:15:59 +09:00
if ( err < 0 )
return err ;
} while ( ! copied ) ;
return copied ;
}
static __poll_t counter_chrdev_poll ( struct file * filp ,
struct poll_table_struct * pollt )
{
struct counter_device * const counter = filp - > private_data ;
__poll_t events = 0 ;
if ( ! counter - > ops )
return events ;
poll_wait ( filp , & counter - > events_wait , pollt ) ;
if ( ! kfifo_is_empty ( & counter - > events ) )
events = EPOLLIN | EPOLLRDNORM ;
return events ;
}
static void counter_events_list_free ( struct list_head * const events_list )
{
struct counter_event_node * p , * n ;
struct counter_comp_node * q , * o ;
list_for_each_entry_safe ( p , n , events_list , l ) {
/* Free associated component nodes */
list_for_each_entry_safe ( q , o , & p - > comp_list , l ) {
list_del ( & q - > l ) ;
kfree ( q ) ;
}
/* Free event node */
list_del ( & p - > l ) ;
kfree ( p ) ;
}
}
static int counter_set_event_node ( struct counter_device * const counter ,
struct counter_watch * const watch ,
const struct counter_comp_node * const cfg )
{
struct counter_event_node * event_node ;
int err = 0 ;
struct counter_comp_node * comp_node ;
/* Search for event in the list */
list_for_each_entry ( event_node , & counter - > next_events_list , l )
if ( event_node - > event = = watch - > event & &
event_node - > channel = = watch - > channel )
break ;
/* If event is not already in the list */
if ( & event_node - > l = = & counter - > next_events_list ) {
/* Allocate new event node */
event_node = kmalloc ( sizeof ( * event_node ) , GFP_KERNEL ) ;
if ( ! event_node )
return - ENOMEM ;
/* Configure event node and add to the list */
event_node - > event = watch - > event ;
event_node - > channel = watch - > channel ;
INIT_LIST_HEAD ( & event_node - > comp_list ) ;
list_add ( & event_node - > l , & counter - > next_events_list ) ;
}
/* Check if component watch has already been set before */
list_for_each_entry ( comp_node , & event_node - > comp_list , l )
if ( comp_node - > parent = = cfg - > parent & &
counter_comp_read_is_equal ( comp_node - > comp , cfg - > comp ) ) {
err = - EINVAL ;
goto exit_free_event_node ;
}
/* Allocate component node */
comp_node = kmalloc ( sizeof ( * comp_node ) , GFP_KERNEL ) ;
if ( ! comp_node ) {
err = - ENOMEM ;
goto exit_free_event_node ;
}
* comp_node = * cfg ;
/* Add component node to event node */
list_add_tail ( & comp_node - > l , & event_node - > comp_list ) ;
exit_free_event_node :
/* Free event node if no one else is watching */
if ( list_empty ( & event_node - > comp_list ) ) {
list_del ( & event_node - > l ) ;
kfree ( event_node ) ;
}
return err ;
}
static int counter_enable_events ( struct counter_device * const counter )
{
unsigned long flags ;
int err = 0 ;
mutex_lock ( & counter - > n_events_list_lock ) ;
spin_lock_irqsave ( & counter - > events_list_lock , flags ) ;
counter_events_list_free ( & counter - > events_list ) ;
list_replace_init ( & counter - > next_events_list ,
& counter - > events_list ) ;
if ( counter - > ops - > events_configure )
err = counter - > ops - > events_configure ( counter ) ;
spin_unlock_irqrestore ( & counter - > events_list_lock , flags ) ;
mutex_unlock ( & counter - > n_events_list_lock ) ;
return err ;
}
static int counter_disable_events ( struct counter_device * const counter )
{
unsigned long flags ;
int err = 0 ;
spin_lock_irqsave ( & counter - > events_list_lock , flags ) ;
counter_events_list_free ( & counter - > events_list ) ;
if ( counter - > ops - > events_configure )
err = counter - > ops - > events_configure ( counter ) ;
spin_unlock_irqrestore ( & counter - > events_list_lock , flags ) ;
mutex_lock ( & counter - > n_events_list_lock ) ;
counter_events_list_free ( & counter - > next_events_list ) ;
mutex_unlock ( & counter - > n_events_list_lock ) ;
return err ;
}
static int counter_add_watch ( struct counter_device * const counter ,
const unsigned long arg )
{
void __user * const uwatch = ( void __user * ) arg ;
struct counter_watch watch ;
struct counter_comp_node comp_node = { } ;
size_t parent , id ;
struct counter_comp * ext ;
size_t num_ext ;
int err = 0 ;
if ( copy_from_user ( & watch , uwatch , sizeof ( watch ) ) )
return - EFAULT ;
if ( watch . component . type = = COUNTER_COMPONENT_NONE )
goto no_component ;
parent = watch . component . parent ;
/* Configure parent component info for comp node */
switch ( watch . component . scope ) {
case COUNTER_SCOPE_DEVICE :
ext = counter - > ext ;
num_ext = counter - > num_ext ;
break ;
case COUNTER_SCOPE_SIGNAL :
if ( parent > = counter - > num_signals )
return - EINVAL ;
parent = array_index_nospec ( parent , counter - > num_signals ) ;
comp_node . parent = counter - > signals + parent ;
ext = counter - > signals [ parent ] . ext ;
num_ext = counter - > signals [ parent ] . num_ext ;
break ;
case COUNTER_SCOPE_COUNT :
if ( parent > = counter - > num_counts )
return - EINVAL ;
parent = array_index_nospec ( parent , counter - > num_counts ) ;
comp_node . parent = counter - > counts + parent ;
ext = counter - > counts [ parent ] . ext ;
num_ext = counter - > counts [ parent ] . num_ext ;
break ;
default :
return - EINVAL ;
}
id = watch . component . id ;
/* Configure component info for comp node */
switch ( watch . component . type ) {
case COUNTER_COMPONENT_SIGNAL :
if ( watch . component . scope ! = COUNTER_SCOPE_SIGNAL )
return - EINVAL ;
comp_node . comp . type = COUNTER_COMP_SIGNAL_LEVEL ;
comp_node . comp . signal_u32_read = counter - > ops - > signal_read ;
break ;
case COUNTER_COMPONENT_COUNT :
if ( watch . component . scope ! = COUNTER_SCOPE_COUNT )
return - EINVAL ;
comp_node . comp . type = COUNTER_COMP_U64 ;
comp_node . comp . count_u64_read = counter - > ops - > count_read ;
break ;
case COUNTER_COMPONENT_FUNCTION :
if ( watch . component . scope ! = COUNTER_SCOPE_COUNT )
return - EINVAL ;
comp_node . comp . type = COUNTER_COMP_FUNCTION ;
comp_node . comp . count_u32_read = counter - > ops - > function_read ;
break ;
case COUNTER_COMPONENT_SYNAPSE_ACTION :
if ( watch . component . scope ! = COUNTER_SCOPE_COUNT )
return - EINVAL ;
if ( id > = counter - > counts [ parent ] . num_synapses )
return - EINVAL ;
id = array_index_nospec ( id , counter - > counts [ parent ] . num_synapses ) ;
comp_node . comp . type = COUNTER_COMP_SYNAPSE_ACTION ;
comp_node . comp . action_read = counter - > ops - > action_read ;
comp_node . comp . priv = counter - > counts [ parent ] . synapses + id ;
break ;
case COUNTER_COMPONENT_EXTENSION :
if ( id > = num_ext )
return - EINVAL ;
id = array_index_nospec ( id , num_ext ) ;
comp_node . comp = ext [ id ] ;
break ;
default :
return - EINVAL ;
}
if ( ! counter_comp_read_is_set ( comp_node . comp ) )
return - EOPNOTSUPP ;
no_component :
mutex_lock ( & counter - > n_events_list_lock ) ;
if ( counter - > ops - > watch_validate ) {
err = counter - > ops - > watch_validate ( counter , & watch ) ;
if ( err < 0 )
goto err_exit ;
}
comp_node . component = watch . component ;
err = counter_set_event_node ( counter , & watch , & comp_node ) ;
err_exit :
mutex_unlock ( & counter - > n_events_list_lock ) ;
return err ;
}
static long counter_chrdev_ioctl ( struct file * filp , unsigned int cmd ,
unsigned long arg )
{
struct counter_device * const counter = filp - > private_data ;
int ret = - ENODEV ;
mutex_lock ( & counter - > ops_exist_lock ) ;
if ( ! counter - > ops )
goto out_unlock ;
switch ( cmd ) {
case COUNTER_ADD_WATCH_IOCTL :
ret = counter_add_watch ( counter , arg ) ;
break ;
case COUNTER_ENABLE_EVENTS_IOCTL :
ret = counter_enable_events ( counter ) ;
break ;
case COUNTER_DISABLE_EVENTS_IOCTL :
ret = counter_disable_events ( counter ) ;
break ;
default :
ret = - ENOIOCTLCMD ;
break ;
}
out_unlock :
mutex_unlock ( & counter - > ops_exist_lock ) ;
return ret ;
}
static int counter_chrdev_open ( struct inode * inode , struct file * filp )
{
struct counter_device * const counter = container_of ( inode - > i_cdev ,
typeof ( * counter ) ,
chrdev ) ;
get_device ( & counter - > dev ) ;
filp - > private_data = counter ;
return nonseekable_open ( inode , filp ) ;
}
static int counter_chrdev_release ( struct inode * inode , struct file * filp )
{
struct counter_device * const counter = filp - > private_data ;
int ret = 0 ;
mutex_lock ( & counter - > ops_exist_lock ) ;
if ( ! counter - > ops ) {
/* Free any lingering held memory */
counter_events_list_free ( & counter - > events_list ) ;
counter_events_list_free ( & counter - > next_events_list ) ;
ret = - ENODEV ;
goto out_unlock ;
}
ret = counter_disable_events ( counter ) ;
if ( ret < 0 ) {
mutex_unlock ( & counter - > ops_exist_lock ) ;
return ret ;
}
out_unlock :
mutex_unlock ( & counter - > ops_exist_lock ) ;
put_device ( & counter - > dev ) ;
return ret ;
}
static const struct file_operations counter_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. read = counter_chrdev_read ,
. poll = counter_chrdev_poll ,
. unlocked_ioctl = counter_chrdev_ioctl ,
. open = counter_chrdev_open ,
. release = counter_chrdev_release ,
} ;
int counter_chrdev_add ( struct counter_device * const counter )
{
/* Initialize Counter events lists */
INIT_LIST_HEAD ( & counter - > events_list ) ;
INIT_LIST_HEAD ( & counter - > next_events_list ) ;
spin_lock_init ( & counter - > events_list_lock ) ;
mutex_init ( & counter - > n_events_list_lock ) ;
init_waitqueue_head ( & counter - > events_wait ) ;
2021-10-21 19:35:40 +09:00
spin_lock_init ( & counter - > events_in_lock ) ;
mutex_init ( & counter - > events_out_lock ) ;
2021-09-29 12:15:59 +09:00
/* Initialize character device */
cdev_init ( & counter - > chrdev , & counter_fops ) ;
/* Allocate Counter events queue */
return kfifo_alloc ( & counter - > events , 64 , GFP_KERNEL ) ;
}
void counter_chrdev_remove ( struct counter_device * const counter )
{
kfifo_free ( & counter - > events ) ;
}
static int counter_get_data ( struct counter_device * const counter ,
const struct counter_comp_node * const comp_node ,
u64 * const value )
{
const struct counter_comp * const comp = & comp_node - > comp ;
void * const parent = comp_node - > parent ;
u8 value_u8 = 0 ;
u32 value_u32 = 0 ;
int ret ;
if ( comp_node - > component . type = = COUNTER_COMPONENT_NONE )
return 0 ;
switch ( comp - > type ) {
case COUNTER_COMP_U8 :
case COUNTER_COMP_BOOL :
switch ( comp_node - > component . scope ) {
case COUNTER_SCOPE_DEVICE :
ret = comp - > device_u8_read ( counter , & value_u8 ) ;
break ;
case COUNTER_SCOPE_SIGNAL :
ret = comp - > signal_u8_read ( counter , parent , & value_u8 ) ;
break ;
case COUNTER_SCOPE_COUNT :
ret = comp - > count_u8_read ( counter , parent , & value_u8 ) ;
break ;
}
* value = value_u8 ;
return ret ;
case COUNTER_COMP_SIGNAL_LEVEL :
case COUNTER_COMP_FUNCTION :
case COUNTER_COMP_ENUM :
case COUNTER_COMP_COUNT_DIRECTION :
case COUNTER_COMP_COUNT_MODE :
switch ( comp_node - > component . scope ) {
case COUNTER_SCOPE_DEVICE :
ret = comp - > device_u32_read ( counter , & value_u32 ) ;
break ;
case COUNTER_SCOPE_SIGNAL :
ret = comp - > signal_u32_read ( counter , parent ,
& value_u32 ) ;
break ;
case COUNTER_SCOPE_COUNT :
ret = comp - > count_u32_read ( counter , parent , & value_u32 ) ;
break ;
}
* value = value_u32 ;
return ret ;
case COUNTER_COMP_U64 :
switch ( comp_node - > component . scope ) {
case COUNTER_SCOPE_DEVICE :
return comp - > device_u64_read ( counter , value ) ;
case COUNTER_SCOPE_SIGNAL :
return comp - > signal_u64_read ( counter , parent , value ) ;
case COUNTER_SCOPE_COUNT :
return comp - > count_u64_read ( counter , parent , value ) ;
default :
return - EINVAL ;
}
case COUNTER_COMP_SYNAPSE_ACTION :
ret = comp - > action_read ( counter , parent , comp - > priv ,
& value_u32 ) ;
* value = value_u32 ;
return ret ;
default :
return - EINVAL ;
}
}
/**
* counter_push_event - queue event for userspace reading
* @ counter : pointer to Counter structure
* @ event : triggered event
* @ channel : event channel
*
* Note : If no one is watching for the respective event , it is silently
* discarded .
*/
void counter_push_event ( struct counter_device * const counter , const u8 event ,
const u8 channel )
{
struct counter_event ev ;
unsigned int copied = 0 ;
unsigned long flags ;
struct counter_event_node * event_node ;
struct counter_comp_node * comp_node ;
ev . timestamp = ktime_get_ns ( ) ;
ev . watch . event = event ;
ev . watch . channel = channel ;
/* Could be in an interrupt context, so use a spin lock */
spin_lock_irqsave ( & counter - > events_list_lock , flags ) ;
/* Search for event in the list */
list_for_each_entry ( event_node , & counter - > events_list , l )
if ( event_node - > event = = event & &
event_node - > channel = = channel )
break ;
/* If event is not in the list */
if ( & event_node - > l = = & counter - > events_list )
goto exit_early ;
/* Read and queue relevant comp for userspace */
list_for_each_entry ( comp_node , & event_node - > comp_list , l ) {
ev . watch . component = comp_node - > component ;
ev . status = - counter_get_data ( counter , comp_node , & ev . value ) ;
2021-10-21 19:35:40 +09:00
copied + = kfifo_in_spinlocked_noirqsave ( & counter - > events , & ev ,
1 , & counter - > events_in_lock ) ;
2021-09-29 12:15:59 +09:00
}
exit_early :
spin_unlock_irqrestore ( & counter - > events_list_lock , flags ) ;
if ( copied )
wake_up_poll ( & counter - > events_wait , EPOLLIN ) ;
}
EXPORT_SYMBOL_GPL ( counter_push_event ) ;