[PATCH] driver core: multithreaded probing - more parallelism control

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

This is a new version of multithreaded probing patch, with more
parallelism control added.

There are more control over which devices and drivers will be probed
parallelized or serially. For example, in IEEE1394 subsystem, the
different "units" in one "node" can be probed serially while the
different "nodes" can be probed parallelized.

The number of threads can be controlled through a kernel command line
parameters.

The patch is against 2.6.22-rc5. The "wait_for_probes" function in the
patch comes from the original multithreaded probing patch. If I need do
anything because of it, please let me know.

Any comment is welcome.

Best Regards,
Huang Ying

---

This patch add multithreaded probing with more parallelism control.

The device/driver probing is done on a probing queue, which is a
customized version of work queue, where the thread of probing queue will
come and go on demand. There is a queue No. for each probing queue. The
queue No. ranges from 0 to (unsigned short) ~0U, any queue No. can be
used by probing independent of the underlying thread number.

The device/driver probing is submitted to a probing queue though
"schedule_probe" function with probing queue No, a probing function and
corresponding parameter specified. The probing with same probing queue
No. will be done serially, while the probing with different probing
queue No. may be done parallelized.

"schedule_probe" is a general interface of multithreaded probing. While
convenient interfaces are as follow.

A field named "probe_queue_no" is added to "struct device", which
indicates probing queue No. on which the probing of the device will be
done. The subsystem can control the parallelism through this field. For
example, in IEEE1394 subsystems, the same probe_queue_no can be assigned
for different "units" in same "node" while different probe_queue_no can
be assigned to different "nodes".

Fields named "has_probe_queue" and "probe_queue_no" are added to "struct
device_driver". This let the probing queue can be controlled through
driver side too. If "has_probe_queue" is set, the "probe_queue_no" of
"struct device_driver" is used, otherwise that of "struct device" is
used.

There are other rules to control the parallelism.

1. The child device will not start probing until the probing of its
parent has been done.
2. The different drivers of same device will be probed serially.

If it is intended to separate the probing process of the device/driver
into synchronous part and asynchronous part, the ".probe" of "struct
device_driver" is used as the synchronous interface of device probing,
while the asynchronous part of probing can be submitted through
"schedule_probe" in ".probe" function. All the synchronous part will be
submitted with same probing queue number.

More strict serialization can be gotten through this mechanism if
needed. For example, if two USB driver is inserted into kernel
simultaneously, in original driver probing code, two USB device may be
probed simultaneously, while they will be probed serially if two device
has same probing queue No. in probing queue based probing code.

There is a problem of this multithreaded probing mechanism. There are
some code in kernel doing hardware access but not in the driver ".probe"
function, which may conflict with the multithreaded probiMore strict
serialization can be gotten through this mechanism if
needed. For example, if two USB driver is inserted into kernel
simultaneously, in original driver probing code, two USB device may be
probed simultaneously, while they will be probed serially if two device
has same probing queue No. in probing queue based probing code.

There is a problem of this multithreaded probing mechanism. There are
some code in kernel doing hardware access but not in the driver ".probe"
function, which may conflict with the multithreaded probing driver code.
A possible mechanism is executing those hardware access code in a
probing queue through "schedule_probe".ng driver code. A possible
mechanism is executing those hardware access code in a probing queue
through "schedule_probe".

Signed-off-by: Huang Ying <[email protected]>

---

 drivers/base/dd.c      |  182 ++++++++++++++++++++++++++++++++++++++++++++++---
 include/linux/device.h |   27 ++++++-
 2 files changed, 199 insertions(+), 10 deletions(-)
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index b0088b0..a5cd629 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -23,6 +23,162 @@
 #include "base.h"
 #include "power/power.h"
 
