Blame view

net/sched/sch_prio.c 8.34 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
2
3
4
5
6
7
8
9
  /*
   * net/sched/sch_prio.c	Simple 3-band priority "scheduler".
   *
   *		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.
   *
   * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
10297b993   YOSHIFUJI Hideaki   [NET] SCHED: Fix ...
10
   * Fixes:       19990609: J Hadi Salim <hadi@nortelnetworks.com>:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
11
12
   *              Init --  EINVAL when opt undefined
   */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
13
  #include <linux/module.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
14
  #include <linux/slab.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
15
16
  #include <linux/types.h>
  #include <linux/kernel.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
17
  #include <linux/string.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
18
  #include <linux/errno.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
19
  #include <linux/skbuff.h>
dc5fc579b   Arnaldo Carvalho de Melo   [NETLINK]: Use nl...
20
  #include <net/netlink.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
21
  #include <net/pkt_sched.h>
cc7ec456f   Eric Dumazet   net_sched: cleanups
22
  struct prio_sched_data {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
23
24
25
26
27
28
29
30
31
32
33
34
35
  	int bands;
  	struct tcf_proto *filter_list;
  	u8  prio2band[TC_PRIO_MAX+1];
  	struct Qdisc *queues[TCQ_PRIO_BANDS];
  };
  
  
  static struct Qdisc *
  prio_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr)
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
  	u32 band = skb->priority;
  	struct tcf_result res;
bdba91ec7   Patrick McHardy   [NET_SCHED]: Fix ...
36
  	int err;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
37

c27f339af   Jarek Poplawski   net_sched: Add qd...
38
  	*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
39
  	if (TC_H_MAJ(skb->priority) != sch->handle) {
bdba91ec7   Patrick McHardy   [NET_SCHED]: Fix ...
40
  		err = tc_classify(skb, q->filter_list, &res);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
41
  #ifdef CONFIG_NET_CLS_ACT
dbaaa07a6   Lucas Nussbaum   [NET_SCHED] sch_p...
42
  		switch (err) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
43
44
  		case TC_ACT_STOLEN:
  		case TC_ACT_QUEUED:
378a2f090   Jarek Poplawski   net_sched: Add qd...
45
  			*qerr = NET_XMIT_SUCCESS | __NET_XMIT_STOLEN;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
46
47
  		case TC_ACT_SHOT:
  			return NULL;
3ff50b799   Stephen Hemminger   [NET]: cleanup ex...
48
  		}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
49
  #endif
bdba91ec7   Patrick McHardy   [NET_SCHED]: Fix ...
50
  		if (!q->filter_list || err < 0) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
51
52
  			if (TC_H_MAJ(band))
  				band = 0;
cc7ec456f   Eric Dumazet   net_sched: cleanups
53
  			return q->queues[q->prio2band[band & TC_PRIO_MAX]];
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
54
55
56
57
  		}
  		band = res.classid;
  	}
  	band = TC_H_MIN(band) - 1;
3e5c2d3bd   Jamal Hadi Salim   [NET_SCHED]: prio...
58
  	if (band >= q->bands)
1d8ae3fde   David S. Miller   pkt_sched: Remove...
59
  		return q->queues[q->prio2band[0]];
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
60
61
62
63
64
65
66
67
68
69
70
71
  	return q->queues[band];
  }
  
  static int
  prio_enqueue(struct sk_buff *skb, struct Qdisc *sch)
  {
  	struct Qdisc *qdisc;
  	int ret;
  
  	qdisc = prio_classify(skb, sch, &ret);
  #ifdef CONFIG_NET_CLS_ACT
  	if (qdisc == NULL) {
29f1df6cc   Jamal Hadi Salim   [PKT_SCHED]: Fix ...
72

c27f339af   Jarek Poplawski   net_sched: Add qd...
73
  		if (ret & __NET_XMIT_BYPASS)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
74
75
76
77
78
  			sch->qstats.drops++;
  		kfree_skb(skb);
  		return ret;
  	}
  #endif
5f86173bd   Jussi Kivilinna   net_sched: Add qd...
79
80
  	ret = qdisc_enqueue(skb, qdisc);
  	if (ret == NET_XMIT_SUCCESS) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
81
82
83
  		sch->q.qlen++;
  		return NET_XMIT_SUCCESS;
  	}
