xref: /openbmc/linux/net/dccp/ccid.c (revision 22246614)
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 
16 static struct ccid_operations *ccids[CCID_MAX];
17 #if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT)
18 static atomic_t ccids_lockct = ATOMIC_INIT(0);
19 static 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  */
25 static 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 
35 static inline void ccids_write_unlock(void)
36 {
37 	spin_unlock(&ccids_lock);
38 }
39 
40 static inline void ccids_read_lock(void)
41 {
42 	atomic_inc(&ccids_lockct);
43 	smp_mb__after_atomic_inc();
44 	spin_unlock_wait(&ccids_lock);
45 }
46 
47 static inline void ccids_read_unlock(void)
48 {
49 	atomic_dec(&ccids_lockct);
50 }
51 
52 #else
53 #define ccids_write_lock() do { } while(0)
54 #define ccids_write_unlock() do { } while(0)
55 #define ccids_read_lock() do { } while(0)
56 #define ccids_read_unlock() do { } while(0)
57 #endif
58 
59 static struct kmem_cache *ccid_kmem_cache_create(int obj_size, const char *fmt,...)
60 {
61 	struct kmem_cache *slab;
62 	char slab_name_fmt[32], *slab_name;
63 	va_list args;
64 
65 	va_start(args, fmt);
66 	vsnprintf(slab_name_fmt, sizeof(slab_name_fmt), fmt, args);
67 	va_end(args);
68 
69 	slab_name = kstrdup(slab_name_fmt, GFP_KERNEL);
70 	if (slab_name == NULL)
71 		return NULL;
72 	slab = kmem_cache_create(slab_name, sizeof(struct ccid) + obj_size, 0,
73 				 SLAB_HWCACHE_ALIGN, NULL);
74 	if (slab == NULL)
75 		kfree(slab_name);
76 	return slab;
77 }
78 
79 static void ccid_kmem_cache_destroy(struct kmem_cache *slab)
80 {
81 	if (slab != NULL) {
82 		const char *name = kmem_cache_name(slab);
83 
84 		kmem_cache_destroy(slab);
85 		kfree(name);
86 	}
87 }
88 
89 int ccid_register(struct ccid_operations *ccid_ops)
90 {
91 	int err = -ENOBUFS;
92 
93 	ccid_ops->ccid_hc_rx_slab =
94 			ccid_kmem_cache_create(ccid_ops->ccid_hc_rx_obj_size,
95 					       "ccid%u_hc_rx_sock",
96 					       ccid_ops->ccid_id);
97 	if (ccid_ops->ccid_hc_rx_slab == NULL)
98 		goto out;
99 
100 	ccid_ops->ccid_hc_tx_slab =
101 			ccid_kmem_cache_create(ccid_ops->ccid_hc_tx_obj_size,
102 					       "ccid%u_hc_tx_sock",
103 					       ccid_ops->ccid_id);
104 	if (ccid_ops->ccid_hc_tx_slab == NULL)
105 		goto out_free_rx_slab;
106 
107 	ccids_write_lock();
108 	err = -EEXIST;
109 	if (ccids[ccid_ops->ccid_id] == NULL) {
110 		ccids[ccid_ops->ccid_id] = ccid_ops;
111 		err = 0;
112 	}
113 	ccids_write_unlock();
114 	if (err != 0)
115 		goto out_free_tx_slab;
116 
117 	pr_info("CCID: Registered CCID %d (%s)\n",
118 		ccid_ops->ccid_id, ccid_ops->ccid_name);
119 out:
120 	return err;
121 out_free_tx_slab:
122 	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_tx_slab);
123 	ccid_ops->ccid_hc_tx_slab = NULL;
124 	goto out;
125 out_free_rx_slab:
126 	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_rx_slab);
127 	ccid_ops->ccid_hc_rx_slab = NULL;
128 	goto out;
129 }
130 
131 EXPORT_SYMBOL_GPL(ccid_register);
132 
133 int ccid_unregister(struct ccid_operations *ccid_ops)
134 {
135 	ccids_write_lock();
136 	ccids[ccid_ops->ccid_id] = NULL;
137 	ccids_write_unlock();
138 
139 	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_tx_slab);
140 	ccid_ops->ccid_hc_tx_slab = NULL;
141 	ccid_kmem_cache_destroy(ccid_ops->ccid_hc_rx_slab);
142 	ccid_ops->ccid_hc_rx_slab = NULL;
143 
144 	pr_info("CCID: Unregistered CCID %d (%s)\n",
145 		ccid_ops->ccid_id, ccid_ops->ccid_name);
146 	return 0;
147 }
148 
149 EXPORT_SYMBOL_GPL(ccid_unregister);
150 
151 struct ccid *ccid_new(unsigned char id, struct sock *sk, int rx, gfp_t gfp)
152 {
153 	struct ccid_operations *ccid_ops;
154 	struct ccid *ccid = NULL;
155 
156 	ccids_read_lock();
157 #ifdef CONFIG_KMOD
158 	if (ccids[id] == NULL) {
159 		/* We only try to load if in process context */
160 		ccids_read_unlock();
161 		if (gfp & GFP_ATOMIC)
162 			goto out;
163 		request_module("net-dccp-ccid-%d", id);
164 		ccids_read_lock();
165 	}
166 #endif
167 	ccid_ops = ccids[id];
168 	if (ccid_ops == NULL)
169 		goto out_unlock;
170 
171 	if (!try_module_get(ccid_ops->ccid_owner))
172 		goto out_unlock;
173 
174 	ccids_read_unlock();
175 
176 	ccid = kmem_cache_alloc(rx ? ccid_ops->ccid_hc_rx_slab :
177 				     ccid_ops->ccid_hc_tx_slab, gfp);
178 	if (ccid == NULL)
179 		goto out_module_put;
180 	ccid->ccid_ops = ccid_ops;
181 	if (rx) {
182 		memset(ccid + 1, 0, ccid_ops->ccid_hc_rx_obj_size);
183 		if (ccid->ccid_ops->ccid_hc_rx_init != NULL &&
184 		    ccid->ccid_ops->ccid_hc_rx_init(ccid, sk) != 0)
185 			goto out_free_ccid;
186 	} else {
187 		memset(ccid + 1, 0, ccid_ops->ccid_hc_tx_obj_size);
188 		if (ccid->ccid_ops->ccid_hc_tx_init != NULL &&
189 		    ccid->ccid_ops->ccid_hc_tx_init(ccid, sk) != 0)
190 			goto out_free_ccid;
191 	}
192 out:
193 	return ccid;
194 out_unlock:
195 	ccids_read_unlock();
196 	goto out;
197 out_free_ccid:
198 	kmem_cache_free(rx ? ccid_ops->ccid_hc_rx_slab :
199 			ccid_ops->ccid_hc_tx_slab, ccid);
200 	ccid = NULL;
201 out_module_put:
202 	module_put(ccid_ops->ccid_owner);
203 	goto out;
204 }
205 
206 EXPORT_SYMBOL_GPL(ccid_new);
207 
208 struct ccid *ccid_hc_rx_new(unsigned char id, struct sock *sk, gfp_t gfp)
209 {
210 	return ccid_new(id, sk, 1, gfp);
211 }
212 
213 EXPORT_SYMBOL_GPL(ccid_hc_rx_new);
214 
215 struct ccid *ccid_hc_tx_new(unsigned char id,struct sock *sk,  gfp_t gfp)
216 {
217 	return ccid_new(id, sk, 0, gfp);
218 }
219 
220 EXPORT_SYMBOL_GPL(ccid_hc_tx_new);
221 
222 static void ccid_delete(struct ccid *ccid, struct sock *sk, int rx)
223 {
224 	struct ccid_operations *ccid_ops;
225 
226 	if (ccid == NULL)
227 		return;
228 
229 	ccid_ops = ccid->ccid_ops;
230 	if (rx) {
231 		if (ccid_ops->ccid_hc_rx_exit != NULL)
232 			ccid_ops->ccid_hc_rx_exit(sk);
233 		kmem_cache_free(ccid_ops->ccid_hc_rx_slab,  ccid);
234 	} else {
235 		if (ccid_ops->ccid_hc_tx_exit != NULL)
236 			ccid_ops->ccid_hc_tx_exit(sk);
237 		kmem_cache_free(ccid_ops->ccid_hc_tx_slab,  ccid);
238 	}
239 	ccids_read_lock();
240 	if (ccids[ccid_ops->ccid_id] != NULL)
241 		module_put(ccid_ops->ccid_owner);
242 	ccids_read_unlock();
243 }
244 
245 void ccid_hc_rx_delete(struct ccid *ccid, struct sock *sk)
246 {
247 	ccid_delete(ccid, sk, 1);
248 }
249 
250 EXPORT_SYMBOL_GPL(ccid_hc_rx_delete);
251 
252 void ccid_hc_tx_delete(struct ccid *ccid, struct sock *sk)
253 {
254 	ccid_delete(ccid, sk, 0);
255 }
256 
257 EXPORT_SYMBOL_GPL(ccid_hc_tx_delete);
258