diff options
Diffstat (limited to 'net/ax25/ax25_ddi.c')
-rw-r--r-- | net/ax25/ax25_ddi.c | 1186 |
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; +} |