/***
|
*
|
* stack/socket.c - sockets implementation for rtnet
|
*
|
* Copyright (C) 1999 Lineo, Inc
|
* 1999, 2002 David A. Schleef <ds@schleef.org>
|
* 2002 Ulrich Marx <marx@kammer.uni-hannover.de>
|
* 2003-2005 Jan Kiszka <jan.kiszka@web.de>
|
*
|
* This program is free software; you can redistribute it and/or modify
|
* it under the terms of the GNU General Public License as published by
|
* the Free Software Foundation; either version 2 of the License, or
|
* (at your option) any later version.
|
*
|
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
*
|
*/
|
|
#include <linux/moduleparam.h>
|
#include <linux/spinlock.h>
|
#include <linux/socket.h>
|
#include <linux/in.h>
|
#include <linux/err.h>
|
#include <linux/ip.h>
|
#include <linux/tcp.h>
|
#include <asm/bitops.h>
|
|
#include <rtdm/net.h>
|
#include <rtnet_internal.h>
|
#include <rtnet_iovec.h>
|
#include <rtnet_socket.h>
|
#include <ipv4/protocol.h>
|
|
#define SKB_POOL_CLOSED 0
|
|
static unsigned int socket_rtskbs = DEFAULT_SOCKET_RTSKBS;
|
module_param(socket_rtskbs, uint, 0444);
|
MODULE_PARM_DESC(socket_rtskbs,
|
"Default number of realtime socket buffers in socket pools");
|
|
/************************************************************************
|
* internal socket functions *
|
************************************************************************/
|
|
int rt_bare_socket_init(struct rtdm_fd *fd, unsigned short protocol,
|
unsigned int priority, unsigned int pool_size)
|
{
|
struct rtsocket *sock = rtdm_fd_to_private(fd);
|
int err;
|
|
err = rtskb_pool_init(&sock->skb_pool, pool_size, NULL, fd);
|
if (err < 0)
|
return err;
|
|
sock->protocol = protocol;
|
sock->priority = priority;
|
|
return err;
|
}
|
EXPORT_SYMBOL_GPL(rt_bare_socket_init);
|
|
/***
|
* rt_socket_init - initialises a new socket structure
|
*/
|
int rt_socket_init(struct rtdm_fd *fd, unsigned short protocol)
|
{
|
struct rtsocket *sock = rtdm_fd_to_private(fd);
|
unsigned int pool_size;
|
|
sock->flags = 0;
|
sock->callback_func = NULL;
|
|
rtskb_queue_init(&sock->incoming);
|
|
sock->timeout = 0;
|
|
rtdm_lock_init(&sock->param_lock);
|
rtdm_sem_init(&sock->pending_sem, 0);
|
|
pool_size = rt_bare_socket_init(fd, protocol,
|
RTSKB_PRIO_VALUE(SOCK_DEF_PRIO,
|
RTSKB_DEF_RT_CHANNEL),
|
socket_rtskbs);
|
sock->pool_size = pool_size;
|
mutex_init(&sock->pool_nrt_lock);
|
|
if (pool_size < socket_rtskbs) {
|
/* fix statistics */
|
if (pool_size == 0)
|
rtskb_pools--;
|
|
rt_socket_cleanup(fd);
|
return -ENOMEM;
|
}
|
|
return 0;
|
}
|
EXPORT_SYMBOL_GPL(rt_socket_init);
|
|
/***
|
* rt_socket_cleanup - releases resources allocated for the socket
|
*/
|
void rt_socket_cleanup(struct rtdm_fd *fd)
|
{
|
struct rtsocket *sock = rtdm_fd_to_private(fd);
|
|
rtdm_sem_destroy(&sock->pending_sem);
|
|
mutex_lock(&sock->pool_nrt_lock);
|
|
set_bit(SKB_POOL_CLOSED, &sock->flags);
|
|
if (sock->pool_size > 0)
|
rtskb_pool_release(&sock->skb_pool);
|
|
mutex_unlock(&sock->pool_nrt_lock);
|
}
|
EXPORT_SYMBOL_GPL(rt_socket_cleanup);
|
|
/***
|
* rt_socket_common_ioctl
|
*/
|
int rt_socket_common_ioctl(struct rtdm_fd *fd, int request, void __user *arg)
|
{
|
struct rtsocket *sock = rtdm_fd_to_private(fd);
|
int ret = 0;
|
struct rtnet_callback *callback;
|
const unsigned int *val;
|
unsigned int _val;
|
const nanosecs_rel_t *timeout;
|
nanosecs_rel_t _timeout;
|
rtdm_lockctx_t context;
|
|
switch (request) {
|
case RTNET_RTIOC_XMITPARAMS:
|
val = rtnet_get_arg(fd, &_val, arg, sizeof(_val));
|
if (IS_ERR(val))
|
return PTR_ERR(val);
|
sock->priority = *val;
|
break;
|
|
case RTNET_RTIOC_TIMEOUT:
|
timeout = rtnet_get_arg(fd, &_timeout, arg, sizeof(_timeout));
|
if (IS_ERR(timeout))
|
return PTR_ERR(timeout);
|
sock->timeout = *timeout;
|
break;
|
|
case RTNET_RTIOC_CALLBACK:
|
if (rtdm_fd_is_user(fd))
|
return -EACCES;
|
|
rtdm_lock_get_irqsave(&sock->param_lock, context);
|
|
callback = arg;
|
sock->callback_func = callback->func;
|
sock->callback_arg = callback->arg;
|
|
rtdm_lock_put_irqrestore(&sock->param_lock, context);
|
break;
|
|
case RTNET_RTIOC_EXTPOOL:
|
val = rtnet_get_arg(fd, &_val, arg, sizeof(_val));
|
if (IS_ERR(val))
|
return PTR_ERR(val);
|
|
if (rtdm_in_rt_context())
|
return -ENOSYS;
|
|
mutex_lock(&sock->pool_nrt_lock);
|
|
if (test_bit(SKB_POOL_CLOSED, &sock->flags)) {
|
mutex_unlock(&sock->pool_nrt_lock);
|
return -EBADF;
|
}
|
ret = rtskb_pool_extend(&sock->skb_pool, *val);
|
sock->pool_size += ret;
|
|
mutex_unlock(&sock->pool_nrt_lock);
|
|
if (ret == 0 && *val > 0)
|
ret = -ENOMEM;
|
|
break;
|
|
case RTNET_RTIOC_SHRPOOL:
|
val = rtnet_get_arg(fd, &_val, arg, sizeof(_val));
|
if (IS_ERR(val))
|
return PTR_ERR(val);
|
|
if (rtdm_in_rt_context())
|
return -ENOSYS;
|
|
mutex_lock(&sock->pool_nrt_lock);
|
|
ret = rtskb_pool_shrink(&sock->skb_pool, *val);
|
sock->pool_size -= ret;
|
|
mutex_unlock(&sock->pool_nrt_lock);
|
|
if (ret == 0 && *val > 0)
|
ret = -EBUSY;
|
|
break;
|
|
default:
|
ret = -EOPNOTSUPP;
|
break;
|
}
|
|
return ret;
|
}
|
EXPORT_SYMBOL_GPL(rt_socket_common_ioctl);
|
|
/***
|
* rt_socket_if_ioctl
|
*/
|
int rt_socket_if_ioctl(struct rtdm_fd *fd, int request, void __user *arg)
|
{
|
struct rtnet_device *rtdev;
|
struct ifreq _ifr, *ifr, *u_ifr;
|
struct sockaddr_in _sin;
|
struct ifconf _ifc, *ifc, *u_ifc;
|
int ret = 0, size = 0, i;
|
short flags;
|
|
if (request == SIOCGIFCONF) {
|
u_ifc = arg;
|
ifc = rtnet_get_arg(fd, &_ifc, u_ifc, sizeof(_ifc));
|
if (IS_ERR(ifc))
|
return PTR_ERR(ifc);
|
|
for (u_ifr = ifc->ifc_req, i = 1; i <= MAX_RT_DEVICES;
|
i++, u_ifr++) {
|
rtdev = rtdev_get_by_index(i);
|
if (rtdev == NULL)
|
continue;
|
|
if ((rtdev->flags & IFF_UP) == 0) {
|
rtdev_dereference(rtdev);
|
continue;
|
}
|
|
size += sizeof(struct ifreq);
|
if (size > ifc->ifc_len) {
|
rtdev_dereference(rtdev);
|
size = ifc->ifc_len;
|
break;
|
}
|
|
ret = rtnet_put_arg(fd, u_ifr->ifr_name, rtdev->name,
|
IFNAMSIZ);
|
if (ret == 0) {
|
memset(&_sin, 0, sizeof(_sin));
|
_sin.sin_family = AF_INET;
|
_sin.sin_addr.s_addr = rtdev->local_ip;
|
ret = rtnet_put_arg(fd, &u_ifr->ifr_addr, &_sin,
|
sizeof(_sin));
|
}
|
|
rtdev_dereference(rtdev);
|
if (ret)
|
return ret;
|
}
|
|
return rtnet_put_arg(fd, &u_ifc->ifc_len, &size, sizeof(size));
|
}
|
|
u_ifr = arg;
|
ifr = rtnet_get_arg(fd, &_ifr, u_ifr, sizeof(_ifr));
|
if (IS_ERR(ifr))
|
return PTR_ERR(ifr);
|
|
if (request == SIOCGIFNAME) {
|
rtdev = rtdev_get_by_index(ifr->ifr_ifindex);
|
if (rtdev == NULL)
|
return -ENODEV;
|
ret = rtnet_put_arg(fd, u_ifr->ifr_name, rtdev->name, IFNAMSIZ);
|
goto out;
|
}
|
|
rtdev = rtdev_get_by_name(ifr->ifr_name);
|
if (rtdev == NULL)
|
return -ENODEV;
|
|
switch (request) {
|
case SIOCGIFINDEX:
|
ret = rtnet_put_arg(fd, &u_ifr->ifr_ifindex, &rtdev->ifindex,
|
sizeof(u_ifr->ifr_ifindex));
|
break;
|
|
case SIOCGIFFLAGS:
|
flags = rtdev->flags;
|
if ((ifr->ifr_flags & IFF_UP) &&
|
(rtdev->link_state &
|
(RTNET_LINK_STATE_PRESENT | RTNET_LINK_STATE_NOCARRIER)) ==
|
RTNET_LINK_STATE_PRESENT)
|
flags |= IFF_RUNNING;
|
ret = rtnet_put_arg(fd, &u_ifr->ifr_flags, &flags,
|
sizeof(u_ifr->ifr_flags));
|
break;
|
|
case SIOCGIFHWADDR:
|
ret = rtnet_put_arg(fd, &u_ifr->ifr_hwaddr.sa_data,
|
rtdev->dev_addr, rtdev->addr_len);
|
if (!ret)
|
ret = rtnet_put_arg(
|
fd, &u_ifr->ifr_hwaddr.sa_family, &rtdev->type,
|
sizeof(u_ifr->ifr_hwaddr.sa_family));
|
break;
|
|
case SIOCGIFADDR:
|
memset(&_sin, 0, sizeof(_sin));
|
_sin.sin_family = AF_INET;
|
_sin.sin_addr.s_addr = rtdev->local_ip;
|
ret = rtnet_put_arg(fd, &u_ifr->ifr_addr, &_sin, sizeof(_sin));
|
break;
|
|
case SIOCETHTOOL:
|
if (rtdev->do_ioctl != NULL) {
|
if (rtdm_in_rt_context())
|
return -ENOSYS;
|
ret = rtdev->do_ioctl(rtdev, ifr, request);
|
} else
|
ret = -EOPNOTSUPP;
|
break;
|
|
case SIOCDEVPRIVATE ... SIOCDEVPRIVATE + 15:
|
if (rtdev->do_ioctl != NULL)
|
ret = rtdev->do_ioctl(rtdev, ifr, request);
|
else
|
ret = -EOPNOTSUPP;
|
break;
|
|
default:
|
ret = -EOPNOTSUPP;
|
break;
|
}
|
|
out:
|
rtdev_dereference(rtdev);
|
return ret;
|
}
|
EXPORT_SYMBOL_GPL(rt_socket_if_ioctl);
|
|
int rt_socket_select_bind(struct rtdm_fd *fd, rtdm_selector_t *selector,
|
enum rtdm_selecttype type, unsigned fd_index)
|
{
|
struct rtsocket *sock = rtdm_fd_to_private(fd);
|
|
switch (type) {
|
case XNSELECT_READ:
|
return rtdm_sem_select(&sock->pending_sem, selector,
|
XNSELECT_READ, fd_index);
|
default:
|
return -EBADF;
|
}
|
|
return -EINVAL;
|
}
|
EXPORT_SYMBOL_GPL(rt_socket_select_bind);
|
|
void *rtnet_get_arg(struct rtdm_fd *fd, void *tmp, const void *src, size_t len)
|
{
|
int ret;
|
|
if (!rtdm_fd_is_user(fd))
|
return (void *)src;
|
|
ret = rtdm_copy_from_user(fd, tmp, src, len);
|
if (ret)
|
return ERR_PTR(ret);
|
|
return tmp;
|
}
|
EXPORT_SYMBOL_GPL(rtnet_get_arg);
|
|
int rtnet_put_arg(struct rtdm_fd *fd, void *dst, const void *src, size_t len)
|
{
|
if (!rtdm_fd_is_user(fd)) {
|
if (dst != src)
|
memcpy(dst, src, len);
|
return 0;
|
}
|
|
return rtdm_copy_to_user(fd, dst, src, len);
|
}
|
EXPORT_SYMBOL_GPL(rtnet_put_arg);
|