diff options
Diffstat (limited to 'net/caif')
-rw-r--r-- | net/caif/Kconfig | 42 | ||||
-rw-r--r-- | net/caif/Makefile | 16 | ||||
-rw-r--r-- | net/caif/caif_config_util.c | 92 | ||||
-rw-r--r-- | net/caif/caif_dev.c | 421 | ||||
-rw-r--r-- | net/caif/caif_socket.c | 1253 | ||||
-rw-r--r-- | net/caif/cfcnfg.c | 504 | ||||
-rw-r--r-- | net/caif/cfctrl.c | 652 | ||||
-rw-r--r-- | net/caif/cfdbgl.c | 40 | ||||
-rw-r--r-- | net/caif/cfdgml.c | 113 | ||||
-rw-r--r-- | net/caif/cffrml.c | 151 | ||||
-rw-r--r-- | net/caif/cfmuxl.c | 252 | ||||
-rw-r--r-- | net/caif/cfpkt_skbuff.c | 579 | ||||
-rw-r--r-- | net/caif/cfrfml.c | 310 | ||||
-rw-r--r-- | net/caif/cfserl.c | 196 | ||||
-rw-r--r-- | net/caif/cfsrvl.c | 210 | ||||
-rw-r--r-- | net/caif/cfutill.c | 109 | ||||
-rw-r--r-- | net/caif/cfveil.c | 102 | ||||
-rw-r--r-- | net/caif/cfvidl.c | 65 | ||||
-rw-r--r-- | net/caif/chnl_net.c | 514 |
19 files changed, 5621 insertions, 0 deletions
diff --git a/net/caif/Kconfig b/net/caif/Kconfig new file mode 100644 index 00000000000..529750da962 --- /dev/null +++ b/net/caif/Kconfig @@ -0,0 +1,42 @@ +# +# CAIF net configurations +# + +menuconfig CAIF + tristate "CAIF support" + select CRC_CCITT + default n + ---help--- + The "Communication CPU to Application CPU Interface" (CAIF) is a packet + based connection-oriented MUX protocol developed by ST-Ericsson for use + with its modems. It is accessed from user space as sockets (PF_CAIF). + + Say Y (or M) here if you build for a phone product (e.g. Android or + MeeGo ) that uses CAIF as transport, if unsure say N. + + If you select to build it as module then CAIF_NETDEV also needs to be + built as modules. You will also need to say yes to any CAIF physical + devices that your platform requires. + + See Documentation/networking/caif for a further explanation on how to + use and configure CAIF. + +config CAIF_DEBUG + bool "Enable Debug" + depends on CAIF + default n + --- help --- + Enable the inclusion of debug code in the CAIF stack. + Be aware that doing this will impact performance. + If unsure say N. + +config CAIF_NETDEV + tristate "CAIF GPRS Network device" + depends on CAIF + default CAIF + ---help--- + Say Y if you will be using a CAIF based GPRS network device. + This can be either built-in or a loadable module, + If you select to build it as a built-in then the main CAIF device must + also be a built-in. + If unsure say Y. diff --git a/net/caif/Makefile b/net/caif/Makefile new file mode 100644 index 00000000000..f87481fb0e6 --- /dev/null +++ b/net/caif/Makefile @@ -0,0 +1,16 @@ +ifeq ($(CONFIG_CAIF_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif + +caif-objs := caif_dev.o \ + cfcnfg.o cfmuxl.o cfctrl.o \ + cffrml.o cfveil.o cfdbgl.o\ + cfserl.o cfdgml.o \ + cfrfml.o cfvidl.o cfutill.o \ + cfsrvl.o cfpkt_skbuff.o caif_config_util.o + +obj-$(CONFIG_CAIF) += caif.o +obj-$(CONFIG_CAIF_NETDEV) += chnl_net.o +obj-$(CONFIG_CAIF) += caif_socket.o + +export-objs := caif.o diff --git a/net/caif/caif_config_util.c b/net/caif/caif_config_util.c new file mode 100644 index 00000000000..76ae68303d3 --- /dev/null +++ b/net/caif/caif_config_util.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * Author: Sjur Brendeland sjur.brandeland@stericsson.com + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/module.h> +#include <linux/spinlock.h> +#include <net/caif/cfctrl.h> +#include <net/caif/cfcnfg.h> +#include <net/caif/caif_dev.h> + +int connect_req_to_link_param(struct cfcnfg *cnfg, + struct caif_connect_request *s, + struct cfctrl_link_param *l) +{ + struct dev_info *dev_info; + enum cfcnfg_phy_preference pref; + memset(l, 0, sizeof(*l)); + l->priority = s->priority; + + if (s->link_name[0] != '\0') + l->phyid = cfcnfg_get_named(cnfg, s->link_name); + else { + switch (s->link_selector) { + case CAIF_LINK_HIGH_BANDW: + pref = CFPHYPREF_HIGH_BW; + break; + case CAIF_LINK_LOW_LATENCY: + pref = CFPHYPREF_LOW_LAT; + break; + default: + return -EINVAL; + } + dev_info = cfcnfg_get_phyid(cnfg, pref); + if (dev_info == NULL) + return -ENODEV; + l->phyid = dev_info->id; + } + switch (s->protocol) { + case CAIFPROTO_AT: + l->linktype = CFCTRL_SRV_VEI; + if (s->sockaddr.u.at.type == CAIF_ATTYPE_PLAIN) + l->chtype = 0x02; + else + l->chtype = s->sockaddr.u.at.type; + l->endpoint = 0x00; + break; + case CAIFPROTO_DATAGRAM: + l->linktype = CFCTRL_SRV_DATAGRAM; + l->chtype = 0x00; + l->u.datagram.connid = s->sockaddr.u.dgm.connection_id; + break; + case CAIFPROTO_DATAGRAM_LOOP: + l->linktype = CFCTRL_SRV_DATAGRAM; + l->chtype = 0x03; + l->endpoint = 0x00; + l->u.datagram.connid = s->sockaddr.u.dgm.connection_id; + break; + case CAIFPROTO_RFM: + l->linktype = CFCTRL_SRV_RFM; + l->u.datagram.connid = s->sockaddr.u.rfm.connection_id; + strncpy(l->u.rfm.volume, s->sockaddr.u.rfm.volume, + sizeof(l->u.rfm.volume)-1); + l->u.rfm.volume[sizeof(l->u.rfm.volume)-1] = 0; + break; + case CAIFPROTO_UTIL: + l->linktype = CFCTRL_SRV_UTIL; + l->endpoint = 0x00; + l->chtype = 0x00; + strncpy(l->u.utility.name, s->sockaddr.u.util.service, + sizeof(l->u.utility.name)-1); + l->u.utility.name[sizeof(l->u.utility.name)-1] = 0; + caif_assert(sizeof(l->u.utility.name) > 10); + l->u.utility.paramlen = s->param.size; + if (l->u.utility.paramlen > sizeof(l->u.utility.params)) + l->u.utility.paramlen = sizeof(l->u.utility.params); + + memcpy(l->u.utility.params, s->param.data, + l->u.utility.paramlen); + + break; + case CAIFPROTO_DEBUG: + l->linktype = CFCTRL_SRV_DBG; + l->endpoint = s->sockaddr.u.dbg.service; + l->chtype = s->sockaddr.u.dbg.type; + break; + default: + return -EINVAL; + } + return 0; +} diff --git a/net/caif/caif_dev.c b/net/caif/caif_dev.c new file mode 100644 index 00000000000..0b586e9d137 --- /dev/null +++ b/net/caif/caif_dev.c @@ -0,0 +1,421 @@ +/* + * CAIF Interface registration. + * Copyright (C) ST-Ericsson AB 2010 + * Author: Sjur Brendeland/sjur.brandeland@stericsson.com + * 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> + * and Sakari Ailus <sakari.ailus@nokia.com> + */ + +#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 <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/cfpkt.h> +#include <net/caif/cfcnfg.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; +}; + +struct caif_device_entry_list { + struct list_head list; + /* Protects simulanous deletes in list */ + spinlock_t lock; +}; + +struct caif_net { + struct caif_device_entry_list caifdevs; +}; + +static int caif_net_id; +static struct cfcnfg *cfg; + +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; +} + +/* 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); + if (!caifd) + return NULL; + caifd->netdev = dev; + list_add(&caifd->list, &caifdevs->list); + init_waitqueue_head(&caifd->event); + return caifd; +} + +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) { + if (caifd->netdev == dev) + return caifd; + } + 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) +{ + struct caif_device_entry *caifd = + container_of(layer, struct caif_device_entry, layer); + struct sk_buff *skb, *skb2; + int ret = -EINVAL; + skb = cfpkt_tonative(pkt); + skb->dev = caifd->netdev; + /* + * 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. + */ + if (netif_queue_stopped(caifd->netdev)) + return -EAGAIN; + skb2 = skb_get(skb); + + ret = dev_queue_xmit(skb2); + + if (!ret) + kfree_skb(skb); + else + return -EAGAIN; + + return 0; +} + +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; +} + +/* + * Stuff received packets to associated sockets. + * 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); + pkt = cfpkt_fromnative(CAIF_DIR_IN, skb); + caifd = caif_get(dev); + if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd) + return NET_RX_DROP; + + if (caifd->layer.up->receive(caifd->layer.up, pkt)) + return NET_RX_DROP; + + return 0; +} + +static struct packet_type caif_packet_type __read_mostly = { + .type = cpu_to_be16(ETH_P_CAIF), + .func = receive, +}; + +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) + return; + + caifd->layer.up->ctrlcmd(caifd->layer.up, + on ? + _CAIF_CTRLCMD_PHYIF_FLOW_ON_IND : + _CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND, + caifd->layer.id); +} + +/* notify Caif of device events */ +static int caif_device_notify(struct notifier_block *me, unsigned long what, + void *arg) +{ + struct net_device *dev = arg; + 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; + + if (dev->type != ARPHRD_CAIF) + return 0; + + switch (what) { + case NETDEV_REGISTER: + pr_info("CAIF: %s():register %s\n", __func__, dev->name); + caifd = caif_device_alloc(dev); + if (caifd == NULL) + break; + caifdev = netdev_priv(dev); + caifdev->flowctrl = dev_flowctrl; + atomic_set(&caifd->state, what); + res = 0; + break; + + case NETDEV_UP: + pr_info("CAIF: %s(): up %s\n", __func__, dev->name); + caifd = caif_get(dev); + if (caifd == NULL) + break; + caifdev = netdev_priv(dev); + if (atomic_read(&caifd->state) == NETDEV_UP) { + pr_info("CAIF: %s():%s already up\n", + __func__, dev->name); + 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; + break; + + case NETDEV_GOING_DOWN: + caifd = caif_get(dev); + if (caifd == NULL) + break; + pr_info("CAIF: %s():going down %s\n", __func__, dev->name); + + if (atomic_read(&caifd->state) == NETDEV_GOING_DOWN || + atomic_read(&caifd->state) == NETDEV_DOWN) + 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(); + 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; + pr_info("CAIF: %s(): down %s\n", __func__, dev->name); + if (atomic_read(&caifd->in_use)) + pr_warning("CAIF: %s(): " + "Unregistering an active CAIF device: %s\n", + __func__, dev->name); + cfcnfg_del_phy_layer(get_caif_conf(), &caifd->layer); + dev_put(dev); + atomic_set(&caifd->state, what); + break; + + case NETDEV_UNREGISTER: + caifd = caif_get(dev); + pr_info("CAIF: %s(): unregister %s\n", __func__, dev->name); + atomic_set(&caifd->state, what); + caif_device_destroy(dev); + break; + } + return 0; +} + +static struct notifier_block caif_device_notifier = { + .notifier_call = caif_device_notify, + .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); + return 0; +} + +static void caif_exit_net(struct net *net) +{ + struct net_device *dev; + int res; + rtnl_lock(); + for_each_netdev(net, dev) { + if (dev->type != ARPHRD_CAIF) + continue; + res = dev_close(dev); + caif_device_destroy(dev); + } + rtnl_unlock(); +} + +static struct pernet_operations caif_net_ops = { + .init = caif_init_net, + .exit = caif_exit_net, + .id = &caif_net_id, + .size = sizeof(struct caif_net), +}; + +/* Initialize Caif devices list */ +static int __init caif_device_init(void) +{ + int result; + cfg = cfcnfg_create(); + if (!cfg) { + pr_warning("CAIF: %s(): can't create cfcnfg.\n", __func__); + goto err_cfcnfg_create_failed; + } + result = register_pernet_device(&caif_net_ops); + + if (result) { + kfree(cfg); + cfg = NULL; + return result; + } + dev_add_pack(&caif_packet_type); + register_netdevice_notifier(&caif_device_notifier); + + 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); +} + +module_init(caif_device_init); +module_exit(caif_device_exit); diff --git a/net/caif/caif_socket.c b/net/caif/caif_socket.c new file mode 100644 index 00000000000..4bf28f25f36 --- /dev/null +++ b/net/caif/caif_socket.c @@ -0,0 +1,1253 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * Author: Sjur Brendeland sjur.brandeland@stericsson.com + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/wait.h> +#include <linux/poll.h> +#include <linux/tcp.h> +#include <linux/uaccess.h> +#include <linux/mutex.h> +#include <linux/debugfs.h> +#include <linux/caif/caif_socket.h> +#include <asm/atomic.h> +#include <net/sock.h> +#include <net/tcp_states.h> +#include <net/caif/caif_layer.h> +#include <net/caif/caif_dev.h> +#include <net/caif/cfpkt.h> + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_NETPROTO(AF_CAIF); + +#define CAIF_DEF_SNDBUF (4096*10) +#define CAIF_DEF_RCVBUF (4096*100) + +/* + * CAIF state is re-using the TCP socket states. + * caif_states stored in sk_state reflect the state as reported by + * the CAIF stack, while sk_socket->state is the state of the socket. + */ +enum caif_states { + CAIF_CONNECTED = TCP_ESTABLISHED, + CAIF_CONNECTING = TCP_SYN_SENT, + CAIF_DISCONNECTED = TCP_CLOSE +}; + +#define TX_FLOW_ON_BIT 1 +#define RX_FLOW_ON_BIT 2 + +static struct dentry *debugfsdir; + +#ifdef CONFIG_DEBUG_FS +struct debug_fs_counter { + atomic_t caif_nr_socks; + atomic_t num_connect_req; + atomic_t num_connect_resp; + atomic_t num_connect_fail_resp; + atomic_t num_disconnect; + atomic_t num_remote_shutdown_ind; + atomic_t num_tx_flow_off_ind; + atomic_t num_tx_flow_on_ind; + atomic_t num_rx_flow_off; + atomic_t num_rx_flow_on; +}; +static struct debug_fs_counter cnt; +#define dbfs_atomic_inc(v) atomic_inc(v) +#define dbfs_atomic_dec(v) atomic_dec(v) +#else +#define dbfs_atomic_inc(v) +#define dbfs_atomic_dec(v) +#endif + +struct caifsock { + struct sock sk; /* must be first member */ + struct cflayer layer; + char name[CAIF_LAYER_NAME_SZ]; /* Used for debugging */ + u32 flow_state; + struct caif_connect_request conn_req; + struct mutex readlock; + struct dentry *debugfs_socket_dir; + int headroom, tailroom, maxframe; +}; + +static int rx_flow_is_on(struct caifsock *cf_sk) +{ + return test_bit(RX_FLOW_ON_BIT, + (void *) &cf_sk->flow_state); +} + +static int tx_flow_is_on(struct caifsock *cf_sk) +{ + return test_bit(TX_FLOW_ON_BIT, + (void *) &cf_sk->flow_state); +} + +static void set_rx_flow_off(struct caifsock *cf_sk) +{ + clear_bit(RX_FLOW_ON_BIT, + (void *) &cf_sk->flow_state); +} + +static void set_rx_flow_on(struct caifsock *cf_sk) +{ + set_bit(RX_FLOW_ON_BIT, + (void *) &cf_sk->flow_state); +} + +static void set_tx_flow_off(struct caifsock *cf_sk) +{ + clear_bit(TX_FLOW_ON_BIT, + (void *) &cf_sk->flow_state); +} + +static void set_tx_flow_on(struct caifsock *cf_sk) +{ + set_bit(TX_FLOW_ON_BIT, + (void *) &cf_sk->flow_state); +} + +static void caif_read_lock(struct sock *sk) +{ + struct caifsock *cf_sk; + cf_sk = container_of(sk, struct caifsock, sk); + mutex_lock(&cf_sk->readlock); +} + +static void caif_read_unlock(struct sock *sk) +{ + struct caifsock *cf_sk; + cf_sk = container_of(sk, struct caifsock, sk); + mutex_unlock(&cf_sk->readlock); +} + +static int sk_rcvbuf_lowwater(struct caifsock *cf_sk) +{ + /* A quarter of full buffer is used a low water mark */ + return cf_sk->sk.sk_rcvbuf / 4; +} + +static void caif_flow_ctrl(struct sock *sk, int mode) +{ + struct caifsock *cf_sk; + cf_sk = container_of(sk, struct caifsock, sk); + if (cf_sk->layer.dn && cf_sk->layer.dn->modemcmd) + cf_sk->layer.dn->modemcmd(cf_sk->layer.dn, mode); +} + +/* + * Copied from sock.c:sock_queue_rcv_skb(), but changed so packets are + * not dropped, but CAIF is sending flow off instead. + */ +static int caif_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + int err; + int skb_len; + unsigned long flags; + struct sk_buff_head *list = &sk->sk_receive_queue; + struct caifsock *cf_sk = container_of(sk, struct caifsock, sk); + + if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >= + (unsigned)sk->sk_rcvbuf && rx_flow_is_on(cf_sk)) { + trace_printk("CAIF: %s():" + " sending flow OFF (queue len = %d %d)\n", + __func__, + atomic_read(&cf_sk->sk.sk_rmem_alloc), + sk_rcvbuf_lowwater(cf_sk)); + set_rx_flow_off(cf_sk); + dbfs_atomic_inc(&cnt.num_rx_flow_off); + caif_flow_ctrl(sk, CAIF_MODEMCMD_FLOW_OFF_REQ); + } + + err = sk_filter(sk, skb); + if (err) + return err; + if (!sk_rmem_schedule(sk, skb->truesize) && rx_flow_is_on(cf_sk)) { + set_rx_flow_off(cf_sk); + trace_printk("CAIF: %s():" + " sending flow OFF due to rmem_schedule\n", + __func__); + dbfs_atomic_inc(&cnt.num_rx_flow_off); + caif_flow_ctrl(sk, CAIF_MODEMCMD_FLOW_OFF_REQ); + } + skb->dev = NULL; + skb_set_owner_r(skb, sk); + /* Cache the SKB length before we tack it onto the receive + * queue. Once it is added it no longer belongs to us and + * may be freed by other threads of control pulling packets + * from the queue. + */ + skb_len = skb->len; + spin_lock_irqsave(&list->lock, flags); + if (!sock_flag(sk, SOCK_DEAD)) + __skb_queue_tail(list, skb); + spin_unlock_irqrestore(&list->lock, flags); + + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_data_ready(sk, skb_len); + else + kfree_skb(skb); + return 0; +} + +/* Packet Receive Callback function called from CAIF Stack */ +static int caif_sktrecv_cb(struct cflayer *layr, struct cfpkt *pkt) +{ + struct caifsock *cf_sk; + struct sk_buff *skb; + + cf_sk = container_of(layr, struct caifsock, layer); + skb = cfpkt_tonative(pkt); + + if (unlikely(cf_sk->sk.sk_state != CAIF_CONNECTED)) { + cfpkt_destroy(pkt); + return 0; + } + caif_queue_rcv_skb(&cf_sk->sk, skb); + return 0; +} + +/* Packet Control Callback function called from CAIF */ +static void caif_ctrl_cb(struct cflayer *layr, + enum caif_ctrlcmd flow, + int phyid) +{ + struct caifsock *cf_sk = container_of(layr, struct caifsock, layer); + switch (flow) { + case CAIF_CTRLCMD_FLOW_ON_IND: + /* OK from modem to start sending again */ + dbfs_atomic_inc(&cnt.num_tx_flow_on_ind); + set_tx_flow_on(cf_sk); + cf_sk->sk.sk_state_change(&cf_sk->sk); + break; + + case CAIF_CTRLCMD_FLOW_OFF_IND: + /* Modem asks us to shut up */ + dbfs_atomic_inc(&cnt.num_tx_flow_off_ind); + set_tx_flow_off(cf_sk); + cf_sk->sk.sk_state_change(&cf_sk->sk); + break; + + case CAIF_CTRLCMD_INIT_RSP: + /* We're now connected */ + dbfs_atomic_inc(&cnt.num_connect_resp); + cf_sk->sk.sk_state = CAIF_CONNECTED; + set_tx_flow_on(cf_sk); + cf_sk->sk.sk_state_change(&cf_sk->sk); + break; + + case CAIF_CTRLCMD_DEINIT_RSP: + /* We're now disconnected */ + cf_sk->sk.sk_state = CAIF_DISCONNECTED; + cf_sk->sk.sk_state_change(&cf_sk->sk); + cfcnfg_release_adap_layer(&cf_sk->layer); + break; + + case CAIF_CTRLCMD_INIT_FAIL_RSP: + /* Connect request failed */ + dbfs_atomic_inc(&cnt.num_connect_fail_resp); + cf_sk->sk.sk_err = ECONNREFUSED; + cf_sk->sk.sk_state = CAIF_DISCONNECTED; + cf_sk->sk.sk_shutdown = SHUTDOWN_MASK; + /* + * Socket "standards" seems to require POLLOUT to + * be set at connect failure. + */ + set_tx_flow_on(cf_sk); + cf_sk->sk.sk_state_change(&cf_sk->sk); + break; + + case CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND: + /* Modem has closed this connection, or device is down. */ + dbfs_atomic_inc(&cnt.num_remote_shutdown_ind); + cf_sk->sk.sk_shutdown = SHUTDOWN_MASK; + cf_sk->sk.sk_err = ECONNRESET; + set_rx_flow_on(cf_sk); + cf_sk->sk.sk_error_report(&cf_sk->sk); + break; + + default: + pr_debug("CAIF: %s(): Unexpected flow command %d\n", + __func__, flow); + } +} + +static void caif_check_flow_release(struct sock *sk) +{ + struct caifsock *cf_sk = container_of(sk, struct caifsock, sk); + + if (rx_flow_is_on(cf_sk)) + return; + + if (atomic_read(&sk->sk_rmem_alloc) <= sk_rcvbuf_lowwater(cf_sk)) { + dbfs_atomic_inc(&cnt.num_rx_flow_on); + set_rx_flow_on(cf_sk); + caif_flow_ctrl(sk, CAIF_MODEMCMD_FLOW_ON_REQ); + } +} + +/* + * Copied from unix_dgram_recvmsg, but removed credit checks, + * changed locking, address handling and added MSG_TRUNC. + */ +static int caif_seqpkt_recvmsg(struct kiocb *iocb, struct socket *sock, + struct msghdr *m, size_t len, int flags) + +{ + struct sock *sk = sock->sk; + struct sk_buff *skb; + int ret; + int copylen; + + ret = -EOPNOTSUPP; + if (m->msg_flags&MSG_OOB) + goto read_error; + + skb = skb_recv_datagram(sk, flags, 0 , &ret); + if (!skb) + goto read_error; + copylen = skb->len; + if (len < copylen) { + m->msg_flags |= MSG_TRUNC; + copylen = len; + } + + ret = skb_copy_datagram_iovec(skb, 0, m->msg_iov, copylen); + if (ret) + goto out_free; + + ret = (flags & MSG_TRUNC) ? skb->len : copylen; +out_free: + skb_free_datagram(sk, skb); + caif_check_flow_release(sk); + return ret; + +read_error: + return ret; +} + + +/* Copied from unix_stream_wait_data, identical except for lock call. */ +static long caif_stream_data_wait(struct sock *sk, long timeo) +{ + DEFINE_WAIT(wait); + lock_sock(sk); + + for (;;) { + prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); + + if (!skb_queue_empty(&sk->sk_receive_queue) || + sk->sk_err || + sk->sk_state != CAIF_CONNECTED || + sock_flag(sk, SOCK_DEAD) || + (sk->sk_shutdown & RCV_SHUTDOWN) || + signal_pending(current) || + !timeo) + break; + + set_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags); + release_sock(sk); + timeo = schedule_timeout(timeo); + lock_sock(sk); + clear_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags); + } + + finish_wait(sk_sleep(sk), &wait); + release_sock(sk); + return timeo; +} + + +/* + * Copied from unix_stream_recvmsg, but removed credit checks, + * changed locking calls, changed address handling. + */ +static int caif_stream_recvmsg(struct kiocb *iocb, struct socket *sock, + struct msghdr *msg, size_t size, + int flags) +{ + struct sock *sk = sock->sk; + int copied = 0; + int target; + int err = 0; + long timeo; + + err = -EOPNOTSUPP; + if (flags&MSG_OOB) + goto out; + + msg->msg_namelen = 0; + + /* + * Lock the socket to prevent queue disordering + * while sleeps in memcpy_tomsg + */ + err = -EAGAIN; + if (sk->sk_state == CAIF_CONNECTING) + goto out; + + caif_read_lock(sk); + target = sock_rcvlowat(sk, flags&MSG_WAITALL, size); + timeo = sock_rcvtimeo(sk, flags&MSG_DONTWAIT); + + do { + int chunk; + struct sk_buff *skb; + + lock_sock(sk); + skb = skb_dequeue(&sk->sk_receive_queue); + caif_check_flow_release(sk); + + if (skb == NULL) { + if (copied >= target) + goto unlock; + /* + * POSIX 1003.1g mandates this order. + */ + err = sock_error(sk); + if (err) + goto unlock; + err = -ECONNRESET; + if (sk->sk_shutdown & RCV_SHUTDOWN) + goto unlock; + + err = -EPIPE; + if (sk->sk_state != CAIF_CONNECTED) + goto unlock; + if (sock_flag(sk, SOCK_DEAD)) + goto unlock; + + release_sock(sk); + + err = -EAGAIN; + if (!timeo) + break; + + caif_read_unlock(sk); + + timeo = caif_stream_data_wait(sk, timeo); + + if (signal_pending(current)) { + err = sock_intr_errno(timeo); + goto out; + } + caif_read_lock(sk); + continue; +unlock: + release_sock(sk); + break; + } + release_sock(sk); + chunk = min_t(unsigned int, skb->len, size); + if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) { + skb_queue_head(&sk->sk_receive_queue, skb); + if (copied == 0) + copied = -EFAULT; + break; + } + copied += chunk; + size -= chunk; + + /* Mark read part of skb as used */ + if (!(flags & MSG_PEEK)) { + skb_pull(skb, chunk); + + /* put the skb back if we didn't use it up. */ + if (skb->len) { + skb_queue_head(&sk->sk_receive_queue, skb); + break; + } + kfree_skb(skb); + + } else { + /* + * It is questionable, see note in unix_dgram_recvmsg. + */ + /* put message back and return */ + skb_queue_head(&sk->sk_receive_queue, skb); + break; + } + } while (size); + caif_read_unlock(sk); + +out: + return copied ? : err; +} + +/* + * Copied from sock.c:sock_wait_for_wmem, but change to wait for + |