Commit d4a8ce7f authored by Christian Gromm's avatar Christian Gromm Committed by Greg Kroah-Hartman

Staging: most: add MOST driver's aim-network module

This patch adds the aim-network module of the MOST driver to the kernel's
driver staging area. This module is part of the MOST driver and handles
user space interaction by means of network devices.

This patch is needed in order to have access to MOST Ethernet Packets (MEP)
through a networking device.
Signed-off-by: default avatarChristian Gromm <christian.gromm@microchip.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 9bc79bbc
...@@ -15,4 +15,6 @@ source "drivers/staging/most/mostcore/Kconfig" ...@@ -15,4 +15,6 @@ source "drivers/staging/most/mostcore/Kconfig"
source "drivers/staging/most/aim-cdev/Kconfig" source "drivers/staging/most/aim-cdev/Kconfig"
source "drivers/staging/most/aim-network/Kconfig"
endif endif
obj-$(CONFIG_MOSTCORE) += mostcore/ obj-$(CONFIG_MOSTCORE) += mostcore/
obj-$(CONFIG_AIM_CDEV) += aim-cdev/ obj-$(CONFIG_AIM_CDEV) += aim-cdev/
obj-$(CONFIG_AIM_NETWORK) += aim-network/
#
# MOST Networking configuration
#
config AIM_NETWORK
tristate "Networking AIM"
---help---
Say Y here if you want to commumicate via a networking device.
To compile this driver as a module, choose M here: the
module will be called aim_networking.
obj-$(CONFIG_AIM_NETWORK) += aim_network.o
aim_network-objs := networking.o
ccflags-y += -Idrivers/staging/most/mostcore/
/*
* 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;
unsigned char link_stat;
struct net_device *dev;
struct net_dev_channel rx;
struct net_dev_channel tx;
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;
pr_info("open net device %s\n", dev->name);
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)) {
pr_err("most_start_channel() failed\n");
return -EBUSY;
}
if (most_start_channel(nd->iface, nd->tx.ch_id)) {
pr_err("most_start_channel() failed\n");
most_stop_channel(nd->iface, nd->rx.ch_id);
return -EBUSY;
}
nd->channels_opened = true;
if (nd->is_mamac) {
nd->link_stat = 1;
netif_wake_queue(dev);
} else {
nd->iface->request_netinfo(nd->iface, nd->tx.ch_id);
}
return 0;
}
static int most_nd_stop(struct net_device *dev)
{
struct net_dev_context *nd = dev->ml_priv;
pr_info("stop net device %s\n", dev->name);
BUG_ON(nd->dev != dev);
netif_stop_queue(dev);
if (nd->channels_opened) {
most_stop_channel(nd->iface, nd->rx.ch_id);
most_stop_channel(nd->iface, nd->tx.ch_id);
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);
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)
{
pr_info("setup net device %s\n", dev->name);
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 = 0;
}
static struct net_dev_context *get_net_dev_context(
struct most_interface *iface)
{
struct net_dev_context *nd, *tmp;
spin_lock(&list_lock);
list_for_each_entry_safe(nd, tmp, &net_devices, list) {
if (nd->iface == iface) {
spin_unlock(&list_lock);
return nd;
}
}
spin_unlock(&list_lock);
return 0;
}
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;
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;
nd->iface = iface;
spin_lock(&list_lock);
list_add(&nd->list, &net_devices);
spin_unlock(&list_lock);
}
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;
dev->ml_priv = nd;
if (register_netdev(dev)) {
pr_err("registering net device failed\n");
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;
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(&list_lock);
list_del(&nd->list);
spin_unlock(&list_lock);
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;
uint32_t len = mbo->processed_length;
struct sk_buff *skb;
struct net_device *dev;
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 */
memcpy(skb_put(skb, ETH_ALEN), dev->dev_addr, ETH_ALEN);
/* 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);
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
netif_rx(skb);
out:
most_put_mbo(mbo);
return 0;
}
static int __init most_net_init(void)
{
pr_info("most_net_init()\n");
spin_lock_init(&list_lock);
aim.name = "networking";
aim.probe_channel = aim_probe_channel;
aim.disconnect_channel = aim_disconnect_channel;
aim.tx_completion = aim_resume_tx_channel;
aim.rx_completion = aim_rx_data;
return most_register_aim(&aim);
}
static void __exit most_net_exit(void)
{
struct net_dev_context *nd, *tmp;
spin_lock(&list_lock);
list_for_each_entry_safe(nd, tmp, &net_devices, list) {
list_del(&nd->list);
spin_unlock(&list_lock);
/*
* 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(&list_lock);
}
spin_unlock(&list_lock);
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;
pr_info("Received netinfo from %s\n", iface->description);
nd = get_net_dev_context(iface);
if (!nd)
return;
dev = nd->dev;
if (!dev)
return;
if (mac_addr)
memcpy(dev->dev_addr, mac_addr, ETH_ALEN);
if (nd->link_stat != link_stat) {
nd->link_stat = link_stat;
if (nd->link_stat)
netif_wake_queue(dev);
else
netif_stop_queue(dev);
}
}
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");
/*
* 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.
*/
#ifndef _NETWORKING_H_
#define _NETWORKING_H_
#include "mostcore.h"
void most_deliver_netinfo(struct most_interface *iface,
unsigned char link_stat, unsigned char *mac_addr);
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment