Commit 635d0b51 authored by Stephen Hemminger's avatar Stephen Hemminger

[NET]: update STRIP driver.

- set owner field on tty ldisc
- allocate network device objects with alloc_netdev
- use list_head macros and put locking around list of devices
- convert to seq_file for /proc
- use change_mtu hook rather than guessing at mtu changes
parent 329c2084
......@@ -103,6 +103,7 @@ static const char StripVersion[] = "1.3A-STUART.CHESHIRE";
#include <linux/if_arp.h>
#include <linux/if_strip.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/serial.h>
#include <linux/serialP.h>
#include <net/arp.h>
......@@ -259,8 +260,8 @@ struct strip {
* Internal variables.
*/
struct strip *next; /* The next struct in the list */
struct strip **referrer; /* The pointer that points to us */
struct list_head list; /* Linked list of devices */
int discard; /* Set if serial error */
int working; /* Is radio working correctly? */
int firmware_level; /* Message structuring level */
......@@ -434,8 +435,8 @@ static const long LongTime = 0x7FFFFFFF;
/************************************************************************/
/* Global variables */
static struct strip *struct_strip_list;
static spinlock_t strip_lock;
static LIST_HEAD(strip_list);
static spinlock_t strip_lock = SPIN_LOCK_UNLOCKED;
/************************************************************************/
/* Macros */
......@@ -844,11 +845,11 @@ static __u8 *radio_address_to_string(const MetricomAddress * addr,
* big enough to receive a large radio neighbour list (currently 4K).
*/
static int allocate_buffers(struct strip *strip_info)
static int allocate_buffers(struct strip *strip_info, int mtu)
{
struct net_device *dev = strip_info->dev;
int sx_size = MAX(STRIP_ENCAP_SIZE(MAX_RECV_MTU), 4096);
int tx_size = STRIP_ENCAP_SIZE(dev->mtu) + MaxCommandStringLength;
int tx_size = STRIP_ENCAP_SIZE(mtu) + MaxCommandStringLength;
__u8 *r = kmalloc(MAX_RECV_MTU, GFP_ATOMIC);
__u8 *s = kmalloc(sx_size, GFP_ATOMIC);
__u8 *t = kmalloc(tx_size, GFP_ATOMIC);
......@@ -858,7 +859,7 @@ static int allocate_buffers(struct strip *strip_info)
strip_info->tx_buff = t;
strip_info->sx_size = sx_size;
strip_info->tx_size = tx_size;
strip_info->mtu = dev->mtu;
strip_info->mtu = dev->mtu = mtu;
return (1);
}
if (r)
......@@ -871,34 +872,31 @@ static int allocate_buffers(struct strip *strip_info)
}
/*
* MTU has been changed by the IP layer. Unfortunately we are not told
* about this, but we spot it ourselves and fix things up. We could be in
* MTU has been changed by the IP layer.
* We could be in
* an upcall from the tty driver, or in an ip packet queue.
*
* Caller must hold the strip_lock
*/
static void strip_changedmtu(struct strip *strip_info)
static int strip_change_mtu(struct net_device *dev, int new_mtu)
{
struct strip *strip_info = dev->priv;
int old_mtu = strip_info->mtu;
struct net_device *dev = strip_info->dev;
unsigned char *orbuff = strip_info->rx_buff;
unsigned char *osbuff = strip_info->sx_buff;
unsigned char *otbuff = strip_info->tx_buff;
if (dev->mtu > MAX_SEND_MTU) {
if (new_mtu > MAX_SEND_MTU) {
printk(KERN_ERR
"%s: MTU exceeds maximum allowable (%d), MTU change cancelled.\n",
strip_info->dev->name, MAX_SEND_MTU);
dev->mtu = old_mtu;
return;
return -EINVAL;
}
if (!allocate_buffers(strip_info)) {
spin_lock_bh(&strip_lock);
if (!allocate_buffers(strip_info, new_mtu)) {
printk(KERN_ERR "%s: unable to grow strip buffers, MTU change cancelled.\n",
strip_info->dev->name);
dev->mtu = old_mtu;
return;
spin_unlock_bh(&strip_lock);
return -ENOMEM;
}
if (strip_info->sx_count) {
......@@ -921,6 +919,7 @@ static void strip_changedmtu(struct strip *strip_info)
}
}
strip_info->tx_head = strip_info->tx_buff;
spin_unlock_bh(&strip_lock);
printk(KERN_NOTICE "%s: strip MTU changed fom %d to %d.\n",
strip_info->dev->name, old_mtu, strip_info->mtu);
......@@ -931,6 +930,8 @@ static void strip_changedmtu(struct strip *strip_info)
kfree(osbuff);
if (otbuff)
kfree(otbuff);
return 0;
}
static void strip_unlock(struct strip *strip_info)
......@@ -944,74 +945,6 @@ static void strip_unlock(struct strip *strip_info)
}
/************************************************************************/
/* Callback routines for exporting information through /proc */
/*
* This function updates the total amount of data printed so far. It then
* determines if the amount of data printed into a buffer has reached the
* offset requested. If it hasn't, then the buffer is shifted over so that
* the next bit of data can be printed over the old bit. If the total
* amount printed so far exceeds the total amount requested, then this
* function returns 1, otherwise 0.
*/
static int
shift_buffer(char *buffer, int requested_offset, int requested_len,
int *total, int *slop, char **buf)
{
int printed;
/* printk(KERN_DEBUG "shift: buffer: %d o: %d l: %d t: %d buf: %d\n",
(int) buffer, requested_offset, requested_len, *total,
(int) *buf); */
printed = *buf - buffer;
if (*total + printed <= requested_offset) {
*total += printed;
*buf = buffer;
} else {
if (*total < requested_offset) {
*slop = requested_offset - *total;
}
*total = requested_offset + printed - *slop;
}
if (*total > requested_offset + requested_len) {
return 1;
} else {
return 0;
}
}
/*
* This function calculates the actual start of the requested data
* in the buffer. It also calculates actual length of data returned,
* which could be less that the amount of data requested.
*/
static int
calc_start_len(char *buffer, char **start, int requested_offset,
int requested_len, int total, char *buf)
{
int return_len, buffer_len;
buffer_len = buf - buffer;
if (buffer_len >= 4095) {
printk(KERN_ERR "STRIP: exceeded /proc buffer size\n");
}
/*
* There may be bytes before and after the
* chunk that was actually requested.
*/
return_len = total - requested_offset;
if (return_len < 0) {
return_len = 0;
}
*start = buf - return_len;
if (return_len > requested_len) {
return_len = requested_len;
}
/* printk(KERN_DEBUG "return_len: %d\n", return_len); */
return return_len;
}
/*
* If the time is in the near future, time_delta prints the number of
......@@ -1032,44 +965,89 @@ static char *time_delta(char buffer[], long time)
return (buffer);
}
static int sprintf_neighbours(char *buffer, MetricomNodeTable * table,
char *title)
#define STRIP_PROC_HEADER ((void *)1)
/* get Nth element of the linked list */
static struct strip *strip_get_idx(loff_t pos)
{
struct list_head *l;
int i = 0;
list_for_each_rcu(l, &strip_list) {
if (pos == i)
return list_entry(l, struct strip, list);
++i;
}
return NULL;
}
static void *strip_seq_start(struct seq_file *seq, loff_t *pos)
{
rcu_read_lock();
return *pos ? strip_get_idx(*pos - 1) : STRIP_PROC_HEADER;
}
static void *strip_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
struct list_head *l;
struct strip *s;
++*pos;
if (v == STRIP_PROC_HEADER)
return strip_get_idx(1);
s = v;
l = &s->list;
list_for_each_continue_rcu(l, &strip_list) {
return list_entry(l, struct strip, list);
}
return NULL;
}
static void strip_seq_stop(struct seq_file *seq, void *v)
{
rcu_read_unlock();
}
static void strip_seq_neighbours(struct seq_file *seq,
const MetricomNodeTable * table,
const char *title)
{
/* We wrap this in a do/while loop, so if the table changes */
/* while we're reading it, we just go around and try again. */
struct timeval t;
char *ptr;
unsigned long flags;
do {
int i;
t = table->timestamp;
ptr = buffer;
if (table->num_nodes)
ptr += sprintf(ptr, "\n %s\n", title);
seq_printf(seq, "\n %s\n", title);
for (i = 0; i < table->num_nodes; i++) {
MetricomNode node;
spin_lock_irqsave(&strip_lock, flags);
spin_lock_bh(&strip_lock);
node = table->node[i];
spin_unlock_irqrestore(&strip_lock, flags);
ptr += sprintf(ptr, " %s\n", node.c);
spin_unlock_bh(&strip_lock);
seq_printf(seq, " %s\n", node.c);
}
} while (table->timestamp.tv_sec != t.tv_sec
|| table->timestamp.tv_usec != t.tv_usec);
return ptr - buffer;
}
/*
* This function prints radio status information into the specified buffer.
* I think the buffer size is 4K, so this routine should never print more
* than 4K of data into it. With the maximum of 32 portables and 32 poletops
* This function prints radio status information via the seq_file
* interface. The interface takes care of buffer size and over
* run issues.
*
* The buffer in seq_file is PAGESIZE (4K)
* so this routine should never print more or it will get truncated.
* With the maximum of 32 portables and 32 poletops
* reported, the routine outputs 3107 bytes into the buffer.
*/
static int sprintf_status_info(char *buffer, struct strip *strip_info)
static void strip_seq_status_info(struct seq_file *seq,
const struct strip *strip_info)
{
char temp[32];
char *p = buffer;
MetricomAddressString addr_string;
/* First, we must copy all of our data to a safe place, */
......@@ -1103,98 +1081,104 @@ static int sprintf_status_info(char *buffer, struct strip *strip_info)
unsigned long tx_ebytes = strip_info->tx_ebytes;
#endif
p += sprintf(p, "\nInterface name\t\t%s\n", if_name);
p += sprintf(p, " Radio working:\t\t%s\n", working ? "Yes" : "No");
seq_printf(seq, "\nInterface name\t\t%s\n", if_name);
seq_printf(seq, " Radio working:\t\t%s\n", working ? "Yes" : "No");
radio_address_to_string(&true_dev_addr, &addr_string);
p += sprintf(p, " Radio address:\t\t%s\n", addr_string.c);
seq_printf(seq, " Radio address:\t\t%s\n", addr_string.c);
if (manual_dev_addr) {
radio_address_to_string(&dev_dev_addr, &addr_string);
p += sprintf(p, " Device address:\t%s\n", addr_string.c);
seq_printf(seq, " Device address:\t%s\n", addr_string.c);
}
p += sprintf(p, " Firmware version:\t%s", !working ? "Unknown" :
seq_printf(seq, " Firmware version:\t%s", !working ? "Unknown" :
!firmware_level ? "Should be upgraded" :
firmware_version.c);
if (firmware_level >= ChecksummedMessages)
p += sprintf(p, " (Checksums Enabled)");
p += sprintf(p, "\n");
p += sprintf(p, " Serial number:\t\t%s\n", serial_number.c);
p += sprintf(p, " Battery voltage:\t%s\n", battery_voltage.c);
p += sprintf(p, " Transmit queue (bytes):%d\n", tx_left);
p += sprintf(p, " Receive packet rate: %ld packets per second\n",
seq_printf(seq, " (Checksums Enabled)");
seq_printf(seq, "\n");
seq_printf(seq, " Serial number:\t\t%s\n", serial_number.c);
seq_printf(seq, " Battery voltage:\t%s\n", battery_voltage.c);
seq_printf(seq, " Transmit queue (bytes):%d\n", tx_left);
seq_printf(seq, " Receive packet rate: %ld packets per second\n",
rx_average_pps / 8);
p += sprintf(p, " Transmit packet rate: %ld packets per second\n",
seq_printf(seq, " Transmit packet rate: %ld packets per second\n",
tx_average_pps / 8);
p += sprintf(p, " Sent packet rate: %ld packets per second\n",
seq_printf(seq, " Sent packet rate: %ld packets per second\n",
sx_average_pps / 8);
p += sprintf(p, " Next watchdog probe:\t%s\n",
seq_printf(seq, " Next watchdog probe:\t%s\n",
time_delta(temp, watchdog_doprobe));
p += sprintf(p, " Next watchdog reset:\t%s\n",
seq_printf(seq, " Next watchdog reset:\t%s\n",
time_delta(temp, watchdog_doreset));
p += sprintf(p, " Next gratuitous ARP:\t");
seq_printf(seq, " Next gratuitous ARP:\t");
if (!memcmp
(strip_info->dev->dev_addr, zero_address.c,
sizeof(zero_address)))
p += sprintf(p, "Disabled\n");
seq_printf(seq, "Disabled\n");
else {
p += sprintf(p, "%s\n", time_delta(temp, gratuitous_arp));
p += sprintf(p, " Next ARP interval:\t%ld seconds\n",
seq_printf(seq, "%s\n", time_delta(temp, gratuitous_arp));
seq_printf(seq, " Next ARP interval:\t%ld seconds\n",
JIFFIE_TO_SEC(arp_interval));
}
if (working) {
#ifdef EXT_COUNTERS
p += sprintf(p, "\n");
p += sprintf(p,
seq_printf(seq, "\n");
seq_printf(seq,
" Total bytes: \trx:\t%lu\ttx:\t%lu\n",
rx_bytes, tx_bytes);
p += sprintf(p,
seq_printf(seq,
" thru radio: \trx:\t%lu\ttx:\t%lu\n",
rx_rbytes, tx_rbytes);
p += sprintf(p,
seq_printf(seq,
" thru serial port: \trx:\t%lu\ttx:\t%lu\n",
rx_sbytes, tx_sbytes);
p += sprintf(p,
seq_printf(seq,
" Total stat/err bytes:\trx:\t%lu\ttx:\t%lu\n",
rx_ebytes, tx_ebytes);
#endif
p += sprintf_neighbours(p, &strip_info->poletops,
strip_seq_neighbours(seq, &strip_info->poletops,
"Poletops:");
p += sprintf_neighbours(p, &strip_info->portables,
strip_seq_neighbours(seq, &strip_info->portables,
"Portables:");
}
return p - buffer;
}
/*
* This function is exports status information from the STRIP driver through
* the /proc file system.
*/
static int strip_seq_show(struct seq_file *seq, void *v)
{
if (v == STRIP_PROC_HEADER)
seq_printf(seq, "strip_version: %s\n", StripVersion);
else
strip_seq_status_info(seq, (const struct strip *)v);
return 0;
}
static int get_status_info(char *buffer, char **start, off_t req_offset,
int req_len)
static struct seq_operations strip_seq_ops = {
.start = strip_seq_start,
.next = strip_seq_next,
.stop = strip_seq_stop,
.show = strip_seq_show,
};
static int strip_seq_open(struct inode *inode, struct file *file)
{
int total = 0, slop = 0;
struct strip *strip_info = struct_strip_list;
char *buf = buffer;
buf += sprintf(buf, "strip_version: %s\n", StripVersion);
if (shift_buffer(buffer, req_offset, req_len, &total, &slop, &buf))
goto exit;
while (strip_info != NULL) {
buf += sprintf_status_info(buf, strip_info);
if (shift_buffer
(buffer, req_offset, req_len, &total, &slop, &buf))
break;
strip_info = strip_info->next;
}
exit:
return (calc_start_len
(buffer, start, req_offset, req_len, total, buf));
return seq_open(file, &strip_seq_ops);
}
static struct file_operations strip_seq_fops = {
.owner = THIS_MODULE,
.open = strip_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
/************************************************************************/
/* Sending routines */
......@@ -1578,7 +1562,6 @@ static void strip_send(struct strip *strip_info, struct sk_buff *skb)
static int strip_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct strip *strip_info = (struct strip *) (dev->priv);
unsigned long flags;
if (!netif_running(dev)) {
printk(KERN_ERR "%s: xmit call when iface is down\n",
......@@ -1617,14 +1600,11 @@ static int strip_xmit(struct sk_buff *skb, struct net_device *dev)
strip_info->dev->name, sx_pps_count / 8);
}
spin_lock_irqsave(&strip_lock, flags);
/* See if someone has been ifconfigging */
if (strip_info->mtu != strip_info->dev->mtu)
strip_changedmtu(strip_info);
spin_lock_bh(&strip_lock);
strip_send(strip_info, skb);
spin_unlock_irqrestore(&strip_lock, flags);
spin_unlock_bh(&strip_lock);
if (skb)
dev_kfree_skb(skb);
......@@ -2317,18 +2297,12 @@ static void strip_receive_buf(struct tty_struct *tty, const unsigned char *cp,
{
struct strip *strip_info = (struct strip *) tty->disc_data;
const unsigned char *end = cp + count;
unsigned long flags;
if (!strip_info || strip_info->magic != STRIP_MAGIC
|| !netif_running(strip_info->dev))
return;
spin_lock_irqsave(&strip_lock, flags);
/* Argh! mtu change time! - costs us the packet part received at the change */
if (strip_info->mtu != strip_info->dev->mtu)
strip_changedmtu(strip_info);
spin_lock_bh(&strip_lock);
#if 0
{
struct timeval tv;
......@@ -2395,7 +2369,7 @@ static void strip_receive_buf(struct tty_struct *tty, const unsigned char *cp,
}
cp++;
}
spin_unlock_irqrestore(&strip_lock, flags);
spin_unlock_bh(&strip_lock);
}
......@@ -2483,7 +2457,7 @@ static int strip_open_low(struct net_device *dev)
if (strip_info->tty == NULL)
return (-ENODEV);
if (!allocate_buffers(strip_info))
if (!allocate_buffers(strip_info, dev->mtu))
return (-ENOMEM);
strip_info->sx_count = 0;
......@@ -2543,12 +2517,14 @@ static int strip_close_low(struct net_device *dev)
* (dynamically assigned) device is registered
*/
static int strip_dev_init(struct net_device *dev)
static void strip_dev_setup(struct net_device *dev)
{
/*
* Finish setting up the DEVICE info.
*/
SET_MODULE_OWNER(dev);
dev->trans_start = 0;
dev->last_rx = 0;
dev->tx_queue_len = 30; /* Drop after 30 frames queued */
......@@ -2576,7 +2552,7 @@ static int strip_dev_init(struct net_device *dev)
dev->rebuild_header = strip_rebuild_header;
dev->set_mac_address = dev_set_mac_address;
dev->get_stats = strip_get_stats;
return 0;
dev->change_mtu = strip_change_mtu;
}
/*
......@@ -2585,65 +2561,34 @@ static int strip_dev_init(struct net_device *dev)
static void strip_free(struct strip *strip_info)
{
*(strip_info->referrer) = strip_info->next;
if (strip_info->next)
strip_info->next->referrer = strip_info->referrer;
spin_lock_bh(&strip_lock);
list_del_rcu(&strip_info->list);
spin_unlock_bh(&strip_lock);
strip_info->magic = 0;
if (strip_info->dev)
kfree(strip_info->dev);
kfree(strip_info);
kfree(strip_info->dev);
}
/*
* Allocate a new free STRIP channel
*/
static struct strip *strip_alloc(void)
{
int channel_id = 0;
struct strip **s = &struct_strip_list;
struct list_head *n;
struct net_device *dev;
struct strip *strip_info = (struct strip *)
kmalloc(sizeof(struct strip), GFP_KERNEL);
struct strip *strip_info;
if (!strip_info)
return NULL; /* If no more memory, return */
dev = alloc_netdev(sizeof(struct strip), "st%d",
strip_dev_setup);
/*
* Clear the allocated memory
*/
if (!dev)
return NULL; /* If no more memory, return */
memset(strip_info, 0, sizeof(struct strip));
/* allocate the net_device */
dev = kmalloc(sizeof(struct net_device), GFP_KERNEL);
if (!dev) {
kfree(strip_info);
return NULL;
}
strip_info = dev->priv;
strip_info->dev = dev;
SET_MODULE_OWNER(dev);
/*
* Search the list to find where to put our new entry
* (and in the process decide what channel number it is
* going to be)
*/
while (*s && (*s)->dev->base_addr == channel_id) {
channel_id++;
s = &(*s)->next;
}
/*
* Fill in the link pointers
*/
strip_info->next = *s;
if (*s)
(*s)->referrer = &strip_info->next;
strip_info->referrer = s;
*s = strip_info;
strip_info->magic = STRIP_MAGIC;
strip_info->tty = NULL;
......@@ -2654,12 +2599,27 @@ static struct strip *strip_alloc(void)
strip_info->idle_timer.data = (long) dev;
strip_info->idle_timer.function = strip_IdleTask;
/* Note: strip_info->if_name is currently 8 characters long */
sprintf(dev->name, "st%d", channel_id);
dev->base_addr = channel_id;
dev->priv = (void *) strip_info;
dev->next = NULL;
dev->init = strip_dev_init;
spin_lock_bh(&strip_lock);
rescan:
/*
* Search the list to find where to put our new entry
* (and in the process decide what channel number it is
* going to be)
*/
list_for_each(n, &strip_list) {
struct strip *s = hlist_entry(n, struct strip, list);
if (s->dev->base_addr == dev->base_addr) {
++dev->base_addr;
goto rescan;
}
}
sprintf(dev->name, "st%ld", dev->base_addr);
list_add_tail_rcu(&strip_info->list, &strip_list);
spin_unlock_bh(&strip_lock);
return strip_info;
}
......@@ -2809,6 +2769,7 @@ static int strip_ioctl(struct tty_struct *tty, struct file *file,
static struct tty_ldisc strip_ldisc = {
.magic = TTY_LDISC_MAGIC,
.name = "strip",
.owner = THIS_MODULE,
.open = strip_open,
.close = strip_close,
.ioctl = strip_ioctl,
......@@ -2832,7 +2793,6 @@ static int __init strip_init_driver(void)
printk(signon, StripVersion);
spin_lock_init(&strip_lock);
/*
* Fill in our line protocol discipline, and register it
......@@ -2844,7 +2804,7 @@ static int __init strip_init_driver(void)
/*
* Register the status file with /proc
*/
proc_net_create("strip", S_IFREG | S_IRUGO, get_status_info);
proc_net_fops_create("strip", S_IFREG | S_IRUGO, &strip_seq_fops);
return status;
}
......@@ -2857,8 +2817,13 @@ static const char signoff[] __exitdata =
static void __exit strip_exit_driver(void)
{
int i;
while (struct_strip_list)
strip_free(struct_strip_list);
struct list_head *p,*n;
/* module ref count rules assure that all entries are unregistered */
list_for_each_safe(p, n, &strip_list) {
struct strip *s = list_entry(p, struct strip, list);
strip_free(s);
}
/* Unregister with the /proc/net file here. */
proc_net_remove("strip");
......
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