02d30ab799
There are not any major distributions that are still supporting kernels older than 2.6.27 so we can remove many typedefs. The primary motivator for this change is that kernel 5.0 is dropping support for timeval and it would be ideal if the in-kernel time representation can standardize on ktime_t, but 2.6.18 did not support the ktime interface that was needed. Signed-off-by: Shaun Ruffell <sruffell@sruffell.net>
420 lines
9.3 KiB
C
420 lines
9.3 KiB
C
/*
|
|
* Dynamic Span Interface for DAHDI (Ethernet Interface)
|
|
*
|
|
* Written by Mark Spencer <markster@digium.com>
|
|
*
|
|
* Copyright (C) 2001-2012, Digium, Inc.
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* See http://www.asterisk.org for more information about
|
|
* the Asterisk project. Please do not directly contact
|
|
* any of the maintainers of this project for assistance;
|
|
* the project provides a web site, mailing lists and IRC
|
|
* channels for your use.
|
|
*
|
|
* This program is free software, distributed under the terms of
|
|
* the GNU General Public License Version 2 as published by the
|
|
* Free Software Foundation. See the LICENSE file included with
|
|
* this program for more details.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/kmod.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/notifier.h>
|
|
|
|
#include <dahdi/kernel.h>
|
|
|
|
#define ETH_P_DAHDI_DETH 0xd00d
|
|
|
|
struct ztdeth_header {
|
|
unsigned short subaddr;
|
|
};
|
|
|
|
/* We take the raw message, put it in an ethernet frame, and add a
|
|
two byte addressing header at the top for future use */
|
|
static DEFINE_SPINLOCK(zlock);
|
|
|
|
static struct sk_buff_head skbs;
|
|
|
|
static struct ztdeth {
|
|
unsigned char addr[ETH_ALEN];
|
|
unsigned short subaddr; /* Network byte order */
|
|
struct dahdi_span *span;
|
|
char ethdev[IFNAMSIZ];
|
|
struct net_device *dev;
|
|
struct ztdeth *next;
|
|
} *zdevs = NULL;
|
|
|
|
static struct dahdi_span *ztdeth_getspan(unsigned char *addr, unsigned short subaddr)
|
|
{
|
|
unsigned long flags;
|
|
struct ztdeth *z;
|
|
struct dahdi_span *span = NULL;
|
|
spin_lock_irqsave(&zlock, flags);
|
|
z = zdevs;
|
|
while(z) {
|
|
if (!memcmp(addr, z->addr, ETH_ALEN) &&
|
|
z->subaddr == subaddr)
|
|
break;
|
|
z = z->next;
|
|
}
|
|
if (z)
|
|
span = z->span;
|
|
spin_unlock_irqrestore(&zlock, flags);
|
|
if (!span || !test_bit(DAHDI_FLAGBIT_REGISTERED, &span->flags))
|
|
return NULL;
|
|
return span;
|
|
}
|
|
|
|
static int ztdeth_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
|
|
{
|
|
struct dahdi_span *span;
|
|
struct ztdeth_header *zh;
|
|
zh = (struct ztdeth_header *)skb_network_header(skb);
|
|
span = ztdeth_getspan(eth_hdr(skb)->h_source, zh->subaddr);
|
|
if (span) {
|
|
skb_pull(skb, sizeof(struct ztdeth_header));
|
|
#ifdef NEW_SKB_LINEARIZE
|
|
if (skb_is_nonlinear(skb))
|
|
skb_linearize(skb);
|
|
#else
|
|
if (skb_is_nonlinear(skb))
|
|
skb_linearize(skb, GFP_KERNEL);
|
|
#endif
|
|
dahdi_dynamic_receive(span, (unsigned char *)skb->data, skb->len);
|
|
}
|
|
kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
static int ztdeth_notifier(struct notifier_block *block, unsigned long event, void *ptr)
|
|
{
|
|
struct net_device *dev = ptr;
|
|
struct ztdeth *z;
|
|
unsigned long flags;
|
|
switch(event) {
|
|
case NETDEV_GOING_DOWN:
|
|
case NETDEV_DOWN:
|
|
spin_lock_irqsave(&zlock, flags);
|
|
z = zdevs;
|
|
while(z) {
|
|
/* Note that the device no longer exists */
|
|
if (z->dev == dev)
|
|
z->dev = NULL;
|
|
z = z->next;
|
|
}
|
|
spin_unlock_irqrestore(&zlock, flags);
|
|
break;
|
|
case NETDEV_UP:
|
|
spin_lock_irqsave(&zlock, flags);
|
|
z = zdevs;
|
|
while(z) {
|
|
/* Now that the device exists again, use it */
|
|
if (!strcmp(z->ethdev, dev->name))
|
|
z->dev = dev;
|
|
z = z->next;
|
|
}
|
|
spin_unlock_irqrestore(&zlock, flags);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ztdeth_transmit(struct dahdi_dynamic *dyn, u8 *msg, size_t msglen)
|
|
{
|
|
struct ztdeth *z;
|
|
struct sk_buff *skb;
|
|
struct ztdeth_header *zh;
|
|
unsigned long flags;
|
|
struct net_device *dev;
|
|
unsigned char addr[ETH_ALEN];
|
|
unsigned short subaddr; /* Network byte order */
|
|
|
|
spin_lock_irqsave(&zlock, flags);
|
|
z = dyn->pvt;
|
|
if (z && z->dev) {
|
|
/* Copy fields to local variables to remove spinlock ASAP */
|
|
dev = z->dev;
|
|
memcpy(addr, z->addr, sizeof(z->addr));
|
|
subaddr = z->subaddr;
|
|
spin_unlock_irqrestore(&zlock, flags);
|
|
skb = dev_alloc_skb(msglen + dev->hard_header_len + sizeof(struct ztdeth_header) + 32);
|
|
if (skb) {
|
|
/* Reserve header space */
|
|
skb_reserve(skb, dev->hard_header_len + sizeof(struct ztdeth_header));
|
|
|
|
/* Copy message body */
|
|
memcpy(skb_put(skb, msglen), msg, msglen);
|
|
|
|
/* Throw on header */
|
|
zh = (struct ztdeth_header *)skb_push(skb, sizeof(struct ztdeth_header));
|
|
zh->subaddr = subaddr;
|
|
|
|
/* Setup protocol and such */
|
|
skb->protocol = __constant_htons(ETH_P_DAHDI_DETH);
|
|
skb_set_network_header(skb, 0);
|
|
skb->dev = dev;
|
|
dev_hard_header(skb, dev, ETH_P_DAHDI_DETH, addr, dev->dev_addr, skb->len);
|
|
skb_queue_tail(&skbs, skb);
|
|
}
|
|
}
|
|
else
|
|
spin_unlock_irqrestore(&zlock, flags);
|
|
}
|
|
|
|
/**
|
|
* ztdeth_flush - Flush all pending transactions.
|
|
*
|
|
* This function is always called in softirq context.
|
|
*/
|
|
static int ztdeth_flush(void)
|
|
{
|
|
struct sk_buff *skb;
|
|
while ((skb = skb_dequeue(&skbs)))
|
|
dev_queue_xmit(skb);
|
|
return 0;
|
|
}
|
|
|
|
static struct packet_type ztdeth_ptype = {
|
|
.type = __constant_htons(ETH_P_DAHDI_DETH), /* Protocol */
|
|
.dev = NULL, /* Device (NULL = wildcard) */
|
|
.func = ztdeth_rcv, /* Receiver */
|
|
};
|
|
|
|
static int digit2int(char d)
|
|
{
|
|
switch(d) {
|
|
case 'F':
|
|
case 'E':
|
|
case 'D':
|
|
case 'C':
|
|
case 'B':
|
|
case 'A':
|
|
return d - 'A' + 10;
|
|
case 'f':
|
|
case 'e':
|
|
case 'd':
|
|
case 'c':
|
|
case 'b':
|
|
case 'a':
|
|
return d - 'a' + 10;
|
|
case '9':
|
|
case '8':
|
|
case '7':
|
|
case '6':
|
|
case '5':
|
|
case '4':
|
|
case '3':
|
|
case '2':
|
|
case '1':
|
|
case '0':
|
|
return d - '0';
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int hex2int(char *s)
|
|
{
|
|
int res;
|
|
int tmp;
|
|
/* Gotta be at least one digit */
|
|
if (strlen(s) < 1)
|
|
return -1;
|
|
/* Can't be more than two */
|
|
if (strlen(s) > 2)
|
|
return -1;
|
|
/* Grab the first digit */
|
|
res = digit2int(s[0]);
|
|
if (res < 0)
|
|
return -1;
|
|
tmp = res;
|
|
/* Grab the next */
|
|
if (strlen(s) > 1) {
|
|
res = digit2int(s[1]);
|
|
if (res < 0)
|
|
return -1;
|
|
tmp = tmp * 16 + res;
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
static void ztdeth_destroy(struct dahdi_dynamic *dyn)
|
|
{
|
|
struct ztdeth *z = dyn->pvt;
|
|
unsigned long flags;
|
|
struct ztdeth *prev=NULL, *cur;
|
|
|
|
spin_lock_irqsave(&zlock, flags);
|
|
cur = zdevs;
|
|
while(cur) {
|
|
if (cur == z) {
|
|
if (prev)
|
|
prev->next = cur->next;
|
|
else
|
|
zdevs = cur->next;
|
|
break;
|
|
}
|
|
prev = cur;
|
|
cur = cur->next;
|
|
}
|
|
spin_unlock_irqrestore(&zlock, flags);
|
|
|
|
if (cur == z) { /* Successfully removed */
|
|
dyn->pvt = NULL;
|
|
dev_put(z->dev);
|
|
printk(KERN_INFO "TDMoE: Removed interface for %s\n", z->span->name);
|
|
kfree(z);
|
|
}
|
|
}
|
|
|
|
static int ztdeth_create(struct dahdi_dynamic *dyn, const char *addr)
|
|
{
|
|
struct ztdeth *z;
|
|
char src[256];
|
|
char tmp[256], *tmp2, *tmp3, *tmp4 = NULL;
|
|
int res,x;
|
|
unsigned long flags;
|
|
struct dahdi_span *const span = &dyn->span;
|
|
|
|
z = kmalloc(sizeof(struct ztdeth), GFP_KERNEL);
|
|
if (z) {
|
|
/* Zero it out */
|
|
memset(z, 0, sizeof(struct ztdeth));
|
|
|
|
/* Address should be <dev>/<macaddr>[/subaddr] */
|
|
strlcpy(tmp, addr, sizeof(tmp));
|
|
tmp2 = strchr(tmp, '/');
|
|
if (tmp2) {
|
|
*tmp2 = '\0';
|
|
tmp2++;
|
|
strlcpy(z->ethdev, tmp, sizeof(z->ethdev));
|
|
} else {
|
|
printk(KERN_NOTICE "Invalid TDMoE address (no device) '%s'\n", addr);
|
|
kfree(z);
|
|
return -EINVAL;
|
|
}
|
|
if (tmp2) {
|
|
tmp4 = strchr(tmp2+1, '/');
|
|
if (tmp4) {
|
|
*tmp4 = '\0';
|
|
tmp4++;
|
|
}
|
|
/* We don't have SSCANF :( Gotta do this the hard way */
|
|
tmp3 = strchr(tmp2, ':');
|
|
for (x=0;x<6;x++) {
|
|
if (tmp2) {
|
|
if (tmp3) {
|
|
*tmp3 = '\0';
|
|
tmp3++;
|
|
}
|
|
res = hex2int(tmp2);
|
|
if (res < 0)
|
|
break;
|
|
z->addr[x] = res & 0xff;
|
|
} else
|
|
break;
|
|
if ((tmp2 = tmp3))
|
|
tmp3 = strchr(tmp2, ':');
|
|
}
|
|
if (x != 6) {
|
|
printk(KERN_NOTICE "TDMoE: Invalid MAC address in: %s\n", addr);
|
|
kfree(z);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
printk(KERN_NOTICE "TDMoE: Missing MAC address\n");
|
|
kfree(z);
|
|
return -EINVAL;
|
|
}
|
|
if (tmp4) {
|
|
int sub = 0;
|
|
int mul = 1;
|
|
|
|
/* We have a subaddr */
|
|
tmp3 = tmp4 + strlen (tmp4) - 1;
|
|
while (tmp3 >= tmp4) {
|
|
if (*tmp3 >= '0' && *tmp3 <= '9') {
|
|
sub += (*tmp3 - '0') * mul;
|
|
} else {
|
|
printk(KERN_NOTICE "TDMoE: Invalid subaddress\n");
|
|
kfree(z);
|
|
return -EINVAL;
|
|
}
|
|
mul *= 10;
|
|
tmp3--;
|
|
}
|
|
z->subaddr = htons(sub);
|
|
}
|
|
z->dev = dev_get_by_name(&init_net, z->ethdev);
|
|
if (!z->dev) {
|
|
printk(KERN_NOTICE "TDMoE: Invalid device '%s'\n", z->ethdev);
|
|
kfree(z);
|
|
return -EINVAL;
|
|
}
|
|
z->span = span;
|
|
src[0] ='\0';
|
|
for (x=0;x<5;x++)
|
|
sprintf(src + strlen(src), "%02x:", z->dev->dev_addr[x]);
|
|
sprintf(src + strlen(src), "%02x", z->dev->dev_addr[5]);
|
|
printk(KERN_INFO "TDMoE: Added new interface for %s at %s (addr=%s, src=%s, subaddr=%d)\n", span->name, z->dev->name, addr, src, ntohs(z->subaddr));
|
|
|
|
spin_lock_irqsave(&zlock, flags);
|
|
z->next = zdevs;
|
|
zdevs = z;
|
|
dyn->pvt = z;
|
|
spin_unlock_irqrestore(&zlock, flags);
|
|
}
|
|
return (z) ? 0 : -ENOMEM;
|
|
}
|
|
|
|
static struct dahdi_dynamic_driver ztd_eth = {
|
|
.owner = THIS_MODULE,
|
|
.name = "eth",
|
|
.desc = "Ethernet",
|
|
.create = ztdeth_create,
|
|
.destroy = ztdeth_destroy,
|
|
.transmit = ztdeth_transmit,
|
|
.flush = ztdeth_flush,
|
|
};
|
|
|
|
static struct notifier_block ztdeth_nblock = {
|
|
.notifier_call = ztdeth_notifier,
|
|
};
|
|
|
|
static int __init ztdeth_init(void)
|
|
{
|
|
skb_queue_head_init(&skbs);
|
|
|
|
dev_add_pack(&ztdeth_ptype);
|
|
register_netdevice_notifier(&ztdeth_nblock);
|
|
dahdi_dynamic_register_driver(&ztd_eth);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit ztdeth_exit(void)
|
|
{
|
|
dahdi_dynamic_unregister_driver(&ztd_eth);
|
|
unregister_netdevice_notifier(&ztdeth_nblock);
|
|
dev_remove_pack(&ztdeth_ptype);
|
|
|
|
skb_queue_purge(&skbs);
|
|
}
|
|
|
|
MODULE_DESCRIPTION("DAHDI Dynamic TDMoE Support");
|
|
MODULE_AUTHOR("Mark Spencer <markster@digium.com>");
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
module_init(ztdeth_init);
|
|
module_exit(ztdeth_exit);
|