Commit b07375b1 authored by Stefan Richter's avatar Stefan Richter

ieee1394: nodemgr: revise semaphore protection of driver core data

 - The list "struct class.children" is supposed to be protected by
   class.sem, not by class.subsys.rwsem.

 - nodemgr_remove_uds() iterated over nodemgr_ud_class.children without
   proper protection.  This was never observed as a bug since the code
   is usually only accessed by knodemgrd.  All knodemgrds are currently
   globally serialized.  But userspace can trigger this code too by
   writing to /sys/bus/ieee1394/destroy_node.

 - Clean up access to the FireWire bus type's subsys.rwsem:  Access it
   uniformly via ieee1394_bus_type.  Shrink rwsem protected regions
   where possible.  Expand them where necessary.  The latter wasn't a
   problem so far because knodemgr is globally serialized.

This should harden the interaction of ieee1394 with sysfs and lay ground
for deserialized operation of multiple knodemgrds and for implementation
of subthreads for parallelized scanning and probing.
Signed-off-by: default avatarStefan Richter <stefanr@s5r6.in-berlin.de>
parent 7fdfc909
...@@ -374,11 +374,11 @@ static ssize_t fw_set_ignore_driver(struct device *dev, struct device_attribute ...@@ -374,11 +374,11 @@ static ssize_t fw_set_ignore_driver(struct device *dev, struct device_attribute
int state = simple_strtoul(buf, NULL, 10); int state = simple_strtoul(buf, NULL, 10);
if (state == 1) { if (state == 1) {
down_write(&dev->bus->subsys.rwsem);
device_release_driver(dev);
ud->ignore_driver = 1; ud->ignore_driver = 1;
up_write(&dev->bus->subsys.rwsem); down_write(&ieee1394_bus_type.subsys.rwsem);
} else if (!state) device_release_driver(dev);
up_write(&ieee1394_bus_type.subsys.rwsem);
} else if (state == 0)
ud->ignore_driver = 0; ud->ignore_driver = 0;
return count; return count;
...@@ -436,7 +436,7 @@ static ssize_t fw_set_ignore_drivers(struct bus_type *bus, const char *buf, size ...@@ -436,7 +436,7 @@ static ssize_t fw_set_ignore_drivers(struct bus_type *bus, const char *buf, size
if (state == 1) if (state == 1)
ignore_drivers = 1; ignore_drivers = 1;
else if (!state) else if (state == 0)
ignore_drivers = 0; ignore_drivers = 0;
return count; return count;
...@@ -734,20 +734,65 @@ static int nodemgr_bus_match(struct device * dev, struct device_driver * drv) ...@@ -734,20 +734,65 @@ static int nodemgr_bus_match(struct device * dev, struct device_driver * drv)
} }
static DEFINE_MUTEX(nodemgr_serialize_remove_uds);
static void nodemgr_remove_uds(struct node_entry *ne) static void nodemgr_remove_uds(struct node_entry *ne)
{ {
struct class_device *cdev, *next; struct class_device *cdev;
struct unit_directory *ud; struct unit_directory *ud, **unreg;
size_t i, count;
/*
* This is awkward:
* Iteration over nodemgr_ud_class.children has to be protected by
* nodemgr_ud_class.sem, but class_device_unregister() will eventually
* take nodemgr_ud_class.sem too. Therefore store all uds to be
* unregistered in a temporary array, release the semaphore, and then
* unregister the uds.
*
* Since nodemgr_remove_uds can also run in other contexts than the
* knodemgrds (which are currently globally serialized), protect the
* gap after release of the semaphore by nodemgr_serialize_remove_uds.
*/
list_for_each_entry_safe(cdev, next, &nodemgr_ud_class.children, node) { mutex_lock(&nodemgr_serialize_remove_uds);
ud = container_of(cdev, struct unit_directory, class_dev);
if (ud->ne != ne) down(&nodemgr_ud_class.sem);
continue; count = 0;
list_for_each_entry(cdev, &nodemgr_ud_class.children, node) {
ud = container_of(cdev, struct unit_directory, class_dev);
if (ud->ne == ne)
count++;
}
if (!count) {
up(&nodemgr_ud_class.sem);
mutex_unlock(&nodemgr_serialize_remove_uds);
return;
}
unreg = kcalloc(count, sizeof(*unreg), GFP_KERNEL);
if (!unreg) {
HPSB_ERR("NodeMgr: out of memory in nodemgr_remove_uds");
up(&nodemgr_ud_class.sem);
mutex_unlock(&nodemgr_serialize_remove_uds);
return;
}
i = 0;
list_for_each_entry(cdev, &nodemgr_ud_class.children, node) {
ud = container_of(cdev, struct unit_directory, class_dev);
if (ud->ne == ne) {
BUG_ON(i >= count);
unreg[i++] = ud;
}
}
up(&nodemgr_ud_class.sem);
class_device_unregister(&ud->class_dev); for (i = 0; i < count; i++) {
device_unregister(&ud->device); class_device_unregister(&unreg[i]->class_dev);
device_unregister(&unreg[i]->device);
} }
kfree(unreg);
mutex_unlock(&nodemgr_serialize_remove_uds);
} }
...@@ -880,12 +925,11 @@ static struct node_entry *nodemgr_create_node(octlet_t guid, struct csr1212_csr ...@@ -880,12 +925,11 @@ static struct node_entry *nodemgr_create_node(octlet_t guid, struct csr1212_csr
static struct node_entry *find_entry_by_guid(u64 guid) static struct node_entry *find_entry_by_guid(u64 guid)
{ {
struct class *class = &nodemgr_ne_class;
struct class_device *cdev; struct class_device *cdev;
struct node_entry *ne, *ret_ne = NULL; struct node_entry *ne, *ret_ne = NULL;
down_read(&class->subsys.rwsem); down(&nodemgr_ne_class.sem);
list_for_each_entry(cdev, &class->children, node) { list_for_each_entry(cdev, &nodemgr_ne_class.children, node) {
ne = container_of(cdev, struct node_entry, class_dev); ne = container_of(cdev, struct node_entry, class_dev);
if (ne->guid == guid) { if (ne->guid == guid) {
...@@ -893,20 +937,20 @@ static struct node_entry *find_entry_by_guid(u64 guid) ...@@ -893,20 +937,20 @@ static struct node_entry *find_entry_by_guid(u64 guid)
break; break;
} }
} }
up_read(&class->subsys.rwsem); up(&nodemgr_ne_class.sem);
return ret_ne; return ret_ne;
} }
static struct node_entry *find_entry_by_nodeid(struct hpsb_host *host, nodeid_t nodeid) static struct node_entry *find_entry_by_nodeid(struct hpsb_host *host,
nodeid_t nodeid)
{ {
struct class *class = &nodemgr_ne_class;
struct class_device *cdev; struct class_device *cdev;
struct node_entry *ne, *ret_ne = NULL; struct node_entry *ne, *ret_ne = NULL;
down_read(&class->subsys.rwsem); down(&nodemgr_ne_class.sem);
list_for_each_entry(cdev, &class->children, node) { list_for_each_entry(cdev, &nodemgr_ne_class.children, node) {
ne = container_of(cdev, struct node_entry, class_dev); ne = container_of(cdev, struct node_entry, class_dev);
if (ne->host == host && ne->nodeid == nodeid) { if (ne->host == host && ne->nodeid == nodeid) {
...@@ -914,7 +958,7 @@ static struct node_entry *find_entry_by_nodeid(struct hpsb_host *host, nodeid_t ...@@ -914,7 +958,7 @@ static struct node_entry *find_entry_by_nodeid(struct hpsb_host *host, nodeid_t
break; break;
} }
} }
up_read(&class->subsys.rwsem); up(&nodemgr_ne_class.sem);
return ret_ne; return ret_ne;
} }
...@@ -1377,7 +1421,6 @@ static void nodemgr_node_scan(struct host_info *hi, int generation) ...@@ -1377,7 +1421,6 @@ static void nodemgr_node_scan(struct host_info *hi, int generation)
} }
/* Caller needs to hold nodemgr_ud_class.subsys.rwsem as reader. */
static void nodemgr_suspend_ne(struct node_entry *ne) static void nodemgr_suspend_ne(struct node_entry *ne)
{ {
struct class_device *cdev; struct class_device *cdev;
...@@ -1389,19 +1432,20 @@ static void nodemgr_suspend_ne(struct node_entry *ne) ...@@ -1389,19 +1432,20 @@ static void nodemgr_suspend_ne(struct node_entry *ne)
ne->in_limbo = 1; ne->in_limbo = 1;
WARN_ON(device_create_file(&ne->device, &dev_attr_ne_in_limbo)); WARN_ON(device_create_file(&ne->device, &dev_attr_ne_in_limbo));
down_write(&ne->device.bus->subsys.rwsem); down(&nodemgr_ud_class.sem);
list_for_each_entry(cdev, &nodemgr_ud_class.children, node) { list_for_each_entry(cdev, &nodemgr_ud_class.children, node) {
ud = container_of(cdev, struct unit_directory, class_dev); ud = container_of(cdev, struct unit_directory, class_dev);
if (ud->ne != ne) if (ud->ne != ne)
continue; continue;
down_write(&ieee1394_bus_type.subsys.rwsem);
if (ud->device.driver && if (ud->device.driver &&
(!ud->device.driver->suspend || (!ud->device.driver->suspend ||
ud->device.driver->suspend(&ud->device, PMSG_SUSPEND))) ud->device.driver->suspend(&ud->device, PMSG_SUSPEND)))
device_release_driver(&ud->device); device_release_driver(&ud->device);
up_write(&ieee1394_bus_type.subsys.rwsem);
} }
up_write(&ne->device.bus->subsys.rwsem); up(&nodemgr_ud_class.sem);
} }
...@@ -1413,45 +1457,47 @@ static void nodemgr_resume_ne(struct node_entry *ne) ...@@ -1413,45 +1457,47 @@ static void nodemgr_resume_ne(struct node_entry *ne)
ne->in_limbo = 0; ne->in_limbo = 0;
device_remove_file(&ne->device, &dev_attr_ne_in_limbo); device_remove_file(&ne->device, &dev_attr_ne_in_limbo);
down_read(&nodemgr_ud_class.subsys.rwsem); down(&nodemgr_ud_class.sem);
down_read(&ne->device.bus->subsys.rwsem);
list_for_each_entry(cdev, &nodemgr_ud_class.children, node) { list_for_each_entry(cdev, &nodemgr_ud_class.children, node) {
ud = container_of(cdev, struct unit_directory, class_dev); ud = container_of(cdev, struct unit_directory, class_dev);
if (ud->ne != ne) if (ud->ne != ne)
continue; continue;
down_read(&ieee1394_bus_type.subsys.rwsem);
if (ud->device.driver && ud->device.driver->resume) if (ud->device.driver && ud->device.driver->resume)
ud->device.driver->resume(&ud->device); ud->device.driver->resume(&ud->device);
up_read(&ieee1394_bus_type.subsys.rwsem);
} }
up_read(&ne->device.bus->subsys.rwsem); up(&nodemgr_ud_class.sem);
up_read(&nodemgr_ud_class.subsys.rwsem);
HPSB_DEBUG("Node resumed: ID:BUS[" NODE_BUS_FMT "] GUID[%016Lx]", HPSB_DEBUG("Node resumed: ID:BUS[" NODE_BUS_FMT "] GUID[%016Lx]",
NODE_BUS_ARGS(ne->host, ne->nodeid), (unsigned long long)ne->guid); NODE_BUS_ARGS(ne->host, ne->nodeid), (unsigned long long)ne->guid);
} }
/* Caller needs to hold nodemgr_ud_class.subsys.rwsem as reader. */
static void nodemgr_update_pdrv(struct node_entry *ne) static void nodemgr_update_pdrv(struct node_entry *ne)
{ {
struct unit_directory *ud; struct unit_directory *ud;
struct hpsb_protocol_driver *pdrv; struct hpsb_protocol_driver *pdrv;
struct class_device *cdev; struct class_device *cdev;
down(&nodemgr_ud_class.sem);
list_for_each_entry(cdev, &nodemgr_ud_class.children, node) { list_for_each_entry(cdev, &nodemgr_ud_class.children, node) {
ud = container_of(cdev, struct unit_directory, class_dev); ud = container_of(cdev, struct unit_directory, class_dev);
if (ud->ne != ne || !ud->device.driver) if (ud->ne != ne)
continue; continue;
pdrv = container_of(ud->device.driver, struct hpsb_protocol_driver, driver); down_write(&ieee1394_bus_type.subsys.rwsem);
if (ud->device.driver) {
if (pdrv->update && pdrv->update(ud)) { pdrv = container_of(ud->device.driver,
down_write(&ud->device.bus->subsys.rwsem); struct hpsb_protocol_driver,
device_release_driver(&ud->device); driver);
up_write(&ud->device.bus->subsys.rwsem); if (pdrv->update && pdrv->update(ud))
device_release_driver(&ud->device);
} }
up_write(&ieee1394_bus_type.subsys.rwsem);
} }
up(&nodemgr_ud_class.sem);
} }
...@@ -1482,8 +1528,6 @@ static void nodemgr_irm_write_bc(struct node_entry *ne, int generation) ...@@ -1482,8 +1528,6 @@ static void nodemgr_irm_write_bc(struct node_entry *ne, int generation)
} }
/* Caller needs to hold nodemgr_ud_class.subsys.rwsem as reader because the
* calls to nodemgr_update_pdrv() and nodemgr_suspend_ne() here require it. */
static void nodemgr_probe_ne(struct host_info *hi, struct node_entry *ne, int generation) static void nodemgr_probe_ne(struct host_info *hi, struct node_entry *ne, int generation)
{ {
struct device *dev; struct device *dev;
...@@ -1516,7 +1560,6 @@ static void nodemgr_probe_ne(struct host_info *hi, struct node_entry *ne, int ge ...@@ -1516,7 +1560,6 @@ static void nodemgr_probe_ne(struct host_info *hi, struct node_entry *ne, int ge
static void nodemgr_node_probe(struct host_info *hi, int generation) static void nodemgr_node_probe(struct host_info *hi, int generation)
{ {
struct hpsb_host *host = hi->host; struct hpsb_host *host = hi->host;
struct class *class = &nodemgr_ne_class;
struct class_device *cdev; struct class_device *cdev;
struct node_entry *ne; struct node_entry *ne;
...@@ -1529,18 +1572,18 @@ static void nodemgr_node_probe(struct host_info *hi, int generation) ...@@ -1529,18 +1572,18 @@ static void nodemgr_node_probe(struct host_info *hi, int generation)
* while probes are time-consuming. (Well, those probes need some * while probes are time-consuming. (Well, those probes need some
* improvement...) */ * improvement...) */
down_read(&class->subsys.rwsem); down(&nodemgr_ne_class.sem);
list_for_each_entry(cdev, &class->children, node) { list_for_each_entry(cdev, &nodemgr_ne_class.children, node) {
ne = container_of(cdev, struct node_entry, class_dev); ne = container_of(cdev, struct node_entry, class_dev);
if (!ne->needs_probe) if (!ne->needs_probe)
nodemgr_probe_ne(hi, ne, generation); nodemgr_probe_ne(hi, ne, generation);
} }
list_for_each_entry(cdev, &class->children, node) { list_for_each_entry(cdev, &nodemgr_ne_class.children, node) {
ne = container_of(cdev, struct node_entry, class_dev); ne = container_of(cdev, struct node_entry, class_dev);
if (ne->needs_probe) if (ne->needs_probe)
nodemgr_probe_ne(hi, ne, generation); nodemgr_probe_ne(hi, ne, generation);
} }
up_read(&class->subsys.rwsem); up(&nodemgr_ne_class.sem);
/* If we had a bus reset while we were scanning the bus, it is /* If we had a bus reset while we were scanning the bus, it is
...@@ -1752,19 +1795,18 @@ static int nodemgr_host_thread(void *__hi) ...@@ -1752,19 +1795,18 @@ static int nodemgr_host_thread(void *__hi)
int nodemgr_for_each_host(void *__data, int (*cb)(struct hpsb_host *, void *)) int nodemgr_for_each_host(void *__data, int (*cb)(struct hpsb_host *, void *))
{ {
struct class *class = &hpsb_host_class;
struct class_device *cdev; struct class_device *cdev;
struct hpsb_host *host; struct hpsb_host *host;
int error = 0; int error = 0;
down_read(&class->subsys.rwsem); down(&hpsb_host_class.sem);
list_for_each_entry(cdev, &class->children, node) { list_for_each_entry(cdev, &hpsb_host_class.children, node) {
host = container_of(cdev, struct hpsb_host, class_dev); host = container_of(cdev, struct hpsb_host, class_dev);
if ((error = cb(host, __data))) if ((error = cb(host, __data)))
break; break;
} }
up_read(&class->subsys.rwsem); up(&hpsb_host_class.sem);
return error; return error;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment