Blame view

drivers/connector/connector.c 10.2 KB
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  /*
   * 	connector.c
   * 
   * 2004-2005 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
   * All rights reserved.
   * 
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 2 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/list.h>
  #include <linux/skbuff.h>
  #include <linux/netlink.h>
  #include <linux/moduleparam.h>
  #include <linux/connector.h>
8ed965d61   Arjan van de Ven   [PATCH] sem2mutex...
29
  #include <linux/mutex.h>
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  
  #include <net/sock.h>
  
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>");
  MODULE_DESCRIPTION("Generic userspace <-> kernelspace connector.");
  
  static u32 cn_idx = CN_IDX_CONNECTOR;
  static u32 cn_val = CN_VAL_CONNECTOR;
  
  module_param(cn_idx, uint, 0);
  module_param(cn_val, uint, 0);
  MODULE_PARM_DESC(cn_idx, "Connector's main device idx.");
  MODULE_PARM_DESC(cn_val, "Connector's main device val.");
8ed965d61   Arjan van de Ven   [PATCH] sem2mutex...
44
  static DEFINE_MUTEX(notify_lock);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
45
46
47
  static LIST_HEAD(notify_list);
  
  static struct cn_dev cdev;
78374676e   Li Zefan   CONNECTOR: make c...
48
  static int cn_already_initialized;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
  
  /*
   * msg->seq and msg->ack are used to determine message genealogy.
   * When someone sends message it puts there locally unique sequence
   * and random acknowledge numbers.  Sequence number may be copied into
   * nlmsghdr->nlmsg_seq too.
   *
   * Sequence number is incremented with each message to be sent.
   *
   * If we expect reply to our message then the sequence number in
   * received message MUST be the same as in original message, and
   * acknowledge number MUST be the same + 1.
   *
   * If we receive a message and its sequence number is not equal to the
   * one we are expecting then it is a new message.
   *
   * If we receive a message and its sequence number is the same as one
   * we are expecting but it's acknowledgement number is not equal to
   * the acknowledgement number in the original message + 1, then it is
   * a new message.
   *
   */
dd0fc66fb   Al Viro   [PATCH] gfp flags...
71
  int cn_netlink_send(struct cn_msg *msg, u32 __group, gfp_t gfp_mask)
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
72
73
74
75
76
77
78
79
80
81
82
83
84
85
  {
  	struct cn_callback_entry *__cbq;
  	unsigned int size;
  	struct sk_buff *skb;
  	struct nlmsghdr *nlh;
  	struct cn_msg *data;
  	struct cn_dev *dev = &cdev;
  	u32 group = 0;
  	int found = 0;
  
  	if (!__group) {
  		spin_lock_bh(&dev->cbdev->queue_lock);
  		list_for_each_entry(__cbq, &dev->cbdev->queue_list,
  				    callback_entry) {
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
86
  			if (cn_cb_equal(&__cbq->id.id, &msg->id)) {
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
87
88
  				found = 1;
  				group = __cbq->group;
fd00eeccd   Li Zefan   [CONNECTOR]: add ...
89
  				break;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
90
91
92
93
94
95
96
97
98
  			}
  		}
  		spin_unlock_bh(&dev->cbdev->queue_lock);
  
  		if (!found)
  			return -ENODEV;
  	} else {
  		group = __group;
  	}
b191ba0d5   Evgeniy Polyakov   [CONNECTOR]: Use ...
99
100
  	if (!netlink_has_listeners(dev->nls, group))
  		return -ESRCH;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
101
102
103
104
105
106
107
108
109
110
111
112
113
  	size = NLMSG_SPACE(sizeof(*msg) + msg->len);
  
  	skb = alloc_skb(size, gfp_mask);
  	if (!skb)
  		return -ENOMEM;
  
  	nlh = NLMSG_PUT(skb, 0, msg->seq, NLMSG_DONE, size - sizeof(*nlh));
  
  	data = NLMSG_DATA(nlh);
  
  	memcpy(data, msg, sizeof(*data) + msg->len);
  
  	NETLINK_CB(skb).dst_group = group;
b191ba0d5   Evgeniy Polyakov   [CONNECTOR]: Use ...
114
  	return netlink_broadcast(dev->nls, skb, 0, group, gfp_mask);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
115
116
117
118
119
  
  nlmsg_failure:
  	kfree_skb(skb);
  	return -EINVAL;
  }
deb0e9b23   Andrew Morton   [PATCH] connector...
120
  EXPORT_SYMBOL_GPL(cn_netlink_send);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
