ccid.c 7.21 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 *  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"

Gerrit Renker's avatar
Gerrit Renker committed
16 17 18 19 20 21 22
static u8 builtin_ccids[] = {
	DCCPC_CCID2,		/* CCID2 is supported by default */
#if defined(CONFIG_IP_DCCP_CCID3) || defined(CONFIG_IP_DCCP_CCID3_MODULE)
	DCCPC_CCID3,
#endif
};

23
static struct ccid_operations *ccids[CCID_MAX];
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
#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);
50
	smp_mb__after_atomic_inc();
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
	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

66
static struct kmem_cache *ccid_kmem_cache_create(int obj_size, const char *fmt,...)
67
{
68
	struct kmem_cache *slab;
69 70 71 72 73 74 75 76 77 78 79
	char slab_name_fmt[32], *slab_name;
	va_list args;

	va_start(args, fmt);
	vsnprintf(slab_name_fmt, sizeof(slab_name_fmt), fmt, args);
	va_end(args);

	slab_name = kstrdup(slab_name_fmt, GFP_KERNEL);
	if (slab_name == NULL)
		return NULL;
	slab = kmem_cache_create(slab_name, sizeof(struct ccid) + obj_size, 0,
80
				 SLAB_HWCACHE_ALIGN, NULL);
81 82 83 84 85
	if (slab == NULL)
		kfree(slab_name);
	return slab;
}

86
static void ccid_kmem_cache_destroy(struct kmem_cache *slab)
87 88 89 90 91 92 93 94 95
{
	if (slab != NULL) {
		const char *name = kmem_cache_name(slab);

		kmem_cache_destroy(slab);
		kfree(name);
	}
}

Gerrit Renker's avatar
Gerrit Renker committed
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
/* check that up to @array_len members in @ccid_array are supported */
bool ccid_support_check(u8 const *ccid_array, u8 array_len)
{
	u8 i, j, found;

	for (i = 0, found = 0; i < array_len; i++, found = 0) {
		for (j = 0; !found && j < ARRAY_SIZE(builtin_ccids); j++)
			found = (ccid_array[i] == builtin_ccids[j]);
		if (!found)
			return false;
	}
	return true;
}

/**
 * ccid_get_builtin_ccids  -  Provide copy of `builtin' CCID array
 * @ccid_array: pointer to copy into
 * @array_len: value to return length into
 * This function allocates memory - caller must see that it is freed after use.
 */
int ccid_get_builtin_ccids(u8 **ccid_array, u8 *array_len)
{
	*ccid_array = kmemdup(builtin_ccids, sizeof(builtin_ccids), gfp_any());
	if (*ccid_array == NULL)
		return -ENOBUFS;
	*array_len = ARRAY_SIZE(builtin_ccids);
	return 0;
}

int ccid_getsockopt_builtin_ccids(struct sock *sk, int len,
				    char __user *optval, int __user *optlen)
{
	if (len < sizeof(builtin_ccids))
		return -EINVAL;

	if (put_user(sizeof(builtin_ccids), optlen) ||
	    copy_to_user(optval, builtin_ccids, sizeof(builtin_ccids)))
		return -EFAULT;
	return 0;
}

137 138 139 140 141 142
int ccid_register(struct ccid_operations *ccid_ops)
{
	int err = -ENOBUFS;

	ccid_ops->ccid_hc_rx_slab =
			ccid_kmem_cache_create(ccid_ops->ccid_hc_rx_obj_size,
143 144
					       "ccid%u_hc_rx_sock",
					       ccid_ops->ccid_id);
145 146 147 148 149
	if (ccid_ops->ccid_hc_rx_slab == NULL)
		goto out;

	ccid_ops->ccid_hc_tx_slab =
			ccid_kmem_cache_create(ccid_ops->ccid_hc_tx_obj_size,
150 151
					       "ccid%u_hc_tx_sock",
					       ccid_ops->ccid_id);
152 153
	if (ccid_ops->ccid_hc_tx_slab == NULL)
		goto out_free_rx_slab;
154 155 156

	ccids_write_lock();
	err = -EEXIST;
157 158
	if (ccids[ccid_ops->ccid_id] == NULL) {
		ccids[ccid_ops->ccid_id] = ccid_ops;
159 160 161
		err = 0;
	}
	ccids_write_unlock();
162 163 164 165 166 167
	if (err != 0)
		goto out_free_tx_slab;

	pr_info("CCID: Registered CCID %d (%s)\n",
		ccid_ops->ccid_id, ccid_ops->ccid_name);
out:
168
	return err;
169 170 171 172 173 174 175 176
out_free_tx_slab:
	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_tx_slab);
	ccid_ops->ccid_hc_tx_slab = NULL;
	goto out;
out_free_rx_slab:
	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_rx_slab);
	ccid_ops->ccid_hc_rx_slab = NULL;
	goto out;
177 178 179 180
}

EXPORT_SYMBOL_GPL(ccid_register);

