blob: b2089e0f5344887714b5b5e9752ed6b6aea3c7e3 [file] [log] [blame]
/*
* File: mhi_dgram.c
*
* Copyright (C) 2011 Renesas Mobile Corporation. All rights reserved.
*
* Author: Petri Mattila <petri.to.mattila@renesasmobile.com>
*
* 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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/socket.h>
#include <linux/mhi.h>
#include <linux/l2mux.h>
#include <asm/ioctls.h>
#include <net/af_mhi.h>
#include <net/mhi/sock.h>
#include <net/mhi/dgram.h>
#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",
msg->msg_flags, __func__);
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",
err, __func__);
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",
err, __func__);
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",
msk->sk_ifindex, __func__);
goto drop;
}
if (!(dev->flags & IFF_UP)) {
printk(KERN_ERR "%s: device %d not IFF_UP\n",
msk->sk_ifindex, __func__);
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",
flags, __func__);
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);
}