diff options
Diffstat (limited to 'net/caif/caif_dev.c')
| -rw-r--r-- | net/caif/caif_dev.c | 585 | 
1 files changed, 368 insertions, 217 deletions
diff --git a/net/caif/caif_dev.c b/net/caif/caif_dev.c index a42a408306e..edbca468fa7 100644 --- a/net/caif/caif_dev.c +++ b/net/caif/caif_dev.c @@ -1,83 +1,108 @@  /*   * CAIF Interface registration.   * Copyright (C) ST-Ericsson AB 2010 - * Author:	Sjur Brendeland/sjur.brandeland@stericsson.com + * Author:	Sjur Brendeland   * License terms: GNU General Public License (GPL) version 2   * - * Borrowed heavily from file: pn_dev.c. Thanks to - *  Remi Denis-Courmont <remi.denis-courmont@nokia.com> + * Borrowed heavily from file: pn_dev.c. Thanks to Remi Denis-Courmont   *  and Sakari Ailus <sakari.ailus@nokia.com>   */  #define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__ -#include <linux/version.h> -#include <linux/module.h>  #include <linux/kernel.h>  #include <linux/if_arp.h>  #include <linux/net.h>  #include <linux/netdevice.h> -#include <linux/skbuff.h> -#include <linux/sched.h> -#include <linux/wait.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/spinlock.h>  #include <net/netns/generic.h>  #include <net/net_namespace.h>  #include <net/pkt_sched.h>  #include <net/caif/caif_device.h> -#include <net/caif/caif_dev.h>  #include <net/caif/caif_layer.h> +#include <net/caif/caif_dev.h>  #include <net/caif/cfpkt.h>  #include <net/caif/cfcnfg.h> +#include <net/caif/cfserl.h>  MODULE_LICENSE("GPL"); -#define TIMEOUT (HZ*5)  /* Used for local tracking of the CAIF net devices */  struct caif_device_entry {  	struct cflayer layer;  	struct list_head list; -	atomic_t in_use; -	atomic_t state; -	u16 phyid;  	struct net_device *netdev; -	wait_queue_head_t event; +	int __percpu *pcpu_refcnt; +	spinlock_t flow_lock; +	struct sk_buff *xoff_skb; +	void (*xoff_skb_dtor)(struct sk_buff *skb); +	bool xoff;  };  struct caif_device_entry_list {  	struct list_head list;  	/* Protects simulanous deletes in list */ -	spinlock_t lock; +	struct mutex lock;  };  struct caif_net { +	struct cfcnfg *cfg;  	struct caif_device_entry_list caifdevs;  };  static int caif_net_id; -static struct cfcnfg *cfg; +static int q_high = 50; /* Percent */ + +struct cfcnfg *get_cfcnfg(struct net *net) +{ +	struct caif_net *caifn; +	caifn = net_generic(net, caif_net_id); +	return caifn->cfg; +} +EXPORT_SYMBOL(get_cfcnfg);  static struct caif_device_entry_list *caif_device_list(struct net *net)  {  	struct caif_net *caifn; -	BUG_ON(!net);  	caifn = net_generic(net, caif_net_id); -	BUG_ON(!caifn);  	return &caifn->caifdevs;  } +static void caifd_put(struct caif_device_entry *e) +{ +	this_cpu_dec(*e->pcpu_refcnt); +} + +static void caifd_hold(struct caif_device_entry *e) +{ +	this_cpu_inc(*e->pcpu_refcnt); +} + +static int caifd_refcnt_read(struct caif_device_entry *e) +{ +	int i, refcnt = 0; +	for_each_possible_cpu(i) +		refcnt += *per_cpu_ptr(e->pcpu_refcnt, i); +	return refcnt; +} +  /* Allocate new CAIF device. */  static struct caif_device_entry *caif_device_alloc(struct net_device *dev)  { -	struct caif_device_entry_list *caifdevs;  	struct caif_device_entry *caifd; -	caifdevs = caif_device_list(dev_net(dev)); -	BUG_ON(!caifdevs); -	caifd = kzalloc(sizeof(*caifd), GFP_ATOMIC); + +	caifd = kzalloc(sizeof(*caifd), GFP_KERNEL);  	if (!caifd)  		return NULL; +	caifd->pcpu_refcnt = alloc_percpu(int); +	if (!caifd->pcpu_refcnt) { +		kfree(caifd); +		return NULL; +	}  	caifd->netdev = dev; -	list_add(&caifd->list, &caifdevs->list); -	init_waitqueue_head(&caifd->event); +	dev_hold(dev);  	return caifd;  } @@ -86,100 +111,166 @@ static struct caif_device_entry *caif_get(struct net_device *dev)  	struct caif_device_entry_list *caifdevs =  	    caif_device_list(dev_net(dev));  	struct caif_device_entry *caifd; -	BUG_ON(!caifdevs); -	list_for_each_entry(caifd, &caifdevs->list, list) { + +	list_for_each_entry_rcu(caifd, &caifdevs->list, list) {  		if (caifd->netdev == dev)  			return caifd;  	}  	return NULL;  } -static void caif_device_destroy(struct net_device *dev) +static void caif_flow_cb(struct sk_buff *skb)  { -	struct caif_device_entry_list *caifdevs = -	    caif_device_list(dev_net(dev));  	struct caif_device_entry *caifd; -	ASSERT_RTNL(); -	if (dev->type != ARPHRD_CAIF) -		return; +	void (*dtor)(struct sk_buff *skb) = NULL; +	bool send_xoff; -	spin_lock_bh(&caifdevs->lock); -	caifd = caif_get(dev); -	if (caifd == NULL) { -		spin_unlock_bh(&caifdevs->lock); +	WARN_ON(skb->dev == NULL); + +	rcu_read_lock(); +	caifd = caif_get(skb->dev); + +	WARN_ON(caifd == NULL); +	if (caifd == NULL)  		return; -	} -	list_del(&caifd->list); -	spin_unlock_bh(&caifdevs->lock); +	caifd_hold(caifd); +	rcu_read_unlock(); + +	spin_lock_bh(&caifd->flow_lock); +	send_xoff = caifd->xoff; +	caifd->xoff = 0; +	dtor = caifd->xoff_skb_dtor; + +	if (WARN_ON(caifd->xoff_skb != skb)) +		skb = NULL; + +	caifd->xoff_skb = NULL; +	caifd->xoff_skb_dtor = NULL; + +	spin_unlock_bh(&caifd->flow_lock); -	kfree(caifd); +	if (dtor && skb) +		dtor(skb); + +	if (send_xoff) +		caifd->layer.up-> +			ctrlcmd(caifd->layer.up, +				_CAIF_CTRLCMD_PHYIF_FLOW_ON_IND, +				caifd->layer.id); +	caifd_put(caifd);  }  static int transmit(struct cflayer *layer, struct cfpkt *pkt)  { +	int err, high = 0, qlen = 0;  	struct caif_device_entry *caifd =  	    container_of(layer, struct caif_device_entry, layer); -	struct sk_buff *skb, *skb2; -	int ret = -EINVAL; +	struct sk_buff *skb; +	struct netdev_queue *txq; + +	rcu_read_lock_bh(); +  	skb = cfpkt_tonative(pkt);  	skb->dev = caifd->netdev; +	skb_reset_network_header(skb); +	skb->protocol = htons(ETH_P_CAIF); + +	/* Check if we need to handle xoff */ +	if (likely(caifd->netdev->tx_queue_len == 0)) +		goto noxoff; + +	if (unlikely(caifd->xoff)) +		goto noxoff; + +	if (likely(!netif_queue_stopped(caifd->netdev))) { +		/* If we run with a TX queue, check if the queue is too long*/ +		txq = netdev_get_tx_queue(skb->dev, 0); +		qlen = qdisc_qlen(rcu_dereference_bh(txq->qdisc)); + +		if (likely(qlen == 0)) +			goto noxoff; + +		high = (caifd->netdev->tx_queue_len * q_high) / 100; +		if (likely(qlen < high)) +			goto noxoff; +	} + +	/* Hold lock while accessing xoff */ +	spin_lock_bh(&caifd->flow_lock); +	if (caifd->xoff) { +		spin_unlock_bh(&caifd->flow_lock); +		goto noxoff; +	} +  	/* -	 * Don't allow SKB to be destroyed upon error, but signal resend -	 * notification to clients. We can't rely on the return value as -	 * congestion (NET_XMIT_CN) sometimes drops the packet, sometimes don't. +	 * Handle flow off, we do this by temporary hi-jacking this +	 * skb's destructor function, and replace it with our own +	 * flow-on callback. The callback will set flow-on and call +	 * the original destructor.  	 */ -	if (netif_queue_stopped(caifd->netdev)) -		return -EAGAIN; -	skb2 = skb_get(skb); -	ret = dev_queue_xmit(skb2); +	pr_debug("queue has stopped(%d) or is full (%d > %d)\n", +			netif_queue_stopped(caifd->netdev), +			qlen, high); +	caifd->xoff = 1; +	caifd->xoff_skb = skb; +	caifd->xoff_skb_dtor = skb->destructor; +	skb->destructor = caif_flow_cb; +	spin_unlock_bh(&caifd->flow_lock); -	if (!ret) -		kfree_skb(skb); -	else -		return -EAGAIN; +	caifd->layer.up->ctrlcmd(caifd->layer.up, +					_CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND, +					caifd->layer.id); +noxoff: +	rcu_read_unlock_bh(); -	return 0; -} +	err = dev_queue_xmit(skb); +	if (err > 0) +		err = -EIO; -static int modemcmd(struct cflayer *layr, enum caif_modemcmd ctrl) -{ -	struct caif_device_entry *caifd; -	struct caif_dev_common *caifdev; -	caifd = container_of(layr, struct caif_device_entry, layer); -	caifdev = netdev_priv(caifd->netdev); -	if (ctrl == _CAIF_MODEMCMD_PHYIF_USEFULL) { -		atomic_set(&caifd->in_use, 1); -		wake_up_interruptible(&caifd->event); - -	} else if (ctrl == _CAIF_MODEMCMD_PHYIF_USELESS) { -		atomic_set(&caifd->in_use, 0); -		wake_up_interruptible(&caifd->event); -	} -	return 0; +	return err;  }  /* - * Stuff received packets to associated sockets. + * Stuff received packets into the CAIF stack.   * On error, returns non-zero and releases the skb.   */  static int receive(struct sk_buff *skb, struct net_device *dev,  		   struct packet_type *pkttype, struct net_device *orig_dev)  { -	struct net *net;  	struct cfpkt *pkt;  	struct caif_device_entry *caifd; -	net = dev_net(dev); +	int err; +  	pkt = cfpkt_fromnative(CAIF_DIR_IN, skb); + +	rcu_read_lock();  	caifd = caif_get(dev); -	if (!caifd || !caifd->layer.up || !caifd->layer.up->receive) -		return NET_RX_DROP; -	if (caifd->layer.up->receive(caifd->layer.up, pkt)) +	if (!caifd || !caifd->layer.up || !caifd->layer.up->receive || +			!netif_oper_up(caifd->netdev)) { +		rcu_read_unlock(); +		kfree_skb(skb);  		return NET_RX_DROP; +	} -	return 0; +	/* Hold reference to netdevice while using CAIF stack */ +	caifd_hold(caifd); +	rcu_read_unlock(); + +	err = caifd->layer.up->receive(caifd->layer.up, pkt); + +	/* For -EILSEQ the packet is not freed so so it now */ +	if (err == -EILSEQ) +		cfpkt_destroy(pkt); + +	/* Release reference to stack upwards */ +	caifd_put(caifd); + +	if (err != 0) +		err = NET_RX_DROP; +	return err;  }  static struct packet_type caif_packet_type __read_mostly = { @@ -189,129 +280,208 @@ static struct packet_type caif_packet_type __read_mostly = {  static void dev_flowctrl(struct net_device *dev, int on)  { -	struct caif_device_entry *caifd = caif_get(dev); -	if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd) +	struct caif_device_entry *caifd; + +	rcu_read_lock(); + +	caifd = caif_get(dev); +	if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd) { +		rcu_read_unlock();  		return; +	} + +	caifd_hold(caifd); +	rcu_read_unlock();  	caifd->layer.up->ctrlcmd(caifd->layer.up,  				 on ?  				 _CAIF_CTRLCMD_PHYIF_FLOW_ON_IND :  				 _CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND,  				 caifd->layer.id); +	caifd_put(caifd); +} + +void caif_enroll_dev(struct net_device *dev, struct caif_dev_common *caifdev, +		     struct cflayer *link_support, int head_room, +		     struct cflayer **layer, +		     int (**rcv_func)(struct sk_buff *, struct net_device *, +				      struct packet_type *, +				      struct net_device *)) +{ +	struct caif_device_entry *caifd; +	enum cfcnfg_phy_preference pref; +	struct cfcnfg *cfg = get_cfcnfg(dev_net(dev)); +	struct caif_device_entry_list *caifdevs; + +	caifdevs = caif_device_list(dev_net(dev)); +	caifd = caif_device_alloc(dev); +	if (!caifd) +		return; +	*layer = &caifd->layer; +	spin_lock_init(&caifd->flow_lock); + +	switch (caifdev->link_select) { +	case CAIF_LINK_HIGH_BANDW: +		pref = CFPHYPREF_HIGH_BW; +		break; +	case CAIF_LINK_LOW_LATENCY: +		pref = CFPHYPREF_LOW_LAT; +		break; +	default: +		pref = CFPHYPREF_HIGH_BW; +		break; +	} +	mutex_lock(&caifdevs->lock); +	list_add_rcu(&caifd->list, &caifdevs->list); + +	strncpy(caifd->layer.name, dev->name, +		sizeof(caifd->layer.name) - 1); +	caifd->layer.name[sizeof(caifd->layer.name) - 1] = 0; +	caifd->layer.transmit = transmit; +	cfcnfg_add_phy_layer(cfg, +				dev, +				&caifd->layer, +				pref, +				link_support, +				caifdev->use_fcs, +				head_room); +	mutex_unlock(&caifdevs->lock); +	if (rcv_func) +		*rcv_func = receive;  } +EXPORT_SYMBOL(caif_enroll_dev);  /* notify Caif of device events */  static int caif_device_notify(struct notifier_block *me, unsigned long what, -			      void *arg) +			      void *ptr)  { -	struct net_device *dev = arg; +	struct net_device *dev = netdev_notifier_info_to_dev(ptr);  	struct caif_device_entry *caifd = NULL;  	struct caif_dev_common *caifdev; -	enum cfcnfg_phy_preference pref; -	int res = -EINVAL; -	enum cfcnfg_phy_type phy_type; +	struct cfcnfg *cfg; +	struct cflayer *layer, *link_support; +	int head_room = 0; +	struct caif_device_entry_list *caifdevs; + +	cfg = get_cfcnfg(dev_net(dev)); +	caifdevs = caif_device_list(dev_net(dev)); -	if (dev->type != ARPHRD_CAIF) +	caifd = caif_get(dev); +	if (caifd == NULL && dev->type != ARPHRD_CAIF)  		return 0;  	switch (what) {  	case NETDEV_REGISTER: -		netdev_info(dev, "register\n"); -		caifd = caif_device_alloc(dev); -		if (caifd == NULL) +		if (caifd != NULL)  			break; +  		caifdev = netdev_priv(dev); + +		link_support = NULL; +		if (caifdev->use_frag) { +			head_room = 1; +			link_support = cfserl_create(dev->ifindex, +							caifdev->use_stx); +			if (!link_support) { +				pr_warn("Out of memory\n"); +				break; +			} +		} +		caif_enroll_dev(dev, caifdev, link_support, head_room, +				&layer, NULL);  		caifdev->flowctrl = dev_flowctrl; -		atomic_set(&caifd->state, what); -		res = 0;  		break;  	case NETDEV_UP: -		netdev_info(dev, "up\n"); +		rcu_read_lock(); +  		caifd = caif_get(dev); -		if (caifd == NULL) -			break; -		caifdev = netdev_priv(dev); -		if (atomic_read(&caifd->state) == NETDEV_UP) { -			netdev_info(dev, "already up\n"); +		if (caifd == NULL) { +			rcu_read_unlock();  			break;  		} -		atomic_set(&caifd->state, what); -		caifd->layer.transmit = transmit; -		caifd->layer.modemcmd = modemcmd; - -		if (caifdev->use_frag) -			phy_type = CFPHYTYPE_FRAG; -		else -			phy_type = CFPHYTYPE_CAIF; - -		switch (caifdev->link_select) { -		case CAIF_LINK_HIGH_BANDW: -			pref = CFPHYPREF_HIGH_BW; -			break; -		case CAIF_LINK_LOW_LATENCY: -			pref = CFPHYPREF_LOW_LAT; -			break; -		default: -			pref = CFPHYPREF_HIGH_BW; -			break; -		} -		dev_hold(dev); -		cfcnfg_add_phy_layer(get_caif_conf(), -				     phy_type, -				     dev, -				     &caifd->layer, -				     &caifd->phyid, -				     pref, -				     caifdev->use_fcs, -				     caifdev->use_stx); -		strncpy(caifd->layer.name, dev->name, -			sizeof(caifd->layer.name) - 1); -		caifd->layer.name[sizeof(caifd->layer.name) - 1] = 0; + +		caifd->xoff = 0; +		cfcnfg_set_phy_state(cfg, &caifd->layer, true); +		rcu_read_unlock(); +  		break; -	case NETDEV_GOING_DOWN: +	case NETDEV_DOWN: +		rcu_read_lock(); +  		caifd = caif_get(dev); -		if (caifd == NULL) -			break; -		netdev_info(dev, "going down\n"); +		if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd) { +			rcu_read_unlock(); +			return -EINVAL; +		} -		if (atomic_read(&caifd->state) == NETDEV_GOING_DOWN || -			atomic_read(&caifd->state) == NETDEV_DOWN) -			break; +		cfcnfg_set_phy_state(cfg, &caifd->layer, false); +		caifd_hold(caifd); +		rcu_read_unlock(); -		atomic_set(&caifd->state, what); -		if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd) -			return -EINVAL;  		caifd->layer.up->ctrlcmd(caifd->layer.up,  					 _CAIF_CTRLCMD_PHYIF_DOWN_IND,  					 caifd->layer.id); -		might_sleep(); -		res = wait_event_interruptible_timeout(caifd->event, -					atomic_read(&caifd->in_use) == 0, -					TIMEOUT); -		break; -	case NETDEV_DOWN: -		caifd = caif_get(dev); -		if (caifd == NULL) -			break; -		netdev_info(dev, "down\n"); -		if (atomic_read(&caifd->in_use)) -			netdev_warn(dev, -				    "Unregistering an active CAIF device\n"); -		cfcnfg_del_phy_layer(get_caif_conf(), &caifd->layer); -		dev_put(dev); -		atomic_set(&caifd->state, what); +		spin_lock_bh(&caifd->flow_lock); + +		/* +		 * Replace our xoff-destructor with original destructor. +		 * We trust that skb->destructor *always* is called before +		 * the skb reference is invalid. The hijacked SKB destructor +		 * takes the flow_lock so manipulating the skb->destructor here +		 * should be safe. +		*/ +		if (caifd->xoff_skb_dtor != NULL && caifd->xoff_skb != NULL) +			caifd->xoff_skb->destructor = caifd->xoff_skb_dtor; + +		caifd->xoff = 0; +		caifd->xoff_skb_dtor = NULL; +		caifd->xoff_skb = NULL; + +		spin_unlock_bh(&caifd->flow_lock); +		caifd_put(caifd);  		break;  	case NETDEV_UNREGISTER: +		mutex_lock(&caifdevs->lock); +  		caifd = caif_get(dev); -		if (caifd == NULL) +		if (caifd == NULL) { +			mutex_unlock(&caifdevs->lock);  			break; -		netdev_info(dev, "unregister\n"); -		atomic_set(&caifd->state, what); -		caif_device_destroy(dev); +		} +		list_del_rcu(&caifd->list); + +		/* +		 * NETDEV_UNREGISTER is called repeatedly until all reference +		 * counts for the net-device are released. If references to +		 * caifd is taken, simply ignore NETDEV_UNREGISTER and wait for +		 * the next call to NETDEV_UNREGISTER. +		 * +		 * If any packets are in flight down the CAIF Stack, +		 * cfcnfg_del_phy_layer will return nonzero. +		 * If no packets are in flight, the CAIF Stack associated +		 * with the net-device un-registering is freed. +		 */ + +		if (caifd_refcnt_read(caifd) != 0 || +			cfcnfg_del_phy_layer(cfg, &caifd->layer) != 0) { + +			pr_info("Wait for device inuse\n"); +			/* Enrole device if CAIF Stack is still in use */ +			list_add_rcu(&caifd->list, &caifdevs->list); +			mutex_unlock(&caifdevs->lock); +			break; +		} + +		synchronize_rcu(); +		dev_put(caifd->netdev); +		free_percpu(caifd->pcpu_refcnt); +		kfree(caifd); + +		mutex_unlock(&caifdevs->lock);  		break;  	}  	return 0; @@ -322,61 +492,51 @@ static struct notifier_block caif_device_notifier = {  	.priority = 0,  }; - -struct cfcnfg *get_caif_conf(void) -{ -	return cfg; -} -EXPORT_SYMBOL(get_caif_conf); - -int caif_connect_client(struct caif_connect_request *conn_req, -			struct cflayer *client_layer, int *ifindex, -			int *headroom, int *tailroom) -{ -	struct cfctrl_link_param param; -	int ret; -	ret = connect_req_to_link_param(get_caif_conf(), conn_req, ¶m); -	if (ret) -		return ret; -	/* Hook up the adaptation layer. */ -	return cfcnfg_add_adaptation_layer(get_caif_conf(), ¶m, -					client_layer, ifindex, -					headroom, tailroom); -} -EXPORT_SYMBOL(caif_connect_client); - -int caif_disconnect_client(struct cflayer *adap_layer) -{ -       return cfcnfg_disconn_adapt_layer(get_caif_conf(), adap_layer); -} -EXPORT_SYMBOL(caif_disconnect_client); - -void caif_release_client(struct cflayer *adap_layer) -{ -       cfcnfg_release_adap_layer(adap_layer); -} -EXPORT_SYMBOL(caif_release_client); -  /* Per-namespace Caif devices handling */  static int caif_init_net(struct net *net)  {  	struct caif_net *caifn = net_generic(net, caif_net_id);  	INIT_LIST_HEAD(&caifn->caifdevs.list); -	spin_lock_init(&caifn->caifdevs.lock); +	mutex_init(&caifn->caifdevs.lock); + +	caifn->cfg = cfcnfg_create(); +	if (!caifn->cfg) +		return -ENOMEM; +  	return 0;  }  static void caif_exit_net(struct net *net)  { -	struct net_device *dev; -	int res; +	struct caif_device_entry *caifd, *tmp; +	struct caif_device_entry_list *caifdevs = +	    caif_device_list(net); +	struct cfcnfg *cfg =  get_cfcnfg(net); +  	rtnl_lock(); -	for_each_netdev(net, dev) { -		if (dev->type != ARPHRD_CAIF) -			continue; -		res = dev_close(dev); -		caif_device_destroy(dev); +	mutex_lock(&caifdevs->lock); + +	list_for_each_entry_safe(caifd, tmp, &caifdevs->list, list) { +		int i = 0; +		list_del_rcu(&caifd->list); +		cfcnfg_set_phy_state(cfg, &caifd->layer, false); + +		while (i < 10 && +			(caifd_refcnt_read(caifd) != 0 || +			cfcnfg_del_phy_layer(cfg, &caifd->layer) != 0)) { + +			pr_info("Wait for device inuse\n"); +			msleep(250); +			i++; +		} +		synchronize_rcu(); +		dev_put(caifd->netdev); +		free_percpu(caifd->pcpu_refcnt); +		kfree(caifd);  	} +	cfcnfg_remove(cfg); + +	mutex_unlock(&caifdevs->lock);  	rtnl_unlock();  } @@ -391,32 +551,23 @@ static struct pernet_operations caif_net_ops = {  static int __init caif_device_init(void)  {  	int result; -	cfg = cfcnfg_create(); -	if (!cfg) { -		pr_warn("can't create cfcnfg\n"); -		goto err_cfcnfg_create_failed; -	} -	result = register_pernet_device(&caif_net_ops); -	if (result) { -		kfree(cfg); -		cfg = NULL; +	result = register_pernet_subsys(&caif_net_ops); + +	if (result)  		return result; -	} -	dev_add_pack(&caif_packet_type); +  	register_netdevice_notifier(&caif_device_notifier); +	dev_add_pack(&caif_packet_type);  	return result; -err_cfcnfg_create_failed: -	return -ENODEV;  }  static void __exit caif_device_exit(void)  { -	dev_remove_pack(&caif_packet_type); -	unregister_pernet_device(&caif_net_ops);  	unregister_netdevice_notifier(&caif_device_notifier); -	cfcnfg_remove(cfg); +	dev_remove_pack(&caif_packet_type); +	unregister_pernet_subsys(&caif_net_ops);  }  module_init(caif_device_init);  | 
