/* rtmac_vnic.c
|
*
|
* rtmac - real-time networking media access control subsystem
|
* Copyright (C) 2002 Marc Kleine-Budde <kleine-budde@gmx.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/netdevice.h>
|
#include <linux/etherdevice.h>
|
#include <linux/rtnetlink.h>
|
|
#include <rtnet_internal.h>
|
#include <rtdev.h>
|
#include <rtnet_port.h> /* for netdev_priv() */
|
#include <rtmac/rtmac_disc.h>
|
#include <rtmac/rtmac_proto.h>
|
#include <rtmac/rtmac_vnic.h>
|
|
static unsigned int vnic_rtskbs = DEFAULT_VNIC_RTSKBS;
|
module_param(vnic_rtskbs, uint, 0444);
|
MODULE_PARM_DESC(vnic_rtskbs,
|
"Number of realtime socket buffers per virtual NIC");
|
|
static rtdm_nrtsig_t vnic_signal;
|
static struct rtskb_queue rx_queue;
|
|
int rtmac_vnic_rx(struct rtskb *rtskb, u16 type)
|
{
|
struct rtmac_priv *mac_priv = rtskb->rtdev->mac_priv;
|
struct rtskb_pool *pool = &mac_priv->vnic_skb_pool;
|
|
if (rtskb_acquire(rtskb, pool) != 0) {
|
mac_priv->vnic_stats.rx_dropped++;
|
kfree_rtskb(rtskb);
|
return -1;
|
}
|
|
rtskb->protocol = type;
|
|
if (rtskb_queue_tail_check(&rx_queue, rtskb))
|
rtdm_nrtsig_pend(&vnic_signal);
|
|
return 0;
|
}
|
|
static void rtmac_vnic_signal_handler(rtdm_nrtsig_t *nrtsig, void *arg)
|
{
|
struct rtskb *rtskb;
|
struct sk_buff *skb;
|
unsigned hdrlen;
|
struct net_device_stats *stats;
|
struct rtnet_device *rtdev;
|
|
while (1) {
|
rtskb = rtskb_dequeue(&rx_queue);
|
if (!rtskb)
|
break;
|
|
rtdev = rtskb->rtdev;
|
hdrlen = rtdev->hard_header_len;
|
|
skb = dev_alloc_skb(hdrlen + rtskb->len + 2);
|
if (skb) {
|
/* the rtskb stamp is useless (different clock), get new one */
|
__net_timestamp(skb);
|
|
skb_reserve(skb,
|
2); /* Align IP on 16 byte boundaries */
|
|
/* copy Ethernet header */
|
memcpy(skb_put(skb, hdrlen),
|
rtskb->data - hdrlen - sizeof(struct rtmac_hdr),
|
hdrlen);
|
|
/* patch the protocol field in the original Ethernet header */
|
((struct ethhdr *)skb->data)->h_proto = rtskb->protocol;
|
|
/* copy data */
|
memcpy(skb_put(skb, rtskb->len), rtskb->data,
|
rtskb->len);
|
|
skb->dev = rtskb->rtdev->mac_priv->vnic;
|
skb->protocol = eth_type_trans(skb, skb->dev);
|
|
stats = &rtskb->rtdev->mac_priv->vnic_stats;
|
|
kfree_rtskb(rtskb);
|
|
stats->rx_packets++;
|
stats->rx_bytes += skb->len;
|
|
netif_rx(skb);
|
} else {
|
printk("RTmac: VNIC fails to allocate linux skb\n");
|
kfree_rtskb(rtskb);
|
}
|
}
|
}
|
|
static int rtmac_vnic_copy_mac(struct net_device *dev)
|
{
|
dev_addr_set(dev,
|
(*(struct rtnet_device **)netdev_priv(dev))->dev_addr);
|
|
return 0;
|
}
|
|
int rtmac_vnic_xmit(struct sk_buff *skb, struct net_device *dev)
|
{
|
struct rtnet_device *rtdev = *(struct rtnet_device **)netdev_priv(dev);
|
struct net_device_stats *stats = &rtdev->mac_priv->vnic_stats;
|
struct rtskb_pool *pool = &rtdev->mac_priv->vnic_skb_pool;
|
struct ethhdr *ethernet = (struct ethhdr *)skb->data;
|
struct rtskb *rtskb;
|
int res;
|
int data_len;
|
|
rtskb = alloc_rtskb((skb->len + sizeof(struct rtmac_hdr) + 15) & ~15,
|
pool);
|
if (!rtskb)
|
return NETDEV_TX_BUSY;
|
|
rtskb_reserve(rtskb, rtdev->hard_header_len + sizeof(struct rtmac_hdr));
|
|
data_len = skb->len - dev->hard_header_len;
|
memcpy(rtskb_put(rtskb, data_len), skb->data + dev->hard_header_len,
|
data_len);
|
|
res = rtmac_add_header(rtdev, ethernet->h_dest, rtskb,
|
ntohs(ethernet->h_proto), RTMAC_FLAG_TUNNEL);
|
if (res < 0) {
|
stats->tx_dropped++;
|
kfree_rtskb(rtskb);
|
goto done;
|
}
|
|
RTNET_ASSERT(rtdev->mac_disc->nrt_packet_tx != NULL, kfree_rtskb(rtskb);
|
goto done;);
|
|
res = rtdev->mac_disc->nrt_packet_tx(rtskb);
|
if (res < 0) {
|
stats->tx_dropped++;
|
kfree_rtskb(rtskb);
|
} else {
|
stats->tx_packets++;
|
stats->tx_bytes += skb->len;
|
}
|
|
done:
|
dev_kfree_skb(skb);
|
return NETDEV_TX_OK;
|
}
|
|
static struct net_device_stats *rtmac_vnic_get_stats(struct net_device *dev)
|
{
|
return &(*(struct rtnet_device **)netdev_priv(dev))
|
->mac_priv->vnic_stats;
|
}
|
|
static int rtmac_vnic_change_mtu(struct net_device *dev, int new_mtu)
|
{
|
if ((new_mtu < 68) ||
|
((unsigned)new_mtu > 1500 - sizeof(struct rtmac_hdr)))
|
return -EINVAL;
|
dev->mtu = new_mtu;
|
return 0;
|
}
|
|
void rtmac_vnic_set_max_mtu(struct rtnet_device *rtdev, unsigned int max_mtu)
|
{
|
struct rtmac_priv *mac_priv = rtdev->mac_priv;
|
struct net_device *vnic = mac_priv->vnic;
|
unsigned int prev_mtu = mac_priv->vnic_max_mtu;
|
|
mac_priv->vnic_max_mtu = max_mtu - sizeof(struct rtmac_hdr);
|
|
/* set vnic mtu in case max_mtu is smaller than the current mtu or
|
the current mtu was set to previous max_mtu */
|
rtnl_lock();
|
if ((vnic->mtu > mac_priv->vnic_max_mtu) ||
|
(prev_mtu == mac_priv->vnic_max_mtu)) {
|
dev_set_mtu(vnic, mac_priv->vnic_max_mtu);
|
}
|
rtnl_unlock();
|
}
|
|
static struct net_device_ops vnic_netdev_ops = {
|
.ndo_open = rtmac_vnic_copy_mac,
|
.ndo_get_stats = rtmac_vnic_get_stats,
|
.ndo_change_mtu = rtmac_vnic_change_mtu,
|
};
|
|
static void rtmac_vnic_setup(struct net_device *dev)
|
{
|
ether_setup(dev);
|
|
dev->netdev_ops = &vnic_netdev_ops;
|
dev->flags &= ~IFF_MULTICAST;
|
}
|
|
int rtmac_vnic_add(struct rtnet_device *rtdev, vnic_xmit_handler vnic_xmit)
|
{
|
int res;
|
struct rtmac_priv *mac_priv = rtdev->mac_priv;
|
struct net_device *vnic;
|
char buf[IFNAMSIZ];
|
|
/* does the discipline request vnic support? */
|
if (!vnic_xmit)
|
return 0;
|
|
mac_priv->vnic = NULL;
|
mac_priv->vnic_max_mtu = rtdev->mtu - sizeof(struct rtmac_hdr);
|
memset(&mac_priv->vnic_stats, 0, sizeof(mac_priv->vnic_stats));
|
|
/* create the rtskb pool */
|
if (rtskb_pool_init(&mac_priv->vnic_skb_pool, vnic_rtskbs, NULL, NULL) <
|
vnic_rtskbs) {
|
res = -ENOMEM;
|
goto error;
|
}
|
|
snprintf(buf, sizeof(buf), "vnic%d", rtdev->ifindex - 1);
|
|
vnic = alloc_netdev(sizeof(struct rtnet_device *), buf,
|
NET_NAME_UNKNOWN, rtmac_vnic_setup);
|
if (!vnic) {
|
res = -ENOMEM;
|
goto error;
|
}
|
|
vnic_netdev_ops.ndo_start_xmit = vnic_xmit;
|
vnic->mtu = mac_priv->vnic_max_mtu;
|
*(struct rtnet_device **)netdev_priv(vnic) = rtdev;
|
rtmac_vnic_copy_mac(vnic);
|
|
res = register_netdev(vnic);
|
if (res < 0)
|
goto error;
|
|
mac_priv->vnic = vnic;
|
|
return 0;
|
|
error:
|
rtskb_pool_release(&mac_priv->vnic_skb_pool);
|
return res;
|
}
|
|
int rtmac_vnic_unregister(struct rtnet_device *rtdev)
|
{
|
struct rtmac_priv *mac_priv = rtdev->mac_priv;
|
|
if (mac_priv->vnic) {
|
rtskb_pool_release(&mac_priv->vnic_skb_pool);
|
unregister_netdev(mac_priv->vnic);
|
free_netdev(mac_priv->vnic);
|
mac_priv->vnic = NULL;
|
}
|
|
return 0;
|
}
|
|
#ifdef CONFIG_XENO_OPT_VFILE
|
int rtnet_rtmac_vnics_show(struct xnvfile_regular_iterator *it, void *d)
|
{
|
struct rtnet_device *rtdev;
|
int i;
|
int err;
|
|
xnvfile_printf(it, "RT-NIC name\tVNIC name\n");
|
|
for (i = 1; i <= MAX_RT_DEVICES; i++) {
|
rtdev = rtdev_get_by_index(i);
|
if (rtdev == NULL)
|
continue;
|
|
err = mutex_lock_interruptible(&rtdev->nrt_lock);
|
if (err < 0) {
|
rtdev_dereference(rtdev);
|
return err;
|
}
|
|
if (rtdev->mac_priv != NULL) {
|
struct rtmac_priv *rtmac;
|
|
rtmac = (struct rtmac_priv *)rtdev->mac_priv;
|
xnvfile_printf(it, "%-15s %s\n", rtdev->name,
|
rtmac->vnic->name);
|
}
|
|
mutex_unlock(&rtdev->nrt_lock);
|
rtdev_dereference(rtdev);
|
}
|
|
return 0;
|
}
|
#endif /* CONFIG_XENO_OPT_VFILE */
|
|
int __init rtmac_vnic_module_init(void)
|
{
|
rtskb_queue_init(&rx_queue);
|
|
rtdm_nrtsig_init(&vnic_signal, rtmac_vnic_signal_handler, NULL);
|
|
return 0;
|
}
|
|
void rtmac_vnic_module_cleanup(void)
|
{
|
struct rtskb *rtskb;
|
|
rtdm_nrtsig_destroy(&vnic_signal);
|
|
while ((rtskb = rtskb_dequeue(&rx_queue)) != NULL) {
|
kfree_rtskb(rtskb);
|
}
|
}
|