aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/uio/uio_pci_generic.c
diff options
context:
space:
mode:
authorMichael S. Tsirkin <mst@redhat.com>2009-07-20 03:29:34 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2009-09-15 12:50:48 -0400
commitccb86a6907c9ba7b5be5f521362fc308e80bed34 (patch)
treee10f409f582a50243124d9b8bf38b2ec8be8d5ac /drivers/uio/uio_pci_generic.c
parenta56af87648054089d89874b52e3fc23ed4f274ad (diff)
uio: add generic driver for PCI 2.3 devices
This adds a generic uio driver that can bind to any PCI device. First user will be virtualization where a qemu userspace process needs to give guest OS access to the device. Interrupts are handled using the Interrupt Disable bit in the PCI command register and Interrupt Status bit in the PCI status register. All devices compliant to PCI 2.3 (circa 2002) and all compliant PCI Express devices should support these bits. Driver detects this support, and won't bind to devices which do not support the Interrupt Disable Bit in the command register. It's expected that more features of interest to virtualization will be added to this driver in the future. Possibilities are: mmap for device resources, MSI/MSI-X, eventfd (to interface with kvm), iommu. Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Acked-by: Chris Wright <chrisw@redhat.com> Signed-off-by: Hans J. Koch <hjk@linutronix.de> Acked-by: Jesse Barnes <jbarnes@virtuousgeek.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/uio/uio_pci_generic.c')
-rw-r--r--drivers/uio/uio_pci_generic.c207
1 files changed, 207 insertions, 0 deletions
diff --git a/drivers/uio/uio_pci_generic.c b/drivers/uio/uio_pci_generic.c
new file mode 100644
index 000000000000..313da35984af
--- /dev/null
+++ b/drivers/uio/uio_pci_generic.c
@@ -0,0 +1,207 @@
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>
25#include <linux/uio_driver.h>
26#include <linux/spinlock.h>
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;
35 spinlock_t lock; /* guards command register accesses */
36};
37
38static inline struct uio_pci_generic_dev *
39to_uio_pci_generic_dev(struct uio_info *info)
40{
41 return container_of(info, struct uio_pci_generic_dev, info);
42}
43
44/* Interrupt handler. Read/modify/write the command register to disable
45 * the interrupt. */
46static irqreturn_t irqhandler(int irq, struct uio_info *info)
47{
48 struct uio_pci_generic_dev *gdev = to_uio_pci_generic_dev(info);
49 struct pci_dev *pdev = gdev->pdev;
50 irqreturn_t ret = IRQ_NONE;
51 u32 cmd_status_dword;
52 u16 origcmd, newcmd, status;
53
54 /* We do a single dword read to retrieve both command and status.
55 * Document assumptions that make this possible. */
56 BUILD_BUG_ON(PCI_COMMAND % 4);
57 BUILD_BUG_ON(PCI_COMMAND + 2 != PCI_STATUS);
58
59 spin_lock_irq(&gdev->lock);
60 pci_block_user_cfg_access(pdev);
61
62 /* Read both command and status registers in a single 32-bit operation.
63 * Note: we could cache the value for command and move the status read
64 * out of the lock if there was a way to get notified of user changes
65 * to command register through sysfs. Should be good for shared irqs. */
66 pci_read_config_dword(pdev, PCI_COMMAND, &cmd_status_dword);
67 origcmd = cmd_status_dword;
68 status = cmd_status_dword >> 16;
69
70 /* Check interrupt status register to see whether our device
71 * triggered the interrupt. */
72 if (!(status & PCI_STATUS_INTERRUPT))
73 goto done;
74
75 /* We triggered the interrupt, disable it. */
76 newcmd = origcmd | PCI_COMMAND_INTX_DISABLE;
77 if (newcmd != origcmd)
78 pci_write_config_word(pdev, PCI_COMMAND, newcmd);
79
80 /* UIO core will signal the user process. */
81 ret = IRQ_HANDLED;
82done:
83
84 pci_unblock_user_cfg_access(pdev);
85 spin_unlock_irq(&gdev->lock);
86 return ret;
87}
88
89/* Verify that the device supports Interrupt Disable bit in command register,
90 * per PCI 2.3, by flipping this bit and reading it back: this bit was readonly
91 * in PCI 2.2. */
92static int __devinit verify_pci_2_3(struct pci_dev *pdev)
93{
94 u16 orig, new;
95 int err = 0;
96
97 pci_block_user_cfg_access(pdev);
98 pci_read_config_word(pdev, PCI_COMMAND, &orig);
99 pci_write_config_word(pdev, PCI_COMMAND,
100 orig ^ PCI_COMMAND_INTX_DISABLE);
101 pci_read_config_word(pdev, PCI_COMMAND, &new);
102 /* There's no way to protect against
103 * hardware bugs or detect them reliably, but as long as we know
104 * what the value should be, let's go ahead and check it. */
105 if ((new ^ orig) & ~PCI_COMMAND_INTX_DISABLE) {
106 err = -EBUSY;
107 dev_err(&pdev->dev, "Command changed from 0x%x to 0x%x: "
108 "driver or HW bug?\n", orig, new);
109 goto err;
110 }
111 if (!((new ^ orig) & PCI_COMMAND_INTX_DISABLE)) {
112 dev_warn(&pdev->dev, "Device does not support "
113 "disabling interrupts: unable to bind.\n");
114 err = -ENODEV;
115 goto err;
116 }
117 /* Now restore the original value. */
118 pci_write_config_word(pdev, PCI_COMMAND, orig);
119err:
120 pci_unblock_user_cfg_access(pdev);
121 return err;
122}
123
124static int __devinit probe(struct pci_dev *pdev,
125 const struct pci_device_id *id)
126{
127 struct uio_pci_generic_dev *gdev;
128 int err;
129
130 if (!pdev->irq) {
131 dev_warn(&pdev->dev, "No IRQ assigned to device: "
132 "no support for interrupts?\n");
133 return -ENODEV;
134 }
135
136 err = pci_enable_device(pdev);
137 if (err) {
138 dev_err(&pdev->dev, "%s: pci_enable_device failed: %d\n",
139 __func__, err);
140 return err;
141 }
142
143 err = verify_pci_2_3(pdev);
144 if (err)
145 goto err_verify;
146
147 gdev = kzalloc(sizeof(struct uio_pci_generic_dev), GFP_KERNEL);
148 if (!gdev) {
149 err = -ENOMEM;
150 goto err_alloc;
151 }
152
153 gdev->info.name = "uio_pci_generic";
154 gdev->info.version = DRIVER_VERSION;
155 gdev->info.irq = pdev->irq;
156 gdev->info.irq_flags = IRQF_SHARED;
157 gdev->info.handler = irqhandler;
158 gdev->pdev = pdev;
159 spin_lock_init(&gdev->lock);
160
161 if (uio_register_device(&pdev->dev, &gdev->info))
162 goto err_register;
163 pci_set_drvdata(pdev, gdev);
164
165 return 0;
166err_register:
167 kfree(gdev);
168err_alloc:
169err_verify:
170 pci_disable_device(pdev);
171 return err;
172}
173
174static void remove(struct pci_dev *pdev)
175{
176 struct uio_pci_generic_dev *gdev = pci_get_drvdata(pdev);
177
178 uio_unregister_device(&gdev->info);
179 pci_disable_device(pdev);
180 kfree(gdev);
181}
182
183static struct pci_driver driver = {
184 .name = "uio_pci_generic",
185 .id_table = NULL, /* only dynamic id's */
186 .probe = probe,
187 .remove = remove,
188};
189
190static int __init init(void)
191{
192 pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
193 return pci_register_driver(&driver);
194}
195
196static void __exit cleanup(void)
197{
198 pci_unregister_driver(&driver);
199}
200
201module_init(init);
202module_exit(cleanup);
203
204MODULE_VERSION(DRIVER_VERSION);
205MODULE_LICENSE("GPL v2");
206MODULE_AUTHOR(DRIVER_AUTHOR);
207MODULE_DESCRIPTION(DRIVER_DESC);