/*
 *  net/dccp/ccid.c
 *
 *  An implementation of the DCCP protocol
 *  Arnaldo Carvalho de Melo <acme@conectiva.com.br>
 *
 *  CCID infrastructure
 *
 *	This program is free software; you can redistribute it and/or modify it
 *	under the terms of the GNU General Public License version 2 as
 *	published by the Free Software Foundation.
 */

#include "ccid.h"

static struct ccid *ccids[CCID_MAX];
#if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT)
static atomic_t ccids_lockct = ATOMIC_INIT(0);
static DEFINE_SPINLOCK(ccids_lock);

/*
 * The strategy is: modifications ccids vector are short, do not sleep and
 * veeery rare, but read access should be free of any exclusive locks.
 */
static void ccids_write_lock(void)
{
	spin_lock(&ccids_lock);
	while (atomic_read(&ccids_lockct) != 0) {
		spin_unlock(&ccids_lock);
		yield();
		spin_lock(&ccids_lock);
	}
}

static inline void ccids_write_unlock(void)
{
	spin_unlock(&ccids_lock);
}

static inline void ccids_read_lock(void)
{
	atomic_inc(&ccids_lockct);
	spin_unlock_wait(&ccids_lock);
}

static inline void ccids_read_unlock(void)
{
	atomic_dec(&ccids_lockct);
}

#else
#define ccids_write_lock() do { } while(0)
#define ccids_write_unlock() do { } while(0)
#define ccids_read_lock() do { } while(0)
#define ccids_read_unlock() do { } while(0)
#endif

int ccid_register(struct ccid *ccid)
{
	int err;

	if (ccid->ccid_init == NULL)
		return -1;

	ccids_write_lock();
	err = -EEXIST;
	if (ccids[ccid->ccid_id] == NULL) {
		ccids[ccid->ccid_id] = ccid;
		err = 0;
	}
	ccids_write_unlock();
	if (err == 0)
		pr_info("CCID: Registered CCID %d (%s)\n",
			ccid->ccid_id, ccid->ccid_name);
	return err;
}

EXPORT_SYMBOL_GPL(ccid_register);

int ccid_unregister(struct ccid *ccid)
{
	ccids_write_lock();
	ccids[ccid->ccid_id] = NULL;
	ccids_write_unlock();
	pr_info("CCID: Unregistered CCID %d (%s)\n",
		ccid->ccid_id, ccid->ccid_name);
	return 0;
}

EXPORT_SYMBOL_GPL(ccid_unregister);

struct ccid *ccid_init(unsigned char id, struct sock *sk)
{
	struct ccid *ccid;

#ifdef CONFIG_KMOD
	if (ccids[id] == NULL)
		request_module("net-dccp-ccid-%d", id);
#endif
	ccids_read_lock();

	ccid = ccids[id];
	if (ccid == NULL)
		goto out;

	if (!try_module_get(ccid->ccid_owner))
		goto out_err;

	if (ccid->ccid_init(sk) != 0)
		goto out_module_put;
out:
	ccids_read_unlock();
	return ccid;
out_module_put:
	module_put(ccid->ccid_owner);
out_err:
	ccid = NULL;
	goto out;
}

EXPORT_SYMBOL_GPL(ccid_init);

void ccid_exit(struct ccid *ccid, struct sock *sk)
{
	if (ccid == NULL)
		return;

	ccids_read_lock();

	if (ccids[ccid->ccid_id] != NULL) {
		if (ccid->ccid_exit != NULL)
			ccid->ccid_exit(sk);
		module_put(ccid->ccid_owner);
	}

	ccids_read_unlock();
}

EXPORT_SYMBOL_GPL(ccid_exit);