Commit c44f2420 authored by Dmitry Torokhov's avatar Dmitry Torokhov
Browse files

Input: gameport - convert to use common workqueue instead of a thread



Instead of creating an exclusive thread to handle gameport events (which
happen rarely), let's switch to common workqueue. With the arrival
of concurrency-managed workqueue infrastructure we are not concerned
that our callers or callees also using workqueue (no deadlocks anymore)
and it should reduce total number of threads in the system.

Signed-off-by: default avatarDmitry Torokhov <dtor@mail.ru>
parent 8ee294cd
Loading
Loading
Loading
Loading
+68 −84
Original line number Diff line number Diff line
@@ -18,13 +18,11 @@
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/gameport.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/workqueue.h>
#include <linux/sched.h>	/* HZ */
#include <linux/mutex.h>
#include <linux/freezer.h>

/*#include <asm/io.h>*/

@@ -234,58 +232,22 @@ struct gameport_event {

static DEFINE_SPINLOCK(gameport_event_lock);	/* protects gameport_event_list */
static LIST_HEAD(gameport_event_list);
static DECLARE_WAIT_QUEUE_HEAD(gameport_wait);
static struct task_struct *gameport_task;

static int gameport_queue_event(void *object, struct module *owner,
				enum gameport_event_type event_type)
static struct gameport_event *gameport_get_event(void)
{
	struct gameport_event *event = NULL;
	unsigned long flags;
	struct gameport_event *event;
	int retval = 0;

	spin_lock_irqsave(&gameport_event_lock, flags);

	/*
	 * Scan event list for the other events for the same gameport port,
	 * starting with the most recent one. If event is the same we
	 * do not need add new one. If event is of different type we
	 * need to add this event and should not look further because
	 * we need to preseve sequence of distinct events.
	 */
	list_for_each_entry_reverse(event, &gameport_event_list, node) {
		if (event->object == object) {
			if (event->type == event_type)
				goto out;
			break;
		}
	}

	event = kmalloc(sizeof(struct gameport_event), GFP_ATOMIC);
	if (!event) {
		pr_err("Not enough memory to queue event %d\n", event_type);
		retval = -ENOMEM;
		goto out;
	}

	if (!try_module_get(owner)) {
		pr_warning("Can't get module reference, dropping event %d\n",
			   event_type);
		kfree(event);
		retval = -EINVAL;
		goto out;
	if (!list_empty(&gameport_event_list)) {
		event = list_first_entry(&gameport_event_list,
					 struct gameport_event, node);
		list_del_init(&event->node);
	}

	event->type = event_type;
	event->object = object;
	event->owner = owner;

	list_add_tail(&event->node, &gameport_event_list);
	wake_up(&gameport_wait);

out:
	spin_unlock_irqrestore(&gameport_event_lock, flags);
	return retval;
	return event;
}

static void gameport_free_event(struct gameport_event *event)
@@ -319,24 +281,8 @@ static void gameport_remove_duplicate_events(struct gameport_event *event)
	spin_unlock_irqrestore(&gameport_event_lock, flags);
}

static struct gameport_event *gameport_get_event(void)
{
	struct gameport_event *event = NULL;
	unsigned long flags;

	spin_lock_irqsave(&gameport_event_lock, flags);

	if (!list_empty(&gameport_event_list)) {
		event = list_first_entry(&gameport_event_list,
					 struct gameport_event, node);
		list_del_init(&event->node);
	}

	spin_unlock_irqrestore(&gameport_event_lock, flags);
	return event;
}

static void gameport_handle_event(void)
static void gameport_handle_events(struct work_struct *work)
{
	struct gameport_event *event;

@@ -368,6 +314,59 @@ static void gameport_handle_event(void)
	mutex_unlock(&gameport_mutex);
}

static DECLARE_WORK(gameport_event_work, gameport_handle_events);

static int gameport_queue_event(void *object, struct module *owner,
				enum gameport_event_type event_type)
{
	unsigned long flags;
	struct gameport_event *event;
	int retval = 0;

	spin_lock_irqsave(&gameport_event_lock, flags);

	/*
	 * Scan event list for the other events for the same gameport port,
	 * starting with the most recent one. If event is the same we
	 * do not need add new one. If event is of different type we
	 * need to add this event and should not look further because
	 * we need to preserve sequence of distinct events.
	 */
	list_for_each_entry_reverse(event, &gameport_event_list, node) {
		if (event->object == object) {
			if (event->type == event_type)
				goto out;
			break;
		}
	}

	event = kmalloc(sizeof(struct gameport_event), GFP_ATOMIC);
	if (!event) {
		pr_err("Not enough memory to queue event %d\n", event_type);
		retval = -ENOMEM;
		goto out;
	}

	if (!try_module_get(owner)) {
		pr_warning("Can't get module reference, dropping event %d\n",
			   event_type);
		kfree(event);
		retval = -EINVAL;
		goto out;
	}

	event->type = event_type;
	event->object = object;
	event->owner = owner;

	list_add_tail(&event->node, &gameport_event_list);
	schedule_work(&gameport_event_work);

out:
	spin_unlock_irqrestore(&gameport_event_lock, flags);
	return retval;
}

/*
 * Remove all events that have been submitted for a given object,
 * be it a gameport port or a driver.
@@ -419,19 +418,6 @@ static struct gameport *gameport_get_pending_child(struct gameport *parent)
	return child;
}

static int gameport_thread(void *nothing)
{
	set_freezable();
	do {
		gameport_handle_event();
		wait_event_freezable(gameport_wait,
			kthread_should_stop() || !list_empty(&gameport_event_list));
	} while (!kthread_should_stop());

	return 0;
}


/*
 * Gameport port operations
 */
@@ -814,13 +800,6 @@ static int __init gameport_init(void)
		return error;
	}

	gameport_task = kthread_run(gameport_thread, NULL, "kgameportd");
	if (IS_ERR(gameport_task)) {
		bus_unregister(&gameport_bus);
		error = PTR_ERR(gameport_task);
		pr_err("Failed to start kgameportd, error: %d\n", error);
		return error;
	}

	return 0;
}
@@ -828,7 +807,12 @@ static int __init gameport_init(void)
static void __exit gameport_exit(void)
{
	bus_unregister(&gameport_bus);
	kthread_stop(gameport_task);

	/*
	 * There should not be any outstanding events but work may
	 * still be scheduled so simply cancel it.
	 */
	cancel_work_sync(&gameport_event_work);
}

subsys_initcall(gameport_init);