aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDamian Hobson-Garcia <dhobsong@igel.co.jp>2012-09-25 02:09:11 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-10-24 18:39:08 -0400
commit0a0c3b5a24bd802b1ebbf99e0b01296647b8199b (patch)
tree62dc10826157e8f3c1ed311ee175c2ab96b8a3b2
parent664df38b3c74656261d4227b4dd380cfa453f78f (diff)
Add new uio device for dynamic memory allocation
This device extends the uio_pdrv_genirq driver to provide limited dynamic memory allocation for UIO devices. This allows UIO devices to use CMA and IOMMU allocated memory regions. This driver is based on the uio_pdrv_genirq driver and provides the same generic interrupt handling capabilities. Like uio_prdv_genirq, a fixed number of memory regions, defined in the platform device's .resources field are exported to userpace. This driver adds the ability to export additional regions whose number and size are known at boot time, but whose memory is not allocated until the uio device file is opened for the first time. When the device file is closed, the allocated memory block is freed. Physical (DMA) addresses for the dynamic regions are provided to the userspace via /sys/class/uio/uioX/maps/mapY/addr in the same way as static addresses are when the uio device file is open, when no processes are holding the device file open, the address returned to userspace is DMA_ERROR_CODE. Signed-off-by: Damian Hobson-Garcia <dhobsong@igel.co.jp> Signed-off-by: "Hans J. Koch" <hjk@hansjkoch.de> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/uio/Kconfig16
-rw-r--r--drivers/uio/Makefile1
-rw-r--r--drivers/uio/uio_dmem_genirq.c354
-rw-r--r--include/linux/platform_data/uio_dmem_genirq.h26
4 files changed, 397 insertions, 0 deletions
diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig
index 6f3ea9bbc81..82e2b89d448 100644
--- a/drivers/uio/Kconfig
+++ b/drivers/uio/Kconfig
@@ -44,6 +44,22 @@ config UIO_PDRV_GENIRQ
44 44
45 If you don't know what to do here, say N. 45 If you don't know what to do here, say N.
46 46
47config UIO_DMEM_GENIRQ
48 tristate "Userspace platform driver with generic irq and dynamic memory"
49 help
50 Platform driver for Userspace I/O devices, including generic
51 interrupt handling code. Shared interrupts are not supported.
52
53 Memory regions can be specified with the same platform device
54 resources as the UIO_PDRV drivers, but dynamic regions can also
55 be specified.
56 The number and size of these regions is static,
57 but the memory allocation is not performed until
58 the associated device file is opened. The
59 memory is freed once the uio device is closed.
60
61 If you don't know what to do here, say N.
62
47config UIO_AEC 63config UIO_AEC
48 tristate "AEC video timestamp device" 64 tristate "AEC video timestamp device"
49 depends on PCI 65 depends on PCI
diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile
index d4dd9a5552f..b354c539507 100644
--- a/drivers/uio/Makefile
+++ b/drivers/uio/Makefile
@@ -2,6 +2,7 @@ obj-$(CONFIG_UIO) += uio.o
2obj-$(CONFIG_UIO_CIF) += uio_cif.o 2obj-$(CONFIG_UIO_CIF) += uio_cif.o
3obj-$(CONFIG_UIO_PDRV) += uio_pdrv.o 3obj-$(CONFIG_UIO_PDRV) += uio_pdrv.o
4obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_genirq.o 4obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_genirq.o
5obj-$(CONFIG_UIO_DMEM_GENIRQ) += uio_dmem_genirq.o
5obj-$(CONFIG_UIO_AEC) += uio_aec.o 6obj-$(CONFIG_UIO_AEC) += uio_aec.o
6obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o 7obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o
7obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o 8obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o
diff --git a/drivers/uio/uio_dmem_genirq.c b/drivers/uio/uio_dmem_genirq.c
new file mode 100644
index 00000000000..4d4dd008c8b
--- /dev/null
+++ b/drivers/uio/uio_dmem_genirq.c
@@ -0,0 +1,354 @@
1/*
2 * drivers/uio/uio_dmem_genirq.c
3 *
4 * Userspace I/O platform driver with generic IRQ handling code.
5 *
6 * Copyright (C) 2012 Damian Hobson-Garcia
7 *
8 * Based on uio_pdrv_genirq.c by Magnus Damm
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License version 2 as published by
12 * the Free Software Foundation.
13 */
14
15#include <linux/platform_device.h>
16#include <linux/uio_driver.h>
17#include <linux/spinlock.h>
18#include <linux/bitops.h>
19#include <linux/module.h>
20#include <linux/interrupt.h>
21#include <linux/platform_data/uio_dmem_genirq.h>
22#include <linux/stringify.h>
23#include <linux/pm_runtime.h>
24#include <linux/dma-mapping.h>
25#include <linux/slab.h>
26
27#include <linux/of.h>
28#include <linux/of_platform.h>
29#include <linux/of_address.h>
30
31#define DRIVER_NAME "uio_dmem_genirq"
32
33struct uio_dmem_genirq_platdata {
34 struct uio_info *uioinfo;
35 spinlock_t lock;
36 unsigned long flags;
37 struct platform_device *pdev;
38 unsigned int dmem_region_start;
39 unsigned int num_dmem_regions;
40 struct mutex alloc_lock;
41 unsigned int refcnt;
42};
43
44static int uio_dmem_genirq_open(struct uio_info *info, struct inode *inode)
45{
46 struct uio_dmem_genirq_platdata *priv = info->priv;
47 struct uio_mem *uiomem;
48 int ret = 0;
49
50 uiomem = &priv->uioinfo->mem[priv->dmem_region_start];
51
52 mutex_lock(&priv->alloc_lock);
53 while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) {
54 void *addr;
55 if (!uiomem->size)
56 break;
57
58 addr = dma_alloc_coherent(&priv->pdev->dev, uiomem->size,
59 (dma_addr_t *)&uiomem->addr, GFP_KERNEL);
60 if (!addr) {
61 ret = -ENOMEM;
62 break;
63 }
64
65 uiomem->internal_addr = addr;
66 ++uiomem;
67 }
68 priv->refcnt++;
69
70 mutex_unlock(&priv->alloc_lock);
71 /* Wait until the Runtime PM code has woken up the device */
72 pm_runtime_get_sync(&priv->pdev->dev);
73 return ret;
74}
75
76static int uio_dmem_genirq_release(struct uio_info *info, struct inode *inode)
77{
78 struct uio_dmem_genirq_platdata *priv = info->priv;
79 struct uio_mem *uiomem;
80
81 /* Tell the Runtime PM code that the device has become idle */
82 pm_runtime_put_sync(&priv->pdev->dev);
83
84 uiomem = &priv->uioinfo->mem[priv->dmem_region_start];
85
86 mutex_lock(&priv->alloc_lock);
87
88 priv->refcnt--;
89 while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) {
90 if (!uiomem->size)
91 break;
92
93 dma_free_coherent(&priv->pdev->dev, uiomem->size,
94 uiomem->internal_addr, uiomem->addr);
95 uiomem->addr = DMA_ERROR_CODE;
96 ++uiomem;
97 }
98
99 mutex_unlock(&priv->alloc_lock);
100 return 0;
101}
102
103static irqreturn_t uio_dmem_genirq_handler(int irq, struct uio_info *dev_info)
104{
105 struct uio_dmem_genirq_platdata *priv = dev_info->priv;
106
107 /* Just disable the interrupt in the interrupt controller, and
108 * remember the state so we can allow user space to enable it later.
109 */
110
111 if (!test_and_set_bit(0, &priv->flags))
112 disable_irq_nosync(irq);
113
114 return IRQ_HANDLED;
115}
116
117static int uio_dmem_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on)
118{
119 struct uio_dmem_genirq_platdata *priv = dev_info->priv;
120 unsigned long flags;
121
122 /* Allow user space to enable and disable the interrupt
123 * in the interrupt controller, but keep track of the
124 * state to prevent per-irq depth damage.
125 *
126 * Serialize this operation to support multiple tasks.
127 */
128
129 spin_lock_irqsave(&priv->lock, flags);
130 if (irq_on) {
131 if (test_and_clear_bit(0, &priv->flags))
132 enable_irq(dev_info->irq);
133 } else {
134 if (!test_and_set_bit(0, &priv->flags))
135 disable_irq(dev_info->irq);
136 }
137 spin_unlock_irqrestore(&priv->lock, flags);
138
139 return 0;
140}
141
142static int uio_dmem_genirq_probe(struct platform_device *pdev)
143{
144 struct uio_dmem_genirq_pdata *pdata = pdev->dev.platform_data;
145 struct uio_info *uioinfo = &pdata->uioinfo;
146 struct uio_dmem_genirq_platdata *priv;
147 struct uio_mem *uiomem;
148 int ret = -EINVAL;
149 int i;
150
151 if (!uioinfo) {
152 int irq;
153
154 /* alloc uioinfo for one device */
155 uioinfo = kzalloc(sizeof(*uioinfo), GFP_KERNEL);
156 if (!uioinfo) {
157 ret = -ENOMEM;
158 dev_err(&pdev->dev, "unable to kmalloc\n");
159 goto bad2;
160 }
161 uioinfo->name = pdev->dev.of_node->name;
162 uioinfo->version = "devicetree";
163
164 /* Multiple IRQs are not supported */
165 irq = platform_get_irq(pdev, 0);
166 if (irq == -ENXIO)
167 uioinfo->irq = UIO_IRQ_NONE;
168 else
169 uioinfo->irq = irq;
170 }
171
172 if (!uioinfo || !uioinfo->name || !uioinfo->version) {
173 dev_err(&pdev->dev, "missing platform_data\n");
174 goto bad0;
175 }
176
177 if (uioinfo->handler || uioinfo->irqcontrol ||
178 uioinfo->irq_flags & IRQF_SHARED) {
179 dev_err(&pdev->dev, "interrupt configuration error\n");
180 goto bad0;
181 }
182
183 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
184 if (!priv) {
185 ret = -ENOMEM;
186 dev_err(&pdev->dev, "unable to kmalloc\n");
187 goto bad0;
188 }
189
190 dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
191
192 priv->uioinfo = uioinfo;
193 spin_lock_init(&priv->lock);
194 priv->flags = 0; /* interrupt is enabled to begin with */
195 priv->pdev = pdev;
196 mutex_init(&priv->alloc_lock);
197
198 if (!uioinfo->irq) {
199 ret = platform_get_irq(pdev, 0);
200 if (ret < 0) {
201 dev_err(&pdev->dev, "failed to get IRQ\n");
202 goto bad0;
203 }
204 uioinfo->irq = ret;
205 }
206 uiomem = &uioinfo->mem[0];
207
208 for (i = 0; i < pdev->num_resources; ++i) {
209 struct resource *r = &pdev->resource[i];
210
211 if (r->flags != IORESOURCE_MEM)
212 continue;
213
214 if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) {
215 dev_warn(&pdev->dev, "device has more than "
216 __stringify(MAX_UIO_MAPS)
217 " I/O memory resources.\n");
218 break;
219 }
220
221 uiomem->memtype = UIO_MEM_PHYS;
222 uiomem->addr = r->start;
223 uiomem->size = resource_size(r);
224 ++uiomem;
225 }
226
227 priv->dmem_region_start = i;
228 priv->num_dmem_regions = pdata->num_dynamic_regions;
229
230 for (i = 0; i < pdata->num_dynamic_regions; ++i) {
231 if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) {
232 dev_warn(&pdev->dev, "device has more than "
233 __stringify(MAX_UIO_MAPS)
234 " dynamic and fixed memory regions.\n");
235 break;
236 }
237 uiomem->memtype = UIO_MEM_PHYS;
238 uiomem->addr = DMA_ERROR_CODE;
239 uiomem->size = pdata->dynamic_region_sizes[i];
240 ++uiomem;
241 }
242
243 while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) {
244 uiomem->size = 0;
245 ++uiomem;
246 }
247
248 /* This driver requires no hardware specific kernel code to handle
249 * interrupts. Instead, the interrupt handler simply disables the
250 * interrupt in the interrupt controller. User space is responsible
251 * for performing hardware specific acknowledge and re-enabling of
252 * the interrupt in the interrupt controller.
253 *
254 * Interrupt sharing is not supported.
255 */
256
257 uioinfo->handler = uio_dmem_genirq_handler;
258 uioinfo->irqcontrol = uio_dmem_genirq_irqcontrol;
259 uioinfo->open = uio_dmem_genirq_open;
260 uioinfo->release = uio_dmem_genirq_release;
261 uioinfo->priv = priv;
262
263 /* Enable Runtime PM for this device:
264 * The device starts in suspended state to allow the hardware to be
265 * turned off by default. The Runtime PM bus code should power on the
266 * hardware and enable clocks at open().
267 */
268 pm_runtime_enable(&pdev->dev);
269
270 ret = uio_register_device(&pdev->dev, priv->uioinfo);
271 if (ret) {
272 dev_err(&pdev->dev, "unable to register uio device\n");
273 goto bad1;
274 }
275
276 platform_set_drvdata(pdev, priv);
277 return 0;
278 bad1:
279 kfree(priv);
280 pm_runtime_disable(&pdev->dev);
281 bad0:
282 /* kfree uioinfo for OF */
283 if (pdev->dev.of_node)
284 kfree(uioinfo);
285 bad2:
286 return ret;
287}
288
289static int uio_dmem_genirq_remove(struct platform_device *pdev)
290{
291 struct uio_dmem_genirq_platdata *priv = platform_get_drvdata(pdev);
292
293 uio_unregister_device(priv->uioinfo);
294 pm_runtime_disable(&pdev->dev);
295
296 priv->uioinfo->handler = NULL;
297 priv->uioinfo->irqcontrol = NULL;
298
299 /* kfree uioinfo for OF */
300 if (pdev->dev.of_node)
301 kfree(priv->uioinfo);
302
303 kfree(priv);
304 return 0;
305}
306
307static int uio_dmem_genirq_runtime_nop(struct device *dev)
308{
309 /* Runtime PM callback shared between ->runtime_suspend()
310 * and ->runtime_resume(). Simply returns success.
311 *
312 * In this driver pm_runtime_get_sync() and pm_runtime_put_sync()
313 * are used at open() and release() time. This allows the
314 * Runtime PM code to turn off power to the device while the
315 * device is unused, ie before open() and after release().
316 *
317 * This Runtime PM callback does not need to save or restore
318 * any registers since user space is responsbile for hardware
319 * register reinitialization after open().
320 */
321 return 0;
322}
323
324static const struct dev_pm_ops uio_dmem_genirq_dev_pm_ops = {
325 .runtime_suspend = uio_dmem_genirq_runtime_nop,
326 .runtime_resume = uio_dmem_genirq_runtime_nop,
327};
328
329#ifdef CONFIG_OF
330static const struct of_device_id uio_of_genirq_match[] = {
331 { /* empty for now */ },
332};
333MODULE_DEVICE_TABLE(of, uio_of_genirq_match);
334#else
335# define uio_of_genirq_match NULL
336#endif
337
338static struct platform_driver uio_dmem_genirq = {
339 .probe = uio_dmem_genirq_probe,
340 .remove = uio_dmem_genirq_remove,
341 .driver = {
342 .name = DRIVER_NAME,
343 .owner = THIS_MODULE,
344 .pm = &uio_dmem_genirq_dev_pm_ops,
345 .of_match_table = uio_of_genirq_match,
346 },
347};
348
349module_platform_driver(uio_dmem_genirq);
350
351MODULE_AUTHOR("Damian Hobson-Garcia");
352MODULE_DESCRIPTION("Userspace I/O platform driver with dynamic memory.");
353MODULE_LICENSE("GPL v2");
354MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/include/linux/platform_data/uio_dmem_genirq.h b/include/linux/platform_data/uio_dmem_genirq.h
new file mode 100644
index 00000000000..973c1bb3216
--- /dev/null
+++ b/include/linux/platform_data/uio_dmem_genirq.h
@@ -0,0 +1,26 @@
1/*
2 * include/linux/platform_data/uio_dmem_genirq.h
3 *
4 * Copyright (C) 2012 Damian Hobson-Garcia
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation version 2.
9 *
10 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
11 * kind, whether express or implied; without even the implied warranty
12 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 */
15
16#ifndef _UIO_DMEM_GENIRQ_H
17#define _UIO_DMEM_GENIRQ_H
18
19#include <linux/uio_driver.h>
20
21struct uio_dmem_genirq_pdata {
22 struct uio_info uioinfo;
23 unsigned int *dynamic_region_sizes;
24 unsigned int num_dynamic_regions;
25};
26#endif /* _UIO_DMEM_GENIRQ_H */