Ethernet MAC repository.

Some ethernet controllers have no built-in way of obtaining an
appropriate Ethernet MAC address.  Such controllers have to be
initialised in a board-specific way, depending on how the allocated
MAC is stored.  The MAC repository provides a set of APIs and a
proc entry (/proc/net/maclist) to store MAC values from the board
so that such drivers can obtain a MAC address without board-specific
code.

Signed-off-by: John Bowler <jbowler@acm.org>

diff -rup linux-2.6.15.1/.pc/91-maclist.patch/drivers/net/Kconfig linux-2.6.15.1/drivers/net/Kconfig
--- linux-2.6.15/drivers/net/Kconfig	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.15/drivers/net/Kconfig	1970-01-01 00:00:00.000000000 +0000
@@ -166,6 +166,21 @@ config NET_ETHERNET
 	  kernel: saying N will just cause the configurator to skip all
 	  the questions about Ethernet network cards. If unsure, say N.
 
+config MACLIST
+	tristate "Ethernet MAC repository"
+	depends on NET_ETHERNET
+	help
+	  Some ethernet controllers have no built-in way of obtaining an
+	  appropriate Ethernet MAC address.  Such controllers have to be
+	  initialised in a board-specific way, depending on how the allocated
+	  MAC is stored.  The MAC repository provides a set of APIs and a
+	  proc entry (/proc/net/maclist) to store MAC values from the board
+	  so that such drivers can obtain a MAC address without board-specific
+	  code.  You do not need to enable this device - it will be selected
+	  automatically by any device which requires it.  It is only useful
+	  to enable it manually when building a device driver independently
+	  of the kernel build.
+
 config MII
 	tristate "Generic Media Independent Interface device support"
 	depends on NET_ETHERNET
diff -rup linux-2.6.15.1/.pc/91-maclist.patch/drivers/net/Makefile linux-2.6.15.1/drivers/net/Makefile
--- linux-2.6.15/drivers/net/Makefile	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.15/drivers/net/Makefile	1970-01-01 00:00:00.000000000 +0000
@@ -70,6 +70,7 @@ obj-$(CONFIG_RIONET) += rionet.o
 # end link order section
 #
 
+obj-$(CONFIG_MACLIST) += maclist.o
 obj-$(CONFIG_MII) += mii.o
 obj-$(CONFIG_PHYLIB) += phy/
 
