Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig UIO
tristate "Userspace I/O drivers"
depends on MMU
help
Enable this to allow the userspace driver core code to be
built. This code allows userspace programs easy access to
kernel interrupts and memory locations, allowing some drivers
to be written in userspace. Note that a small kernel driver
is also required for interrupt handling to work properly.
If you don't know what to do here, say N.
if UIO
config UIO_CIF
tristate "generic Hilscher CIF Card driver"
depends on PCI
help
Driver for Hilscher CIF DeviceNet and Profibus cards. This
driver requires a userspace component called cif that handles
all of the heavy lifting and can be found at:
<http://www.osadl.org/projects/downloads/UIO/user/>
To compile this driver as a module, choose M here: the module
will be called uio_cif.
config UIO_PDRV_GENIRQ
tristate "Userspace I/O platform driver with generic IRQ handling"
help
Platform driver for Userspace I/O devices, including generic
interrupt handling code. Shared interrupts are not supported.
This kernel driver requires that the matching userspace driver
handles interrupts in a special way. Userspace is responsible
for acknowledging the hardware device if needed, and re-enabling
interrupts in the interrupt controller using the write() syscall.
If you don't know what to do here, say N.
config UIO_DMEM_GENIRQ
tristate "Userspace platform driver with generic irq and dynamic memory"
depends on HAS_DMA
help
Platform driver for Userspace I/O devices, including generic
interrupt handling code. Shared interrupts are not supported.
Memory regions can be specified with the same platform device
resources as the UIO_PDRV drivers, but dynamic regions can also
be specified.
The number and size of these regions is static,
but the memory allocation is not performed until
the associated device file is opened. The
memory is freed once the uio device is closed.
If you don't know what to do here, say N.
config UIO_AEC
tristate "AEC video timestamp device"
depends on PCI
help
UIO driver for the Adrienne Electronics Corporation PCI time
code device.
This device differs from other UIO devices since it uses I/O
ports instead of memory mapped I/O. In order to make it
possible for UIO to work with this device a utility, uioport,
can be used to read and write the ports:
git clone git://ifup.org/philips/uioport.git
If you compile this as a module, it will be called uio_aec.
config UIO_SERCOS3
tristate "Automata Sercos III PCI card driver"
depends on PCI
help
Userspace I/O interface for the Sercos III PCI card from
Automata GmbH. The userspace part of this driver will be
available for download from the Automata GmbH web site.
Automata GmbH: http://www.automataweb.com
Sercos III interface: http://www.sercos.com
If you compile this as a module, it will be called uio_sercos3.
config UIO_PCI_GENERIC
tristate "Generic driver for PCI 2.3 and PCI Express cards"
depends on PCI
help
Generic driver that you can bind, dynamically, to any
PCI 2.3 compliant and PCI Express card. It is useful,
primarily, for virtualization scenarios.
If you compile this as a module, it will be called uio_pci_generic.
config UIO_NETX
tristate "Hilscher NetX Card driver"
depends on PCI
help
Driver for Hilscher NetX based fieldbus cards (cifX, comX).
This driver requires a userspace component that comes with the card
or is available from Hilscher (http://www.hilscher.com).
To compile this driver as a module, choose M here; the module
will be called uio_netx.
config UIO_FSL_ELBC_GPCM
tristate "eLBC/GPCM driver"
depends on FSL_LBC
help
Generic driver for accessing a peripheral connected to an eLBC port
that is running in GPCM mode. GPCM is an interface for simple lower
performance memories and memory-mapped devices. For devices using
FCM or UPM eLBC modes, other device-specific drivers are available.
config UIO_FSL_ELBC_GPCM_NETX5152
bool "eLBC/GPCM netX 51/52 support"
depends on UIO_FSL_ELBC_GPCM
help
This will add support for netX 51/52 devices connected via eLBC/GPCM.
In particular, it implements interrupt handling. This can be used
together with the userspace netX stack from Hilscher.
Information about this hardware can be found at:
http://www.hilscher.com/netx
config UIO_MF624
tristate "Humusoft MF624 DAQ PCI card driver"
depends on PCI
help
Userspace I/O interface for the Humusoft MF624 PCI card.
A sample userspace application using this driver is available
(among other MF624 related information and software components)
for download in a git repository:
git clone git://rtime.felk.cvut.cz/mf6xx.git
If you compile this as a module, it will be called uio_mf624.
config UIO_HV_GENERIC
tristate "Generic driver for Hyper-V VMBus"
depends on HYPERV_VMBUS
help
Generic driver that you can bind, dynamically, to any
Hyper-V VMBus device. It is useful to provide direct access
to network and storage devices from userspace.
If you compile this as a module, it will be called uio_hv_generic.
config UIO_DFL
tristate "Generic driver for DFL (Device Feature List) bus"
depends on FPGA_DFL
help
Generic DFL (Device Feature List) driver for Userspace I/O devices.
It is useful to provide direct access to DFL devices from userspace.
A sample userspace application using this driver is available for
download in a git repository:
git clone https://github.com/OPAE/opae-sdk.git
It could be found at:
opae-sdk/tools/libopaeuio/
If you compile this as a module, it will be called uio_dfl.
config UIO_PCI_GENERIC_SVA
tristate "Generic driver for PCI Express that supports sva"
depends on PCI && IOMMU_SVA
help
Userspace I/O driver for PCI devices that support Shared Virtual
Addressing (SVA), enabling direct use of user-space virtual
addresses in device DMA operations via IOMMU hardware.
This driver binds to PCI devices and exposes them to userspace
via the UIO framework.
endif
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_UIO) += uio.o
obj-$(CONFIG_UIO_CIF) += uio_cif.o
obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_genirq.o
obj-$(CONFIG_UIO_DMEM_GENIRQ) += uio_dmem_genirq.o
obj-$(CONFIG_UIO_AEC) += uio_aec.o
obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o
obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o
obj-$(CONFIG_UIO_NETX) += uio_netx.o
obj-$(CONFIG_UIO_MF624) += uio_mf624.o
obj-$(CONFIG_UIO_FSL_ELBC_GPCM) += uio_fsl_elbc_gpcm.o
obj-$(CONFIG_UIO_HV_GENERIC) += uio_hv_generic.o
obj-$(CONFIG_UIO_DFL) += uio_dfl.o
obj-$(CONFIG_UIO_PCI_GENERIC_SVA) += uio_pci_generic_sva.o
File diff suppressed because it is too large Load Diff
+148
View File
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* uio_aec.c -- simple driver for Adrienne Electronics Corp time code PCI device
*
* Copyright (C) 2008 Brandon Philips <brandon@ifup.org>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/uio_driver.h>
#include <linux/slab.h>
#define PCI_VENDOR_ID_AEC 0xaecb
#define PCI_DEVICE_ID_AEC_VITCLTC 0x6250
#define INT_ENABLE_ADDR 0xFC
#define INT_ENABLE 0x10
#define INT_DISABLE 0x0
#define INT_MASK_ADDR 0x2E
#define INT_MASK_ALL 0x3F
#define INTA_DRVR_ADDR 0xFE
#define INTA_ENABLED_FLAG 0x08
#define INTA_FLAG 0x01
#define MAILBOX 0x0F
static const struct pci_device_id ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AEC, PCI_DEVICE_ID_AEC_VITCLTC), },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, ids);
static irqreturn_t aectc_irq(int irq, struct uio_info *dev_info)
{
void __iomem *int_flag = dev_info->priv + INTA_DRVR_ADDR;
unsigned char status = ioread8(int_flag);
if ((status & INTA_ENABLED_FLAG) && (status & INTA_FLAG)) {
/* application writes 0x00 to 0x2F to get next interrupt */
status = ioread8(dev_info->priv + MAILBOX);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static void print_board_data(struct pci_dev *pdev, struct uio_info *i)
{
dev_info(&pdev->dev, "PCI-TC board vendor: %x%x number: %x%x"
" revision: %c%c\n",
ioread8(i->priv + 0x01),
ioread8(i->priv + 0x00),
ioread8(i->priv + 0x03),
ioread8(i->priv + 0x02),
ioread8(i->priv + 0x06),
ioread8(i->priv + 0x07));
}
static int probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct uio_info *info;
int ret;
info = devm_kzalloc(&pdev->dev, sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
if (pci_enable_device(pdev))
return -ENODEV;
if (pci_request_regions(pdev, "aectc"))
goto out_disable;
info->name = "aectc";
info->port[0].start = pci_resource_start(pdev, 0);
if (!info->port[0].start)
goto out_release;
info->priv = pci_iomap(pdev, 0, 0);
if (!info->priv)
goto out_release;
info->port[0].size = pci_resource_len(pdev, 0);
info->port[0].porttype = UIO_PORT_GPIO;
info->version = "0.0.1";
info->irq = pdev->irq;
info->irq_flags = IRQF_SHARED;
info->handler = aectc_irq;
print_board_data(pdev, info);
ret = uio_register_device(&pdev->dev, info);
if (ret)
goto out_unmap;
iowrite32(INT_ENABLE, info->priv + INT_ENABLE_ADDR);
iowrite8(INT_MASK_ALL, info->priv + INT_MASK_ADDR);
if (!(ioread8(info->priv + INTA_DRVR_ADDR)
& INTA_ENABLED_FLAG))
dev_err(&pdev->dev, "aectc: interrupts not enabled\n");
pci_set_drvdata(pdev, info);
return 0;
out_unmap:
pci_iounmap(pdev, info->priv);
out_release:
pci_release_regions(pdev);
out_disable:
pci_disable_device(pdev);
return -ENODEV;
}
static void remove(struct pci_dev *pdev)
{
struct uio_info *info = pci_get_drvdata(pdev);
/* disable interrupts */
iowrite8(INT_DISABLE, info->priv + INT_MASK_ADDR);
iowrite32(INT_DISABLE, info->priv + INT_ENABLE_ADDR);
/* read mailbox to ensure board drops irq */
ioread8(info->priv + MAILBOX);
uio_unregister_device(info);
pci_release_regions(pdev);
pci_disable_device(pdev);
pci_iounmap(pdev, info->priv);
}
static struct pci_driver pci_driver = {
.name = "aectc",
.id_table = ids,
.probe = probe,
.remove = remove,
};
module_pci_driver(pci_driver);
MODULE_DESCRIPTION("Adrienne Electronics Corp time code PCI device");
MODULE_LICENSE("GPL");
+135
View File
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: GPL-2.0
/*
* UIO Hilscher CIF card driver
*
* (C) 2007 Hans J. Koch <hjk@hansjkoch.de>
* Original code (C) 2005 Benedikt Spranger <b.spranger@linutronix.de>
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/uio_driver.h>
#include <asm/io.h>
#define PLX9030_INTCSR 0x4C
#define INTSCR_INT1_ENABLE 0x01
#define INTSCR_INT1_STATUS 0x04
#define INT1_ENABLED_AND_ACTIVE (INTSCR_INT1_ENABLE | INTSCR_INT1_STATUS)
#define PCI_SUBVENDOR_ID_PEP 0x1518
#define CIF_SUBDEVICE_PROFIBUS 0x430
#define CIF_SUBDEVICE_DEVICENET 0x432
static irqreturn_t hilscher_handler(int irq, struct uio_info *dev_info)
{
void __iomem *plx_intscr = dev_info->mem[0].internal_addr
+ PLX9030_INTCSR;
if ((ioread8(plx_intscr) & INT1_ENABLED_AND_ACTIVE)
!= INT1_ENABLED_AND_ACTIVE)
return IRQ_NONE;
/* Disable interrupt */
iowrite8(ioread8(plx_intscr) & ~INTSCR_INT1_ENABLE, plx_intscr);
return IRQ_HANDLED;
}
static int hilscher_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
struct uio_info *info;
info = devm_kzalloc(&dev->dev, sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
if (pci_enable_device(dev))
return -ENODEV;
if (pci_request_regions(dev, "hilscher"))
goto out_disable;
info->mem[0].addr = pci_resource_start(dev, 0);
if (!info->mem[0].addr)
goto out_release;
info->mem[0].internal_addr = pci_ioremap_bar(dev, 0);
if (!info->mem[0].internal_addr)
goto out_release;
info->mem[0].size = pci_resource_len(dev, 0);
info->mem[0].memtype = UIO_MEM_PHYS;
info->mem[1].addr = pci_resource_start(dev, 2);
info->mem[1].size = pci_resource_len(dev, 2);
info->mem[1].memtype = UIO_MEM_PHYS;
switch (id->subdevice) {
case CIF_SUBDEVICE_PROFIBUS:
info->name = "CIF_Profibus";
break;
case CIF_SUBDEVICE_DEVICENET:
info->name = "CIF_Devicenet";
break;
default:
info->name = "CIF_???";
}
info->version = "0.0.1";
info->irq = dev->irq;
info->irq_flags = IRQF_SHARED;
info->handler = hilscher_handler;
if (uio_register_device(&dev->dev, info))
goto out_unmap;
pci_set_drvdata(dev, info);
return 0;
out_unmap:
iounmap(info->mem[0].internal_addr);
out_release:
pci_release_regions(dev);
out_disable:
pci_disable_device(dev);
return -ENODEV;
}
static void hilscher_pci_remove(struct pci_dev *dev)
{
struct uio_info *info = pci_get_drvdata(dev);
uio_unregister_device(info);
pci_release_regions(dev);
pci_disable_device(dev);
iounmap(info->mem[0].internal_addr);
}
static const struct pci_device_id hilscher_pci_ids[] = {
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = PCI_SUBVENDOR_ID_PEP,
.subdevice = CIF_SUBDEVICE_PROFIBUS,
},
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = PCI_SUBVENDOR_ID_PEP,
.subdevice = CIF_SUBDEVICE_DEVICENET,
},
{ 0, }
};
static struct pci_driver hilscher_pci_driver = {
.name = "hilscher",
.id_table = hilscher_pci_ids,
.probe = hilscher_pci_probe,
.remove = hilscher_pci_remove,
};
module_pci_driver(hilscher_pci_driver);
MODULE_DEVICE_TABLE(pci, hilscher_pci_ids);
MODULE_DESCRIPTION("UIO Hilscher CIF card driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Hans J. Koch, Benedikt Spranger");
+72
View File
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Generic DFL driver for Userspace I/O devicess
*
* Copyright (C) 2021 Intel Corporation, Inc.
*/
#include <linux/dfl.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/uio_driver.h>
#define DRIVER_NAME "uio_dfl"
static int uio_dfl_probe(struct dfl_device *ddev)
{
struct resource *r = &ddev->mmio_res;
struct device *dev = &ddev->dev;
struct uio_info *uioinfo;
struct uio_mem *uiomem;
int ret;
uioinfo = devm_kzalloc(dev, sizeof(struct uio_info), GFP_KERNEL);
if (!uioinfo)
return -ENOMEM;
uioinfo->name = DRIVER_NAME;
uioinfo->version = "0";
uiomem = &uioinfo->mem[0];
uiomem->memtype = UIO_MEM_PHYS;
uiomem->addr = r->start & PAGE_MASK;
uiomem->offs = r->start & ~PAGE_MASK;
uiomem->size = (uiomem->offs + resource_size(r)
+ PAGE_SIZE - 1) & PAGE_MASK;
uiomem->name = r->name;
/* Irq is yet to be supported */
uioinfo->irq = UIO_IRQ_NONE;
ret = devm_uio_register_device(dev, uioinfo);
if (ret)
dev_err(dev, "unable to register uio device\n");
return ret;
}
#define FME_FEATURE_ID_ETH_GROUP 0x10
#define FME_FEATURE_ID_HSSI_SUBSYS 0x15
#define FME_FEATURE_ID_VENDOR_SPECIFIC 0x23
#define PORT_FEATURE_ID_IOPLL_USRCLK 0x14
static const struct dfl_device_id uio_dfl_ids[] = {
{ FME_ID, FME_FEATURE_ID_ETH_GROUP },
{ FME_ID, FME_FEATURE_ID_HSSI_SUBSYS },
{ FME_ID, FME_FEATURE_ID_VENDOR_SPECIFIC },
{ PORT_ID, PORT_FEATURE_ID_IOPLL_USRCLK },
{ }
};
MODULE_DEVICE_TABLE(dfl, uio_dfl_ids);
static struct dfl_driver uio_dfl_driver = {
.drv = {
.name = DRIVER_NAME,
},
.id_table = uio_dfl_ids,
.probe = uio_dfl_probe,
};
module_dfl_driver(uio_dfl_driver);
MODULE_DESCRIPTION("Generic DFL driver for Userspace I/O devices");
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL v2");
+320
View File
@@ -0,0 +1,320 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* drivers/uio/uio_dmem_genirq.c
*
* Userspace I/O platform driver with generic IRQ handling code.
*
* Copyright (C) 2012 Damian Hobson-Garcia
*
* Based on uio_pdrv_genirq.c by Magnus Damm
*/
#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/platform_data/uio_dmem_genirq.h>
#include <linux/stringify.h>
#include <linux/pm_runtime.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#define DRIVER_NAME "uio_dmem_genirq"
#define DMEM_MAP_ERROR (~0)
struct uio_dmem_genirq_platdata {
struct uio_info *uioinfo;
spinlock_t lock;
unsigned long flags;
struct platform_device *pdev;
unsigned int dmem_region_start;
unsigned int num_dmem_regions;
struct mutex alloc_lock;
unsigned int refcnt;
};
/* Bits in uio_dmem_genirq_platdata.flags */
enum {
UIO_IRQ_DISABLED = 0,
};
static int uio_dmem_genirq_open(struct uio_info *info, struct inode *inode)
{
struct uio_dmem_genirq_platdata *priv = info->priv;
struct uio_mem *uiomem;
uiomem = &priv->uioinfo->mem[priv->dmem_region_start];
mutex_lock(&priv->alloc_lock);
while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) {
void *addr;
if (!uiomem->size)
break;
addr = dma_alloc_coherent(&priv->pdev->dev, uiomem->size,
&uiomem->dma_addr, GFP_KERNEL);
uiomem->addr = addr ? (uintptr_t) addr : DMEM_MAP_ERROR;
++uiomem;
}
priv->refcnt++;
mutex_unlock(&priv->alloc_lock);
/* Wait until the Runtime PM code has woken up the device */
pm_runtime_get_sync(&priv->pdev->dev);
return 0;
}
static int uio_dmem_genirq_release(struct uio_info *info, struct inode *inode)
{
struct uio_dmem_genirq_platdata *priv = info->priv;
struct uio_mem *uiomem;
/* Tell the Runtime PM code that the device has become idle */
pm_runtime_put_sync(&priv->pdev->dev);
uiomem = &priv->uioinfo->mem[priv->dmem_region_start];
mutex_lock(&priv->alloc_lock);
priv->refcnt--;
while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) {
if (!uiomem->size)
break;
if (uiomem->addr) {
dma_free_coherent(uiomem->dma_device, uiomem->size,
(void *) (uintptr_t) uiomem->addr,
uiomem->dma_addr);
}
uiomem->addr = DMEM_MAP_ERROR;
++uiomem;
}
mutex_unlock(&priv->alloc_lock);
return 0;
}
static irqreturn_t uio_dmem_genirq_handler(int irq, struct uio_info *dev_info)
{
struct uio_dmem_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_dmem_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on)
{
struct uio_dmem_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 void uio_dmem_genirq_pm_disable(void *data)
{
struct device *dev = data;
pm_runtime_disable(dev);
}
static int uio_dmem_genirq_probe(struct platform_device *pdev)
{
struct uio_dmem_genirq_pdata *pdata = dev_get_platdata(&pdev->dev);
struct uio_info *uioinfo = &pdata->uioinfo;
struct uio_dmem_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 = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%pOFn",
pdev->dev.of_node);
if (!uioinfo->name)
return -ENOMEM;
uioinfo->version = "devicetree";
}
if (!uioinfo || !uioinfo->name || !uioinfo->version) {
dev_err(&pdev->dev, "missing platform_data\n");
return -EINVAL;
}
if (uioinfo->handler || uioinfo->irqcontrol ||
uioinfo->irq_flags & IRQF_SHARED) {
dev_err(&pdev->dev, "interrupt configuration error\n");
return -EINVAL;
}
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(&pdev->dev, "unable to kmalloc\n");
return -ENOMEM;
}
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(&pdev->dev, "DMA enable failed\n");
return ret;
}
priv->uioinfo = uioinfo;
spin_lock_init(&priv->lock);
priv->flags = 0; /* interrupt is enabled to begin with */
priv->pdev = pdev;
mutex_init(&priv->alloc_lock);
if (!uioinfo->irq) {
/* Multiple IRQs are not supported */
ret = platform_get_irq(pdev, 0);
if (ret == -ENXIO && pdev->dev.of_node)
ret = UIO_IRQ_NONE;
else if (ret < 0)
return ret;
uioinfo->irq = ret;
}
if (uioinfo->irq) {
/*
* If a level interrupt, dont do lazy disable. Otherwise the
* irq will fire again since clearing of the actual cause, on
* device level, is done in userspace
* irqd_is_level_type() isn't used since isn't valid until
* irq is configured.
*/
if (irq_get_trigger_type(uioinfo->irq) & IRQ_TYPE_LEVEL_MASK) {
dev_dbg(&pdev->dev, "disable lazy unmask\n");
irq_set_status_flags(uioinfo->irq, IRQ_DISABLE_UNLAZY);
}
}
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;
}
priv->dmem_region_start = uiomem - &uioinfo->mem[0];
priv->num_dmem_regions = pdata->num_dynamic_regions;
for (i = 0; i < pdata->num_dynamic_regions; ++i) {
if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) {
dev_warn(&pdev->dev, "device has more than "
__stringify(MAX_UIO_MAPS)
" dynamic and fixed memory regions.\n");
break;
}
uiomem->memtype = UIO_MEM_DMA_COHERENT;
uiomem->dma_device = &pdev->dev;
uiomem->addr = DMEM_MAP_ERROR;
uiomem->size = pdata->dynamic_region_sizes[i];
++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_dmem_genirq_handler;
uioinfo->irqcontrol = uio_dmem_genirq_irqcontrol;
uioinfo->open = uio_dmem_genirq_open;
uioinfo->release = uio_dmem_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 = devm_add_action_or_reset(&pdev->dev, uio_dmem_genirq_pm_disable, &pdev->dev);
if (ret)
return ret;
return devm_uio_register_device(&pdev->dev, priv->uioinfo);
}
#ifdef CONFIG_OF
static const struct of_device_id uio_of_genirq_match[] = {
{ /* empty for now */ },
};
MODULE_DEVICE_TABLE(of, uio_of_genirq_match);
#endif
static struct platform_driver uio_dmem_genirq = {
.probe = uio_dmem_genirq_probe,
.driver = {
.name = DRIVER_NAME,
.of_match_table = of_match_ptr(uio_of_genirq_match),
},
};
module_platform_driver(uio_dmem_genirq);
MODULE_AUTHOR("Damian Hobson-Garcia");
MODULE_DESCRIPTION("Userspace I/O platform driver with dynamic memory.");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRIVER_NAME);
+469
View File
@@ -0,0 +1,469 @@
// SPDX-License-Identifier: GPL-2.0
/* uio_fsl_elbc_gpcm: UIO driver for eLBC/GPCM peripherals
Copyright (C) 2014 Linutronix GmbH
Author: John Ogness <john.ogness@linutronix.de>
This driver provides UIO access to memory of a peripheral connected
to the Freescale enhanced local bus controller (eLBC) interface
using the general purpose chip-select mode (GPCM).
Here is an example of the device tree entries:
localbus@ffe05000 {
ranges = <0x2 0x0 0x0 0xff810000 0x10000>;
dpm@2,0 {
compatible = "fsl,elbc-gpcm-uio";
reg = <0x2 0x0 0x10000>;
elbc-gpcm-br = <0xff810800>;
elbc-gpcm-or = <0xffff09f7>;
interrupt-parent = <&mpic>;
interrupts = <4 1>;
device_type = "netx5152";
uio_name = "netx_custom";
netx5152,init-win0-offset = <0x0>;
};
};
Only the entries reg (to identify bank) and elbc-gpcm-* (initial BR/OR
values) are required. The entries interrupt*, device_type, and uio_name
are optional (as well as any type-specific options such as
netx5152,init-win0-offset). As long as no interrupt handler is needed,
this driver can be used without any type-specific implementation.
The netx5152 type has been tested to work with the netX 51/52 hardware
from Hilscher using the Hilscher userspace netX stack.
The netx5152 type should serve as a model to add new type-specific
devices as needed.
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/uio_driver.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/fsl_lbc.h>
#define MAX_BANKS 8
struct fsl_elbc_gpcm {
struct device *dev;
struct fsl_lbc_regs __iomem *lbc;
u32 bank;
const char *name;
void (*init)(struct uio_info *info);
void (*shutdown)(struct uio_info *info, bool init_err);
irqreturn_t (*irq_handler)(int irq, struct uio_info *info);
};
static ssize_t reg_show(struct device *dev, struct device_attribute *attr,
char *buf);
static ssize_t reg_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
static DEVICE_ATTR(reg_br, 0664, reg_show, reg_store);
static DEVICE_ATTR(reg_or, 0664, reg_show, reg_store);
static struct attribute *uio_fsl_elbc_gpcm_attrs[] = {
&dev_attr_reg_br.attr,
&dev_attr_reg_or.attr,
NULL,
};
ATTRIBUTE_GROUPS(uio_fsl_elbc_gpcm);
static ssize_t reg_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct uio_info *info = dev_get_drvdata(dev);
struct fsl_elbc_gpcm *priv = info->priv;
struct fsl_lbc_bank *bank = &priv->lbc->bank[priv->bank];
if (attr == &dev_attr_reg_br) {
return scnprintf(buf, PAGE_SIZE, "0x%08x\n",
in_be32(&bank->br));
} else if (attr == &dev_attr_reg_or) {
return scnprintf(buf, PAGE_SIZE, "0x%08x\n",
in_be32(&bank->or));
}
return 0;
}
static ssize_t reg_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct uio_info *info = dev_get_drvdata(dev);
struct fsl_elbc_gpcm *priv = info->priv;
struct fsl_lbc_bank *bank = &priv->lbc->bank[priv->bank];
unsigned long val;
u32 reg_br_cur;
u32 reg_or_cur;
u32 reg_new;
/* parse use input */
if (kstrtoul(buf, 0, &val) != 0)
return -EINVAL;
reg_new = (u32)val;
/* read current values */
reg_br_cur = in_be32(&bank->br);
reg_or_cur = in_be32(&bank->or);
if (attr == &dev_attr_reg_br) {
/* not allowed to change effective base address */
if ((reg_br_cur & reg_or_cur & BR_BA) !=
(reg_new & reg_or_cur & BR_BA)) {
return -EINVAL;
}
/* not allowed to change mode */
if ((reg_new & BR_MSEL) != BR_MS_GPCM)
return -EINVAL;
/* write new value (force valid) */
out_be32(&bank->br, reg_new | BR_V);
} else if (attr == &dev_attr_reg_or) {
/* not allowed to change access mask */
if ((reg_or_cur & OR_GPCM_AM) != (reg_new & OR_GPCM_AM))
return -EINVAL;
/* write new value */
out_be32(&bank->or, reg_new);
} else {
return -EINVAL;
}
return count;
}
#ifdef CONFIG_UIO_FSL_ELBC_GPCM_NETX5152
#define DPM_HOST_WIN0_OFFSET 0xff00
#define DPM_HOST_INT_STAT0 0xe0
#define DPM_HOST_INT_EN0 0xf0
#define DPM_HOST_INT_MASK 0xe600ffff
#define DPM_HOST_INT_GLOBAL_EN 0x80000000
static irqreturn_t netx5152_irq_handler(int irq, struct uio_info *info)
{
void __iomem *reg_int_en = info->mem[0].internal_addr +
DPM_HOST_WIN0_OFFSET +
DPM_HOST_INT_EN0;
void __iomem *reg_int_stat = info->mem[0].internal_addr +
DPM_HOST_WIN0_OFFSET +
DPM_HOST_INT_STAT0;
/* check if an interrupt is enabled and active */
if ((ioread32(reg_int_en) & ioread32(reg_int_stat) &
DPM_HOST_INT_MASK) == 0) {
return IRQ_NONE;
}
/* disable interrupts */
iowrite32(ioread32(reg_int_en) & ~DPM_HOST_INT_GLOBAL_EN, reg_int_en);
return IRQ_HANDLED;
}
static void netx5152_init(struct uio_info *info)
{
unsigned long win0_offset = DPM_HOST_WIN0_OFFSET;
struct fsl_elbc_gpcm *priv = info->priv;
const void *prop;
/* get an optional initial win0 offset */
prop = of_get_property(priv->dev->of_node,
"netx5152,init-win0-offset", NULL);
if (prop)
win0_offset = of_read_ulong(prop, 1);
/* disable interrupts */
iowrite32(0, info->mem[0].internal_addr + win0_offset +
DPM_HOST_INT_EN0);
}
static void netx5152_shutdown(struct uio_info *info, bool init_err)
{
if (init_err)
return;
/* disable interrupts */
iowrite32(0, info->mem[0].internal_addr + DPM_HOST_WIN0_OFFSET +
DPM_HOST_INT_EN0);
}
#endif
static void setup_periph(struct fsl_elbc_gpcm *priv,
const char *type)
{
#ifdef CONFIG_UIO_FSL_ELBC_GPCM_NETX5152
if (strcmp(type, "netx5152") == 0) {
priv->irq_handler = netx5152_irq_handler;
priv->init = netx5152_init;
priv->shutdown = netx5152_shutdown;
priv->name = "netX 51/52";
return;
}
#endif
}
static int check_of_data(struct fsl_elbc_gpcm *priv,
struct resource *res,
u32 reg_br, u32 reg_or)
{
/* check specified bank */
if (priv->bank >= MAX_BANKS) {
dev_err(priv->dev, "invalid bank\n");
return -ENODEV;
}
/* check specified mode (BR_MS_GPCM is 0) */
if ((reg_br & BR_MSEL) != BR_MS_GPCM) {
dev_err(priv->dev, "unsupported mode\n");
return -ENODEV;
}
/* check specified mask vs. resource size */
if ((~(reg_or & OR_GPCM_AM) + 1) != resource_size(res)) {
dev_err(priv->dev, "address mask / size mismatch\n");
return -ENODEV;
}
/* check specified address */
if ((reg_br & reg_or & BR_BA) != fsl_lbc_addr(res->start)) {
dev_err(priv->dev, "base address mismatch\n");
return -ENODEV;
}
return 0;
}
static int get_of_data(struct fsl_elbc_gpcm *priv, struct device_node *node,
struct resource *res, u32 *reg_br,
u32 *reg_or, unsigned int *irq, char **name)
{
const char *dt_name;
const char *type;
int ret;
/* get the memory resource */
ret = of_address_to_resource(node, 0, res);
if (ret) {
dev_err(priv->dev, "failed to get resource\n");
return ret;
}
/* get the bank number */
ret = of_property_read_u32(node, "reg", &priv->bank);
if (ret) {
dev_err(priv->dev, "failed to get bank number\n");
return ret;
}
/* get BR value to set */
ret = of_property_read_u32(node, "elbc-gpcm-br", reg_br);
if (ret) {
dev_err(priv->dev, "missing elbc-gpcm-br value\n");
return ret;
}
/* get OR value to set */
ret = of_property_read_u32(node, "elbc-gpcm-or", reg_or);
if (ret) {
dev_err(priv->dev, "missing elbc-gpcm-or value\n");
return ret;
}
/* get optional peripheral type */
priv->name = "generic";
if (of_property_read_string(node, "device_type", &type) == 0)
setup_periph(priv, type);
/* get optional irq value */
*irq = irq_of_parse_and_map(node, 0);
/* sanity check device tree data */
ret = check_of_data(priv, res, *reg_br, *reg_or);
if (ret)
return ret;
/* get optional uio name */
if (of_property_read_string(node, "uio_name", &dt_name) != 0)
dt_name = "eLBC_GPCM";
*name = devm_kstrdup(priv->dev, dt_name, GFP_KERNEL);
if (!*name)
return -ENOMEM;
return 0;
}
static int uio_fsl_elbc_gpcm_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct fsl_elbc_gpcm *priv;
struct uio_info *info;
char *uio_name = NULL;
struct resource res;
unsigned int irq;
u32 reg_br_cur;
u32 reg_or_cur;
u32 reg_br_new;
u32 reg_or_new;
int ret;
if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)
return -ENODEV;
/* allocate private data */
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = &pdev->dev;
priv->lbc = fsl_lbc_ctrl_dev->regs;
/* get device tree data */
ret = get_of_data(priv, node, &res, &reg_br_new, &reg_or_new,
&irq, &uio_name);
if (ret)
return ret;
/* allocate UIO structure */
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
/* get current BR/OR values */
reg_br_cur = in_be32(&priv->lbc->bank[priv->bank].br);
reg_or_cur = in_be32(&priv->lbc->bank[priv->bank].or);
/* if bank already configured, make sure it matches */
if ((reg_br_cur & BR_V)) {
if ((reg_br_cur & BR_MSEL) != BR_MS_GPCM ||
(reg_br_cur & reg_or_cur & BR_BA)
!= fsl_lbc_addr(res.start)) {
dev_err(priv->dev,
"bank in use by another peripheral\n");
return -ENODEV;
}
/* warn if behavior settings changing */
if ((reg_br_cur & ~(BR_BA | BR_V)) !=
(reg_br_new & ~(BR_BA | BR_V))) {
dev_warn(priv->dev,
"modifying BR settings: 0x%08x -> 0x%08x",
reg_br_cur, reg_br_new);
}
if ((reg_or_cur & ~OR_GPCM_AM) != (reg_or_new & ~OR_GPCM_AM)) {
dev_warn(priv->dev,
"modifying OR settings: 0x%08x -> 0x%08x",
reg_or_cur, reg_or_new);
}
}
/* configure the bank (force base address and GPCM) */
reg_br_new &= ~(BR_BA | BR_MSEL);
reg_br_new |= fsl_lbc_addr(res.start) | BR_MS_GPCM | BR_V;
out_be32(&priv->lbc->bank[priv->bank].or, reg_or_new);
out_be32(&priv->lbc->bank[priv->bank].br, reg_br_new);
/* map the memory resource */
info->mem[0].internal_addr = ioremap(res.start, resource_size(&res));
if (!info->mem[0].internal_addr) {
dev_err(priv->dev, "failed to map chip region\n");
return -ENODEV;
}
/* set all UIO data */
info->mem[0].name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%pOFn", node);
if (!info->mem[0].name) {
ret = -ENODEV;
goto out_err3;
}
info->mem[0].addr = res.start;
info->mem[0].size = resource_size(&res);
info->mem[0].memtype = UIO_MEM_PHYS;
info->priv = priv;
info->name = uio_name;
info->version = "0.0.1";
if (irq) {
if (priv->irq_handler) {
info->irq = irq;
info->irq_flags = IRQF_SHARED;
info->handler = priv->irq_handler;
} else {
irq = 0;
dev_warn(priv->dev, "ignoring irq, no handler\n");
}
}
if (priv->init)
priv->init(info);
/* register UIO device */
if (uio_register_device(priv->dev, info) != 0) {
dev_err(priv->dev, "UIO registration failed\n");
ret = -ENODEV;
goto out_err2;
}
/* store private data */
platform_set_drvdata(pdev, info);
dev_info(priv->dev,
"eLBC/GPCM device (%s) at 0x%llx, bank %d, irq=%d\n",
priv->name, (unsigned long long)res.start, priv->bank,
irq ? : -1);
return 0;
out_err2:
if (priv->shutdown)
priv->shutdown(info, true);
out_err3:
iounmap(info->mem[0].internal_addr);
return ret;
}
static void uio_fsl_elbc_gpcm_remove(struct platform_device *pdev)
{
struct uio_info *info = platform_get_drvdata(pdev);
struct fsl_elbc_gpcm *priv = info->priv;
platform_set_drvdata(pdev, NULL);
uio_unregister_device(info);
if (priv->shutdown)
priv->shutdown(info, false);
iounmap(info->mem[0].internal_addr);
}
static const struct of_device_id uio_fsl_elbc_gpcm_match[] = {
{ .compatible = "fsl,elbc-gpcm-uio", },
{}
};
MODULE_DEVICE_TABLE(of, uio_fsl_elbc_gpcm_match);
static struct platform_driver uio_fsl_elbc_gpcm_driver = {
.driver = {
.name = "fsl,elbc-gpcm-uio",
.of_match_table = uio_fsl_elbc_gpcm_match,
.dev_groups = uio_fsl_elbc_gpcm_groups,
},
.probe = uio_fsl_elbc_gpcm_probe,
.remove = uio_fsl_elbc_gpcm_remove,
};
module_platform_driver(uio_fsl_elbc_gpcm_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>");
MODULE_DESCRIPTION("Freescale Enhanced Local Bus Controller GPCM driver");
+430
View File
@@ -0,0 +1,430 @@
// SPDX-License-Identifier: GPL-2.0
/*
* uio_hv_generic - generic UIO driver for VMBus
*
* Copyright (c) 2013-2016 Brocade Communications Systems, Inc.
* Copyright (c) 2016, Microsoft Corporation.
*
* Since the driver does not declare any device ids, you must allocate
* id and bind the device to the driver yourself. For example:
*
* Associate Network GUID with UIO device
* # echo "f8615163-df3e-46c5-913f-f2d2f965ed0e" \
* > /sys/bus/vmbus/drivers/uio_hv_generic/new_id
* Then rebind
* # echo -n "ed963694-e847-4b2a-85af-bc9cfc11d6f3" \
* > /sys/bus/vmbus/drivers/hv_netvsc/unbind
* # echo -n "ed963694-e847-4b2a-85af-bc9cfc11d6f3" \
* > /sys/bus/vmbus/drivers/uio_hv_generic/bind
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/uio_driver.h>
#include <linux/netdevice.h>
#include <linux/if_ether.h>
#include <linux/skbuff.h>
#include <linux/hyperv.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include "../hv/hyperv_vmbus.h"
#define DRIVER_VERSION "0.02.1"
#define DRIVER_AUTHOR "Stephen Hemminger <sthemmin at microsoft.com>"
#define DRIVER_DESC "Generic UIO driver for VMBus devices"
#define SEND_BUFFER_SIZE (16 * 1024 * 1024)
#define RECV_BUFFER_SIZE (31 * 1024 * 1024)
/*
* List of resources to be mapped to user space
* can be extended up to MAX_UIO_MAPS(5) items
*/
enum hv_uio_map {
TXRX_RING_MAP = 0,
INT_PAGE_MAP,
MON_PAGE_MAP,
RECV_BUF_MAP,
SEND_BUF_MAP
};
struct hv_uio_private_data {
struct uio_info info;
struct hv_device *device;
atomic_t refcnt;
void *recv_buf;
struct vmbus_gpadl recv_gpadl;
char recv_name[32]; /* "recv_4294967295" */
void *send_buf;
struct vmbus_gpadl send_gpadl;
char send_name[32];
};
static void set_event(struct vmbus_channel *channel, s32 irq_state)
{
channel->inbound.ring_buffer->interrupt_mask = !irq_state;
if (!channel->offermsg.monitor_allocated && irq_state) {
/* MB is needed for host to see the interrupt mask first */
virt_mb();
vmbus_set_event(channel);
}
}
/*
* This is the irqcontrol callback to be registered to uio_info.
* It can be used to disable/enable interrupt from user space processes.
*
* @param info
* pointer to uio_info.
* @param irq_state
* state value. 1 to enable interrupt, 0 to disable interrupt.
*/
static int
hv_uio_irqcontrol(struct uio_info *info, s32 irq_state)
{
struct hv_uio_private_data *pdata = info->priv;
struct hv_device *dev = pdata->device;
struct vmbus_channel *primary, *sc;
primary = dev->channel;
set_event(primary, irq_state);
mutex_lock(&vmbus_connection.channel_mutex);
list_for_each_entry(sc, &primary->sc_list, sc_list)
set_event(sc, irq_state);
mutex_unlock(&vmbus_connection.channel_mutex);
return 0;
}
/*
* Callback from vmbus_event when something is in inbound ring.
*/
static void hv_uio_channel_cb(void *context)
{
struct vmbus_channel *chan = context;
struct hv_device *hv_dev;
struct hv_uio_private_data *pdata;
virt_mb();
/*
* The callback may come from a subchannel, in which case look
* for the hv device in the primary channel
*/
hv_dev = chan->primary_channel ?
chan->primary_channel->device_obj : chan->device_obj;
pdata = hv_get_drvdata(hv_dev);
uio_event_notify(&pdata->info);
}
/*
* Callback from vmbus_event when channel is rescinded.
* It is meant for rescind of primary channels only.
*/
static void hv_uio_rescind(struct vmbus_channel *channel)
{
struct hv_device *hv_dev = channel->device_obj;
struct hv_uio_private_data *pdata = hv_get_drvdata(hv_dev);
/*
* Turn off the interrupt file handle
* Next read for event will return -EIO
*/
pdata->info.irq = 0;
/* Wake up reader */
uio_event_notify(&pdata->info);
/*
* With rescind callback registered, rescind path will not unregister the device
* from vmbus when the primary channel is rescinded.
* Without it, rescind handling is incomplete and next onoffer msg does not come.
* Unregister the device from vmbus here.
*/
vmbus_device_unregister(channel->device_obj);
}
/* Function used for mmap of ring buffer sysfs interface.
* The ring buffer is allocated as contiguous memory by vmbus_open
*/
static int
hv_uio_ring_mmap_prepare(struct vmbus_channel *channel, struct vm_area_desc *desc)
{
void *ring_buffer = page_address(channel->ringbuffer_page);
if (channel->state != CHANNEL_OPENED_STATE)
return -ENODEV;
mmap_action_simple_ioremap(desc, virt_to_phys(ring_buffer),
channel->ringbuffer_pagecount << PAGE_SHIFT);
return 0;
}
/* Callback from VMBUS subsystem when new channel created. */
static void
hv_uio_new_channel(struct vmbus_channel *new_sc)
{
struct hv_device *hv_dev = new_sc->primary_channel->device_obj;
struct device *device = &hv_dev->device;
const size_t ring_bytes = SZ_2M;
int ret;
/* Create host communication ring */
ret = vmbus_open(new_sc, ring_bytes, ring_bytes, NULL, 0,
hv_uio_channel_cb, new_sc);
if (ret) {
dev_err(device, "vmbus_open subchannel failed: %d\n", ret);
return;
}
set_channel_read_mode(new_sc, HV_CALL_ISR);
ret = hv_create_ring_sysfs(new_sc, hv_uio_ring_mmap_prepare);
if (ret) {
dev_err(device, "sysfs create ring bin file failed; %d\n", ret);
vmbus_close(new_sc);
}
}
/* free the reserved buffers for send and receive */
static void
hv_uio_cleanup(struct hv_device *dev, struct hv_uio_private_data *pdata)
{
if (pdata->send_gpadl.gpadl_handle) {
vmbus_teardown_gpadl(dev->channel, &pdata->send_gpadl);
if (!pdata->send_gpadl.decrypted)
vfree(pdata->send_buf);
}
if (pdata->recv_gpadl.gpadl_handle) {
vmbus_teardown_gpadl(dev->channel, &pdata->recv_gpadl);
if (!pdata->recv_gpadl.decrypted)
vfree(pdata->recv_buf);
}
}
/* VMBus primary channel is opened on first use */
static int
hv_uio_open(struct uio_info *info, struct inode *inode)
{
struct hv_uio_private_data *pdata
= container_of(info, struct hv_uio_private_data, info);
struct hv_device *dev = pdata->device;
int ret;
if (atomic_inc_return(&pdata->refcnt) != 1)
return 0;
vmbus_set_chn_rescind_callback(dev->channel, hv_uio_rescind);
vmbus_set_sc_create_callback(dev->channel, hv_uio_new_channel);
ret = vmbus_connect_ring(dev->channel,
hv_uio_channel_cb, dev->channel);
if (ret)
atomic_dec(&pdata->refcnt);
return ret;
}
/* VMBus primary channel is closed on last close */
static int
hv_uio_release(struct uio_info *info, struct inode *inode)
{
struct hv_uio_private_data *pdata
= container_of(info, struct hv_uio_private_data, info);
struct hv_device *dev = pdata->device;
int ret = 0;
if (atomic_dec_and_test(&pdata->refcnt))
ret = vmbus_disconnect_ring(dev->channel);
return ret;
}
static int
hv_uio_probe(struct hv_device *dev,
const struct hv_vmbus_device_id *dev_id)
{
struct vmbus_channel *channel = dev->channel;
struct hv_uio_private_data *pdata;
void *ring_buffer;
int ret;
size_t ring_size = hv_dev_ring_size(channel);
if (!ring_size)
ring_size = SZ_2M;
/* Adjust ring size if necessary to have it page aligned */
ring_size = VMBUS_RING_SIZE(ring_size);
pdata = devm_kzalloc(&dev->device, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
ret = vmbus_alloc_ring(channel, ring_size, ring_size);
if (ret)
return ret;
set_channel_read_mode(channel, HV_CALL_ISR);
/* Fill general uio info */
pdata->info.name = "uio_hv_generic";
pdata->info.version = DRIVER_VERSION;
pdata->info.irqcontrol = hv_uio_irqcontrol;
pdata->info.open = hv_uio_open;
pdata->info.release = hv_uio_release;
pdata->info.irq = UIO_IRQ_CUSTOM;
atomic_set(&pdata->refcnt, 0);
/* mem resources */
pdata->info.mem[TXRX_RING_MAP].name = "txrx_rings";
ring_buffer = page_address(channel->ringbuffer_page);
pdata->info.mem[TXRX_RING_MAP].addr
= (uintptr_t)virt_to_phys(ring_buffer);
pdata->info.mem[TXRX_RING_MAP].size
= channel->ringbuffer_pagecount << PAGE_SHIFT;
pdata->info.mem[TXRX_RING_MAP].memtype = UIO_MEM_IOVA;
pdata->info.mem[INT_PAGE_MAP].name = "int_page";
pdata->info.mem[INT_PAGE_MAP].addr
= (uintptr_t)vmbus_connection.int_page;
pdata->info.mem[INT_PAGE_MAP].size = HV_HYP_PAGE_SIZE;
pdata->info.mem[INT_PAGE_MAP].memtype = UIO_MEM_LOGICAL;
pdata->info.mem[MON_PAGE_MAP].name = "monitor_page";
pdata->info.mem[MON_PAGE_MAP].addr
= (uintptr_t)vmbus_connection.monitor_pages[1];
pdata->info.mem[MON_PAGE_MAP].size = HV_HYP_PAGE_SIZE;
pdata->info.mem[MON_PAGE_MAP].memtype = UIO_MEM_LOGICAL;
if (channel->device_id == HV_NIC) {
pdata->recv_buf = vzalloc(RECV_BUFFER_SIZE);
if (!pdata->recv_buf) {
ret = -ENOMEM;
goto fail_free_ring;
}
ret = vmbus_establish_gpadl(channel, pdata->recv_buf,
RECV_BUFFER_SIZE, &pdata->recv_gpadl);
if (ret) {
if (!pdata->recv_gpadl.decrypted)
vfree(pdata->recv_buf);
goto fail_close;
}
/* put Global Physical Address Label in name */
snprintf(pdata->recv_name, sizeof(pdata->recv_name),
"recv:%u", pdata->recv_gpadl.gpadl_handle);
pdata->info.mem[RECV_BUF_MAP].name = pdata->recv_name;
pdata->info.mem[RECV_BUF_MAP].addr = (uintptr_t)pdata->recv_buf;
pdata->info.mem[RECV_BUF_MAP].size = RECV_BUFFER_SIZE;
pdata->info.mem[RECV_BUF_MAP].memtype = UIO_MEM_VIRTUAL;
pdata->send_buf = vzalloc(SEND_BUFFER_SIZE);
if (!pdata->send_buf) {
ret = -ENOMEM;
goto fail_close;
}
ret = vmbus_establish_gpadl(channel, pdata->send_buf,
SEND_BUFFER_SIZE, &pdata->send_gpadl);
if (ret) {
if (!pdata->send_gpadl.decrypted)
vfree(pdata->send_buf);
goto fail_close;
}
snprintf(pdata->send_name, sizeof(pdata->send_name),
"send:%u", pdata->send_gpadl.gpadl_handle);
pdata->info.mem[SEND_BUF_MAP].name = pdata->send_name;
pdata->info.mem[SEND_BUF_MAP].addr = (uintptr_t)pdata->send_buf;
pdata->info.mem[SEND_BUF_MAP].size = SEND_BUFFER_SIZE;
pdata->info.mem[SEND_BUF_MAP].memtype = UIO_MEM_VIRTUAL;
}
pdata->info.priv = pdata;
pdata->device = dev;
ret = uio_register_device(&dev->device, &pdata->info);
if (ret) {
dev_err(&dev->device, "hv_uio register failed\n");
goto fail_close;
}
/*
* This internally calls sysfs_update_group, which returns a non-zero value if it executes
* before sysfs_create_group. This is expected as the 'ring' will be created later in
* vmbus_device_register() -> vmbus_add_channel_kobj(). Thus, no need to check the return
* value and print warning.
*
* Creating/exposing sysfs in driver probe is not encouraged as it can lead to race
* conditions with userspace. For backward compatibility, "ring" sysfs could not be removed
* or decoupled from uio_hv_generic probe. Userspace programs can make use of inotify
* APIs to make sure that ring is created.
*/
hv_create_ring_sysfs(channel, hv_uio_ring_mmap_prepare);
hv_set_drvdata(dev, pdata);
return 0;
fail_close:
hv_uio_cleanup(dev, pdata);
fail_free_ring:
vmbus_free_ring(dev->channel);
return ret;
}
static void
hv_uio_remove(struct hv_device *dev)
{
struct hv_uio_private_data *pdata = hv_get_drvdata(dev);
if (!pdata)
return;
hv_remove_ring_sysfs(dev->channel);
uio_unregister_device(&pdata->info);
hv_uio_cleanup(dev, pdata);
vmbus_free_ring(dev->channel);
}
static const struct hv_vmbus_device_id hv_uio_id_table[] = {
{ HV_FCOPY_GUID },
{}
};
MODULE_DEVICE_TABLE(vmbus, hv_uio_id_table);
static struct hv_driver hv_uio_drv = {
.name = "uio_hv_generic",
.id_table = hv_uio_id_table,
.probe = hv_uio_probe,
.remove = hv_uio_remove,
};
static int __init
hyperv_module_init(void)
{
return vmbus_driver_register(&hv_uio_drv);
}
static void __exit
hyperv_module_exit(void)
{
vmbus_driver_unregister(&hv_uio_drv);
}
module_init(hyperv_module_init);
module_exit(hyperv_module_exit);
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
+226
View File
@@ -0,0 +1,226 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* UIO driver for Humusoft MF624 DAQ card.
* Copyright (C) 2011 Rostislav Lisovy <lisovy@gmail.com>,
* Czech Technical University in Prague
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/uio_driver.h>
#define PCI_VENDOR_ID_HUMUSOFT 0x186c
#define PCI_DEVICE_ID_MF624 0x0624
#define PCI_SUBVENDOR_ID_HUMUSOFT 0x186c
#define PCI_SUBDEVICE_DEVICE 0x0624
/* BAR0 Interrupt control/status register */
#define INTCSR 0x4C
#define INTCSR_ADINT_ENABLE (1 << 0)
#define INTCSR_CTR4INT_ENABLE (1 << 3)
#define INTCSR_PCIINT_ENABLE (1 << 6)
#define INTCSR_ADINT_STATUS (1 << 2)
#define INTCSR_CTR4INT_STATUS (1 << 5)
enum mf624_interrupt_source {ADC, CTR4, ALL};
static void mf624_disable_interrupt(enum mf624_interrupt_source source,
struct uio_info *info)
{
void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR;
switch (source) {
case ADC:
iowrite32(ioread32(INTCSR_reg)
& ~(INTCSR_ADINT_ENABLE | INTCSR_PCIINT_ENABLE),
INTCSR_reg);
break;
case CTR4:
iowrite32(ioread32(INTCSR_reg)
& ~(INTCSR_CTR4INT_ENABLE | INTCSR_PCIINT_ENABLE),
INTCSR_reg);
break;
case ALL:
default:
iowrite32(ioread32(INTCSR_reg)
& ~(INTCSR_ADINT_ENABLE | INTCSR_CTR4INT_ENABLE
| INTCSR_PCIINT_ENABLE),
INTCSR_reg);
break;
}
}
static void mf624_enable_interrupt(enum mf624_interrupt_source source,
struct uio_info *info)
{
void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR;
switch (source) {
case ADC:
iowrite32(ioread32(INTCSR_reg)
| INTCSR_ADINT_ENABLE | INTCSR_PCIINT_ENABLE,
INTCSR_reg);
break;
case CTR4:
iowrite32(ioread32(INTCSR_reg)
| INTCSR_CTR4INT_ENABLE | INTCSR_PCIINT_ENABLE,
INTCSR_reg);
break;
case ALL:
default:
iowrite32(ioread32(INTCSR_reg)
| INTCSR_ADINT_ENABLE | INTCSR_CTR4INT_ENABLE
| INTCSR_PCIINT_ENABLE,
INTCSR_reg);
break;
}
}
static irqreturn_t mf624_irq_handler(int irq, struct uio_info *info)
{
void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR;
if ((ioread32(INTCSR_reg) & INTCSR_ADINT_ENABLE)
&& (ioread32(INTCSR_reg) & INTCSR_ADINT_STATUS)) {
mf624_disable_interrupt(ADC, info);
return IRQ_HANDLED;
}
if ((ioread32(INTCSR_reg) & INTCSR_CTR4INT_ENABLE)
&& (ioread32(INTCSR_reg) & INTCSR_CTR4INT_STATUS)) {
mf624_disable_interrupt(CTR4, info);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int mf624_irqcontrol(struct uio_info *info, s32 irq_on)
{
if (irq_on == 0)
mf624_disable_interrupt(ALL, info);
else if (irq_on == 1)
mf624_enable_interrupt(ALL, info);
return 0;
}
static int mf624_setup_mem(struct pci_dev *dev, int bar, struct uio_mem *mem, const char *name)
{
resource_size_t start = pci_resource_start(dev, bar);
resource_size_t len = pci_resource_len(dev, bar);
mem->name = name;
mem->addr = start & PAGE_MASK;
mem->offs = start & ~PAGE_MASK;
if (!mem->addr)
return -ENODEV;
mem->size = ((start & ~PAGE_MASK) + len + PAGE_SIZE - 1) & PAGE_MASK;
mem->memtype = UIO_MEM_PHYS;
mem->internal_addr = pci_ioremap_bar(dev, bar);
if (!mem->internal_addr)
return -ENODEV;
return 0;
}
static int mf624_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
struct uio_info *info;
info = devm_kzalloc(&dev->dev, sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
if (pci_enable_device(dev))
return -ENODEV;
if (pci_request_regions(dev, "mf624"))
goto out_disable;
info->name = "mf624";
info->version = "0.0.1";
/* Note: Datasheet says device uses BAR0, BAR1, BAR2 -- do not trust it */
/* BAR0 */
if (mf624_setup_mem(dev, 0, &info->mem[0], "PCI chipset, interrupts, status "
"bits, special functions"))
goto out_release;
/* BAR2 */
if (mf624_setup_mem(dev, 2, &info->mem[1], "ADC, DAC, DIO"))
goto out_unmap0;
/* BAR4 */
if (mf624_setup_mem(dev, 4, &info->mem[2], "Counter/timer chip"))
goto out_unmap1;
info->irq = dev->irq;
info->irq_flags = IRQF_SHARED;
info->handler = mf624_irq_handler;
info->irqcontrol = mf624_irqcontrol;
if (uio_register_device(&dev->dev, info))
goto out_unmap2;
pci_set_drvdata(dev, info);
return 0;
out_unmap2:
iounmap(info->mem[2].internal_addr);
out_unmap1:
iounmap(info->mem[1].internal_addr);
out_unmap0:
iounmap(info->mem[0].internal_addr);
out_release:
pci_release_regions(dev);
out_disable:
pci_disable_device(dev);
return -ENODEV;
}
static void mf624_pci_remove(struct pci_dev *dev)
{
struct uio_info *info = pci_get_drvdata(dev);
mf624_disable_interrupt(ALL, info);
uio_unregister_device(info);
pci_release_regions(dev);
pci_disable_device(dev);
iounmap(info->mem[0].internal_addr);
iounmap(info->mem[1].internal_addr);
iounmap(info->mem[2].internal_addr);
}
static const struct pci_device_id mf624_pci_id[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_HUMUSOFT, PCI_DEVICE_ID_MF624) },
{ 0, }
};
static struct pci_driver mf624_pci_driver = {
.name = "mf624",
.id_table = mf624_pci_id,
.probe = mf624_pci_probe,
.remove = mf624_pci_remove,
};
MODULE_DEVICE_TABLE(pci, mf624_pci_id);
module_pci_driver(mf624_pci_driver);
MODULE_DESCRIPTION("UIO driver for Humusoft MF624 DAQ card");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Rostislav Lisovy <lisovy@gmail.com>");
+175
View File
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0
/*
* UIO driver for Hilscher NetX based fieldbus cards (cifX, comX).
* See http://www.hilscher.com for details.
*
* (C) 2007 Hans J. Koch <hjk@hansjkoch.de>
* (C) 2008 Manuel Traut <manut@linutronix.de>
*
*/
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/uio_driver.h>
#define PCI_VENDOR_ID_HILSCHER 0x15CF
#define PCI_DEVICE_ID_HILSCHER_NETX 0x0000
#define PCI_DEVICE_ID_HILSCHER_NETPLC 0x0010
#define PCI_SUBDEVICE_ID_NETPLC_RAM 0x0000
#define PCI_SUBDEVICE_ID_NETPLC_FLASH 0x0001
#define PCI_SUBDEVICE_ID_NXSB_PCA 0x3235
#define PCI_SUBDEVICE_ID_NXPCA 0x3335
#define DPM_HOST_INT_EN0 0xfff0
#define DPM_HOST_INT_STAT0 0xffe0
#define DPM_HOST_INT_MASK 0xe600ffff
#define DPM_HOST_INT_GLOBAL_EN 0x80000000
static irqreturn_t netx_handler(int irq, struct uio_info *dev_info)
{
void __iomem *int_enable_reg = dev_info->mem[0].internal_addr
+ DPM_HOST_INT_EN0;
void __iomem *int_status_reg = dev_info->mem[0].internal_addr
+ DPM_HOST_INT_STAT0;
/* Is one of our interrupts enabled and active ? */
if (!(ioread32(int_enable_reg) & ioread32(int_status_reg)
& DPM_HOST_INT_MASK))
return IRQ_NONE;
/* Disable interrupt */
iowrite32(ioread32(int_enable_reg) & ~DPM_HOST_INT_GLOBAL_EN,
int_enable_reg);
return IRQ_HANDLED;
}
static int netx_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
struct uio_info *info;
int bar;
info = devm_kzalloc(&dev->dev, sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
if (pci_enable_device(dev))
return -ENODEV;
if (pci_request_regions(dev, "netx"))
goto out_disable;
switch (id->device) {
case PCI_DEVICE_ID_HILSCHER_NETX:
bar = 0;
info->name = "netx";
break;
case PCI_DEVICE_ID_HILSCHER_NETPLC:
bar = 0;
info->name = "netplc";
break;
default:
bar = 2;
info->name = "netx_plx";
}
/* BAR0 or 2 points to the card's dual port memory */
info->mem[0].addr = pci_resource_start(dev, bar);
if (!info->mem[0].addr)
goto out_release;
info->mem[0].internal_addr = ioremap(pci_resource_start(dev, bar),
pci_resource_len(dev, bar));
if (!info->mem[0].internal_addr)
goto out_release;
info->mem[0].size = pci_resource_len(dev, bar);
info->mem[0].memtype = UIO_MEM_PHYS;
info->irq = dev->irq;
info->irq_flags = IRQF_SHARED;
info->handler = netx_handler;
info->version = "0.0.1";
/* Make sure all interrupts are disabled */
iowrite32(0, info->mem[0].internal_addr + DPM_HOST_INT_EN0);
if (uio_register_device(&dev->dev, info))
goto out_unmap;
pci_set_drvdata(dev, info);
dev_info(&dev->dev, "Found %s card, registered UIO device.\n",
info->name);
return 0;
out_unmap:
iounmap(info->mem[0].internal_addr);
out_release:
pci_release_regions(dev);
out_disable:
pci_disable_device(dev);
return -ENODEV;
}
static void netx_pci_remove(struct pci_dev *dev)
{
struct uio_info *info = pci_get_drvdata(dev);
/* Disable all interrupts */
iowrite32(0, info->mem[0].internal_addr + DPM_HOST_INT_EN0);
uio_unregister_device(info);
pci_release_regions(dev);
pci_disable_device(dev);
iounmap(info->mem[0].internal_addr);
}
static const struct pci_device_id netx_pci_ids[] = {
{
.vendor = PCI_VENDOR_ID_HILSCHER,
.device = PCI_DEVICE_ID_HILSCHER_NETX,
.subvendor = 0,
.subdevice = 0,
},
{
.vendor = PCI_VENDOR_ID_HILSCHER,
.device = PCI_DEVICE_ID_HILSCHER_NETPLC,
.subvendor = PCI_VENDOR_ID_HILSCHER,
.subdevice = PCI_SUBDEVICE_ID_NETPLC_RAM,
},
{
.vendor = PCI_VENDOR_ID_HILSCHER,
.device = PCI_DEVICE_ID_HILSCHER_NETPLC,
.subvendor = PCI_VENDOR_ID_HILSCHER,
.subdevice = PCI_SUBDEVICE_ID_NETPLC_FLASH,
},
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = PCI_VENDOR_ID_PLX,
.subdevice = PCI_SUBDEVICE_ID_NXSB_PCA,
},
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = PCI_VENDOR_ID_PLX,
.subdevice = PCI_SUBDEVICE_ID_NXPCA,
},
{ 0, }
};
static struct pci_driver netx_pci_driver = {
.name = "netx",
.id_table = netx_pci_ids,
.probe = netx_pci_probe,
.remove = netx_pci_remove,
};
module_pci_driver(netx_pci_driver);
MODULE_DEVICE_TABLE(pci, netx_pci_ids);
MODULE_DESCRIPTION("UIO driver for Hilscher NetX based fieldbus cards");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Hans J. Koch, Manuel Traut");
+149
View File
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-2.0
/* uio_pci_generic - generic UIO driver for PCI 2.3 devices
*
* Copyright (C) 2009 Red Hat, Inc.
* Author: Michael S. Tsirkin <mst@redhat.com>
*
* Since the driver does not declare any device ids, you must allocate
* id and bind the device to the driver yourself. For example:
*
* # echo "8086 10f5" > /sys/bus/pci/drivers/uio_pci_generic/new_id
* # echo -n 0000:00:19.0 > /sys/bus/pci/drivers/e1000e/unbind
* # echo -n 0000:00:19.0 > /sys/bus/pci/drivers/uio_pci_generic/bind
* # ls -l /sys/bus/pci/devices/0000:00:19.0/driver
* .../0000:00:19.0/driver -> ../../../bus/pci/drivers/uio_pci_generic
*
* Driver won't bind to devices which do not support the Interrupt Disable Bit
* in the command register. All devices compliant to PCI 2.3 (circa 2002) and
* all compliant PCI Express devices should support this bit.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/uio_driver.h>
#define DRIVER_VERSION "0.01.0"
#define DRIVER_AUTHOR "Michael S. Tsirkin <mst@redhat.com>"
#define DRIVER_DESC "Generic UIO driver for PCI 2.3 devices"
struct uio_pci_generic_dev {
struct uio_info info;
struct pci_dev *pdev;
};
static inline struct uio_pci_generic_dev *
to_uio_pci_generic_dev(struct uio_info *info)
{
return container_of(info, struct uio_pci_generic_dev, info);
}
static int release(struct uio_info *info, struct inode *inode)
{
struct uio_pci_generic_dev *gdev = to_uio_pci_generic_dev(info);
/*
* This driver is insecure when used with devices doing DMA, but some
 * people (mis)use it with such devices.
 * Let's at least make sure DMA isn't left enabled after the userspace
 * driver closes the fd.
 * Note that there's a non-zero chance doing this will wedge the device
 * at least until reset.
*/
pci_clear_master(gdev->pdev);
return 0;
}
/* Interrupt handler. Read/modify/write the command register to disable
* the interrupt. */
static irqreturn_t irqhandler(int irq, struct uio_info *info)
{
struct uio_pci_generic_dev *gdev = to_uio_pci_generic_dev(info);
if (!pci_check_and_mask_intx(gdev->pdev))
return IRQ_NONE;
/* UIO core will signal the user process. */
return IRQ_HANDLED;
}
static int probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct uio_pci_generic_dev *gdev;
struct uio_mem *uiomem;
int err;
int i;
err = pcim_enable_device(pdev);
if (err) {
dev_err(&pdev->dev, "%s: pci_enable_device failed: %d\n",
__func__, err);
return err;
}
if (pdev->irq && !pci_intx_mask_supported(pdev))
return -ENODEV;
gdev = devm_kzalloc(&pdev->dev, sizeof(struct uio_pci_generic_dev), GFP_KERNEL);
if (!gdev)
return -ENOMEM;
gdev->info.name = "uio_pci_generic";
gdev->info.version = DRIVER_VERSION;
gdev->info.release = release;
gdev->pdev = pdev;
if (pdev->irq && (pdev->irq != IRQ_NOTCONNECTED)) {
gdev->info.irq = pdev->irq;
gdev->info.irq_flags = IRQF_SHARED;
gdev->info.handler = irqhandler;
} else {
dev_warn(&pdev->dev, "No IRQ assigned to device: "
"no support for interrupts?\n");
}
uiomem = &gdev->info.mem[0];
for (i = 0; i < MAX_UIO_MAPS; ++i) {
struct resource *r = &pdev->resource[i];
if (r->flags != (IORESOURCE_SIZEALIGN | IORESOURCE_MEM))
continue;
if (uiomem >= &gdev->info.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 & PAGE_MASK;
uiomem->offs = r->start & ~PAGE_MASK;
uiomem->size =
(uiomem->offs + resource_size(r) + PAGE_SIZE - 1) &
PAGE_MASK;
uiomem->name = r->name;
++uiomem;
}
while (uiomem < &gdev->info.mem[MAX_UIO_MAPS]) {
uiomem->size = 0;
++uiomem;
}
return devm_uio_register_device(&pdev->dev, &gdev->info);
}
static struct pci_driver uio_pci_driver = {
.name = "uio_pci_generic",
.id_table = NULL, /* only dynamic id's */
.probe = probe,
};
module_pci_driver(uio_pci_driver);
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0
/*
* UIO PCI Express sva driver
*
* Copyright (c) 2025 Beijing Institute of Open Source Chip (BOSC)
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/uio_driver.h>
#include <linux/iommu.h>
struct uio_pci_sva_dev {
struct pci_dev *pdev;
struct uio_info info;
struct iommu_sva *sva_handle;
int pasid;
};
static irqreturn_t irq_handler(int irq, struct uio_info *dev_info)
{
return IRQ_HANDLED;
}
static int uio_pci_sva_open(struct uio_info *info, struct inode *inode)
{
struct iommu_sva *handle;
struct uio_pci_sva_dev *udev = info->priv;
struct iommu_domain *domain;
if (!udev || !udev->pdev)
return -ENODEV;
domain = iommu_get_domain_for_dev(&udev->pdev->dev);
if (domain)
iommu_detach_device(domain, &udev->pdev->dev);
handle = iommu_sva_bind_device(&udev->pdev->dev, current->mm);
if (IS_ERR(handle))
return -EINVAL;
udev->pasid = iommu_sva_get_pasid(handle);
udev->sva_handle = handle;
return 0;
}
static int uio_pci_sva_release(struct uio_info *info, struct inode *inode)
{
struct uio_pci_sva_dev *udev = info->priv;
if (!udev || !udev->pdev)
return -ENODEV;
iommu_sva_unbind_device(udev->sva_handle);
return 0;
}
static int probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct uio_pci_sva_dev *udev;
int ret, i, irq = 0;
ret = pci_enable_device(pdev);
if (ret) {
dev_err(&pdev->dev, "pci_enable_device failed: %d\n", ret);
return ret;
}
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret)
goto out_disable;
pci_set_master(pdev);
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX | PCI_IRQ_MSI);
if (ret > 0) {
irq = pci_irq_vector(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Failed to get MSI vector\n");
ret = irq;
goto out_disable;
}
} else
dev_warn(&pdev->dev,
"No IRQ vectors available (%d), using polling\n", ret);
udev = devm_kzalloc(&pdev->dev, sizeof(struct uio_pci_sva_dev),
GFP_KERNEL);
if (!udev) {
ret = -ENOMEM;
goto out_disable;
}
udev->pdev = pdev;
udev->info.name = "uio_pci_sva";
udev->info.version = "0.0.1";
udev->info.open = uio_pci_sva_open;
udev->info.release = uio_pci_sva_release;
udev->info.irq = irq;
udev->info.handler = irq_handler;
udev->info.priv = udev;
for (i = 0; i < MAX_UIO_MAPS; i++) {
struct resource *r = &pdev->resource[i];
struct uio_mem *uiomem = &udev->info.mem[i];
if (r->flags != (IORESOURCE_SIZEALIGN | IORESOURCE_MEM))
continue;
if (uiomem >= &udev->info.mem[MAX_UIO_MAPS]) {
dev_warn(&pdev->dev, "Do not support more than %d iomem\n",
MAX_UIO_MAPS);
break;
}
uiomem->memtype = UIO_MEM_PHYS;
uiomem->addr = r->start & PAGE_MASK;
uiomem->offs = r->start & ~PAGE_MASK;
uiomem->size =
(uiomem->offs + resource_size(r) + PAGE_SIZE - 1) &
PAGE_MASK;
uiomem->name = r->name;
}
ret = devm_uio_register_device(&pdev->dev, &udev->info);
if (ret) {
dev_err(&pdev->dev, "Failed to register uio device\n");
goto out_disable;
}
pci_set_drvdata(pdev, udev);
return 0;
out_disable:
pci_disable_device(pdev);
return ret;
}
static void remove(struct pci_dev *pdev)
{
pci_release_regions(pdev);
pci_disable_device(pdev);
}
static ssize_t pasid_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct uio_pci_sva_dev *udev = pci_get_drvdata(pdev);
return sysfs_emit(buf, "%d\n", udev->pasid);
}
static DEVICE_ATTR_RO(pasid);
static struct attribute *uio_pci_sva_attrs[] = {
&dev_attr_pasid.attr,
NULL
};
static const struct attribute_group uio_pci_sva_attr_group = {
.attrs = uio_pci_sva_attrs,
};
static const struct attribute_group *uio_pci_sva_attr_groups[] = {
&uio_pci_sva_attr_group,
NULL
};
static struct pci_driver uio_pci_generic_sva_driver = {
.name = "uio_pci_sva",
.dev_groups = uio_pci_sva_attr_groups,
.id_table = NULL,
.probe = probe,
.remove = remove,
};
module_pci_driver(uio_pci_generic_sva_driver);
MODULE_VERSION("0.0.01");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Yaxing Guo <guoyaxing@bosc.ac.cn>");
MODULE_DESCRIPTION("Generic UIO sva driver for PCI");
+273
View File
@@ -0,0 +1,273 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 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.
*/
#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/irq.h>
#include <linux/of.h>
#include <linux/property.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 void uio_pdrv_genirq_cleanup(void *data)
{
struct device *dev = data;
pm_runtime_disable(dev);
}
static int uio_pdrv_genirq_probe(struct platform_device *pdev)
{
struct uio_info *uioinfo = dev_get_platdata(&pdev->dev);
struct fwnode_handle *node = dev_fwnode(&pdev->dev);
struct uio_pdrv_genirq_platdata *priv;
struct uio_mem *uiomem;
int ret = -EINVAL;
int i;
if (node) {
const char *name;
/* 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;
}
if (!device_property_read_string(&pdev->dev, "linux,uio-name", &name))
uioinfo->name = devm_kstrdup(&pdev->dev, name, GFP_KERNEL);
else
uioinfo->name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
"%pfwP", node);
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_optional(pdev, 0);
uioinfo->irq = ret;
if (ret == -ENXIO)
uioinfo->irq = UIO_IRQ_NONE;
else if (ret == -EPROBE_DEFER)
return ret;
else if (ret < 0) {
dev_err(&pdev->dev, "failed to get IRQ\n");
return ret;
}
}
if (uioinfo->irq) {
/*
* If a level interrupt, dont do lazy disable. Otherwise the
* irq will fire again since clearing of the actual cause, on
* device level, is done in userspace
* irqd_is_level_type() isn't used since isn't valid until
* irq is configured.
*/
if (irq_get_trigger_type(uioinfo->irq) & IRQ_TYPE_LEVEL_MASK) {
dev_dbg(&pdev->dev, "disable lazy unmask\n");
irq_set_status_flags(uioinfo->irq, IRQ_DISABLE_UNLAZY);
}
}
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 & PAGE_MASK;
uiomem->offs = r->start & ~PAGE_MASK;
uiomem->size = (uiomem->offs + resource_size(r)
+ PAGE_SIZE - 1) & PAGE_MASK;
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 = devm_add_action_or_reset(&pdev->dev, uio_pdrv_genirq_cleanup,
&pdev->dev);
if (ret)
return ret;
ret = devm_uio_register_device(&pdev->dev, priv->uioinfo);
if (ret)
dev_err(&pdev->dev, "unable to register uio device\n");
return ret;
}
#ifdef CONFIG_OF
static struct of_device_id uio_of_genirq_match[] = {
{ /* This is filled with module_parm */ },
{ /* Sentinel */ },
};
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,
.driver = {
.name = DRIVER_NAME,
.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);
+226
View File
@@ -0,0 +1,226 @@
// SPDX-License-Identifier: GPL-2.0
/* sercos3: UIO driver for the Automata Sercos III PCI card
Copyright (C) 2008 Linutronix GmbH
Author: John Ogness <john.ogness@linutronix.de>
This is a straight-forward UIO driver, where interrupts are disabled
by the interrupt handler and re-enabled via a write to the UIO device
by the userspace-part.
The only part that may seem odd is the use of a logical OR when
storing and restoring enabled interrupts. This is done because the
userspace-part could directly modify the Interrupt Enable Register
at any time. To reduce possible conflicts, the kernel driver uses
a logical OR to make more controlled changes (rather than blindly
overwriting previous values).
Race conditions exist if the userspace-part directly modifies the
Interrupt Enable Register while in operation. The consequences are
that certain interrupts would fail to be enabled or disabled. For
this reason, the userspace-part should only directly modify the
Interrupt Enable Register at the beginning (to get things going).
The userspace-part can safely disable interrupts at any time using
a write to the UIO device.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/uio_driver.h>
#include <linux/io.h>
#include <linux/slab.h>
/* ID's for SERCOS III PCI card (PLX 9030) */
#define SERCOS_SUB_VENDOR_ID 0x1971
#define SERCOS_SUB_SYSID_3530 0x3530
#define SERCOS_SUB_SYSID_3535 0x3535
#define SERCOS_SUB_SYSID_3780 0x3780
/* Interrupt Enable Register */
#define IER0_OFFSET 0x08
/* Interrupt Status Register */
#define ISR0_OFFSET 0x18
struct sercos3_priv {
u32 ier0_cache;
spinlock_t ier0_cache_lock;
};
/* this function assumes ier0_cache_lock is locked! */
static void sercos3_disable_interrupts(struct uio_info *info,
struct sercos3_priv *priv)
{
void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
/* add enabled interrupts to cache */
priv->ier0_cache |= ioread32(ier0);
/* disable interrupts */
iowrite32(0, ier0);
}
/* this function assumes ier0_cache_lock is locked! */
static void sercos3_enable_interrupts(struct uio_info *info,
struct sercos3_priv *priv)
{
void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
/* restore previously enabled interrupts */
iowrite32(ioread32(ier0) | priv->ier0_cache, ier0);
priv->ier0_cache = 0;
}
static irqreturn_t sercos3_handler(int irq, struct uio_info *info)
{
struct sercos3_priv *priv = info->priv;
void __iomem *isr0 = info->mem[3].internal_addr + ISR0_OFFSET;
void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
if (!(ioread32(isr0) & ioread32(ier0)))
return IRQ_NONE;
spin_lock(&priv->ier0_cache_lock);
sercos3_disable_interrupts(info, priv);
spin_unlock(&priv->ier0_cache_lock);
return IRQ_HANDLED;
}
static int sercos3_irqcontrol(struct uio_info *info, s32 irq_on)
{
struct sercos3_priv *priv = info->priv;
spin_lock_irq(&priv->ier0_cache_lock);
if (irq_on)
sercos3_enable_interrupts(info, priv);
else
sercos3_disable_interrupts(info, priv);
spin_unlock_irq(&priv->ier0_cache_lock);
return 0;
}
static int sercos3_setup_iomem(struct pci_dev *dev, struct uio_info *info,
int n, int pci_bar)
{
info->mem[n].addr = pci_resource_start(dev, pci_bar);
if (!info->mem[n].addr)
return -1;
info->mem[n].internal_addr = ioremap(pci_resource_start(dev, pci_bar),
pci_resource_len(dev, pci_bar));
if (!info->mem[n].internal_addr)
return -1;
info->mem[n].size = pci_resource_len(dev, pci_bar);
info->mem[n].memtype = UIO_MEM_PHYS;
return 0;
}
static int sercos3_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
struct uio_info *info;
struct sercos3_priv *priv;
int i;
info = devm_kzalloc(&dev->dev, sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
priv = devm_kzalloc(&dev->dev, sizeof(struct sercos3_priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
if (pci_enable_device(dev))
return -ENODEV;
if (pci_request_regions(dev, "sercos3"))
goto out_disable;
/* we only need PCI BAR's 0, 2, 3, 4, 5 */
if (sercos3_setup_iomem(dev, info, 0, 0))
goto out_unmap;
if (sercos3_setup_iomem(dev, info, 1, 2))
goto out_unmap;
if (sercos3_setup_iomem(dev, info, 2, 3))
goto out_unmap;
if (sercos3_setup_iomem(dev, info, 3, 4))
goto out_unmap;
if (sercos3_setup_iomem(dev, info, 4, 5))
goto out_unmap;
spin_lock_init(&priv->ier0_cache_lock);
info->priv = priv;
info->name = "Sercos_III_PCI";
info->version = "0.0.1";
info->irq = dev->irq;
info->irq_flags = IRQF_SHARED;
info->handler = sercos3_handler;
info->irqcontrol = sercos3_irqcontrol;
pci_set_drvdata(dev, info);
if (uio_register_device(&dev->dev, info))
goto out_unmap;
return 0;
out_unmap:
for (i = 0; i < 5; i++) {
if (info->mem[i].internal_addr)
iounmap(info->mem[i].internal_addr);
}
pci_release_regions(dev);
out_disable:
pci_disable_device(dev);
return -ENODEV;
}
static void sercos3_pci_remove(struct pci_dev *dev)
{
struct uio_info *info = pci_get_drvdata(dev);
int i;
uio_unregister_device(info);
pci_release_regions(dev);
pci_disable_device(dev);
for (i = 0; i < 5; i++) {
if (info->mem[i].internal_addr)
iounmap(info->mem[i].internal_addr);
}
}
static const struct pci_device_id sercos3_pci_ids[] = {
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = SERCOS_SUB_VENDOR_ID,
.subdevice = SERCOS_SUB_SYSID_3530,
},
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = SERCOS_SUB_VENDOR_ID,
.subdevice = SERCOS_SUB_SYSID_3535,
},
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = SERCOS_SUB_VENDOR_ID,
.subdevice = SERCOS_SUB_SYSID_3780,
},
{ 0, }
};
static struct pci_driver sercos3_pci_driver = {
.name = "sercos3",
.id_table = sercos3_pci_ids,
.probe = sercos3_pci_probe,
.remove = sercos3_pci_remove,
};
module_pci_driver(sercos3_pci_driver);
MODULE_DESCRIPTION("UIO driver for the Automata Sercos III PCI card");
MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>");
MODULE_LICENSE("GPL v2");