blob: 647cb0614f843752ec64106bb179ecc3a44c9db6 [file] [log] [blame]
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -07001/*
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
Gerrit Renkerd90ebcb2008-11-12 00:47:26 -080016static u8 builtin_ccids[] = {
17 DCCPC_CCID2, /* CCID2 is supported by default */
18#if defined(CONFIG_IP_DCCP_CCID3) || defined(CONFIG_IP_DCCP_CCID3_MODULE)
19 DCCPC_CCID3,
20#endif
21};
22
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -080023static struct ccid_operations *ccids[CCID_MAX];
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -070024#if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT)
25static atomic_t ccids_lockct = ATOMIC_INIT(0);
26static DEFINE_SPINLOCK(ccids_lock);
27
28/*
29 * The strategy is: modifications ccids vector are short, do not sleep and
30 * veeery rare, but read access should be free of any exclusive locks.
31 */
32static void ccids_write_lock(void)
33{
34 spin_lock(&ccids_lock);
35 while (atomic_read(&ccids_lockct) != 0) {
36 spin_unlock(&ccids_lock);
37 yield();
38 spin_lock(&ccids_lock);
39 }
40}
41
42static inline void ccids_write_unlock(void)
43{
44 spin_unlock(&ccids_lock);
45}
46
47static inline void ccids_read_lock(void)
48{
49 atomic_inc(&ccids_lockct);
Oleg Nesterovd725fdc2007-08-10 15:21:17 -070050 smp_mb__after_atomic_inc();
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -070051 spin_unlock_wait(&ccids_lock);
52}
53
54static inline void ccids_read_unlock(void)
55{
56 atomic_dec(&ccids_lockct);
57}
58
59#else
60#define ccids_write_lock() do { } while(0)
61#define ccids_write_unlock() do { } while(0)
62#define ccids_read_lock() do { } while(0)
63#define ccids_read_unlock() do { } while(0)
64#endif
65
Christoph Lametere18b8902006-12-06 20:33:20 -080066static struct kmem_cache *ccid_kmem_cache_create(int obj_size, const char *fmt,...)
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -070067{
Christoph Lametere18b8902006-12-06 20:33:20 -080068 struct kmem_cache *slab;
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -080069 char slab_name_fmt[32], *slab_name;
70 va_list args;
71
72 va_start(args, fmt);
73 vsnprintf(slab_name_fmt, sizeof(slab_name_fmt), fmt, args);
74 va_end(args);
75
76 slab_name = kstrdup(slab_name_fmt, GFP_KERNEL);
77 if (slab_name == NULL)
78 return NULL;
79 slab = kmem_cache_create(slab_name, sizeof(struct ccid) + obj_size, 0,
Paul Mundt20c2df82007-07-20 10:11:58 +090080 SLAB_HWCACHE_ALIGN, NULL);
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -080081 if (slab == NULL)
82 kfree(slab_name);
83 return slab;
84}
85
Christoph Lametere18b8902006-12-06 20:33:20 -080086static void ccid_kmem_cache_destroy(struct kmem_cache *slab)
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -080087{
88 if (slab != NULL) {
89 const char *name = kmem_cache_name(slab);
90
91 kmem_cache_destroy(slab);
92 kfree(name);
93 }
94}
95
Gerrit Renkerd90ebcb2008-11-12 00:47:26 -080096/* check that up to @array_len members in @ccid_array are supported */
97bool ccid_support_check(u8 const *ccid_array, u8 array_len)
98{
99 u8 i, j, found;
100
101 for (i = 0, found = 0; i < array_len; i++, found = 0) {
102 for (j = 0; !found && j < ARRAY_SIZE(builtin_ccids); j++)
103 found = (ccid_array[i] == builtin_ccids[j]);
104 if (!found)
105 return false;
106 }
107 return true;
108}
109
110/**
111 * ccid_get_builtin_ccids - Provide copy of `builtin' CCID array
112 * @ccid_array: pointer to copy into
113 * @array_len: value to return length into
114 * This function allocates memory - caller must see that it is freed after use.
115 */
116int ccid_get_builtin_ccids(u8 **ccid_array, u8 *array_len)
117{
118 *ccid_array = kmemdup(builtin_ccids, sizeof(builtin_ccids), gfp_any());
119 if (*ccid_array == NULL)
120 return -ENOBUFS;
121 *array_len = ARRAY_SIZE(builtin_ccids);
122 return 0;
123}
124
125int ccid_getsockopt_builtin_ccids(struct sock *sk, int len,
126 char __user *optval, int __user *optlen)
127{
128 if (len < sizeof(builtin_ccids))
129 return -EINVAL;
130
131 if (put_user(sizeof(builtin_ccids), optlen) ||
132 copy_to_user(optval, builtin_ccids, sizeof(builtin_ccids)))
133 return -EFAULT;
134 return 0;
135}
136
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800137int ccid_register(struct ccid_operations *ccid_ops)
138{
139 int err = -ENOBUFS;
140
141 ccid_ops->ccid_hc_rx_slab =
142 ccid_kmem_cache_create(ccid_ops->ccid_hc_rx_obj_size,
Gerrit Renker84a97b02007-12-13 23:33:25 -0200143 "ccid%u_hc_rx_sock",
144 ccid_ops->ccid_id);
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800145 if (ccid_ops->ccid_hc_rx_slab == NULL)
146 goto out;
147
148 ccid_ops->ccid_hc_tx_slab =
149 ccid_kmem_cache_create(ccid_ops->ccid_hc_tx_obj_size,
Gerrit Renker84a97b02007-12-13 23:33:25 -0200150 "ccid%u_hc_tx_sock",
151 ccid_ops->ccid_id);
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800152 if (ccid_ops->ccid_hc_tx_slab == NULL)
153 goto out_free_rx_slab;
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700154
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700155 ccids_write_lock();
156 err = -EEXIST;
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800157 if (ccids[ccid_ops->ccid_id] == NULL) {
158 ccids[ccid_ops->ccid_id] = ccid_ops;
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700159 err = 0;
160 }
161 ccids_write_unlock();
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800162 if (err != 0)
163 goto out_free_tx_slab;
164
165 pr_info("CCID: Registered CCID %d (%s)\n",
166 ccid_ops->ccid_id, ccid_ops->ccid_name);
167out:
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700168 return err;
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800169out_free_tx_slab:
170 ccid_kmem_cache_destroy(ccid_ops->ccid_hc_tx_slab);
171 ccid_ops->ccid_hc_tx_slab = NULL;
172 goto out;
173out_free_rx_slab:
174 ccid_kmem_cache_destroy(ccid_ops->ccid_hc_rx_slab);
175 ccid_ops->ccid_hc_rx_slab = NULL;
176 goto out;
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700177}
178
179EXPORT_SYMBOL_GPL(ccid_register);
180
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800181int ccid_unregister(struct ccid_operations *ccid_ops)
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700182{
183 ccids_write_lock();
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800184 ccids[ccid_ops->ccid_id] = NULL;
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700185 ccids_write_unlock();
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800186
187 ccid_kmem_cache_destroy(ccid_ops->ccid_hc_tx_slab);
188 ccid_ops->ccid_hc_tx_slab = NULL;
189 ccid_kmem_cache_destroy(ccid_ops->ccid_hc_rx_slab);
190 ccid_ops->ccid_hc_rx_slab = NULL;
191
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700192 pr_info("CCID: Unregistered CCID %d (%s)\n",
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800193 ccid_ops->ccid_id, ccid_ops->ccid_name);
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700194 return 0;
195}
196
197EXPORT_SYMBOL_GPL(ccid_unregister);
198
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800199struct ccid *ccid_new(unsigned char id, struct sock *sk, int rx, gfp_t gfp)
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700200{
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800201 struct ccid_operations *ccid_ops;
202 struct ccid *ccid = NULL;
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700203
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700204 ccids_read_lock();
Johannes Berg95a5afc2008-10-16 15:24:51 -0700205#ifdef CONFIG_MODULES
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800206 if (ccids[id] == NULL) {
207 /* We only try to load if in process context */
208 ccids_read_unlock();
209 if (gfp & GFP_ATOMIC)
210 goto out;
211 request_module("net-dccp-ccid-%d", id);
212 ccids_read_lock();
213 }
214#endif
215 ccid_ops = ccids[id];
216 if (ccid_ops == NULL)
217 goto out_unlock;
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700218
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800219 if (!try_module_get(ccid_ops->ccid_owner))
220 goto out_unlock;
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700221
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700222 ccids_read_unlock();
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800223
224 ccid = kmem_cache_alloc(rx ? ccid_ops->ccid_hc_rx_slab :
225 ccid_ops->ccid_hc_tx_slab, gfp);
226 if (ccid == NULL)
227 goto out_module_put;
228 ccid->ccid_ops = ccid_ops;
229 if (rx) {
230 memset(ccid + 1, 0, ccid_ops->ccid_hc_rx_obj_size);
231 if (ccid->ccid_ops->ccid_hc_rx_init != NULL &&
232 ccid->ccid_ops->ccid_hc_rx_init(ccid, sk) != 0)
233 goto out_free_ccid;
234 } else {
235 memset(ccid + 1, 0, ccid_ops->ccid_hc_tx_obj_size);
236 if (ccid->ccid_ops->ccid_hc_tx_init != NULL &&
237 ccid->ccid_ops->ccid_hc_tx_init(ccid, sk) != 0)
238 goto out_free_ccid;
239 }
240out:
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700241 return ccid;
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800242out_unlock:
243 ccids_read_unlock();
244 goto out;
245out_free_ccid:
246 kmem_cache_free(rx ? ccid_ops->ccid_hc_rx_slab :
247 ccid_ops->ccid_hc_tx_slab, ccid);
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700248 ccid = NULL;
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800249out_module_put:
250 module_put(ccid_ops->ccid_owner);
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700251 goto out;
252}
253
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800254EXPORT_SYMBOL_GPL(ccid_new);
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700255
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800256struct ccid *ccid_hc_rx_new(unsigned char id, struct sock *sk, gfp_t gfp)
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700257{
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800258 return ccid_new(id, sk, 1, gfp);
259}
260
261EXPORT_SYMBOL_GPL(ccid_hc_rx_new);
262
263struct ccid *ccid_hc_tx_new(unsigned char id,struct sock *sk, gfp_t gfp)
264{
265 return ccid_new(id, sk, 0, gfp);
266}
267
268EXPORT_SYMBOL_GPL(ccid_hc_tx_new);
269
270static void ccid_delete(struct ccid *ccid, struct sock *sk, int rx)
271{
272 struct ccid_operations *ccid_ops;
273
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700274 if (ccid == NULL)
275 return;
276
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800277 ccid_ops = ccid->ccid_ops;
278 if (rx) {
279 if (ccid_ops->ccid_hc_rx_exit != NULL)
280 ccid_ops->ccid_hc_rx_exit(sk);
281 kmem_cache_free(ccid_ops->ccid_hc_rx_slab, ccid);
282 } else {
283 if (ccid_ops->ccid_hc_tx_exit != NULL)
284 ccid_ops->ccid_hc_tx_exit(sk);
285 kmem_cache_free(ccid_ops->ccid_hc_tx_slab, ccid);
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700286 }
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800287 ccids_read_lock();
288 if (ccids[ccid_ops->ccid_id] != NULL)
289 module_put(ccid_ops->ccid_owner);
Arnaldo Carvalho de Melo7c657872005-08-09 20:14:34 -0700290 ccids_read_unlock();
291}
292
Arnaldo Carvalho de Melo91f0ebf2006-03-20 19:21:44 -0800293void ccid_hc_rx_delete(struct ccid *ccid, struct sock *sk)
294{
295 ccid_delete(ccid, sk, 1);
296}
297
298EXPORT_SYMBOL_GPL(ccid_hc_rx_delete);
299
300void ccid_hc_tx_delete(struct ccid *ccid, struct sock *sk)
301{
302 ccid_delete(ccid, sk, 0);
303}
304
305EXPORT_SYMBOL_GPL(ccid_hc_tx_delete);