diff -rup linux-2.6.15.1/.pc/91-maclist.patch/drivers/net/maclist.c linux-2.6.15.1/drivers/net/maclist.c
--- linux-2.6.15/drivers/net/maclist.c	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.15/drivers/net/maclist.c	1970-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,465 @@
+/*
+ * drivers/net/maclist.c
+ *
+ * a simple driver to remember ethernet MAC values
+ *
+ * Some Ethernet hardware implementations have no built-in
+ * storage for allocated MAC values - an example is the Intel
+ * IXP420 chip which has support for Ethernet but no defined
+ * way of storing allocated MAC values.  With such hardware
+ * different board level implementations store the allocated
+ * MAC (or MACs) in different ways.  Rather than put board
+ * level code into a specific Ethernet driver this driver
+ * provides a generally accessible repository for the MACs
+ * which can be written by board level code and read by the
+ * driver.
+ *
+ * The implementation also allows user level programs to
+ * access the MAC information in /proc/net/maclist.  This is
+ * useful as it allows user space code to use the MAC if it
+ * is not used by a built-in driver.
+ *
+ * Copyright (C) 2005 John Bowler
+ * Author: John Bowler <jbowler@acm.org>
+ * Maintainers: http://www.nslu2-linux.org/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ */
+
+/*
+ * External interfaces:
+ *  Interfaces to linux kernel (and modules)
+ *   maclist_add:   add a single MAC, sequenced with a single
+ *                  writer lock (reads may happen simultaneously
+ *                  because of the way the list is built)
+ *   maclist_count: total number of MACs stored
+ *   maclist_read:  read a MAC 0..(maclist_count-1).  Call this
+ *                  to get a specific MAC.  If the argument is
+ *                  a new key and all the allocaed MACs have been
+ *                  assigned a random but valid MAC will be return
+ *                  (and this will be stored for later retrieval
+ *                  under the given key.)
+ *
+ * Sequencing:
+ *  The MAC ids must be added before any driver tries to use them
+ *  (this is obvious isn't it?)  This can be made to happen by
+ *  sequencing the initcalls correctly.  The module or kernel
+ *  parameters have been handled before any init call happens.
+ *  The important trick here is to ensure that the platform
+ *  initialises any devices with MAC ids *before* any devices
+ *  which might use them.
+ *
+ *  When this code is a module any other module which adds a
+ *  MAC should be modprobed before modules for ethernet
+ *  devices.
+ *
+ *  The failure case is 'soft' - the device will get a valid, but
+ *  random, MAC and the real allocated MACs will never get used.
+ *  This can be seen by looking at the list of ids in sysfs (there
+ *  will be extra, random, ones after the allocated ones).
+ *
+ * Recommendations:
+ *  For ethernet drivers which are known to be the sole driver on
+ *  the board (without a built in MAC) and where the number of
+ *  devices driven is known simply use an index 0..(n-1) as a
+ *  key for each of the n devices.
+ *
+ *  This is the common case, it works where one driver handles
+ *  multiple devices so long as the total number of devices can
+ *  be determined reliably.  It is sufficient merely to maintain
+ *  a global count of the number of devices initialised so far,
+ *  just so long as the initialisation order is consistent.
+ *
+ *  When the driver is generic and the board may be populated with
+ *  other devices which allocate MACs from the maclist pool and
+ *  use different drivers create a random key and compile this into
+ *  the code.  Use this as the base for all devices from the driver
+ *  (using a global device count for this driver if necessary).
+ *
+ *  With the second strategy the assignment of MACs will depend on
+ *  the order of initialisation of the different drivers.  To avoid
+ *  this provide a kernel (or module) command line parameter to
+ *  specify a base index and (optional) count for each driver or
+ *  pass in a (struct resource) with the start and end of the keys
+ *  to pass to maclist_read.  Either method allows the higher levels
+ *  (boot loader or machine description) to specify which MACs in
+ *  the list to assign to each device.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/spinlock.h>
+#include <linux/etherdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/errno.h>
+
+#include <net/maclist.h>
+
+#define MACLIST_NAME "maclist"
+
+MODULE_AUTHOR("John Bowler <jbowler@acm.org>");
+MODULE_DESCRIPTION("MAC list repository");
+MODULE_LICENSE("GPL");
+
+typedef struct maclist_entry {
+	struct maclist_entry *next;  /* Linked list, first first */
+	u32                   key;   /* count or key for this entry */
+	u16                   flags;
+	u8                    id[6]; /* 6 byte Ethernet MAC */
+} maclist_entry_t;
+
+/*
+ * flag definitions
+ */
+#define MACLIST_ALLOCATED 1
+#define MACLIST_RANDOM    2
+
+/* Access to this list is protected by a standard rwlock_t. */
+static maclist_entry_t *maclist_list = 0;
+
+static DEFINE_RWLOCK(maclist_lock);
+
+/*
+ * External interfaces.
+ *
+ * Add a single entry, returns 0 on success else an error
+ * code.  Checks for invalid addresses.
+ */
+int maclist_add(const u8 new_id[6]) {
+	maclist_entry_t *new_entry, **tail;
+
+	if (new_id == 0 || !is_valid_ether_addr(new_id)) {
+		printk(KERN_ERR MACLIST_NAME ": invalid ethernet address\n");
+		return -EINVAL;
+	}
+	new_entry = kmalloc(sizeof *new_entry, GFP_KERNEL);
+	if (new_entry == 0)
+		return -ENOMEM;
+	new_entry->next  = 0;
+	new_entry->key   = 0;
+	new_entry->flags = 0;
+	memcpy(new_entry->id, new_id, sizeof new_entry->id);
+
+	tail = &maclist_list;
+
+	write_lock(&maclist_lock);
+	while (*tail != 0)
+		tail = &(*tail)->next;
+	*tail = new_entry;
+	write_unlock(&maclist_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(maclist_add);
+
+/*
+ * Return the current entry count.
+ */
+static int maclist_count_unlocked(void) {
+	maclist_entry_t *tail = maclist_list;
+	int count = 0;
+
+	while (tail != 0) {
+		tail = tail->next;
+		++count;
+	}
+
+	return count;
+}
+
+int maclist_count(void) {
+	int count;
+
+	read_lock(&maclist_lock);
+		count = maclist_count_unlocked();
+	read_unlock(&maclist_lock);
+
+	return count;
+}
+EXPORT_SYMBOL(maclist_count);
+
+/*
+ * Return the ID with the given key (the key is allocated
+ * to an entry if not found).
+ */
+void maclist_read(u8 (*id)[6], u32 key) {
+	int              count, index;
+	maclist_entry_t *entry, *entry_to_allocate;
+	
+	/* Do this under a write lock to avoid the SMP race
+	 * where we find the key isn't assigned, drop the lock,
+	 * have another CPU assign it, then assign it on this
+	 * CPU too - very bad...
+	 */
+	write_lock(&maclist_lock);
+		count             = maclist_count_unlocked();
+		index             = key % count; /* index of entry to allocate */
+		entry_to_allocate = 0;
+
+		entry = maclist_list;
+		while (entry != 0) {
+			if ((entry->flags & MACLIST_ALLOCATED) != 0) {
+				if (entry->key == key) {
+					/* Found it, use this entry. */
+					entry_to_allocate = entry;
+					break;
+				}
+			} else if (entry_to_allocate == 0 || count <= index) {
+				/* The algorithm is to try for entry
+				 * (key % count), but if this isn't possible
+				 * return the prior unallocated entry.
+				 */
+				entry_to_allocate = entry;
+			}
+
+			++count;
+			entry = entry->next;
+		}
+
+		/* Use entry_to_allocate, allocating it if necessary. */
+		if (entry_to_allocate != 0) {
+			if ((entry_to_allocate->flags & MACLIST_ALLOCATED) == 0) {
+				entry_to_allocate->key = key;
+				entry_to_allocate->flags |= MACLIST_ALLOCATED;
+			}
+			memcpy(id, entry_to_allocate->id, sizeof *id);
+		}
+	write_unlock(&maclist_lock);
+
+	if (entry_to_allocate == 0) {
+		/* No unallocated entries.  Make a new one and return it. */
+		printk(KERN_INFO MACLIST_NAME ": adding random MAC for key 0x%x\n", key);
+		random_ether_addr(*id);
+		if (maclist_add(*id) == 0)
+			maclist_read(id, key);
+	}
+}
+EXPORT_SYMBOL(maclist_read);
+
+/*
+ * Parameter parsing.  The option string is a list of MAC
+ * addresses, comma separated.  (The parsing really should
+ * be somewhere central...)
+ */
+static int __init maclist_setup(const char *param) {
+	int bytes = 0, seen_a_digit = 0;
+	u8 id[6];
+
+	memset(id, 0, sizeof id);
+
+	if (param) do {
+		int digit = -1;
+		switch (*param) {
+		case '0': digit = 0; break;
+		case '1': digit = 1; break;
+		case '2': digit = 2; break;
+		case '3': digit = 3; break;
+		case '4': digit = 4; break;
+		case '5': digit = 5; break;
+		case '6': digit = 6; break;
+		case '7': digit = 7; break;
+		case '8': digit = 8; break;
+		case '9': digit = 9; break;
+		case 'a': case 'A': digit = 10; break;
+		case 'b': case 'B': digit = 11; break;
+		case 'c': case 'C': digit = 12; break;
+		case 'd': case 'D': digit = 13; break;
+		case 'e': case 'E': digit = 14; break;
+		case 'f': case 'F': digit = 15; break;
+		case ':':
+			if (seen_a_digit)
+				bytes = (bytes+1) & ~1;
+			else
+				bytes += 2; /* i.e. ff::ff is ff:00:ff */
+			seen_a_digit = 0;
+			break;
+		case 0:
+			if (bytes == 0) /* nothing new seen so far */
+				return 0;
+			/*fall through*/
+		case ',': case ';':
+			if (bytes > 0)
+				bytes = 12; /* i.e. all trailing bytes 0 */
+			break;
+		default:
+			printk(KERN_ERR MACLIST_NAME ": invalid character <%c[%d]>\n",
+					*param, *param);
+			return -EINVAL;
+		}
+
+		if (digit >= 0) {
+			id[bytes>>1] = (id[bytes>>1] << 4) + digit; break;
+			++bytes;
+			seen_a_digit = 1;
+		}
+
+		if (bytes >= 12) {
+			int rc = maclist_add(id);
+			if (unlikely(rc))
+				return rc;
+			bytes = 0;
+			seen_a_digit = 0;
+			memset(id, 0, sizeof id);
+			if (*param == 0)
+				return 0;
+		}
+		++param;
+	} while (1);
+
+	return 0;
+}
+
+#if (defined CONFIG_PROC_FS) || (defined MODULE)
+/*
+ * Character device read
+ */
+static int maclist_getchar(off_t n) {
+	static char xdigit[16] = "0123456789abcdef";
+	maclist_entry_t *head = maclist_list;
+	int b;
+
+	do {
+		if (head == 0)
+			return -1;
+		if (n < 18)
+			break;
+		head = head->next;
+		n -= 18;
+	} while (1);
+
+	if (n == 17)
+		return '\n';
+
+	b = n/3;
+	switch (n - b*3) {
+	case 0: return xdigit[head->id[b] >> 4];
+	case 1: return xdigit[head->id[b] & 0xf];
+	default: return ':';
+	}
+}
+#endif
+
+/*
+ * procfs support, if compiled in.
+ */
+#ifdef CONFIG_PROC_FS
+/*
+ * The extensively undocumented proc_read_t callback is implemented here.
+ * Go look in fs/proc/generic.c:
+ *
+ * Prototype:
+ *    int f(char *buffer, char **start, off_t offset,
+ *          int count, int *peof, void *dat)
+ *
+ * Assume that the buffer is "count" bytes in size.
+ *
+ * 2) Set *start = an address within the buffer.
+ *    Put the data of the requested offset at *start.
+ *    Return the number of bytes of data placed there.
+ *    If this number is greater than zero and you
+ *    didn't signal eof and the reader is prepared to
+ *    take more data you will be called again with the
+ *    requested offset advanced by the number of bytes
+ *    absorbed.
+ */
+static int maclist_proc_read(char *buffer, char **start, off_t offset,
+		int count, int *peof, void *dat) {
+	int total;
+
+	*start = buffer;
+	total = 0;
+
+	while (total < count) {
+		int ch = maclist_getchar(offset++);
+		if (ch == -1) {
+			*peof = 1;
+			break;
+		}
+		*buffer++ = ch;
+		++total;
+	}
+
+	return total;
+}
+#endif
+
+/*
+ * set works once, at init time (the param is set to 0444 below),
+ * get works any time.
+ */
+static int param_set_maclist(const char *val, struct kernel_param *kp)
+{
+	if (maclist_list == 0)
+		return maclist_setup(val);
+
+	printk(KERN_ERR MACLIST_NAME ": call to set parameters too late\n");
+	return -EINVAL;
+}
+
+static int param_get_maclist(char *buffer, struct kernel_param *kp)
+{
+#ifdef MODULE
+	off_t offset = 0;
+
+	/* buffer is only 4k! */
+	while (offset < 4096) {
+		int ch = maclist_getchar(offset++);
+		if (ch < 0) {
+			*buffer = 0;
+			return 0;
+		}
+		*buffer++ = ch;
+	}
+
+	*--buffer = 0;
+	return -ENOMEM;
+#else
+	return -EINVAL;
+#endif
+}
+
+/*
+ * module: the argument is ids=mac,mac,mac
+ * kernel command line: maclist.ids=mac,mac,mac
+ */
+#define param_check_maclist(name, p) __param_check(name, p, maclist_entry_t*)
+module_param_named(ids, maclist_list, maclist, 0444);
+MODULE_PARM_DESC(ids, "comma separated list of MAC ids\n");
+
+/*
+ * Finally, the init/exit functions.
+ */
+static void __exit maclist_exit(void)
+{
+	maclist_entry_t *list;
+
+	remove_proc_entry(MACLIST_NAME, proc_net);
+
+	write_lock(&maclist_lock);
+		list = maclist_list;
+		maclist_list = 0;
+	write_unlock(&maclist_lock);
+
+	while (list != 0) {
+		maclist_entry_t *head = list;
+		list = head->next;
+		kfree(head);
+	}
+}
+
+static int __init maclist_init(void)
+{
+#	ifdef MODULE
+		if (ids[0])
+			maclist_setup(ids);
+#	endif
+
+	/* Ignore failure, the module will still work. */
+	(void)create_proc_read_entry(MACLIST_NAME, S_IRUGO, proc_net, maclist_proc_read, NULL);
+
+	return 0;
+}
+
+module_init(maclist_init);
+module_exit(maclist_exit);
diff -rup linux-2.6.15.1/.pc/91-maclist.patch/include/net/maclist.h linux-2.6.15.1/include/net/maclist.h
--- linux-2.6.15/include/net/maclist.h	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.15/include/net/maclist.h	1970-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,49 @@
+#ifndef _MACLIST_H
+#define _MACLIST_H 1
+/*
+ * Interfaces to the MAC repository
+ *
+ * Copyright (C) 2005 John Bowler
+ * Author: John Bowler <jbowler@acm.org>
+ * Maintainers: http://www.nslu2-linux.org/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ */
+
+/*
+ * Add a single entry, returns 0 on success else an error
+ * code.  Allocates memory, claims and releases a write
+ * lock.
+ */
+extern int maclist_add(const u8 id_to_add[6]);
+
+/*
+ * Return the current entry count, claims and releases a
+ * read lock.
+ */
+extern int maclist_count(void);
+
+/*
+ * Return the ID from the given entry.  Always succeeds.
+ * Claims and releases a write lock.
+ *
+ * If any entry has not been allocated for this key one
+ * is allocated.  If there are no remaining unallocated
+ * entries a new one is created.
+ *
+ * If the value of the key is less than maclist_count()
+ * the entry indexed by the key (i.e. for key 'n' the
+ * n'th entry starting at 0) will be returned if available.
+ * Otherwise the entry to be returned will be unpredictable
+ * but consistent for a given value of maclist_count().
+ */
+extern void maclist_read(u8 (*buffer_for_id)[6],
+		u32 key_of_entry_to_return);
+
+/*
+ * See the implementation in drivers/net/maclist.c for
+ * more information.
+ */
+#endif /*_MACLIST_H*/