firewire: Switch cdev code over to use register_chrdev and keep a list of devices.
authorKristian Høgsberg <krh@redhat.com>
Wed, 7 Mar 2007 17:12:44 +0000 (12:12 -0500)
committerStefan Richter <stefanr@s5r6.in-berlin.de>
Fri, 9 Mar 2007 21:03:09 +0000 (22:03 +0100)
The old mechanism kept a struct cdev for each fw device, but fops->release
would reference this struct after the device got freed in some cases.

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
drivers/firewire/fw-device-cdev.c
drivers/firewire/fw-device.c
drivers/firewire/fw-device.h
drivers/firewire/fw-transaction.c

index d9f3bb2be1c2a55155c147cfae367a1e9974496e..54ef27b2adb5ac2cf5bcfb6e7c5bbbef64d67528 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/poll.h>
 #include <linux/delay.h>
 #include <linux/mm.h>
+#include <linux/idr.h>
 #include <linux/compat.h>
 #include <asm/uaccess.h>
 #include "fw-transaction.h"
@@ -103,7 +104,9 @@ static int fw_device_op_open(struct inode *inode, struct file *file)
        struct client *client;
        unsigned long flags;
 
-       device = container_of(inode->i_cdev, struct fw_device, cdev);
+       device = fw_device_from_devt(inode->i_rdev);
+       if (device == NULL)
+               return -ENODEV;
 
        client = kzalloc(sizeof *client, GFP_KERNEL);
        if (client == NULL)
index ccc05e511a4f6b4372ffc3e6370f234d96e25a50..b24090ae9c705f7841808a89b869caa41eab9b7d 100644 (file)
@@ -25,6 +25,7 @@
 #include <linux/kthread.h>
 #include <linux/device.h>
 #include <linux/delay.h>
+#include <linux/idr.h>
 #include "fw-transaction.h"
 #include "fw-topology.h"
 #include "fw-device.h"
@@ -407,14 +408,31 @@ static int shutdown_unit(struct device *device, void *data)
        return 0;
 }
 
+static DEFINE_IDR(fw_device_idr);
+int fw_cdev_major;
+
+struct fw_device *fw_device_from_devt(dev_t devt)
+{
+       struct fw_device *device;
+
+       down_read(&fw_bus_type.subsys.rwsem);
+       device = idr_find(&fw_device_idr, MINOR(devt));
+       up_read(&fw_bus_type.subsys.rwsem);
+
+       return device;
+}
+
 static void fw_device_shutdown(struct work_struct *work)
 {
        struct fw_device *device =
                container_of(work, struct fw_device, work.work);
+       int minor = MINOR(device->device.devt);
+
+       down_write(&fw_bus_type.subsys.rwsem);
+       idr_remove(&fw_device_idr, minor);
+       up_write(&fw_bus_type.subsys.rwsem);
 
        device_remove_file(&device->device, &config_rom_attribute);
-       cdev_del(&device->cdev);
-       unregister_chrdev_region(device->device.devt, 1);
        device_for_each_child(&device->device, NULL, shutdown_unit);
        device_unregister(&device->device);
 }
@@ -434,9 +452,9 @@ static void fw_device_shutdown(struct work_struct *work)
 
 static void fw_device_init(struct work_struct *work)
 {
-       static atomic_t serial = ATOMIC_INIT(-1);
        struct fw_device *device =
                container_of(work, struct fw_device, work.work);
+       int minor, err;
 
        /* All failure paths here set node->data to NULL, so that we
         * don't try to do device_for_each_child() on a kfree()'d
@@ -456,28 +474,24 @@ static void fw_device_init(struct work_struct *work)
                return;
        }
 
+       err = -ENOMEM;
+       down_write(&fw_bus_type.subsys.rwsem);
+       if (idr_pre_get(&fw_device_idr, GFP_KERNEL))
+               err = idr_get_new(&fw_device_idr, device, &minor);
+       up_write(&fw_bus_type.subsys.rwsem);
+       if (err < 0)
+               goto error;
+
        device->device.bus = &fw_bus_type;
        device->device.release = fw_device_release;
        device->device.parent = device->card->device;
+       device->device.devt = MKDEV(fw_cdev_major, minor);
        snprintf(device->device.bus_id, sizeof device->device.bus_id,
-                "fw%d", atomic_inc_return(&serial));
-
-       if (alloc_chrdev_region(&device->device.devt, 0, 1, "fw")) {
-               fw_error("Failed to register char device region.\n");
-               goto error;
-       }
-
-       cdev_init(&device->cdev, &fw_device_ops);
-       device->cdev.owner = THIS_MODULE;
-       kobject_set_name(&device->cdev.kobj, device->device.bus_id);
-       if (cdev_add(&device->cdev, device->device.devt, 1)) {
-               fw_error("Failed to register char device.\n");
-               goto error;
-       }
+                "fw%d", minor);
 
        if (device_add(&device->device)) {
                fw_error("Failed to add device.\n");
-               goto error;
+               goto error_with_cdev;
        }
 
        if (device_create_file(&device->device, &config_rom_attribute) < 0) {
@@ -513,9 +527,11 @@ static void fw_device_init(struct work_struct *work)
 
  error_with_device:
        device_del(&device->device);
+ error_with_cdev:
+       down_write(&fw_bus_type.subsys.rwsem);
+       idr_remove(&fw_device_idr, minor);
+       up_write(&fw_bus_type.subsys.rwsem);
  error:
-       cdev_del(&device->cdev);
-       unregister_chrdev_region(device->device.devt, 1);
        put_device(&device->device);
 }
 
index 4f731c2b121ca02be8eff969012a9c673a9c05e5..1a3655bea3350b4e84562d331d263e4ca6d3f801 100644 (file)
@@ -39,7 +39,7 @@ struct fw_device {
        int generation;
        struct fw_card *card;
        struct device device;
-       struct cdev cdev;
+       struct list_head link;
        struct list_head client_list;
        __be32 *config_rom;
        size_t config_rom_length;
@@ -59,6 +59,9 @@ int fw_device_enable_phys_dma(struct fw_device *device);
 
 void fw_device_cdev_update(struct fw_device *device);
 
+struct fw_device *fw_device_from_devt(dev_t devt);
+extern int fw_cdev_major;
+
 struct fw_unit {
        struct device device;
        u32 *directory;
index 8e2b9455739031d6c0a8f170748dd18e82b3393b..3052698c13a6bd0c6aa09e61e03af4dd36555d8b 100644 (file)
@@ -761,6 +761,12 @@ static int __init fw_core_init(void)
        if (retval < 0)
                return retval;
 
+       fw_cdev_major = register_chrdev(0, "firewire", &fw_device_ops);
+       if (fw_cdev_major < 0) {
+               bus_unregister(&fw_bus_type);
+               return fw_cdev_major;
+       }
+
        /* Add the vendor textual descriptor. */
        retval = fw_core_add_descriptor(&vendor_id_descriptor);
        BUG_ON(retval < 0);
@@ -772,6 +778,7 @@ static int __init fw_core_init(void)
 
 static void __exit fw_core_cleanup(void)
 {
+       unregister_chrdev(fw_cdev_major, "firewire");
        bus_unregister(&fw_bus_type);
 }