162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* uio_pci_generic - generic UIO driver for PCI 2.3 devices
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2009 Red Hat, Inc.
562306a36Sopenharmony_ci * Author: Michael S. Tsirkin <mst@redhat.com>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Since the driver does not declare any device ids, you must allocate
862306a36Sopenharmony_ci * id and bind the device to the driver yourself.  For example:
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * # echo "8086 10f5" > /sys/bus/pci/drivers/uio_pci_generic/new_id
1162306a36Sopenharmony_ci * # echo -n 0000:00:19.0 > /sys/bus/pci/drivers/e1000e/unbind
1262306a36Sopenharmony_ci * # echo -n 0000:00:19.0 > /sys/bus/pci/drivers/uio_pci_generic/bind
1362306a36Sopenharmony_ci * # ls -l /sys/bus/pci/devices/0000:00:19.0/driver
1462306a36Sopenharmony_ci * .../0000:00:19.0/driver -> ../../../bus/pci/drivers/uio_pci_generic
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * Driver won't bind to devices which do not support the Interrupt Disable Bit
1762306a36Sopenharmony_ci * in the command register. All devices compliant to PCI 2.3 (circa 2002) and
1862306a36Sopenharmony_ci * all compliant PCI Express devices should support this bit.
1962306a36Sopenharmony_ci */
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <linux/device.h>
2262306a36Sopenharmony_ci#include <linux/module.h>
2362306a36Sopenharmony_ci#include <linux/pci.h>
2462306a36Sopenharmony_ci#include <linux/slab.h>
2562306a36Sopenharmony_ci#include <linux/uio_driver.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define DRIVER_VERSION	"0.01.0"
2862306a36Sopenharmony_ci#define DRIVER_AUTHOR	"Michael S. Tsirkin <mst@redhat.com>"
2962306a36Sopenharmony_ci#define DRIVER_DESC	"Generic UIO driver for PCI 2.3 devices"
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistruct uio_pci_generic_dev {
3262306a36Sopenharmony_ci	struct uio_info info;
3362306a36Sopenharmony_ci	struct pci_dev *pdev;
3462306a36Sopenharmony_ci};
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic inline struct uio_pci_generic_dev *
3762306a36Sopenharmony_cito_uio_pci_generic_dev(struct uio_info *info)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	return container_of(info, struct uio_pci_generic_dev, info);
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int release(struct uio_info *info, struct inode *inode)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct uio_pci_generic_dev *gdev = to_uio_pci_generic_dev(info);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	/*
4762306a36Sopenharmony_ci	 * This driver is insecure when used with devices doing DMA, but some
4862306a36Sopenharmony_ci	 * people (mis)use it with such devices.
4962306a36Sopenharmony_ci	 * Let's at least make sure DMA isn't left enabled after the userspace
5062306a36Sopenharmony_ci	 * driver closes the fd.
5162306a36Sopenharmony_ci	 * Note that there's a non-zero chance doing this will wedge the device
5262306a36Sopenharmony_ci	 * at least until reset.
5362306a36Sopenharmony_ci	 */
5462306a36Sopenharmony_ci	pci_clear_master(gdev->pdev);
5562306a36Sopenharmony_ci	return 0;
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci/* Interrupt handler. Read/modify/write the command register to disable
5962306a36Sopenharmony_ci * the interrupt. */
6062306a36Sopenharmony_cistatic irqreturn_t irqhandler(int irq, struct uio_info *info)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct uio_pci_generic_dev *gdev = to_uio_pci_generic_dev(info);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	if (!pci_check_and_mask_intx(gdev->pdev))
6562306a36Sopenharmony_ci		return IRQ_NONE;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	/* UIO core will signal the user process. */
6862306a36Sopenharmony_ci	return IRQ_HANDLED;
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic int probe(struct pci_dev *pdev,
7262306a36Sopenharmony_ci			   const struct pci_device_id *id)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	struct uio_pci_generic_dev *gdev;
7562306a36Sopenharmony_ci	struct uio_mem *uiomem;
7662306a36Sopenharmony_ci	int err;
7762306a36Sopenharmony_ci	int i;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	err = pcim_enable_device(pdev);
8062306a36Sopenharmony_ci	if (err) {
8162306a36Sopenharmony_ci		dev_err(&pdev->dev, "%s: pci_enable_device failed: %d\n",
8262306a36Sopenharmony_ci			__func__, err);
8362306a36Sopenharmony_ci		return err;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (pdev->irq && !pci_intx_mask_supported(pdev))
8762306a36Sopenharmony_ci		return -ENODEV;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	gdev = devm_kzalloc(&pdev->dev, sizeof(struct uio_pci_generic_dev), GFP_KERNEL);
9062306a36Sopenharmony_ci	if (!gdev)
9162306a36Sopenharmony_ci		return -ENOMEM;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	gdev->info.name = "uio_pci_generic";
9462306a36Sopenharmony_ci	gdev->info.version = DRIVER_VERSION;
9562306a36Sopenharmony_ci	gdev->info.release = release;
9662306a36Sopenharmony_ci	gdev->pdev = pdev;
9762306a36Sopenharmony_ci	if (pdev->irq && (pdev->irq != IRQ_NOTCONNECTED)) {
9862306a36Sopenharmony_ci		gdev->info.irq = pdev->irq;
9962306a36Sopenharmony_ci		gdev->info.irq_flags = IRQF_SHARED;
10062306a36Sopenharmony_ci		gdev->info.handler = irqhandler;
10162306a36Sopenharmony_ci	} else {
10262306a36Sopenharmony_ci		dev_warn(&pdev->dev, "No IRQ assigned to device: "
10362306a36Sopenharmony_ci			 "no support for interrupts?\n");
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	uiomem = &gdev->info.mem[0];
10762306a36Sopenharmony_ci	for (i = 0; i < MAX_UIO_MAPS; ++i) {
10862306a36Sopenharmony_ci		struct resource *r = &pdev->resource[i];
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci		if (r->flags != (IORESOURCE_SIZEALIGN | IORESOURCE_MEM))
11162306a36Sopenharmony_ci			continue;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci		if (uiomem >= &gdev->info.mem[MAX_UIO_MAPS]) {
11462306a36Sopenharmony_ci			dev_warn(
11562306a36Sopenharmony_ci				&pdev->dev,
11662306a36Sopenharmony_ci				"device has more than " __stringify(
11762306a36Sopenharmony_ci					MAX_UIO_MAPS) " I/O memory resources.\n");
11862306a36Sopenharmony_ci			break;
11962306a36Sopenharmony_ci		}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci		uiomem->memtype = UIO_MEM_PHYS;
12262306a36Sopenharmony_ci		uiomem->addr = r->start & PAGE_MASK;
12362306a36Sopenharmony_ci		uiomem->offs = r->start & ~PAGE_MASK;
12462306a36Sopenharmony_ci		uiomem->size =
12562306a36Sopenharmony_ci			(uiomem->offs + resource_size(r) + PAGE_SIZE - 1) &
12662306a36Sopenharmony_ci			PAGE_MASK;
12762306a36Sopenharmony_ci		uiomem->name = r->name;
12862306a36Sopenharmony_ci		++uiomem;
12962306a36Sopenharmony_ci	}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	while (uiomem < &gdev->info.mem[MAX_UIO_MAPS]) {
13262306a36Sopenharmony_ci		uiomem->size = 0;
13362306a36Sopenharmony_ci		++uiomem;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	return devm_uio_register_device(&pdev->dev, &gdev->info);
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic struct pci_driver uio_pci_driver = {
14062306a36Sopenharmony_ci	.name = "uio_pci_generic",
14162306a36Sopenharmony_ci	.id_table = NULL, /* only dynamic id's */
14262306a36Sopenharmony_ci	.probe = probe,
14362306a36Sopenharmony_ci};
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cimodule_pci_driver(uio_pci_driver);
14662306a36Sopenharmony_ciMODULE_VERSION(DRIVER_VERSION);
14762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
14862306a36Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR);
14962306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC);
150