diff options
Diffstat (limited to 'package/lqtapi/src/tapi/tapi-stream.c')
-rw-r--r-- | package/lqtapi/src/tapi/tapi-stream.c | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/package/lqtapi/src/tapi/tapi-stream.c b/package/lqtapi/src/tapi/tapi-stream.c new file mode 100644 index 000000000..7277a5c61 --- /dev/null +++ b/package/lqtapi/src/tapi/tapi-stream.c @@ -0,0 +1,201 @@ +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/list.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/poll.h> + +#include <linux/tapi/tapi.h> +#include <linux/tapi/tapi-ioctl.h> + + +struct tapi_stream_file { + struct tapi_device *tdev; + struct tapi_stream *stream; +}; + +static inline struct tapi_device *inode_to_tdev(struct inode *inode) +{ + return container_of(inode->i_cdev, struct tapi_char_device, cdev)->tdev; +} + +static int tapi_stream_open(struct inode *inode, struct file *file) +{ + int ret; + struct tapi_device *tdev = inode_to_tdev(inode); + struct tapi_stream_file *stream; + + get_device(&tdev->dev); + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) { + ret = -ENOMEM; + goto err_put; + } + + stream->stream = tapi_stream_alloc(tdev); + if (IS_ERR(stream->stream)) { + ret = PTR_ERR(stream->stream); + goto err_free; + } + stream->tdev = tdev; + + init_waitqueue_head(&stream->stream->recv_wait); + skb_queue_head_init(&stream->stream->recv_queue); + + file->private_data = stream; + + return 0; + +err_free: + kfree(stream); +err_put: + put_device(&tdev->dev); + return ret; +} + +static int tapi_stream_release(struct inode *inode, struct file *file) +{ + struct tapi_stream_file *stream = file->private_data; + + if (stream) { + tapi_stream_free(stream->tdev, stream->stream); + put_device(&stream->tdev->dev); + kfree(stream); + } + + return 0; +} + +static long tapi_stream_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + struct tapi_stream_file *stream = file->private_data; + struct tapi_device *tdev = stream->tdev; + + switch (cmd) { + case TAPI_STREAM_IOCTL_GET_ENDPOINT: + ret = stream->stream->ep.id; + break; + case TAPI_STREAM_IOCTL_CONFIGURE: + break; + case TAPI_STREAM_IOCTL_START: + ret = tapi_stream_start(tdev, stream->stream); + break; + case TAPI_STREAM_IOCTL_STOP: + ret = tapi_stream_stop(tdev, stream->stream); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static unsigned int tapi_stream_poll(struct file *file, struct poll_table_struct *wait) +{ + struct tapi_stream_file *stream = file->private_data; + int ret; + + poll_wait(file, &stream->stream->recv_wait, wait); + + ret = POLLOUT; + + if (!skb_queue_empty(&stream->stream->recv_queue)) + ret |= POLLIN; + + return ret; +} + +static ssize_t tapi_stream_read(struct file *file, char __user *buffer, + size_t count, loff_t *offset) +{ + struct tapi_stream_file *stream = file->private_data; + struct sk_buff *skb; + + skb = skb_dequeue(&stream->stream->recv_queue); + if (!skb) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + do { + interruptible_sleep_on(&stream->stream->recv_wait); + skb = skb_dequeue(&stream->stream->recv_queue); + } while (skb == NULL && !signal_pending(current)); + + if (skb == NULL) + return -ERESTARTNOHAND; + } + + if (skb->len > count) { + skb_queue_head(&stream->stream->recv_queue, skb); + return -EMSGSIZE; + } + + if (copy_to_user(buffer, skb->data, skb->len)) { + skb_queue_head(&stream->stream->recv_queue, skb); + return -EFAULT; + } + + count = skb->len; + + kfree_skb(skb); + + return count; +} + +static ssize_t tapi_stream_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct tapi_stream_file *stream = file->private_data; + struct tapi_device *tdev = stream->tdev; + struct sk_buff *skb; + + if (count == 0) + return 0; + + skb = alloc_skb(count, GFP_USER); + if (!skb) + return -ENOMEM; + + if (copy_from_user(skb_put(skb, count), buffer, count)) { + kfree_skb(skb); + return -EFAULT; + } + + tdev->ops->stream_send(tdev, stream->stream, skb); + + return count; +} + +static const struct file_operations tapi_stream_file_ops = { + .owner = THIS_MODULE, + .read = tapi_stream_read, + .write = tapi_stream_write, + .open = tapi_stream_open, + .release = tapi_stream_release, + .poll = tapi_stream_poll, + .unlocked_ioctl = tapi_stream_ioctl, +}; + +int tapi_register_stream_device(struct tapi_device* tdev) +{ + dev_set_name(&tdev->stream_dev.dev, "tapi%uS", tdev->id); + return tapi_char_device_register(tdev, &tdev->stream_dev, &tapi_stream_file_ops); +} + +int tapi_stream_recv(struct tapi_device *tdev, struct tapi_stream * stream, + struct sk_buff *skb) +{ + skb_queue_tail(&stream->recv_queue, skb); + wake_up(&stream->recv_wait); + + return 0; +} +EXPORT_SYMBOL_GPL(tapi_stream_recv); |