/* Copyright (c) 2013 Freescale Semiconductor, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Freescale Semiconductor nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * * ALTERNATIVELY, this software may be distributed under the terms of the * GNU General Public License ("GPL") as published by the Free Software * Foundation, either version 2 of that License or (at your option) any * later version. * * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* define a device that allows USPDAA processes to open a file descriptor and specify which IRQ it wants to montior using an ioctl() When an IRQ is received, the device becomes readable so that a process can use read() or select() type calls to monitor for IRQs */ #include #include #include #include #include #include #include #include #include #include #include #include "qman_low.h" #include "bman_low.h" struct usdpaa_irq_ctx { int irq_set; /* Set to true once the irq is set via ioctl */ unsigned int irq_num; u32 last_irq_count; /* Last value returned from read */ u32 irq_count; /* Number of irqs since last read */ wait_queue_head_t wait_queue; /* Waiting processes */ spinlock_t lock; void *inhibit_addr; /* inhibit register address */ struct file *usdpaa_filp; }; static int usdpaa_irq_open(struct inode *inode, struct file *filp) { struct usdpaa_irq_ctx *ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; ctx->irq_set = 0; ctx->irq_count = 0; ctx->last_irq_count = 0; init_waitqueue_head(&ctx->wait_queue); spin_lock_init(&ctx->lock); filp->private_data = ctx; return 0; } static int usdpaa_irq_release(struct inode *inode, struct file *filp) { struct usdpaa_irq_ctx *ctx = filp->private_data; if (ctx->irq_set) { /* Inhibit the IRQ */ out_be32(ctx->inhibit_addr, 0x1); irq_set_affinity_hint(ctx->irq_num, NULL); free_irq(ctx->irq_num, ctx); ctx->irq_set = 0; fput(ctx->usdpaa_filp); } kfree(filp->private_data); return 0; } static irqreturn_t usdpaa_irq_handler(int irq, void *_ctx) { unsigned long flags; struct usdpaa_irq_ctx *ctx = _ctx; spin_lock_irqsave(&ctx->lock, flags); ++ctx->irq_count; spin_unlock_irqrestore(&ctx->lock, flags); wake_up_all(&ctx->wait_queue); /* Set the inhibit register. This will be reenabled once the USDPAA code handles the IRQ */ out_be32(ctx->inhibit_addr, 0x1); return IRQ_HANDLED; } static int map_irq(struct file *fp, struct usdpaa_ioctl_irq_map *irq_map) { struct usdpaa_irq_ctx *ctx = fp->private_data; int ret; if (ctx->irq_set) { pr_debug("Setting USDPAA IRQ when it was already set!\n"); return -EBUSY; } ctx->usdpaa_filp = fget(irq_map->fd); if (!ctx->usdpaa_filp) { pr_debug("USDPAA fget(%d) returned NULL\n", irq_map->fd); return -EINVAL; } ret = usdpaa_get_portal_config(ctx->usdpaa_filp, irq_map->portal_cinh, irq_map->type, &ctx->irq_num, &ctx->inhibit_addr); if (ret) { pr_debug("USDPAA IRQ couldn't identify portal\n"); fput(ctx->usdpaa_filp); return ret; } ctx->irq_set = 1; ret = request_irq(ctx->irq_num, usdpaa_irq_handler, 0, "usdpaa_irq", ctx); if (ret) { pr_err("USDPAA request_irq(%d) failed, ret= %d\n", ctx->irq_num, ret); ctx->irq_set = 0; fput(ctx->usdpaa_filp); return ret; } ret = irq_set_affinity(ctx->irq_num, tsk_cpus_allowed(current)); if (ret) pr_err("USDPAA irq_set_affinity() failed, ret= %d\n", ret); ret = irq_set_affinity_hint(ctx->irq_num, tsk_cpus_allowed(current)); if (ret) pr_err("USDPAA irq_set_affinity_hint() failed, ret= %d\n", ret); return 0; } static long usdpaa_irq_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { int ret; struct usdpaa_ioctl_irq_map irq_map; if (cmd != USDPAA_IOCTL_PORTAL_IRQ_MAP) { pr_debug("USDPAA IRQ unknown command 0x%x\n", cmd); return -EINVAL; } ret = copy_from_user(&irq_map, (void __user *)arg, sizeof(irq_map)); if (ret) return ret; return map_irq(fp, &irq_map); } static ssize_t usdpaa_irq_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) { struct usdpaa_irq_ctx *ctx = filp->private_data; int ret; if (!ctx->irq_set) { pr_debug("Reading USDPAA IRQ before it was set\n"); return -EINVAL; } if (count < sizeof(ctx->irq_count)) { pr_debug("USDPAA IRQ Read too small\n"); return -EINVAL; } if (ctx->irq_count == ctx->last_irq_count) { if (filp->f_flags & O_NONBLOCK) return -EAGAIN; ret = wait_event_interruptible(ctx->wait_queue, ctx->irq_count != ctx->last_irq_count); if (ret == -ERESTARTSYS) return ret; } ctx->last_irq_count = ctx->irq_count; if (copy_to_user(buff, &ctx->last_irq_count, sizeof(ctx->last_irq_count))) return -EFAULT; return sizeof(ctx->irq_count); } static unsigned int usdpaa_irq_poll(struct file *filp, poll_table *wait) { struct usdpaa_irq_ctx *ctx = filp->private_data; unsigned int ret = 0; unsigned long flags; if (!ctx->irq_set) return POLLHUP; poll_wait(filp, &ctx->wait_queue, wait); spin_lock_irqsave(&ctx->lock, flags); if (ctx->irq_count != ctx->last_irq_count) ret |= POLLIN | POLLRDNORM; spin_unlock_irqrestore(&ctx->lock, flags); return ret; } static long usdpaa_irq_ioctl_compat(struct file *fp, unsigned int cmd, unsigned long arg) { #ifdef CONFIG_COMPAT void __user *a = (void __user *)arg; #endif switch (cmd) { #ifdef CONFIG_COMPAT case USDPAA_IOCTL_PORTAL_IRQ_MAP_COMPAT: { struct compat_ioctl_irq_map input; struct usdpaa_ioctl_irq_map converted; if (copy_from_user(&input, a, sizeof(input))) return -EFAULT; converted.type = input.type; converted.fd = input.fd; converted.portal_cinh = compat_ptr(input.portal_cinh); return map_irq(fp, &converted); } #endif default: return usdpaa_irq_ioctl(fp, cmd, arg); } } static const struct file_operations usdpaa_irq_fops = { .open = usdpaa_irq_open, .release = usdpaa_irq_release, .unlocked_ioctl = usdpaa_irq_ioctl, .compat_ioctl = usdpaa_irq_ioctl_compat, .read = usdpaa_irq_read, .poll = usdpaa_irq_poll }; static struct miscdevice usdpaa_miscdev = { .name = "fsl-usdpaa-irq", .fops = &usdpaa_irq_fops, .minor = MISC_DYNAMIC_MINOR, }; static int __init usdpaa_irq_init(void) { int ret; pr_info("Freescale USDPAA process IRQ driver\n"); ret = misc_register(&usdpaa_miscdev); if (ret) pr_err("fsl-usdpaa-irq: failed to register misc device\n"); return ret; } static void __exit usdpaa_irq_exit(void) { misc_deregister(&usdpaa_miscdev); } module_init(usdpaa_irq_init); module_exit(usdpaa_irq_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Freescale Semiconductor"); MODULE_DESCRIPTION("Freescale USDPAA process IRQ driver");