diff options
Diffstat (limited to 'package/lqtapi/src/tapi/tapi-control.c')
-rw-r--r-- | package/lqtapi/src/tapi/tapi-control.c | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/package/lqtapi/src/tapi/tapi-control.c b/package/lqtapi/src/tapi/tapi-control.c new file mode 100644 index 0000000000..a4f245e92f --- /dev/null +++ b/package/lqtapi/src/tapi/tapi-control.c @@ -0,0 +1,193 @@ +#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/tapi/tapi.h> +#include <linux/tapi/tapi-ioctl.h> + +/* FIXME Does it acutally make sense to allow more then one application at a + * time to open the control device? For example calling sync from one app will + * also sync all others. */ + +struct tapi_control_file { + struct tapi_device *tdev; + struct list_head links; +}; + +static struct tapi_endpoint *tapi_lookup_endpoint(struct tapi_device *tdev, + unsigned int ep_id) +{ + struct tapi_stream *stream; + + if (ep_id < tdev->num_ports) + return &tdev->ports[ep_id].ep; + + list_for_each_entry(stream, &tdev->streams, head) { + if (stream->ep.id == ep_id) + return &stream->ep; + } + + return ERR_PTR(-ENOENT); +} + +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_control_open(struct inode *inode, struct file *file) +{ + int ret; + struct tapi_device *tdev = inode_to_tdev(inode); + struct tapi_control_file *tctrl; + + get_device(&tdev->dev); + + tctrl = kzalloc(sizeof(*tctrl), GFP_KERNEL); + if (!tctrl) { + ret = -ENOMEM; + goto err_put_device; + } + + INIT_LIST_HEAD(&tctrl->links); + tctrl->tdev = tdev; + + file->private_data = tctrl; + + return 0; + +err_put_device: + put_device(&tdev->dev); + + return ret; +} + +static int tapi_control_release(struct inode *inode, struct file *file) +{ + struct tapi_control_file *tctrl = file->private_data; + struct tapi_link *link; + + if (tctrl) { + list_for_each_entry(link, &tctrl->links, head) + tapi_link_free(tctrl->tdev, link); + + put_device(&tctrl->tdev->dev); + } + + return 0; +} + +static long tapi_control_ioctl_link_alloc(struct tapi_control_file *tctrl, + unsigned long arg) +{ + struct tapi_link *link; + struct tapi_endpoint *ep1, *ep2; + + ep1 = tapi_lookup_endpoint(tctrl->tdev, arg >> 16); + ep2 = tapi_lookup_endpoint(tctrl->tdev, arg & 0xffff); + + link = tapi_link_alloc(tctrl->tdev, ep1, ep2); + if (IS_ERR(link)) + return PTR_ERR(link); + + list_add_tail(&link->head, &tctrl->links); + + return link->id; +} + +struct tapi_link *tapi_control_lookup_link(struct tapi_control_file *tctrl, + unsigned int id) +{ + struct tapi_link *link; + + list_for_each_entry(link, &tctrl->links, head) { + if (link->id == id) + return link; + } + + return NULL; +} + +static long tapi_control_ioctl_link_free(struct tapi_control_file *tctrl, + unsigned long arg) +{ + struct tapi_link *link = tapi_control_lookup_link(tctrl, arg); + if (!link) + return -ENOENT; + + tapi_link_free(tctrl->tdev, link); + list_del(&link->head); + + return 0; +} + +static long tapi_control_ioctl_link_enable(struct tapi_control_file *tctrl, + unsigned long arg) +{ + struct tapi_link *link = tapi_control_lookup_link(tctrl, arg); + if (!link) + return -ENOENT; + + return tapi_link_enable(tctrl->tdev, link); +} + +static long tapi_control_ioctl_link_disable(struct tapi_control_file *tctrl, + unsigned long arg) +{ + struct tapi_link *link = tapi_control_lookup_link(tctrl, arg); + if (!link) + return -ENOENT; + + return tapi_link_disable(tctrl->tdev, link); +} + +static long tapi_control_ioctl_sync(struct tapi_control_file *tctrl) +{ + return tapi_sync(tctrl->tdev); +} + +static long tapi_control_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret; + struct tapi_control_file *tctrl = file->private_data; + + switch (cmd) { + case TAPI_CONTROL_IOCTL_LINK_ALLOC: + ret = tapi_control_ioctl_link_alloc(tctrl, arg); + break; + case TAPI_CONTROL_IOCTL_LINK_FREE: + ret = tapi_control_ioctl_link_free(tctrl, arg); + break; + case TAPI_CONTROL_IOCTL_LINK_ENABLE: + ret = tapi_control_ioctl_link_enable(tctrl, arg); + break; + case TAPI_CONTROL_IOCTL_LINK_DISABLE: + ret = tapi_control_ioctl_link_disable(tctrl, arg); + break; + case TAPI_CONTROL_IOCTL_SYNC: + ret = tapi_control_ioctl_sync(tctrl); + break; + default: + return -EINVAL; + } + + return ret; +} + +static const struct file_operations tapi_control_file_ops = { + .owner = THIS_MODULE, + .open = tapi_control_open, + .release = tapi_control_release, + .unlocked_ioctl = tapi_control_ioctl, +}; + +int tapi_register_control_device(struct tapi_device* tdev) +{ + dev_set_name(&tdev->control_dev.dev, "tapi%uC", tdev->id); + return tapi_char_device_register(tdev, &tdev->control_dev, &tapi_control_file_ops); +} |