121
122
123
124
125
126
  
  /*
   * Callback helper - queues work and setup destructor for given data.
   */
  static int cn_call_callback(struct cn_msg *msg, void (*destruct_data)(void *), void *data)
  {
05e52dd73   Philipp Reisner   [CONNECTOR]: Bugf...
127
  	struct cn_callback_entry *__cbq, *__new_cbq;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
128
  	struct cn_dev *dev = &cdev;
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
129
  	int err = -ENODEV;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
130
131
132
  
  	spin_lock_bh(&dev->cbdev->queue_lock);
  	list_for_each_entry(__cbq, &dev->cbdev->queue_list, callback_entry) {
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
133
  		if (cn_cb_equal(&__cbq->id.id, &msg->id)) {
a240d9f1d   Evgeniy Polyakov   [CONNECTOR]: Repl...
134
  			if (likely(!work_pending(&__cbq->work) &&
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
135
136
  					__cbq->data.ddata == NULL)) {
  				__cbq->data.callback_priv = msg;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
137

acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
138
139
  				__cbq->data.ddata = data;
  				__cbq->data.destruct_data = destruct_data;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
140

a240d9f1d   Evgeniy Polyakov   [CONNECTOR]: Repl...
141
142
  				if (queue_work(dev->cbdev->cn_queue,
  							&__cbq->work))
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
143
  					err = 0;
134d99e30   Li Zefan   [CONNECTOR]: Retu...
144
145
  				else
  					err = -EINVAL;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
146
  			} else {
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
147
148
  				struct cn_callback_data *d;
  				
05e52dd73   Philipp Reisner   [CONNECTOR]: Bugf...
149
150
151
152
  				err = -ENOMEM;
  				__new_cbq = kzalloc(sizeof(struct cn_callback_entry), GFP_ATOMIC);
  				if (__new_cbq) {
  					d = &__new_cbq->data;
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
153
154
155
156
  					d->callback_priv = msg;
  					d->callback = __cbq->data.callback;
  					d->ddata = data;
  					d->destruct_data = destruct_data;
05e52dd73   Philipp Reisner   [CONNECTOR]: Bugf...
157
  					d->free = __new_cbq;
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
158

05e52dd73   Philipp Reisner   [CONNECTOR]: Bugf...
159
  					INIT_WORK(&__new_cbq->work,
a240d9f1d   Evgeniy Polyakov   [CONNECTOR]: Repl...
160
  							&cn_queue_wrapper);
05e52dd73   Philipp Reisner   [CONNECTOR]: Bugf...
161

a240d9f1d   Evgeniy Polyakov   [CONNECTOR]: Repl...
162
  					if (queue_work(dev->cbdev->cn_queue,
05e52dd73   Philipp Reisner   [CONNECTOR]: Bugf...
163
  						    &__new_cbq->work))
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
164
165
  						err = 0;
  					else {
05e52dd73   Philipp Reisner   [CONNECTOR]: Bugf...
166
  						kfree(__new_cbq);
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
167
168
  						err = -EINVAL;
  					}
05e52dd73   Philipp Reisner   [CONNECTOR]: Bugf...
169
  				}
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
170
171
172
173
174
  			}
  			break;
  		}
  	}
  	spin_unlock_bh(&dev->cbdev->queue_lock);
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
175
  	return err;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
176
177
178
  }
  
  /*
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
179
180
   * Main netlink receiving function.
   *
00f5e06c0   Li Zefan   [CONNECTOR]: clea...
181
   * It checks skb, netlink header and msg sizes, and calls callback helper.
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
182
183
184
   */
  static void cn_rx_skb(struct sk_buff *__skb)
  {
00f5e06c0   Li Zefan   [CONNECTOR]: clea...
185
  	struct cn_msg *msg;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
186
  	struct nlmsghdr *nlh;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
187
188
189
190
191
192
  	int err;
  	struct sk_buff *skb;
  
  	skb = skb_get(__skb);
  
  	if (skb->len >= NLMSG_SPACE(0)) {
b529ccf27   Arnaldo Carvalho de Melo   [NETLINK]: Introd...
193
  		nlh = nlmsg_hdr(skb);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
194
195
196
197
198
  
  		if (nlh->nlmsg_len < sizeof(struct cn_msg) ||
  		    skb->len < nlh->nlmsg_len ||
  		    nlh->nlmsg_len > CONNECTOR_MAX_MSG_SIZE) {
  			kfree_skb(skb);
6cf92e98a   Michal Januszewski   [CONNECTOR]: Fix ...
199
  			return;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
200
  		}
00f5e06c0   Li Zefan   [CONNECTOR]: clea...
201
202
  		msg = NLMSG_DATA(nlh);
  		err = cn_call_callback(msg, (void (*)(void *))kfree_skb, skb);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
203
204
205
  		if (err < 0)
  			kfree_skb(skb);
  	}
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
206
207
208
  }
  
  /*
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
209
210
211
212
213
214
215
216
217
218
   * Notification routing.
   *
   * Gets id and checks if there are notification request for it's idx
   * and val.  If there are such requests notify the listeners with the
   * given notify event.
   *
   */
  static void cn_notify(struct cb_id *id, u32 notify_event)
  {
  	struct cn_ctl_entry *ent;
8ed965d61   Arjan van de Ven   [PATCH] sem2mutex...
219
  	mutex_lock(&notify_lock);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
  	list_for_each_entry(ent, &notify_list, notify_entry) {
  		int i;
  		struct cn_notify_req *req;
  		struct cn_ctl_msg *ctl = ent->msg;
  		int idx_found, val_found;
  
  		idx_found = val_found = 0;
  
  		req = (struct cn_notify_req *)ctl->data;
  		for (i = 0; i < ctl->idx_notify_num; ++i, ++req) {
  			if (id->idx >= req->first && 
  					id->idx < req->first + req->range) {
  				idx_found = 1;
  				break;
  			}
  		}
  
  		for (i = 0; i < ctl->val_notify_num; ++i, ++req) {
  			if (id->val >= req->first && 
  					id->val < req->first + req->range) {
  				val_found = 1;
  				break;
  			}
  		}
  
  		if (idx_found && val_found) {
  			struct cn_msg m = { .ack = notify_event, };
  
  			memcpy(&m.id, id, sizeof(m.id));
  			cn_netlink_send(&m, ctl->group, GFP_KERNEL);
  		}
  	}
8ed965d61   Arjan van de Ven   [PATCH] sem2mutex...
252
  	mutex_unlock(&notify_lock);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
253
254
255
256
257
258
259
260
261
262
263
264
  }
  
  /*
   * Callback add routing - adds callback with given ID and name.
   * If there is registered callback with the same ID it will not be added.
   *
   * May sleep.
   */
  int cn_add_callback(struct cb_id *id, char *name, void (*callback)(void *))
  {
  	int err;
  	struct cn_dev *dev = &cdev;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
265

d6cc7f1a3   Evgeniy Polyakov   [CONNECTOR]: Init...
266
267
  	if (!cn_already_initialized)
  		return -EAGAIN;
acd042bb2   Evgeniy Polyakov   [CONNECTOR]: asyn...
268
269
  	err = cn_queue_add_callback(dev->cbdev, name, id, callback);
  	if (err)
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
270
  		return err;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
271
272
273
274
275
  
  	cn_notify(id, 0);
  
  	return 0;
  }
