Changes in 4.9.231 KVM: s390: reduce number of IO pins to 1 gpu: host1x: Detach driver on unregister spi: spidev: fix a race between spidev_release and spidev_remove spi: spidev: fix a potential use-after-free in spidev_release() s390/kasan: fix early pgm check handler execution cifs: update ctime and mtime during truncate ARM: imx6: add missing put_device() call in imx6q_suspend_init() scsi: mptscsih: Fix read sense data size net: cxgb4: fix return error value in t4_prep_fw smsc95xx: check return value of smsc95xx_reset smsc95xx: avoid memory leak in smsc95xx_bind ALSA: compress: fix partial_drain completion state arm64: kgdb: Fix single-step exception handling oops bnxt_en: fix NULL dereference in case SR-IOV configuration fails net: macb: mark device wake capable when "magic-packet" property present ALSA: opl3: fix infoleak in opl3 ALSA: hda - let hs_mic be picked ahead of hp_mic ALSA: usb-audio: add quirk for MacroSilicon MS2109 KVM: arm64: Fix definition of PAGE_HYP_DEVICE KVM: x86: bit 8 of non-leaf PDPEs is not reserved Revert "ath9k: Fix general protection fault in ath9k_hif_usb_rx_cb" btrfs: fix fatal extent_buffer readahead vs releasepage race drm/radeon: fix double free ARC: entry: fix potential EFA clobber when TIF_SYSCALL_TRACE ARC: elf: use right ELF_ARCH s390/mm: fix huge pte soft dirty copying ipv4: fill fl4_icmp_{type,code} in ping_v4_sendmsg l2tp: remove skb_dst_set() from l2tp_xmit_skb() llc: make sure applications use ARPHRD_ETHER net: Added pointer check for dst->ops->neigh_lookup in dst_neigh_lookup_skb net: usb: qmi_wwan: add support for Quectel EG95 LTE modem tcp: md5: add missing memory barriers in tcp_md5_do_add()/tcp_md5_hash_key() tcp: md5: refine tcp_md5_do_add()/tcp_md5_hash_key() barriers genetlink: remove genl_bind tcp: make sure listeners don't initialize congestion-control state tcp: md5: do not send silly options in SYNCOOKIES tcp: md5: allow changing MD5 keys in all socket states cgroup: fix cgroup_sk_alloc() for sk_clone_lock() cgroup: Fix sock_cgroup_data on big-endian. i2c: eg20t: Load module automatically if ID matches iio:magnetometer:ak8974: Fix alignment and data leak issues iio: magnetometer: ak8974: Fix runtime PM imbalance on error iio: mma8452: Add missed iio_device_unregister() call in mma8452_probe() iio: pressure: zpa2326: handle pm_runtime_get_sync failure iio:pressure:ms5611 Fix buffer element alignment iio:health:afe4403 Fix timestamp alignment and prevent data leak. spi: fix initial SPI_SR value in spi-fsl-dspi net: dsa: bcm_sf2: Fix node reference count Revert "usb/ehci-platform: Set PM runtime as active on resume" Revert "usb/xhci-plat: Set PM runtime as active on resume" Revert "usb/ohci-platform: Fix a warning when hibernating" iio:health:afe4404 Fix timestamp alignment and prevent data leak. spi: spi-sun6i: sun6i_spi_transfer_one(): fix setting of clock rate usb: gadget: udc: atmel: fix uninitialized read in debug printk staging: comedi: verify array index is correct before using it Revert "thermal: mediatek: fix register index error" ARM: dts: socfpga: Align L2 cache-controller nodename with dtschema perf stat: Zero all the 'ena' and 'run' array slot stats for interval mode mtd: rawnand: brcmnand: fix CS0 layout HID: magicmouse: do not set up autorepeat usb: core: Add a helper function to check the validity of EP type in URB ALSA: line6: Perform sanity check for each URB creation ALSA: usb-audio: Fix race against the error recovery URB submission USB: c67x00: fix use after free in c67x00_giveback_urb usb: dwc2: Fix shutdown callback in platform usb: chipidea: core: add wakeup support for extcon usb: gadget: function: fix missing spinlock in f_uac1_legacy USB: serial: iuu_phoenix: fix memory corruption USB: serial: cypress_m8: enable Simply Automated UPB PIM USB: serial: ch341: add new Product ID for CH340 USB: serial: option: add GosunCn GM500 series USB: serial: option: add Quectel EG95 LTE modem virtio: virtio_console: add missing MODULE_DEVICE_TABLE() for rproc serial fuse: Fix parameter for FS_IOC_{GET,SET}FLAGS mei: bus: don't clean driver pointer Input: i8042 - add Lenovo XiaoXin Air 12 to i8042 nomux list uio_pdrv_genirq: fix use without device tree and no interrupt timer: Fix wheel index calculation on last level MIPS: Fix build for LTS kernel caused by backporting lpj adjustment hwmon: (emc2103) fix unable to change fan pwm1_enable attribute dmaengine: fsl-edma: Fix NULL pointer exception in fsl_edma_tx_handler misc: atmel-ssc: lock with mutex instead of spinlock arm64: ptrace: Override SPSR.SS when single-stepping is enabled sched/fair: handle case of task_h_load() returning 0 irqchip/gic: Atomically update affinity x86/cpu: Move x86_cache_bits settings Linux 4.9.231 Signed-off-by: Greg Kroah-Hartman <gregkh@google.com> Change-Id: I57b0f3c4ffcdc49231d1c48f3f25fae424daeeb9
280 lines
7.4 KiB
C
280 lines
7.4 KiB
C
/*
|
|
* drivers/uio/uio_pdrv_genirq.c
|
|
*
|
|
* Userspace I/O platform driver with generic IRQ handling code.
|
|
*
|
|
* Copyright (C) 2008 Magnus Damm
|
|
*
|
|
* Based on uio_pdrv.c by Uwe Kleine-Koenig,
|
|
* Copyright (C) 2008 by Digi International Inc.
|
|
* 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 as published by
|
|
* the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/uio_driver.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/module.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/stringify.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/of_address.h>
|
|
|
|
#define DRIVER_NAME "uio_pdrv_genirq"
|
|
|
|
struct uio_pdrv_genirq_platdata {
|
|
struct uio_info *uioinfo;
|
|
spinlock_t lock;
|
|
unsigned long flags;
|
|
struct platform_device *pdev;
|
|
};
|
|
|
|
/* Bits in uio_pdrv_genirq_platdata.flags */
|
|
enum {
|
|
UIO_IRQ_DISABLED = 0,
|
|
};
|
|
|
|
static int uio_pdrv_genirq_open(struct uio_info *info, struct inode *inode)
|
|
{
|
|
struct uio_pdrv_genirq_platdata *priv = info->priv;
|
|
|
|
/* Wait until the Runtime PM code has woken up the device */
|
|
pm_runtime_get_sync(&priv->pdev->dev);
|
|
return 0;
|
|
}
|
|
|
|
static int uio_pdrv_genirq_release(struct uio_info *info, struct inode *inode)
|
|
{
|
|
struct uio_pdrv_genirq_platdata *priv = info->priv;
|
|
|
|
/* Tell the Runtime PM code that the device has become idle */
|
|
pm_runtime_put_sync(&priv->pdev->dev);
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t uio_pdrv_genirq_handler(int irq, struct uio_info *dev_info)
|
|
{
|
|
struct uio_pdrv_genirq_platdata *priv = dev_info->priv;
|
|
|
|
/* Just disable the interrupt in the interrupt controller, and
|
|
* remember the state so we can allow user space to enable it later.
|
|
*/
|
|
|
|
spin_lock(&priv->lock);
|
|
if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags))
|
|
disable_irq_nosync(irq);
|
|
spin_unlock(&priv->lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int uio_pdrv_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on)
|
|
{
|
|
struct uio_pdrv_genirq_platdata *priv = dev_info->priv;
|
|
unsigned long flags;
|
|
|
|
/* Allow user space to enable and disable the interrupt
|
|
* in the interrupt controller, but keep track of the
|
|
* state to prevent per-irq depth damage.
|
|
*
|
|
* Serialize this operation to support multiple tasks and concurrency
|
|
* with irq handler on SMP systems.
|
|
*/
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
if (irq_on) {
|
|
if (__test_and_clear_bit(UIO_IRQ_DISABLED, &priv->flags))
|
|
enable_irq(dev_info->irq);
|
|
} else {
|
|
if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags))
|
|
disable_irq_nosync(dev_info->irq);
|
|
}
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int uio_pdrv_genirq_probe(struct platform_device *pdev)
|
|
{
|
|
struct uio_info *uioinfo = dev_get_platdata(&pdev->dev);
|
|
struct uio_pdrv_genirq_platdata *priv;
|
|
struct uio_mem *uiomem;
|
|
int ret = -EINVAL;
|
|
int i;
|
|
|
|
if (pdev->dev.of_node) {
|
|
/* alloc uioinfo for one device */
|
|
uioinfo = devm_kzalloc(&pdev->dev, sizeof(*uioinfo),
|
|
GFP_KERNEL);
|
|
if (!uioinfo) {
|
|
dev_err(&pdev->dev, "unable to kmalloc\n");
|
|
return -ENOMEM;
|
|
}
|
|
uioinfo->name = pdev->dev.of_node->name;
|
|
uioinfo->version = "devicetree";
|
|
/* Multiple IRQs are not supported */
|
|
}
|
|
|
|
if (!uioinfo || !uioinfo->name || !uioinfo->version) {
|
|
dev_err(&pdev->dev, "missing platform_data\n");
|
|
return ret;
|
|
}
|
|
|
|
if (uioinfo->handler || uioinfo->irqcontrol ||
|
|
uioinfo->irq_flags & IRQF_SHARED) {
|
|
dev_err(&pdev->dev, "interrupt configuration error\n");
|
|
return ret;
|
|
}
|
|
|
|
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv) {
|
|
dev_err(&pdev->dev, "unable to kmalloc\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->uioinfo = uioinfo;
|
|
spin_lock_init(&priv->lock);
|
|
priv->flags = 0; /* interrupt is enabled to begin with */
|
|
priv->pdev = pdev;
|
|
|
|
if (!uioinfo->irq) {
|
|
ret = platform_get_irq(pdev, 0);
|
|
uioinfo->irq = ret;
|
|
if (ret == -ENXIO)
|
|
uioinfo->irq = UIO_IRQ_NONE;
|
|
else if (ret < 0) {
|
|
dev_err(&pdev->dev, "failed to get IRQ\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
uiomem = &uioinfo->mem[0];
|
|
|
|
for (i = 0; i < pdev->num_resources; ++i) {
|
|
struct resource *r = &pdev->resource[i];
|
|
|
|
if (r->flags != IORESOURCE_MEM)
|
|
continue;
|
|
|
|
if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) {
|
|
dev_warn(&pdev->dev, "device has more than "
|
|
__stringify(MAX_UIO_MAPS)
|
|
" I/O memory resources.\n");
|
|
break;
|
|
}
|
|
|
|
uiomem->memtype = UIO_MEM_PHYS;
|
|
uiomem->addr = r->start;
|
|
uiomem->size = resource_size(r);
|
|
uiomem->name = r->name;
|
|
++uiomem;
|
|
}
|
|
|
|
while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) {
|
|
uiomem->size = 0;
|
|
++uiomem;
|
|
}
|
|
|
|
/* This driver requires no hardware specific kernel code to handle
|
|
* interrupts. Instead, the interrupt handler simply disables the
|
|
* interrupt in the interrupt controller. User space is responsible
|
|
* for performing hardware specific acknowledge and re-enabling of
|
|
* the interrupt in the interrupt controller.
|
|
*
|
|
* Interrupt sharing is not supported.
|
|
*/
|
|
|
|
uioinfo->handler = uio_pdrv_genirq_handler;
|
|
uioinfo->irqcontrol = uio_pdrv_genirq_irqcontrol;
|
|
uioinfo->open = uio_pdrv_genirq_open;
|
|
uioinfo->release = uio_pdrv_genirq_release;
|
|
uioinfo->priv = priv;
|
|
|
|
/* Enable Runtime PM for this device:
|
|
* The device starts in suspended state to allow the hardware to be
|
|
* turned off by default. The Runtime PM bus code should power on the
|
|
* hardware and enable clocks at open().
|
|
*/
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
ret = uio_register_device(&pdev->dev, priv->uioinfo);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "unable to register uio device\n");
|
|
pm_runtime_disable(&pdev->dev);
|
|
return ret;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, priv);
|
|
return 0;
|
|
}
|
|
|
|
static int uio_pdrv_genirq_remove(struct platform_device *pdev)
|
|
{
|
|
struct uio_pdrv_genirq_platdata *priv = platform_get_drvdata(pdev);
|
|
|
|
uio_unregister_device(priv->uioinfo);
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
priv->uioinfo->handler = NULL;
|
|
priv->uioinfo->irqcontrol = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int uio_pdrv_genirq_runtime_nop(struct device *dev)
|
|
{
|
|
/* Runtime PM callback shared between ->runtime_suspend()
|
|
* and ->runtime_resume(). Simply returns success.
|
|
*
|
|
* In this driver pm_runtime_get_sync() and pm_runtime_put_sync()
|
|
* are used at open() and release() time. This allows the
|
|
* Runtime PM code to turn off power to the device while the
|
|
* device is unused, ie before open() and after release().
|
|
*
|
|
* This Runtime PM callback does not need to save or restore
|
|
* any registers since user space is responsbile for hardware
|
|
* register reinitialization after open().
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = {
|
|
.runtime_suspend = uio_pdrv_genirq_runtime_nop,
|
|
.runtime_resume = uio_pdrv_genirq_runtime_nop,
|
|
};
|
|
|
|
#ifdef CONFIG_OF
|
|
static struct of_device_id uio_of_genirq_match[] = {
|
|
{ /* This is filled with module_parm */ },
|
|
{ /* Sentinel */ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, uio_of_genirq_match);
|
|
module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0);
|
|
MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio");
|
|
#endif
|
|
|
|
static struct platform_driver uio_pdrv_genirq = {
|
|
.probe = uio_pdrv_genirq_probe,
|
|
.remove = uio_pdrv_genirq_remove,
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.pm = &uio_pdrv_genirq_dev_pm_ops,
|
|
.of_match_table = of_match_ptr(uio_of_genirq_match),
|
|
},
|
|
};
|
|
|
|
module_platform_driver(uio_pdrv_genirq);
|
|
|
|
MODULE_AUTHOR("Magnus Damm");
|
|
MODULE_DESCRIPTION("Userspace I/O platform driver with generic IRQ handling");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:" DRIVER_NAME);
|