503 lines
13 KiB
C
Raw Normal View History

/*
* main.c -- the bare skypopen char module
*
* Copyright (C) 2010 Giovanni Maruzzelli
* Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
* Copyright (C) 2001 O'Reilly & Associates
*
* The source code in this file can be freely used, adapted,
* and redistributed in source or binary form, so long as an
* acknowledgment appears in derived source files. The citation
* should list that the code comes from the book "Linux Device
* Drivers" by Alessandro Rubini and Jonathan Corbet, published
* by O'Reilly & Associates. No warranty is attached;
* we cannot take responsibility for errors or fitness for use.
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>
#include <asm/uaccess.h> /* copy_*_user */
#include <linux/soundcard.h>
#include <linux/delay.h>
#include <linux/hrtimer.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/jiffies.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include "skypopen.h" /* local definitions */
2011-04-20 03:32:03 -05:00
/*
* Our parameters which can be set at load time.
*/
int skypopen_major = SKYPOPEN_MAJOR;
2011-04-20 03:32:03 -05:00
int skypopen_minor = SKYPOPEN_MINOR;
int skypopen_nr_devs = SKYPOPEN_NR_DEVS; /* number of bare skypopen devices */
module_param(skypopen_major, int, S_IRUGO);
module_param(skypopen_minor, int, S_IRUGO);
module_param(skypopen_nr_devs, int, S_IRUGO);
MODULE_AUTHOR("Original: Alessandro Rubini, Jonathan Corbet. Modified by: Giovanni Maruzzelli for FreeSWITCH skypopen");
MODULE_LICENSE("Dual BSD/GPL");
static struct skypopen_dev *skypopen_devices; /* allocated in skypopen_init_module */
static int unload = 0;
#ifndef WANT_HRTIMER
void my_timer_callback_inq( unsigned long data )
{
struct skypopen_dev *dev = (void *)data;
wake_up_interruptible(&dev->inq);
mod_timer( &dev->timer_inq, jiffies + msecs_to_jiffies(SKYPOPEN_SLEEP) );
}
void my_timer_callback_outq( unsigned long data )
{
struct skypopen_dev *dev = (void *)data;
wake_up_interruptible(&dev->outq);
mod_timer( &dev->timer_outq, jiffies + msecs_to_jiffies(SKYPOPEN_SLEEP) );
}
#else// WANT_HRTIMER
2011-04-20 03:32:03 -05:00
#ifndef CENTOS_5
static enum hrtimer_restart my_hrtimer_callback_inq( struct hrtimer *timer_inq )
{
struct skypopen_dev *dev = container_of(timer_inq, struct skypopen_dev, timer_inq);
if(unload)
return HRTIMER_NORESTART;
hrtimer_forward(&dev->timer_inq, timer_inq->_softexpires, ktime_set(0, SKYPOPEN_SLEEP * 1000000));
wake_up_interruptible(&dev->inq);
return HRTIMER_RESTART;
}
static enum hrtimer_restart my_hrtimer_callback_outq( struct hrtimer *timer_outq )
{
struct skypopen_dev *dev = container_of(timer_outq, struct skypopen_dev, timer_outq);
if(unload)
return HRTIMER_NORESTART;
hrtimer_forward(&dev->timer_outq, timer_outq->_softexpires, ktime_set(0, SKYPOPEN_SLEEP * 1000000));
wake_up_interruptible(&dev->outq);
return HRTIMER_RESTART;
}
2011-04-20 03:32:03 -05:00
#else// CENTOS_5
static int my_hrtimer_callback_inq( struct hrtimer *timer_inq )
{
struct skypopen_dev *dev = container_of(timer_inq, struct skypopen_dev, timer_inq);
if(unload)
return HRTIMER_NORESTART;
hrtimer_forward(&dev->timer_inq, timer_inq->expires, ktime_set(0, SKYPOPEN_SLEEP * 1000000));
wake_up_interruptible(&dev->inq);
return HRTIMER_RESTART;
}
static int my_hrtimer_callback_outq( struct hrtimer *timer_outq )
{
struct skypopen_dev *dev = container_of(timer_outq, struct skypopen_dev, timer_outq);
if(unload)
return HRTIMER_NORESTART;
hrtimer_forward(&dev->timer_outq, timer_outq->expires, ktime_set(0, SKYPOPEN_SLEEP * 1000000));
wake_up_interruptible(&dev->outq);
return HRTIMER_RESTART;
}
2011-04-20 03:32:03 -05:00
#endif// CENTOS_5
#endif// WANT_HRTIMER
/* The clone-specific data structure includes a key field */
struct skypopen_listitem {
struct skypopen_dev device;
dev_t key;
struct list_head list;
};
/* The list of devices, and a lock to protect it */
static LIST_HEAD(skypopen_c_list);
#ifdef WANT_DEFINE_SPINLOCK
static DEFINE_SPINLOCK(skypopen_c_lock);
#else // WANT_DEFINE_SPINLOCK
static spinlock_t skypopen_c_lock = SPIN_LOCK_UNLOCKED;
#endif // WANT_DEFINE_SPINLOCK
/* Look for a device or create one if missing */
static struct skypopen_dev *skypopen_c_lookfor_device(dev_t key)
{
struct skypopen_listitem *lptr;
#ifdef WANT_HRTIMER
#endif// WANT_HRTIMER
list_for_each_entry(lptr, &skypopen_c_list, list) {
if (lptr->key == key)
return &(lptr->device);
}
/* not found */
lptr = kmalloc(sizeof(struct skypopen_listitem), GFP_KERNEL);
if (!lptr)
return NULL;
/* initialize the device */
memset(lptr, 0, sizeof(struct skypopen_listitem));
lptr->key = key;
init_waitqueue_head(&lptr->device.inq);
init_waitqueue_head(&lptr->device.outq);
2011-04-20 03:32:03 -05:00
#ifndef WANT_HRTIMER
setup_timer( &lptr->device.timer_inq, my_timer_callback_inq, (long int)lptr );
setup_timer( &lptr->device.timer_outq, my_timer_callback_outq, (long int)lptr );
printk( "Starting skypopen OSS driver read timer (%dms) skype client:(%d)\n", SKYPOPEN_SLEEP, current->tgid );
mod_timer( &lptr->device.timer_inq, jiffies + msecs_to_jiffies(SKYPOPEN_SLEEP) );
printk( "Starting skypopen OSS driver write timer (%dms) skype client:(%d)\n", SKYPOPEN_SLEEP, current->tgid );
mod_timer( &lptr->device.timer_outq, jiffies + msecs_to_jiffies(SKYPOPEN_SLEEP) );
#endif// WANT_HRTIMER
/* place it in the list */
list_add(&lptr->list, &skypopen_c_list);
return &(lptr->device);
}
/*
* Open and close
*/
static int skypopen_c_open(struct inode *inode, struct file *filp)
{
struct skypopen_dev *dev;
dev_t key;
key = current->tgid;
/* look for a skypopenc device in the list */
spin_lock(&skypopen_c_lock);
dev = skypopen_c_lookfor_device(key);
if (dev){
dev->opened++;
}
spin_unlock(&skypopen_c_lock);
if (!dev)
return -ENOMEM;
/* then, everything else is copied from the bare skypopen device */
filp->private_data = dev;
return 0; /* success */
}
static int skypopen_c_release(struct inode *inode, struct file *filp)
{
dev_t key;
struct skypopen_dev *dev = filp->private_data;
int ret;
key = current->tgid;
spin_lock(&skypopen_c_lock);
dev->opened--;
spin_unlock(&skypopen_c_lock);
if(!dev->opened){
#ifdef WANT_HRTIMER
if(dev->timer_inq_started){
ret = hrtimer_cancel( &dev->timer_inq );
//printk( "Stopped skypopen OSS driver read HRtimer skype client:(%d) ret=%d\n", key, ret);
dev->timer_inq_started=0;
}
if(dev->timer_outq_started){
ret = hrtimer_cancel( &dev->timer_outq );
//printk( "Stopped skypopen OSS driver write HRtimer skype client:(%d) ret=%d\n", key, ret);
dev->timer_outq_started=0;
}
#endif// WANT_HRTIMER
}
return 0;
}
/*************************************************************/
static ssize_t skypopen_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
DEFINE_WAIT(wait);
struct skypopen_dev *dev = filp->private_data;
dev_t key;
key = current->tgid;
if(unload)
return -1;
#ifdef WANT_HRTIMER
if(dev->timer_inq_started == 0){
ktime_t ktime_inq;
ktime_inq = ktime_set( 0, SKYPOPEN_SLEEP * 1000000);
hrtimer_init( &dev->timer_inq, CLOCK_MONOTONIC, HRTIMER_MODE_REL );
dev->timer_inq.function = &my_hrtimer_callback_inq;
hrtimer_start( &dev->timer_inq, ktime_inq, HRTIMER_MODE_REL );
dev->timer_inq_started = 1;
//printk( "Started skypopen OSS driver read HRtimer skype client:(%d) \n", key);
}
#endif// WANT_HRTIMER
//printk("READ\n");
prepare_to_wait(&dev->inq, &wait, TASK_INTERRUPTIBLE);
schedule();
finish_wait(&dev->inq, &wait);
2011-04-20 03:32:03 -05:00
return count;
}
static ssize_t skypopen_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
DEFINE_WAIT(wait);
struct skypopen_dev *dev = filp->private_data;
dev_t key;
key = current->tgid;
if(unload)
return -1;
#ifdef WANT_HRTIMER
if(dev->timer_outq_started == 0){
ktime_t ktime_outq;
ktime_outq = ktime_set( 0, SKYPOPEN_SLEEP * 1000000);
hrtimer_init( &dev->timer_outq, CLOCK_MONOTONIC, HRTIMER_MODE_REL );
dev->timer_outq.function = &my_hrtimer_callback_outq;
hrtimer_start( &dev->timer_outq, ktime_outq, HRTIMER_MODE_REL );
dev->timer_outq_started = 1;
//printk( "Started skypopen OSS driver write HRtimer skype client:(%d) \n", key);
}
#endif// WANT_HRTIMER
//printk("WRITE\n");
prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE);
schedule();
finish_wait(&dev->outq, &wait);
return count;
}
/*
* The ioctl() implementation
*/
#ifndef HAVE_UNLOCKED_IOCTL
static int skypopen_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
switch (cmd) {
case OSS_GETVERSION:
return put_user(SOUND_VERSION, p);
case SNDCTL_DSP_GETBLKSIZE:
return put_user(SKYPOPEN_BLK, p);
case SNDCTL_DSP_GETFMTS:
return put_user(28731, p);
default:
return 0;
}
}
#else// HAVE_UNLOCKED_IOCTL
static long skypopen_unlocked_ioctl(struct file *filp,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
switch (cmd) {
case OSS_GETVERSION:
return put_user(SOUND_VERSION, p);
case SNDCTL_DSP_GETBLKSIZE:
return put_user(SKYPOPEN_BLK, p);
case SNDCTL_DSP_GETFMTS:
return put_user(28731, p);
default:
return 0;
}
}
#endif// HAVE_UNLOCKED_IOCTL
struct file_operations skypopen_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = skypopen_read,
.write = skypopen_write,
#ifndef HAVE_UNLOCKED_IOCTL
.ioctl = skypopen_ioctl,
#else// HAVE_UNLOCKED_IOCTL
.unlocked_ioctl = skypopen_unlocked_ioctl,
#endif// HAVE_UNLOCKED_IOCTL
.open = skypopen_c_open,
.release = skypopen_c_release,
};
/*
* Finally, the module stuff
*/
/*
* The cleanup function is used to handle initialization failures as well.
* Thefore, it must be careful to work correctly even if some of the items
* have not been initialized
*/
void skypopen_cleanup_module(void)
{
int i;
int ret;
struct skypopen_listitem *lptr, *next;
dev_t devno = MKDEV(skypopen_major, skypopen_minor);
unload = 1;
msleep(100);
/* Get rid of our char dev entries */
if (skypopen_devices) {
for (i = 0; i < skypopen_nr_devs; i++) {
cdev_del(&skypopen_devices[i].cdev);
}
kfree(skypopen_devices);
}
/* And all the cloned devices */
list_for_each_entry_safe(lptr, next, &skypopen_c_list, list) {
#ifndef WANT_HRTIMER
ret= del_timer( &lptr->device.timer_inq );
printk( "Stopped skypopen OSS driver read timer\n");
ret= del_timer( &lptr->device.timer_outq );
printk( "Stopped skypopen OSS driver write timer\n");
#else// WANT_HRTIMER
if(lptr->device.timer_inq_started){
ret = hrtimer_cancel( &lptr->device.timer_inq );
printk( "Stopped skypopen OSS driver read HRtimer\n");
}
if(lptr->device.timer_outq_started){
ret = hrtimer_cancel( &lptr->device.timer_outq );
printk( "Stopped skypopen OSS driver write HRtimer\n");
}
#endif// WANT_HRTIMER
list_del(&lptr->list);
kfree(lptr);
}
/* cleanup_module is never called if registering failed */
unregister_chrdev_region(devno, skypopen_nr_devs);
printk("skypopen OSS driver unloaded\n");
}
/*
* Set up the char_dev structure for this device.
*/
static void skypopen_setup_cdev(struct skypopen_dev *dev, int index)
{
int err, devno = MKDEV(skypopen_major, skypopen_minor + index);
cdev_init(&dev->cdev, &skypopen_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &skypopen_fops;
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding skypopen%d", err, index);
}
int skypopen_init_module(void)
{
int result, i;
dev_t dev = 0;
printk("skypopen OSS driver loading (www.freeswitch.org)\n");
2011-04-20 03:32:03 -05:00
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if (skypopen_major) {
dev = MKDEV(skypopen_major, skypopen_minor);
result = register_chrdev_region(dev, skypopen_nr_devs, "dsp");
} else {
result = alloc_chrdev_region(&dev, skypopen_minor, skypopen_nr_devs,
"dsp");
skypopen_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "skypopen OSS driver: can't get major %d\n", skypopen_major);
return result;
}
/*
* allocate the devices -- we can't have them static, as the number
* can be specified at load time
*/
skypopen_devices = kmalloc(skypopen_nr_devs * sizeof(struct skypopen_dev), GFP_KERNEL);
if (!skypopen_devices) {
result = -ENOMEM;
goto fail; /* Make this more graceful */
}
memset(skypopen_devices, 0, skypopen_nr_devs * sizeof(struct skypopen_dev));
/* Initialize each device. */
for (i = 0; i < skypopen_nr_devs; i++) {
skypopen_setup_cdev(&skypopen_devices[i], i);
}
/* At this point call the init function for any friend device */
dev = MKDEV(skypopen_major, skypopen_minor + skypopen_nr_devs);
return 0; /* succeed */
fail:
skypopen_cleanup_module();
return result;
}
module_init(skypopen_init_module);
module_exit(skypopen_cleanup_module);