89397a01ce
Add module parameter 'carrier='on|off' to set the default carrier state for linux network interfaces created by the KNI module. The default carrier state is 'off'. For KNI interfaces which need to reflect the carrier state of a physical Ethernet port controlled by the DPDK application, the default carrier state should be left set to 'off'. The application can set the carrier state of the KNI interface to reflect the state of the physical Ethernet port using rte_kni_update_link(). For KNI interfaces which are purely virtual, the default carrier state can be set to 'on'. This enables the KNI interface to be used without having to explicity set the carrier state to 'on' using rte_kni_update_link(). Signed-off-by: Dan Gora <dg@adax.com> Acked-by: Ferruh Yigit <ferruh.yigit@intel.com>
708 lines
16 KiB
C
708 lines
16 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright(c) 2010-2014 Intel Corporation.
|
|
*/
|
|
|
|
#include <linux/version.h>
|
|
#include <linux/module.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/rwsem.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/nsproxy.h>
|
|
#include <net/net_namespace.h>
|
|
#include <net/netns/generic.h>
|
|
|
|
#include <exec-env/rte_kni_common.h>
|
|
|
|
#include "compat.h"
|
|
#include "kni_dev.h"
|
|
|
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
MODULE_AUTHOR("Intel Corporation");
|
|
MODULE_DESCRIPTION("Kernel Module for managing kni devices");
|
|
|
|
#define KNI_RX_LOOP_NUM 1000
|
|
|
|
#define KNI_MAX_DEVICES 32
|
|
|
|
extern const struct pci_device_id ixgbe_pci_tbl[];
|
|
extern const struct pci_device_id igb_pci_tbl[];
|
|
|
|
/* loopback mode */
|
|
static char *lo_mode;
|
|
|
|
/* Kernel thread mode */
|
|
static char *kthread_mode;
|
|
static uint32_t multiple_kthread_on;
|
|
|
|
/* Default carrier state for created KNI network interfaces */
|
|
static char *carrier;
|
|
uint32_t dflt_carrier;
|
|
|
|
#define KNI_DEV_IN_USE_BIT_NUM 0 /* Bit number for device in use */
|
|
|
|
static int kni_net_id;
|
|
|
|
struct kni_net {
|
|
unsigned long device_in_use; /* device in use flag */
|
|
struct mutex kni_kthread_lock;
|
|
struct task_struct *kni_kthread;
|
|
struct rw_semaphore kni_list_lock;
|
|
struct list_head kni_list_head;
|
|
};
|
|
|
|
static int __net_init
|
|
kni_init_net(struct net *net)
|
|
{
|
|
#ifdef HAVE_SIMPLIFIED_PERNET_OPERATIONS
|
|
struct kni_net *knet = net_generic(net, kni_net_id);
|
|
|
|
memset(knet, 0, sizeof(*knet));
|
|
#else
|
|
struct kni_net *knet;
|
|
int ret;
|
|
|
|
knet = kzalloc(sizeof(struct kni_net), GFP_KERNEL);
|
|
if (!knet) {
|
|
ret = -ENOMEM;
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* Clear the bit of device in use */
|
|
clear_bit(KNI_DEV_IN_USE_BIT_NUM, &knet->device_in_use);
|
|
|
|
mutex_init(&knet->kni_kthread_lock);
|
|
|
|
init_rwsem(&knet->kni_list_lock);
|
|
INIT_LIST_HEAD(&knet->kni_list_head);
|
|
|
|
#ifdef HAVE_SIMPLIFIED_PERNET_OPERATIONS
|
|
return 0;
|
|
#else
|
|
ret = net_assign_generic(net, kni_net_id, knet);
|
|
if (ret < 0)
|
|
kfree(knet);
|
|
|
|
return ret;
|
|
#endif
|
|
}
|
|
|
|
static void __net_exit
|
|
kni_exit_net(struct net *net)
|
|
{
|
|
struct kni_net *knet __maybe_unused;
|
|
|
|
knet = net_generic(net, kni_net_id);
|
|
mutex_destroy(&knet->kni_kthread_lock);
|
|
|
|
#ifndef HAVE_SIMPLIFIED_PERNET_OPERATIONS
|
|
kfree(knet);
|
|
#endif
|
|
}
|
|
|
|
static struct pernet_operations kni_net_ops = {
|
|
.init = kni_init_net,
|
|
.exit = kni_exit_net,
|
|
#ifdef HAVE_SIMPLIFIED_PERNET_OPERATIONS
|
|
.id = &kni_net_id,
|
|
.size = sizeof(struct kni_net),
|
|
#endif
|
|
};
|
|
|
|
static int
|
|
kni_thread_single(void *data)
|
|
{
|
|
struct kni_net *knet = data;
|
|
int j;
|
|
struct kni_dev *dev;
|
|
|
|
while (!kthread_should_stop()) {
|
|
down_read(&knet->kni_list_lock);
|
|
for (j = 0; j < KNI_RX_LOOP_NUM; j++) {
|
|
list_for_each_entry(dev, &knet->kni_list_head, list) {
|
|
kni_net_rx(dev);
|
|
kni_net_poll_resp(dev);
|
|
}
|
|
}
|
|
up_read(&knet->kni_list_lock);
|
|
#ifdef RTE_KNI_PREEMPT_DEFAULT
|
|
/* reschedule out for a while */
|
|
schedule_timeout_interruptible(
|
|
usecs_to_jiffies(KNI_KTHREAD_RESCHEDULE_INTERVAL));
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
kni_thread_multiple(void *param)
|
|
{
|
|
int j;
|
|
struct kni_dev *dev = param;
|
|
|
|
while (!kthread_should_stop()) {
|
|
for (j = 0; j < KNI_RX_LOOP_NUM; j++) {
|
|
kni_net_rx(dev);
|
|
kni_net_poll_resp(dev);
|
|
}
|
|
#ifdef RTE_KNI_PREEMPT_DEFAULT
|
|
schedule_timeout_interruptible(
|
|
usecs_to_jiffies(KNI_KTHREAD_RESCHEDULE_INTERVAL));
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
kni_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct net *net = current->nsproxy->net_ns;
|
|
struct kni_net *knet = net_generic(net, kni_net_id);
|
|
|
|
/* kni device can be opened by one user only per netns */
|
|
if (test_and_set_bit(KNI_DEV_IN_USE_BIT_NUM, &knet->device_in_use))
|
|
return -EBUSY;
|
|
|
|
file->private_data = get_net(net);
|
|
pr_debug("/dev/kni opened\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
kni_dev_remove(struct kni_dev *dev)
|
|
{
|
|
if (!dev)
|
|
return -ENODEV;
|
|
|
|
#ifdef RTE_KNI_KMOD_ETHTOOL
|
|
if (dev->pci_dev) {
|
|
if (pci_match_id(ixgbe_pci_tbl, dev->pci_dev))
|
|
ixgbe_kni_remove(dev->pci_dev);
|
|
else if (pci_match_id(igb_pci_tbl, dev->pci_dev))
|
|
igb_kni_remove(dev->pci_dev);
|
|
}
|
|
#endif
|
|
|
|
if (dev->net_dev) {
|
|
unregister_netdev(dev->net_dev);
|
|
free_netdev(dev->net_dev);
|
|
}
|
|
|
|
kni_net_release_fifo_phy(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
kni_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct net *net = file->private_data;
|
|
struct kni_net *knet = net_generic(net, kni_net_id);
|
|
struct kni_dev *dev, *n;
|
|
|
|
/* Stop kernel thread for single mode */
|
|
if (multiple_kthread_on == 0) {
|
|
mutex_lock(&knet->kni_kthread_lock);
|
|
/* Stop kernel thread */
|
|
if (knet->kni_kthread != NULL) {
|
|
kthread_stop(knet->kni_kthread);
|
|
knet->kni_kthread = NULL;
|
|
}
|
|
mutex_unlock(&knet->kni_kthread_lock);
|
|
}
|
|
|
|
down_write(&knet->kni_list_lock);
|
|
list_for_each_entry_safe(dev, n, &knet->kni_list_head, list) {
|
|
/* Stop kernel thread for multiple mode */
|
|
if (multiple_kthread_on && dev->pthread != NULL) {
|
|
kthread_stop(dev->pthread);
|
|
dev->pthread = NULL;
|
|
}
|
|
|
|
kni_dev_remove(dev);
|
|
list_del(&dev->list);
|
|
}
|
|
up_write(&knet->kni_list_lock);
|
|
|
|
/* Clear the bit of device in use */
|
|
clear_bit(KNI_DEV_IN_USE_BIT_NUM, &knet->device_in_use);
|
|
|
|
put_net(net);
|
|
pr_debug("/dev/kni closed\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
kni_check_param(struct kni_dev *kni, struct rte_kni_device_info *dev)
|
|
{
|
|
if (!kni || !dev)
|
|
return -1;
|
|
|
|
/* Check if network name has been used */
|
|
if (!strncmp(kni->name, dev->name, RTE_KNI_NAMESIZE)) {
|
|
pr_err("KNI name %s duplicated\n", dev->name);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
kni_run_thread(struct kni_net *knet, struct kni_dev *kni, uint8_t force_bind)
|
|
{
|
|
/**
|
|
* Create a new kernel thread for multiple mode, set its core affinity,
|
|
* and finally wake it up.
|
|
*/
|
|
if (multiple_kthread_on) {
|
|
kni->pthread = kthread_create(kni_thread_multiple,
|
|
(void *)kni, "kni_%s", kni->name);
|
|
if (IS_ERR(kni->pthread)) {
|
|
kni_dev_remove(kni);
|
|
return -ECANCELED;
|
|
}
|
|
|
|
if (force_bind)
|
|
kthread_bind(kni->pthread, kni->core_id);
|
|
wake_up_process(kni->pthread);
|
|
} else {
|
|
mutex_lock(&knet->kni_kthread_lock);
|
|
|
|
if (knet->kni_kthread == NULL) {
|
|
knet->kni_kthread = kthread_create(kni_thread_single,
|
|
(void *)knet, "kni_single");
|
|
if (IS_ERR(knet->kni_kthread)) {
|
|
mutex_unlock(&knet->kni_kthread_lock);
|
|
kni_dev_remove(kni);
|
|
return -ECANCELED;
|
|
}
|
|
|
|
if (force_bind)
|
|
kthread_bind(knet->kni_kthread, kni->core_id);
|
|
wake_up_process(knet->kni_kthread);
|
|
}
|
|
|
|
mutex_unlock(&knet->kni_kthread_lock);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
kni_ioctl_create(struct net *net, uint32_t ioctl_num,
|
|
unsigned long ioctl_param)
|
|
{
|
|
struct kni_net *knet = net_generic(net, kni_net_id);
|
|
int ret;
|
|
struct rte_kni_device_info dev_info;
|
|
struct net_device *net_dev = NULL;
|
|
struct kni_dev *kni, *dev, *n;
|
|
#ifdef RTE_KNI_KMOD_ETHTOOL
|
|
struct pci_dev *found_pci = NULL;
|
|
struct net_device *lad_dev = NULL;
|
|
struct pci_dev *pci = NULL;
|
|
#endif
|
|
|
|
pr_info("Creating kni...\n");
|
|
/* Check the buffer size, to avoid warning */
|
|
if (_IOC_SIZE(ioctl_num) > sizeof(dev_info))
|
|
return -EINVAL;
|
|
|
|
/* Copy kni info from user space */
|
|
ret = copy_from_user(&dev_info, (void *)ioctl_param, sizeof(dev_info));
|
|
if (ret) {
|
|
pr_err("copy_from_user in kni_ioctl_create");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Check if name is zero-ended */
|
|
if (strnlen(dev_info.name, sizeof(dev_info.name)) == sizeof(dev_info.name)) {
|
|
pr_err("kni.name not zero-terminated");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* Check if the cpu core id is valid for binding.
|
|
*/
|
|
if (dev_info.force_bind && !cpu_online(dev_info.core_id)) {
|
|
pr_err("cpu %u is not online\n", dev_info.core_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check if it has been created */
|
|
down_read(&knet->kni_list_lock);
|
|
list_for_each_entry_safe(dev, n, &knet->kni_list_head, list) {
|
|
if (kni_check_param(dev, &dev_info) < 0) {
|
|
up_read(&knet->kni_list_lock);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
up_read(&knet->kni_list_lock);
|
|
|
|
net_dev = alloc_netdev(sizeof(struct kni_dev), dev_info.name,
|
|
#ifdef NET_NAME_USER
|
|
NET_NAME_USER,
|
|
#endif
|
|
kni_net_init);
|
|
if (net_dev == NULL) {
|
|
pr_err("error allocating device \"%s\"\n", dev_info.name);
|
|
return -EBUSY;
|
|
}
|
|
|
|
dev_net_set(net_dev, net);
|
|
|
|
kni = netdev_priv(net_dev);
|
|
|
|
kni->net_dev = net_dev;
|
|
kni->group_id = dev_info.group_id;
|
|
kni->core_id = dev_info.core_id;
|
|
strncpy(kni->name, dev_info.name, RTE_KNI_NAMESIZE);
|
|
|
|
/* Translate user space info into kernel space info */
|
|
kni->tx_q = phys_to_virt(dev_info.tx_phys);
|
|
kni->rx_q = phys_to_virt(dev_info.rx_phys);
|
|
kni->alloc_q = phys_to_virt(dev_info.alloc_phys);
|
|
kni->free_q = phys_to_virt(dev_info.free_phys);
|
|
|
|
kni->req_q = phys_to_virt(dev_info.req_phys);
|
|
kni->resp_q = phys_to_virt(dev_info.resp_phys);
|
|
kni->sync_va = dev_info.sync_va;
|
|
kni->sync_kva = phys_to_virt(dev_info.sync_phys);
|
|
|
|
kni->mbuf_size = dev_info.mbuf_size;
|
|
|
|
pr_debug("tx_phys: 0x%016llx, tx_q addr: 0x%p\n",
|
|
(unsigned long long) dev_info.tx_phys, kni->tx_q);
|
|
pr_debug("rx_phys: 0x%016llx, rx_q addr: 0x%p\n",
|
|
(unsigned long long) dev_info.rx_phys, kni->rx_q);
|
|
pr_debug("alloc_phys: 0x%016llx, alloc_q addr: 0x%p\n",
|
|
(unsigned long long) dev_info.alloc_phys, kni->alloc_q);
|
|
pr_debug("free_phys: 0x%016llx, free_q addr: 0x%p\n",
|
|
(unsigned long long) dev_info.free_phys, kni->free_q);
|
|
pr_debug("req_phys: 0x%016llx, req_q addr: 0x%p\n",
|
|
(unsigned long long) dev_info.req_phys, kni->req_q);
|
|
pr_debug("resp_phys: 0x%016llx, resp_q addr: 0x%p\n",
|
|
(unsigned long long) dev_info.resp_phys, kni->resp_q);
|
|
pr_debug("mbuf_size: %u\n", kni->mbuf_size);
|
|
|
|
pr_debug("PCI: %02x:%02x.%02x %04x:%04x\n",
|
|
dev_info.bus,
|
|
dev_info.devid,
|
|
dev_info.function,
|
|
dev_info.vendor_id,
|
|
dev_info.device_id);
|
|
#ifdef RTE_KNI_KMOD_ETHTOOL
|
|
pci = pci_get_device(dev_info.vendor_id, dev_info.device_id, NULL);
|
|
|
|
/* Support Ethtool */
|
|
while (pci) {
|
|
pr_debug("pci_bus: %02x:%02x:%02x\n",
|
|
pci->bus->number,
|
|
PCI_SLOT(pci->devfn),
|
|
PCI_FUNC(pci->devfn));
|
|
|
|
if ((pci->bus->number == dev_info.bus) &&
|
|
(PCI_SLOT(pci->devfn) == dev_info.devid) &&
|
|
(PCI_FUNC(pci->devfn) == dev_info.function)) {
|
|
found_pci = pci;
|
|
|
|
if (pci_match_id(ixgbe_pci_tbl, found_pci))
|
|
ret = ixgbe_kni_probe(found_pci, &lad_dev);
|
|
else if (pci_match_id(igb_pci_tbl, found_pci))
|
|
ret = igb_kni_probe(found_pci, &lad_dev);
|
|
else
|
|
ret = -1;
|
|
|
|
pr_debug("PCI found: pci=0x%p, lad_dev=0x%p\n",
|
|
pci, lad_dev);
|
|
if (ret == 0) {
|
|
kni->lad_dev = lad_dev;
|
|
kni_set_ethtool_ops(kni->net_dev);
|
|
} else {
|
|
pr_err("Device not supported by ethtool");
|
|
kni->lad_dev = NULL;
|
|
}
|
|
|
|
kni->pci_dev = found_pci;
|
|
kni->device_id = dev_info.device_id;
|
|
break;
|
|
}
|
|
pci = pci_get_device(dev_info.vendor_id,
|
|
dev_info.device_id, pci);
|
|
}
|
|
if (pci)
|
|
pci_dev_put(pci);
|
|
#endif
|
|
|
|
if (kni->lad_dev)
|
|
ether_addr_copy(net_dev->dev_addr, kni->lad_dev->dev_addr);
|
|
else {
|
|
/* if user has provided a valid mac address */
|
|
if (is_valid_ether_addr((unsigned char *)(dev_info.mac_addr)))
|
|
memcpy(net_dev->dev_addr, dev_info.mac_addr, ETH_ALEN);
|
|
else
|
|
/*
|
|
* Generate random mac address. eth_random_addr() is the
|
|
* newer version of generating mac address in kernel.
|
|
*/
|
|
random_ether_addr(net_dev->dev_addr);
|
|
}
|
|
|
|
if (dev_info.mtu)
|
|
net_dev->mtu = dev_info.mtu;
|
|
|
|
ret = register_netdev(net_dev);
|
|
if (ret) {
|
|
pr_err("error %i registering device \"%s\"\n",
|
|
ret, dev_info.name);
|
|
kni->net_dev = NULL;
|
|
kni_dev_remove(kni);
|
|
free_netdev(net_dev);
|
|
return -ENODEV;
|
|
}
|
|
|
|
netif_carrier_off(net_dev);
|
|
|
|
ret = kni_run_thread(knet, kni, dev_info.force_bind);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
down_write(&knet->kni_list_lock);
|
|
list_add(&kni->list, &knet->kni_list_head);
|
|
up_write(&knet->kni_list_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
kni_ioctl_release(struct net *net, uint32_t ioctl_num,
|
|
unsigned long ioctl_param)
|
|
{
|
|
struct kni_net *knet = net_generic(net, kni_net_id);
|
|
int ret = -EINVAL;
|
|
struct kni_dev *dev, *n;
|
|
struct rte_kni_device_info dev_info;
|
|
|
|
if (_IOC_SIZE(ioctl_num) > sizeof(dev_info))
|
|
return -EINVAL;
|
|
|
|
ret = copy_from_user(&dev_info, (void *)ioctl_param, sizeof(dev_info));
|
|
if (ret) {
|
|
pr_err("copy_from_user in kni_ioctl_release");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Release the network device according to its name */
|
|
if (strlen(dev_info.name) == 0)
|
|
return ret;
|
|
|
|
down_write(&knet->kni_list_lock);
|
|
list_for_each_entry_safe(dev, n, &knet->kni_list_head, list) {
|
|
if (strncmp(dev->name, dev_info.name, RTE_KNI_NAMESIZE) != 0)
|
|
continue;
|
|
|
|
if (multiple_kthread_on && dev->pthread != NULL) {
|
|
kthread_stop(dev->pthread);
|
|
dev->pthread = NULL;
|
|
}
|
|
|
|
kni_dev_remove(dev);
|
|
list_del(&dev->list);
|
|
ret = 0;
|
|
break;
|
|
}
|
|
up_write(&knet->kni_list_lock);
|
|
pr_info("%s release kni named %s\n",
|
|
(ret == 0 ? "Successfully" : "Unsuccessfully"), dev_info.name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
kni_ioctl(struct inode *inode, uint32_t ioctl_num, unsigned long ioctl_param)
|
|
{
|
|
int ret = -EINVAL;
|
|
struct net *net = current->nsproxy->net_ns;
|
|
|
|
pr_debug("IOCTL num=0x%0x param=0x%0lx\n", ioctl_num, ioctl_param);
|
|
|
|
/*
|
|
* Switch according to the ioctl called
|
|
*/
|
|
switch (_IOC_NR(ioctl_num)) {
|
|
case _IOC_NR(RTE_KNI_IOCTL_TEST):
|
|
/* For test only, not used */
|
|
break;
|
|
case _IOC_NR(RTE_KNI_IOCTL_CREATE):
|
|
ret = kni_ioctl_create(net, ioctl_num, ioctl_param);
|
|
break;
|
|
case _IOC_NR(RTE_KNI_IOCTL_RELEASE):
|
|
ret = kni_ioctl_release(net, ioctl_num, ioctl_param);
|
|
break;
|
|
default:
|
|
pr_debug("IOCTL default\n");
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
kni_compat_ioctl(struct inode *inode, uint32_t ioctl_num,
|
|
unsigned long ioctl_param)
|
|
{
|
|
/* 32 bits app on 64 bits OS to be supported later */
|
|
pr_debug("Not implemented.\n");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const struct file_operations kni_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = kni_open,
|
|
.release = kni_release,
|
|
.unlocked_ioctl = (void *)kni_ioctl,
|
|
.compat_ioctl = (void *)kni_compat_ioctl,
|
|
};
|
|
|
|
static struct miscdevice kni_misc = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = KNI_DEVICE,
|
|
.fops = &kni_fops,
|
|
};
|
|
|
|
static int __init
|
|
kni_parse_kthread_mode(void)
|
|
{
|
|
if (!kthread_mode)
|
|
return 0;
|
|
|
|
if (strcmp(kthread_mode, "single") == 0)
|
|
return 0;
|
|
else if (strcmp(kthread_mode, "multiple") == 0)
|
|
multiple_kthread_on = 1;
|
|
else
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init
|
|
kni_parse_carrier_state(void)
|
|
{
|
|
if (!carrier) {
|
|
dflt_carrier = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (strcmp(carrier, "off") == 0)
|
|
dflt_carrier = 0;
|
|
else if (strcmp(carrier, "on") == 0)
|
|
dflt_carrier = 1;
|
|
else
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init
|
|
kni_init(void)
|
|
{
|
|
int rc;
|
|
|
|
if (kni_parse_kthread_mode() < 0) {
|
|
pr_err("Invalid parameter for kthread_mode\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (multiple_kthread_on == 0)
|
|
pr_debug("Single kernel thread for all KNI devices\n");
|
|
else
|
|
pr_debug("Multiple kernel thread mode enabled\n");
|
|
|
|
if (kni_parse_carrier_state() < 0) {
|
|
pr_err("Invalid parameter for carrier\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dflt_carrier == 0)
|
|
pr_debug("Default carrier state set to off.\n");
|
|
else
|
|
pr_debug("Default carrier state set to on.\n");
|
|
|
|
#ifdef HAVE_SIMPLIFIED_PERNET_OPERATIONS
|
|
rc = register_pernet_subsys(&kni_net_ops);
|
|
#else
|
|
rc = register_pernet_gen_subsys(&kni_net_id, &kni_net_ops);
|
|
#endif
|
|
if (rc)
|
|
return -EPERM;
|
|
|
|
rc = misc_register(&kni_misc);
|
|
if (rc != 0) {
|
|
pr_err("Misc registration failed\n");
|
|
goto out;
|
|
}
|
|
|
|
/* Configure the lo mode according to the input parameter */
|
|
kni_net_config_lo_mode(lo_mode);
|
|
|
|
return 0;
|
|
|
|
out:
|
|
#ifdef HAVE_SIMPLIFIED_PERNET_OPERATIONS
|
|
unregister_pernet_subsys(&kni_net_ops);
|
|
#else
|
|
unregister_pernet_gen_subsys(kni_net_id, &kni_net_ops);
|
|
#endif
|
|
return rc;
|
|
}
|
|
|
|
static void __exit
|
|
kni_exit(void)
|
|
{
|
|
misc_deregister(&kni_misc);
|
|
#ifdef HAVE_SIMPLIFIED_PERNET_OPERATIONS
|
|
unregister_pernet_subsys(&kni_net_ops);
|
|
#else
|
|
unregister_pernet_gen_subsys(kni_net_id, &kni_net_ops);
|
|
#endif
|
|
}
|
|
|
|
module_init(kni_init);
|
|
module_exit(kni_exit);
|
|
|
|
module_param(lo_mode, charp, 0644);
|
|
MODULE_PARM_DESC(lo_mode,
|
|
"KNI loopback mode (default=lo_mode_none):\n"
|
|
"\t\tlo_mode_none Kernel loopback disabled\n"
|
|
"\t\tlo_mode_fifo Enable kernel loopback with fifo\n"
|
|
"\t\tlo_mode_fifo_skb Enable kernel loopback with fifo and skb buffer\n"
|
|
"\t\t"
|
|
);
|
|
|
|
module_param(kthread_mode, charp, 0644);
|
|
MODULE_PARM_DESC(kthread_mode,
|
|
"Kernel thread mode (default=single):\n"
|
|
"\t\tsingle Single kernel thread mode enabled.\n"
|
|
"\t\tmultiple Multiple kernel thread mode enabled.\n"
|
|
"\t\t"
|
|
);
|
|
|
|
module_param(carrier, charp, 0644);
|
|
MODULE_PARM_DESC(carrier,
|
|
"Default carrier state for KNI interface (default=off):\n"
|
|
"\t\toff Interfaces will be created with carrier state set to off.\n"
|
|
"\t\ton Interfaces will be created with carrier state set to on.\n"
|
|
"\t\t"
|
|
);
|