]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blame - drivers/uio/uio_pci_generic.c
PCI: Introduce INTx check & mask API
[mirror_ubuntu-bionic-kernel.git] / drivers / uio / uio_pci_generic.c
CommitLineData
ccb86a69
MT
1/* uio_pci_generic - generic UIO driver for PCI 2.3 devices
2 *
3 * Copyright (C) 2009 Red Hat, Inc.
4 * Author: Michael S. Tsirkin <mst@redhat.com>
5 *
6 * This work is licensed under the terms of the GNU GPL, version 2.
7 *
8 * Since the driver does not declare any device ids, you must allocate
9 * id and bind the device to the driver yourself. For example:
10 *
11 * # echo "8086 10f5" > /sys/bus/pci/drivers/uio_pci_generic/new_id
12 * # echo -n 0000:00:19.0 > /sys/bus/pci/drivers/e1000e/unbind
13 * # echo -n 0000:00:19.0 > /sys/bus/pci/drivers/uio_pci_generic/bind
14 * # ls -l /sys/bus/pci/devices/0000:00:19.0/driver
15 * .../0000:00:19.0/driver -> ../../../bus/pci/drivers/uio_pci_generic
16 *
17 * Driver won't bind to devices which do not support the Interrupt Disable Bit
18 * in the command register. All devices compliant to PCI 2.3 (circa 2002) and
19 * all compliant PCI Express devices should support this bit.
20 */
21
22#include <linux/device.h>
23#include <linux/module.h>
24#include <linux/pci.h>
5a0e3ad6 25#include <linux/slab.h>
ccb86a69 26#include <linux/uio_driver.h>
ccb86a69
MT
27
28#define DRIVER_VERSION "0.01.0"
29#define DRIVER_AUTHOR "Michael S. Tsirkin <mst@redhat.com>"
30#define DRIVER_DESC "Generic UIO driver for PCI 2.3 devices"
31
32struct uio_pci_generic_dev {
33 struct uio_info info;
34 struct pci_dev *pdev;
ccb86a69
MT
35};
36
37static inline struct uio_pci_generic_dev *
38to_uio_pci_generic_dev(struct uio_info *info)
39{
40 return container_of(info, struct uio_pci_generic_dev, info);
41}
42
43/* Interrupt handler. Read/modify/write the command register to disable
44 * the interrupt. */
45static irqreturn_t irqhandler(int irq, struct uio_info *info)
46{
47 struct uio_pci_generic_dev *gdev = to_uio_pci_generic_dev(info);
48 struct pci_dev *pdev = gdev->pdev;
49 irqreturn_t ret = IRQ_NONE;
50 u32 cmd_status_dword;
51 u16 origcmd, newcmd, status;
52
53 /* We do a single dword read to retrieve both command and status.
54 * Document assumptions that make this possible. */
55 BUILD_BUG_ON(PCI_COMMAND % 4);
56 BUILD_BUG_ON(PCI_COMMAND + 2 != PCI_STATUS);
57
fb51ccbf
JK
58 if (!pci_cfg_access_trylock(pdev))
59 goto error;
ccb86a69
MT
60
61 /* Read both command and status registers in a single 32-bit operation.
62 * Note: we could cache the value for command and move the status read
63 * out of the lock if there was a way to get notified of user changes
64 * to command register through sysfs. Should be good for shared irqs. */
65 pci_read_config_dword(pdev, PCI_COMMAND, &cmd_status_dword);
66 origcmd = cmd_status_dword;
67 status = cmd_status_dword >> 16;
68
69 /* Check interrupt status register to see whether our device
70 * triggered the interrupt. */
71 if (!(status & PCI_STATUS_INTERRUPT))
72 goto done;
73
74 /* We triggered the interrupt, disable it. */
75 newcmd = origcmd | PCI_COMMAND_INTX_DISABLE;
76 if (newcmd != origcmd)
77 pci_write_config_word(pdev, PCI_COMMAND, newcmd);
78
79 /* UIO core will signal the user process. */
80 ret = IRQ_HANDLED;
81done:
82
fb51ccbf 83 pci_cfg_access_lock(pdev);
ccb86a69
MT
84 return ret;
85}
86
87/* Verify that the device supports Interrupt Disable bit in command register,
88 * per PCI 2.3, by flipping this bit and reading it back: this bit was readonly
89 * in PCI 2.2. */
90static int __devinit verify_pci_2_3(struct pci_dev *pdev)
91{
92 u16 orig, new;
93 int err = 0;
94
fb51ccbf 95 pci_cfg_access_lock(pdev);
ccb86a69
MT
96 pci_read_config_word(pdev, PCI_COMMAND, &orig);
97 pci_write_config_word(pdev, PCI_COMMAND,
98 orig ^ PCI_COMMAND_INTX_DISABLE);
99 pci_read_config_word(pdev, PCI_COMMAND, &new);
100 /* There's no way to protect against
101 * hardware bugs or detect them reliably, but as long as we know
102 * what the value should be, let's go ahead and check it. */
103 if ((new ^ orig) & ~PCI_COMMAND_INTX_DISABLE) {
104 err = -EBUSY;
105 dev_err(&pdev->dev, "Command changed from 0x%x to 0x%x: "
106 "driver or HW bug?\n", orig, new);
107 goto err;
108 }
109 if (!((new ^ orig) & PCI_COMMAND_INTX_DISABLE)) {
110 dev_warn(&pdev->dev, "Device does not support "
111 "disabling interrupts: unable to bind.\n");
112 err = -ENODEV;
113 goto err;
114 }
115 /* Now restore the original value. */
116 pci_write_config_word(pdev, PCI_COMMAND, orig);
117err:
fb51ccbf 118 pci_cfg_access_unlock(pdev);
ccb86a69
MT
119 return err;
120}
121
122static int __devinit probe(struct pci_dev *pdev,
123 const struct pci_device_id *id)
124{
125 struct uio_pci_generic_dev *gdev;
126 int err;
127
ccb86a69
MT
128 err = pci_enable_device(pdev);
129 if (err) {
130 dev_err(&pdev->dev, "%s: pci_enable_device failed: %d\n",
131 __func__, err);
132 return err;
133 }
134
1037246c
KV
135 if (!pdev->irq) {
136 dev_warn(&pdev->dev, "No IRQ assigned to device: "
137 "no support for interrupts?\n");
138 pci_disable_device(pdev);
139 return -ENODEV;
140 }
141
ccb86a69
MT
142 err = verify_pci_2_3(pdev);
143 if (err)
144 goto err_verify;
145
146 gdev = kzalloc(sizeof(struct uio_pci_generic_dev), GFP_KERNEL);
147 if (!gdev) {
148 err = -ENOMEM;
149 goto err_alloc;
150 }
151
152 gdev->info.name = "uio_pci_generic";
153 gdev->info.version = DRIVER_VERSION;
154 gdev->info.irq = pdev->irq;
155 gdev->info.irq_flags = IRQF_SHARED;
156 gdev->info.handler = irqhandler;
157 gdev->pdev = pdev;
ccb86a69
MT
158
159 if (uio_register_device(&pdev->dev, &gdev->info))
160 goto err_register;
161 pci_set_drvdata(pdev, gdev);
162
163 return 0;
164err_register:
165 kfree(gdev);
166err_alloc:
167err_verify:
168 pci_disable_device(pdev);
169 return err;
170}
171
172static void remove(struct pci_dev *pdev)
173{
174 struct uio_pci_generic_dev *gdev = pci_get_drvdata(pdev);
175
176 uio_unregister_device(&gdev->info);
177 pci_disable_device(pdev);
178 kfree(gdev);
179}
180
181static struct pci_driver driver = {
182 .name = "uio_pci_generic",
183 .id_table = NULL, /* only dynamic id's */
184 .probe = probe,
185 .remove = remove,
186};
187
188static int __init init(void)
189{
190 pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
191 return pci_register_driver(&driver);
192}
193
194static void __exit cleanup(void)
195{
196 pci_unregister_driver(&driver);
197}
198
199module_init(init);
200module_exit(cleanup);
201
202MODULE_VERSION(DRIVER_VERSION);
203MODULE_LICENSE("GPL v2");
204MODULE_AUTHOR(DRIVER_AUTHOR);
205MODULE_DESCRIPTION(DRIVER_DESC);