deb0e9b23   Andrew Morton   [PATCH] connector...
276
  EXPORT_SYMBOL_GPL(cn_add_callback);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
  
  /*
   * Callback remove routing - removes callback
   * with given ID.
   * If there is no registered callback with given
   * ID nothing happens.
   *
   * May sleep while waiting for reference counter to become zero.
   */
  void cn_del_callback(struct cb_id *id)
  {
  	struct cn_dev *dev = &cdev;
  
  	cn_queue_del_callback(dev->cbdev, id);
  	cn_notify(id, 1);
  }
deb0e9b23   Andrew Morton   [PATCH] connector...
293
  EXPORT_SYMBOL_GPL(cn_del_callback);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
  
  /*
   * Checks two connector's control messages to be the same.
   * Returns 1 if they are the same or if the first one is corrupted.
   */
  static int cn_ctl_msg_equals(struct cn_ctl_msg *m1, struct cn_ctl_msg *m2)
  {
  	int i;
  	struct cn_notify_req *req1, *req2;
  
  	if (m1->idx_notify_num != m2->idx_notify_num)
  		return 0;
  
  	if (m1->val_notify_num != m2->val_notify_num)
  		return 0;
  
  	if (m1->len != m2->len)
  		return 0;
  
  	if ((m1->idx_notify_num + m1->val_notify_num) * sizeof(*req1) !=
  	    m1->len)
  		return 1;
  
  	req1 = (struct cn_notify_req *)m1->data;
  	req2 = (struct cn_notify_req *)m2->data;
  
  	for (i = 0; i < m1->idx_notify_num; ++i) {
  		if (req1->first != req2->first || req1->range != req2->range)
  			return 0;
  		req1++;
  		req2++;
  	}
  
  	for (i = 0; i < m1->val_notify_num; ++i) {
  		if (req1->first != req2->first || req1->range != req2->range)
  			return 0;
  		req1++;
  		req2++;
  	}
  
  	return 1;
  }
  
  /*
   * Main connector device's callback.
   *
   * Used for notification of a request's processing.
   */
  static void cn_callback(void *data)
  {
  	struct cn_msg *msg = data;
  	struct cn_ctl_msg *ctl;
  	struct cn_ctl_entry *ent;
  	u32 size;
  
  	if (msg->len < sizeof(*ctl))
  		return;
  
  	ctl = (struct cn_ctl_msg *)msg->data;
  
  	size = (sizeof(*ctl) + ((ctl->idx_notify_num +
  				 ctl->val_notify_num) *
  				sizeof(struct cn_notify_req)));
  
  	if (msg->len != size)
  		return;
  
  	if (ctl->len + sizeof(*ctl) != msg->len)
  		return;
  
  	/*
  	 * Remove notification.
  	 */
  	if (ctl->group == 0) {
  		struct cn_ctl_entry *n;
8ed965d61   Arjan van de Ven   [PATCH] sem2mutex...
369
  		mutex_lock(&notify_lock);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
370
371
372
373
374
375
  		list_for_each_entry_safe(ent, n, &notify_list, notify_entry) {
  			if (cn_ctl_msg_equals(ent->msg, ctl)) {
  				list_del(&ent->notify_entry);
  				kfree(ent);
  			}
  		}
8ed965d61   Arjan van de Ven   [PATCH] sem2mutex...
376
  		mutex_unlock(&notify_lock);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
377
378
379
380
381
382
383
384
385
386
387
388
389
  
  		return;
  	}
  
  	size += sizeof(*ent);
  
  	ent = kzalloc(size, GFP_KERNEL);
  	if (!ent)
  		return;
  
  	ent->msg = (struct cn_ctl_msg *)(ent + 1);
  
  	memcpy(ent->msg, ctl, size - sizeof(*ent));
8ed965d61   Arjan van de Ven   [PATCH] sem2mutex...
390
  	mutex_lock(&notify_lock);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
391
  	list_add(&ent->notify_entry, &notify_list);
8ed965d61   Arjan van de Ven   [PATCH] sem2mutex...
392
  	mutex_unlock(&notify_lock);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
393
  }
