[RFC] cpufreq event driver (aka my first lkm)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello,

I attached my first attempt to write a kernel module (exposes some cpufreq
events to userspace) and even if I can't think of a real usage of such a
driver I'm pretty happy with it.

I'd really appreciate if someone could comment on the code style, bugs
and anything else, especially SMP safeness.

Also, suppose that this driver is somewhat useful (ala
/proc/acpi/event), is the char dev choice correct or are there better
ways to obtain the same (I read too many times not to use /proc
anymore, so I already excluded it from the list)?

thanks in advance
-- 
mattia
:wq!
/*
 *  cpufreq_event.c
 *
 *  Copyright (C)  2005 Mattia Dongili <malattia@xxxxxxxxx>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * CPUFREQ_EVENT
 * This module reports cpufreq event to userspace either by keeping
 * a queue of the last N events readable through sysfs or by
 * implementing a char device that blocks until events are 
 * available.
 * The two options can't cohexist... well they can actually but the
 * sysfs attribute becomes useless as events are not enqueued if the
 * device has not been opened.
 * 
 * Build with:
 *	make EXTRA_CFLAGS+='-DCONFIG_CPUFREQ_EV_CHR'
 *	or
 *	make EXTRA_CFLAGS+='-DCONFIG_CPUFREQ_EV_SYSFS'
 *
 * add '-g' to the EXTRA_CFLAGS to enable debug
 */
#ifdef CONFIG_CPUFREQ_EV_CHR
#  ifdef CONFIG_CPUFREQ_EV_SYSFS
#    error "can't define both CONFIG_CPUFREQ_EV_SYSFS and CONFIG_CPUFREQ_EV_CHR"
#  endif
#else
#  ifndef CONFIG_CPUFREQ_EV_SYSFS
#     error "define at least one of CONFIG_CPUFREQ_EV_SYSFS and CONFIG_CPUFREQ_EV_CHR"
#  endif
#endif

#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/cpufreq.h>
#include <linux/sysdev.h>
#include <linux/notifier.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <asm/uaccess.h>

/* event queue */
#define EVENTQ_LEN	10
#define EVBUFFER_LEN	64
static char		ev_queue [EVENTQ_LEN] [EVBUFFER_LEN];
static unsigned int	ev_count;
static unsigned int	reader_pos;
static DECLARE_MUTEX	(ev_sem);

#ifdef CONFIG_CPUFREQ_EV_SYSFS
/******************** SYSFS stuff ********************
 * In SYSFS mode the driver keeps the last EVENTQ_LEN
 * events for the readers.
 */
/* used to create only one sysfs entry */
static int		usage_count;
static DECLARE_MUTEX	(usage_count_sem);
/* See drivers/base/cpu.c
 * we attach to /sys/devices/system/cpu 
 */
extern struct sysdev_class cpu_sysdev_class;

/* 
 * cpufreq_event_show - Shows the last event received.
 * Beware! sysfs provides a PAGE_SIZE wide buffer to store all
 * the output so keep it in mind when playing with EVENTQ_LEN.
 * (EVENTQ_LEN*EVBUFFER_LEN < PAGE_SIZE)
 */
static ssize_t
cpufreq_event_show(struct sys_device *unused, char *buf)
{
	int ret=0;

	if (down_interruptible(&ev_sem))
		return -ERESTARTSYS;

	/* flush the queue */
	while(ev_count) {
		ret += sprintf(buf+ret, "%s", 
				ev_queue[reader_pos]);
		reader_pos=(reader_pos+1)%EVENTQ_LEN;
		ev_count--;
	}
	up(&ev_sem);
	return ret;
}
SYSDEV_ATTR(cpufreq_event, 0444, cpufreq_event_show, NULL);

/*
 * cpufreqev_add_dev - Invoked when a sys_device is added.
 */
static int
cpufreq_event_add_dev(struct sys_device *sd)
{
	down(&usage_count_sem);
	/*  create the sysfs file only once */
	if (usage_count > 0)
		goto out;
	
	/* create sysfs entry see include/linux/sysfs.h */
	if (sysfs_create_file(sd->kobj.parent, &attr_cpufreq_event.attr))
		goto out;

	usage_count++;
out:
	up(&usage_count_sem);
	return 0;
}

