[PATCH 1/2] uio: be aware of open(), mmap(), close()

From: Benedikt Spranger
Date: Thu Dec 06 2012 - 07:47:54 EST


After open(), mmap(), close() the mmaped pointer is valid. Removing the
underlaying module causes a Oops or other strange effects. Keep all structures
valid till the last user is gone.

Signed-off-by: Benedikt Spranger <b.spranger@xxxxxxxxxxxxx>
---
drivers/uio/uio.c | 59 ++++++++++++++++++++++++++++++-----------
drivers/uio/uio_pdrv_genirq.c | 4 +--
include/linux/uio_driver.h | 4 +--
3 files changed, 48 insertions(+), 19 deletions(-)

diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c
index 5110f36..b96499a 100644
--- a/drivers/uio/uio.c
+++ b/drivers/uio/uio.c
@@ -433,8 +433,22 @@ static irqreturn_t uio_interrupt(int irq, void *dev_id)
struct uio_listener {
struct uio_device *dev;
s32 event_count;
+ struct kref kref;
};

+static void uio_release_listener(struct kref *kref)
+{
+ struct uio_listener *listener = container_of(kref, struct uio_listener,
+ kref);
+ struct uio_device *idev = listener->dev;
+
+ if (idev->info->release)
+ idev->info->release(idev->info);
+
+ module_put(idev->owner);
+ kfree(listener);
+}
+
static int uio_open(struct inode *inode, struct file *filep)
{
struct uio_device *idev;
@@ -465,10 +479,13 @@ static int uio_open(struct inode *inode, struct file *filep)
filep->private_data = listener;

if (idev->info->open) {
- ret = idev->info->open(idev->info, inode);
+ ret = idev->info->open(idev->info);
if (ret)
goto err_infoopen;
}
+
+ kref_init(&listener->kref);
+
return 0;

err_infoopen:
@@ -491,16 +508,11 @@ static int uio_fasync(int fd, struct file *filep, int on)

static int uio_release(struct inode *inode, struct file *filep)
{
- int ret = 0;
struct uio_listener *listener = filep->private_data;
- struct uio_device *idev = listener->dev;

- if (idev->info->release)
- ret = idev->info->release(idev->info, inode);
+ kref_put(&listener->kref, uio_release_listener);

- module_put(idev->owner);
- kfree(listener);
- return ret;
+ return 0;
}

static unsigned int uio_poll(struct file *filep, poll_table *wait)
@@ -605,19 +617,22 @@ static int uio_find_mem_index(struct vm_area_struct *vma)

static void uio_vma_open(struct vm_area_struct *vma)
{
- struct uio_device *idev = vma->vm_private_data;
- idev->vma_count++;
+ struct uio_listener *listener = vma->vm_private_data;
+
+ kref_get(&listener->kref);
}

static void uio_vma_close(struct vm_area_struct *vma)
{
- struct uio_device *idev = vma->vm_private_data;
- idev->vma_count--;
+ struct uio_listener *listener = vma->vm_private_data;
+
+ kref_put(&listener->kref, uio_release_listener);
}

static int uio_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
- struct uio_device *idev = vma->vm_private_data;
+ struct uio_listener *listener = vma->vm_private_data;
+ struct uio_device *idev = listener->dev;
struct page *page;
unsigned long offset;

@@ -646,20 +661,34 @@ static const struct vm_operations_struct uio_vm_ops = {
.fault = uio_vma_fault,
};

+static const struct vm_operations_struct uio_phys_ops = {
+ .open = uio_vma_open,
+ .close = uio_vma_close,
+};
+
static int uio_mmap_physical(struct vm_area_struct *vma)
{
struct uio_device *idev = vma->vm_private_data;
int mi = uio_find_mem_index(vma);
+ int ret;
+
if (mi < 0)
return -EINVAL;

vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ vma->vm_ops = &uio_phys_ops;

- return remap_pfn_range(vma,
+ ret = remap_pfn_range(vma,
vma->vm_start,
idev->info->mem[mi].addr >> PAGE_SHIFT,
vma->vm_end - vma->vm_start,
vma->vm_page_prot);
+ if (ret)
+ return ret;
+
+ uio_vma_open(vma);
+
+ return 0;
}

static int uio_mmap_logical(struct vm_area_struct *vma)
@@ -681,7 +710,7 @@ static int uio_mmap(struct file *filep, struct vm_area_struct *vma)
if (vma->vm_end < vma->vm_start)
return -EINVAL;

- vma->vm_private_data = idev;
+ vma->vm_private_data = listener;

mi = uio_find_mem_index(vma);
if (mi < 0)
diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c
index 42202cd..a4e32fa 100644
--- a/drivers/uio/uio_pdrv_genirq.c
+++ b/drivers/uio/uio_pdrv_genirq.c
@@ -37,7 +37,7 @@ struct uio_pdrv_genirq_platdata {
struct platform_device *pdev;
};

-static int uio_pdrv_genirq_open(struct uio_info *info, struct inode *inode)
+static int uio_pdrv_genirq_open(struct uio_info *info)
{
struct uio_pdrv_genirq_platdata *priv = info->priv;

@@ -46,7 +46,7 @@ static int uio_pdrv_genirq_open(struct uio_info *info, struct inode *inode)
return 0;
}

-static int uio_pdrv_genirq_release(struct uio_info *info, struct inode *inode)
+static int uio_pdrv_genirq_release(struct uio_info *info)
{
struct uio_pdrv_genirq_platdata *priv = info->priv;

diff --git a/include/linux/uio_driver.h b/include/linux/uio_driver.h
index 1ad4724..1bc6493 100644
--- a/include/linux/uio_driver.h
+++ b/include/linux/uio_driver.h
@@ -92,8 +92,8 @@ struct uio_info {
void *priv;
irqreturn_t (*handler)(int irq, struct uio_info *dev_info);
int (*mmap)(struct uio_info *info, struct vm_area_struct *vma);
- int (*open)(struct uio_info *info, struct inode *inode);
- int (*release)(struct uio_info *info, struct inode *inode);
+ int (*open)(struct uio_info *info);
+ void (*release)(struct uio_info *info);
int (*irqcontrol)(struct uio_info *info, s32 irq_on);
};

--
1.7.10.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/