summaryrefslogtreecommitdiffstats
path: root/net/ax25/ax25_ddi.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/ax25/ax25_ddi.c')
-rw-r--r--net/ax25/ax25_ddi.c1186
1 files changed, 1186 insertions, 0 deletions
diff --git a/net/ax25/ax25_ddi.c b/net/ax25/ax25_ddi.c
new file mode 100644
index 000000000..594484c77
--- /dev/null
+++ b/net/ax25/ax25_ddi.c
@@ -0,0 +1,1186 @@
+/*
+ * ax25_ddi.c: Device Driver Independent Module for NEW-AX.25
+ *
+ * Authors: Jens David (DG1KJD), Matthias Welwarsky (DG2FEF),
+ *
+ * Comment: Contains device driver interface, scheduler, channel arbitration
+ * LAPB state machine is synchronized here to avoid race conditions
+ * Written from scratch by Matthias Welwarsky in 1998.
+ *
+ * Changelog:
+ *
+ * License: This module 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.
+ */
+
+#include <linux/config.h>
+#include <linux/netdevice.h>
+#include <linux/tqueue.h>
+#include <linux/time.h>
+#include <linux/spinlock.h>
+
+#include <net/tcp.h>
+#include <net/ax25.h>
+#include <net/ax25dev.h>
+
+#include "af_ax25.h"
+#include "ax25_ddi.h"
+#include "ax25_core.h"
+#include "ax25_in.h"
+#include "ax25_subr.h"
+#include "ax25_route.h"
+#include "ax25_timer.h"
+
+struct net_device *ax25_devices[AX25_MAX_DEVICES];
+rwlock_t ax25_dev_lock = RW_LOCK_UNLOCKED;
+
+
+
+/*
+ * ------------------------------------------------------------------------
+ * declaration of private functions
+ * ------------------------------------------------------------------------
+ */
+
+static void clear_ax25devices(void);
+static void ax25_dev_timer(unsigned long);
+static void ax25_dev_tic(unsigned long);
+static void ax25_transmit_buffer(ax25_cb*, struct sk_buff*, int);
+static void ax25_send_iframe(ax25_cb*, struct sk_buff*, int);
+static void ax25_send_control(ax25_cb*, int, int, int);
+static void ax25_kick_device(struct ax25_dev*);
+static __inline__ void ax25_dev_add_ready(struct ax25_dev *, ax25_cb *);
+static __inline__ void ax25_dev_remove_active(struct ax25_dev *);
+static __inline__ void ax25_dev_remove_ready(struct ax25_dev *, ax25_cb *);
+static void ax25_dev_set_tic(struct ax25_dev *);
+static void ax25_dev_set_timer(struct ax25_dev *, unsigned int);
+static void ax25_queue_xmit(struct sk_buff *);
+static struct ax25_dev *ax25_dev_get_dev(struct net_device *);
+
+/*
+ * ------------------------------------------------------------------------
+ * Interface implementation
+ * All public functions of this module are defined here
+ * ------------------------------------------------------------------------
+ */
+
+void ax25_ddi_init(void)
+{
+ clear_ax25devices();
+}
+
+/*
+ * queue a fully assembled frame in the unproto queue of the
+ * device and mark the channel ready for transmission
+ */
+void ax25_send_unproto(struct sk_buff* skb, struct net_device* dev)
+{
+ struct ax25_dev* ax25_device = AX25_PTR(dev);
+
+ skb->dev = dev;
+ skb_queue_tail(&ax25_device->unproto_queue, skb);
+ ax25_kick_device(ax25_device);
+}
+
+void ax25_send_broadcast(struct sk_buff *skb)
+{
+ int i;
+
+ read_lock(&ax25_dev_lock);
+ for (i = 0; i < AX25_MAX_DEVICES; i++) {
+ struct net_device *dev = ax25_devices[i];
+
+ if (dev != NULL && (dev->flags & (IFF_UP|IFF_BROADCAST)) != 0) {
+ struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);
+ if (newskb != NULL)
+ ax25_send_unproto(newskb, dev);
+ else
+ printk(KERN_ERR "ax25_send_broadcast: unable to clone packet.\n");
+ }
+ }
+ read_unlock(&ax25_dev_lock);
+
+ /* caller frees original packet */
+}
+
+/*
+ * put a connection on the ready list of it's device and mark the device
+ * ready for transmission.
+ */
+void ax25_kick(ax25_cb *ax25)
+{
+ if (ax25->device != NULL) {
+ struct ax25_dev *ax25_device = AX25_PTR(ax25->device);
+
+ /*
+ * put the connection on the readylist of this channel,
+ * if it's not already there.
+ */
+ ax25_dev_add_ready(ax25_device, ax25);
+
+ /*
+ * mark the channel ready
+ */
+ ax25_kick_device(ax25_device);
+ }
+}
+
+/*
+ * return the connection list to a given device
+ */
+ax25_cb *ax25_dev_list(struct net_device *dev)
+{
+ struct ax25_dev *ax25_device;
+
+ if (dev == NULL)
+ return ax25_list;
+
+ if ((ax25_device = AX25_PTR(dev)) != NULL && ax25_device->magic == AX25_DEV_MAGIC)
+ return ax25_device->list.all;
+
+ return NULL;
+}
+
+/*
+ * insert a connection into a device queue
+ */
+void ax25_dev_insert_cb(ax25_cb *ax25)
+{
+ struct ax25_dev *ax25_device = AX25_PTR(ax25->device);
+
+ if (ax25_device->magic != AX25_DEV_MAGIC) {
+ printk(KERN_ERR "ax25_dev_insert_cb: wrong magic number.\n");
+ return;
+ }
+
+ ax25->prev = NULL;
+ ax25->next = ax25_device->list.all;
+
+ write_lock(&ax25_dev_lock);
+ if (ax25_device->list.all != NULL)
+ ax25_device->list.all->prev = ax25;
+ ax25_device->list.all = ax25;
+ write_unlock(&ax25_dev_lock);
+
+ ax25->inserted = 1;
+}
+
+/*
+ * remove a connection from a device queue
+ */
+void ax25_dev_remove_cb(ax25_cb *ax25)
+{
+ struct ax25_dev *ax25_device = AX25_PTR(ax25->device);
+ struct net_device *dev = ax25->device;
+ struct ax25_cb *axp;
+
+ if (ax25_device->magic != AX25_DEV_MAGIC) {
+ printk(KERN_ERR "ax25_dev_remove_cb: wrong magic number.\n");
+ return;
+ }
+
+ if (ax25_device->list.all == NULL) {
+ printk(KERN_ERR "ax25_dev_remove_cb: empty list.\n");
+ return;
+ }
+
+ write_lock(&ax25_dev_lock);
+ if (ax25->prev == NULL) {
+ ax25_device->list.all = ax25->next;
+ } else {
+ ax25->prev->next = ax25->next;
+ }
+
+ if (ax25->next != NULL)
+ ax25->next->prev = ax25->prev;
+
+ ax25->inserted = 0;
+
+ if (xchg(&ax25->ready.state, AX25_SCHED_IDLE) == AX25_SCHED_READY)
+ ax25_dev_remove_ready(ax25_device, ax25);
+
+ /* search for active circuits and set DAMA flag accordingly */
+ for (axp=ax25_device->list.all; axp!=NULL; axp=axp->next)
+ if ((axp->state == AX25_STATE_3) || (axp->state == AX25_STATE_4)) break;
+ if (axp == NULL) ax25_dev_set_dama(dev, 0);
+
+ write_unlock(&ax25_dev_lock);
+}
+
+/*
+ * Look for any matching address.
+ */
+int ax25_dev_match_addr(ax25_address *addr, struct net_device *dev)
+{
+ ax25_cb *s;
+
+ for (s = ax25_dev_list(dev); s != NULL; s = s->next) {
+ if (s->state == AX25_LISTEN && s->sk == NULL && ax25cmp(&s->addr.src, addr) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Find a control block that wants to accept the SABM we have just
+ * received.
+ */
+ax25_cb *ax25_dev_find_listener(ax25_address *addr, int digi, struct net_device *dev)
+{
+ ax25_cb *s;
+
+ read_lock(&ax25_dev_lock);
+ for (s = ax25_dev_list(dev); s != NULL; s = s->next) {
+ if (s->state != AX25_LISTEN)
+ continue;
+ if ((s->iamdigi && !digi) || (!s->iamdigi && digi))
+ continue;
+ if (ax25cmp(&s->addr.src, addr) == 0)
+ break;
+ }
+ read_unlock(&ax25_dev_lock);
+ return s;
+}
+
+/*
+ * Find an AX.25 socket given both ends.
+ */
+struct sock *ax25_dev_find_socket(ax25_address *my_addr, ax25_address *dest_addr, struct net_device *dev, int type)
+{
+ ax25_cb *s;
+
+ read_lock(&ax25_dev_lock);
+ for (s = ax25_dev_list(dev); s != NULL; s = s->next) {
+ if (s->sk != NULL && ax25cmp(&s->addr.src, my_addr) == 0
+ && ax25cmp(&s->addr.dest, dest_addr) == 0 && s->sk->type == type) {
+ read_unlock(&ax25_dev_lock);
+ return s->sk;
+ }
+ }
+ read_unlock(&ax25_dev_lock);
+ return NULL;
+}
+
+/*
+ * This function is called whenever a parameter is modified using
+ * ax25_dev_set_value_notify or via the proc/sysctl interface. It
+ * decides whether to notify the device driver of the event. If the
+ * decision is positive, it uses the parameter_change downcall.
+ * The driver can then react and re-set the value or pick the
+ * closest value the hardware allows (e.g. by baud rate divider etc.).
+ * The most important values for the device driver are duplex, txdelay,
+ * txtail, {tx,rx}bitrate. Slottime and p-persistence are currently
+ * only "for info" since channel arbitration is done by DDI layer now.
+ */
+void ax25_notify_dispatcher(struct net_device *dev, int id, int oldval, int newval)
+{
+ struct ax25_dev *ax_dev;
+
+ if (!dev) return; /* paranoia */
+ ax_dev = AX25_PTR(dev);
+ if (!ax_dev) return; /* paranoia */
+
+ switch (id) {
+ case AX25_VALUES_MEDIA_DUPLEX:
+ case AX25_VALUES_MEDIA_TXDELAY:
+ case AX25_VALUES_MEDIA_TXTAIL:
+ case AX25_VALUES_MEDIA_TXBITRATE:
+ case AX25_VALUES_MEDIA_RXBITRATE:
+ case AX25_VALUES_MEDIA_SLOTTIME:
+ case AX25_VALUES_MEDIA_PPERSISTENCE:
+ case AX25_VALUES_MEDIA_AUTO_ADJUST:
+ if (ax_dev->hw.parameter_change_notify) {
+ (ax_dev->hw.parameter_change_notify)(dev, id, oldval, newval);
+ }
+ break;
+ default:
+ break;
+ }
+ return;
+}
+
+/*
+ * Call this function from AX.25 driver to check if driver has
+ * to be notified of the event.
+ */
+void ax25_dev_set_value_notify(struct net_device *dev, int valueno, int newvalue)
+{
+ int oldvalue;
+
+ oldvalue = ax25_dev_get_value(dev, valueno);
+ ax25_dev_set_value(dev, valueno, newvalue);
+ if (oldvalue != newvalue)
+ ax25_notify_dispatcher(dev, valueno, oldvalue, newvalue);
+}
+
+/*
+ * This is called when an interface is brought up. These are
+ * reasonable defaults. We try not to mess with the media parameters
+ * if they appear as having been set already.
+ */
+void ax25_dev_device_up(struct net_device *dev)
+{
+ struct ax25_dev *ax25_device = AX25_PTR(dev);
+ int txbitrate;
+
+ if (!ax25_device || ax25_device->magic != AX25_DEV_MAGIC)
+ return;
+
+ ax25_device->ready_lock = RW_LOCK_UNLOCKED;
+ ax25_device->forward = NULL;
+ ax25_device->list.all = NULL;
+ ax25_device->list.ready = NULL;
+ skb_queue_head_init(&ax25_device->unproto_queue);
+ ax25_device->bytes_sent = 0;
+ ax25_device->dama_mode = 0;
+
+ ax25_dev_set_value_notify(dev, AX25_VALUES_IPDEFMODE, AX25_DEF_IPDEFMODE);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_AXDEFMODE, AX25_DEF_AXDEFMODE);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_BACKOFF, AX25_DEF_BACKOFF);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_CONMODE, AX25_DEF_CONMODE);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_WINDOW, AX25_DEF_WINDOW);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_EWINDOW, AX25_DEF_EWINDOW);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_T1, AX25_DEF_T1);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_T3, AX25_DEF_T3);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_IDLE, AX25_DEF_IDLE);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_N2, AX25_DEF_N2);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_PACLEN, AX25_DEF_PACLEN);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_PROTOCOL, AX25_DEF_PROTOCOL);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_DAMA_SLAVE_TIMEOUT, AX25_DEF_DAMA_SLAVE_TIMEOUT);
+
+ txbitrate = ax25_dev_get_value(dev, AX25_VALUES_MEDIA_TXBITRATE);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_T2,
+ txbitrate > 0 ? (3600 / AX25_TICS) * HZ / txbitrate : 0);
+ if (ax25_dev_get_value(dev, AX25_VALUES_MEDIA_PPERSISTENCE) == 0)
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_PPERSISTENCE, AX25_DEF_MEDIA_PPERSISTENCE);
+ if (ax25_dev_get_value(dev, AX25_VALUES_MEDIA_SLOTTIME) == 0)
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_SLOTTIME, AX25_DEF_MEDIA_SLOTTIME);
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_AUTO_ADJUST, AX25_DEF_MEDIA_AUTO_ADJUST);
+
+ init_timer(&ax25_device->timer);
+ ax25_dev_set_timer(ax25_device, AX25_TICS);
+ init_timer(&ax25_device->tics);
+ ax25_dev_set_tic(ax25_device);
+}
+
+/*
+ * this is called when a device is brought down. Delete the device
+ * timers and update the sysctl interface.
+ */
+void ax25_dev_device_down(struct net_device *dev)
+{
+ struct ax25_dev *ax25_device = AX25_PTR(dev);
+
+ ax25_kill_by_device(dev);
+ ax25_rt_device_down(dev);
+
+ if (!ax25_device || ax25_device->magic != AX25_DEV_MAGIC) {
+ printk(KERN_ERR "ax25_dev_device_down: not an AX.25 device.\n");
+ return;
+ }
+
+ del_timer(&ax25_device->timer);
+ del_timer(&ax25_device->tics);
+
+ /* FIXME: do I have to lock this or not? */
+ /* start_bh_atomic(); */
+ skb_queue_purge(&ax25_device->unproto_queue);
+ /* end_bh_atomic(); */
+}
+
+/*
+ * Packet forwarding control IOCTL
+ * FIXME: does anybody really need this feature?
+ */
+int ax25_fwd_ioctl(unsigned int cmd, struct ax25_fwd_struct *fwd)
+{
+ struct net_device *dev;
+ struct ax25_dev *ax25_dev;
+
+ if ((dev = ax25rtr_get_dev(&fwd->port_from)) == NULL)
+ return -EINVAL;
+
+ if ((ax25_dev = ax25_dev_get_dev(dev)) == NULL)
+ return -EINVAL;
+
+ switch (cmd) {
+ case SIOCAX25ADDFWD:
+ if ((dev = ax25rtr_get_dev(&fwd->port_to)) == NULL)
+ return -EINVAL;
+ if (ax25_dev->forward != NULL)
+ return -EINVAL;
+ ax25_dev->forward = dev;
+ break;
+
+ case SIOCAX25DELFWD:
+ if (ax25_dev->forward == NULL)
+ return -EINVAL;
+ ax25_dev->forward = NULL;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct net_device *ax25_fwd_dev(struct net_device *dev)
+{
+ struct ax25_dev *ax25_dev;
+
+ if ((ax25_dev = ax25_dev_get_dev(dev)) == NULL)
+ return dev;
+
+ if (ax25_dev->forward == NULL)
+ return dev;
+
+ return ax25_dev->forward;
+}
+
+int ax25_dev_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int i;
+ struct net_device *dev;
+ char devname[7];
+
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+
+ len += sprintf(buffer, "device hwaddr rifr tifr rrej rkby tkby duplex tx-bps rx-bps ppers slot auto txd txt \n");
+
+ read_lock(&ax25_dev_lock);
+ for (i = 0; i < AX25_MAX_DEVICES; i++) {
+ if ((dev = ax25_devices[i]) != NULL) {
+ strncpy(devname, dev->name, 6);
+ devname[6] = 0;
+ len += sprintf(buffer+len, "%-6s %-9s %-6ld %-6ld %-6ld %-9ld %-9ld %-6s %-8d %-8d %-5d %-4d %-4s %-4d %-4d\n",
+ devname, ax2asc((ax25_address *)dev->dev_addr),
+ AX25_PTR(dev)->rx_iframes, AX25_PTR(dev)->tx_iframes,
+ AX25_PTR(dev)->rx_rejects,
+ AX25_PTR(dev)->rx_bytes/1024,
+ AX25_PTR(dev)->tx_bytes/1024,
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_DUPLEX) ? "full" : "half",
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_TXBITRATE),
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_RXBITRATE),
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_PPERSISTENCE),
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_SLOTTIME),
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_AUTO_ADJUST) ? "on" : "off",
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_TXDELAY),
+ ax25_dev_get_value(dev, AX25_VALUES_MEDIA_TXTAIL));
+
+ pos = begin + len;
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length)
+ break;
+ }
+ }
+ read_unlock(&ax25_dev_lock);
+
+ *start = buffer + (offset - begin);
+ len -= offset - begin;
+
+ if (len > length) len = length;
+
+ return len;
+}
+
+/*
+ * This function is called by core/dev.c whenever a new netdevice is
+ * being registerd. We initialize its ax25_dev structure and include
+ * it in our list. We also register the sysctl tree for it and initialize
+ * its parameters.
+ */
+void register_ax25device(struct net_device *dev)
+{
+ int i;
+ struct ax25_dev *axdev = AX25_PTR(dev);
+
+ axdev->magic = AX25_DEV_MAGIC;
+ axdev->netdev = dev;
+
+ memcpy((char *) dev->broadcast, (char *) asc2ax("QST-0"), AX25_ADDR_LEN);
+
+ ax25_unregister_sysctl();
+ write_lock(&ax25_dev_lock);
+ for (i = 0; i < AX25_MAX_DEVICES; i++) {
+ if (ax25_devices[i] == NULL) {
+ ax25_devices[i] = dev;
+ break;
+ }
+ }
+
+ ax25_register_sysctl();
+ if (i == AX25_MAX_DEVICES) {
+ printk(KERN_ERR "AX.25: Too many devices, could not register.\n");
+ goto done;
+ }
+
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_DUPLEX, 0);
+ if (ax25_dev_get_value(dev, AX25_VALUES_MEDIA_TXDELAY) == 0)
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_TXDELAY, AX25_DEF_MEDIA_TXDELAY);
+ if (ax25_dev_get_value(dev, AX25_VALUES_MEDIA_TXTAIL) == 0)
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_TXTAIL, AX25_DEF_MEDIA_TXTAIL);
+ if (ax25_dev_get_value(dev, AX25_VALUES_MEDIA_TXBITRATE) == 0)
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_TXBITRATE, AX25_DEF_MEDIA_TXBITRATE);
+ if (ax25_dev_get_value(dev, AX25_VALUES_MEDIA_RXBITRATE) == 0)
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_RXBITRATE, AX25_DEF_MEDIA_RXBITRATE);
+ /*
+ * slottime, p-persistence and auto-adjust defaults are
+ * loaded upon interface start
+ */
+
+done:
+ write_unlock(&ax25_dev_lock);
+}
+
+/*
+ * This function is executed when an interface is about to be removed.
+ * It must already have been downed before. We remove it from our
+ * list and remove sysctl directory entry.
+ */
+void unregister_ax25device(struct net_device *dev)
+{
+ int i;
+
+ ax25_unregister_sysctl();
+ write_lock(&ax25_dev_lock);
+ for (i = 0; i < AX25_MAX_DEVICES; i++) {
+ if (ax25_devices[i] == dev) {
+ ax25_devices[i] = NULL;
+ break;
+ }
+ }
+ write_unlock(&ax25_dev_lock);
+ ax25_register_sysctl();
+}
+
+/*
+ * Activate/Deactivate DAMA on a given interface.
+ * We automagically configure the media for full duplex if neccessary.
+ */
+void ax25_dev_set_dama(struct net_device *dev, int dama)
+{
+ if (dama && (ax25_dev_get_value(dev, AX25_VALUES_PROTOCOL) == 1)) {
+ if (!(AX25_PTR(dev)->dama_mode & DAMA_SLAVE)) {
+ AX25_PTR(dev)->dama_mode |= DAMA_SLAVE;
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_DUPLEX, 1);
+ }
+ } else {
+ if (AX25_PTR(dev)->dama_mode & DAMA_SLAVE) {
+ AX25_PTR(dev)->dama_mode &= ~DAMA_SLAVE;
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_DUPLEX, 0);
+ }
+ }
+ return;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ * End of public area, all private functions of this module are defined
+ * here.
+ * ------------------------------------------------------------------------
+ */
+
+static void clear_ax25devices(void)
+{
+ int i;
+
+ write_lock(&ax25_dev_lock);
+ for (i = 0; i < AX25_MAX_DEVICES; i++)
+ ax25_devices[i] = NULL;
+ write_unlock(&ax25_dev_lock);
+}
+
+/*
+ * simple pseudo-random number generator, stolen from hdlcdrv.c :)
+ */
+static inline unsigned short random_num(void)
+{
+ static unsigned short random_seed;
+
+ random_seed = 28629 * random_seed + 157;
+ return random_seed & 0xFF;
+}
+
+/*
+ * add a connection to the channels readylist
+ */
+static inline void ax25_dev_add_ready(struct ax25_dev *ax25_device, ax25_cb *ax25)
+{
+ write_lock(&ax25_device->ready_lock);
+ if (ax25->ready.state != AX25_SCHED_READY) {
+ ax25->ready.state = AX25_SCHED_READY;
+ if (ax25_device->list.ready == NULL) {
+ ax25->ready.prev = ax25;
+ ax25->ready.next = ax25;
+ ax25_device->list.ready = ax25;
+ } else {
+ ax25->ready.next = ax25_device->list.ready;
+ ax25->ready.prev = ax25_device->list.ready->ready.prev;
+ ax25_device->list.ready->ready.prev->ready.next = ax25;
+ ax25_device->list.ready->ready.prev = ax25;
+ }
+ }
+ write_unlock(&ax25_device->ready_lock);
+}
+
+/*
+ * remove the active connection from the channels readylist
+ * NB: caller must do write_lock() on ax25_device->ready_lock!
+ */
+static inline void ax25_dev_remove_active(struct ax25_dev *ax25_device)
+{
+ ax25_cb *active = ax25_device->list.ready;
+ if (active->ready.next == active) {
+ ax25_device->list.ready = NULL;
+ } else {
+ ax25_device->list.ready = active->ready.next;
+ active->ready.next->ready.prev = active->ready.prev;
+ active->ready.prev->ready.next = active->ready.next;
+ }
+ active->ready.state = AX25_SCHED_IDLE;
+}
+
+/*
+ * remove a connection from the channels readylist
+ */
+static inline void ax25_dev_remove_ready(struct ax25_dev *ax25_device, ax25_cb *ax25)
+{
+ write_lock(&ax25_device->ready_lock);
+
+ if (ax25 == ax25_device->list.ready) {
+ ax25_dev_remove_active(ax25_device);
+ } else {
+ ax25->ready.next->ready.prev = ax25->ready.prev;
+ ax25->ready.prev->ready.next = ax25->ready.next;
+ ax25->ready.state = AX25_SCHED_IDLE;
+ }
+
+ write_unlock(&ax25_device->ready_lock);
+}
+
+/*
+ * Timer for a per device 100ms timing tic. AX.25 Timers of all
+ * connections on this device are driven by this timer.
+ */
+static void ax25_dev_set_tic(struct ax25_dev *this)
+{
+ this->tics.data = (unsigned long)this;
+ this->tics.function = &ax25_dev_tic;
+ this->tics.expires = jiffies + AX25_TICS;
+
+ add_timer(&this->tics);
+}
+
+static void ax25_dev_tic(unsigned long param)
+{
+ ax25_cb *active;
+ struct ax25_dev *this = (struct ax25_dev *) param;
+
+ if (!this->needs_transmit && ((!this->hw.ptt) || (!this->hw.ptt(this->netdev)))) {
+ for (active = this->list.all; active; active = active->next) {
+ /*
+ * only run the timer on idle connections.
+ */
+ if (!active->ready.state)
+ ax25_timer(active);
+ }
+ }
+ ax25_dev_set_tic(this);
+}
+
+/*
+ * Timer for channel access arbitration. Fires every 100ms if the channel
+ * is idle (i.e. no connections need to transmit), and in intervals of
+ * half of a frame length if trying to transmit
+ */
+static void ax25_dev_set_timer(struct ax25_dev *this, unsigned int tics)
+{
+ this->timer.data = (unsigned long)this;
+ this->timer.function = &ax25_dev_timer;
+ this->timer.expires = jiffies + tics;
+
+ add_timer(&this->timer);
+}
+
+static void ax25_dev_timer(unsigned long param)
+{
+ struct ax25_dev *this = (struct ax25_dev *) param;
+ struct net_device *dev = this->netdev;
+ ax25_cb *active;
+ struct sk_buff *skb;
+ unsigned int bytes_sent = 0;
+ unsigned int max_bytes;
+ int ppers = ax25_dev_get_value(dev, AX25_VALUES_MEDIA_PPERSISTENCE);
+ int br = ax25_dev_get_value(dev, AX25_VALUES_MEDIA_TXBITRATE);
+ int duplex = ax25_dev_get_value(dev, AX25_VALUES_MEDIA_DUPLEX);
+ int bit_per_jiffie;
+ int jiffies_per_slot;
+
+ if (br == 0) {
+ printk(KERN_ERR "ax25_dev_timer(%s): TX-Bitrate unset!!!\n", dev->name);
+ }
+ bit_per_jiffie = br / HZ;
+ jiffies_per_slot = 1200 * HZ / br + 1;
+
+ if (this->dama_mode & DAMA_SLAVE) {
+ /* >>>>> DAMA slave <<<<<
+ *
+ * we only transmit when we are asked to do so or when
+ * T3 ran out, which should only occur if the master forgot
+ * our circuits (i.e. had a reset or is broken otherwise).
+ */
+ if (this->dama_polled) {
+ /* we have been polled, it's ok to transmit */
+ this->dama_polled = 0;
+ goto arbitration_ok;
+ } else {
+ /*
+ * we are not allowed to transmit. Maybe next time.
+ */
+ ax25_dev_set_timer(this, jiffies_per_slot);
+ return;
+ }
+ } else if (this->dama_mode & DAMA_MASTER) {
+ /* >>>>> DAMA master <<<<<
+ *
+ * insert code here
+ * this could have been your ad! :-)
+ */
+ } else {
+ /* >>>>> CSMA <<<<<
+ *
+ * this implements a rather innovative channel access method.
+ * the basic idea is to run the usual slottime/persistence
+ * scheme, but with two significant changes:
+ * 1. slottime is derived from the bitrate of the channel
+ * 2. persistence is variable, depending on the dcd pattern
+ * of the channel.
+ *
+ * "Sample the dcd in intervals of half of a frames length and
+ * - increment persistence value if dcd is inactive,
+ * - decrement persistence value if dcd is active."
+ *
+ * simulations show that this scheme gives good collision
+ * avoidance and throughput without knowledge about the
+ * dcd propagation delay and station count. It will probably
+ * perform *much* too aggressive in a hidden station environment.
+ *
+ * Note: The check for hw.fast skips the channel arbitration
+ * stuff. Set this for KISS and ethernet devices.
+ */
+ if (!this->hw.fast && !duplex && !this->hw.ptt(this->netdev)) {
+ /* decide whether this is a "good" slot or not */
+ if (random_num() < ppers) {
+ /* ok, a good one, check the dcd now */
+ if (this->hw.dcd(this->netdev)) {
+ this->dcd_memory = 1;
+ /*
+ * too bad, dcd is up. we're too aggressive,
+ * but we must wait for a falling edge of the dcd
+ * before we can decrement persistence
+ */
+ if (this->dcd_dropped && ppers > 1)
+ if (ax25_dev_get_value(dev, AX25_VALUES_MEDIA_AUTO_ADJUST))
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_PPERSISTENCE, ppers);
+ if (this->needs_transmit)
+ ax25_dev_set_timer(this, jiffies_per_slot);
+ return;
+ }
+ /* update dcd memory */
+ this->dcd_memory = 0;
+ this->dcd_dropped = 0;
+ goto arbitration_ok;
+ } else {
+ /* a bad slot, check the dcd */
+ if (!this->hw.dcd(this->netdev)) {
+ /* um. dcd is down, we should have tx'd here. */
+ if (ppers < 128)
+ if (ax25_dev_get_value(dev, AX25_VALUES_MEDIA_AUTO_ADJUST))
+ ax25_dev_set_value_notify(dev, AX25_VALUES_MEDIA_PPERSISTENCE, ppers+1);
+ /* was it up the slot before? */
+ if (this->dcd_memory) {
+ this->dcd_dropped = 1;
+ }
+ this->dcd_memory = 0;
+ } else {
+ this->dcd_memory = 1;
+ }
+ if (this->needs_transmit)
+ ax25_dev_set_timer(this, jiffies_per_slot);
+ return;
+ }
+ }
+ }
+
+arbitration_ok:
+ /*
+ * OK, we may transmit, arbitration successful.
+ */
+ if (this->hw.rts) this->hw.rts(this->netdev);
+
+ /*
+ * compute the amount of bytes to send during 100ms (AX25_TICS)
+ */
+ max_bytes = (bit_per_jiffie * AX25_TICS);
+
+ /*
+ * UI Frames
+ */
+ while ((bytes_sent < max_bytes || this->hw.fast)
+ && ((skb = skb_dequeue(&this->unproto_queue)) != NULL)) {
+ ax25_queue_xmit(skb);
+ bytes_sent += skb->len;
+ }
+
+ /*
+ * traverse our list of connections. we're messing with a
+ * private list here and we will not sleep and schedule, so no
+ * further protection should be necessary.
+ *
+ * we implement a simple round robin style packet scheduler here.
+ * each device has a list of cnnections ready to transmit packets,
+ * and we loop through the connections until
+ * a. the list becomes empty
+ * b. the transmit time limit is reached.
+ * if a connection has no more packets left or exceeds its window
+ * of outbound packets, it is removed from the list.
+ */
+ while ((active = this->list.ready) != NULL && ((bytes_sent < max_bytes) || this->hw.fast)) {
+ unsigned short start;
+ unsigned short end;
+ struct sk_buff *skbn;
+ ax25_cb *peer;
+ int in_retransmit = 0;
+
+ skbn = skb_peek(&active->ack_queue);
+
+ /* transmit supervisory stuff first */
+ if (active->tx_rsp) {
+ int poll_bit = active->tx_rsp & 0x100;
+ int frametype = active->tx_rsp & 0x0ff;
+ active->tx_rsp = 0;
+ ax25_send_control(active, frametype, poll_bit, AX25_RESPONSE);
+
+
+ /*
+ * supervisory stuff is all done, clear state-change flag
+ */
+ ax25_clr_cond(active, AX25_COND_STATE_CHANGE);
+
+ if ((frametype & AX25_U) == AX25_S) { /* S frames carry NR */
+ active->ack_timer = 0;
+ ax25_clr_cond(active, AX25_COND_ACK_PENDING);
+ }
+ }
+
+ if (active->tx_cmd) {
+ int poll_bit = active->tx_cmd & 0x100;
+ int frametype = active->tx_cmd & 0x0ff;
+
+ active->tx_cmd = 0;
+
+ /*
+ * a problem exists due to a race condition between linux'
+ * packet-scheduler and the timer routine: a write timeout might
+ * happen before the packet actually reaches the device and is copied
+ * for transmission. our transmit routine will then grab the first
+ * packet off the ack queue, put a header in front of the data and
+ * queue it for transmission. now we have the obscure situation that
+ * we have two packets in our transmit queue that share a single data
+ * segment. this isn't bad by itself, but since the first
+ * retransmitted frame will have the poll bit set and eventually will
+ * carry an updated N(r), we modify the header of a yet unsent packet,
+ * resulting in a protocol violation.
+ *
+ * we do the obvious thing to prevent this here: if the packet we
+ * got from the ack queue is cloned, we make a private copy of the
+ * data.
+ */
+ if (poll_bit && skbn
+ && frametype == AX25_RR
+ && !(active->condition & (AX25_COND_PEER_RX_BUSY|AX25_COND_STATE_CHANGE))
+ && active->n2count < 4)
+ {
+ if (skb_cloned(skbn)) {
+ skb = skb_copy(skbn, GFP_ATOMIC);
+ } else
+ skb = skb_clone(skbn, GFP_ATOMIC);
+ if (skb) {
+ active->vs = active->va;
+ ax25_send_iframe(active, skb, AX25_POLLON);
+ active->vs = active->vs_max;
+ }
+ } else {
+ ax25_send_control(active, frametype, poll_bit, AX25_COMMAND);
+ }
+ /*
+ * supervisory stuff is all done, clear state-change flag
+ */
+ ax25_clr_cond(active, AX25_COND_STATE_CHANGE);
+ if ((frametype & AX25_U) == AX25_S) { /* S frames carry NR */
+ active->ack_timer = 0;
+ ax25_clr_cond(active, AX25_COND_ACK_PENDING);
+ }
+ }
+
+ /*
+ * if the write queue and ack queue are both empty,
+ * or connection is not in info transfer state
+ * or the peer station is busy
+ * or the window is closed
+ * or the write queue is empty and we may not retransmit yet
+ * then remove connection from the devices' readylist;
+ *
+ * NOTE: ax25_dev_remove_active implicitly advances the
+ * round robin pointer to schedule the next connection
+ * on the readylist.
+ */
+ skb = skb_peek(&active->write_queue);
+ if ((skb == NULL && skbn == NULL)
+ || active->state != AX25_STATE_3
+ || (active->condition & AX25_COND_PEER_RX_BUSY) != 0
+ || (start = active->vs) == (end = (active->va + active->window) & active->seqmask)
+ || (skb == NULL && start != active->va))
+ {
+ if (active->condition & AX25_COND_START_T1) {
+ ax25_clr_cond(active, AX25_COND_START_T1);
+ write_lock(&active->timer_lock);
+ active->wrt_timer = active->t1 = ax25_calculate_t1(active);
+ write_unlock(&active->timer_lock);
+ }
+ write_lock(&this->ready_lock); /* paranoia */
+ ax25_dev_remove_active(this);
+ write_unlock(&this->ready_lock);
+ continue;
+ }
+
+ /*
+ * handle RTS/CTS handshaking. drivers can request TX-Delay
+ * by returning 0 in the cts method. Note, that the driver still
+ * has to handle handshaking itself, but it can prevent to be
+ * flooded with frames while it's not ready to send.
+ */
+ if (this->needs_transmit < AX25_TX_STATE_CTS) {
+ if (this->hw.cts == NULL || this->hw.cts(this->netdev))
+ this->needs_transmit = AX25_TX_STATE_CTS;
+ else if (this->needs_transmit == AX25_TX_STATE_RTS)
+ this->needs_transmit = AX25_TX_STATE_WAIT_CTS;
+ else
+ break;
+ }
+
+ if (skbn != NULL && start == active->va) {
+ skb = skbn;
+ in_retransmit = 1;
+ }
+
+ /*
+ * clone the buffer, put the original into the
+ * ack_queue and transmit the copy. That way the
+ * socket will be uncharged from the memory when
+ * the packet is acked, not when it's transmitted.
+ */
+ if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL)
+ break;
+
+ /* advance pointer to current connection */
+ this->list.ready = active->ready.next;
+
+ ax25_send_iframe(active, skbn, AX25_POLLOFF);
+ if (!(DAMA_STATE(active) & DAMA_SLAVE)) {
+ ax25_start_t1(active);
+ }
+
+ /* implicit ACK */
+ ax25_clr_cond(active, AX25_COND_ACK_PENDING);
+
+ if (!in_retransmit) {
+ active->vs_max = active->vs = (active->vs + 1) & active->seqmask;
+ skb_dequeue(&active->write_queue);
+ skb_queue_tail(&active->ack_queue, skb);
+
+ if (active->vs_rtt == -1) {
+ active->rtt_timestamp = jiffies;
+ active->vs_rtt = active->vs;
+ }
+
+ this->tx_iframes++;
+ this->tx_bytes += skbn->len;
+ } else {
+ active->vs = active->vs_max;
+ if (active->condition & AX25_COND_START_T1) {
+ ax25_clr_cond(active, AX25_COND_START_T1);
+ write_lock(&active->timer_lock);
+ active->wrt_timer = active->t1 = ax25_calculate_t1(active);
+ write_unlock(&active->timer_lock);
+ }
+ ax25_dev_remove_ready(this, active);
+ }
+
+ bytes_sent += skbn->len;
+
+ peer = active->peer;
+ if (peer && (peer->condition & AX25_COND_OWN_RX_BUSY)
+ && skb_queue_len(&active->write_queue) < 5)
+ {
+ ax25_clr_cond(peer, AX25_COND_OWN_RX_BUSY);
+ ax25_set_cond(peer, AX25_COND_STATE_CHANGE);
+ peer->state = AX25_STATE_4;
+ ax25_transmit_enquiry(peer);
+ }
+ }
+
+ this->bytes_sent += bytes_sent;
+
+ if (this->list.ready == NULL) {
+ this->bytes_sent = 0;
+ this->needs_transmit = AX25_TX_STATE_IDLE;
+ } else {
+ if (this->bytes_sent > this->max_bytes) {
+ this->bytes_sent = 0;
+ ax25_dev_set_timer(this, HZ/2);
+ } else
+ ax25_dev_set_timer(this, AX25_TICS);
+ }
+}
+
+/*
+ * send a control frame
+ */
+static void ax25_send_control(ax25_cb *ax25, int frametype, int poll_bit, int type)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ struct net_device *dev;
+
+ if ((dev = ax25->device) == NULL)
+ return; /* Route died */
+
+ if ((skb = alloc_skb(AX25_BPQ_HEADER_LEN + ax25_sizeof_addr(&ax25->addr) + 2, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, AX25_BPQ_HEADER_LEN + ax25_sizeof_addr(&ax25->addr));
+
+ /* Assume a response - address structure for DTE */
+ if (ax25->seqmask == AX25_SEQMASK) {
+ dptr = skb_put(skb, 1);
+ *dptr = frametype;
+ *dptr |= (poll_bit) ? AX25_PF : 0;
+ if ((frametype & AX25_U) == AX25_S) /* S frames carry NR */
+ *dptr |= (ax25->vr << 5);
+ } else {
+ if ((frametype & AX25_U) == AX25_U) {
+ dptr = skb_put(skb, 1);
+ *dptr = frametype;
+ *dptr |= (poll_bit) ? AX25_PF : 0;
+ } else {
+ dptr = skb_put(skb, 2);
+ dptr[0] = frametype;
+ dptr[1] = (ax25->vr << 1);
+ dptr[1] |= (poll_bit) ? AX25_EPF : 0;
+ }
+ }
+
+ skb->nh.raw = skb->data;
+ ax25_transmit_buffer(ax25, skb, type);
+ ax25->vl = ax25->vr; /* vl: last acked frame */
+}
+
+static void ax25_kick_device(struct ax25_dev* ax25_device)
+{
+ write_lock(&ax25_dev_lock);
+ if (!ax25_device->needs_transmit) {
+ ax25_device->needs_transmit = AX25_TX_STATE_RTS;
+ ax25_device->task_queue.routine = (void *) ax25_dev_timer;
+ ax25_device->task_queue.data = (void *)ax25_device;
+ ax25_device->task_queue.sync = 0;
+ queue_task(&ax25_device->task_queue, &tq_immediate);
+ mark_bh(IMMEDIATE_BH);
+ }
+ write_unlock(&ax25_dev_lock);
+}
+
+/*
+ * This procedure is passed a buffer descriptor for an iframe. It builds
+ * the rest of the control part of the frame and then writes it out.
+ *
+ */
+static void ax25_send_iframe(ax25_cb *ax25, struct sk_buff *skb, int poll_bit)
+{
+ unsigned char *frame;
+
+ skb->nh.raw = skb->data;
+
+ if (ax25->seqmask == AX25_SEQMASK) {
+ frame = skb_push(skb, 1);
+
+ *frame = AX25_I;
+ *frame |= (poll_bit) ? AX25_PF : 0;
+ *frame |= (ax25->vr << 5);
+ *frame |= (ax25->vs << 1);
+ } else {
+ frame = skb_push(skb, 2);
+
+ frame[0] = AX25_I;
+ frame[0] |= (ax25->vs << 1);
+ frame[1] = (poll_bit) ? AX25_EPF : 0;
+ frame[1] |= (ax25->vr << 1);
+ }
+
+ ax25->idletimer = ax25->idle;
+ ax25_transmit_buffer(ax25, skb, AX25_COMMAND);
+ ax25->vl = ax25->vr; /* vl: last acked frame */
+}
+
+static void ax25_transmit_buffer(ax25_cb *ax25, struct sk_buff *skb, int type)
+{
+ unsigned char *ptr;
+
+ if (ax25->device == NULL)
+ return;
+
+ if (skb_headroom(skb) < ax25_sizeof_addr(&ax25->addr)) {
+ printk(KERN_WARNING "ax25_transmit_buffer: not enough room for digi-peaters\n");
+ kfree_skb(skb);
+ return;
+ }
+
+ ptr = skb_push(skb, ax25_sizeof_addr(&ax25->addr));
+ ax25_build_addr(ptr, &ax25->addr, type, ax25->seqmask);
+ skb->dev = ax25->device;
+ ax25_queue_xmit(skb);
+}
+
+/* ---------------------------------------------------------------------*/
+
+/* A small shim to dev_queue_xmit to do any packet forwarding in operation. */
+static void ax25_queue_xmit(struct sk_buff *skb)
+{
+ skb->protocol = htons(ETH_P_AX25);
+ skb->dev = ax25_fwd_dev(skb->dev);
+
+ dev_queue_xmit(skb);
+}
+
+/* ---------------------------------------------------------------------*/
+
+static struct ax25_dev *ax25_dev_get_dev(struct net_device *dev)
+{
+ struct ax25_dev *ax25_device = AX25_PTR(dev);
+
+ if (ax25_device == NULL)
+ return NULL;
+
+ if (ax25_device->magic == AX25_DEV_MAGIC)
+ return ax25_device;
+
+ return NULL;
+}