ethtool: reduce stack usage
[safe/jmp/linux-2.6] / net / phonet / pep.c
index c5dfecb..360cf37 100644 (file)
@@ -31,6 +31,7 @@
 #include <linux/phonet.h>
 #include <net/phonet/phonet.h>
 #include <net/phonet/pep.h>
+#include <net/phonet/gprs.h>
 
 /* sk_state values:
  * TCP_CLOSE           sock not in use yet
@@ -137,14 +138,15 @@ static int pep_reject_conn(struct sock *sk, struct sk_buff *skb, u8 code)
 
 /* Control requests are not sent by the pipe service and have a specific
  * message format. */
-static int pep_ctrlreq_error(struct sock *sk, struct sk_buff *oskb, u8 code)
+static int pep_ctrlreq_error(struct sock *sk, struct sk_buff *oskb, u8 code,
+                               gfp_t priority)
 {
        const struct pnpipehdr *oph = pnp_hdr(oskb);
        struct sk_buff *skb;
        struct pnpipehdr *ph;
        struct sockaddr_pn dst;
 
-       skb = alloc_skb(MAX_PNPIPE_HEADER + 4, GFP_ATOMIC);
+       skb = alloc_skb(MAX_PNPIPE_HEADER + 4, priority);
        if (!skb)
                return -ENOMEM;
        skb_set_owner_w(skb, sk);
@@ -223,6 +225,7 @@ static int pipe_rcv_status(struct sock *sk, struct sk_buff *skb)
 {
        struct pep_sock *pn = pep_sk(sk);
        struct pnpipehdr *hdr = pnp_hdr(skb);
+       int wake = 0;
 
        if (!pskb_may_pull(skb, sizeof(*hdr) + 4))
                return -EINVAL;
@@ -239,16 +242,16 @@ static int pipe_rcv_status(struct sock *sk, struct sk_buff *skb)
                case PN_LEGACY_FLOW_CONTROL:
                        switch (hdr->data[4]) {
                        case PEP_IND_BUSY:
-                               pn->tx_credits = 0;
+                               atomic_set(&pn->tx_credits, 0);
                                break;
                        case PEP_IND_READY:
-                               pn->tx_credits = 1;
+                               atomic_set(&pn->tx_credits, wake = 1);
                                break;
                        }
                        break;
                case PN_ONE_CREDIT_FLOW_CONTROL:
                        if (hdr->data[4] == PEP_IND_READY)
-                               pn->tx_credits = 1;
+                               atomic_set(&pn->tx_credits, wake = 1);
                        break;
                }
                break;
@@ -256,10 +259,7 @@ static int pipe_rcv_status(struct sock *sk, struct sk_buff *skb)
        case PN_PEP_IND_ID_MCFC_GRANT_CREDITS:
                if (pn->tx_fc != PN_MULTI_CREDIT_FLOW_CONTROL)
                        break;
-               if (pn->tx_credits + hdr->data[4] > 0xff)
-                       pn->tx_credits = 0xff;
-               else
-                       pn->tx_credits += hdr->data[4];
+               atomic_add(wake = hdr->data[4], &pn->tx_credits);
                break;
 
        default:
@@ -267,7 +267,7 @@ static int pipe_rcv_status(struct sock *sk, struct sk_buff *skb)
                                (unsigned)hdr->data[1]);
                return -EOPNOTSUPP;
        }
-       if (pn->tx_credits)
+       if (wake)
                sk->sk_write_space(sk);
        return 0;
 }
@@ -305,6 +305,7 @@ static int pipe_do_rcv(struct sock *sk, struct sk_buff *skb)
 {
        struct pep_sock *pn = pep_sk(sk);
        struct pnpipehdr *hdr = pnp_hdr(skb);
+       struct sk_buff_head *queue;
        int err = 0;
 
        BUG_ON(sk->sk_state == TCP_CLOSE_WAIT);
@@ -340,15 +341,22 @@ static int pipe_do_rcv(struct sock *sk, struct sk_buff *skb)
                }
                /* fall through */
        case PNS_PEP_DISABLE_REQ:
-               pn->tx_credits = 0;
+               atomic_set(&pn->tx_credits, 0);
                pep_reply(sk, skb, PN_PIPE_NO_ERROR, NULL, 0, GFP_ATOMIC);
                break;
 
        case PNS_PEP_CTRL_REQ:
