*
* interruptible_sleep_on_timeout() replaced Nishanth Aravamudan <nacc@us.ibm.com>
* 050103
+ *
+ * MPLS support by Steven Whitehouse <steve@chygwyn.com>
+ *
*/
#include <linux/sys.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/smp_lock.h>
+#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <asm/div64.h> /* do_div */
#include <asm/timex.h>
-#define VERSION "pktgen v2.64: Packet Generator for packet performance testing.\n"
+#define VERSION "pktgen v2.67: Packet Generator for packet performance testing.\n"
/* #define PG_DEBUG(a) a */
#define PG_DEBUG(a)
/* The buckets are exponential in 'width' */
#define LAT_BUCKETS_MAX 32
#define IP_NAME_SZ 32
+#define MAX_MPLS_LABELS 16 /* This is the max label stack depth */
+#define MPLS_STACK_BOTTOM __constant_htonl(0x00000100)
/* Device flag bits */
#define F_IPSRC_RND (1<<0) /* IP-Src Random */
#define F_MACDST_RND (1<<5) /* MAC-Dst Random */
#define F_TXSIZE_RND (1<<6) /* Transmit size is random */
#define F_IPV6 (1<<7) /* Interface in IPV6 Mode */
+#define F_MPLS_RND (1<<8) /* Random MPLS labels */
/* Thread control flag bits */
#define T_TERMINATE (1<<0)
#define T_REMDEVALL (1<<3) /* Remove all devs */
#define T_REMDEV (1<<4) /* Remove one dev */
-/* Locks */
-#define thread_lock() down(&pktgen_sem)
-#define thread_unlock() up(&pktgen_sem)
-
/* If lock -- can be removed after some work */
#define if_lock(t) spin_lock(&(t->if_lock));
#define if_unlock(t) spin_unlock(&(t->if_lock));
char result[512];
struct pktgen_thread *pg_thread; /* the owner */
- struct pktgen_dev *next; /* Used for chaining in the thread's run-queue */
+ struct list_head list; /* Used for chaining in the thread's run-queue */
int running; /* if this changes to false, the test will stop */
__u16 udp_dst_min; /* inclusive, dest UDP port */
__u16 udp_dst_max; /* exclusive, dest UDP port */
+ /* MPLS */
+ unsigned nr_labels; /* Depth of stack, 0 = no MPLS */
+ __be32 labels[MAX_MPLS_LABELS];
+
__u32 src_mac_count; /* How many MACs to iterate through */
__u32 dst_mac_count; /* How many MACs to iterate through */
struct pktgen_thread {
spinlock_t if_lock;
- struct pktgen_dev *if_list; /* All device here */
+ struct list_head if_list; /* All device here */
struct list_head th_list;
int removed;
char name[32];
static int pg_clone_skb_d;
static int debug;
-static DECLARE_MUTEX(pktgen_sem);
+static DEFINE_MUTEX(pktgen_thread_lock);
static LIST_HEAD(pktgen_threads);
static struct notifier_block pktgen_notifier_block = {
pkt_dev->udp_dst_min, pkt_dev->udp_dst_max);
seq_printf(seq,
- " src_mac_count: %d dst_mac_count: %d \n Flags: ",
+ " src_mac_count: %d dst_mac_count: %d\n",
pkt_dev->src_mac_count, pkt_dev->dst_mac_count);
+ if (pkt_dev->nr_labels) {
+ unsigned i;
+ seq_printf(seq, " mpls: ");
+ for(i = 0; i < pkt_dev->nr_labels; i++)
+ seq_printf(seq, "%08x%s", ntohl(pkt_dev->labels[i]),
+ i == pkt_dev->nr_labels-1 ? "\n" : ", ");
+ }
+
+ seq_printf(seq, " Flags: ");
+
if (pkt_dev->flags & F_IPV6)
seq_printf(seq, "IPV6 ");
if (pkt_dev->flags & F_UDPDST_RND)
seq_printf(seq, "UDPDST_RND ");
+ if (pkt_dev->flags & F_MPLS_RND)
+ seq_printf(seq, "MPLS_RND ");
+
if (pkt_dev->flags & F_MACSRC_RND)
seq_printf(seq, "MACSRC_RND ");
return 0;
}
+
+static int hex32_arg(const char __user *user_buffer, __u32 *num)
+{
+ int i = 0;
+ *num = 0;
+
+ for(; i < 8; i++) {
+ char c;
+ *num <<= 4;
+ if (get_user(c, &user_buffer[i]))
+ return -EFAULT;
+ if ((c >= '0') && (c <= '9'))
+ *num |= c - '0';
+ else if ((c >= 'a') && (c <= 'f'))
+ *num |= c - 'a' + 10;
+ else if ((c >= 'A') && (c <= 'F'))
+ *num |= c - 'A' + 10;
+ else
+ break;
+ }
+ return i;
+}
+
static int count_trail_chars(const char __user * user_buffer,
unsigned int maxlen)
{
return i;
}
+static ssize_t get_labels(const char __user *buffer, struct pktgen_dev *pkt_dev)
+{
+ unsigned n = 0;
+ char c;
+ ssize_t i = 0;
+ int len;
+
+ pkt_dev->nr_labels = 0;
+ do {
+ __u32 tmp;
+ len = hex32_arg(&buffer[i], &tmp);
+ if (len <= 0)
+ return len;
+ pkt_dev->labels[n] = htonl(tmp);
+ if (pkt_dev->labels[n] & MPLS_STACK_BOTTOM)
+ pkt_dev->flags |= F_MPLS_RND;
+ i += len;
+ if (get_user(c, &buffer[i]))
+ return -EFAULT;
+ i++;
+ n++;
+ if (n >= MAX_MPLS_LABELS)
+ return -E2BIG;
+ } while(c == ',');
+
+ pkt_dev->nr_labels = n;
+ return i;
+}
+
static ssize_t pktgen_if_write(struct file *file,
const char __user * user_buffer, size_t count,
loff_t * offset)
else if (strcmp(f, "!MACDST_RND") == 0)
pkt_dev->flags &= ~F_MACDST_RND;
+ else if (strcmp(f, "MPLS_RND") == 0)
+ pkt_dev->flags |= F_MPLS_RND;
+
+ else if (strcmp(f, "!MPLS_RND") == 0)
+ pkt_dev->flags &= ~F_MPLS_RND;
+
else {
sprintf(pg_result,
"Flag -:%s:- unknown\nAvailable flags, (prepend ! to un-set flag):\n%s",
return count;
}
+ if (!strcmp(name, "mpls")) {
+ unsigned n, offset;
+ len = get_labels(&user_buffer[i], pkt_dev);
+ if (len < 0) { return len; }
+ i += len;
+ offset = sprintf(pg_result, "OK: mpls=");
+ for(n = 0; n < pkt_dev->nr_labels; n++)
+ offset += sprintf(pg_result + offset,
+ "%08x%s", ntohl(pkt_dev->labels[n]),
+ n == pkt_dev->nr_labels-1 ? "" : ",");
+ return count;
+ }
+
sprintf(pkt_dev->result, "No such parameter \"%s\"", name);
return -EINVAL;
}
static int pktgen_thread_show(struct seq_file *seq, void *v)
{
struct pktgen_thread *t = seq->private;
- struct pktgen_dev *pkt_dev = NULL;
+ struct pktgen_dev *pkt_dev;
BUG_ON(!t);
seq_printf(seq, "Running: ");
if_lock(t);
- for (pkt_dev = t->if_list; pkt_dev; pkt_dev = pkt_dev->next)
+ list_for_each_entry(pkt_dev, &t->if_list, list)
if (pkt_dev->running)
seq_printf(seq, "%s ", pkt_dev->ifname);
seq_printf(seq, "\nStopped: ");
- for (pkt_dev = t->if_list; pkt_dev; pkt_dev = pkt_dev->next)
+ list_for_each_entry(pkt_dev, &t->if_list, list)
if (!pkt_dev->running)
seq_printf(seq, "%s ", pkt_dev->ifname);
if (copy_from_user(f, &user_buffer[i], len))
return -EFAULT;
i += len;
- thread_lock();
+ mutex_lock(&pktgen_thread_lock);
pktgen_add_device(t, f);
- thread_unlock();
+ mutex_unlock(&pktgen_thread_lock);
ret = count;
sprintf(pg_result, "OK: add_device=%s", f);
goto out;
}
if (!strcmp(name, "rem_device_all")) {
- thread_lock();
+ mutex_lock(&pktgen_thread_lock);
t->control |= T_REMDEVALL;
- thread_unlock();
+ mutex_unlock(&pktgen_thread_lock);
schedule_timeout_interruptible(msecs_to_jiffies(125)); /* Propagate thread->control */
ret = count;
sprintf(pg_result, "OK: rem_device_all");
if (!strcmp(name, "max_before_softirq")) {
len = num_arg(&user_buffer[i], 10, &value);
- thread_lock();
+ mutex_lock(&pktgen_thread_lock);
t->max_before_softirq = value;
- thread_unlock();
+ mutex_unlock(&pktgen_thread_lock);
ret = count;
sprintf(pg_result, "OK: max_before_softirq=%lu", value);
goto out;
int i = 0;
int ret = 0;
- thread_lock();
+ mutex_lock(&pktgen_thread_lock);
PG_DEBUG(printk("pktgen: pktgen_mark_device marking %s for removal\n",
ifname));
if (pkt_dev == NULL)
break; /* success */
- thread_unlock();
+ mutex_unlock(&pktgen_thread_lock);
PG_DEBUG(printk("pktgen: pktgen_mark_device waiting for %s "
"to disappear....\n", ifname));
schedule_timeout_interruptible(msecs_to_jiffies(msec_per_try));
- thread_lock();
+ mutex_lock(&pktgen_thread_lock);
if (++i >= max_tries) {
printk("pktgen_mark_device: timed out after waiting "
}
- thread_unlock();
+ mutex_unlock(&pktgen_thread_lock);
return ret;
}
pkt_dev->hh[1] = tmp;
}
+ if (pkt_dev->flags & F_MPLS_RND) {
+ unsigned i;
+ for(i = 0; i < pkt_dev->nr_labels; i++)
+ if (pkt_dev->labels[i] & MPLS_STACK_BOTTOM)
+ pkt_dev->labels[i] = MPLS_STACK_BOTTOM |
+ (pktgen_random() &
+ htonl(0x000fffff));
+ }
+
if (pkt_dev->udp_src_min < pkt_dev->udp_src_max) {
if (pkt_dev->flags & F_UDPSRC_RND)
pkt_dev->cur_udp_src =
pkt_dev->flows[flow].count++;
}
+static void mpls_push(__be32 *mpls, struct pktgen_dev *pkt_dev)
+{
+ unsigned i;
+ for(i = 0; i < pkt_dev->nr_labels; i++) {
+ *mpls++ = pkt_dev->labels[i] & ~MPLS_STACK_BOTTOM;
+ }
+ mpls--;
+ *mpls |= MPLS_STACK_BOTTOM;
+}
+
static struct sk_buff *fill_packet_ipv4(struct net_device *odev,
struct pktgen_dev *pkt_dev)
{
int datalen, iplen;
struct iphdr *iph;
struct pktgen_hdr *pgh = NULL;
+ __be16 protocol = __constant_htons(ETH_P_IP);
+ __be32 *mpls;
+
+ if (pkt_dev->nr_labels)
+ protocol = __constant_htons(ETH_P_MPLS_UC);
/* Update any of the values, used when we're incrementing various
* fields.
mod_cur_headers(pkt_dev);
datalen = (odev->hard_header_len + 16) & ~0xf;
- skb = alloc_skb(pkt_dev->cur_pkt_size + 64 + datalen, GFP_ATOMIC);
+ skb = alloc_skb(pkt_dev->cur_pkt_size + 64 + datalen +
+ pkt_dev->nr_labels*sizeof(u32), GFP_ATOMIC);
if (!skb) {
sprintf(pkt_dev->result, "No memory");
return NULL;
/* Reserve for ethernet and IP header */
eth = (__u8 *) skb_push(skb, 14);
+ mpls = (__be32 *)skb_put(skb, pkt_dev->nr_labels*sizeof(__u32));
+ if (pkt_dev->nr_labels)
+ mpls_push(mpls, pkt_dev);
iph = (struct iphdr *)skb_put(skb, sizeof(struct iphdr));
udph = (struct udphdr *)skb_put(skb, sizeof(struct udphdr));
memcpy(eth, pkt_dev->hh, 12);
- *(u16 *) & eth[12] = __constant_htons(ETH_P_IP);
+ *(u16 *) & eth[12] = protocol;
- datalen = pkt_dev->cur_pkt_size - 14 - 20 - 8; /* Eth + IPh + UDPh */
+ /* Eth + IPh + UDPh + mpls */
+ datalen = pkt_dev->cur_pkt_size - 14 - 20 - 8 -
+ pkt_dev->nr_labels*sizeof(u32);
if (datalen < sizeof(struct pktgen_hdr))
datalen = sizeof(struct pktgen_hdr);
iph->tot_len = htons(iplen);
iph->check = 0;
iph->check = ip_fast_csum((void *)iph, iph->ihl);
- skb->protocol = __constant_htons(ETH_P_IP);
- skb->mac.raw = ((u8 *) iph) - 14;
+ skb->protocol = protocol;
+ skb->mac.raw = ((u8 *) iph) - 14 - pkt_dev->nr_labels*sizeof(u32);
skb->dev = odev;
skb->pkt_type = PACKET_HOST;
int datalen;
struct ipv6hdr *iph;
struct pktgen_hdr *pgh = NULL;
+ __be16 protocol = __constant_htons(ETH_P_IPV6);
+ __be32 *mpls;
+
+ if (pkt_dev->nr_labels)
+ protocol = __constant_htons(ETH_P_MPLS_UC);
/* Update any of the values, used when we're incrementing various
* fields.
*/
mod_cur_headers(pkt_dev);
- skb = alloc_skb(pkt_dev->cur_pkt_size + 64 + 16, GFP_ATOMIC);
+ skb = alloc_skb(pkt_dev->cur_pkt_size + 64 + 16 +
+ pkt_dev->nr_labels*sizeof(u32), GFP_ATOMIC);
if (!skb) {
sprintf(pkt_dev->result, "No memory");
return NULL;
/* Reserve for ethernet and IP header */
eth = (__u8 *) skb_push(skb, 14);
+ mpls = (__be32 *)skb_put(skb, pkt_dev->nr_labels*sizeof(__u32));
+ if (pkt_dev->nr_labels)
+ mpls_push(mpls, pkt_dev);
iph = (struct ipv6hdr *)skb_put(skb, sizeof(struct ipv6hdr));
udph = (struct udphdr *)skb_put(skb, sizeof(struct udphdr));
memcpy(eth, pkt_dev->hh, 12);
*(u16 *) & eth[12] = __constant_htons(ETH_P_IPV6);
- datalen = pkt_dev->cur_pkt_size - 14 - sizeof(struct ipv6hdr) - sizeof(struct udphdr); /* Eth + IPh + UDPh */
+ /* Eth + IPh + UDPh + mpls */
+ datalen = pkt_dev->cur_pkt_size - 14 -
+ sizeof(struct ipv6hdr) - sizeof(struct udphdr) -
+ pkt_dev->nr_labels*sizeof(u32);
if (datalen < sizeof(struct pktgen_hdr)) {
datalen = sizeof(struct pktgen_hdr);
ipv6_addr_copy(&iph->daddr, &pkt_dev->cur_in6_daddr);
ipv6_addr_copy(&iph->saddr, &pkt_dev->cur_in6_saddr);
- skb->mac.raw = ((u8 *) iph) - 14;
- skb->protocol = __constant_htons(ETH_P_IPV6);
+ skb->mac.raw = ((u8 *) iph) - 14 - pkt_dev->nr_labels*sizeof(u32);
+ skb->protocol = protocol;
skb->dev = odev;
skb->pkt_type = PACKET_HOST;
static void pktgen_run(struct pktgen_thread *t)
{
- struct pktgen_dev *pkt_dev = NULL;
+ struct pktgen_dev *pkt_dev;
int started = 0;
PG_DEBUG(printk("pktgen: entering pktgen_run. %p\n", t));
if_lock(t);
- for (pkt_dev = t->if_list; pkt_dev; pkt_dev = pkt_dev->next) {
+ list_for_each_entry(pkt_dev, &t->if_list, list) {
/*
* setup odev and create initial packet.
PG_DEBUG(printk("pktgen: entering pktgen_stop_all_threads_ifs.\n"));
- thread_lock();
+ mutex_lock(&pktgen_thread_lock);
list_for_each_entry(t, &pktgen_threads, th_list)
t->control |= T_STOP;
- thread_unlock();
+ mutex_unlock(&pktgen_thread_lock);
}
static int thread_is_running(struct pktgen_thread *t)
{
- struct pktgen_dev *next;
+ struct pktgen_dev *pkt_dev;
int res = 0;
- for (next = t->if_list; next; next = next->next) {
- if (next->running) {
+ list_for_each_entry(pkt_dev, &t->if_list, list)
+ if (pkt_dev->running) {
res = 1;
break;
}
- }
return res;
}
struct pktgen_thread *t;
int sig = 1;
- thread_lock();
+ mutex_lock(&pktgen_thread_lock);
list_for_each_entry(t, &pktgen_threads, th_list) {
sig = pktgen_wait_thread_run(t);
list_for_each_entry(t, &pktgen_threads, th_list)
t->control |= (T_STOP);
- thread_unlock();
+ mutex_unlock(&pktgen_thread_lock);
return sig;
}
PG_DEBUG(printk("pktgen: entering pktgen_run_all_threads.\n"));
- thread_lock();
+ mutex_lock(&pktgen_thread_lock);
list_for_each_entry(t, &pktgen_threads, th_list)
t->control |= (T_RUN);
- thread_unlock();
+ mutex_unlock(&pktgen_thread_lock);
schedule_timeout_interruptible(msecs_to_jiffies(125)); /* Propagate thread->control */
static struct pktgen_dev *next_to_run(struct pktgen_thread *t)
{
- struct pktgen_dev *next, *best = NULL;
+ struct pktgen_dev *pkt_dev, *best = NULL;
if_lock(t);
- for (next = t->if_list; next; next = next->next) {
- if (!next->running)
+ list_for_each_entry(pkt_dev, &t->if_list, list) {
+ if (!pkt_dev->running)
continue;
if (best == NULL)
- best = next;
- else if (next->next_tx_us < best->next_tx_us)
- best = next;
+ best = pkt_dev;
+ else if (pkt_dev->next_tx_us < best->next_tx_us)
+ best = pkt_dev;
}
if_unlock(t);
return best;
static void pktgen_stop(struct pktgen_thread *t)
{
- struct pktgen_dev *next = NULL;
+ struct pktgen_dev *pkt_dev;
PG_DEBUG(printk("pktgen: entering pktgen_stop\n"));
if_lock(t);
- for (next = t->if_list; next; next = next->next) {
- pktgen_stop_device(next);
- if (next->skb)
- kfree_skb(next->skb);
+ list_for_each_entry(pkt_dev, &t->if_list, list) {
+ pktgen_stop_device(pkt_dev);
+ if (pkt_dev->skb)
+ kfree_skb(pkt_dev->skb);
- next->skb = NULL;
+ pkt_dev->skb = NULL;
}
if_unlock(t);
*/
static void pktgen_rem_one_if(struct pktgen_thread *t)
{
- struct pktgen_dev *cur, *next = NULL;
+ struct list_head *q, *n;
+ struct pktgen_dev *cur;
PG_DEBUG(printk("pktgen: entering pktgen_rem_one_if\n"));
if_lock(t);
- for (cur = t->if_list; cur; cur = next) {
- next = cur->next;
+ list_for_each_safe(q, n, &t->if_list) {
+ cur = list_entry(q, struct pktgen_dev, list);
if (!cur->removal_mark)
continue;
static void pktgen_rem_all_ifs(struct pktgen_thread *t)
{
- struct pktgen_dev *cur, *next = NULL;
+ struct list_head *q, *n;
+ struct pktgen_dev *cur;
/* Remove all devices, free mem */
PG_DEBUG(printk("pktgen: entering pktgen_rem_all_ifs\n"));
if_lock(t);
- for (cur = t->if_list; cur; cur = next) {
- next = cur->next;
+ list_for_each_safe(q, n, &t->if_list) {
+ cur = list_entry(q, struct pktgen_dev, list);
if (cur->skb)
kfree_skb(cur->skb);
remove_proc_entry(t->name, pg_proc_dir);
- thread_lock();
+ mutex_lock(&pktgen_thread_lock);
list_del(&t->th_list);
- thread_unlock();
+ mutex_unlock(&pktgen_thread_lock);
}
static __inline__ void pktgen_xmit(struct pktgen_dev *pkt_dev)
}
}
- spin_lock_bh(&odev->xmit_lock);
+ netif_tx_lock_bh(odev);
if (!netif_queue_stopped(odev)) {
atomic_inc(&(pkt_dev->skb->users));
pkt_dev->next_tx_ns = 0;
}
- spin_unlock_bh(&odev->xmit_lock);
+ netif_tx_unlock_bh(odev);
/* If pkt_dev->count is zero, then run forever */
if ((pkt_dev->count != 0) && (pkt_dev->sofar >= pkt_dev->count)) {
static struct pktgen_dev *pktgen_find_dev(struct pktgen_thread *t,
const char *ifname)
{
- struct pktgen_dev *pkt_dev = NULL;
+ struct pktgen_dev *p, *pkt_dev = NULL;
if_lock(t);
- for (pkt_dev = t->if_list; pkt_dev; pkt_dev = pkt_dev->next) {
- if (strncmp(pkt_dev->ifname, ifname, IFNAMSIZ) == 0) {
+ list_for_each_entry(p, &t->if_list, list)
+ if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0) {
+ pkt_dev = p;
break;
}
- }
if_unlock(t);
PG_DEBUG(printk("pktgen: find_dev(%s) returning %p\n", ifname, pkt_dev));
rv = -EBUSY;
goto out;
}
- pkt_dev->next = t->if_list;
- t->if_list = pkt_dev;
+
+ list_add(&pkt_dev->list, &t->if_list);
pkt_dev->pg_thread = t;
pkt_dev->running = 0;
{
struct pktgen_thread *t;
- thread_lock();
+ mutex_lock(&pktgen_thread_lock);
list_for_each_entry(t, &pktgen_threads, th_list)
if (strcmp(t->name, name) == 0) {
- thread_unlock();
+ mutex_unlock(&pktgen_thread_lock);
return t;
}
- thread_unlock();
+ mutex_unlock(&pktgen_thread_lock);
return NULL;
}
static int __init pktgen_create_thread(const char *name, int cpu)
{
+ int err;
struct pktgen_thread *t = NULL;
struct proc_dir_entry *pe;
pe->proc_fops = &pktgen_thread_fops;
pe->data = t;
+ INIT_LIST_HEAD(&t->if_list);
+
list_add_tail(&t->th_list, &pktgen_threads);
t->removed = 0;
- if (kernel_thread((void *)pktgen_thread_worker, (void *)t,
- CLONE_FS | CLONE_FILES | CLONE_SIGHAND) < 0)
+ err = kernel_thread((void *)pktgen_thread_worker, (void *)t,
+ CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
+ if (err < 0) {
printk("pktgen: kernel_thread() failed for cpu %d\n", t->cpu);
+ remove_proc_entry(t->name, pg_proc_dir);
+ list_del(&t->th_list);
+ kfree(t);
+ return err;
+ }
return 0;
}
static void _rem_dev_from_if_list(struct pktgen_thread *t,
struct pktgen_dev *pkt_dev)
{
- struct pktgen_dev *i, *prev = NULL;
-
- i = t->if_list;
+ struct list_head *q, *n;
+ struct pktgen_dev *p;
- while (i) {
- if (i == pkt_dev) {
- if (prev)
- prev->next = i->next;
- else
- t->if_list = NULL;
- break;
- }
- prev = i;
- i = i->next;
+ list_for_each_safe(q, n, &t->if_list) {
+ p = list_entry(q, struct pktgen_dev, list);
+ if (p == pkt_dev)
+ list_del(&p->list);
}
}
register_netdevice_notifier(&pktgen_notifier_block);
for_each_online_cpu(cpu) {
+ int err;
char buf[30];
sprintf(buf, "kpktgend_%i", cpu);
- pktgen_create_thread(buf, cpu);
+ err = pktgen_create_thread(buf, cpu);
+ if (err)
+ printk("pktgen: WARNING: Cannot create thread for cpu %d (%d)\n",
+ cpu, err);
}
+
+ if (list_empty(&pktgen_threads)) {
+ printk("pktgen: ERROR: Initialization failed for all threads\n");
+ unregister_netdevice_notifier(&pktgen_notifier_block);
+ remove_proc_entry(PGCTRL, pg_proc_dir);
+ proc_net_remove(PG_PROC_DIR);
+ return -ENODEV;
+ }
+
return 0;
}