1/*
2 *  net/dccp/ccid.c
3 *
4 *  An implementation of the DCCP protocol
5 *  Arnaldo Carvalho de Melo <acme@conectiva.com.br>
6 *
7 *  CCID infrastructure
8 *
9 *	This program is free software; you can redistribute it and/or modify it
10 *	under the terms of the GNU General Public License version 2 as
11 *	published by the Free Software Foundation.
12 */
13
14#include "ccid.h"
15
16static struct ccid_operations *ccids[CCID_MAX];
17#if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT)
18static atomic_t ccids_lockct = ATOMIC_INIT(0);
19static DEFINE_SPINLOCK(ccids_lock);
20
21/*
22 * The strategy is: modifications ccids vector are short, do not sleep and
23 * veeery rare, but read access should be free of any exclusive locks.
24 */
25static void ccids_write_lock(void)
26{
27	spin_lock(&ccids_lock);
28	while (atomic_read(&ccids_lockct) != 0) {
29		spin_unlock(&ccids_lock);
30		yield();
31		spin_lock(&ccids_lock);
32	}
33}
34
35static inline void ccids_write_unlock(void)
36{
37	spin_unlock(&ccids_lock);
38}
39
40static inline void ccids_read_lock(void)
41{
42	atomic_inc(&ccids_lockct);
43	spin_unlock_wait(&ccids_lock);
44}
45
46static inline void ccids_read_unlock(void)
47{
48	atomic_dec(&ccids_lockct);
49}
50
51#else
52#define ccids_write_lock() do { } while(0)
53#define ccids_write_unlock() do { } while(0)
54#define ccids_read_lock() do { } while(0)
55#define ccids_read_unlock() do { } while(0)
56#endif
57
58static struct kmem_cache *ccid_kmem_cache_create(int obj_size, const char *fmt,...)
59{
60	struct kmem_cache *slab;
61	char slab_name_fmt[32], *slab_name;
62	va_list args;
63
64	va_start(args, fmt);
65	vsnprintf(slab_name_fmt, sizeof(slab_name_fmt), fmt, args);
66	va_end(args);
67
68	slab_name = kstrdup(slab_name_fmt, GFP_KERNEL);
69	if (slab_name == NULL)
70		return NULL;
71	slab = kmem_cache_create(slab_name, sizeof(struct ccid) + obj_size, 0,
72				 SLAB_HWCACHE_ALIGN, NULL, NULL);
73	if (slab == NULL)
74		kfree(slab_name);
75	return slab;
76}
77
78static void ccid_kmem_cache_destroy(struct kmem_cache *slab)
79{
80	if (slab != NULL) {
81		const char *name = kmem_cache_name(slab);
82
83		kmem_cache_destroy(slab);
84		kfree(name);
85	}
86}
87
88int ccid_register(struct ccid_operations *ccid_ops)
89{
90	int err = -ENOBUFS;
91
92	ccid_ops->ccid_hc_rx_slab =
93			ccid_kmem_cache_create(ccid_ops->ccid_hc_rx_obj_size,
94					       "%s_hc_rx_sock",
95					       ccid_ops->ccid_name);
96	if (ccid_ops->ccid_hc_rx_slab == NULL)
97		goto out;
98
99	ccid_ops->ccid_hc_tx_slab =
100			ccid_kmem_cache_create(ccid_ops->ccid_hc_tx_obj_size,
101					       "%s_hc_tx_sock",
102					       ccid_ops->ccid_name);
103	if (ccid_ops->ccid_hc_tx_slab == NULL)
104		goto out_free_rx_slab;
105
106	ccids_write_lock();
107	err = -EEXIST;
108	if (ccids[ccid_ops->ccid_id] == NULL) {
109		ccids[ccid_ops->ccid_id] = ccid_ops;
110		err = 0;
111	}
112	ccids_write_unlock();
113	if (err != 0)
114		goto out_free_tx_slab;
115
116	pr_info("CCID: Registered CCID %d (%s)\n",
117		ccid_ops->ccid_id, ccid_ops->ccid_name);
118out:
119	return err;
120out_free_tx_slab:
121	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_tx_slab);
122	ccid_ops->ccid_hc_tx_slab = NULL;
123	goto out;
124out_free_rx_slab:
125	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_rx_slab);
126	ccid_ops->ccid_hc_rx_slab = NULL;
127	goto out;
128}
129
130EXPORT_SYMBOL_GPL(ccid_register);
131
132int ccid_unregister(struct ccid_operations *ccid_ops)
133{
134	ccids_write_lock();
135	ccids[ccid_ops->ccid_id] = NULL;
136	ccids_write_unlock();
137
138	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_tx_slab);
139	ccid_ops->ccid_hc_tx_slab = NULL;
140	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_rx_slab);
141	ccid_ops->ccid_hc_rx_slab = NULL;
142
143	pr_info("CCID: Unregistered CCID %d (%s)\n",
144		ccid_ops->ccid_id, ccid_ops->ccid_name);
145	return 0;
146}
147
148EXPORT_SYMBOL_GPL(ccid_unregister);
149
150struct ccid *ccid_new(unsigned char id, struct sock *sk, int rx, gfp_t gfp)
151{
152	struct ccid_operations *ccid_ops;
153	struct ccid *ccid = NULL;
154
155	ccids_read_lock();
156#ifdef CONFIG_KMOD
157	if (ccids[id] == NULL) {
158		/* We only try to load if in process context */
159		ccids_read_unlock();
160		if (gfp & GFP_ATOMIC)
161			goto out;
162		request_module("net-dccp-ccid-%d", id);
163		ccids_read_lock();
164	}
165#endif
166	ccid_ops = ccids[id];
167	if (ccid_ops == NULL)
168		goto out_unlock;
169
170	if (!try_module_get(ccid_ops->ccid_owner))
171		goto out_unlock;
172
173	ccids_read_unlock();
174
175	ccid = kmem_cache_alloc(rx ? ccid_ops->ccid_hc_rx_slab :
176				     ccid_ops->ccid_hc_tx_slab, gfp);
177	if (ccid == NULL)
178		goto out_module_put;
179	ccid->ccid_ops = ccid_ops;
180	if (rx) {
181		memset(ccid + 1, 0, ccid_ops->ccid_hc_rx_obj_size);
182		if (ccid->ccid_ops->ccid_hc_rx_init != NULL &&
183		    ccid->ccid_ops->ccid_hc_rx_init(ccid, sk) != 0)
184			goto out_free_ccid;
185	} else {
186		memset(ccid + 1, 0, ccid_ops->ccid_hc_tx_obj_size);
187		if (ccid->ccid_ops->ccid_hc_tx_init != NULL &&
188		    ccid->ccid_ops->ccid_hc_tx_init(ccid, sk) != 0)
189			goto out_free_ccid;
190	}
191out:
192	return ccid;
193out_unlock:
194	ccids_read_unlock();
195	goto out;
196out_free_ccid:
197	kmem_cache_free(rx ? ccid_ops->ccid_hc_rx_slab :
198			ccid_ops->ccid_hc_tx_slab, ccid);
199	ccid = NULL;
200out_module_put:
201	module_put(ccid_ops->ccid_owner);
202	goto out;
203}
204
205EXPORT_SYMBOL_GPL(ccid_new);
206
207struct ccid *ccid_hc_rx_new(unsigned char id, struct sock *sk, gfp_t gfp)
208{
209	return ccid_new(id, sk, 1, gfp);
210}
211
212EXPORT_SYMBOL_GPL(ccid_hc_rx_new);
213
214struct ccid *ccid_hc_tx_new(unsigned char id,struct sock *sk,  gfp_t gfp)
215{
216	return ccid_new(id, sk, 0, gfp);
217}
218
219EXPORT_SYMBOL_GPL(ccid_hc_tx_new);
220
221static void ccid_delete(struct ccid *ccid, struct sock *sk, int rx)
222{
223	struct ccid_operations *ccid_ops;
224
225	if (ccid == NULL)
226		return;
227
228	ccid_ops = ccid->ccid_ops;
229	if (rx) {
230		if (ccid_ops->ccid_hc_rx_exit != NULL)
231			ccid_ops->ccid_hc_rx_exit(sk);
232		kmem_cache_free(ccid_ops->ccid_hc_rx_slab,  ccid);
233	} else {
234		if (ccid_ops->ccid_hc_tx_exit != NULL)
235			ccid_ops->ccid_hc_tx_exit(sk);
236		kmem_cache_free(ccid_ops->ccid_hc_tx_slab,  ccid);
237	}
238	ccids_read_lock();
239	if (ccids[ccid_ops->ccid_id] != NULL)
240		module_put(ccid_ops->ccid_owner);
241	ccids_read_unlock();
242}
243
244void ccid_hc_rx_delete(struct ccid *ccid, struct sock *sk)
245{
246	ccid_delete(ccid, sk, 1);
247}
248
249EXPORT_SYMBOL_GPL(ccid_hc_rx_delete);
250
251void ccid_hc_tx_delete(struct ccid *ccid, struct sock *sk)
252{
253	ccid_delete(ccid, sk, 0);
254}
255
256EXPORT_SYMBOL_GPL(ccid_hc_tx_delete);
257