-               /* TODO */
-               pep_ctrlreq_error(sk, skb, PN_PIPE_NO_ERROR);
-               break;
+               if (skb_queue_len(&pn->ctrlreq_queue) >= PNPIPE_CTRLREQ_MAX) {
+                       atomic_inc(&sk->sk_drops);
+                       break;
+               }
+               __skb_pull(skb, 4);
+               queue = &pn->ctrlreq_queue;
+               goto queue;
 
+       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)) {
@@ -359,17 +367,13 @@ static int pipe_do_rcv(struct sock *sk, struct sk_buff *skb)
                }
 
                if (pn->rx_credits == 0) {
+                       atomic_inc(&sk->sk_drops);
                        err = -ENOBUFS;
                        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 0;
+               queue = &sk->sk_receive_queue;
+               goto queue;
 
        case PNS_PEP_STATUS_IND:
                pipe_rcv_status(sk, skb);
@@ -390,7 +394,7 @@ static int pipe_do_rcv(struct sock *sk, struct sk_buff *skb)
                /* fall through */
        case PNS_PIPE_ENABLED_IND:
                if (!pn_flow_safe(pn->tx_fc)) {
-                       pn->tx_credits = 1;
+                       atomic_set(&pn->tx_credits, 1);
                        sk->sk_write_space(sk);
                }
                if (sk->sk_state == TCP_ESTABLISHED)
@@ -412,12 +416,24 @@ static int pipe_do_rcv(struct sock *sk, struct sk_buff *skb)
 out:
        kfree_skb(skb);
        return err;
+
+queue:
+       skb->dev = NULL;
+       skb_set_owner_r(skb, sk);
+       err = skb->len;
+       skb_queue_tail(queue, skb);
+       if (!sock_flag(sk, SOCK_DEAD))
+               sk->sk_data_ready(sk, err);
+       return 0;
 }
 
 /* Destroy connected sock. */
 static void pipe_destruct(struct sock *sk)
 {
+       struct pep_sock *pn = pep_sk(sk);
+
        skb_queue_purge(&sk->sk_receive_queue);
+       skb_queue_purge(&pn->ctrlreq_queue);
 }
 
 static int pep_connreq_rcv(struct sock *sk, struct sk_buff *skb)
@@ -428,6 +444,7 @@ static int pep_connreq_rcv(struct sock *sk, struct sk_buff *skb)
        struct sockaddr_pn dst;
        u16 peer_type;
        u8 pipe_handle, enabled, n_sb;
+       u8 aligned = 0;
 
        if (!pskb_pull(skb, sizeof(*hdr) + 4))
                return -EINVAL;
@@ -466,6 +483,9 @@ static int pep_connreq_rcv(struct sock *sk, struct sk_buff *skb)
                                return -EINVAL;
                        peer_type = (peer_type & 0xff00) | data[0];
                        break;
+               case PN_PIPE_SB_ALIGNED_DATA:
+                       aligned = data[0] != 0;
+                       break;
                }
                n_sb--;
        }
@@ -490,11 +510,14 @@ static int pep_connreq_rcv(struct sock *sk, struct sk_buff *skb)
        pn_skb_get_dst_sockaddr(skb, &dst);
        newpn->pn_sk.sobject = pn_sockaddr_get_object(&dst);
        newpn->pn_sk.resource = pn->pn_sk.resource;
+       skb_queue_head_init(&newpn->ctrlreq_queue);
        newpn->pipe_handle = pipe_handle;
+       atomic_set(&newpn->tx_credits, 0);
        newpn->peer_type = peer_type;
-       newpn->rx_credits = newpn->tx_credits = 0;
+       newpn->rx_credits = 0;
        newpn->rx_fc = newpn->tx_fc = PN_LEGACY_FLOW_CONTROL;
        newpn->init_enable = enabled;
+       newpn->aligned = aligned;
 
        BUG_ON(!skb_queue_empty(&newsk->sk_receive_queue));
        skb_queue_head(&newsk->sk_receive_queue, skb);
