/* * File: mhi_dgram.c * * Copyright (C) 2011 Renesas Mobile Corporation. All rights reserved. * * Author: Petri Mattila * * DGRAM socket implementation for MHI protocol family. * * It uses the MHI socket framework in mhi_socket.c * * This implementation is the most basic frame passing interface. * The user space can use the sendmsg() and recvmsg() system calls * to access the frames. The socket is created with the socket() * system call, e.g. socket(PF_MHI,SOCK_DGRAM,l2proto). * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_MHI_DEBUG # define DPRINTK(...) printk(KERN_DEBUG "MHI/DGRAM: " __VA_ARGS__) #else # define DPRINTK(...) #endif /*** Prototypes ***/ static struct proto mhi_dgram_proto; static void mhi_dgram_destruct(struct sock *sk); /*** Functions ***/ int mhi_dgram_sock_create( struct net *net, struct socket *sock, int proto, int kern) { struct sock *sk; struct mhi_sock *msk; DPRINTK("mhi_dgram_sock_create: proto:%d type:%d\n", proto, sock->type); if (sock->type != SOCK_DGRAM) return -EPROTONOSUPPORT; if (proto == MHI_L3_ANY) return -EPROTONOSUPPORT; sk = sk_alloc(net, PF_MHI, GFP_KERNEL, &mhi_dgram_proto); if (!sk) return -ENOMEM; sock_init_data(sock, sk); sock->ops = &mhi_socket_ops; sock->state = SS_UNCONNECTED; sk->sk_protocol = proto; sk->sk_destruct = mhi_dgram_destruct; sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv; sk->sk_prot->init(sk); msk = mhi_sk(sk); msk->sk_l3proto = proto; msk->sk_ifindex = -1; return 0; } static int mhi_dgram_init(struct sock *sk) { return 0; } static void mhi_dgram_destruct(struct sock *sk) { skb_queue_purge(&sk->sk_receive_queue); } static void mhi_dgram_close(struct sock *sk, long timeout) { sk_common_release(sk); } static int mhi_dgram_ioctl(struct sock *sk, int cmd, unsigned long arg) { int err; DPRINTK("mhi_dgram_ioctl: cmd:%d arg:%lu\n", cmd, arg); switch (cmd) { case SIOCOUTQ: { int len; len = sk_wmem_alloc_get(sk); err = put_user(len, (int __user *)arg); } break; case SIOCINQ: { struct sk_buff *skb; int len; lock_sock(sk); { skb = skb_peek(&sk->sk_receive_queue); len = skb ? skb->len : 0; } release_sock(sk); err = put_user(len, (int __user *)arg); } break; default: err = -ENOIOCTLCMD; } return err; } static int mhi_dgram_sendmsg( struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len) { struct mhi_sock *msk = mhi_sk(sk); struct net_device *dev = NULL; struct l2muxhdr *l2hdr; struct sk_buff *skb; unsigned mflags; int err = -EFAULT; mflags = (MSG_DONTWAIT|MSG_EOR|MSG_NOSIGNAL|MSG_CMSG_COMPAT); if (msg->msg_flags & ~mflags) { printk(KERN_WARNING "%s: incompatible msg_flags: 0x%08X\n", __func__, msg->msg_flags); err = -EOPNOTSUPP; goto out; } skb = sock_alloc_send_skb(sk, len + L2MUX_HDR_SIZE + ETH_HLEN, (msg->msg_flags & MSG_DONTWAIT), &err); if (!skb) { printk(KERN_ERR "%s: sock_alloc_send_skb failed: %d\n", __func__, err); goto out; } skb_reserve(skb, L2MUX_HDR_SIZE + ETH_HLEN); skb_reset_transport_header(skb); err = memcpy_fromiovec((void *)skb_put(skb, len), msg->msg_iov, len); if (err < 0) { printk(KERN_ERR "%s: memcpy_fromiovec failed: %d\n", __func__, err); goto drop; } if (msk->sk_ifindex) dev = dev_get_by_index(sock_net(sk), msk->sk_ifindex); if (!dev) { printk(KERN_ERR "%s: no device for ifindex:%d\n", __func__, msk->sk_ifindex); goto drop; } if (!(dev->flags & IFF_UP)) { printk(KERN_ERR "%s: device %d not IFF_UP\n", __func__, msk->sk_ifindex); err = -ENETDOWN; goto drop; } if (len + L2MUX_HDR_SIZE > dev->mtu) { err = -EMSGSIZE; goto drop; } skb_reset_network_header(skb); skb_push(skb, L2MUX_HDR_SIZE); skb_reset_mac_header(skb); l2hdr = l2mux_hdr(skb); l2mux_set_proto(l2hdr, sk->sk_protocol); l2mux_set_length(l2hdr, len); err = mhi_skb_send(skb, dev, sk->sk_protocol); goto put; drop: kfree(skb); put: if (dev) dev_put(dev); out: return err; } static int mhi_dgram_recvmsg( struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len, int noblock, int flags, int *addr_len) { struct sk_buff *skb = NULL; int cnt, err; unsigned mflags; err = -EOPNOTSUPP; mflags = (MSG_PEEK|MSG_TRUNC|MSG_DONTWAIT|MSG_NOSIGNAL| MSG_CMSG_COMPAT); if (flags & ~mflags) { printk(KERN_WARNING "%s: incompatible socket flags: 0x%08X", __func__, flags); goto out2; } if (addr_len) addr_len[0] = 0; skb = skb_recv_datagram(sk, flags, noblock, &err); if (!skb) goto out2; cnt = skb->len - L2MUX_HDR_SIZE; if (len < cnt) { msg->msg_flags |= MSG_TRUNC; cnt = len; } err = skb_copy_datagram_iovec(skb, L2MUX_HDR_SIZE, msg->msg_iov, cnt); if (err) goto out; if (flags & MSG_TRUNC) err = skb->len - L2MUX_HDR_SIZE; else err = cnt; out: skb_free_datagram(sk, skb); out2: return err; } static int mhi_dgram_backlog_rcv(struct sock *sk, struct sk_buff *skb) { if (sock_queue_rcv_skb(sk, skb) < 0) { kfree_skb(skb); return NET_RX_DROP; } return NET_RX_SUCCESS; } static struct proto mhi_dgram_proto = { .name = "MHI-DGRAM", .owner = THIS_MODULE, .close = mhi_dgram_close, .ioctl = mhi_dgram_ioctl, .init = mhi_dgram_init, .sendmsg = mhi_dgram_sendmsg, .recvmsg = mhi_dgram_recvmsg, .backlog_rcv = mhi_dgram_backlog_rcv, .hash = mhi_sock_hash, .unhash = mhi_sock_unhash, .obj_size = sizeof(struct mhi_sock), }; int mhi_dgram_proto_init(void) { DPRINTK("mhi_dgram_proto_init\n"); return proto_register(&mhi_dgram_proto, 1); } void mhi_dgram_proto_exit(void) { DPRINTK("mhi_dgram_proto_exit\n"); proto_unregister(&mhi_dgram_proto); }