+struct probe_queue {
+	spinlock_t lock;
+	struct list_head probe_list;
+	unsigned has_thread : 1;
+};
+
+struct simple_probe {
+	struct probe probe;
+	struct device *dev;
+	struct device_driver *drv;
+};
+
+static struct probe_queue *probe_queues;
+static atomic_t probe_count = ATOMIC_INIT(0);
+static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);
+
+static int probe_thread_num = 1;
+
+static int __init set_probe_thread_num(char *str)
+{
+	get_option(&str, &probe_thread_num);
+	if (probe_thread_num <= 0 || probe_thread_num > (unsigned short) ~0U)
+		probe_thread_num = 1;
+	return 1;
+}
+
+__setup("probe_thread_num=", set_probe_thread_num);
+
+static int really_probe(struct probe *probe);
+
+static void init_probe_queue(struct probe_queue *probe_queue)
+{
+	spin_lock_init(&probe_queue->lock);
+	INIT_LIST_HEAD(&probe_queue->probe_list);
+	probe_queue->has_thread = 0;
+}
+
+static int probe_thread(void *data)
+{
+	struct probe_queue *probe_queue = data;
+	struct probe *probe;
+	int ret = 0;
+
+	while (1) {
+		spin_lock(&probe_queue->lock);
+		if (list_empty(&probe_queue->probe_list)) {
+			probe_queue->has_thread = 0;
+			spin_unlock(&probe_queue->lock);
+			break;
+		}
+		probe = list_entry(probe_queue->probe_list.next,
+				   struct probe, entry);
+		list_del_init(probe_queue->probe_list.next);
+		spin_unlock(&probe_queue->lock);
+		ret = probe->probe_func(probe);
+		atomic_dec(&probe_count);
+		wake_up(&probe_waitqueue);
+	}
+	return ret;
+}
+
+/**
+ * schedule_probe - schedule a probing to be done later
+ * @probe_queue_no: probing queue No. on which the probing will be done
+ * @probe: probing information include probing function and parameter
+ *
+ * Schedule a probing to be done later on specified probing queue. The
+ * probing with same probing queue No. will be probed serially while
+ * the probing with different probing queue No. may be probed
+ * simultaneously.
+ */
+
+int schedule_probe(unsigned short probe_queue_no, struct probe *probe)
+{
+	int ret = 0;
+	struct task_struct *thread;
+	struct probe_queue *probe_queue =
+		&probe_queues[probe_queue_no%probe_thread_num];
+
+	atomic_inc(&probe_count);
+	probe->probe_queue_no = probe_queue_no;
+	spin_lock(&probe_queue->lock);
+	list_add_tail(&probe->entry, &probe_queue->probe_list);
+	if (probe_queue->has_thread) {
+		spin_unlock(&probe_queue->lock);
+		return ret;
+	}
+	probe_queue->has_thread = 1;
+	spin_unlock(&probe_queue->lock);
+	thread = kthread_run(probe_thread, probe_queue,
+			     "probe-%u", probe_queue_no);
+	if (!thread)
+		ret = probe_thread(probe_queue);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(schedule_probe);
+
+static int simple_schedule_probe(struct device *dev,
+				 struct device_driver *drv)
+{
+	struct simple_probe *simple_probe;
+	unsigned short probe_queue_no;
+
+	pr_debug("simple_schedule:%s,%s\n", dev->bus_id, drv->name);
+	simple_probe = kmalloc(sizeof(struct simple_probe), GFP_KERNEL);
+	if (!simple_probe)
+		return -ENOMEM;
+	INIT_PROBE(&simple_probe->probe, really_probe);
+	simple_probe->dev = get_device(dev);
+	simple_probe->drv = get_driver(drv);
+	dev->is_probing = 1;
+	if (drv->has_probe_queue)
+		probe_queue_no = drv->probe_queue_no;
+	else
+		probe_queue_no = dev->probe_queue_no;
+	return schedule_probe(probe_queue_no, &simple_probe->probe);
+}
+
+static int __init init_probe_queues(void)
+{
+	int i;
+
+	probe_queues = kmalloc(probe_thread_num * sizeof(struct probe_queue),
+			       GFP_KERNEL);
+	if (!probe_queues)
+		return -ENOMEM;
+	for (i = 0; i < probe_thread_num; i++)
+		init_probe_queue(&probe_queues[i]);
+	return 0;
+}
+
+core_initcall_sync(init_probe_queues);
+
+static int __init wait_for_probes(void)
+{
+	DEFINE_WAIT(wait);
+
+	if (!atomic_read(&probe_count))
+		return 0;
+	while (atomic_read(&probe_count)) {
+		prepare_to_wait(&probe_waitqueue, &wait, TASK_UNINTERRUPTIBLE);
+		if (atomic_read(&probe_count))
+			schedule();
+	}
+	finish_wait(&probe_waitqueue, &wait);
+	return 0;
+}
+
+core_initcall_sync(wait_for_probes);
+postcore_initcall_sync(wait_for_probes);
+arch_initcall_sync(wait_for_probes);
+subsys_initcall_sync(wait_for_probes);
+fs_initcall_sync(wait_for_probes);
+device_initcall_sync(wait_for_probes);
+late_initcall_sync(wait_for_probes);
+
 #define to_drv(node) container_of(node, struct device_driver, kobj.entry)
 
 
@@ -94,18 +250,25 @@ int device_bind_driver(struct device *dev)
 	return ret;
 }
 
