Skip to content

Commit

Permalink
Phonet: support active connection without pipe controller on modem
Browse files Browse the repository at this point in the history
This provides support for newer ISI modems with no need for the
earlier experimental compile-time alternative choice. With this,
we can now use the same kernel and userspace with both types of
modems.

This also avoids confusing two different and incompatible state
machines, actively connected vs accepted sockets, and adds
connection response error handling (processing "SYN/RST" of sorts).

Signed-off-by: Rémi Denis-Courmont <remi.denis-courmont@nokia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Rémi Denis-Courmont authored and davem330 committed Mar 9, 2011
1 parent acaf7df commit 297edb6
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 162 deletions.
53 changes: 24 additions & 29 deletions Documentation/networking/phonet.txt
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,28 @@ connections, one per accept()'d socket.
write(cfd, msg, msglen);
}

Connections are established between two endpoints by a "third party"
application. This means that both endpoints are passive; so connect()
is not possible.
Connections are traditionally established between two endpoints by a
"third party" application. This means that both endpoints are passive.


As of Linux kernel version 2.6.39, it is also possible to connect
two endpoints directly, using connect() on the active side. This is
intended to support the newer Nokia Wireless Modem API, as found in
e.g. the Nokia Slim Modem in the ST-Ericsson U8500 platform:

struct sockaddr_spn spn;
int fd;

fd = socket(PF_PHONET, SOCK_SEQPACKET, PN_PROTO_PIPE);
memset(&spn, 0, sizeof(spn));
spn.spn_family = AF_PHONET;
spn.spn_obj = ...;
spn.spn_dev = ...;
spn.spn_resource = 0xD9;
connect(fd, (struct sockaddr *)&spn, sizeof(spn));
/* normal I/O here ... */
close(fd);


WARNING:
When polling a connected pipe socket for writability, there is an
Expand Down Expand Up @@ -189,39 +208,15 @@ The pipe protocol provides two socket options at the SOL_PNPIPE level:
Phonet Pipe-controller Implementation
-------------------------------------

Phonet Pipe-controller is enabled by selecting the CONFIG_PHONET_PIPECTRLR Kconfig
option. It is useful when communicating with those Nokia Modems which do not
implement Pipe controller in them e.g. Nokia Slim Modem used in ST-Ericsson
U8500 platform.

The implementation is based on the Data Connection Establishment Sequence
depicted in 'Nokia Wireless Modem API - Wireless_modem_user_guide.pdf'
document.

It allows a phonet sequenced socket (host-pep) to initiate a Pipe connection
between itself and a remote pipe-end point (e.g. modem).
Phonet Pipe-controller is enabled by selecting the CONFIG_PHONET_PIPECTRLR
Kconfig option.

The implementation adds socket options at SOL_PNPIPE level:

PNPIPE_ENABLE accepts one integer value (int). If set to zero, the pipe
is disabled. If the value is non-zero, the pipe is enabled. If the pipe
is not (yet) connected, ENOTCONN is error is returned.

The implementation also adds socket 'connect'. On calling the 'connect', pipe
will be created between the source socket and the destination, and the pipe
state will be set to PIPE_DISABLED.

After a pipe has been created and enabled successfully, the Pipe data can be
exchanged between the host-pep and remote-pep (modem).

User-space would typically follow below sequence with Pipe controller:-
-socket
-bind
-setsockopt for PNPIPE_PIPE_HANDLE
-connect
-setsockopt for PNPIPE_ENCAP_IP
-setsockopt for PNPIPE_ENABLE


Authors
-------
Expand Down
172 changes: 101 additions & 71 deletions net/phonet/pep.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ static int pep_indicate(struct sock *sk, u8 id, u8 code,

#define PAD 0x00

#ifdef CONFIG_PHONET_PIPECTRLR
static int pipe_handler_request(struct sock *sk, u8 id, u8 code,
const void *data, int len)
{
Expand Down Expand Up @@ -168,11 +167,7 @@ static int pipe_handler_send_created_ind(struct sock *sk)
data, 4, GFP_ATOMIC);
}

static int pipe_handler_send_ind(struct sock *sk, u8 id)
{
return pep_indicate(sk, id, PAD, NULL, 0, GFP_ATOMIC);
}

#ifdef CONFIG_PHONET_PIPECTRLR
static int pipe_handler_enable_pipe(struct sock *sk, int enable)
{
u8 id = enable ? PNS_PEP_ENABLE_REQ : PNS_PEP_DISABLE_REQ;
Expand Down Expand Up @@ -376,32 +371,11 @@ static int pipe_do_rcv(struct sock *sk, struct sk_buff *skb)
sk->sk_state_change(sk);
break;

#ifdef CONFIG_PHONET_PIPECTRLR
case PNS_PEP_DISCONNECT_RESP:
sk->sk_state = TCP_CLOSE;
break;
#endif

case PNS_PEP_ENABLE_REQ:
/* Wait for PNS_PIPE_(ENABLED|REDIRECTED)_IND */
pep_reply(sk, skb, PN_PIPE_NO_ERROR, NULL, 0, GFP_ATOMIC);
break;

#ifdef CONFIG_PHONET_PIPECTRLR
case PNS_PEP_ENABLE_RESP:
pipe_handler_send_ind(sk, PNS_PIPE_ENABLED_IND);

if (!pn_flow_safe(pn->tx_fc)) {
atomic_set(&pn->tx_credits, 1);
sk->sk_write_space(sk);
}
if (sk->sk_state == TCP_ESTABLISHED)
break; /* Nothing to do */
sk->sk_state = TCP_ESTABLISHED;
pipe_grant_credits(sk, GFP_ATOMIC);
break;
#endif

case PNS_PEP_RESET_REQ:
switch (hdr->state_after_reset) {
case PN_PIPE_DISABLE:
Expand All @@ -420,15 +394,6 @@ static int pipe_do_rcv(struct sock *sk, struct sk_buff *skb)
pep_reply(sk, skb, PN_PIPE_NO_ERROR, NULL, 0, GFP_ATOMIC);
break;

#ifdef CONFIG_PHONET_PIPECTRLR
case PNS_PEP_DISABLE_RESP:
atomic_set(&pn->tx_credits, 0);
pipe_handler_send_ind(sk, PNS_PIPE_DISABLED_IND);
sk->sk_state = TCP_SYN_RECV;
pn->rx_credits = 0;
break;
#endif

case PNS_PEP_CTRL_REQ:
if (skb_queue_len(&pn->ctrlreq_queue) >= PNPIPE_CTRLREQ_MAX) {
atomic_inc(&sk->sk_drops);
Expand Down Expand Up @@ -521,7 +486,6 @@ static void pipe_destruct(struct sock *sk)
skb_queue_purge(&pn->ctrlreq_queue);
}

#ifdef CONFIG_PHONET_PIPECTRLR
static u8 pipe_negotiate_fc(const u8 *fcs, unsigned n)
{
unsigned i;
Expand All @@ -546,6 +510,8 @@ static int pep_connresp_rcv(struct sock *sk, struct sk_buff *skb)
return -EINVAL;

hdr = pnp_hdr(skb);
if (hdr->error_code != PN_PIPE_NO_ERROR)
return -ECONNREFUSED;

/* Parse sub-blocks */
n_sb = hdr->data[4];
Expand Down Expand Up @@ -573,14 +539,74 @@ static int pep_connresp_rcv(struct sock *sk, struct sk_buff *skb)
n_sb--;
}

sk->sk_state = TCP_SYN_RECV;
sk->sk_backlog_rcv = pipe_do_rcv;
pn->rx_credits = 0;
sk->sk_state_change(sk);

return pipe_handler_send_created_ind(sk);
}
#endif

/* Queue an skb to an actively connected sock.
* Socket lock must be held. */
static int pipe_handler_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct pep_sock *pn = pep_sk(sk);
struct pnpipehdr *hdr = pnp_hdr(skb);
int err = NET_RX_SUCCESS;

switch (hdr->message_id) {
case PNS_PIPE_ALIGNED_DATA:
__skb_pull(skb, 1);
/* fall through */
case PNS_PIPE_DATA:
__skb_pull(skb, 3); /* Pipe data header */
if (!pn_flow_safe(pn->rx_fc)) {
err = sock_queue_rcv_skb(sk, skb);
if (!err)
return NET_RX_SUCCESS;
err = NET_RX_DROP;
break;
}

if (pn->rx_credits == 0) {
atomic_inc(&sk->sk_drops);
err = NET_RX_DROP;
break;
}
pn->rx_credits--;
skb->dev = NULL;
skb_set_owner_r(skb, sk);
err = skb->len;
skb_queue_tail(&sk->sk_receive_queue, skb);
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_data_ready(sk, err);
return NET_RX_SUCCESS;

case PNS_PEP_CONNECT_RESP:
if (sk->sk_state != TCP_SYN_SENT)
break;
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_state_change(sk);
if (pep_connresp_rcv(sk, skb)) {
sk->sk_state = TCP_CLOSE_WAIT;
break;
}

sk->sk_state = TCP_ESTABLISHED;
if (!pn_flow_safe(pn->tx_fc)) {
atomic_set(&pn->tx_credits, 1);
sk->sk_write_space(sk);
}
pipe_grant_credits(sk, GFP_ATOMIC);
break;

case PNS_PEP_DISCONNECT_RESP:
/* sock should already be dead, nothing to do */
break;

case PNS_PEP_STATUS_IND:
pipe_rcv_status(sk, skb);
break;
}
kfree_skb(skb);
return err;
}