378a2f090   Jarek Poplawski   net_sched: Add qd...
84
85
  	if (net_xmit_drop_count(ret))
  		sch->qstats.drops++;
10297b993   YOSHIFUJI Hideaki   [NET] SCHED: Fix ...
86
  	return ret;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
87
  }
48a8f519e   Patrick McHardy   pkt_sched: Add ->...
88
89
90
91
92
93
94
95
96
97
98
99
100
  static struct sk_buff *prio_peek(struct Qdisc *sch)
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
  	int prio;
  
  	for (prio = 0; prio < q->bands; prio++) {
  		struct Qdisc *qdisc = q->queues[prio];
  		struct sk_buff *skb = qdisc->ops->peek(qdisc);
  		if (skb)
  			return skb;
  	}
  	return NULL;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
101

cc7ec456f   Eric Dumazet   net_sched: cleanups
102
  static struct sk_buff *prio_dequeue(struct Qdisc *sch)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
103
  {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
104
105
  	struct prio_sched_data *q = qdisc_priv(sch);
  	int prio;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
106
107
  
  	for (prio = 0; prio < q->bands; prio++) {
1d8ae3fde   David S. Miller   pkt_sched: Remove...
108
  		struct Qdisc *qdisc = q->queues[prio];
3557619f0   Florian Westphal   net_sched: prio: ...
109
  		struct sk_buff *skb = qdisc_dequeue_peeked(qdisc);
1d8ae3fde   David S. Miller   pkt_sched: Remove...
110
  		if (skb) {
9190b3b32   Eric Dumazet   net_sched: accura...
111
  			qdisc_bstats_update(sch, skb);
1d8ae3fde   David S. Miller   pkt_sched: Remove...
112
113
  			sch->q.qlen--;
  			return skb;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
114
115
116
117
118
  		}
  	}
  	return NULL;
  
  }
cc7ec456f   Eric Dumazet   net_sched: cleanups
119
  static unsigned int prio_drop(struct Qdisc *sch)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
120
121
122
123
124
125
126
127
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
  	int prio;
  	unsigned int len;
  	struct Qdisc *qdisc;
  
  	for (prio = q->bands-1; prio >= 0; prio--) {
  		qdisc = q->queues[prio];
6d037a26f   Patrick McHardy   [PKT_SCHED]: Qdis...
128
  		if (qdisc->ops->drop && (len = qdisc->ops->drop(qdisc)) != 0) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
129
130
131
132
133
134
135
136
137
  			sch->q.qlen--;
  			return len;
  		}
  	}
  	return 0;
  }
  
  
  static void
cc7ec456f   Eric Dumazet   net_sched: cleanups
138
  prio_reset(struct Qdisc *sch)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
139
140
141
  {
  	int prio;
  	struct prio_sched_data *q = qdisc_priv(sch);
cc7ec456f   Eric Dumazet   net_sched: cleanups
142
  	for (prio = 0; prio < q->bands; prio++)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
143
144
145
146
147
  		qdisc_reset(q->queues[prio]);
  	sch->q.qlen = 0;
  }
  
  static void
cc7ec456f   Eric Dumazet   net_sched: cleanups
148
  prio_destroy(struct Qdisc *sch)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
149
150
151
  {
  	int prio;
  	struct prio_sched_data *q = qdisc_priv(sch);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
152

ff31ab56c   Patrick McHardy   net-sched: change...
153
  	tcf_destroy_chain(&q->filter_list);
cc7ec456f   Eric Dumazet   net_sched: cleanups
154
  	for (prio = 0; prio < q->bands; prio++)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
155
156
  		qdisc_destroy(q->queues[prio]);
  }
1e90474c3   Patrick McHardy   [NET_SCHED]: Conv...
157
  static int prio_tune(struct Qdisc *sch, struct nlattr *opt)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
158
159
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
d62733c8e   Peter P Waskiewicz Jr   [SCHED]: Qdisc ch...
160
  	struct tc_prio_qopt *qopt;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
161
  	int i;
1d8ae3fde   David S. Miller   pkt_sched: Remove...
162
163
164
  	if (nla_len(opt) < sizeof(*qopt))
  		return -EINVAL;
  	qopt = nla_data(opt);
d62733c8e   Peter P Waskiewicz Jr   [SCHED]: Qdisc ch...
165

1d8ae3fde   David S. Miller   pkt_sched: Remove...
166
  	if (qopt->bands > TCQ_PRIO_BANDS || qopt->bands < 2)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
167
  		return -EINVAL;
cc7ec456f   Eric Dumazet   net_sched: cleanups
168
  	for (i = 0; i <= TC_PRIO_MAX; i++) {
1d8ae3fde   David S. Miller   pkt_sched: Remove...
169
  		if (qopt->priomap[i] >= qopt->bands)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
170
171
172
173
  			return -EINVAL;
  	}
  
  	sch_tree_lock(sch);
1d8ae3fde   David S. Miller   pkt_sched: Remove...
174
  	q->bands = qopt->bands;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
175
  	memcpy(q->prio2band, qopt->priomap, TC_PRIO_MAX+1);
cc7ec456f   Eric Dumazet   net_sched: cleanups
176
  	for (i = q->bands; i < TCQ_PRIO_BANDS; i++) {
b94c8afcb   Patrick McHardy   pkt_sched: remove...
177
178
  		struct Qdisc *child = q->queues[i];
  		q->queues[i] = &noop_qdisc;
5e50da01d   Patrick McHardy   [NET_SCHED]: Fix ...
179
180
  		if (child != &noop_qdisc) {
  			qdisc_tree_decrease_qlen(child, child->q.qlen);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
181
  			qdisc_destroy(child);
5e50da01d   Patrick McHardy   [NET_SCHED]: Fix ...
182
  		}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
183
184
  	}
  	sch_tree_unlock(sch);
cc7ec456f   Eric Dumazet   net_sched: cleanups
185
  	for (i = 0; i < q->bands; i++) {
dd914b408   Amnon Aaronsohn   [PKT_SCHED] sch_p...
186
  		if (q->queues[i] == &noop_qdisc) {
b94c8afcb   Patrick McHardy   pkt_sched: remove...
187
  			struct Qdisc *child, *old;
cc7ec456f   Eric Dumazet   net_sched: cleanups
188

3511c9132   Changli Gao   net_sched: remove...
189
  			child = qdisc_create_dflt(sch->dev_queue,
bb949fbd1   David S. Miller   netdev: Create ne...
190
  						  &pfifo_qdisc_ops,
9f9afec48   Patrick McHardy   [NET_SCHED]: Set ...
191
  						  TC_H_MAKE(sch->handle, i + 1));
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
192
193
  			if (child) {
  				sch_tree_lock(sch);
b94c8afcb   Patrick McHardy   pkt_sched: remove...
194
195
  				old = q->queues[i];
  				q->queues[i] = child;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
196

b94c8afcb   Patrick McHardy   pkt_sched: remove...
197
198
199
200
  				if (old != &noop_qdisc) {
  					qdisc_tree_decrease_qlen(old,
  								 old->q.qlen);
  					qdisc_destroy(old);
5e50da01d   Patrick McHardy   [NET_SCHED]: Fix ...
201
  				}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
202
203
204
205
206
207
  				sch_tree_unlock(sch);
  			}
  		}
  	}
  	return 0;
  }
1e90474c3   Patrick McHardy   [NET_SCHED]: Conv...
208
  static int prio_init(struct Qdisc *sch, struct nlattr *opt)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
209
210
211
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
  	int i;
cc7ec456f   Eric Dumazet   net_sched: cleanups
212
  	for (i = 0; i < TCQ_PRIO_BANDS; i++)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
213
214
215
216
217
218
  		q->queues[i] = &noop_qdisc;
  
  	if (opt == NULL) {
  		return -EINVAL;
  	} else {
  		int err;
cc7ec456f   Eric Dumazet   net_sched: cleanups
219
  		if ((err = prio_tune(sch, opt)) != 0)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
220
221
222
223
224
225
226
227
  			return err;
  	}
  	return 0;
  }
  
  static int prio_dump(struct Qdisc *sch, struct sk_buff *skb)
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
27a884dc3   Arnaldo Carvalho de Melo   [SK_BUFF]: Conver...
228
  	unsigned char *b = skb_tail_pointer(skb);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
229
230
231
  	struct tc_prio_qopt opt;
  
  	opt.bands = q->bands;
cc7ec456f   Eric Dumazet   net_sched: cleanups
232
  	memcpy(&opt.priomap, q->prio2band, TC_PRIO_MAX + 1);
d62733c8e   Peter P Waskiewicz Jr   [SCHED]: Qdisc ch...
233

2c10b32bf   Thomas Graf   netlink: Remove c...
234
  	NLA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt);
d62733c8e   Peter P Waskiewicz Jr   [SCHED]: Qdisc ch...
235

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
236
  	return skb->len;
1e90474c3   Patrick McHardy   [NET_SCHED]: Conv...
237
  nla_put_failure:
dc5fc579b   Arnaldo Carvalho de Melo   [NETLINK]: Use nl...
238
  	nlmsg_trim(skb, b);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
239
240
241
242
243
244
245
246
  	return -1;
  }
  
  static int prio_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
  		      struct Qdisc **old)
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
  	unsigned long band = arg - 1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
247
248
249
250
251
252
  	if (new == NULL)
  		new = &noop_qdisc;
  
  	sch_tree_lock(sch);
  	*old = q->queues[band];
  	q->queues[band] = new;
5e50da01d   Patrick McHardy   [NET_SCHED]: Fix ...
253
  	qdisc_tree_decrease_qlen(*old, (*old)->q.qlen);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
254
255
256
257
258
259
260
261
262
263
264
  	qdisc_reset(*old);
  	sch_tree_unlock(sch);
  
  	return 0;
  }
  
  static struct Qdisc *
  prio_leaf(struct Qdisc *sch, unsigned long arg)
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
  	unsigned long band = arg - 1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
  	return q->queues[band];
  }
  
  static unsigned long prio_get(struct Qdisc *sch, u32 classid)
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
  	unsigned long band = TC_H_MIN(classid);
  
  	if (band - 1 >= q->bands)
  		return 0;
  	return band;
  }
  
  static unsigned long prio_bind(struct Qdisc *sch, unsigned long parent, u32 classid)
  {
  	return prio_get(sch, classid);
  }
  
  
  static void prio_put(struct Qdisc *q, unsigned long cl)
  {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
286
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
287
288
289
290
  static int prio_dump_class(struct Qdisc *sch, unsigned long cl, struct sk_buff *skb,
  			   struct tcmsg *tcm)
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
291
  	tcm->tcm_handle |= TC_H_MIN(cl);
5b9a9ccfa   Patrick McHardy   net_sched: remove...
292
  	tcm->tcm_info = q->queues[cl-1]->handle;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
293
294
  	return 0;
  }
2cf6c36cb   Jarek Poplawski   [NET_SCHED] sch_p...
295
296
297
298
299
300
301
  static int prio_dump_class_stats(struct Qdisc *sch, unsigned long cl,
  				 struct gnet_dump *d)
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
  	struct Qdisc *cl_q;
  
  	cl_q = q->queues[cl - 1];
