/* rtmac_vnic.c * * rtmac - real-time networking media access control subsystem * Copyright (C) 2002 Marc Kleine-Budde , * 2003-2005 Jan Kiszka * * 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 #include #include #include #include #include #include /* for netdev_priv() */ #include #include #include 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); } }