@@ -541,7 +564,7 @@ static int pep_do_rcv(struct sock *sk, struct sk_buff *skb)
 {
        struct pep_sock *pn = pep_sk(sk);
        struct sock *sknode;
-       struct pnpipehdr *hdr = pnp_hdr(skb);
+       struct pnpipehdr *hdr;
        struct sockaddr_pn dst;
        int err = NET_RX_SUCCESS;
        u8 pipe_handle;
@@ -581,7 +604,7 @@ static int pep_do_rcv(struct sock *sk, struct sk_buff *skb)
                break;
 
        case PNS_PEP_CTRL_REQ:
-               pep_ctrlreq_error(sk, skb, PN_PIPE_INVALID_HANDLE);
+               pep_ctrlreq_error(sk, skb, PN_PIPE_INVALID_HANDLE, GFP_ATOMIC);
                break;
 
        case PNS_PEP_RESET_REQ:
@@ -600,6 +623,7 @@ drop:
 static void pep_sock_close(struct sock *sk, long timeout)
 {
        struct pep_sock *pn = pep_sk(sk);
+       int ifindex = 0;
 
        sk_common_release(sk);
 
@@ -613,7 +637,12 @@ static void pep_sock_close(struct sock *sk, long timeout)
                        sk_del_node_init(sknode);
                sk->sk_state = TCP_CLOSE;
        }
+       ifindex = pn->ifindex;
+       pn->ifindex = 0;
        release_sock(sk);
+
+       if (ifindex)
+               gprs_detach(sk);
 }
 
 static int pep_wait_connreq(struct sock *sk, int noblock)