a19d21584   Jarek Poplawski   pkt_sched: Fix qs...
302
  	cl_q->qstats.qlen = cl_q->q.qlen;
2cf6c36cb   Jarek Poplawski   [NET_SCHED] sch_p...
303
304
305
306
307
308
  	if (gnet_stats_copy_basic(d, &cl_q->bstats) < 0 ||
  	    gnet_stats_copy_queue(d, &cl_q->qstats) < 0)
  		return -1;
  
  	return 0;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
309
310
311
312
313
314
315
316
317
318
319
320
321
  static void prio_walk(struct Qdisc *sch, struct qdisc_walker *arg)
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
  	int prio;
  
  	if (arg->stop)
  		return;
  
  	for (prio = 0; prio < q->bands; prio++) {
  		if (arg->count < arg->skip) {
  			arg->count++;
  			continue;
  		}
cc7ec456f   Eric Dumazet   net_sched: cleanups
322
  		if (arg->fn(sch, prio + 1, arg) < 0) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
323
324
325
326
327
328
  			arg->stop = 1;
  			break;
  		}
  		arg->count++;
  	}
  }
cc7ec456f   Eric Dumazet   net_sched: cleanups
329
  static struct tcf_proto **prio_find_tcf(struct Qdisc *sch, unsigned long cl)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
330
331
332
333
334
335
336
  {
  	struct prio_sched_data *q = qdisc_priv(sch);
  
  	if (cl)
  		return NULL;
  	return &q->filter_list;
  }
20fea08b5   Eric Dumazet   [NET]: Move Qdisc...
337
  static const struct Qdisc_class_ops prio_class_ops = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
338
339
340
341
  	.graft		=	prio_graft,
  	.leaf		=	prio_leaf,
  	.get		=	prio_get,
  	.put		=	prio_put,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
342
343
344
345
346
  	.walk		=	prio_walk,
  	.tcf_chain	=	prio_find_tcf,
  	.bind_tcf	=	prio_bind,
  	.unbind_tcf	=	prio_put,
  	.dump		=	prio_dump_class,
2cf6c36cb   Jarek Poplawski   [NET_SCHED] sch_p...
347
  	.dump_stats	=	prio_dump_class_stats,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
348
  };
20fea08b5   Eric Dumazet   [NET]: Move Qdisc...
349
  static struct Qdisc_ops prio_qdisc_ops __read_mostly = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
350
351
352
353
354
355
  	.next		=	NULL,
  	.cl_ops		=	&prio_class_ops,
  	.id		=	"prio",
  	.priv_size	=	sizeof(struct prio_sched_data),
  	.enqueue	=	prio_enqueue,
  	.dequeue	=	prio_dequeue,
48a8f519e   Patrick McHardy   pkt_sched: Add ->...
356
  	.peek		=	prio_peek,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
357
358
359
360
361
362
363
364
365
366
367
  	.drop		=	prio_drop,
  	.init		=	prio_init,
  	.reset		=	prio_reset,
  	.destroy	=	prio_destroy,
  	.change		=	prio_tune,
  	.dump		=	prio_dump,
  	.owner		=	THIS_MODULE,
  };
  
  static int __init prio_module_init(void)
  {
1d8ae3fde   David S. Miller   pkt_sched: Remove...
368
  	return register_qdisc(&prio_qdisc_ops);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
369
  }
10297b993   YOSHIFUJI Hideaki   [NET] SCHED: Fix ...
370
  static void __exit prio_module_exit(void)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
371
372
373
374
375
376
377
378
  {
  	unregister_qdisc(&prio_qdisc_ops);
  }
  
  module_init(prio_module_init)
  module_exit(prio_module_exit)
  
  MODULE_LICENSE("GPL");