Commit bd30ce4b authored by sjur.brandeland@stericsson.com's avatar sjur.brandeland@stericsson.com Committed by David S. Miller

caif: Use RCU instead of spin-lock in caif_dev.c

RCU read_lock and refcount is used to protect in-flight packets.

Use RCU and counters to manage freeing lower part of the CAIF stack if
CAIF-link layer is removed. Old solution based on delaying removal of
device is removed.

When CAIF link layer goes down the use of CAIF link layer is disabled
(by calling caif_set_phy_state()), but removal and freeing of the
lower part of the CAIF stack is done when Link layer is unregistered.
Signed-off-by: default avatarSjur Brændeland <sjur.brandeland@stericsson.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 0b1e9738
...@@ -145,4 +145,14 @@ struct dev_info *cfcnfg_get_phyid(struct cfcnfg *cnfg, ...@@ -145,4 +145,14 @@ struct dev_info *cfcnfg_get_phyid(struct cfcnfg *cnfg,
* @ifi: ifindex obtained from socket.c bindtodevice. * @ifi: ifindex obtained from socket.c bindtodevice.
*/ */
int cfcnfg_get_id_from_ifi(struct cfcnfg *cnfg, int ifi); int cfcnfg_get_id_from_ifi(struct cfcnfg *cnfg, int ifi);
/**
* cfcnfg_set_phy_state() - Set the state of the physical interface device.
* @cnfg: Configuration object
* @phy_layer: Physical Layer representation
* @up: State of device
*/
int cfcnfg_set_phy_state(struct cfcnfg *cnfg, struct cflayer *phy_layer,
bool up);
#endif /* CFCNFG_H_ */ #endif /* CFCNFG_H_ */
...@@ -12,14 +12,11 @@ ...@@ -12,14 +12,11 @@
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__ #define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
#include <linux/version.h> #include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/if_arp.h> #include <linux/if_arp.h>
#include <linux/net.h> #include <linux/net.h>
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/skbuff.h> #include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <net/netns/generic.h> #include <net/netns/generic.h>
#include <net/net_namespace.h> #include <net/net_namespace.h>
#include <net/pkt_sched.h> #include <net/pkt_sched.h>
...@@ -30,23 +27,19 @@ ...@@ -30,23 +27,19 @@
#include <net/caif/cfcnfg.h> #include <net/caif/cfcnfg.h>
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
#define TIMEOUT (HZ*5)
/* Used for local tracking of the CAIF net devices */ /* Used for local tracking of the CAIF net devices */
struct caif_device_entry { struct caif_device_entry {
struct cflayer layer; struct cflayer layer;
struct list_head list; struct list_head list;
atomic_t in_use;
atomic_t state;
u16 phyid;
struct net_device *netdev; struct net_device *netdev;
wait_queue_head_t event; int __percpu *pcpu_refcnt;
}; };
struct caif_device_entry_list { struct caif_device_entry_list {
struct list_head list; struct list_head list;
/* Protects simulanous deletes in list */ /* Protects simulanous deletes in list */
spinlock_t lock; struct mutex lock;
}; };
struct caif_net { struct caif_net {
...@@ -65,19 +58,39 @@ static struct caif_device_entry_list *caif_device_list(struct net *net) ...@@ -65,19 +58,39 @@ static struct caif_device_entry_list *caif_device_list(struct net *net)
return &caifn->caifdevs; return &caifn->caifdevs;
} }
static void caifd_put(struct caif_device_entry *e)
{
irqsafe_cpu_dec(*e->pcpu_refcnt);
}
static void caifd_hold(struct caif_device_entry *e)
{
irqsafe_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. */ /* Allocate new CAIF device. */
static struct caif_device_entry *caif_device_alloc(struct net_device *dev) static struct caif_device_entry *caif_device_alloc(struct net_device *dev)
{ {
struct caif_device_entry_list *caifdevs; struct caif_device_entry_list *caifdevs;
struct caif_device_entry *caifd; struct caif_device_entry *caifd;
caifdevs = caif_device_list(dev_net(dev)); caifdevs = caif_device_list(dev_net(dev));
BUG_ON(!caifdevs); BUG_ON(!caifdevs);
caifd = kzalloc(sizeof(*caifd), GFP_ATOMIC); caifd = kzalloc(sizeof(*caifd), GFP_ATOMIC);
if (!caifd) if (!caifd)
return NULL; return NULL;
caifd->pcpu_refcnt = alloc_percpu(int);
caifd->netdev = dev; caifd->netdev = dev;
list_add(&caifd->list, &caifdevs->list); dev_hold(dev);
init_waitqueue_head(&caifd->event);
return caifd; return caifd;
} }
...@@ -87,35 +100,13 @@ static struct caif_device_entry *caif_get(struct net_device *dev) ...@@ -87,35 +100,13 @@ static struct caif_device_entry *caif_get(struct net_device *dev)
caif_device_list(dev_net(dev)); caif_device_list(dev_net(dev));
struct caif_device_entry *caifd; struct caif_device_entry *caifd;
BUG_ON(!caifdevs); BUG_ON(!caifdevs);
list_for_each_entry(caifd, &caifdevs->list, list) { list_for_each_entry_rcu(caifd, &caifdevs->list, list) {
if (caifd->netdev == dev) if (caifd->netdev == dev)
return caifd; return caifd;
} }
return NULL; return NULL;
} }
static void caif_device_destroy(struct net_device *dev)
{
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;
spin_lock_bh(&caifdevs->lock);
caifd = caif_get(dev);
if (caifd == NULL) {
spin_unlock_bh(&caifdevs->lock);
return;
}
list_del(&caifd->list);
spin_unlock_bh(&caifdevs->lock);
kfree(caifd);
}
static int transmit(struct cflayer *layer, struct cfpkt *pkt) static int transmit(struct cflayer *layer, struct cfpkt *pkt)
{ {
struct caif_device_entry *caifd = struct caif_device_entry *caifd =
...@@ -130,23 +121,8 @@ static int transmit(struct cflayer *layer, struct cfpkt *pkt) ...@@ -130,23 +121,8 @@ static int transmit(struct cflayer *layer, struct cfpkt *pkt)
return 0; return 0;
} }
static int modemcmd(struct cflayer *layr, enum caif_modemcmd ctrl)
{
struct caif_device_entry *caifd;
caifd = container_of(layr, struct caif_device_entry, layer);
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;
}
/* /*
* Stuff received packets to associated sockets. * Stuff received packets into the CAIF stack.
* On error, returns non-zero and releases the skb. * On error, returns non-zero and releases the skb.
*/ */
static int receive(struct sk_buff *skb, struct net_device *dev, static int receive(struct sk_buff *skb, struct net_device *dev,
...@@ -154,14 +130,27 @@ static int receive(struct sk_buff *skb, struct net_device *dev, ...@@ -154,14 +130,27 @@ static int receive(struct sk_buff *skb, struct net_device *dev,
{ {
struct cfpkt *pkt; struct cfpkt *pkt;
struct caif_device_entry *caifd; struct caif_device_entry *caifd;
pkt = cfpkt_fromnative(CAIF_DIR_IN, skb); pkt = cfpkt_fromnative(CAIF_DIR_IN, skb);
rcu_read_lock();
caifd = caif_get(dev); 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 NET_RX_DROP;
}
/* Hold reference to netdevice while using CAIF stack */
caifd_hold(caifd);
rcu_read_unlock();
caifd->layer.up->receive(caifd->layer.up, pkt);
/* Release reference to stack upwards */
caifd_put(caifd);
return 0; return 0;
} }
...@@ -172,15 +161,25 @@ static struct packet_type caif_packet_type __read_mostly = { ...@@ -172,15 +161,25 @@ static struct packet_type caif_packet_type __read_mostly = {
static void dev_flowctrl(struct net_device *dev, int on) static void dev_flowctrl(struct net_device *dev, int on)
{ {
struct caif_device_entry *caifd = caif_get(dev); struct caif_device_entry *caifd;
if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd)
rcu_read_lock();
caifd = caif_get(dev);
if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd) {
rcu_read_unlock();
return; return;
}
caifd_hold(caifd);
rcu_read_unlock();
caifd->layer.up->ctrlcmd(caifd->layer.up, caifd->layer.up->ctrlcmd(caifd->layer.up,
on ? on ?
_CAIF_CTRLCMD_PHYIF_FLOW_ON_IND : _CAIF_CTRLCMD_PHYIF_FLOW_ON_IND :
_CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND, _CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND,
caifd->layer.id); caifd->layer.id);
caifd_put(caifd);
} }
/* notify Caif of device events */ /* notify Caif of device events */
...@@ -192,34 +191,22 @@ static int caif_device_notify(struct notifier_block *me, unsigned long what, ...@@ -192,34 +191,22 @@ static int caif_device_notify(struct notifier_block *me, unsigned long what,
struct caif_dev_common *caifdev; struct caif_dev_common *caifdev;
enum cfcnfg_phy_preference pref; enum cfcnfg_phy_preference pref;
enum cfcnfg_phy_type phy_type; enum cfcnfg_phy_type phy_type;
struct caif_device_entry_list *caifdevs =
caif_device_list(dev_net(dev));
if (dev->type != ARPHRD_CAIF) if (dev->type != ARPHRD_CAIF)
return 0; return 0;
switch (what) { switch (what) {
case NETDEV_REGISTER: case NETDEV_REGISTER:
netdev_info(dev, "register\n");
caifd = caif_device_alloc(dev); caifd = caif_device_alloc(dev);
if (caifd == NULL) if (!caifd)
break; return 0;
caifdev = netdev_priv(dev); caifdev = netdev_priv(dev);
caifdev->flowctrl = dev_flowctrl; caifdev->flowctrl = dev_flowctrl;
atomic_set(&caifd->state, what);
break;
case NETDEV_UP:
netdev_info(dev, "up\n");
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");
break;
}
atomic_set(&caifd->state, what);
caifd->layer.transmit = transmit; caifd->layer.transmit = transmit;
caifd->layer.modemcmd = modemcmd;
if (caifdev->use_frag) if (caifdev->use_frag)
phy_type = CFPHYTYPE_FRAG; phy_type = CFPHYTYPE_FRAG;
...@@ -237,62 +224,95 @@ static int caif_device_notify(struct notifier_block *me, unsigned long what, ...@@ -237,62 +224,95 @@ static int caif_device_notify(struct notifier_block *me, unsigned long what,
pref = CFPHYPREF_HIGH_BW; pref = CFPHYPREF_HIGH_BW;
break; break;
} }
dev_hold(dev); strncpy(caifd->layer.name, dev->name,
sizeof(caifd->layer.name) - 1);
caifd->layer.name[sizeof(caifd->layer.name) - 1] = 0;
mutex_lock(&caifdevs->lock);
list_add_rcu(&caifd->list, &caifdevs->list);
cfcnfg_add_phy_layer(cfg, cfcnfg_add_phy_layer(cfg,
phy_type, phy_type,
dev, dev,
&caifd->layer, &caifd->layer,
&caifd->phyid, 0,
pref, pref,
caifdev->use_fcs, caifdev->use_fcs,
caifdev->use_stx); caifdev->use_stx);
strncpy(caifd->layer.name, dev->name, mutex_unlock(&caifdevs->lock);
sizeof(caifd->layer.name) - 1);
caifd->layer.name[sizeof(caifd->layer.name) - 1] = 0;
break; break;
case NETDEV_GOING_DOWN: case NETDEV_UP:
rcu_read_lock();
caifd = caif_get(dev); caifd = caif_get(dev);
if (caifd == NULL) if (caifd == NULL) {
rcu_read_unlock();
break; break;
netdev_info(dev, "going down\n"); }
if (atomic_read(&caifd->state) == NETDEV_GOING_DOWN || cfcnfg_set_phy_state(cfg, &caifd->layer, true);
atomic_read(&caifd->state) == NETDEV_DOWN) rcu_read_unlock();
break;
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();
wait_event_interruptible_timeout(caifd->event,
atomic_read(&caifd->in_use) == 0,
TIMEOUT);
break; break;
case NETDEV_DOWN: case NETDEV_DOWN:
rcu_read_lock();
caifd = caif_get(dev); caifd = caif_get(dev);
if (caifd == NULL) if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd) {
break; rcu_read_unlock();
netdev_info(dev, "down\n"); return -EINVAL;
if (atomic_read(&caifd->in_use)) }
netdev_warn(dev,
"Unregistering an active CAIF device\n"); cfcnfg_set_phy_state(cfg, &caifd->layer, false);
cfcnfg_del_phy_layer(cfg, &caifd->layer); caifd_hold(caifd);
dev_put(dev); rcu_read_unlock();
atomic_set(&caifd->state, what);
caifd->layer.up->ctrlcmd(caifd->layer.up,
_CAIF_CTRLCMD_PHYIF_DOWN_IND,
caifd->layer.id);
caifd_put(caifd);
break; break;
case NETDEV_UNREGISTER: case NETDEV_UNREGISTER:
mutex_lock(&caifdevs->lock);
caifd = caif_get(dev); caifd = caif_get(dev);
if (caifd == NULL) if (caifd == NULL) {
mutex_unlock(&caifdevs->lock);
break; break;
netdev_info(dev, "unregister\n"); }
atomic_set(&caifd->state, what); list_del_rcu(&caifd->list);
caif_device_destroy(dev);
/*
* 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; break;
} }
return 0; return 0;
...@@ -304,8 +324,8 @@ static struct notifier_block caif_device_notifier = { ...@@ -304,8 +324,8 @@ static struct notifier_block caif_device_notifier = {
}; };
int caif_connect_client(struct caif_connect_request *conn_req, int caif_connect_client(struct caif_connect_request *conn_req,
struct cflayer *client_layer, int *ifindex, struct cflayer *client_layer, int *ifindex,
int *headroom, int *tailroom) int *headroom, int *tailroom)
{ {
struct cfctrl_link_param param; struct cfctrl_link_param param;
int ret; int ret;
...@@ -315,8 +335,8 @@ int caif_connect_client(struct caif_connect_request *conn_req, ...@@ -315,8 +335,8 @@ int caif_connect_client(struct caif_connect_request *conn_req,
return ret; return ret;
/* Hook up the adaptation layer. */ /* Hook up the adaptation layer. */
return cfcnfg_add_adaptation_layer(cfg, &param, return cfcnfg_add_adaptation_layer(cfg, &param,
client_layer, ifindex, client_layer, ifindex,
headroom, tailroom); headroom, tailroom);
} }
EXPORT_SYMBOL(caif_connect_client); EXPORT_SYMBOL(caif_connect_client);
...@@ -331,20 +351,40 @@ static int caif_init_net(struct net *net) ...@@ -331,20 +351,40 @@ static int caif_init_net(struct net *net)
{ {
struct caif_net *caifn = net_generic(net, caif_net_id); struct caif_net *caifn = net_generic(net, caif_net_id);
INIT_LIST_HEAD(&caifn->caifdevs.list); INIT_LIST_HEAD(&caifn->caifdevs.list);
spin_lock_init(&caifn->caifdevs.lock); mutex_init(&caifn->caifdevs.lock);
return 0; return 0;
} }
static void caif_exit_net(struct net *net) static void caif_exit_net(struct net *net)
{ {
struct net_device *dev; struct caif_device_entry *caifd, *tmp;
struct caif_device_entry_list *caifdevs =
caif_device_list(net);
rtnl_lock(); rtnl_lock();
for_each_netdev(net, dev) { mutex_lock(&caifdevs->lock);
if (dev->type != ARPHRD_CAIF)
continue; list_for_each_entry_safe(caifd, tmp, &caifdevs->list, list) {
dev_close(dev); int i = 0;
caif_device_destroy(dev); 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);
} }
mutex_unlock(&caifdevs->lock);
rtnl_unlock(); rtnl_unlock();
} }
...@@ -359,6 +399,7 @@ static struct pernet_operations caif_net_ops = { ...@@ -359,6 +399,7 @@ static struct pernet_operations caif_net_ops = {
static int __init caif_device_init(void) static int __init caif_device_init(void)
{ {
int result; int result;
cfg = cfcnfg_create(); cfg = cfcnfg_create();
if (!cfg) { if (!cfg) {
pr_warn("can't create cfcnfg\n"); pr_warn("can't create cfcnfg\n");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment