diff options
author | Suren A. Chilingaryan <csa@dside.dyndns.org> | 2011-02-13 03:07:11 +0100 |
---|---|---|
committer | Suren A. Chilingaryan <csa@dside.dyndns.org> | 2011-02-13 03:07:11 +0100 |
commit | b72e8567723c30f0e5a83675743e9380c48e5d55 (patch) | |
tree | f0dc052508543771fca08c358836507770864249 /driver/ioctl.c | |
download | ipecamera-b72e8567723c30f0e5a83675743e9380c48e5d55.tar.gz ipecamera-b72e8567723c30f0e5a83675743e9380c48e5d55.tar.bz2 ipecamera-b72e8567723c30f0e5a83675743e9380c48e5d55.tar.xz ipecamera-b72e8567723c30f0e5a83675743e9380c48e5d55.zip |
Initial import
Diffstat (limited to 'driver/ioctl.c')
-rw-r--r-- | driver/ioctl.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/driver/ioctl.c b/driver/ioctl.c new file mode 100644 index 0000000..59d9245 --- /dev/null +++ b/driver/ioctl.c @@ -0,0 +1,446 @@ +/** + * + * @file ioctl.c + * @author Guillermo Marcus + * @date 2009-04-05 + * @brief Contains the functions handling the different ioctl calls. + * + */ +#include <linux/version.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/sysfs.h> +#include <asm/atomic.h> +#include <linux/pagemap.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <asm/scatterlist.h> +#include <linux/vmalloc.h> +#include <linux/stat.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/sched.h> + +#include "config.h" /* Configuration for the driver */ +#include "compat.h" /* Compatibility functions/definitions */ +#include "pciDriver.h" /* External interface for the driver */ +#include "common.h" /* Internal definitions for all parts */ +#include "kmem.h" /* Internal definitions for kernel memory */ +#include "umem.h" /* Internal definitions for user space memory */ +#include "ioctl.h" /* Internal definitions for the ioctl part */ + +/** Declares a variable of the given type with the given name and copies it from userspace */ +#define READ_FROM_USER(type, name) \ + type name; \ + if ((ret = copy_from_user(&name, (type*)arg, sizeof(name))) != 0) \ + return -EFAULT; + +/** Writes back the given variable with the given type to userspace */ +#define WRITE_TO_USER(type, name) \ + if ((ret = copy_to_user((type*)arg, &name, sizeof(name))) != 0) \ + return -EFAULT; + +/** + * + * Sets the mmap mode for following mmap() calls. + * + * @param arg Not a pointer, but either PCIDRIVER_MMAP_PCI or PCIDRIVER_MMAP_KMEM + * + */ +static int ioctl_mmap_mode(pcidriver_privdata_t *privdata, unsigned long arg) +{ + if ((arg != PCIDRIVER_MMAP_PCI) && (arg != PCIDRIVER_MMAP_KMEM)) + return -EINVAL; + + /* change the mode */ + privdata->mmap_mode = arg; + + return 0; +} + +/** + * + * Sets the mmap area (BAR) for following mmap() calls. + * + */ +static int ioctl_mmap_area(pcidriver_privdata_t *privdata, unsigned long arg) +{ + /* validate input */ + if ((arg < PCIDRIVER_BAR0) || (arg > PCIDRIVER_BAR5)) + return -EINVAL; + + /* change the PCI area to mmap */ + privdata->mmap_area = arg; + + return 0; +} + +/** + * + * Reads/writes a byte/word/dword of the device's PCI config. + * + * @see pcidriver_pci_read + * @see pcidriver_pci_write + * + */ +static int ioctl_pci_config_read_write(pcidriver_privdata_t *privdata, unsigned int cmd, unsigned long arg) +{ + int ret; + READ_FROM_USER(pci_cfg_cmd, pci_cmd); + + if (cmd == PCIDRIVER_IOC_PCI_CFG_RD) { + switch (pci_cmd.size) { + case PCIDRIVER_PCI_CFG_SZ_BYTE: + ret = pci_read_config_byte( privdata->pdev, pci_cmd.addr, &(pci_cmd.val.byte) ); + break; + case PCIDRIVER_PCI_CFG_SZ_WORD: + ret = pci_read_config_word( privdata->pdev, pci_cmd.addr, &(pci_cmd.val.word) ); + break; + case PCIDRIVER_PCI_CFG_SZ_DWORD: + ret = pci_read_config_dword( privdata->pdev, pci_cmd.addr, &(pci_cmd.val.dword) ); + break; + default: + return -EINVAL; /* Wrong size setting */ + } + } else { + switch (pci_cmd.size) { + case PCIDRIVER_PCI_CFG_SZ_BYTE: + ret = pci_write_config_byte( privdata->pdev, pci_cmd.addr, pci_cmd.val.byte ); + break; + case PCIDRIVER_PCI_CFG_SZ_WORD: + ret = pci_write_config_word( privdata->pdev, pci_cmd.addr, pci_cmd.val.word ); + break; + case PCIDRIVER_PCI_CFG_SZ_DWORD: + ret = pci_write_config_dword( privdata->pdev, pci_cmd.addr, pci_cmd.val.dword ); + break; + default: + return -EINVAL; /* Wrong size setting */ + break; + } + } + + WRITE_TO_USER(pci_cfg_cmd, pci_cmd); + + return 0; +} + +/** + * + * Gets the PCI information for the device. + * + * @see pcidriver_pci_info + * + */ +static int ioctl_pci_info(pcidriver_privdata_t *privdata, unsigned long arg) +{ + int ret; + int bar; + READ_FROM_USER(pci_board_info, pci_info); + + pci_info.vendor_id = privdata->pdev->vendor; + pci_info.device_id = privdata->pdev->device; + pci_info.bus = privdata->pdev->bus->number; + pci_info.slot = PCI_SLOT(privdata->pdev->devfn); + pci_info.devfn = privdata->pdev->devfn; + + if ((ret = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_PIN, &(pci_info.interrupt_pin))) != 0) + return ret; + + if ((ret = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_LINE, &(pci_info.interrupt_line))) != 0) + return ret; + + for (bar = 0; bar < 6; bar++) { + pci_info.bar_start[bar] = pci_resource_start(privdata->pdev, bar); + pci_info.bar_length[bar] = pci_resource_len(privdata->pdev, bar); + } + + WRITE_TO_USER(pci_board_info, pci_info); + + return 0; +} + +/** + * + * Allocates kernel memory. + * + * @see pcidriver_kmem_alloc + * + */ +static int ioctl_kmem_alloc(pcidriver_privdata_t *privdata, unsigned long arg) +{ + int ret; + READ_FROM_USER(kmem_handle_t, khandle); + + if ((ret = pcidriver_kmem_alloc(privdata, &khandle)) != 0) + return ret; + + WRITE_TO_USER(kmem_handle_t, khandle); + + return 0; +} + +/** + * + * Frees kernel memory. + * + * @see pcidriver_kmem_free + * + */ +static int ioctl_kmem_free(pcidriver_privdata_t *privdata, unsigned long arg) +{ + int ret; + READ_FROM_USER(kmem_handle_t, khandle); + + if ((ret = pcidriver_kmem_free(privdata, &khandle)) != 0) + return ret; + + return 0; +} + +/** + * + * Syncs kernel memory. + * + * @see pcidriver_kmem_sync + * + */ +static int ioctl_kmem_sync(pcidriver_privdata_t *privdata, unsigned long arg) +{ + int ret; + READ_FROM_USER(kmem_sync_t, ksync); + + return pcidriver_kmem_sync(privdata, &ksync); +} + +/* + * + * Maps the given scatter/gather list from memory to PCI bus addresses. + * + * @see pcidriver_umem_sgmap + * + */ +static int ioctl_umem_sgmap(pcidriver_privdata_t *privdata, unsigned long arg) +{ + int ret; + READ_FROM_USER(umem_handle_t, uhandle); + + if ((ret = pcidriver_umem_sgmap(privdata, &uhandle)) != 0) + return ret; + + WRITE_TO_USER(umem_handle_t, uhandle); + + return 0; +} + +/** + * + * Unmaps the given scatter/gather list. + * + * @see pcidriver_umem_sgunmap + * + */ +static int ioctl_umem_sgunmap(pcidriver_privdata_t *privdata, unsigned long arg) +{ + int ret; + pcidriver_umem_entry_t *umem_entry; + READ_FROM_USER(umem_handle_t, uhandle); + + /* Find the associated umem_entry for this buffer, + * return -EINVAL if the specified handle id is invalid */ + if ((umem_entry = pcidriver_umem_find_entry_id(privdata, uhandle.handle_id)) == NULL) + return -EINVAL; + + if ((ret = pcidriver_umem_sgunmap(privdata, umem_entry)) != 0) + return ret; + + return 0; +} + +/** + * + * Copies the scatter/gather list from kernelspace to userspace. + * + * @see pcidriver_umem_sgget + * + */ +static int ioctl_umem_sgget(pcidriver_privdata_t *privdata, unsigned long arg) +{ + int ret; + READ_FROM_USER(umem_sglist_t, usglist); + + /* The umem_sglist_t has a pointer to the scatter/gather list itself which + * needs to be copied separately. The number of elements is stored in ->nents. + * As the list can get very big, we need to use vmalloc. */ + if ((usglist.sg = vmalloc(usglist.nents * sizeof(umem_sgentry_t))) == NULL) + return -ENOMEM; + + /* copy array to kernel structure */ + ret = copy_from_user(usglist.sg, ((umem_sglist_t *)arg)->sg, (usglist.nents)*sizeof(umem_sgentry_t)); + if (ret) return -EFAULT; + + if ((ret = pcidriver_umem_sgget(privdata, &usglist)) != 0) + return ret; + + /* write data to user space */ + ret = copy_to_user(((umem_sglist_t *)arg)->sg, usglist.sg, (usglist.nents)*sizeof(umem_sgentry_t)); + if (ret) return -EFAULT; + + /* free array memory */ + vfree(usglist.sg); + + /* restore sg pointer to vma address in user space before copying */ + usglist.sg = ((umem_sglist_t *)arg)->sg; + + WRITE_TO_USER(umem_sglist_t, usglist); + + return 0; +} + +/** + * + * Syncs user memory. + * + * @see pcidriver_umem_sync + * + */ +static int ioctl_umem_sync(pcidriver_privdata_t *privdata, unsigned long arg) +{ + int ret; + READ_FROM_USER(umem_handle_t, uhandle); + + return pcidriver_umem_sync( privdata, &uhandle ); +} + +/** + * + * Waits for an interrupt + * + * @param arg Not a pointer, but the irq source to wait for (unsigned int) + * + */ +static int ioctl_wait_interrupt(pcidriver_privdata_t *privdata, unsigned long arg) +{ +#ifdef ENABLE_IRQ + unsigned int irq_source; + int temp; + + if (arg >= PCIDRIVER_INT_MAXSOURCES) + return -EFAULT; /* User tried to overrun the IRQ_SOURCES array */ + + irq_source = arg; + + /* Thanks to Joern for the correction and tips! */ + /* done this way to avoid wrong behaviour (endless loop) of the compiler in AMD platforms */ + temp=1; + while (temp) { + /* We wait here with an interruptible timeout. This will be interrupted + * by int.c:check_acknowledge_channel() as soon as in interrupt for + * the specified source arrives. */ + wait_event_interruptible_timeout( (privdata->irq_queues[irq_source]), (atomic_read(&(privdata->irq_outstanding[irq_source])) > 0), (10*HZ/1000) ); + + if (atomic_add_negative( -1, &(privdata->irq_outstanding[irq_source])) ) + atomic_inc( &(privdata->irq_outstanding[irq_source]) ); + else + temp =0; + } + + return 0; +#else + mod_info("Asked to wait for interrupt but interrupts are not enabled in the driver\n"); + return -EFAULT; +#endif +} + +/** + * + * Clears the interrupt wait queue. + * + * @param arg Not a pointer, but the irq source (unsigned int) + * @returns -EFAULT if the user specified an irq source out of range + * + */ +static int ioctl_clear_ioq(pcidriver_privdata_t *privdata, unsigned long arg) +{ +#ifdef ENABLE_IRQ + unsigned int irq_source; + + if (arg >= PCIDRIVER_INT_MAXSOURCES) + return -EFAULT; + + irq_source = arg; + atomic_set(&(privdata->irq_outstanding[irq_source]), 0); + + return 0; +#else + mod_info("Asked to wait for interrupt but interrupts are not enabled in the driver\n"); + return -EFAULT; +#endif +} + +/** + * + * This function handles all ioctl file operations. + * Generally, the data of the ioctl is copied from userspace to kernelspace, a separate + * function is called to handle the ioctl itself, then the data is copied back to userspace. + * + * @returns -EFAULT when an invalid memory pointer is passed + * + */ +int pcidriver_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + pcidriver_privdata_t *privdata = filp->private_data; + + /* Select the appropiate command */ + switch (cmd) { + case PCIDRIVER_IOC_MMAP_MODE: + return ioctl_mmap_mode(privdata, arg); + + case PCIDRIVER_IOC_MMAP_AREA: + return ioctl_mmap_area(privdata, arg); + + case PCIDRIVER_IOC_PCI_CFG_RD: + case PCIDRIVER_IOC_PCI_CFG_WR: + return ioctl_pci_config_read_write(privdata, cmd, arg); + + case PCIDRIVER_IOC_PCI_INFO: + return ioctl_pci_info(privdata, arg); + + case PCIDRIVER_IOC_KMEM_ALLOC: + return ioctl_kmem_alloc(privdata, arg); + + case PCIDRIVER_IOC_KMEM_FREE: + return ioctl_kmem_free(privdata, arg); + + case PCIDRIVER_IOC_KMEM_SYNC: + return ioctl_kmem_sync(privdata, arg); + + case PCIDRIVER_IOC_UMEM_SGMAP: + return ioctl_umem_sgmap(privdata, arg); + + case PCIDRIVER_IOC_UMEM_SGUNMAP: + return ioctl_umem_sgunmap(privdata, arg); + + case PCIDRIVER_IOC_UMEM_SGGET: + return ioctl_umem_sgget(privdata, arg); + + case PCIDRIVER_IOC_UMEM_SYNC: + return ioctl_umem_sync(privdata, arg); + + case PCIDRIVER_IOC_WAITI: + return ioctl_wait_interrupt(privdata, arg); + + case PCIDRIVER_IOC_CLEAR_IOQ: + return ioctl_clear_ioq(privdata, arg); + + default: + return -EINVAL; + } +} |