/*
 * cpufreqev_remove_dev - Invoked when a sys_device is removed.
 */
static int
cpufreq_event_remove_dev(struct sys_device *sd)
{
	down(&usage_count_sem);
	usage_count--;
	/* remove only if the last device was removed */
	if (usage_count > 0) {
		goto out;
	}
	/* remove sysfs entry see include/linux/sysfs.h */
	sysfs_remove_file(sd->kobj.parent, &attr_cpufreq_event.attr);
out:
	up(&usage_count_sem);
	return 0;
}

static struct sysdev_driver cpufreqev_sysdev_driver = {
	.add		= cpufreq_event_add_dev,
	.remove		= cpufreq_event_remove_dev,
};
/*
 *
 *****************************************************/
#endif

#ifdef CONFIG_CPUFREQ_EV_CHR
/***************** Char Device stuff *****************
 *
 */
static int		device_open;
static DECLARE_MUTEX	(device_open_sem);

/* queue on which the reader will wait until data available */
DECLARE_WAIT_QUEUE_HEAD(cpufreq_event_queue);

static int
cpufreq_event_open(struct inode *inode, struct file *filp)
{
	if (down_interruptible(&device_open_sem))
		return -ERESTARTSYS;

	/* allow only one reader */
	if (device_open) {
		up(&device_open_sem);
		return -EBUSY;
	}
	device_open = 1;
	up(&device_open_sem);
	return 0;
}

static ssize_t
cpufreq_event_read(struct file *filp, char __user *buf, size_t count,
                loff_t *f_pos)
{
	ssize_t ret=0, evlen=0;

	/* if the user supplied buffer is smaller than at least one event
	 * then return with an error (seen in dirvers/input/evedev.c)
	 */
	if (count<EVBUFFER_LEN)
		return -EINVAL;
	
	if (down_interruptible(&ev_sem))
		return -ERESTARTSYS;

	/* wait until events available */
	while (ev_count == 0) {
		/* break if non-blocking op requested */
		if (filp->f_flags & O_NONBLOCK) {
			ret = -EAGAIN;
			goto out;
		}
		up(&ev_sem);
		/* wait for input available */
		if (wait_event_interruptible(cpufreq_event_queue, ev_count))
			return -ERESTARTSYS;
		if (down_interruptible(&ev_sem))
			return -ERESTARTSYS;
	}

	/*  copy all events in the queue (only full events) */
	while (ev_count > 0 && (ret+EVBUFFER_LEN) <= count) {
		evlen = strlen(ev_queue[reader_pos]);
		if (copy_to_user(buf+ret, ev_queue[reader_pos], evlen)) {
			ret = -EFAULT;
			goto out;
		}
		ret += evlen;
		reader_pos=(reader_pos+1)%EVENTQ_LEN;
		ev_count--;
	}
out:
	up(&ev_sem);
#if 0
	printk(KERN_WARNING"/dev/cpufreq_event read (c:%d r:%d el:%d)\n",
			count, ret, evlen);
#endif
	return ret;
}

static int
cpufreq_event_release(struct inode *inode, struct file *filp)
{
	if (down_interruptible(&device_open_sem))
		return -ERESTARTSYS;

	if (device_open) {
		device_open = 0;
	}
	up(&device_open_sem);
	return 0;
}

static struct cdev	cpufreq_event_cdev;
static dev_t		cpufreq_event_dev;
struct file_operations	cpufreq_event_ops = {
	.owner	=	THIS_MODULE,
	.read	=	cpufreq_event_read,
	.open	=	cpufreq_event_open,
	.release =	cpufreq_event_release,
};

/*
 *
 *****************************************************/
#endif

/******************* CPUFREQ stuff *******************
 * We will setup a listener to receive cpufreq core events
 */
/* 
 * cpufreq_changed_frequency - Get the frequency event and
 * enqueue a descriptive string to the ev_queue circular
 * buffer.
 */