-static atomic_t probe_count = ATOMIC_INIT(0);
-static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);
-
-static int really_probe(struct device *dev, struct device_driver *drv)
+static int really_probe(struct probe *probe)
 {
+	struct device *dev, *parent;
+	struct device_driver *drv;
+	struct simple_probe *simple_probe;
 	int ret = 0;
 
-	atomic_inc(&probe_count);
+	simple_probe = container_of(probe, struct simple_probe, probe);
+	dev = simple_probe->dev;
+	drv = simple_probe->drv;
+	parent = get_device(dev->parent);
+	while (parent && parent->is_probing)
+		yield();
+	put_device(parent);
 	pr_debug("%s: Probing driver %s with device %s\n",
 		 drv->bus->name, drv->name, dev->bus_id);
 	WARN_ON(!list_empty(&dev->devres_head));
 
+	down(&dev->sem);
 	dev->driver = drv;
 	if (driver_sysfs_add(dev)) {
 		printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
@@ -146,8 +309,11 @@ probe_failed:
 	 */
 	ret = 0;
 done:
-	atomic_dec(&probe_count);
-	wake_up(&probe_waitqueue);
+	dev->is_probing = 0;
+	up(&dev->sem);
+	put_device(dev);
+	put_driver(drv);
+	kfree(simple_probe);
 	return ret;
 }
 
@@ -195,7 +361,7 @@ int driver_probe_device(struct device_driver * drv, struct device * dev)
 	pr_debug("%s: Matched Device %s with Driver %s\n",
 		 drv->bus->name, dev->bus_id, drv->name);
 
-	ret = really_probe(dev, drv);
+	ret = simple_schedule_probe(dev, drv);
 
 done:
 	return ret;
diff --git a/include/linux/device.h b/include/linux/device.h
index 2e1a298..9e63602 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -133,6 +133,9 @@ struct device_driver {
 	const char 		* mod_name;	/* used for built-in modules */
 	struct module_kobject	* mkobj;
 
+	unsigned short		has_probe_queue:1;
+	unsigned short		probe_queue_no;
+
 	int	(*probe)	(struct device * dev);
 	int	(*remove)	(struct device * dev);
 	void	(*shutdown)	(struct device * dev);
@@ -417,8 +420,10 @@ struct device {
 	struct kobject kobj;
 	char	bus_id[BUS_ID_SIZE];	/* position on parent bus */
 	struct device_type	*type;
-	unsigned		is_registered:1;
-	unsigned		uevent_suppress:1;
+	unsigned short		is_registered:1;
+	unsigned short		uevent_suppress:1;
+	unsigned short		is_probing:1;
+	unsigned short		probe_queue_no;
 	struct device_attribute uevent_attr;
 	struct device_attribute *devt_attr;
 
@@ -526,6 +531,24 @@ extern int  __must_check device_attach(struct device * dev);
 extern int __must_check driver_attach(struct device_driver *drv);
 extern int __must_check device_reprobe(struct device *dev);
 
+struct probe {
+	struct list_head entry;
+	int ( * probe_func)(struct probe *probe);
+	unsigned short probe_queue_no;
+};
+
+/**
+ * initialize the probing item
+ */
+#define INIT_PROBE(_probe, _probe_func)				\
+	do {							\
+		INIT_LIST_HEAD(&(_probe)->entry);		\
+		(_probe)->probe_func = (_probe_func);		\
+		(_probe)->probe_queue_no = 0;			\
+	 } while (0);
+
+int schedule_probe(unsigned short probe_queue_no, struct probe *probe);
+
 /*
  * Easy functions for dynamically creating devices on the fly
  */
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux