Home | History | Annotate | Download | only in Modules
      1 /* pg3.c: Packet Generator for packet performance testing.
      2  *
      3  * Copyright 2001 by Robert Olsson <robert.olsson (at) its.uu.se>
      4  *                                 Uppsala University, Sweden
      5  *
      6  * This program is free software; you can redistribute it and/or modify
      7  * it under the terms of the GNU General Public License as published by
      8  * the Free Software Foundation; either version 2 of the License, or
      9  * (at your option) any later version.
     10  *
     11  *
     12  *
     13  */
     14 
     15 /*
     16 
     17 A tool for loading a network with a preconfigurated packets. The tool is
     18 implemented as a linux module. Parameters as output device IPG interpacket
     19 packet, number of packets can be configured. pg uses already intalled
     20 device driver output routine.
     21 
     22 
     23 Additional hacking by:
     24 
     25 Jens.Laas (at) data.slu.se
     26 Improved by ANK. 010120.
     27 Improved by ANK even more. 010212.
     28 MAC address typo fixed. 010417 --ro
     29 
     30 
     31 TODO:
     32 * could release kernel lock yet.
     33 
     34 
     35 HOWTO:
     36 
     37 1. Compile module pg3.o and install it in the place where modprobe may find it.
     38 2. Cut script "ipg" (see below).
     39 3. Edit script to set preferred device and destination IP address.
     40 4. . ipg
     41 5. After this two commands are defined:
     42    A. "pg" to start generator and to get results.
     43    B. "pgset" to change generator parameters. F.e.
     44       pgset "pkt_size 9014"   sets packet size to 9014
     45       pgset "frags 5"         packet will consist of 5 fragments
     46       pgset "count 200000"    sets number of packets to send
     47       pgset "ipg 5000"        sets artificial gap inserted between packets
     48 			      to 5000 nanoseconds
     49       pgset "dst 10.0.0.1"    sets IP destination address
     50 			      (BEWARE! This generator is very aggressive!)
     51       pgset "dstmac 00:00:00:00:00:00"    sets MAC destination address
     52       pgset stop    	      aborts injection
     53 
     54   Also, ^C aborts generator.
     55 
     56 ---- cut here
     57 
     58 #! /bin/sh
     59 
     60 modprobe pg3.o
     61 
     62 function pgset() {
     63     local result
     64 
     65     echo $1 > /proc/net/pg
     66 
     67     result=`cat /proc/net/pg | fgrep "Result: OK:"`
     68     if [ "$result" = "" ]; then
     69 	 cat /proc/net/pg | fgrep Result:
     70     fi
     71 }
     72 
     73 function pg() {
     74     echo inject > /proc/net/pg
     75     cat /proc/net/pg
     76 }
     77 
     78 pgset "odev eth0"
     79 pgset "dst 0.0.0.0"
     80 
     81 ---- cut here
     82 */
     83 
     84 #include <linux/module.h>
     85 #include <linux/kernel.h>
     86 #include <linux/sched.h>
     87 #include <linux/types.h>
     88 #include <linux/string.h>
     89 #include <linux/ptrace.h>
     90 #include <linux/errno.h>
     91 #include <linux/ioport.h>
     92 #include <linux/malloc.h>
     93 #include <linux/interrupt.h>
     94 #include <linux/pci.h>
     95 #include <linux/delay.h>
     96 #include <linux/init.h>
     97 #include <linux/inet.h>
     98 #include <asm/byteorder.h>
     99 #include <asm/bitops.h>
    100 #include <asm/io.h>
    101 #include <asm/dma.h>
    102 
    103 #include <linux/in.h>
    104 #include <linux/ip.h>
    105 #include <linux/udp.h>
    106 #include <linux/skbuff.h>
    107 #include <linux/netdevice.h>
    108 #include <linux/inetdevice.h>
    109 #include <linux/rtnetlink.h>
    110 #include <linux/proc_fs.h>
    111 #include <linux/if_arp.h>
    112 #include <net/checksum.h>
    113 
    114 static char version[] __initdata =
    115   "pg3.c: v1.0 010812: Packet Generator for packet performance testing.\n";
    116 
    117 
    118 
    119 /* Parameters */
    120 
    121 char pg_outdev[32], pg_dst[32];
    122 int pkt_size=ETH_ZLEN;
    123 int nfrags=0;
    124 __u32 pg_count = 100000;  /* Default No packets to send */
    125 __u32 pg_ipg = 0;  /* Default Interpacket gap in nsec */
    126 
    127 /* Globar vars */
    128 
    129 int debug;
    130 int forced_stop;
    131 int pg_cpu_speed;
    132 int pg_busy;
    133 
    134 static __u8 hh[14] = {
    135     0x00, 0x80, 0xC8, 0x79, 0xB3, 0xCB,
    136 
    137     /* We fill in SRC address later */
    138     0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    139     0x08, 0x00
    140 };
    141 
    142 unsigned char *pg_dstmac = hh;
    143 char pg_result[512];
    144 
    145 
    146 static struct net_device *pg_setup_inject(u32 *saddrp)
    147 {
    148 	int p1, p2;
    149 	struct net_device *odev;
    150 	u32 saddr;
    151 
    152 	rtnl_lock();
    153 	odev = __dev_get_by_name(pg_outdev);
    154 	if (!odev) {
    155 		sprintf(pg_result, "No such netdevice: \"%s\"", pg_outdev);
    156 		goto out_unlock;
    157 	}
    158 
    159 	if (odev->type != ARPHRD_ETHER) {
    160 		sprintf(pg_result, "Not ethernet device: \"%s\"", pg_outdev);
    161 		goto out_unlock;
    162 	}
    163 
    164 	if (!netif_running(odev)) {
    165 		sprintf(pg_result, "Device is down: \"%s\"", pg_outdev);
    166 		goto out_unlock;
    167 	}
    168 
    169 	for(p1=6,p2=0; p1 < odev->addr_len+6;p1++)
    170 		hh[p1]=odev->dev_addr[p2++];
    171 
    172 	saddr = 0;
    173 	if (odev->ip_ptr) {
    174 		struct in_device *in_dev = odev->ip_ptr;
    175 
    176 		if (in_dev->ifa_list)
    177 			saddr = in_dev->ifa_list->ifa_address;
    178 	}
    179 	atomic_inc(&odev->refcnt);
    180 	rtnl_unlock();
    181 
    182 	*saddrp = saddr;
    183 	return odev;
    184 
    185 out_unlock:
    186 	rtnl_unlock();
    187 	return NULL;
    188 }
    189 
    190 
    191 u32 idle_acc_lo, idle_acc_hi;
    192 
    193 void nanospin(int pg_ipg)
    194 {
    195 	u32 idle_start, idle;
    196 
    197 	idle_start = get_cycles();
    198 
    199 	for (;;) {
    200 		barrier();
    201 		idle = get_cycles() - idle_start;
    202 		if (idle*1000 >= pg_ipg*pg_cpu_speed)
    203 			break;
    204 	}
    205 	idle_acc_lo += idle;
    206 	if (idle_acc_lo < idle)
    207 		idle_acc_hi++;
    208 }
    209 
    210 int calc_mhz(void)
    211 {
    212 	struct timeval start, stop;
    213 	u32 start_s, elapsed;
    214 
    215 	do_gettimeofday(&start);
    216 	start_s = get_cycles();
    217 	do {
    218 		barrier();
    219 		elapsed = get_cycles() - start_s;
    220 		if (elapsed == 0)
    221 			return 0;
    222 	} while (elapsed < 1000*50000);
    223 	do_gettimeofday(&stop);
    224 	return elapsed/(stop.tv_usec-start.tv_usec+1000000*(stop.tv_sec-start.tv_sec));
    225 }
    226 
    227 static void cycles_calibrate(void)
    228 {
    229 	int i;
    230 
    231 	for (i=0; i<3; i++) {
    232 		int res = calc_mhz();
    233 		if (res > pg_cpu_speed)
    234 			pg_cpu_speed = res;
    235 	}
    236 }
    237 
    238 struct sk_buff *
    239 fill_packet(struct net_device *odev, __u32 saddr)
    240 {
    241 	struct sk_buff *skb;
    242 	__u8 *eth;
    243 	struct udphdr *udph;
    244 	int datalen, iplen;
    245 	struct iphdr *iph;
    246 
    247 	skb = alloc_skb(pkt_size+64+16, GFP_ATOMIC);
    248 	if (!skb) {
    249 		sprintf(pg_result, "No memory");
    250 		return NULL;
    251 	}
    252 
    253 	skb_reserve(skb, 16);
    254 
    255 	/*  Reserve for ethernet and IP header  */
    256 	eth = (__u8 *) skb_push(skb, 14);
    257 	iph = (struct iphdr*)skb_put(skb, sizeof( struct iphdr));
    258 	udph = (struct udphdr*)skb_put(skb, sizeof( struct udphdr));
    259 
    260 	/*  Copy the ethernet header  */
    261 	memcpy(eth, hh, 14);
    262 
    263 	datalen = pkt_size-14-20-8; /* Eth + IPh + UDPh */
    264 	if (datalen < 0)
    265 		datalen = 0;
    266 
    267 	udph->source= htons(9);
    268 	udph->dest= htons(9);
    269 	udph->len= htons(datalen+8); /* DATA + udphdr */
    270 	udph->check=0;  /* No checksum */
    271 
    272 	iph->ihl=5;
    273 	iph->version=4;
    274 	iph->ttl=3;
    275 	iph->tos=0;
    276 	iph->protocol = IPPROTO_UDP; /* UDP */
    277 	iph->saddr =  saddr;
    278 	iph->daddr =  in_aton(pg_dst);
    279 	iph->frag_off = 0;
    280 	iplen = 20 + 8 + datalen;
    281 	iph->tot_len = htons(iplen);
    282 	iph->check = 0;
    283 	iph->check = ip_fast_csum((void *)iph, iph->ihl);
    284 	skb->protocol = __constant_htons(ETH_P_IP);
    285 	skb->mac.raw = ((u8*)iph) - 14;
    286 	skb->dev = odev;
    287 	skb->pkt_type = PACKET_HOST;
    288 
    289 	if (nfrags<=0) {
    290 		skb_put(skb, datalen);
    291 	} else {
    292 		int frags = nfrags;
    293 		int i;
    294 
    295 		if (frags > MAX_SKB_FRAGS)
    296 			frags = MAX_SKB_FRAGS;
    297 		if (datalen > frags*PAGE_SIZE) {
    298 			skb_put(skb, datalen-frags*PAGE_SIZE);
    299 			datalen = frags*PAGE_SIZE;
    300 		}
    301 
    302 		i = 0;
    303 		while (datalen > 0) {
    304 			struct page *page = alloc_pages(GFP_KERNEL, 0);
    305 			skb_shinfo(skb)->frags[i].page = page;
    306 			skb_shinfo(skb)->frags[i].page_offset = 0;
    307 			skb_shinfo(skb)->frags[i].size = (datalen < PAGE_SIZE ? datalen : PAGE_SIZE);
    308 			datalen -= skb_shinfo(skb)->frags[i].size;
    309 			skb->len += skb_shinfo(skb)->frags[i].size;
    310 			skb->data_len += skb_shinfo(skb)->frags[i].size;
    311 			i++;
    312 			skb_shinfo(skb)->nr_frags = i;
    313 		}
    314 
    315 		while (i < frags) {
    316 			int rem;
    317 
    318 			if (i == 0)
    319 				break;
    320 
    321 			rem = skb_shinfo(skb)->frags[i-1].size/2;
    322 			if (rem == 0)
    323 				break;
    324 
    325 			skb_shinfo(skb)->frags[i-1].size -= rem;
    326 
    327 			skb_shinfo(skb)->frags[i] = skb_shinfo(skb)->frags[i-1];
    328 			get_page(skb_shinfo(skb)->frags[i].page);
    329 			skb_shinfo(skb)->frags[i].page = skb_shinfo(skb)->frags[i-1].page;
    330 			skb_shinfo(skb)->frags[i].page_offset += skb_shinfo(skb)->frags[i-1].size;
    331 			skb_shinfo(skb)->frags[i].size = rem;
    332 			i++;
    333 			skb_shinfo(skb)->nr_frags = i;
    334 		}
    335 	}
    336 
    337 	return skb;
    338 }
    339 
    340 
    341 static void pg_inject(void)
    342 {
    343 	u32 saddr;
    344 	struct net_device *odev;
    345 	struct sk_buff *skb;
    346 	struct timeval start, stop;
    347 	u32 total, idle;
    348 	int pc, lcount;
    349 
    350 	odev = pg_setup_inject(&saddr);
    351 	if (!odev)
    352 		return;
    353 
    354 	skb = fill_packet(odev, saddr);
    355 	if (skb == NULL)
    356 		goto out_reldev;
    357 
    358 	forced_stop = 0;
    359 	idle_acc_hi = 0;
    360 	idle_acc_lo = 0;
    361 	pc = 0;
    362 	lcount = pg_count;
    363 	do_gettimeofday(&start);
    364 
    365 	for(;;) {
    366 		spin_lock_bh(&odev->xmit_lock);
    367 		atomic_inc(&skb->users);
    368 		if (!netif_queue_stopped(odev)) {
    369 			if (odev->hard_start_xmit(skb, odev)) {
    370 				kfree_skb(skb);
    371 				if (net_ratelimit())
    372 					printk(KERN_INFO "Hard xmit error\n");
    373 			}
    374 			pc++;
    375 		} else {
    376 			kfree_skb(skb);
    377 		}
    378 		spin_unlock_bh(&odev->xmit_lock);
    379 
    380 		if (pg_ipg)
    381 			nanospin(pg_ipg);
    382 		if (forced_stop)
    383 			goto out_intr;
    384 		if (signal_pending(current))
    385 			goto out_intr;
    386 
    387 		if (--lcount == 0) {
    388 			if (atomic_read(&skb->users) != 1) {
    389 				u32 idle_start, idle;
    390 
    391 				idle_start = get_cycles();
    392 				while (atomic_read(&skb->users) != 1) {
    393 					if (signal_pending(current))
    394 						goto out_intr;
    395 					schedule();
    396 				}
    397 				idle = get_cycles() - idle_start;
    398 				idle_acc_lo += idle;
    399 				if (idle_acc_lo < idle)
    400 					idle_acc_hi++;
    401 			}
    402 			break;
    403 		}
    404 
    405 		if (netif_queue_stopped(odev) || current->need_resched) {
    406 			u32 idle_start, idle;
    407 
    408 			idle_start = get_cycles();
    409 			do {
    410 				if (signal_pending(current))
    411 					goto out_intr;
    412 				if (!netif_running(odev))
    413 					goto out_intr;
    414 				if (current->need_resched)
    415 					schedule();
    416 				else
    417 					do_softirq();
    418 			} while (netif_queue_stopped(odev));
    419 			idle = get_cycles() - idle_start;
    420 			idle_acc_lo += idle;
    421 			if (idle_acc_lo < idle)
    422 				idle_acc_hi++;
    423 		}
    424 	}
    425 
    426 	do_gettimeofday(&stop);
    427 
    428 	total = (stop.tv_sec - start.tv_sec)*1000000 +
    429 		stop.tv_usec - start.tv_usec;
    430 
    431 	idle = (((idle_acc_hi<<20)/pg_cpu_speed)<<12)+idle_acc_lo/pg_cpu_speed;
    432 
    433 	if (1) {
    434 		char *p = pg_result;
    435 
    436 		p += sprintf(p, "OK: %u(c%u+d%u) usec, %u (%dbyte,%dfrags) %upps %uMB/sec",
    437 			     total, total-idle, idle,
    438 			     pc, skb->len, skb_shinfo(skb)->nr_frags,
    439 			     ((pc*1000)/(total/1000)),
    440 			     (((pc*1000)/(total/1000))*pkt_size)/1024/1024
    441 			     );
    442 	}
    443 
    444 out_relskb:
    445 	kfree_skb(skb);
    446 out_reldev:
    447 	dev_put(odev);
    448 	return;
    449 
    450 out_intr:
    451 	sprintf(pg_result, "Interrupted");
    452 	goto out_relskb;
    453 }
    454 
    455 /* proc/net/pg */
    456 
    457 static struct proc_dir_entry *pg_proc_ent = 0;
    458 static struct proc_dir_entry *pg_busy_proc_ent = 0;
    459 
    460 int proc_pg_busy_read(char *buf , char **start, off_t offset,
    461 		      int len, int *eof, void *data)
    462 {
    463 	char *p;
    464 
    465 	p = buf;
    466 	p += sprintf(p, "%d\n", pg_busy);
    467 	*eof = 1;
    468 
    469 	return p-buf;
    470 }
    471 
    472 int proc_pg_read(char *buf , char **start, off_t offset,
    473 		 int len, int *eof, void *data)
    474 {
    475 	char *p;
    476 	int i;
    477 
    478 	p = buf;
    479 	p += sprintf(p, "Params: count=%u pkt_size=%u frags %d ipg %u odev \"%s\" dst %s dstmac ",
    480 		     pg_count, pkt_size, nfrags, pg_ipg,
    481 		     pg_outdev, pg_dst);
    482 	for(i=0;i<6;i++)
    483 		p += sprintf(p, "%02X%s", pg_dstmac[i], i == 5 ? "\n" : ":");
    484 
    485 	if(pg_result[0])
    486 		p += sprintf(p, "Result: %s\n", pg_result);
    487 	else
    488 		p += sprintf(p, "Result: Idle\n");
    489 	*eof = 1;
    490 	return p-buf;
    491 }
    492 
    493 int count_trail_chars(const char *buffer, unsigned int maxlen)
    494 {
    495 	int i;
    496 
    497 	for(i=0; i<maxlen;i++) {
    498 		switch(buffer[i]) {
    499 		case '\"':
    500 		case '\n':
    501 		case '\r':
    502 		case '\t':
    503 		case ' ':
    504 		case '=':
    505 			break;
    506 		default:
    507 			goto done;
    508 		}
    509 	}
    510 done:
    511 	return i;
    512 }
    513 
    514 unsigned long num_arg(const char *buffer, unsigned long maxlen,
    515 		      unsigned long *num)
    516 {
    517 	int i=0;
    518 	*num = 0;
    519 
    520 	for(; i<maxlen;i++) {
    521 		if( (buffer[i] >= '0') && (buffer[i] <= '9')) {
    522 			*num *= 10;
    523 			*num += buffer[i] -'0';
    524 		}
    525 		else
    526 			break;
    527 	}
    528 	return i;
    529 }
    530 
    531 int strn_len(const char *buffer, unsigned int maxlen)
    532 {
    533 	int i=0;
    534 
    535 	for(; i<maxlen;i++)
    536 		switch(buffer[i]) {
    537 		case '\"':
    538 		case '\n':
    539 		case '\r':
    540 		case '\t':
    541 		case ' ':
    542 			goto done_str;
    543 		default:
    544 		}
    545 done_str:
    546 	return i;
    547 }
    548 
    549 int proc_pg_write(struct file *file, const char *buffer,
    550 		     unsigned long count, void *data)
    551 {
    552 	int i=0, max, len;
    553 	char name[16], valstr[32];
    554 	unsigned long value = 0;
    555 
    556 	if (count < 1) {
    557 		sprintf(pg_result, "Wrong command format");
    558 		return -EINVAL;
    559 	}
    560 
    561 	max = count -i;
    562 	i += count_trail_chars(&buffer[i], max);
    563 
    564 	/* Read variable name */
    565 
    566 	len = strn_len(&buffer[i], sizeof(name)-1);
    567 	memset(name, 0, sizeof(name));
    568 	strncpy(name, &buffer[i], len);
    569 	i += len;
    570 
    571 	max = count -i;
    572 	len = count_trail_chars(&buffer[i], max);
    573 	i += len;
    574 
    575 	if (debug)
    576 		printk("pg: %s,%lu\n", name, count);
    577 
    578 	/* Only stop is allowed when we are running */
    579 
    580 	if(!strcmp(name, "stop")) {
    581 		forced_stop=1;
    582 		if (pg_busy)
    583 			strcpy(pg_result, "Stopping");
    584 		return count;
    585 	}
    586 
    587 	if (pg_busy) {
    588 		strcpy(pg_result, "Busy");
    589 		return -EINVAL;
    590 	}
    591 
    592 	if(!strcmp(name, "pkt_size")) {
    593 		len = num_arg(&buffer[i], 10, &value);
    594 		i += len;
    595 		if (value < 14+20+8)
    596 			value = 14+20+8;
    597 		pkt_size = value;
    598 		sprintf(pg_result, "OK: pkt_size=%u", pkt_size);
    599 		return count;
    600 	}
    601 	if(!strcmp(name, "frags")) {
    602 		len = num_arg(&buffer[i], 10, &value);
    603 		i += len;
    604 		nfrags = value;
    605 		sprintf(pg_result, "OK: frags=%u", nfrags);
    606 		return count;
    607 	}
    608 	if(!strcmp(name, "ipg")) {
    609 		len = num_arg(&buffer[i], 10, &value);
    610 		i += len;
    611 		pg_ipg = value;
    612 		sprintf(pg_result, "OK: ipg=%u", pg_ipg);
    613 		return count;
    614 	}
    615 	if(!strcmp(name, "count")) {
    616 		len = num_arg(&buffer[i], 10, &value);
    617 		i += len;
    618 		pg_count = value;
    619 		sprintf(pg_result, "OK: count=%u", pg_count);
    620 		return count;
    621 	}
    622 	if(!strcmp(name, "odev")) {
    623 		len = strn_len(&buffer[i], sizeof(pg_outdev)-1);
    624 		memset(pg_outdev, 0, sizeof(pg_outdev));
    625 		strncpy(pg_outdev, &buffer[i], len);
    626 		i += len;
    627 		sprintf(pg_result, "OK: odev=%s", pg_outdev);
    628 		return count;
    629 	}
    630 	if(!strcmp(name, "dst")) {
    631 		len = strn_len(&buffer[i], sizeof(pg_dst)-1);
    632 		memset(pg_dst, 0, sizeof(pg_dst));
    633 		strncpy(pg_dst, &buffer[i], len);
    634 		if(debug)
    635 			printk("pg: dst set to: %s\n", pg_dst);
    636 		i += len;
    637 		sprintf(pg_result, "OK: dst=%s", pg_dst);
    638 		return count;
    639 	}
    640 	if(!strcmp(name, "dstmac")) {
    641 		char *v = valstr;
    642 		unsigned char *m = pg_dstmac;
    643 
    644 		len = strn_len(&buffer[i], sizeof(valstr)-1);
    645 		memset(valstr, 0, sizeof(valstr));
    646 		strncpy(valstr, &buffer[i], len);
    647 		i += len;
    648 
    649 		for(*m = 0;*v && m < pg_dstmac+6;v++) {
    650 			if(*v >= '0' && *v <= '9') {
    651 				*m *= 16;
    652 				*m += *v - '0';
    653 			}
    654 			if(*v >= 'A' && *v <= 'F') {
    655 				*m *= 16;
    656 				*m += *v - 'A' + 10;
    657 			}
    658 			if(*v >= 'a' && *v <= 'f') {
    659 				*m *= 16;
    660 				*m += *v - 'a' + 10;
    661 			}
    662 			if(*v == ':') {
    663 				m++;
    664 				*m = 0;
    665 			}
    666 		}
    667 		sprintf(pg_result, "OK: dstmac");
    668 		return count;
    669 	}
    670 
    671 	if (!strcmp(name, "inject") || !strcmp(name, "start") ) {
    672 		MOD_INC_USE_COUNT;
    673 		pg_busy = 1;
    674 		strcpy(pg_result, "Starting");
    675 		pg_inject();
    676 		pg_busy = 0;
    677 		MOD_DEC_USE_COUNT;
    678 		return count;
    679 	}
    680 
    681 	sprintf(pg_result, "No such parameter \"%s\"", name);
    682 	return -EINVAL;
    683 }
    684 
    685 static int pg_init(void)
    686 {
    687 	printk(version);
    688 	cycles_calibrate();
    689 	if (pg_cpu_speed == 0) {
    690 		printk("pg3: Error: your machine does not have working cycle counter.\n");
    691 		return -EINVAL;
    692 	}
    693 	if(!pg_proc_ent) {
    694 		pg_proc_ent = create_proc_entry("net/pg", 0600, 0);
    695 		if (pg_proc_ent) {
    696 			pg_proc_ent->read_proc = proc_pg_read;
    697 			pg_proc_ent->write_proc = proc_pg_write;
    698 			pg_proc_ent->data = 0;
    699 		}
    700 		pg_busy_proc_ent = create_proc_entry("net/pg_busy", 0, 0);
    701 		if (pg_busy_proc_ent) {
    702 			pg_busy_proc_ent->read_proc = proc_pg_busy_read;
    703 			pg_busy_proc_ent->data = 0;
    704 		}
    705 	}
    706 	return 0;
    707 }
    708 
    709 void pg_cleanup(void)
    710 {
    711 	if (pg_proc_ent) {
    712 		remove_proc_entry("net/pg", NULL);
    713 		pg_proc_ent = 0;
    714 		remove_proc_entry("net/pg_busy", NULL);
    715 		pg_busy_proc_ent = 0;
    716 	}
    717 }
    718 
    719 module_init(pg_init);
    720 module_exit(pg_cleanup);
    721 
    722 
    723 #if LINUX_VERSION_CODE > 0x20118
    724 MODULE_AUTHOR("Robert Olsson <robert.olsson (at) its.uu.se");
    725 MODULE_DESCRIPTION("Packet Generator tool");
    726 MODULE_PARM(pg_count, "i");
    727 MODULE_PARM(pg_ipg, "i");
    728 MODULE_PARM(pg_cpu_speed, "i");
    729 #endif
    730 
    731 /*
    732  * Local variables:
    733  * compile-command: "gcc -DMODULE -D__KERNEL__ -I/usr/src/linux/include -Wall -Wstrict-prototypes -O2 -c pg3.c"
    734  * End:
    735  */
    736