static int 
cpufreq_changed_frequency(struct notifier_block *self,
				unsigned long val, void *v)
{
	struct cpufreq_freqs *freq = (struct cpufreq_freqs *) v;

	/* drop the event if nobody's waiting (chardev only) */
#ifdef CONFIG_CPUFREQ_EV_CHR
	down(&device_open_sem);
	if (!device_open) {
		up(&device_open_sem);
		return 0;
	}
	up(&device_open_sem);
#endif

	down(&ev_sem);
	/* enqueue the event */
	snprintf(ev_queue[(reader_pos+ev_count)%EVENTQ_LEN],
			EVBUFFER_LEN, "cpu %u (ev:%lu): %u -> %u\n",
			freq->cpu, val, freq->old, freq->new);

	/* if overflowing EVENTQ_LEN don't increase ev_count 
	 * only shift the reader start position
	 */
	if (ev_count >= EVENTQ_LEN) {
#ifdef CONFIG_CPUFREQ_EV_CHR
		/* don't print this warning if using sysfs not to pollute logs */
		printk(KERN_WARNING"CPUFreq Event queue full, discarding old event\n");
#endif
		reader_pos = (reader_pos+1) % EVENTQ_LEN;
	} else {
		ev_count++;
	}
	up(&ev_sem);

#ifdef CONFIG_CPUFREQ_EV_CHR
	/* notify readers */
	wake_up_interruptible(&cpufreq_event_queue);
#endif
	return 0;
}

/* Notifier struct to receive CPUFreq transition events */
static struct notifier_block frequency_changed = {
	.notifier_call = &cpufreq_changed_frequency
};

/*
 *
 *****************************************************/

static __init int cpufreq_event_init(void)
{
	int ret = 0;
	
	/* register our listener (after that we could get events!) */
	ret = cpufreq_register_notifier
		(&frequency_changed, CPUFREQ_TRANSITION_NOTIFIER);
	if (ret < 0) {
		printk(KERN_WARNING"Couldn't register cpufreq listener\n");
		goto out;
	}
	
#ifdef CONFIG_CPUFREQ_EV_CHR
	/* device allocation */
	ret = alloc_chrdev_region(&cpufreq_event_dev, 0, 1, "cpufreq_event");
	if (ret<0) {
		printk(KERN_WARNING"Couldn't create chrdev\n");
		goto out_chrdev;
	}
	printk(KERN_WARNING"chrdev registered (M:%d m:%d)\n",
			MAJOR(cpufreq_event_dev), MINOR(cpufreq_event_dev));

	cdev_init(&cpufreq_event_cdev, &cpufreq_event_ops);
	ret = cdev_add(&cpufreq_event_cdev, cpufreq_event_dev, 1);
	if (ret < 0) {
		printk(KERN_WARNING"Couldn't add chrdev\n");
		goto out_chrdev_add;
	}
#endif

#ifdef CONFIG_CPUFREQ_EV_SYSFS
	/* register sysdev driver */
	sysdev_driver_register(&cpu_sysdev_class, &cpufreqev_sysdev_driver);
#endif
	goto out;

#ifdef CONFIG_CPUFREQ_EV_CHR
out_chrdev_add:
	unregister_chrdev_region(cpufreq_event_dev, 1);
out_chrdev:
	cpufreq_unregister_notifier(&frequency_changed,
		CPUFREQ_TRANSITION_NOTIFIER);
#endif
out:
	return ret;
}

static __exit void cpufreq_event_exit(void)
{
#ifdef CONFIG_CPUFREQ_EV_SYSFS
	/* unregister sysdev driver */
	sysdev_driver_unregister(&cpu_sysdev_class, &cpufreqev_sysdev_driver);
#endif

#ifdef CONFIG_CPUFREQ_EV_CHR
	/* device del */
	cdev_del(&cpufreq_event_cdev);

	/* device de-allocation */
	unregister_chrdev_region(cpufreq_event_dev, 1);
#endif	
	/* unregister listener (no harm if we didn't registered
	 * successfully)
	 */
	cpufreq_unregister_notifier(&frequency_changed,
		CPUFREQ_TRANSITION_NOTIFIER);
}

MODULE_AUTHOR ("Mattia Dongili <malattia@xxxxxxxxx>");
MODULE_DESCRIPTION ("'cpufreq_event' - Delivers cpufreq events to userspace");
MODULE_LICENSE("GPL");

module_init(cpufreq_event_init);
module_exit(cpufreq_event_exit);

[Index of Archives]     [Newbies FAQ]     [Linux Kernel Mentors]     [Linux Kernel Development]     [IETF Annouce]     [Git]     [Networking]     [Security]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux SCSI]     [Linux ACPI]
  Powered by Linux