/* Listening sock must be locked */
static struct sock *pep_find_pipe(const struct hlist_head *hlist,
Expand Down Expand Up @@ -649,12 +675,6 @@ static int pep_do_rcv(struct sock *sk, struct sk_buff *skb)
sk->sk_data_ready(sk, 0);
return NET_RX_SUCCESS;

#ifdef CONFIG_PHONET_PIPECTRLR
case PNS_PEP_CONNECT_RESP:
pep_connresp_rcv(sk, skb);
break;
#endif

case PNS_PEP_DISCONNECT_REQ:
pep_reply(sk, skb, PN_PIPE_NO_ERROR, NULL, 0, GFP_ATOMIC);
break;
Expand All @@ -667,15 +687,19 @@ static int pep_do_rcv(struct sock *sk, struct sk_buff *skb)
case PNS_PEP_ENABLE_REQ:
case PNS_PEP_DISABLE_REQ:
/* invalid handle is not even allowed here! */
default:
break;

default:
if ((1 << sk->sk_state)
& ~(TCPF_CLOSE|TCPF_LISTEN|TCPF_CLOSE_WAIT))
/* actively connected socket */
return pipe_handler_do_rcv(sk, skb);
}
drop:
kfree_skb(skb);
return NET_RX_SUCCESS;
}

#ifndef CONFIG_PHONET_PIPECTRLR
static int pipe_do_remove(struct sock *sk)
{
struct pep_sock *pn = pep_sk(sk);
Expand All @@ -693,7 +717,6 @@ static int pipe_do_remove(struct sock *sk)
ph->data[0] = PAD;
return pn_skb_send(sk, skb, NULL);
}
#endif

/* associated socket ceases to exist */
static void pep_sock_close(struct sock *sk, long timeout)
Expand All @@ -706,13 +729,12 @@ static void pep_sock_close(struct sock *sk, long timeout)

