231 lines
6.3 KiB
C
231 lines
6.3 KiB
C
|
/**
|
||
|
* TSIF driver client
|
||
|
*
|
||
|
* Character device that, being read
|
||
|
* returns stream of TSIF packets.
|
||
|
*
|
||
|
* Copyright (c) 2009-2011, The Linux Foundation. All rights
|
||
|
* reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*/
|
||
|
|
||
|
#include <linux/module.h> /* Needed by all modules */
|
||
|
#include <linux/kernel.h> /* Needed for KERN_INFO */
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/err.h> /* IS_ERR etc. */
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/sched.h> /* TASK_INTERRUPTIBLE */
|
||
|
|
||
|
#include <linux/uaccess.h> /* copy_to_user */
|
||
|
|
||
|
#include <linux/tsif_api.h>
|
||
|
|
||
|
struct tsif_chrdev {
|
||
|
struct cdev cdev;
|
||
|
struct device *dev;
|
||
|
wait_queue_head_t wq_read;
|
||
|
void *cookie;
|
||
|
/* mirror for tsif data */
|
||
|
void *data_buffer;
|
||
|
unsigned buf_size_packets; /**< buffer size in packets */
|
||
|
unsigned ri, wi;
|
||
|
enum tsif_state state;
|
||
|
unsigned rptr;
|
||
|
};
|
||
|
|
||
|
static ssize_t tsif_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
int rc;
|
||
|
struct tsif_chrdev *the_dev =
|
||
|
container_of(inode->i_cdev, struct tsif_chrdev, cdev);
|
||
|
if (!the_dev->cookie) /* not bound yet */
|
||
|
return -ENODEV;
|
||
|
file->private_data = the_dev;
|
||
|
rc = tsif_start(the_dev->cookie);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
tsif_get_info(the_dev->cookie, &the_dev->data_buffer,
|
||
|
&the_dev->buf_size_packets);
|
||
|
the_dev->rptr = 0;
|
||
|
return nonseekable_open(inode, file);
|
||
|
}
|
||
|
|
||
|
static ssize_t tsif_release(struct inode *inode, struct file *filp)
|
||
|
{
|
||
|
struct tsif_chrdev *the_dev = filp->private_data;
|
||
|
tsif_stop(the_dev->cookie);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t tsif_read(struct file *filp, char __user *buf, size_t count,
|
||
|
loff_t *f_pos)
|
||
|
{
|
||
|
int avail = 0;
|
||
|
int wi;
|
||
|
struct tsif_chrdev *the_dev = filp->private_data;
|
||
|
tsif_get_state(the_dev->cookie, &the_dev->ri, &the_dev->wi,
|
||
|
&the_dev->state);
|
||
|
/* consistency check */
|
||
|
if (the_dev->ri != (the_dev->rptr / TSIF_PKT_SIZE)) {
|
||
|
dev_err(the_dev->dev,
|
||
|
"%s: inconsistent read pointers: ri %d rptr %d\n",
|
||
|
__func__, the_dev->ri, the_dev->rptr);
|
||
|
the_dev->rptr = the_dev->ri * TSIF_PKT_SIZE;
|
||
|
}
|
||
|
/* ri == wi if no data */
|
||
|
if (the_dev->ri == the_dev->wi) {
|
||
|
/* shall I block waiting for data? */
|
||
|
if (filp->f_flags & O_NONBLOCK) {
|
||
|
if (the_dev->state == tsif_state_running) {
|
||
|
return -EAGAIN;
|
||
|
} else {
|
||
|
/* not running -> EOF */
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
if (wait_event_interruptible(the_dev->wq_read,
|
||
|
(the_dev->ri != the_dev->wi) ||
|
||
|
(the_dev->state != tsif_state_running))) {
|
||
|
/* got signal -> tell FS to handle it */
|
||
|
return -ERESTARTSYS;
|
||
|
}
|
||
|
if (the_dev->ri == the_dev->wi) {
|
||
|
/* still no data -> EOF */
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
/* contiguous chunk last up to wi or end of buffer */
|
||
|
wi = (the_dev->wi > the_dev->ri) ?
|
||
|
the_dev->wi : the_dev->buf_size_packets;
|
||
|
avail = min(wi * TSIF_PKT_SIZE - the_dev->rptr, count);
|
||
|
if (copy_to_user(buf, the_dev->data_buffer + the_dev->rptr, avail))
|
||
|
return -EFAULT;
|
||
|
the_dev->rptr = (the_dev->rptr + avail) %
|
||
|
(TSIF_PKT_SIZE * the_dev->buf_size_packets);
|
||
|
the_dev->ri = the_dev->rptr / TSIF_PKT_SIZE;
|
||
|
*f_pos += avail;
|
||
|
tsif_reclaim_packets(the_dev->cookie, the_dev->ri);
|
||
|
return avail;
|
||
|
}
|
||
|
|
||
|
static void tsif_notify(void *data)
|
||
|
{
|
||
|
struct tsif_chrdev *the_dev = data;
|
||
|
tsif_get_state(the_dev->cookie, &the_dev->ri, &the_dev->wi,
|
||
|
&the_dev->state);
|
||
|
wake_up_interruptible(&the_dev->wq_read);
|
||
|
}
|
||
|
|
||
|
static const struct file_operations tsif_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.read = tsif_read,
|
||
|
.open = tsif_open,
|
||
|
.release = tsif_release,
|
||
|
};
|
||
|
|
||
|
static struct class *tsif_class;
|
||
|
static dev_t tsif_dev; /**< 1-st dev_t from allocated range */
|
||
|
static dev_t tsif_dev0; /**< next not yet assigned dev_t */
|
||
|
|
||
|
static int tsif_init_one(struct tsif_chrdev *the_dev, int index)
|
||
|
{
|
||
|
int rc;
|
||
|
pr_info("%s[%d]\n", __func__, index);
|
||
|
cdev_init(&the_dev->cdev, &tsif_fops);
|
||
|
the_dev->cdev.owner = THIS_MODULE;
|
||
|
init_waitqueue_head(&the_dev->wq_read);
|
||
|
rc = cdev_add(&the_dev->cdev, tsif_dev0++, 1);
|
||
|
the_dev->dev = device_create(tsif_class, NULL, the_dev->cdev.dev,
|
||
|
the_dev, "tsif%d", index);
|
||
|
if (IS_ERR(the_dev->dev)) {
|
||
|
rc = PTR_ERR(the_dev->dev);
|
||
|
pr_err("device_create failed: %d\n", rc);
|
||
|
goto err_create;
|
||
|
}
|
||
|
the_dev->cookie = tsif_attach(index, tsif_notify, the_dev);
|
||
|
if (IS_ERR(the_dev->cookie)) {
|
||
|
rc = PTR_ERR(the_dev->cookie);
|
||
|
pr_err("tsif_attach failed: %d\n", rc);
|
||
|
goto err_attach;
|
||
|
}
|
||
|
/* now data buffer is not allocated yet */
|
||
|
tsif_get_info(the_dev->cookie, &the_dev->data_buffer, NULL);
|
||
|
dev_info(the_dev->dev,
|
||
|
"Device %d.%d attached to TSIF, buffer size %d\n",
|
||
|
MAJOR(the_dev->cdev.dev), MINOR(the_dev->cdev.dev),
|
||
|
the_dev->buf_size_packets);
|
||
|
return 0;
|
||
|
err_attach:
|
||
|
device_destroy(tsif_class, the_dev->cdev.dev);
|
||
|
err_create:
|
||
|
cdev_del(&the_dev->cdev);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static void tsif_exit_one(struct tsif_chrdev *the_dev)
|
||
|
{
|
||
|
dev_info(the_dev->dev, "%s\n", __func__);
|
||
|
tsif_detach(the_dev->cookie);
|
||
|
device_destroy(tsif_class, the_dev->cdev.dev);
|
||
|
cdev_del(&the_dev->cdev);
|
||
|
}
|
||
|
|
||
|
#define TSIF_NUM_DEVS 1 /**< support this many devices */
|
||
|
|
||
|
struct tsif_chrdev the_devices[TSIF_NUM_DEVS];
|
||
|
|
||
|
static int __init mod_init(void)
|
||
|
{
|
||
|
int rc;
|
||
|
int instance;
|
||
|
rc = alloc_chrdev_region(&tsif_dev, 0, TSIF_NUM_DEVS, "tsif");
|
||
|
if (rc) {
|
||
|
pr_err("alloc_chrdev_region failed: %d\n", rc);
|
||
|
goto err_devrgn;
|
||
|
}
|
||
|
tsif_dev0 = tsif_dev;
|
||
|
tsif_class = class_create(THIS_MODULE, "tsif");
|
||
|
if (IS_ERR(tsif_class)) {
|
||
|
rc = PTR_ERR(tsif_class);
|
||
|
pr_err("Error creating tsif class: %d\n", rc);
|
||
|
goto err_class;
|
||
|
}
|
||
|
instance = tsif_get_active();
|
||
|
if (instance >= 0)
|
||
|
rc = tsif_init_one(&the_devices[0], instance);
|
||
|
else
|
||
|
rc = instance;
|
||
|
if (rc)
|
||
|
goto err_init1;
|
||
|
return 0;
|
||
|
err_init1:
|
||
|
class_destroy(tsif_class);
|
||
|
err_class:
|
||
|
unregister_chrdev_region(tsif_dev, TSIF_NUM_DEVS);
|
||
|
err_devrgn:
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static void __exit mod_exit(void)
|
||
|
{
|
||
|
tsif_exit_one(&the_devices[0]);
|
||
|
class_destroy(tsif_class);
|
||
|
unregister_chrdev_region(tsif_dev, TSIF_NUM_DEVS);
|
||
|
}
|
||
|
|
||
|
module_init(mod_init);
|
||
|
module_exit(mod_exit);
|
||
|
|
||
|
MODULE_DESCRIPTION("TSIF character device interface");
|
||
|
MODULE_LICENSE("GPL v2");
|
||
|
|