181
int ccid_unregister(struct ccid_operations *ccid_ops)
182 183
{
	ccids_write_lock();
184
	ccids[ccid_ops->ccid_id] = NULL;
185
	ccids_write_unlock();
186 187 188 189 190 191

	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_tx_slab);
	ccid_ops->ccid_hc_tx_slab = NULL;
	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_rx_slab);
	ccid_ops->ccid_hc_rx_slab = NULL;

192
	pr_info("CCID: Unregistered CCID %d (%s)\n",
193
		ccid_ops->ccid_id, ccid_ops->ccid_name);
194 195 196 197 198
	return 0;
}

EXPORT_SYMBOL_GPL(ccid_unregister);

199
struct ccid *ccid_new(unsigned char id, struct sock *sk, int rx, gfp_t gfp)
200
{
201 202
	struct ccid_operations *ccid_ops;
	struct ccid *ccid = NULL;
203

204
	ccids_read_lock();
205
#ifdef CONFIG_KMOD
206 207 208 209 210
	if (ccids[id] == NULL) {
		/* We only try to load if in process context */
		ccids_read_unlock();
		if (gfp & GFP_ATOMIC)
			goto out;
211
		request_module("net-dccp-ccid-%d", id);
212 213
		ccids_read_lock();
	}
214
#endif
215 216 217
	ccid_ops = ccids[id];
	if (ccid_ops == NULL)
		goto out_unlock;
218

219 220
	if (!try_module_get(ccid_ops->ccid_owner))
		goto out_unlock;
221

222
	ccids_read_unlock();
223

224 225 226
	ccid = kmem_cache_alloc(rx ? ccid_ops->ccid_hc_rx_slab :
				     ccid_ops->ccid_hc_tx_slab, gfp);
	if (ccid == NULL)
227
		goto out_module_put;
228 229 230 231 232 233 234 235 236 237 238 239
	ccid->ccid_ops = ccid_ops;
	if (rx) {
		memset(ccid + 1, 0, ccid_ops->ccid_hc_rx_obj_size);
		if (ccid->ccid_ops->ccid_hc_rx_init != NULL &&
		    ccid->ccid_ops->ccid_hc_rx_init(ccid, sk) != 0)
			goto out_free_ccid;
	} else {
		memset(ccid + 1, 0, ccid_ops->ccid_hc_tx_obj_size);
		if (ccid->ccid_ops->ccid_hc_tx_init != NULL &&
		    ccid->ccid_ops->ccid_hc_tx_init(ccid, sk) != 0)
			goto out_free_ccid;
	}
240 241
out:
	return ccid;
242 243 244 245 246 247
out_unlock:
	ccids_read_unlock();
	goto out;
out_free_ccid:
	kmem_cache_free(rx ? ccid_ops->ccid_hc_rx_slab :
			ccid_ops->ccid_hc_tx_slab, ccid);
248
	ccid = NULL;
249 250
out_module_put:
	module_put(ccid_ops->ccid_owner);
251 252 253
	goto out;
}

254
EXPORT_SYMBOL_GPL(ccid_new);
255

256
struct ccid *ccid_hc_rx_new(unsigned char id, struct sock *sk, gfp_t gfp)
257
{
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
	return ccid_new(id, sk, 1, gfp);
}

EXPORT_SYMBOL_GPL(ccid_hc_rx_new);

struct ccid *ccid_hc_tx_new(unsigned char id,struct sock *sk,  gfp_t gfp)
{
	return ccid_new(id, sk, 0, gfp);
}

EXPORT_SYMBOL_GPL(ccid_hc_tx_new);

static void ccid_delete(struct ccid *ccid, struct sock *sk, int rx)
{
	struct ccid_operations *ccid_ops;

274 275 276
	if (ccid == NULL)
		return;

277 278 279 280 281 282 283 284 285 286
	ccid_ops = ccid->ccid_ops;
	if (rx) {
		if (ccid_ops->ccid_hc_rx_exit != NULL)
			ccid_ops->ccid_hc_rx_exit(sk);
		kmem_cache_free(ccid_ops->ccid_hc_rx_slab,  ccid);
	} else {
		if (ccid_ops->ccid_hc_tx_exit != NULL)
			ccid_ops->ccid_hc_tx_exit(sk);
		kmem_cache_free(ccid_ops->ccid_hc_tx_slab,  ccid);
	}
287
	ccids_read_lock();
288 289 290 291
	if (ccids[ccid_ops->ccid_id] != NULL)
		module_put(ccid_ops->ccid_owner);
	ccids_read_unlock();
}
292

293 294 295 296
void ccid_hc_rx_delete(struct ccid *ccid, struct sock *sk)
{
	ccid_delete(ccid, sk, 1);
}
297

298 299 300 301 302
EXPORT_SYMBOL_GPL(ccid_hc_rx_delete);

void ccid_hc_tx_delete(struct ccid *ccid, struct sock *sk)
{
	ccid_delete(ccid, sk, 0);
303 304
}

305
EXPORT_SYMBOL_GPL(ccid_hc_tx_delete);