lock_sock(sk);
if ((1 << sk->sk_state) & (TCPF_SYN_RECV|TCPF_ESTABLISHED)) {
#ifndef CONFIG_PHONET_PIPECTRLR
/* Forcefully remove dangling Phonet pipe */
pipe_do_remove(sk);
#else
/* send pep disconnect request */
pipe_handler_request(sk, PNS_PEP_DISCONNECT_REQ, PAD, NULL, 0);
#endif
if (sk->sk_backlog_rcv == pipe_do_rcv)
/* Forcefully remove dangling Phonet pipe */
pipe_do_remove(sk);
else
pipe_handler_request(sk, PNS_PEP_DISCONNECT_REQ, PAD,
NULL, 0);
}
sk->sk_state = TCP_CLOSE;

Expand Down Expand Up @@ -844,20 +866,22 @@ static struct sock *pep_sock_accept(struct sock *sk, int flags, int *errp)
return newsk;
}

#ifdef CONFIG_PHONET_PIPECTRLR
static int pep_sock_connect(struct sock *sk, struct sockaddr *addr, int len)
{
struct pep_sock *pn = pep_sk(sk);
const struct sockaddr_pn *spn = (struct sockaddr_pn *)addr;
int err;
u8 data[4] = { 0 /* sub-blocks */, PAD, PAD, PAD };

pn->pn_sk.dobject = pn_sockaddr_get_object(spn);
pn->pn_sk.resource = pn_sockaddr_get_resource(spn);
pn->pipe_handle = 1; /* anything but INVALID_HANDLE */
return pipe_handler_request(sk, PNS_PEP_CONNECT_REQ,
PN_PIPE_DISABLE, data, 4);
err = pipe_handler_request(sk, PNS_PEP_CONNECT_REQ,
PN_PIPE_ENABLE, data, 4);
if (err) {
pn->pipe_handle = PN_PIPE_INVALID_HANDLE;
return err;
}
sk->sk_state = TCP_SYN_SENT;
return 0;
}
#endif

static int pep_ioctl(struct sock *sk, int cmd, unsigned long arg)
{
Expand Down Expand Up @@ -890,8 +914,16 @@ static int pep_init(struct sock *sk)

sk->sk_destruct = pipe_destruct;
INIT_HLIST_HEAD(&pn->hlist);
pn->listener = NULL;
skb_queue_head_init(&pn->ctrlreq_queue);
atomic_set(&pn->tx_credits, 0);
pn->ifindex = 0;
pn->peer_type = 0;
pn->pipe_handle = PN_PIPE_INVALID_HANDLE;
pn->rx_credits = 0;
pn->rx_fc = pn->tx_fc = PN_LEGACY_FLOW_CONTROL;
pn->init_enable = 1;
pn->aligned = 0;
return 0;
}

Expand Down Expand Up @@ -1219,17 +1251,17 @@ static void pep_sock_unhash(struct sock *sk)

lock_sock(sk);

#ifndef CONFIG_PHONET_PIPECTRLR
if ((1 << sk->sk_state) & ~(TCPF_CLOSE|TCPF_LISTEN)) {
if (pn->listener != NULL) {
skparent = pn->listener;
pn->listener = NULL;
release_sock(sk);

pn = pep_sk(skparent);
lock_sock(skparent);
sk_del_node_init(sk);
sk = skparent;
}
#endif

/* Unhash a listening sock only when it is closed
* and all of its active connected pipes are closed. */
if (hlist_empty(&pn->hlist))
Expand All @@ -1243,9 +1275,7 @@ static void pep_sock_unhash(struct sock *sk)
static struct proto pep_proto = {
.close = pep_sock_close,
.accept = pep_sock_accept,
#ifdef CONFIG_PHONET_PIPECTRLR
.connect = pep_sock_connect,
#endif
.ioctl = pep_ioctl,
.init = pep_init,
.setsockopt = pep_setsockopt,
Expand Down
Loading

0 comments on commit 297edb6

Please sign in to comment.