595 lines
13 KiB
C
595 lines
13 KiB
C
/*
|
|
* Networking AIM - Networking Application Interface Module for MostCore
|
|
*
|
|
* Copyright (C) 2015, Microchip Technology Germany II GmbH & Co. KG
|
|
*
|
|
* 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.
|
|
*
|
|
* This file is licensed under GPLv2.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/init.h>
|
|
#include <linux/list.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/kobject.h>
|
|
#include "mostcore.h"
|
|
#include "networking.h"
|
|
|
|
#define MEP_HDR_LEN 8
|
|
#define MDP_HDR_LEN 16
|
|
#define MAMAC_DATA_LEN (1024 - MDP_HDR_LEN)
|
|
|
|
#define PMHL 5
|
|
|
|
#define PMS_TELID_UNSEGM_MAMAC 0x0A
|
|
#define PMS_FIFONO_MDP 0x01
|
|
#define PMS_FIFONO_MEP 0x04
|
|
#define PMS_MSGTYPE_DATA 0x04
|
|
#define PMS_DEF_PRIO 0
|
|
#define MEP_DEF_RETRY 15
|
|
|
|
#define PMS_FIFONO_MASK 0x07
|
|
#define PMS_FIFONO_SHIFT 3
|
|
#define PMS_RETRY_SHIFT 4
|
|
#define PMS_TELID_MASK 0x0F
|
|
#define PMS_TELID_SHIFT 4
|
|
|
|
#define HB(value) ((u8)((u16)(value) >> 8))
|
|
#define LB(value) ((u8)(value))
|
|
|
|
#define EXTRACT_BIT_SET(bitset_name, value) \
|
|
(((value) >> bitset_name##_SHIFT) & bitset_name##_MASK)
|
|
|
|
#define PMS_IS_MEP(buf, len) \
|
|
((len) > MEP_HDR_LEN && \
|
|
EXTRACT_BIT_SET(PMS_FIFONO, (buf)[3]) == PMS_FIFONO_MEP)
|
|
|
|
#define PMS_IS_MAMAC(buf, len) \
|
|
((len) > MDP_HDR_LEN && \
|
|
EXTRACT_BIT_SET(PMS_FIFONO, (buf)[3]) == PMS_FIFONO_MDP && \
|
|
EXTRACT_BIT_SET(PMS_TELID, (buf)[14]) == PMS_TELID_UNSEGM_MAMAC)
|
|
|
|
struct net_dev_channel {
|
|
bool linked;
|
|
int ch_id;
|
|
};
|
|
|
|
struct net_dev_context {
|
|
struct most_interface *iface;
|
|
bool channels_opened;
|
|
bool is_mamac;
|
|
struct net_device *dev;
|
|
struct net_dev_channel rx;
|
|
struct net_dev_channel tx;
|
|
struct completion mac_compl;
|
|
struct list_head list;
|
|
};
|
|
|
|
static struct list_head net_devices = LIST_HEAD_INIT(net_devices);
|
|
static struct spinlock list_lock;
|
|
static struct most_aim aim;
|
|
|
|
static int skb_to_mamac(const struct sk_buff *skb, struct mbo *mbo)
|
|
{
|
|
u8 *buff = mbo->virt_address;
|
|
const u8 broadcast[] = { 0x03, 0xFF };
|
|
const u8 *dest_addr = skb->data + 4;
|
|
const u8 *eth_type = skb->data + 12;
|
|
unsigned int payload_len = skb->len - ETH_HLEN;
|
|
unsigned int mdp_len = payload_len + MDP_HDR_LEN;
|
|
|
|
if (mbo->buffer_length < mdp_len) {
|
|
pr_err("drop: too small buffer! (%d for %d)\n",
|
|
mbo->buffer_length, mdp_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (skb->len < ETH_HLEN) {
|
|
pr_err("drop: too small packet! (%d)\n", skb->len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dest_addr[0] == 0xFF && dest_addr[1] == 0xFF)
|
|
dest_addr = broadcast;
|
|
|
|
*buff++ = HB(mdp_len - 2);
|
|
*buff++ = LB(mdp_len - 2);
|
|
|
|
*buff++ = PMHL;
|
|
*buff++ = (PMS_FIFONO_MDP << PMS_FIFONO_SHIFT) | PMS_MSGTYPE_DATA;
|
|
*buff++ = PMS_DEF_PRIO;
|
|
*buff++ = dest_addr[0];
|
|
*buff++ = dest_addr[1];
|
|
*buff++ = 0x00;
|
|
|
|
*buff++ = HB(payload_len + 6);
|
|
*buff++ = LB(payload_len + 6);
|
|
|
|
/* end of FPH here */
|
|
|
|
*buff++ = eth_type[0];
|
|
*buff++ = eth_type[1];
|
|
*buff++ = 0;
|
|
*buff++ = 0;
|
|
|
|
*buff++ = PMS_TELID_UNSEGM_MAMAC << 4 | HB(payload_len);
|
|
*buff++ = LB(payload_len);
|
|
|
|
memcpy(buff, skb->data + ETH_HLEN, payload_len);
|
|
mbo->buffer_length = mdp_len;
|
|
return 0;
|
|
}
|
|
|
|
static int skb_to_mep(const struct sk_buff *skb, struct mbo *mbo)
|
|
{
|
|
u8 *buff = mbo->virt_address;
|
|
unsigned int mep_len = skb->len + MEP_HDR_LEN;
|
|
|
|
if (mbo->buffer_length < mep_len) {
|
|
pr_err("drop: too small buffer! (%d for %d)\n",
|
|
mbo->buffer_length, mep_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
*buff++ = HB(mep_len - 2);
|
|
*buff++ = LB(mep_len - 2);
|
|
|
|
*buff++ = PMHL;
|
|
*buff++ = (PMS_FIFONO_MEP << PMS_FIFONO_SHIFT) | PMS_MSGTYPE_DATA;
|
|
*buff++ = (MEP_DEF_RETRY << PMS_RETRY_SHIFT) | PMS_DEF_PRIO;
|
|
*buff++ = 0;
|
|
*buff++ = 0;
|
|
*buff++ = 0;
|
|
|
|
memcpy(buff, skb->data, skb->len);
|
|
mbo->buffer_length = mep_len;
|
|
return 0;
|
|
}
|
|
|
|
static int most_nd_set_mac_address(struct net_device *dev, void *p)
|
|
{
|
|
struct net_dev_context *nd = dev->ml_priv;
|
|
int err = eth_mac_addr(dev, p);
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
BUG_ON(nd->dev != dev);
|
|
|
|
nd->is_mamac =
|
|
(dev->dev_addr[0] == 0 && dev->dev_addr[1] == 0 &&
|
|
dev->dev_addr[2] == 0 && dev->dev_addr[3] == 0);
|
|
|
|
/*
|
|
* Set default MTU for the given packet type.
|
|
* It is still possible to change MTU using ip tools afterwards.
|
|
*/
|
|
dev->mtu = nd->is_mamac ? MAMAC_DATA_LEN : ETH_DATA_LEN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int most_nd_open(struct net_device *dev)
|
|
{
|
|
struct net_dev_context *nd = dev->ml_priv;
|
|
long ret;
|
|
|
|
netdev_info(dev, "open net device\n");
|
|
|
|
BUG_ON(nd->dev != dev);
|
|
|
|
if (nd->channels_opened)
|
|
return -EFAULT;
|
|
|
|
BUG_ON(!nd->tx.linked || !nd->rx.linked);
|
|
|
|
if (most_start_channel(nd->iface, nd->rx.ch_id, &aim)) {
|
|
netdev_err(dev, "most_start_channel() failed\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (most_start_channel(nd->iface, nd->tx.ch_id, &aim)) {
|
|
netdev_err(dev, "most_start_channel() failed\n");
|
|
most_stop_channel(nd->iface, nd->rx.ch_id, &aim);
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (!is_valid_ether_addr(dev->dev_addr)) {
|
|
nd->iface->request_netinfo(nd->iface, nd->tx.ch_id);
|
|
ret = wait_for_completion_interruptible_timeout(
|
|
&nd->mac_compl, msecs_to_jiffies(5000));
|
|
if (!ret) {
|
|
netdev_err(dev, "mac timeout\n");
|
|
ret = -EBUSY;
|
|
goto err;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
netdev_warn(dev, "mac waiting interrupted\n");
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
nd->channels_opened = true;
|
|
netif_wake_queue(dev);
|
|
return 0;
|
|
|
|
err:
|
|
most_stop_channel(nd->iface, nd->tx.ch_id, &aim);
|
|
most_stop_channel(nd->iface, nd->rx.ch_id, &aim);
|
|
return ret;
|
|
}
|
|
|
|
static int most_nd_stop(struct net_device *dev)
|
|
{
|
|
struct net_dev_context *nd = dev->ml_priv;
|
|
|
|
netdev_info(dev, "stop net device\n");
|
|
|
|
BUG_ON(nd->dev != dev);
|
|
netif_stop_queue(dev);
|
|
|
|
if (nd->channels_opened) {
|
|
most_stop_channel(nd->iface, nd->rx.ch_id, &aim);
|
|
most_stop_channel(nd->iface, nd->tx.ch_id, &aim);
|
|
nd->channels_opened = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static netdev_tx_t most_nd_start_xmit(struct sk_buff *skb,
|
|
struct net_device *dev)
|
|
{
|
|
struct net_dev_context *nd = dev->ml_priv;
|
|
struct mbo *mbo;
|
|
int ret;
|
|
|
|
BUG_ON(nd->dev != dev);
|
|
|
|
mbo = most_get_mbo(nd->iface, nd->tx.ch_id, &aim);
|
|
|
|
if (!mbo) {
|
|
netif_stop_queue(dev);
|
|
dev->stats.tx_fifo_errors++;
|
|
return NETDEV_TX_BUSY;
|
|
}
|
|
|
|
if (nd->is_mamac)
|
|
ret = skb_to_mamac(skb, mbo);
|
|
else
|
|
ret = skb_to_mep(skb, mbo);
|
|
|
|
if (ret) {
|
|
most_put_mbo(mbo);
|
|
dev->stats.tx_dropped++;
|
|
kfree_skb(skb);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
most_submit_mbo(mbo);
|
|
dev->stats.tx_packets++;
|
|
dev->stats.tx_bytes += skb->len;
|
|
kfree_skb(skb);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
static const struct net_device_ops most_nd_ops = {
|
|
.ndo_open = most_nd_open,
|
|
.ndo_stop = most_nd_stop,
|
|
.ndo_start_xmit = most_nd_start_xmit,
|
|
.ndo_set_mac_address = most_nd_set_mac_address,
|
|
};
|
|
|
|
static void most_nd_setup(struct net_device *dev)
|
|
{
|
|
ether_setup(dev);
|
|
dev->netdev_ops = &most_nd_ops;
|
|
}
|
|
|
|
static void most_net_rm_netdev_safe(struct net_dev_context *nd)
|
|
{
|
|
if (!nd->dev)
|
|
return;
|
|
|
|
pr_info("remove net device %p\n", nd->dev);
|
|
|
|
unregister_netdev(nd->dev);
|
|
free_netdev(nd->dev);
|
|
nd->dev = NULL;
|
|
}
|
|
|
|
static struct net_dev_context *get_net_dev_context(
|
|
struct most_interface *iface)
|
|
{
|
|
struct net_dev_context *nd, *tmp;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&list_lock, flags);
|
|
list_for_each_entry_safe(nd, tmp, &net_devices, list) {
|
|
if (nd->iface == iface) {
|
|
spin_unlock_irqrestore(&list_lock, flags);
|
|
return nd;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&list_lock, flags);
|
|
return NULL;
|
|
}
|
|
|
|
static int aim_probe_channel(struct most_interface *iface, int channel_idx,
|
|
struct most_channel_config *ccfg,
|
|
struct kobject *parent, char *name)
|
|
{
|
|
struct net_dev_context *nd;
|
|
struct net_dev_channel *ch;
|
|
unsigned long flags;
|
|
|
|
if (!iface)
|
|
return -EINVAL;
|
|
|
|
if (ccfg->data_type != MOST_CH_ASYNC)
|
|
return -EINVAL;
|
|
|
|
nd = get_net_dev_context(iface);
|
|
|
|
if (!nd) {
|
|
nd = kzalloc(sizeof(*nd), GFP_KERNEL);
|
|
if (!nd)
|
|
return -ENOMEM;
|
|
|
|
init_completion(&nd->mac_compl);
|
|
nd->iface = iface;
|
|
|
|
spin_lock_irqsave(&list_lock, flags);
|
|
list_add(&nd->list, &net_devices);
|
|
spin_unlock_irqrestore(&list_lock, flags);
|
|
}
|
|
|
|
ch = ccfg->direction == MOST_CH_TX ? &nd->tx : &nd->rx;
|
|
if (ch->linked) {
|
|
pr_err("only one channel per instance & direction allowed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (nd->tx.linked || nd->rx.linked) {
|
|
struct net_device *dev =
|
|
alloc_netdev(0, "meth%d", NET_NAME_UNKNOWN,
|
|
most_nd_setup);
|
|
|
|
if (!dev) {
|
|
pr_err("no memory for net_device\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
nd->dev = dev;
|
|
ch->ch_id = channel_idx;
|
|
ch->linked = true;
|
|
|
|
dev->ml_priv = nd;
|
|
if (register_netdev(dev)) {
|
|
pr_err("registering net device failed\n");
|
|
ch->linked = false;
|
|
free_netdev(dev);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
ch->ch_id = channel_idx;
|
|
ch->linked = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aim_disconnect_channel(struct most_interface *iface,
|
|
int channel_idx)
|
|
{
|
|
struct net_dev_context *nd;
|
|
struct net_dev_channel *ch;
|
|
unsigned long flags;
|
|
|
|
nd = get_net_dev_context(iface);
|
|
if (!nd)
|
|
return -EINVAL;
|
|
|
|
if (nd->rx.linked && channel_idx == nd->rx.ch_id)
|
|
ch = &nd->rx;
|
|
else if (nd->tx.linked && channel_idx == nd->tx.ch_id)
|
|
ch = &nd->tx;
|
|
else
|
|
return -EINVAL;
|
|
|
|
ch->linked = false;
|
|
|
|
/*
|
|
* do not call most_stop_channel() here, because channels are
|
|
* going to be closed in ndo_stop() after unregister_netdev()
|
|
*/
|
|
most_net_rm_netdev_safe(nd);
|
|
|
|
if (!nd->rx.linked && !nd->tx.linked) {
|
|
spin_lock_irqsave(&list_lock, flags);
|
|
list_del(&nd->list);
|
|
spin_unlock_irqrestore(&list_lock, flags);
|
|
kfree(nd);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aim_resume_tx_channel(struct most_interface *iface,
|
|
int channel_idx)
|
|
{
|
|
struct net_dev_context *nd;
|
|
|
|
nd = get_net_dev_context(iface);
|
|
if (!nd || !nd->channels_opened || nd->tx.ch_id != channel_idx)
|
|
return 0;
|
|
|
|
if (!nd->dev)
|
|
return 0;
|
|
|
|
netif_wake_queue(nd->dev);
|
|
return 0;
|
|
}
|
|
|
|
static int aim_rx_data(struct mbo *mbo)
|
|
{
|
|
const u32 zero = 0;
|
|
struct net_dev_context *nd;
|
|
char *buf = mbo->virt_address;
|
|
u32 len = mbo->processed_length;
|
|
struct sk_buff *skb;
|
|
struct net_device *dev;
|
|
unsigned int skb_len;
|
|
|
|
nd = get_net_dev_context(mbo->ifp);
|
|
if (!nd || !nd->channels_opened || nd->rx.ch_id != mbo->hdm_channel_id)
|
|
return -EIO;
|
|
|
|
dev = nd->dev;
|
|
if (!dev) {
|
|
pr_err_once("drop packet: missing net_device\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (nd->is_mamac) {
|
|
if (!PMS_IS_MAMAC(buf, len))
|
|
return -EIO;
|
|
|
|
skb = dev_alloc_skb(len - MDP_HDR_LEN + 2 * ETH_ALEN + 2);
|
|
} else {
|
|
if (!PMS_IS_MEP(buf, len))
|
|
return -EIO;
|
|
|
|
skb = dev_alloc_skb(len - MEP_HDR_LEN);
|
|
}
|
|
|
|
if (!skb) {
|
|
dev->stats.rx_dropped++;
|
|
pr_err_once("drop packet: no memory for skb\n");
|
|
goto out;
|
|
}
|
|
|
|
skb->dev = dev;
|
|
|
|
if (nd->is_mamac) {
|
|
/* dest */
|
|
ether_addr_copy(skb_put(skb, ETH_ALEN), dev->dev_addr);
|
|
|
|
/* src */
|
|
memcpy(skb_put(skb, 4), &zero, 4);
|
|
memcpy(skb_put(skb, 2), buf + 5, 2);
|
|
|
|
/* eth type */
|
|
memcpy(skb_put(skb, 2), buf + 10, 2);
|
|
|
|
buf += MDP_HDR_LEN;
|
|
len -= MDP_HDR_LEN;
|
|
} else {
|
|
buf += MEP_HDR_LEN;
|
|
len -= MEP_HDR_LEN;
|
|
}
|
|
|
|
memcpy(skb_put(skb, len), buf, len);
|
|
skb->protocol = eth_type_trans(skb, dev);
|
|
skb_len = skb->len;
|
|
if (netif_rx(skb) == NET_RX_SUCCESS) {
|
|
dev->stats.rx_packets++;
|
|
dev->stats.rx_bytes += skb_len;
|
|
} else {
|
|
dev->stats.rx_dropped++;
|
|
}
|
|
|
|
out:
|
|
most_put_mbo(mbo);
|
|
return 0;
|
|
}
|
|
|
|
static struct most_aim aim = {
|
|
.name = "networking",
|
|
.probe_channel = aim_probe_channel,
|
|
.disconnect_channel = aim_disconnect_channel,
|
|
.tx_completion = aim_resume_tx_channel,
|
|
.rx_completion = aim_rx_data,
|
|
};
|
|
|
|
static int __init most_net_init(void)
|
|
{
|
|
pr_info("most_net_init()\n");
|
|
spin_lock_init(&list_lock);
|
|
return most_register_aim(&aim);
|
|
}
|
|
|
|
static void __exit most_net_exit(void)
|
|
{
|
|
struct net_dev_context *nd, *tmp;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&list_lock, flags);
|
|
list_for_each_entry_safe(nd, tmp, &net_devices, list) {
|
|
list_del(&nd->list);
|
|
spin_unlock_irqrestore(&list_lock, flags);
|
|
/*
|
|
* do not call most_stop_channel() here, because channels are
|
|
* going to be closed in ndo_stop() after unregister_netdev()
|
|
*/
|
|
most_net_rm_netdev_safe(nd);
|
|
kfree(nd);
|
|
spin_lock_irqsave(&list_lock, flags);
|
|
}
|
|
spin_unlock_irqrestore(&list_lock, flags);
|
|
|
|
most_deregister_aim(&aim);
|
|
pr_info("most_net_exit()\n");
|
|
}
|
|
|
|
/**
|
|
* most_deliver_netinfo - callback for HDM to be informed about HW's MAC
|
|
* @param iface - most interface instance
|
|
* @param link_stat - link status
|
|
* @param mac_addr - MAC address
|
|
*/
|
|
void most_deliver_netinfo(struct most_interface *iface,
|
|
unsigned char link_stat, unsigned char *mac_addr)
|
|
{
|
|
struct net_dev_context *nd;
|
|
struct net_device *dev;
|
|
const u8 *m = mac_addr;
|
|
|
|
nd = get_net_dev_context(iface);
|
|
if (!nd)
|
|
return;
|
|
|
|
dev = nd->dev;
|
|
if (!dev)
|
|
return;
|
|
|
|
if (m && is_valid_ether_addr(m)) {
|
|
if (!is_valid_ether_addr(dev->dev_addr)) {
|
|
netdev_info(dev, "set mac %02x-%02x-%02x-%02x-%02x-%02x\n",
|
|
m[0], m[1], m[2], m[3], m[4], m[5]);
|
|
ether_addr_copy(dev->dev_addr, m);
|
|
complete(&nd->mac_compl);
|
|
} else if (!ether_addr_equal(dev->dev_addr, m)) {
|
|
netdev_warn(dev, "reject mac %02x-%02x-%02x-%02x-%02x-%02x\n",
|
|
m[0], m[1], m[2], m[3], m[4], m[5]);
|
|
}
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(most_deliver_netinfo);
|
|
|
|
module_init(most_net_init);
|
|
module_exit(most_net_exit);
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Andrey Shvetsov <andrey.shvetsov@k2l.de>");
|
|
MODULE_DESCRIPTION("Networking Application Interface Module for MostCore");
|