summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/base/core.c29
-rw-r--r--drivers/hid/hid-core.c20
-rw-r--r--drivers/s390/cio/ccwgroup.c18
-rw-r--r--drivers/scsi/scsi_sysfs.c14
4 files changed, 66 insertions, 15 deletions
diff --git a/drivers/base/core.c b/drivers/base/core.c
index f191afe62b4..ad0f4a2f25c 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -407,6 +407,35 @@ void device_remove_bin_file(struct device *dev, struct bin_attribute *attr)
}
EXPORT_SYMBOL_GPL(device_remove_bin_file);
+/**
+ * device_schedule_callback - helper to schedule a callback for a device
+ * @dev: device.
+ * @func: callback function to invoke later.
+ *
+ * Attribute methods must not unregister themselves or their parent device
+ * (which would amount to the same thing). Attempts to do so will deadlock,
+ * since unregistration is mutually exclusive with driver callbacks.
+ *
+ * Instead methods can call this routine, which will attempt to allocate
+ * and schedule a workqueue request to call back @func with @dev as its
+ * argument in the workqueue's process context. @dev will be pinned until
+ * @func returns.
+ *
+ * Returns 0 if the request was submitted, -ENOMEM if storage could not
+ * be allocated.
+ *
+ * NOTE: This routine won't work if CONFIG_SYSFS isn't set! It uses an
+ * underlying sysfs routine (since it is intended for use by attribute
+ * methods), and if sysfs isn't available you'll get nothing but -ENOSYS.
+ */
+int device_schedule_callback(struct device *dev,
+ void (*func)(struct device *))
+{
+ return sysfs_schedule_callback(&dev->kobj,
+ (void (*)(void *)) func, dev);
+}
+EXPORT_SYMBOL_GPL(device_schedule_callback);
+
static void klist_children_get(struct klist_node *n)
{
struct device *dev = container_of(n, struct device, knode_parent);
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 9c8157fb6d7..67f3347afcf 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -26,6 +26,7 @@
#include <asm/byteorder.h>
#include <linux/input.h>
#include <linux/wait.h>
+#include <linux/vmalloc.h>
#include <linux/hid.h>
#include <linux/hiddev.h>
@@ -654,12 +655,13 @@ struct hid_device *hid_parse_report(__u8 *start, unsigned size)
memcpy(device->rdesc, start, size);
device->rsize = size;
- if (!(parser = kzalloc(sizeof(struct hid_parser), GFP_KERNEL))) {
+ if (!(parser = vmalloc(sizeof(struct hid_parser)))) {
kfree(device->rdesc);
kfree(device->collection);
kfree(device);
return NULL;
}
+ memset(parser, 0, sizeof(struct hid_parser));
parser->device = device;
end = start + size;
@@ -668,7 +670,7 @@ struct hid_device *hid_parse_report(__u8 *start, unsigned size)
if (item.format != HID_ITEM_FORMAT_SHORT) {
dbg("unexpected long global item");
hid_free_device(device);
- kfree(parser);
+ vfree(parser);
return NULL;
}
@@ -676,7 +678,7 @@ struct hid_device *hid_parse_report(__u8 *start, unsigned size)
dbg("item %u %u %u %u parsing failed\n",
item.format, (unsigned)item.size, (unsigned)item.type, (unsigned)item.tag);
hid_free_device(device);
- kfree(parser);
+ vfree(parser);
return NULL;
}
@@ -684,23 +686,23 @@ struct hid_device *hid_parse_report(__u8 *start, unsigned size)
if (parser->collection_stack_ptr) {
dbg("unbalanced collection at end of report description");
hid_free_device(device);
- kfree(parser);
+ vfree(parser);
return NULL;
}
if (parser->local.delimiter_depth) {
dbg("unbalanced delimiter at end of report description");
hid_free_device(device);
- kfree(parser);
+ vfree(parser);
return NULL;
}
- kfree(parser);
+ vfree(parser);
return device;
}
}
dbg("item fetching failed at offset %d\n", (int)(end - start));
hid_free_device(device);
- kfree(parser);
+ vfree(parser);
return NULL;
}
EXPORT_SYMBOL_GPL(hid_parse_report);
@@ -872,10 +874,6 @@ static void hid_output_field(struct hid_field *field, __u8 *data)
unsigned size = field->report_size;
unsigned n;
- /* make sure the unused bits in the last byte are zeros */
- if (count > 0 && size > 0)
- data[(offset+count*size-1)/8] = 0;
-
for (n = 0; n < count; n++) {
if (field->logical_minimum < 0) /* signed values */
implement(data, offset + n * size, size, s32ton(field->value[n], size));
diff --git a/drivers/s390/cio/ccwgroup.c b/drivers/s390/cio/ccwgroup.c
index d48e3ca4752..5aeb68e732b 100644
--- a/drivers/s390/cio/ccwgroup.c
+++ b/drivers/s390/cio/ccwgroup.c
@@ -71,19 +71,31 @@ __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
* Provide an 'ungroup' attribute so the user can remove group devices no
* longer needed or accidentially created. Saves memory :)
*/
+static void ccwgroup_ungroup_callback(struct device *dev)
+{
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+
+ __ccwgroup_remove_symlinks(gdev);
+ device_unregister(dev);
+}
+
static ssize_t
ccwgroup_ungroup_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
struct ccwgroup_device *gdev;
+ int rc;
gdev = to_ccwgroupdev(dev);
if (gdev->state != CCWGROUP_OFFLINE)
return -EINVAL;
- __ccwgroup_remove_symlinks(gdev);
- device_unregister(dev);
-
+ /* Note that we cannot unregister the device from one of its
+ * attribute methods, so we have to use this roundabout approach.
+ */
+ rc = device_schedule_callback(dev, ccwgroup_ungroup_callback);
+ if (rc)
+ count = rc;
return count;
}
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index c275dcac3f1..939de0de18b 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -452,10 +452,22 @@ store_rescan_field (struct device *dev, struct device_attribute *attr, const cha
}
static DEVICE_ATTR(rescan, S_IWUSR, NULL, store_rescan_field);
+static void sdev_store_delete_callback(struct device *dev)
+{
+ scsi_remove_device(to_scsi_device(dev));
+}
+
static ssize_t sdev_store_delete(struct device *dev, struct device_attribute *attr, const char *buf,
size_t count)
{
- scsi_remove_device(to_scsi_device(dev));
+ int rc;
+
+ /* An attribute cannot be unregistered by one of its own methods,
+ * so we have to use this roundabout approach.
+ */
+ rc = device_schedule_callback(dev, sdev_store_delete_callback);
+ if (rc)
+ count = rc;
return count;
};
static DEVICE_ATTR(delete, S_IWUSR, NULL, sdev_store_delete);