[S390] vmur: fix reference counting for vmur device structure

When a vmur device is removed due to a detach of the device, currently the
ur device structure is freed. Unfortunately it can happen, that there is
still a user of the device structure, when the character device is open
during the detach process. To fix this, reference counting for the vmur
structure is introduced.
In addition to that, the online, offline, probe and remove functions are
serialized now using a global mutex.

Signed-off-by: Michael Holzheu <holzheu@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
This commit is contained in:
Michael Holzheu 2007-08-22 13:51:41 +02:00 committed by Martin Schwidefsky
parent 0a87c5cfc0
commit 8127a1f80a
2 changed files with 162 additions and 59 deletions

View File

@ -69,8 +69,26 @@ static struct ccw_driver ur_driver = {
.set_offline = ur_set_offline, .set_offline = ur_set_offline,
}; };
static DEFINE_MUTEX(vmur_mutex);
/* /*
* Allocation, freeing, getting and putting of urdev structures * Allocation, freeing, getting and putting of urdev structures
*
* Each ur device (urd) contains a reference to its corresponding ccw device
* (cdev) using the urd->cdev pointer. Each ccw device has a reference to the
* ur device using the cdev->dev.driver_data pointer.
*
* urd references:
* - ur_probe gets a urd reference, ur_remove drops the reference
* (cdev->dev.driver_data)
* - ur_open gets a urd reference, ur_relase drops the reference
* (urf->urd)
*
* cdev references:
* - urdev_alloc get a cdev reference (urd->cdev)
* - urdev_free drops the cdev reference (urd->cdev)
*
* Setting and clearing of cdev->dev.driver_data is protected by the ccwdev lock
*/ */
static struct urdev *urdev_alloc(struct ccw_device *cdev) static struct urdev *urdev_alloc(struct ccw_device *cdev)
{ {
@ -79,42 +97,61 @@ static struct urdev *urdev_alloc(struct ccw_device *cdev)
urd = kzalloc(sizeof(struct urdev), GFP_KERNEL); urd = kzalloc(sizeof(struct urdev), GFP_KERNEL);
if (!urd) if (!urd)
return NULL; return NULL;
urd->cdev = cdev;
urd->reclen = cdev->id.driver_info; urd->reclen = cdev->id.driver_info;
ccw_device_get_id(cdev, &urd->dev_id); ccw_device_get_id(cdev, &urd->dev_id);
mutex_init(&urd->io_mutex); mutex_init(&urd->io_mutex);
mutex_init(&urd->open_mutex); mutex_init(&urd->open_mutex);
atomic_set(&urd->ref_count, 1);
urd->cdev = cdev;
get_device(&cdev->dev);
return urd; return urd;
} }
static void urdev_free(struct urdev *urd) static void urdev_free(struct urdev *urd)
{ {
TRACE("urdev_free: %p\n", urd);
if (urd->cdev)
put_device(&urd->cdev->dev);
kfree(urd); kfree(urd);
} }
/* static void urdev_get(struct urdev *urd)
* This is how the character device driver gets a reference to a {
* ur device. When this call returns successfully, a reference has atomic_inc(&urd->ref_count);
* been taken (by get_device) on the underlying kobject. The recipient }
* of this urdev pointer must eventually drop it with urdev_put(urd)
* which does the corresponding put_device(). static struct urdev *urdev_get_from_cdev(struct ccw_device *cdev)
*/ {
struct urdev *urd;
unsigned long flags;
spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
urd = cdev->dev.driver_data;
if (urd)
urdev_get(urd);
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
return urd;
}
static struct urdev *urdev_get_from_devno(u16 devno) static struct urdev *urdev_get_from_devno(u16 devno)
{ {
char bus_id[16]; char bus_id[16];
struct ccw_device *cdev; struct ccw_device *cdev;
struct urdev *urd;
sprintf(bus_id, "0.0.%04x", devno); sprintf(bus_id, "0.0.%04x", devno);
cdev = get_ccwdev_by_busid(&ur_driver, bus_id); cdev = get_ccwdev_by_busid(&ur_driver, bus_id);
if (!cdev) if (!cdev)
return NULL; return NULL;
urd = urdev_get_from_cdev(cdev);
return cdev->dev.driver_data; put_device(&cdev->dev);
return urd;
} }
static void urdev_put(struct urdev *urd) static void urdev_put(struct urdev *urd)
{ {
put_device(&urd->cdev->dev); if (atomic_dec_and_test(&urd->ref_count))
urdev_free(urd);
} }
/* /*
@ -246,6 +283,7 @@ static void ur_int_handler(struct ccw_device *cdev, unsigned long intparm,
return; return;
} }
urd = cdev->dev.driver_data; urd = cdev->dev.driver_data;
BUG_ON(!urd);
/* On special conditions irb is an error pointer */ /* On special conditions irb is an error pointer */
if (IS_ERR(irb)) if (IS_ERR(irb))
urd->io_request_rc = PTR_ERR(irb); urd->io_request_rc = PTR_ERR(irb);
@ -263,9 +301,15 @@ static void ur_int_handler(struct ccw_device *cdev, unsigned long intparm,
static ssize_t ur_attr_reclen_show(struct device *dev, static ssize_t ur_attr_reclen_show(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
struct urdev *urd = dev->driver_data; struct urdev *urd;
int rc;
return sprintf(buf, "%zu\n", urd->reclen); urd = urdev_get_from_cdev(to_ccwdev(dev));
if (!urd)
return -ENODEV;
rc = sprintf(buf, "%zu\n", urd->reclen);
urdev_put(urd);
return rc;
} }
static DEVICE_ATTR(reclen, 0444, ur_attr_reclen_show, NULL); static DEVICE_ATTR(reclen, 0444, ur_attr_reclen_show, NULL);
@ -726,64 +770,63 @@ static struct file_operations ur_fops = {
/* /*
* ccw_device infrastructure: * ccw_device infrastructure:
* ur_probe gets its own ref to the device (i.e. get_device), * ur_probe creates the struct urdev (with refcount = 1), the device
* creates the struct urdev, the device attributes, sets up * attributes, sets up the interrupt handler and validates the virtual
* the interrupt handler and validates the virtual unit record device. * unit record device.
* ur_remove removes the device attributes, frees the struct urdev * ur_remove removes the device attributes and drops the reference to
* and drops (put_device) the ref to the device we got in ur_probe. * struct urdev.
*
* ur_probe, ur_remove, ur_set_online and ur_set_offline are serialized
* by the vmur_mutex lock.
*
* urd->char_device is used as indication that the online function has
* been completed successfully.
*/ */
static int ur_probe(struct ccw_device *cdev) static int ur_probe(struct ccw_device *cdev)
{ {
struct urdev *urd; struct urdev *urd;
int rc; int rc;
TRACE("ur_probe: cdev=%p state=%d\n", cdev, *(int *) cdev->private); TRACE("ur_probe: cdev=%p\n", cdev);
if (!get_device(&cdev->dev))
return -ENODEV;
mutex_lock(&vmur_mutex);
urd = urdev_alloc(cdev); urd = urdev_alloc(cdev);
if (!urd) { if (!urd) {
rc = -ENOMEM; rc = -ENOMEM;
goto fail; goto fail_unlock;
} }
rc = ur_create_attributes(&cdev->dev); rc = ur_create_attributes(&cdev->dev);
if (rc) { if (rc) {
rc = -ENOMEM; rc = -ENOMEM;
goto fail; goto fail_urdev_put;
} }
cdev->dev.driver_data = urd;
cdev->handler = ur_int_handler; cdev->handler = ur_int_handler;
/* validate virtual unit record device */ /* validate virtual unit record device */
urd->class = get_urd_class(urd); urd->class = get_urd_class(urd);
if (urd->class < 0) { if (urd->class < 0) {
rc = urd->class; rc = urd->class;
goto fail; goto fail_remove_attr;
} }
if ((urd->class != DEV_CLASS_UR_I) && (urd->class != DEV_CLASS_UR_O)) { if ((urd->class != DEV_CLASS_UR_I) && (urd->class != DEV_CLASS_UR_O)) {
rc = -ENOTSUPP; rc = -ENOTSUPP;
goto fail; goto fail_remove_attr;
} }
spin_lock_irq(get_ccwdev_lock(cdev));
cdev->dev.driver_data = urd;
spin_unlock_irq(get_ccwdev_lock(cdev));
mutex_unlock(&vmur_mutex);
return 0; return 0;
fail: fail_remove_attr:
urdev_free(urd);
put_device(&cdev->dev);
return rc;
}
static void ur_remove(struct ccw_device *cdev)
{
struct urdev *urd = cdev->dev.driver_data;
TRACE("ur_remove\n");
if (cdev->online)
ur_set_offline(cdev);
ur_remove_attributes(&cdev->dev); ur_remove_attributes(&cdev->dev);
urdev_free(urd); fail_urdev_put:
put_device(&cdev->dev); urdev_put(urd);
fail_unlock:
mutex_unlock(&vmur_mutex);
return rc;
} }
static int ur_set_online(struct ccw_device *cdev) static int ur_set_online(struct ccw_device *cdev)
@ -792,20 +835,29 @@ static int ur_set_online(struct ccw_device *cdev)
int minor, major, rc; int minor, major, rc;
char node_id[16]; char node_id[16];
TRACE("ur_set_online: cdev=%p state=%d\n", cdev, TRACE("ur_set_online: cdev=%p\n", cdev);
*(int *) cdev->private);
if (!try_module_get(ur_driver.owner)) mutex_lock(&vmur_mutex);
return -EINVAL; urd = urdev_get_from_cdev(cdev);
if (!urd) {
/* ur_remove already deleted our urd */
rc = -ENODEV;
goto fail_unlock;
}
if (urd->char_device) {
/* Another ur_set_online was faster */
rc = -EBUSY;
goto fail_urdev_put;
}
urd = (struct urdev *) cdev->dev.driver_data;
minor = urd->dev_id.devno; minor = urd->dev_id.devno;
major = MAJOR(ur_first_dev_maj_min); major = MAJOR(ur_first_dev_maj_min);
urd->char_device = cdev_alloc(); urd->char_device = cdev_alloc();
if (!urd->char_device) { if (!urd->char_device) {
rc = -ENOMEM; rc = -ENOMEM;
goto fail_module_put; goto fail_urdev_put;
} }
cdev_init(urd->char_device, &ur_fops); cdev_init(urd->char_device, &ur_fops);
@ -834,29 +886,79 @@ static int ur_set_online(struct ccw_device *cdev)
TRACE("ur_set_online: device_create rc=%d\n", rc); TRACE("ur_set_online: device_create rc=%d\n", rc);
goto fail_free_cdev; goto fail_free_cdev;
} }
urdev_put(urd);
mutex_unlock(&vmur_mutex);
return 0; return 0;
fail_free_cdev: fail_free_cdev:
cdev_del(urd->char_device); cdev_del(urd->char_device);
fail_module_put: urd->char_device = NULL;
module_put(ur_driver.owner); fail_urdev_put:
urdev_put(urd);
fail_unlock:
mutex_unlock(&vmur_mutex);
return rc;
}
static int ur_set_offline_force(struct ccw_device *cdev, int force)
{
struct urdev *urd;
int rc;
TRACE("ur_set_offline: cdev=%p\n", cdev);
urd = urdev_get_from_cdev(cdev);
if (!urd)
/* ur_remove already deleted our urd */
return -ENODEV;
if (!urd->char_device) {
/* Another ur_set_offline was faster */
rc = -EBUSY;
goto fail_urdev_put;
}
if (!force && (atomic_read(&urd->ref_count) > 2)) {
/* There is still a user of urd (e.g. ur_open) */
TRACE("ur_set_offline: BUSY\n");
rc = -EBUSY;
goto fail_urdev_put;
}
device_destroy(vmur_class, urd->char_device->dev);
cdev_del(urd->char_device);
urd->char_device = NULL;
rc = 0;
fail_urdev_put:
urdev_put(urd);
return rc; return rc;
} }
static int ur_set_offline(struct ccw_device *cdev) static int ur_set_offline(struct ccw_device *cdev)
{ {
struct urdev *urd; int rc;
TRACE("ur_set_offline: cdev=%p cdev->private=%p state=%d\n", mutex_lock(&vmur_mutex);
cdev, cdev->private, *(int *) cdev->private); rc = ur_set_offline_force(cdev, 0);
urd = (struct urdev *) cdev->dev.driver_data; mutex_unlock(&vmur_mutex);
device_destroy(vmur_class, urd->char_device->dev); return rc;
cdev_del(urd->char_device); }
module_put(ur_driver.owner);
return 0; static void ur_remove(struct ccw_device *cdev)
{
unsigned long flags;
TRACE("ur_remove\n");
mutex_lock(&vmur_mutex);
if (cdev->online)
ur_set_offline_force(cdev, 1);
ur_remove_attributes(&cdev->dev);
spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
urdev_put(cdev->dev.driver_data);
cdev->dev.driver_data = NULL;
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
mutex_unlock(&vmur_mutex);
} }
/* /*

View File

@ -70,6 +70,7 @@ struct urdev {
size_t reclen; /* Record length for *write* CCWs */ size_t reclen; /* Record length for *write* CCWs */
int class; /* VM device class */ int class; /* VM device class */
int io_request_rc; /* return code from I/O request */ int io_request_rc; /* return code from I/O request */
atomic_t ref_count; /* reference counter */
}; };
/* /*