d6cc7f1a3   Evgeniy Polyakov   [CONNECTOR]: Init...
394
  static int __devinit cn_init(void)
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
395
396
397
  {
  	struct cn_dev *dev = &cdev;
  	int err;
cd40b7d39   Denis V. Lunev   [NET]: make netli...
398
  	dev->input = cn_rx_skb;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
399
400
  	dev->id.idx = cn_idx;
  	dev->id.val = cn_val;
b4b510290   Eric W. Biederman   [NET]: Support mu...
401
  	dev->nls = netlink_kernel_create(&init_net, NETLINK_CONNECTOR,
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
402
  					 CN_NETLINK_USERS + 0xf,
af65bdfce   Patrick McHardy   [NETLINK]: Switch...
403
  					 dev->input, NULL, THIS_MODULE);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
404
405
406
407
408
  	if (!dev->nls)
  		return -EIO;
  
  	dev->cbdev = cn_queue_alloc_dev("cqueue", dev->nls);
  	if (!dev->cbdev) {
b7c6ba6eb   Denis V. Lunev   [NETNS]: Consolid...
409
  		netlink_kernel_release(dev->nls);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
410
411
  		return -EINVAL;
  	}
d6cc7f1a3   Evgeniy Polyakov   [CONNECTOR]: Init...
412
413
  	
  	cn_already_initialized = 1;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
414
415
416
  
  	err = cn_add_callback(&dev->id, "connector", &cn_callback);
  	if (err) {
d6cc7f1a3   Evgeniy Polyakov   [CONNECTOR]: Init...
417
  		cn_already_initialized = 0;
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
418
  		cn_queue_free_dev(dev->cbdev);
b7c6ba6eb   Denis V. Lunev   [NETNS]: Consolid...
419
  		netlink_kernel_release(dev->nls);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
420
421
  		return -EINVAL;
  	}
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
422
423
  	return 0;
  }
d6cc7f1a3   Evgeniy Polyakov   [CONNECTOR]: Init...
424
  static void __devexit cn_fini(void)
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
425
426
427
428
429
430
431
  {
  	struct cn_dev *dev = &cdev;
  
  	cn_already_initialized = 0;
  
  	cn_del_callback(&dev->id);
  	cn_queue_free_dev(dev->cbdev);
b7c6ba6eb   Denis V. Lunev   [NETNS]: Consolid...
432
  	netlink_kernel_release(dev->nls);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
433
  }
d6cc7f1a3   Evgeniy Polyakov   [CONNECTOR]: Init...
434
  subsys_initcall(cn_init);
7672d0b54   Evgeniy Polyakov   [NET]: Add netlin...
435
  module_exit(cn_fini);