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 --- drivers/net/Kconfig | 15 + drivers/net/Makefile | 1 drivers/net/maclist.c | 465 ++++++++++++++++++++++++++++++++++++++++++++++++++ include/net/maclist.h | 49 +++++ 4 files changed, 530 insertions(+) --- linux-ixp4xx.orig/drivers/net/Kconfig 2006-02-23 18:27:12.000000000 +0100 +++ linux-ixp4xx/drivers/net/Kconfig 2006-02-23 18:30:54.000000000 +0100 @@ -177,6 +177,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 --- linux-ixp4xx.orig/drivers/net/Makefile 2006-02-23 18:27:12.000000000 +0100 +++ linux-ixp4xx/drivers/net/Makefile 2006-02-23 18:30:54.000000000 +0100 @@ -74,6 +74,7 @@ obj-$(CONFIG_RIONET) += rionet.o # end link order section # +obj-$(CONFIG_MACLIST) += maclist.o obj-$(CONFIG_MII) += mii.o obj-$(CONFIG_PHYLIB) += phy/ --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-ixp4xx/drivers/net/maclist.c 2006-02-23 18:30:54.000000000 +0100 @@ -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 + * 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 +#include +#include +#include +#include +#include + +#include + +#define MACLIST_NAME "maclist" + +MODULE_AUTHOR("John Bowler "); +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); --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-ixp4xx/include/net/maclist.h 2006-02-23 18:30:54.000000000 +0100 @@ -0,0 +1,49 @@ +#ifndef _MACLIST_H +#define _MACLIST_H 1 +/* + * Interfaces to the MAC repository + * + * Copyright (C) 2005 John Bowler + * Author: John Bowler + * 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*/