@@ -684,6 +713,7 @@ out:
 
 static int pep_ioctl(struct sock *sk, int cmd, unsigned long arg)
 {
+       struct pep_sock *pn = pep_sk(sk);
        int answ;
 
        switch (cmd) {
@@ -692,7 +722,10 @@ static int pep_ioctl(struct sock *sk, int cmd, unsigned long arg)
                        return -EINVAL;
 
                lock_sock(sk);
-               if (!skb_queue_empty(&sk->sk_receive_queue))
+               if (sock_flag(sk, SOCK_URGINLINE) &&
+                   !skb_queue_empty(&pn->ctrlreq_queue))
+                       answ = skb_peek(&pn->ctrlreq_queue)->len;
+               else if (!skb_queue_empty(&sk->sk_receive_queue))
                        answ = skb_peek(&sk->sk_receive_queue)->len;
                else
                        answ = 0;
@@ -709,23 +742,139 @@ static int pep_init(struct sock *sk)
 
        INIT_HLIST_HEAD(&pn->ackq);
        INIT_HLIST_HEAD(&pn->hlist);
+       skb_queue_head_init(&pn->ctrlreq_queue);
        pn->pipe_handle = PN_PIPE_INVALID_HANDLE;
        return 0;
 }
 
+static int pep_setsockopt(struct sock *sk, int level, int optname,
+                               char __user *optval, unsigned int optlen)
+{
+       struct pep_sock *pn = pep_sk(sk);
+       int val = 0, err = 0;
+
+       if (level != SOL_PNPIPE)
+               return -ENOPROTOOPT;
+       if (optlen >= sizeof(int)) {
+               if (get_user(val, (int __user *) optval))
+                       return -EFAULT;
+       }
+
+       lock_sock(sk);
+       switch (optname) {
+       case PNPIPE_ENCAP:
+               if (val && val != PNPIPE_ENCAP_IP) {
+                       err = -EINVAL;
+                       break;
+               }
+               if (!pn->ifindex == !val)
+                       break; /* Nothing to do! */
+               if (!capable(CAP_NET_ADMIN)) {
+                       err = -EPERM;
+                       break;
+               }
+               if (val) {
+                       release_sock(sk);
+                       err = gprs_attach(sk);
+                       if (err > 0) {
+                               pn->ifindex = err;
+                               err = 0;
+                       }
+               } else {
+                       pn->ifindex = 0;
+                       release_sock(sk);
+                       gprs_detach(sk);
+                       err = 0;
+               }
+               goto out_norel;
+       default:
+               err = -ENOPROTOOPT;
+       }
+       release_sock(sk);
+
+out_norel:
+       return err;
+}
+
+static int pep_getsockopt(struct sock *sk, int level, int optname,
+                               char __user *optval, int __user *optlen)
+{
+       struct pep_sock *pn = pep_sk(sk);
+       int len, val;
+
+       if (level != SOL_PNPIPE)
+               return -ENOPROTOOPT;
+       if (get_user(len, optlen))
+               return -EFAULT;
+
+       switch (optname) {
+       case PNPIPE_ENCAP:
+               val = pn->ifindex ? PNPIPE_ENCAP_IP : PNPIPE_ENCAP_NONE;
+               break;
+       case PNPIPE_IFINDEX:
+               val = pn->ifindex;
+               break;
+       default:
+               return -ENOPROTOOPT;
+       }
+
+       len = min_t(unsigned int, sizeof(int), len);
+       if (put_user(len, optlen))
+               return -EFAULT;
+       if (put_user(val, (int __user *) optval))
+               return -EFAULT;
+       return 0;
+}
+
+static int pipe_skb_send(struct sock *sk, struct sk_buff *skb)
+{
+       struct pep_sock *pn = pep_sk(sk);
+       struct pnpipehdr *ph;
+
+       if (pn_flow_safe(pn->tx_fc) &&
+           !atomic_add_unless(&pn->tx_credits, -1, 0)) {
+               kfree_skb(skb);
+               return -ENOBUFS;
+       }
+
+       skb_push(skb, 3 + pn->aligned);
+       skb_reset_transport_header(skb);
+       ph = pnp_hdr(skb);
+       ph->utid = 0;
+       if (pn->aligned) {
+               ph->message_id = PNS_PIPE_ALIGNED_DATA;
+               ph->data[0] = 0; /* padding */
+       } else
+               ph->message_id = PNS_PIPE_DATA;
+       ph->pipe_handle = pn->pipe_handle;
+
+       return pn_skb_send(sk, skb, &pipe_srv);
+}
+
 static int pep_sendmsg(struct kiocb *iocb, struct sock *sk,
                        struct msghdr *msg, size_t len)
 {
        struct pep_sock *pn = pep_sk(sk);
-       struct sk_buff *skb = NULL;
-       struct pnpipehdr *ph;
+       struct sk_buff *skb;
        long timeo;
        int flags = msg->msg_flags;
        int err, done;
 
-       if (msg->msg_flags & MSG_OOB || !(msg->msg_flags & MSG_EOR))
+       if ((msg->msg_flags & ~(MSG_DONTWAIT|MSG_EOR|MSG_NOSIGNAL|
+                               MSG_CMSG_COMPAT)) ||
+                       !(msg->msg_flags & MSG_EOR))
                return -EOPNOTSUPP;
 
+       skb = sock_alloc_send_skb(sk, MAX_PNPIPE_HEADER + len,
+                                       flags & MSG_DONTWAIT, &err);
+       if (!skb)
+               return -ENOBUFS;
+
+       skb_reserve(skb, MAX_PHONET_HEADER + 3);
+       err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
+       if (err < 0)
+               goto outfree;
+
        lock_sock(sk);
        timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
        if ((1 << sk->sk_state) & (TCPF_LISTEN|TCPF_CLOSE)) {
@@ -747,7 +896,7 @@ disabled:
        BUG_ON(sk->sk_state != TCP_ESTABLISHED);
 
        /* Wait until flow control allows TX */
-       done = pn->tx_credits > 0;
+       done = atomic_read(&pn->tx_credits);
        while (!done) {
                DEFINE_WAIT(wait);
 
@@ -762,47 +911,71 @@ disabled:
 
                prepare_to_wait(&sk->sk_socket->wait, &wait,
                                TASK_INTERRUPTIBLE);
-               done = sk_wait_event(sk, &timeo, pn->tx_credits > 0);
+               done = sk_wait_event(sk, &timeo, atomic_read(&pn->tx_credits));
                finish_wait(&sk->sk_socket->wait, &wait);
 
                if (sk->sk_state != TCP_ESTABLISHED)
                        goto disabled;
        }
 
-       if (!skb) {
-               skb = sock_alloc_send_skb(sk, MAX_PNPIPE_HEADER + len,
-                                               flags & MSG_DONTWAIT, &err);
-               if (skb == NULL)
-                       goto out;
-               skb_reserve(skb, MAX_PHONET_HEADER + 3);
-
-               if (sk->sk_state != TCP_ESTABLISHED || !pn->tx_credits)
-                       goto disabled; /* sock_alloc_send_skb might sleep */
-       }
-
-       err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
-       if (err < 0)
-               goto out;
-
-       __skb_push(skb, 3);
-       skb_reset_transport_header(skb);
-       ph = pnp_hdr(skb);
-       ph->utid = 0;
-       ph->message_id = PNS_PIPE_DATA;
-       ph->pipe_handle = pn->pipe_handle;
-       if (pn_flow_safe(pn->tx_fc)) /* credit-based flow control */
-               pn->tx_credits--;
-
-       err = pn_skb_send(sk, skb, &pipe_srv);
+       err = pipe_skb_send(sk, skb);
        if (err >= 0)
                err = len; /* success! */
        skb = NULL;
 out:
        release_sock(sk);
+outfree:
        kfree_skb(skb);
        return err;
 }
 
+int pep_writeable(struct sock *sk)
+{
+       struct pep_sock *pn = pep_sk(sk);
+
+       return atomic_read(&pn->tx_credits);
+}
+
+int pep_write(struct sock *sk, struct sk_buff *skb)
+{
+       struct sk_buff *rskb, *fs;
+       int flen = 0;
+
+       if (pep_sk(sk)->aligned)
+               return pipe_skb_send(sk, skb);
+
+       rskb = alloc_skb(MAX_PNPIPE_HEADER, GFP_ATOMIC);
+       if (!rskb) {
+               kfree_skb(skb);
+               return -ENOMEM;
+       }
+       skb_shinfo(rskb)->frag_list = skb;
+       rskb->len += skb->len;
+       rskb->data_len += rskb->len;
+       rskb->truesize += rskb->len;
+
+       /* Avoid nested fragments */
+       skb_walk_frags(skb, fs)
+               flen += fs->len;
+       skb->next = skb_shinfo(skb)->frag_list;
+       skb_frag_list_init(skb);
+       skb->len -= flen;
+       skb->data_len -= flen;
+       skb->truesize -= flen;
+
+       skb_reserve(rskb, MAX_PHONET_HEADER + 3);
+       return pipe_skb_send(sk, rskb);
+}
+
+struct sk_buff *pep_read(struct sock *sk)
+{
+       struct sk_buff *skb = skb_dequeue(&sk->sk_receive_queue);
+
+       if (sk->sk_state == TCP_ESTABLISHED)
+               pipe_grant_credits(sk);
+       return skb;
+}
+
 static int pep_recvmsg(struct kiocb *iocb, struct sock *sk,
                        struct msghdr *msg, size_t len, int noblock,
                        int flags, int *addr_len)
@@ -810,11 +983,30 @@ static int pep_recvmsg(struct kiocb *iocb, struct sock *sk,
        struct sk_buff *skb;
        int err;
 
-       if (unlikely(flags & MSG_OOB))
+       if (flags & ~(MSG_OOB|MSG_PEEK|MSG_TRUNC|MSG_DONTWAIT|MSG_WAITALL|
+                       MSG_NOSIGNAL|MSG_CMSG_COMPAT))
                return -EOPNOTSUPP;
+
        if (unlikely(1 << sk->sk_state & (TCPF_LISTEN | TCPF_CLOSE)))
                return -ENOTCONN;
 
+       if ((flags & MSG_OOB) || sock_flag(sk, SOCK_URGINLINE)) {
+               /* Dequeue and acknowledge control request */
+               struct pep_sock *pn = pep_sk(sk);
+
+               if (flags & MSG_PEEK)
+                       return -EOPNOTSUPP;
+               skb = skb_dequeue(&pn->ctrlreq_queue);
+               if (skb) {
+                       pep_ctrlreq_error(sk, skb, PN_PIPE_NO_ERROR,
+                                               GFP_KERNEL);
+                       msg->msg_flags |= MSG_OOB;
+                       goto copy;
+               }
+               if (flags & MSG_OOB)
+                       return -EINVAL;
+       }
+
        skb = skb_recv_datagram(sk, flags, noblock, &err);
        lock_sock(sk);
        if (skb == NULL) {
@@ -827,9 +1019,8 @@ static int pep_recvmsg(struct kiocb *iocb, struct sock *sk,
        if (sk->sk_state == TCP_ESTABLISHED)
                pipe_grant_credits(sk);
        release_sock(sk);
-
+copy:
        msg->msg_flags |= MSG_EOR;
-
        if (skb->len > len)
                msg->msg_flags |= MSG_TRUNC;
        else
@@ -873,6 +1064,8 @@ static struct proto pep_proto = {
        .accept         = pep_sock_accept,
        .ioctl          = pep_ioctl,
        .init           = pep_init,
+       .setsockopt     = pep_setsockopt,
+       .getsockopt     = pep_getsockopt,
        .sendmsg        = pep_sendmsg,
        .recvmsg        = pep_recvmsg,
        .backlog_rcv    = pep_do_rcv,