/*
 *  rw_semaphores generic spinlock version
 *  Copyright (C) 2001 Andrea Arcangeli <andrea@suse.de> SuSE
 */

#include <linux/sched.h>
#include <linux/module.h>
#include <asm/semaphore.h>

struct rwsem_wait_queue {
	unsigned long retire;
	struct task_struct * task;
	struct list_head task_list;
};

void rwsem_down_failed(struct rw_semaphore *sem, long retire)
{
	struct task_struct *tsk = current;
	struct rwsem_wait_queue wait;

	sem->count += retire;
	wait.retire = retire;
	wait.task = tsk;
	INIT_LIST_HEAD(&wait.task_list);
	list_add(&wait.task_list, &sem->wait);

	do {
		__set_task_state(tsk, TASK_UNINTERRUPTIBLE);
		spin_unlock(&sem->lock);
		schedule();
		spin_lock(&sem->lock);
	} while(wait.task_list.next);
}

void rwsem_wake(struct rw_semaphore *sem)
{
	struct list_head * entry, * head = &sem->wait;
	int last = 0;

	while ((entry = head->prev) != head) {
		struct rwsem_wait_queue * wait;

		wait = list_entry(entry, struct rwsem_wait_queue, task_list);

		if (wait->retire == RWSEM_WRITE_BLOCKING_BIAS) {
			if (sem->count & RWSEM_READ_MASK)
				break;
			last = 1;
		}

		/* convert write lock into read lock when read become active */
		sem->count -= wait->retire;
		list_del(entry);
		entry->next = NULL;
		wake_up_process(wait->task);
			
		if (last)
			break;
	}
}

EXPORT_SYMBOL(rwsem_down_failed);
EXPORT_SYMBOL(rwsem_wake);
