mISDN/drivers/isdn/hardware/mISDN/netdev.c

317 lines
6.2 KiB
C

/*
* gateway between mISDN and Linux's netdev subsystem
*
* Copyright (C) 2005-2006 Christian Richter
*
* Authors: Christian Richter
*
* Thanks to Daniele Orlandi from the vISDN Developer Team
*
* This program is free software and may be modified and distributed
* under the terms and conditions of the GNU General Public License.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/netdevice.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/crc32.h>
#include <linux/mISDNif.h>
#include "core.h"
#include "lapd.h"
wait_queue_head_t waitq;
struct sk_buff_head finishq;
struct mISDN_netdev {
struct sk_buff_head workq;
struct list_head list;
struct net_device *netdev;
struct net_device_stats stats;
mISDNstack_t *st;
int nt;
int addr;
};
LIST_HEAD(mISDN_netdev_list);
static int misdn_chan_frame_xmit(
struct net_device *netdev,
struct sk_buff *skb, int rx)
{
struct mISDN_netdev *ndev=netdev->priv;
struct lapd_prim_hdr *prim_hdr;
netdev->last_rx = jiffies;
skb->protocol = htons(ETH_P_LAPD);
skb->dev = netdev;
skb->ip_summed = CHECKSUM_UNNECESSARY;
skb->pkt_type = PACKET_HOST;
skb_push(skb, sizeof(struct lapd_prim_hdr));
prim_hdr = (struct lapd_prim_hdr *)skb->data;
prim_hdr->primitive_type = LAPD_PH_DATA_INDICATION;
if (rx)
ndev->stats.rx_packets++;
else
ndev->stats.rx_packets++;
return netif_rx(skb);
}
static struct net_device_stats *misdn_netdev_get_stats(
struct net_device *netdev)
{
struct mISDN_netdev *ndev=netdev->priv;
return &ndev->stats;
}
static void misdn_netdev_set_multicast_list(
struct net_device *netdev)
{
}
static int misdn_netdev_do_ioctl(
struct net_device *netdev,
struct ifreq *ifr, int cmd)
{
return -EOPNOTSUPP;
}
static int misdn_netdev_change_mtu(
struct net_device *netdev,
int new_mtu)
{
return 0;
}
static int lapd_mac_addr(struct net_device *dev, void *addr)
{
return 0;
}
static void setup_lapd(struct net_device *netdev)
{
netdev->change_mtu = NULL;
netdev->hard_header = NULL;
netdev->rebuild_header = NULL;
netdev->set_mac_address = lapd_mac_addr;
netdev->hard_header_cache = NULL;
netdev->header_cache_update= NULL;
//netdev->hard_header_parse = lapd_hard_header_parse;
netdev->hard_header_parse = NULL;
netdev->type = ARPHRD_LAPD;
netdev->hard_header_len = 0;
netdev->addr_len = 1;
netdev->tx_queue_len = 10;
memset(netdev->broadcast, 0, sizeof(netdev->broadcast));
netdev->flags = 0;
}
static int misdn_netdev_open(struct net_device *netdev)
{
return 0;
}
static int misdn_netdev_stop(struct net_device *netdev)
{
return 0;
}
static int mISDN_netdevd(void *data)
{
struct sk_buff *skb;
struct mISDN_netdev *ndev;
for(;;) {
wait_event_interruptible(waitq, 1);
if ((skb=skb_dequeue(&finishq))) {
kfree_skb(skb);
return 0;
}
list_for_each_entry(ndev, &mISDN_netdev_list, list) {
while ((skb=skb_dequeue(&ndev->workq))) {
misdn_chan_frame_xmit(ndev->netdev, skb, 1);
}
}
}
return 0;
}
static int misdn_netdev_frame_xmit(
struct sk_buff *skb,
struct net_device *netdev)
{
return 0;
}
/*************************/
/** Interface Functions **/
/*************************/
void misdn_log_frame(mISDNstack_t* st, unsigned char *data, int len, int direction)
{
struct sk_buff *skb;
struct mISDN_netdev *ndev;
int i;
skb = alloc_skb(sizeof(struct lapd_prim_hdr)+len, GFP_KERNEL);
if (!skb) {
printk ("%s: no mem for skb\n", __FUNCTION__);
return;
}
skb_reserve(skb, sizeof(struct lapd_prim_hdr));
skb_reserve(skb, 4);
memcpy(skb_put(skb, len), data, len);
list_for_each_entry(ndev, &mISDN_netdev_list, list) {
if (ndev->st==st) break;
}
// use syslog as long as kernel oops when using netdev
printk ("%s: st(%x): %s ", __FUNCTION__, st->id, (direction==FLG_MSG_UP)?"-->":"<--");
for (i=0; i<len; i++)
printk("0x%02x ", data[i]);
printk ("\n");
kfree_skb(skb);
/*
if (ndev)
misdn_chan_frame_xmit(ndev->netdev, skb , 1);
else
printk(KERN_WARNING "No mISDN_netdev for stack:%x\n",st->id);
*/
}
int misdn_netdev_addstack(mISDNstack_t *st)
{
struct net_device *netdev;
struct mISDN_netdev *ndev;
int err;
char name[8];
sprintf(name, "mISDN%02x",
((((st->id >> 8) & 0xFF) +
((st->id >> 12) & 0xFF))
& 0xFF));
printk(KERN_NOTICE "allocating netdev(%s) for stack(%x)\n", name, st->id);
netdev = alloc_netdev(0, name, setup_lapd);
if(!netdev) {
printk(KERN_ERR "net_device alloc failed, abort.\n");
return -ENOMEM;
}
ndev = kmalloc(sizeof(struct mISDN_netdev), GFP_KERNEL);
if(!ndev) {
printk(KERN_ERR "mISDN_netdevice alloc failed, abort.\n");
return -ENOMEM;
}
memset(&ndev->stats,0,sizeof(ndev->stats));
skb_queue_head_init(&ndev->workq);
ndev->netdev=netdev;
netdev->priv = ndev;
netdev->open = misdn_netdev_open;
netdev->stop = misdn_netdev_stop;
netdev->hard_start_xmit = misdn_netdev_frame_xmit;
netdev->get_stats = misdn_netdev_get_stats;
netdev->set_multicast_list = misdn_netdev_set_multicast_list;
netdev->do_ioctl = misdn_netdev_do_ioctl;
netdev->change_mtu = misdn_netdev_change_mtu;
netdev->features = NETIF_F_NO_CSUM;
switch (st->pid.protocol[0] & ~ISDN_PID_FEATURE_MASK) {
case ISDN_PID_L0_TE_S0:
case ISDN_PID_L0_TE_E1:
printk(KERN_NOTICE " --> TE Mode\n");
memset(netdev->dev_addr, 0, sizeof(netdev->dev_addr));
break;
case ISDN_PID_L0_NT_S0:
case ISDN_PID_L0_NT_E1:
printk(KERN_NOTICE " --> NT Mode\n");
memset(netdev->dev_addr, 1, sizeof(netdev->dev_addr));
break;
}
SET_MODULE_OWNER(netdev);
netdev->irq = 0;
netdev->base_addr = 0;
list_add(&ndev->list, &mISDN_netdev_list);
err = register_netdev(netdev);
if (err < 0) {
printk(KERN_ERR "Cannot register net device %s, aborting.\n",
netdev->name);
}
return 0;
}
int misdn_netdev_init(void)
{
int err=0;
init_waitqueue_head(&waitq);
skb_queue_head_init(&finishq);
kernel_thread(mISDN_netdevd, NULL, 0);
return err;
}
void misdn_netdev_exit(void)
{
struct sk_buff *skb=alloc_skb(8,GFP_KERNEL);
struct mISDN_netdev *ndev;
skb_queue_tail(&finishq,skb);
list_for_each_entry(ndev, &mISDN_netdev_list, list) {
unregister_netdev(ndev->netdev);
}
}