diff options
| -rw-r--r-- | package/swconfig/Makefile | 47 | ||||
| -rw-r--r-- | package/swconfig/src/Makefile | 12 | ||||
| -rw-r--r-- | package/swconfig/src/cli.c | 255 | ||||
| -rw-r--r-- | package/swconfig/src/swlib.c | 614 | ||||
| -rw-r--r-- | package/swconfig/src/swlib.h | 213 | ||||
| -rw-r--r-- | target/linux/generic-2.6/config-2.6.26 | 1 | ||||
| -rw-r--r-- | target/linux/generic-2.6/config-2.6.27 | 1 | ||||
| -rw-r--r-- | target/linux/generic-2.6/files-2.6.26/drivers/net/phy/swconfig.c | 872 | ||||
| -rw-r--r-- | target/linux/generic-2.6/files-2.6.26/include/linux/switch.h | 168 | ||||
| -rw-r--r-- | target/linux/generic-2.6/files-2.6.27/drivers/net/phy/swconfig.c | 872 | ||||
| -rw-r--r-- | target/linux/generic-2.6/files-2.6.27/include/linux/switch.h | 168 | ||||
| -rw-r--r-- | target/linux/generic-2.6/patches-2.6.26/650-swconfig.patch | 25 | ||||
| -rw-r--r-- | target/linux/generic-2.6/patches-2.6.27/650-swconfig.patch | 25 | 
13 files changed, 3273 insertions, 0 deletions
| diff --git a/package/swconfig/Makefile b/package/swconfig/Makefile new file mode 100644 index 000000000..0d86438a7 --- /dev/null +++ b/package/swconfig/Makefile @@ -0,0 +1,47 @@ +#  +# Copyright (C) 2006 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# +# $Id$ + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=kmod-swconfig +PKG_RELEASE:=1 +PKG_BUILD_DEPENDS:=libnl + +include $(INCLUDE_DIR)/package.mk + +define Package/swconfig +  SECTION:=base +  CATEGORY:=Base system +  DEPENDS:=@LINUX_2_6_26||LINUX_2_6_27 +  TITLE:=Switch configuration utility +endef + +TARGET_CPPFLAGS += \ +	-I$(STAGING_DIR)/usr/include \ +	-I$(LINUX_DIR)/include \ +	-I$(PKG_BUILD_DIR) + +define Build/Prepare +	mkdir -p $(PKG_BUILD_DIR) +	$(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Build/Compile +	CFLAGS="$(TARGET_CFLAGS) $(TARGET_CPPFLAGS)" \ +	$(MAKE) -C $(PKG_BUILD_DIR) \ +		$(TARGET_CONFIGURE_OPTS) \ +		LIBS="$(STAGING_DIR)/usr/lib/libnl.a -lm" +endef + +define Package/swconfig/install +	$(INSTALL_DIR) $(1)/bin +	$(INSTALL_BIN) $(PKG_BUILD_DIR)/swconfig $(1)/bin/swconfig +endef + +$(eval $(call BuildPackage,swconfig)) diff --git a/package/swconfig/src/Makefile b/package/swconfig/src/Makefile new file mode 100644 index 000000000..64816af54 --- /dev/null +++ b/package/swconfig/src/Makefile @@ -0,0 +1,12 @@ +ifndef CFLAGS +CFLAGS = -O2 -g -I ../src +endif +LIBS=-lnl + +all: swconfig + +%.o: %.c +	$(CC) $(CFLAGS) -c -o $@ $^ + +swconfig: cli.o swlib.o +	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) diff --git a/package/swconfig/src/cli.c b/package/swconfig/src/cli.c new file mode 100644 index 000000000..c6035e585 --- /dev/null +++ b/package/swconfig/src/cli.c @@ -0,0 +1,255 @@ +/* + * swconfig.c: Switch configuration utility + * + * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.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 Foundatio. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <inttypes.h> +#include <errno.h> +#include <stdint.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <linux/types.h> +#include <linux/netlink.h> +#include <linux/genetlink.h> +#include <netlink/netlink.h> +#include <netlink/genl/genl.h> +#include <netlink/genl/ctrl.h> +#include <linux/switch.h> +#include "swlib.h" + +#define GET		1 +#define SET		2 + +void print_attrs(struct switch_attr *attr) +{ +	int i = 0; +	while (attr) { +		const char *type; +		switch(attr->type) { +			case SWITCH_TYPE_INT: +				type = "int"; +				break; +			case SWITCH_TYPE_STRING: +				type = "string"; +				break; +			case SWITCH_TYPE_PORTS: +				type = "ports"; +				break; +			case SWITCH_TYPE_NOVAL: +				type = "none"; +				break; +			default: +				type = "unknown"; +				break; +		} +		printf("\tAttribute %d (%s): %s (%s)\n", ++i, type, attr->name, attr->description); +		attr = attr->next; +	} +} + +void list_attributes(struct switch_dev *dev) +{ +	printf("Switch %d: %s(%s), ports: %d, vlans: %d\n", dev->id, dev->dev_name, dev->name, dev->ports, dev->vlans); +	printf("     --switch\n"); +	print_attrs(dev->ops); +	printf("     --vlan\n"); +	print_attrs(dev->vlan_ops); +	printf("     --port\n"); +	print_attrs(dev->port_ops); +} + +void print_usage(void) +{ +	printf("swconfig dev <dev> [port <port>|vlan <vlan>] (help|set <key> <value>|get <key>)\n"); +	exit(0); +} + +int main(int argc, char **argv) +{ +	int retval = 0; +	struct switch_dev *dev; +	struct switch_attr *a; +	struct switch_val val; +	int err; +	int i; + +	struct switch_port *ports; + +	int cmd = 0; +	char *cdev = NULL; +	int cport = -1; +	int cvlan = -1; +	char *ckey = NULL; +	char *cvalue = NULL; +	int chelp = 0; + +	if(argc < 4) +		print_usage(); + +	if(strcmp(argv[1], "dev")) +		print_usage(); + +	cdev = argv[2]; + +	for(i = 3; i < argc; i++) +	{ +		int p; +		if(!strcmp(argv[i], "help")) +		{ +			chelp = 1; +			continue; +		} +		if(i + 1 >= argc) +			print_usage(); +		p = atoi(argv[i + 1]); +		if(!strcmp(argv[i], "port")) +		{ +			cport = p; +		} else if(!strcmp(argv[i], "vlan")) +		{ +			cvlan = p; +		} else if(!strcmp(argv[i], "set")) +		{ +			if(argc <= i + 1) +				print_usage(); +			cmd = SET; +			ckey = argv[i + 1]; +			if (argc > i + 2) +				cvalue = argv[i + 2]; +			else +				cvalue = NULL; +			i++; +		} else if(!strcmp(argv[i], "get")) +		{ +			cmd = GET; +			ckey = argv[i + 1]; +		} else{ +			print_usage(); +		} +		i++; +	} + +	if(cport > -1 && cvlan > -1) +		print_usage(); + +	dev = swlib_connect(cdev); +	if (!dev) { +		fprintf(stderr, "Failed to connect to the switch\n"); +		return 1; +	} + +	ports = malloc(sizeof(struct switch_port) * dev->ports); +	memset(ports, 0, sizeof(struct switch_port) * dev->ports); +	swlib_scan(dev); + +	if(chelp) +	{ +		list_attributes(dev); +		goto out; +	} + +	if(cport > -1) +		a = swlib_lookup_attr(dev, SWLIB_ATTR_GROUP_PORT, ckey); +	else if(cvlan > -1) +		a = swlib_lookup_attr(dev, SWLIB_ATTR_GROUP_VLAN, ckey); +	else +		a = swlib_lookup_attr(dev, SWLIB_ATTR_GROUP_GLOBAL, ckey); + +	if(!a) +	{ +		fprintf(stderr, "Unknown attribute \"%s\"\n", ckey); +		goto out; +	} + +	switch(cmd) +	{ +	case SET: +		if ((a->type != SWITCH_TYPE_NOVAL) && +				(cvalue == NULL)) +			print_usage(); + +		switch(a->type) { +		case SWITCH_TYPE_INT: +			val.value.i = atoi(cvalue); +			break; +		case SWITCH_TYPE_STRING: +			val.value.s = cvalue; +			break; +		case SWITCH_TYPE_PORTS: +			val.len = 0; +			while(cvalue && *cvalue) +			{ +				ports[val.len].flags = 0; +				ports[val.len].id = strtol(cvalue, &cvalue, 10); +				while(*cvalue && !isspace(*cvalue)) { +					if (*cvalue == 't') +						ports[val.len].flags |= SWLIB_PORT_FLAG_TAGGED; +					cvalue++; +				} +				if (*cvalue) +					cvalue++; +				val.len++; +			} +			val.value.ports = ports; +			break; +		default: +			break; +		} +		if(cvlan > -1) +			val.port_vlan = cvlan; +		if(cport > -1) +			val.port_vlan = cport; +		if(swlib_set_attr(dev, a, &val) < 0) +		{ +			fprintf(stderr, "failed\n"); +			retval = -1; +			goto out; +		} +		break; +	case GET: +		if(cvlan > -1) +			val.port_vlan = cvlan; +		if(cport > -1) +			val.port_vlan = cport; +		if(swlib_get_attr(dev, a, &val) < 0) +		{ +			fprintf(stderr, "failed\n"); +			retval = -1; +			goto out; +		} +		switch(a->type) { +		case SWITCH_TYPE_INT: +			printf("%d\n", val.value.i); +			break; +		case SWITCH_TYPE_STRING: +			printf("%s\n", val.value.s); +			break; +		case SWITCH_TYPE_PORTS: +			for(i = 0; i < val.len; i++) +				printf("%d ", val.value.ports[i]); +			printf("\n"); +			break; +		} +	} + +out: +	swlib_free_all(dev); +	free(ports); + +	return 0; +} diff --git a/package/swconfig/src/swlib.c b/package/swconfig/src/swlib.c new file mode 100644 index 000000000..3fde81641 --- /dev/null +++ b/package/swconfig/src/swlib.c @@ -0,0 +1,614 @@ +/* + * swlib.c: Switch configuration API (user space part) + * + * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <inttypes.h> +#include <errno.h> +#include <stdint.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <linux/switch.h> +#include "swlib.h" + +//#define DEBUG 1 +#ifdef DEBUG +#define DPRINTF(fmt, ...) fprintf(stderr, "%s(%d): " fmt, __func__, __LINE__, ##__VA_ARGS__) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +static struct nl_handle *handle; +static struct nl_cache *cache; +static struct genl_family *family; +static struct nlattr *tb[SWITCH_ATTR_MAX]; +static int refcount = 0; + +static struct nla_policy port_policy[] = { +	[SWITCH_PORT_ID] = { .type = NLA_U32 }, +	[SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG }, +}; + +static inline void * +swlib_alloc(size_t size) +{ +	void *ptr; + +	ptr = malloc(size); +	if (!ptr) +		goto done; +	memset(ptr, 0, size); + +done: +	return ptr; +} + +static int +wait_handler(struct nl_msg *msg, void *arg) +{ +	int *finished = arg; + +	*finished = 1; +	return NL_STOP; +} + +/* helper function for performing netlink requests */ +static int +swlib_call(int cmd, int (*call)(struct nl_msg *, void *), +		int (*data)(struct nl_msg *, void *), void *arg) +{ +	struct nl_msg *msg; +	struct nl_cb *cb = NULL; +	int finished; +	int flags = 0; +	int err; + +	msg = nlmsg_alloc(); +	if (!msg) { +		fprintf(stderr, "Out of memory!\n"); +		exit(1); +	} + +	if (!data) +		flags |= NLM_F_DUMP; + +	genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, genl_family_get_id(family), 0, flags, cmd, 0); +	if (data) { +		if (data(msg, arg) < 0) +			goto nla_put_failure; +	} + +	cb = nl_cb_alloc(NL_CB_CUSTOM); +	if (!cb) { +		fprintf(stderr, "nl_cb_alloc failed.\n"); +		exit(1); +	} + +	err = nl_send_auto_complete(handle, msg); +	if (err < 0) { +		fprintf(stderr, "nl_send_auto_complete failed: %d\n", err); +		goto out; +	} + +	finished = 0; + +	if (call) +		nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, call, arg); + +	if (data) +		nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, wait_handler, &finished); +	else +		nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, wait_handler, &finished); + +	err = nl_recvmsgs(handle, cb); +	if (err < 0) { +		goto out; +	} + +	if (!finished) +		err = nl_wait_for_ack(handle); + +out: +	if (cb) +		nl_cb_put(cb); +nla_put_failure: +	nlmsg_free(msg); +	return err; +} + +static int +send_attr(struct nl_msg *msg, void *arg) +{ +	struct switch_val *val = arg; +	struct switch_attr *attr = val->attr; + +	NLA_PUT_U32(msg, SWITCH_ATTR_ID, attr->dev->id); +	NLA_PUT_U32(msg, SWITCH_ATTR_OP_ID, attr->id); +	switch(attr->atype) { +	case SWLIB_ATTR_GROUP_PORT: +		NLA_PUT_U32(msg, SWITCH_ATTR_OP_PORT, val->port_vlan); +		break; +	case SWLIB_ATTR_GROUP_VLAN: +		NLA_PUT_U32(msg, SWITCH_ATTR_OP_VLAN, val->port_vlan); +		break; +	default: +		break; +	} + +	return 0; + +nla_put_failure: +	return -1; +} + +static int +store_port_val(struct nl_msg *msg, struct nlattr *nla, struct switch_val *val) +{ +	struct nlattr *p; +	int ports = val->attr->dev->ports; +	int err = 0; +	int remaining; + +	if (!val->value.ports) +		val->value.ports = malloc(sizeof(struct switch_port) * ports); + +	nla_for_each_nested(p, nla, remaining) { +		struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1]; +		struct switch_port *port; + +		if (val->len >= ports) +			break; + +		err = nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, p, port_policy); +		if (err < 0) +			goto out; + +		if (!tb[SWITCH_PORT_ID]) +			continue; + +		port = &val->value.ports[val->len]; +		port->id = nla_get_u32(tb[SWITCH_PORT_ID]); +		port->flags = 0; +		if (tb[SWITCH_PORT_FLAG_TAGGED]) +			port->flags |= SWLIB_PORT_FLAG_TAGGED; + +		val->len++; +	} + +out: +	return err; +} + +static int +store_val(struct nl_msg *msg, void *arg) +{ +	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); +	struct switch_val *val = arg; +	struct switch_attr *attr = val->attr; + +	if (!val) +		goto error; + +	if (nla_parse(tb, SWITCH_ATTR_MAX - 1, genlmsg_attrdata(gnlh, 0), +			genlmsg_attrlen(gnlh, 0), NULL) < 0) { +		goto error; +	} + +	if (tb[SWITCH_ATTR_OP_VALUE_INT]) +		val->value.i = nla_get_u32(tb[SWITCH_ATTR_OP_VALUE_INT]); +	else if (tb[SWITCH_ATTR_OP_VALUE_STR]) +		val->value.s = strdup(nla_get_string(tb[SWITCH_ATTR_OP_VALUE_STR])); +	else if (tb[SWITCH_ATTR_OP_VALUE_PORTS]) +		val->err = store_port_val(msg, tb[SWITCH_ATTR_OP_VALUE_PORTS], val); + +	val->err = 0; +	return 0; + +error: +	return NL_SKIP; +} + +int +swlib_get_attr(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val) +{ +	int cmd; +	int err; + +	switch(attr->atype) { +	case SWLIB_ATTR_GROUP_GLOBAL: +		cmd = SWITCH_CMD_GET_GLOBAL; +		break; +	case SWLIB_ATTR_GROUP_PORT: +		cmd = SWITCH_CMD_GET_PORT; +		break; +	case SWLIB_ATTR_GROUP_VLAN: +		cmd = SWITCH_CMD_GET_VLAN; +		break; +	default: +		return -EINVAL; +	} + +	memset(&val->value, 0, sizeof(val->value)); +	val->len = 0; +	val->attr = attr; +	val->err = -EINVAL; +	err = swlib_call(cmd, store_val, send_attr, val); +	if (!err) +		err = val->err; + +	return err; +} + +static int +send_attr_ports(struct nl_msg *msg, struct switch_val *val) +{ +	struct nlattr *n; +	int i; + +	/* TODO implement multipart? */ +	if (val->len == 0) +		goto done; +	n = nla_nest_start(msg, SWITCH_ATTR_OP_VALUE_PORTS); +	if (!n) +		goto nla_put_failure; +	for (i = 0; i < val->len; i++) { +		struct switch_port *port = &val->value.ports[i]; +		struct nlattr *np; + +		np = nla_nest_start(msg, SWITCH_ATTR_PORT); +		if (!np) +			goto nla_put_failure; + +		NLA_PUT_U32(msg, SWITCH_PORT_ID, port->id); +		if (port->flags & SWLIB_PORT_FLAG_TAGGED) +			NLA_PUT_FLAG(msg, SWITCH_PORT_FLAG_TAGGED); + +		nla_nest_end(msg, np); +	} +	nla_nest_end(msg, n); +done: +	return 0; + +nla_put_failure: +	return -1; +} + +static int +send_attr_val(struct nl_msg *msg, void *arg) +{ +	struct switch_val *val = arg; +	struct switch_attr *attr = val->attr; + +	if (send_attr(msg, arg)) +		goto nla_put_failure; + +	switch(attr->type) { +	case SWITCH_TYPE_NOVAL: +		break; +	case SWITCH_TYPE_INT: +		NLA_PUT_U32(msg, SWITCH_ATTR_OP_VALUE_INT, val->value.i); +		break; +	case SWITCH_TYPE_STRING: +		if (!val->value.s) +			goto nla_put_failure; +		NLA_PUT_STRING(msg, SWITCH_ATTR_OP_VALUE_STR, val->value.s); +		break; +	case SWITCH_TYPE_PORTS: +		if (send_attr_ports(msg, val) < 0) +			goto nla_put_failure; +		break; +	default: +		goto nla_put_failure; +	} +	return 0; + +nla_put_failure: +	return -1; +} + +int +swlib_set_attr(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val) +{ +	int cmd; + +	switch(attr->atype) { +	case SWLIB_ATTR_GROUP_GLOBAL: +		cmd = SWITCH_CMD_SET_GLOBAL; +		break; +	case SWLIB_ATTR_GROUP_PORT: +		cmd = SWITCH_CMD_SET_PORT; +		break; +	case SWLIB_ATTR_GROUP_VLAN: +		cmd = SWITCH_CMD_SET_VLAN; +		break; +	default: +		return -EINVAL; +	} + +	val->attr = attr; +	return swlib_call(cmd, NULL, send_attr_val, val); +} + + +struct attrlist_arg { +	int id; +	int atype; +	struct switch_dev *dev; +	struct switch_attr *prev; +	struct switch_attr **head; +}; + +static int +add_id(struct nl_msg *msg, void *arg) +{ +	struct attrlist_arg *l = arg; + +	NLA_PUT_U32(msg, SWITCH_ATTR_ID, l->id); + +	return 0; +nla_put_failure: +	return -1; +} + +static int +add_attr(struct nl_msg *msg, void *ptr) +{ +	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); +	struct attrlist_arg *arg = ptr; +	struct switch_attr *new; + +	if (nla_parse(tb, SWITCH_ATTR_MAX - 1, genlmsg_attrdata(gnlh, 0), +			genlmsg_attrlen(gnlh, 0), NULL) < 0) +		goto done; + +	new = swlib_alloc(sizeof(struct switch_attr)); +	if (!new) +		goto done; + +	new->dev = arg->dev; +	new->atype = arg->atype; +	if (arg->prev) { +		arg->prev->next = new; +	} else { +		arg->prev = *arg->head; +	} +	*arg->head = new; +	arg->head = &new->next; + +	if (tb[SWITCH_ATTR_OP_ID]) +		new->id = nla_get_u32(tb[SWITCH_ATTR_OP_ID]); +	if (tb[SWITCH_ATTR_OP_TYPE]) +		new->type = nla_get_u32(tb[SWITCH_ATTR_OP_TYPE]); +	if (tb[SWITCH_ATTR_OP_NAME]) +		new->name = strdup(nla_get_string(tb[SWITCH_ATTR_OP_NAME])); +	if (tb[SWITCH_ATTR_OP_DESCRIPTION]) +		new->description = strdup(nla_get_string(tb[SWITCH_ATTR_OP_DESCRIPTION])); + +done: +	return NL_SKIP; +} + +int +swlib_scan(struct switch_dev *dev) +{ +	struct attrlist_arg arg; + +	if (dev->ops || dev->port_ops || dev->vlan_ops) +		return 0; + +	arg.atype = SWLIB_ATTR_GROUP_GLOBAL; +	arg.dev = dev; +	arg.id = dev->id; +	arg.prev = NULL; +	arg.head = &dev->ops; +	swlib_call(SWITCH_CMD_LIST_GLOBAL, add_attr, add_id, &arg); + +	arg.atype = SWLIB_ATTR_GROUP_PORT; +	arg.prev = NULL; +	arg.head = &dev->port_ops; +	swlib_call(SWITCH_CMD_LIST_PORT, add_attr, add_id, &arg); + +	arg.atype = SWLIB_ATTR_GROUP_VLAN; +	arg.prev = NULL; +	arg.head = &dev->vlan_ops; +	swlib_call(SWITCH_CMD_LIST_VLAN, add_attr, add_id, &arg); + +	return 0; +} + +struct switch_attr *swlib_lookup_attr(struct switch_dev *dev, +		enum swlib_attr_group atype, const char *name) +{ +	struct switch_attr *head; + +	if (!name || !dev) +		return NULL; + +	switch(atype) { +	case SWLIB_ATTR_GROUP_GLOBAL: +		head = dev->ops; +		break; +	case SWLIB_ATTR_GROUP_PORT: +		head = dev->port_ops; +		break; +	case SWLIB_ATTR_GROUP_VLAN: +		head = dev->vlan_ops; +		break; +	} +	while(head) { +		if (!strcmp(name, head->name)) +			return head; +		head = head->next; +	} + +	return NULL; +} + +static void +swlib_priv_free(void) +{ +	if (cache) +		nl_cache_free(cache); +	if (handle) +		nl_handle_destroy(handle); +	handle = NULL; +	cache = NULL; +} + +static int +swlib_priv_init(void) +{ +	handle = nl_handle_alloc(); +	if (!handle) { +		DPRINTF("Failed to create handle\n"); +		goto err; +	} + +	if (genl_connect(handle)) { +		DPRINTF("Failed to connect to generic netlink\n"); +		goto err; +	} + +	cache = genl_ctrl_alloc_cache(handle); +	if (!cache) { +		DPRINTF("Failed to allocate netlink cache\n"); +		goto err; +	} + +	family = genl_ctrl_search_by_name(cache, "switch"); +	if (!family) { +		DPRINTF("Switch API not present\n"); +		goto err; +	} +	return 0; + +err: +	swlib_priv_free(); +	return -EINVAL; +} + +struct swlib_scan_arg { +	const char *name; +	struct switch_dev *head; +	struct switch_dev *ptr; +}; + +static int +add_switch(struct nl_msg *msg, void *arg) +{ +	struct swlib_scan_arg *sa = arg; +	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); +	struct switch_dev *dev; +	const char *name; + +	if (nla_parse(tb, SWITCH_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0) +		goto done; + +	if (!tb[SWITCH_ATTR_DEV_NAME]) +		goto done; + +	name = nla_get_string(tb[SWITCH_ATTR_DEV_NAME]); +	if (sa->name && (strcmp(name, sa->name) != 0)) +		goto done; + +	dev = swlib_alloc(sizeof(struct switch_dev)); +	if (!dev) +		goto done; + +	dev->dev_name = strdup(name); +	if (tb[SWITCH_ATTR_ID]) +		dev->id = nla_get_u32(tb[SWITCH_ATTR_ID]); +	if (tb[SWITCH_ATTR_NAME]) +		dev->name = strdup(nla_get_string(tb[SWITCH_ATTR_DEV_NAME])); +	if (tb[SWITCH_ATTR_PORTS]) +		dev->ports = nla_get_u32(tb[SWITCH_ATTR_PORTS]); +	if (tb[SWITCH_ATTR_VLANS]) +		dev->vlans = nla_get_u32(tb[SWITCH_ATTR_VLANS]); + +	if (!sa->head) { +		sa->head = dev; +		sa->ptr = dev; +	} else { +		sa->ptr->next = dev; +		sa->ptr = dev; +	} + +	refcount++; +done: +	return NL_SKIP; +} + + +struct switch_dev * +swlib_connect(const char *name) +{ +	struct swlib_scan_arg arg; +	int err; + +	if (!refcount) { +		if (swlib_priv_init() < 0) +			return NULL; +	}; + +	arg.head = NULL; +	arg.ptr = NULL; +	arg.name = name; +	swlib_call(SWITCH_CMD_GET_SWITCH, add_switch, NULL, &arg); + +	if (!refcount) +		swlib_priv_free(); + +	return arg.head; +} + +static void +swlib_free_attributes(struct switch_attr **head) +{ +	struct switch_attr *a = *head; +	struct switch_attr *next; + +	while (a) { +		next = a->next; +		free(a); +		a = next; +	} +	*head = NULL; +} + +void +swlib_free(struct switch_dev *dev) +{ +	swlib_free_attributes(&dev->ops); +	swlib_free_attributes(&dev->port_ops); +	swlib_free_attributes(&dev->vlan_ops); +	free(dev); + +	if (--refcount == 0) +		swlib_priv_free(); +} + +void +swlib_free_all(struct switch_dev *dev) +{ +	struct switch_dev *p; + +	while (dev) { +		p = dev->next; +		swlib_free(dev); +		dev = p; +	} +} diff --git a/package/swconfig/src/swlib.h b/package/swconfig/src/swlib.h new file mode 100644 index 000000000..e00ff47a5 --- /dev/null +++ b/package/swconfig/src/swlib.h @@ -0,0 +1,213 @@ +/* + * swlib.h: Switch configuration API (user space part) + * + * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + +Usage of the library functions: + +  The main datastructure for a switch is the struct switch_device +  To get started, you first need to use switch_connect() to probe +  for switches and allocate an instance of this struct. + +  There are two possible usage modes: +    dev = switch_connect("eth0"); +      - this call will look for a switch registered for the linux device +  	  "eth0" and only allocate a switch_device for this particular switch. + +    dev = switch_connect(NULL) +      - this will return one switch_device struct for each available +  	  switch. The switch_device structs are chained with by ->next pointer + +  Then to query a switch for all available attributes, use: +    swlib_scan(dev); + +  All allocated datastructures for the switch_device struct can be freed with +    swlib_free(dev); +  or +    swlib_free_all(dev); + +  The latter traverses a whole chain of switch_device structs and frees them all + +  Switch attributes (struct switch_attr) are divided into three groups: +    dev->ops: +      - global settings +    dev->port_ops: +      - per-port settings +    dev->vlan_ops: +      - per-vlan settings + +  switch_lookup_attr() is a small helper function to locate attributes +  by name. + +  switch_set_attr() and switch_get_attr() can alter or request the values +  of attributes. + +Usage of the switch_attr struct: + +  ->atype: attribute group, one of: +    - SWLIB_ATTR_GROUP_GLOBAL +    - SWLIB_ATTR_GROUP_VLAN +    - SWLIB_ATTR_GROUP_PORT + +  ->id: identifier for the attribute + +  ->type: data type, one of: +    - SWITCH_TYPE_INT +    - SWITCH_TYPE_STRING +    - SWITCH_TYPE_PORT + +  ->name: short name of the attribute +  ->description: longer description +  ->next: pointer to the next attribute of the current group + + +Usage of the switch_val struct: + +  When setting attributes, following members of the struct switch_val need +  to be set up: + +    ->len (for attr->type == SWITCH_TYPE_PORT) +    ->port_vlan: +      - port number (for attr->atype == SWLIB_ATTR_GROUP_PORT), or: +      - vlan number (for attr->atype == SWLIB_ATTR_GROUP_VLAN) +    ->value.i (for attr->type == SWITCH_TYPE_INT) +    ->value.s (for attr->type == SWITCH_TYPE_STRING) +      - owned by the caller, not stored in the library internally +    ->value.ports (for attr->type == SWITCH_TYPE_PORT) +      - must point to an array of at lest val->len * sizeof(struct switch_port) + +  When getting string attributes, val->value.s must be freed by the caller +  When getting port list attributes, an internal static buffer is used, +  which changes from call to call. + + */ + +#ifndef __SWLIB_H +#define __SWLIB_H + +enum swlib_attr_group { +	SWLIB_ATTR_GROUP_GLOBAL, +	SWLIB_ATTR_GROUP_VLAN, +	SWLIB_ATTR_GROUP_PORT, +}; + +enum swlib_port_flags { +	SWLIB_PORT_FLAG_TAGGED = (1 << 0), +}; + + +struct switch_dev; +struct switch_attr; +struct switch_port; +struct switch_val; + +struct switch_dev { +	int id; +	const char *name; +	const char *dev_name; +	int ports; +	int vlans; +	struct switch_attr *ops; +	struct switch_attr *port_ops; +	struct switch_attr *vlan_ops; +	struct switch_dev *next; +	void *priv; +}; + +struct switch_val { +	struct switch_attr *attr; +	int len; +	int err; +	int port_vlan; +	union { +		const char *s; +		int i; +		struct switch_port *ports; +	} value; +}; + +struct switch_attr { +	struct switch_dev *dev; +	int atype; +	int id; +	int type; +	const char *name; +	const char *description; +	struct switch_attr *next; +}; + +struct switch_port { +	unsigned int id; +	unsigned int flags; +}; + +/** + * swlib_connect: connect to the switch through netlink + * @name: name of the ethernet interface, + * + * if name is NULL, it connect and builds a chain of all switches + */ +struct switch_dev *swlib_connect(const char *name); + +/** + * swlib_free: free all dynamically allocated data for the switch connection + * @dev: switch device struct + * + * all members of a switch device chain (generated by swlib_connect(NULL)) + * must be freed individually + */ +void swlib_free(struct switch_dev *dev); + +/** + * swlib_free_all: run swlib_free on all devices in the chain + * @dev: switch device struct + */ +void swlib_free_all(struct switch_dev *dev); + +/** + * swlib_scan: probe the switch driver for available commands/attributes + * @dev: switch device struct + */ +int swlib_scan(struct switch_dev *dev); + +/** + * swlib_lookup_attr: look up a switch attribute + * @dev: switch device struct + * @type: global, port or vlan + * @name: name of the attribute + */ +struct switch_attr *swlib_lookup_attr(struct switch_dev *dev, +		enum swlib_attr_group atype, const char *name); + +/** + * swlib_set_attr: set the value for an attribute + * @dev: switch device struct + * @attr: switch attribute struct + * @val: attribute value pointer + * returns 0 on success + */ +int swlib_set_attr(struct switch_dev *dev, struct switch_attr *attr, +		struct switch_val *val); + +/** + * swlib_get_attr: get the value for an attribute + * @dev: switch device struct + * @attr: switch attribute struct + * @val: attribute value pointer + * returns 0 on success + * for string attributes, the result string must be freed by the caller + */ +int swlib_get_attr(struct switch_dev *dev, struct switch_attr *attr, +		struct switch_val *val); + +#endif diff --git a/target/linux/generic-2.6/config-2.6.26 b/target/linux/generic-2.6/config-2.6.26 index 5335c2a34..7aa4bb750 100644 --- a/target/linux/generic-2.6/config-2.6.26 +++ b/target/linux/generic-2.6/config-2.6.26 @@ -1538,6 +1538,7 @@ CONFIG_SUNRPC_GSS=m  # CONFIG_SUN_PARTITION is not set  CONFIG_SUSPEND_UP_POSSIBLE=y  CONFIG_SWAP=y +# CONFIG_SWCONFIG is not set  # CONFIG_SYNCLINK_CS is not set  CONFIG_SYN_COOKIES=y  CONFIG_SYSCTL=y diff --git a/target/linux/generic-2.6/config-2.6.27 b/target/linux/generic-2.6/config-2.6.27 index 198b62a61..7b2a4b603 100644 --- a/target/linux/generic-2.6/config-2.6.27 +++ b/target/linux/generic-2.6/config-2.6.27 @@ -1580,6 +1580,7 @@ CONFIG_SUNRPC_GSS=m  # CONFIG_SUN_PARTITION is not set  CONFIG_SUSPEND_UP_POSSIBLE=y  CONFIG_SWAP=y +# CONFIG_SWCONFIG is not set  # CONFIG_SYNCLINK_CS is not set  CONFIG_SYN_COOKIES=y  CONFIG_SYSCTL=y diff --git a/target/linux/generic-2.6/files-2.6.26/drivers/net/phy/swconfig.c b/target/linux/generic-2.6/files-2.6.26/drivers/net/phy/swconfig.c new file mode 100644 index 000000000..ade28429d --- /dev/null +++ b/target/linux/generic-2.6/files-2.6.26/drivers/net/phy/swconfig.c @@ -0,0 +1,872 @@ +/* + * swconfig.c: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> + * + * This program 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/if.h> +#include <linux/if_ether.h> +#include <linux/capability.h> +#include <linux/skbuff.h> +#include <linux/switch.h> + +//#define DEBUG 1 +#ifdef DEBUG +#define DPRINTF(format, ...) printk("%s: " format, __func__, ##__VA_ARGS__) +#else +#define DPRINTF(...) do {} while(0) +#endif + +MODULE_AUTHOR("Felix Fietkau <nbd@openwrt.org>"); +MODULE_LICENSE("GPL"); + +static int swdev_id = 0; +static struct list_head swdevs; +static spinlock_t swdevs_lock = SPIN_LOCK_UNLOCKED; +struct swconfig_callback; + +struct swconfig_callback +{ +	struct sk_buff *msg; +	struct genlmsghdr *hdr; +	struct genl_info *info; +	int cmd; + +	/* callback for filling in the message data */ +	int (*fill)(struct swconfig_callback *cb, void *arg); + +	/* callback for closing the message before sending it */ +	int (*close)(struct swconfig_callback *cb, void *arg); + +	struct nlattr *nest[4]; +	int args[4]; +}; + +/* defaults */ + +static int +swconfig_get_vlan_ports(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val) +{ +	int ret; +	if (val->port_vlan >= dev->vlans) +		return -EINVAL; + +	if (!dev->get_vlan_ports) +		return -EOPNOTSUPP; + +	ret = dev->get_vlan_ports(dev, val); +	printk("SET PORTS %d\n", val->len); +	return ret; +} + +static int +swconfig_set_vlan_ports(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val) +{ +	int i; + +	if (val->port_vlan >= dev->vlans) +		return -EINVAL; + +	/* validate ports */ +	if (val->len > dev->ports) +		return -EINVAL; + +	for (i = 0; i < val->len; i++) { +		if (val->value.ports[i].id >= dev->ports) +			return -EINVAL; +	} + +	if (!dev->set_vlan_ports) +		return -EOPNOTSUPP; + +	printk("SET PORTS %d\n", val->len); +	return dev->set_vlan_ports(dev, val); +} + +static int +swconfig_apply_config(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val) +{ +	/* don't complain if not supported by the switch driver */ +	if (!dev->apply_config) +		return 0; + +	return dev->apply_config(dev); +} + + +enum global_defaults { +	GLOBAL_APPLY, +}; + +enum vlan_defaults { +	VLAN_PORTS, +}; + +enum port_defaults { +	PORT_LINK, +}; + +static struct switch_attr default_global[] = { +	[GLOBAL_APPLY] = { +		.type = SWITCH_TYPE_NOVAL, +		.name = "apply", +		.description = "Activate changes in the hardware", +		.set = swconfig_apply_config, +	} +}; + +static struct switch_attr default_port[] = { +	[PORT_LINK] = { +		.type = SWITCH_TYPE_INT, +		.name = "link", +		.description = "Current link speed", +	} +}; + +static struct switch_attr default_vlan[] = { +	[VLAN_PORTS] = { +		.type = SWITCH_TYPE_PORTS, +		.name = "ports", +		.description = "VLAN port mapping", +		.set = swconfig_set_vlan_ports, +		.get = swconfig_get_vlan_ports, +	}, +}; + + +static void swconfig_defaults_init(struct switch_dev *dev) +{ +	dev->def_global = 0; +	dev->def_vlan = 0; +	dev->def_port = 0; + +	if (dev->get_vlan_ports || dev->set_vlan_ports) +		set_bit(VLAN_PORTS, &dev->def_vlan); + +	/* always present, can be no-op */ +	set_bit(GLOBAL_APPLY, &dev->def_global); +} + + +static struct genl_family switch_fam = { +	.id = GENL_ID_GENERATE, +	.name = "switch", +	.hdrsize = 0, +	.version = 1, +	.maxattr = SWITCH_ATTR_MAX, +}; + +static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = { +	[SWITCH_ATTR_ID] = { .type = NLA_U32 }, +	[SWITCH_ATTR_OP_ID] = { .type = NLA_U32 }, +	[SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 }, +	[SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 }, +	[SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 }, +	[SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING }, +	[SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED }, +	[SWITCH_ATTR_TYPE] = { .type = NLA_U32 }, +}; + +static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = { +	[SWITCH_PORT_ID] = { .type = NLA_U32 }, +	[SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG }, +}; + +static inline void +swconfig_lock(void) +{ +	spin_lock(&swdevs_lock); +} + +static inline void +swconfig_unlock(void) +{ +	spin_unlock(&swdevs_lock); +} + +static struct switch_dev * +swconfig_get_dev(struct genl_info *info) +{ +	struct switch_dev *dev = NULL; +	struct switch_dev *p; +	int id; + +	if (!info->attrs[SWITCH_ATTR_ID]) +		goto done; + +	id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]); +	swconfig_lock(); +	list_for_each_entry(p, &swdevs, dev_list) { +		if (id != p->id) +			continue; + +		dev = p; +		break; +	} +	if (dev) +		spin_lock(&dev->lock); +	else +		DPRINTF("device %d not found\n", id); +	swconfig_unlock(); +done: +	return dev; +} + +static inline void +swconfig_put_dev(struct switch_dev *dev) +{ +	spin_unlock(&dev->lock); +} + +static int +swconfig_dump_attr(struct swconfig_callback *cb, void *arg) +{ +	struct switch_attr *op = arg; +	struct genl_info *info = cb->info; +	struct sk_buff *msg = cb->msg; +	int id = cb->args[0]; +	void *hdr; + +	hdr = genlmsg_put(msg, info->snd_pid, info->snd_seq, &switch_fam, +			NLM_F_MULTI, SWITCH_CMD_NEW_ATTR); +	if (IS_ERR(hdr)) +		return -1; + +	NLA_PUT_U32(msg, SWITCH_ATTR_OP_ID, id); +	NLA_PUT_U32(msg, SWITCH_ATTR_OP_TYPE, op->type); +	NLA_PUT_STRING(msg, SWITCH_ATTR_OP_NAME, op->name); +	if (op->description) +		NLA_PUT_STRING(msg, SWITCH_ATTR_OP_DESCRIPTION, +			op->description); + +	return genlmsg_end(msg, hdr); +nla_put_failure: +	genlmsg_cancel(msg, hdr); +	return -EMSGSIZE; +} + +/* spread multipart messages across multiple message buffers */ +static int +swconfig_send_multipart(struct swconfig_callback *cb, void *arg) +{ +	struct genl_info *info = cb->info; +	int restart = 0; +	int err; + +	do { +		if (!cb->msg) { +			cb->msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); +			if (cb->msg == NULL) +				goto error; +		} + +		if (!(cb->fill(cb, arg) < 0)) +			break; + +		/* fill failed, check if this was already the second attempt */ +		if (restart) +			goto error; + +		/* try again in a new message, send the current one */ +		restart = 1; +		if (cb->close) { +			if (cb->close(cb, arg) < 0) +				goto error; +		} +		err = genlmsg_unicast(cb->msg, info->snd_pid); +		cb->msg = NULL; +		if (err < 0) +			goto error; + +	} while (restart); + +	return 0; + +error: +	if (cb->msg) +		nlmsg_free(cb->msg); +	return -1; +} + +static int +swconfig_list_attrs(struct sk_buff *skb, struct genl_info *info) +{ +	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); +	const struct switch_attrlist *alist; +	struct switch_dev *dev; +	struct swconfig_callback cb; +	int err = -EINVAL; +	int i; + +	/* defaults */ +	struct switch_attr *def_list; +	unsigned long *def_active; +	int n_def; + +	dev = swconfig_get_dev(info); +	if (!dev) +		return -EINVAL; + +	switch(hdr->cmd) { +	case SWITCH_CMD_LIST_GLOBAL: +		alist = &dev->attr_global; +		def_list = default_global; +		def_active = &dev->def_global; +		n_def = ARRAY_SIZE(default_global); +		break; +	case SWITCH_CMD_LIST_VLAN: +		alist = &dev->attr_vlan; +		def_list = default_vlan; +		def_active = &dev->def_vlan; +		n_def = ARRAY_SIZE(default_vlan); +		break; +	case SWITCH_CMD_LIST_PORT: +		alist = &dev->attr_port; +		def_list = default_port; +		def_active = &dev->def_port; +		n_def = ARRAY_SIZE(default_port); +		break; +	default: +		WARN_ON(1); +		goto out; +	} + +	memset(&cb, 0, sizeof(cb)); +	cb.info = info; +	cb.fill = swconfig_dump_attr; +	for (i = 0; i < alist->n_attr; i++) { +		if (alist->attr[i].disabled) +			continue; +		cb.args[0] = i; +		err = swconfig_send_multipart(&cb, &alist->attr[i]); +		if (err < 0) +			goto error; +	} + +	/* defaults */ +	for (i = 0; i < n_def; i++) { +		if (!test_bit(i, def_active)) +			continue; +		cb.args[0] = SWITCH_ATTR_DEFAULTS_OFFSET + i; +		err = swconfig_send_multipart(&cb, &def_list[i]); +		if (err < 0) +			goto error; +	} +	swconfig_put_dev(dev); + +	if (!cb.msg) +		return 0; + +	return genlmsg_unicast(cb.msg, info->snd_pid); + +error: +	if (cb.msg) +		nlmsg_free(cb.msg); +out: +	swconfig_put_dev(dev); +	return err; +} + +static struct switch_attr * +swconfig_lookup_attr(struct switch_dev *dev, struct genl_info *info, +		struct switch_val *val) +{ +	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); +	const struct switch_attrlist *alist; +	struct switch_attr *attr = NULL; +	int attr_id; + +	/* defaults */ +	struct switch_attr *def_list; +	unsigned long *def_active; +	int n_def; + +	if (!info->attrs[SWITCH_ATTR_OP_ID]) +		goto done; + +	switch(hdr->cmd) { +	case SWITCH_CMD_SET_GLOBAL: +	case SWITCH_CMD_GET_GLOBAL: +		alist = &dev->attr_global; +		def_list = default_global; +		def_active = &dev->def_global; +		n_def = ARRAY_SIZE(default_global); +		break; +	case SWITCH_CMD_SET_VLAN: +	case SWITCH_CMD_GET_VLAN: +		alist = &dev->attr_vlan; +		def_list = default_vlan; +		def_active = &dev->def_vlan; +		n_def = ARRAY_SIZE(default_vlan); +		if (!info->attrs[SWITCH_ATTR_OP_VLAN]) +			goto done; +		val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_VLAN]); +		break; +	case SWITCH_CMD_SET_PORT: +	case SWITCH_CMD_GET_PORT: +		alist = &dev->attr_port; +		def_list = default_port; +		def_active = &dev->def_port; +		n_def = ARRAY_SIZE(default_port); +		if (!info->attrs[SWITCH_ATTR_OP_PORT]) +			goto done; +		val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_PORT]); +		break; +	default: +		WARN_ON(1); +		goto done; +	} + +	if (!alist) +		goto done; + +	attr_id = nla_get_u32(info->attrs[SWITCH_ATTR_OP_ID]); +	if (attr_id >= SWITCH_ATTR_DEFAULTS_OFFSET) { +		attr_id -= SWITCH_ATTR_DEFAULTS_OFFSET; +		if (attr_id >= n_def) +			goto done; +		if (!test_bit(attr_id, def_active)) +			goto done; +		attr = &def_list[attr_id]; +	} else { +		if (attr_id >= alist->n_attr) +			goto done; +		attr = &alist->attr[attr_id]; +	} + +	if (attr->disabled) +		attr = NULL; + +done: +	if (!attr) +		DPRINTF("attribute lookup failed\n"); +	val->attr = attr; +	return attr; +} + +static int +swconfig_parse_ports(struct sk_buff *msg, struct nlattr *head, +		struct switch_val *val, int max) +{ +	struct nlattr *nla; +	int rem; + +	val->len = 0; +	nla_for_each_nested(nla, head, rem) { +		struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1]; +		struct switch_port *port = &val->value.ports[val->len]; + +		if (val->len >= max) +			return -EINVAL; + +		if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, +				port_policy)) +			return -EINVAL; + +		if (!tb[SWITCH_PORT_ID]) +			return -EINVAL; + +		port->id = nla_get_u32(tb[SWITCH_PORT_ID]); +		if (tb[SWITCH_PORT_FLAG_TAGGED]) +			port->flags |= (1 << SWITCH_PORT_FLAG_TAGGED); +		val->len++; +	} + +	return 0; +} + +static int +swconfig_set_attr(struct sk_buff *skb, struct genl_info *info) +{ +	struct switch_attr *attr; +	struct switch_dev *dev; +	struct switch_val val; +	int err = -EINVAL; + +	dev = swconfig_get_dev(info); +	if (!dev) +		return -EINVAL; + +	memset(&val, 0, sizeof(val)); +	attr = swconfig_lookup_attr(dev, info, &val); +	if (!attr || !attr->set) +		goto error; + +	val.attr = attr; +	switch(attr->type) { +	case SWITCH_TYPE_NOVAL: +		break; +	case SWITCH_TYPE_INT: +		if (!info->attrs[SWITCH_ATTR_OP_VALUE_INT]) +			goto error; +		val.value.i = +			nla_get_u32(info->attrs[SWITCH_ATTR_OP_VALUE_INT]); +		break; +	case SWITCH_TYPE_STRING: +		if (!info->attrs[SWITCH_ATTR_OP_VALUE_STR]) +			goto error; +		val.value.s = +			nla_data(info->attrs[SWITCH_ATTR_OP_VALUE_STR]); +		break; +	case SWITCH_TYPE_PORTS: +		val.value.ports = dev->portbuf; +		memset(dev->portbuf, 0, +			sizeof(struct switch_port) * dev->ports); + +		/* TODO: implement multipart? */ +		if (info->attrs[SWITCH_ATTR_OP_VALUE_PORTS]) { +			err = swconfig_parse_ports(skb, +				info->attrs[SWITCH_ATTR_OP_VALUE_PORTS], &val, dev->ports); +			if (err < 0) +				goto error; +		} else { +			val.len = 0; +			err = 0; +		} +		break; +	default: +		goto error; +	} + +	err = attr->set(dev, attr, &val); +error: +	swconfig_put_dev(dev); +	return err; +} + +static int +swconfig_close_portlist(struct swconfig_callback *cb, void *arg) +{ +	if (cb->nest[0]) +		nla_nest_end(cb->msg, cb->nest[0]); +	return 0; +} + +static int +swconfig_send_port(struct swconfig_callback *cb, void *arg) +{ +	const struct switch_port *port = arg; +	struct nlattr *p = NULL; + +	if (!cb->nest[0]) { +		cb->nest[0] = nla_nest_start(cb->msg, cb->cmd); +		if (!cb->nest[0]) +			return -1; +	} + +	p = nla_nest_start(cb->msg, SWITCH_ATTR_PORT); +	if (!p) +		goto error; + +	NLA_PUT_U32(cb->msg, SWITCH_PORT_ID, port->id); +	if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) +		NLA_PUT_FLAG(cb->msg, SWITCH_PORT_FLAG_TAGGED); + +	nla_nest_end(cb->msg, p); +	return 0; + +nla_put_failure: +		nla_nest_cancel(cb->msg, p); +error: +	nla_nest_cancel(cb->msg, cb->nest[0]); +	return -1; +} + +static int +swconfig_send_ports(struct sk_buff **msg, struct genl_info *info, int attr, +		const struct switch_val *val) +{ +	struct swconfig_callback cb; +	int err = 0; +	int i; + +	if (!val->value.ports) +		return -EINVAL; + +	memset(&cb, 0, sizeof(cb)); +	cb.cmd = attr; +	cb.msg = *msg; +	cb.info = info; +	cb.fill = swconfig_send_port; +	cb.close = swconfig_close_portlist; + +	cb.nest[0] = nla_nest_start(cb.msg, cb.cmd); +	for (i = 0; i < val->len; i++) { +		err = swconfig_send_multipart(&cb, &val->value.ports[i]); +		if (err) +			goto done; +	} +	err = val->len; +	swconfig_close_portlist(&cb, NULL); +	*msg = cb.msg; + +done: +	return err; +} + +static int +swconfig_get_attr(struct sk_buff *skb, struct genl_info *info) +{ +	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); +	struct switch_attr *attr; +	struct switch_dev *dev; +	struct sk_buff *msg = NULL; +	struct switch_val val; +	int err = -EINVAL; +	int cmd = hdr->cmd; + +	dev = swconfig_get_dev(info); +	if (!dev) +		return -EINVAL; + +	memset(&val, 0, sizeof(val)); +	attr = swconfig_lookup_attr(dev, info, &val); +	if (!attr || !attr->get) +		goto error_dev; + +	if (attr->type == SWITCH_TYPE_PORTS) { +		val.value.ports = dev->portbuf; +		memset(dev->portbuf, 0, +			sizeof(struct switch_port) * dev->ports); +	} + +	err = attr->get(dev, attr, &val); +	if (err) +		goto error; + +	msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); +	if (!msg) +		goto error; + +	hdr = genlmsg_put(msg, info->snd_pid, info->snd_seq, &switch_fam, +			0, cmd); +	if (IS_ERR(hdr)) +		goto nla_put_failure; + +	switch(attr->type) { +	case SWITCH_TYPE_INT: +		NLA_PUT_U32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i); +		break; +	case SWITCH_TYPE_STRING: +		NLA_PUT_STRING(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s); +		break; +	case SWITCH_TYPE_PORTS: +		err = swconfig_send_ports(&msg, info, +				SWITCH_ATTR_OP_VALUE_PORTS, &val); +		if (err < 0) +			goto nla_put_failure; +		break; +	default: +		DPRINTF("invalid type in attribute\n"); +		err = -EINVAL; +		goto error; +	} +	err = genlmsg_end(msg, hdr); +	if (err < 0) +		goto nla_put_failure; + +	swconfig_put_dev(dev); +	return genlmsg_unicast(msg, info->snd_pid); + +nla_put_failure: +	if (msg) +		nlmsg_free(msg); +error_dev: +	swconfig_put_dev(dev); +error: +	if (!err) +		err = -ENOMEM; +	return err; +} + +static int +swconfig_send_switch(struct sk_buff *msg, u32 pid, u32 seq, int flags, +		const struct switch_dev *dev) +{ +	void *hdr; + +	hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags, +			SWITCH_CMD_NEW_ATTR); +	if (IS_ERR(hdr)) +		return -1; + +	NLA_PUT_U32(msg, SWITCH_ATTR_ID, dev->id); +	NLA_PUT_STRING(msg, SWITCH_ATTR_NAME, dev->name); +	NLA_PUT_STRING(msg, SWITCH_ATTR_DEV_NAME, dev->devname); +	NLA_PUT_U32(msg, SWITCH_ATTR_VLANS, dev->vlans); +	NLA_PUT_U32(msg, SWITCH_ATTR_PORTS, dev->ports); + +	return genlmsg_end(msg, hdr); +nla_put_failure: +	genlmsg_cancel(msg, hdr); +	return -EMSGSIZE; +} + +static int swconfig_dump_switches(struct sk_buff *skb, +		struct netlink_callback *cb) +{ +	struct switch_dev *dev; +	int start = cb->args[0]; +	int idx = 0; + +	swconfig_lock(); +	list_for_each_entry(dev, &swdevs, dev_list) { +		if (++idx <= start) +			continue; +		if (swconfig_send_switch(skb, NETLINK_CB(cb->skb).pid, +				cb->nlh->nlmsg_seq, NLM_F_MULTI, +				dev) < 0) +			break; +	} +	swconfig_unlock(); +	cb->args[0] = idx; + +	return skb->len; +} + +static int +swconfig_done(struct netlink_callback *cb) +{ +	return 0; +} + +static struct genl_ops swconfig_ops[] = { +	{ +		.cmd = SWITCH_CMD_LIST_GLOBAL, +		.doit = swconfig_list_attrs, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_LIST_VLAN, +		.doit = swconfig_list_attrs, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_LIST_PORT, +		.doit = swconfig_list_attrs, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_GET_GLOBAL, +		.doit = swconfig_get_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_GET_VLAN, +		.doit = swconfig_get_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_GET_PORT, +		.doit = swconfig_get_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_SET_GLOBAL, +		.doit = swconfig_set_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_SET_VLAN, +		.doit = swconfig_set_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_SET_PORT, +		.doit = swconfig_set_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_GET_SWITCH, +		.dumpit = swconfig_dump_switches, +		.policy = switch_policy, +		.done = swconfig_done, +	} +}; + +int +register_switch(struct switch_dev *dev, struct net_device *netdev) +{ +	INIT_LIST_HEAD(&dev->dev_list); +	if (netdev) { +		dev->netdev = netdev; +		if (!dev->devname) +			dev->devname = netdev->name; +	} +	BUG_ON(!dev->devname); + +	if (dev->ports > 0) { +		dev->portbuf = kzalloc(sizeof(struct switch_port) * dev->ports, +				GFP_KERNEL); +		if (!dev->portbuf) +			return -ENOMEM; +	} +	dev->id = ++swdev_id; +	swconfig_defaults_init(dev); +	spin_lock_init(&dev->lock); +	swconfig_lock(); +	list_add(&dev->dev_list, &swdevs); +	swconfig_unlock(); + +	return 0; +} +EXPORT_SYMBOL_GPL(register_switch); + +void +unregister_switch(struct switch_dev *dev) +{ +	kfree(dev->portbuf); +	spin_lock(&dev->lock); +	swconfig_lock(); +	list_del(&dev->dev_list); +	swconfig_unlock(); +} +EXPORT_SYMBOL_GPL(unregister_switch); + + +static int __init +swconfig_init(void) +{ +	int i, err; + +	INIT_LIST_HEAD(&swdevs); +	err = genl_register_family(&switch_fam); +	if (err) +		return err; + +	for (i = 0; i < ARRAY_SIZE(swconfig_ops); i++) { +		err = genl_register_ops(&switch_fam, &swconfig_ops[i]); +		if (err) +			goto unregister; +	} + +	return 0; + +unregister: +	genl_unregister_family(&switch_fam); +	return err; +} + +static void __exit +swconfig_exit(void) +{ +	genl_unregister_family(&switch_fam); +} + +module_init(swconfig_init); +module_exit(swconfig_exit); + diff --git a/target/linux/generic-2.6/files-2.6.26/include/linux/switch.h b/target/linux/generic-2.6/files-2.6.26/include/linux/switch.h new file mode 100644 index 000000000..9411e84bc --- /dev/null +++ b/target/linux/generic-2.6/files-2.6.26/include/linux/switch.h @@ -0,0 +1,168 @@ +/* + * switch.h: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> + * + * This program 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#ifndef __LINUX_SWITCH_H +#define __LINUX_SWITCH_H + +#include <linux/types.h> +#include <linux/netdevice.h> +#include <linux/netlink.h> +#include <linux/genetlink.h> +#ifndef __KERNEL__ +#include <netlink/netlink.h> +#include <netlink/genl/genl.h> +#include <netlink/genl/ctrl.h> +#else +#include <net/genetlink.h> +#endif + +/* main attributes */ +enum { +	SWITCH_ATTR_UNSPEC, +	/* global */ +	SWITCH_ATTR_TYPE, +	/* device */ +	SWITCH_ATTR_ID, +	SWITCH_ATTR_NAME, +	SWITCH_ATTR_DEV_NAME, +	SWITCH_ATTR_VLANS, +	SWITCH_ATTR_PORTS, +	/* attributes */ +	SWITCH_ATTR_OP_ID, +	SWITCH_ATTR_OP_TYPE, +	SWITCH_ATTR_OP_NAME, +	SWITCH_ATTR_OP_PORT, +	SWITCH_ATTR_OP_VLAN, +	SWITCH_ATTR_OP_VALUE_INT, +	SWITCH_ATTR_OP_VALUE_STR, +	SWITCH_ATTR_OP_VALUE_PORTS, +	SWITCH_ATTR_OP_DESCRIPTION, +	/* port lists */ +	SWITCH_ATTR_PORT, +	SWITCH_ATTR_MAX +}; + +/* commands */ +enum { +	SWITCH_CMD_UNSPEC, +	SWITCH_CMD_GET_SWITCH, +	SWITCH_CMD_NEW_ATTR, +	SWITCH_CMD_LIST_GLOBAL, +	SWITCH_CMD_GET_GLOBAL, +	SWITCH_CMD_SET_GLOBAL, +	SWITCH_CMD_LIST_PORT, +	SWITCH_CMD_GET_PORT, +	SWITCH_CMD_SET_PORT, +	SWITCH_CMD_LIST_VLAN, +	SWITCH_CMD_GET_VLAN, +	SWITCH_CMD_SET_VLAN +}; + +/* data types */ +enum switch_val_type { +	SWITCH_TYPE_UNSPEC, +	SWITCH_TYPE_INT, +	SWITCH_TYPE_STRING, +	SWITCH_TYPE_PORTS, +	SWITCH_TYPE_NOVAL, +}; + +/* port nested attributes */ +enum { +	SWITCH_PORT_UNSPEC, +	SWITCH_PORT_ID, +	SWITCH_PORT_FLAG_TAGGED, +	SWITCH_PORT_ATTR_MAX +}; + +#define SWITCH_ATTR_DEFAULTS_OFFSET	0x1000 + +#ifdef __KERNEL__ + +struct switch_dev; +struct switch_op; +struct switch_val; +struct switch_attr; +struct switch_attrlist; + +int register_switch(struct switch_dev *dev, struct net_device *netdev); +void unregister_switch(struct switch_dev *dev); + +struct switch_attrlist { +	/* filled in by the driver */ +	int n_attr; +	struct switch_attr *attr; +}; + + +struct switch_dev { +	int id; +	void *priv; +	const char *name; + +	/* NB: either devname or netdev must be set */ +	const char *devname; +	struct net_device *netdev; + +	int ports; +	int vlans; +	int cpu_port; +	struct switch_attrlist attr_global, attr_port, attr_vlan; + +	spinlock_t lock; +	struct switch_port *portbuf; +	struct list_head dev_list; +	unsigned long def_global, def_port, def_vlan; + +	int (*get_vlan_ports)(struct switch_dev *dev, struct switch_val *val); +	int (*set_vlan_ports)(struct switch_dev *dev, struct switch_val *val); +	int (*apply_config)(struct switch_dev *dev); +}; + +struct switch_port { +	u32 id; +	u32 flags; +}; + +struct switch_val { +	struct switch_attr *attr; +	int port_vlan; +	int len; +	union { +		const char *s; +		u32 i; +		struct switch_port *ports; +	} value; +}; + +struct switch_attr { +	int disabled; +	int type; +	const char *name; +	const char *description; + +	int (*set)(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val); +	int (*get)(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val); + +	/* for driver internal use */ +	int id; +	int ofs; +	int max; +}; + +#endif + +#endif diff --git a/target/linux/generic-2.6/files-2.6.27/drivers/net/phy/swconfig.c b/target/linux/generic-2.6/files-2.6.27/drivers/net/phy/swconfig.c new file mode 100644 index 000000000..ade28429d --- /dev/null +++ b/target/linux/generic-2.6/files-2.6.27/drivers/net/phy/swconfig.c @@ -0,0 +1,872 @@ +/* + * swconfig.c: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> + * + * This program 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/if.h> +#include <linux/if_ether.h> +#include <linux/capability.h> +#include <linux/skbuff.h> +#include <linux/switch.h> + +//#define DEBUG 1 +#ifdef DEBUG +#define DPRINTF(format, ...) printk("%s: " format, __func__, ##__VA_ARGS__) +#else +#define DPRINTF(...) do {} while(0) +#endif + +MODULE_AUTHOR("Felix Fietkau <nbd@openwrt.org>"); +MODULE_LICENSE("GPL"); + +static int swdev_id = 0; +static struct list_head swdevs; +static spinlock_t swdevs_lock = SPIN_LOCK_UNLOCKED; +struct swconfig_callback; + +struct swconfig_callback +{ +	struct sk_buff *msg; +	struct genlmsghdr *hdr; +	struct genl_info *info; +	int cmd; + +	/* callback for filling in the message data */ +	int (*fill)(struct swconfig_callback *cb, void *arg); + +	/* callback for closing the message before sending it */ +	int (*close)(struct swconfig_callback *cb, void *arg); + +	struct nlattr *nest[4]; +	int args[4]; +}; + +/* defaults */ + +static int +swconfig_get_vlan_ports(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val) +{ +	int ret; +	if (val->port_vlan >= dev->vlans) +		return -EINVAL; + +	if (!dev->get_vlan_ports) +		return -EOPNOTSUPP; + +	ret = dev->get_vlan_ports(dev, val); +	printk("SET PORTS %d\n", val->len); +	return ret; +} + +static int +swconfig_set_vlan_ports(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val) +{ +	int i; + +	if (val->port_vlan >= dev->vlans) +		return -EINVAL; + +	/* validate ports */ +	if (val->len > dev->ports) +		return -EINVAL; + +	for (i = 0; i < val->len; i++) { +		if (val->value.ports[i].id >= dev->ports) +			return -EINVAL; +	} + +	if (!dev->set_vlan_ports) +		return -EOPNOTSUPP; + +	printk("SET PORTS %d\n", val->len); +	return dev->set_vlan_ports(dev, val); +} + +static int +swconfig_apply_config(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val) +{ +	/* don't complain if not supported by the switch driver */ +	if (!dev->apply_config) +		return 0; + +	return dev->apply_config(dev); +} + + +enum global_defaults { +	GLOBAL_APPLY, +}; + +enum vlan_defaults { +	VLAN_PORTS, +}; + +enum port_defaults { +	PORT_LINK, +}; + +static struct switch_attr default_global[] = { +	[GLOBAL_APPLY] = { +		.type = SWITCH_TYPE_NOVAL, +		.name = "apply", +		.description = "Activate changes in the hardware", +		.set = swconfig_apply_config, +	} +}; + +static struct switch_attr default_port[] = { +	[PORT_LINK] = { +		.type = SWITCH_TYPE_INT, +		.name = "link", +		.description = "Current link speed", +	} +}; + +static struct switch_attr default_vlan[] = { +	[VLAN_PORTS] = { +		.type = SWITCH_TYPE_PORTS, +		.name = "ports", +		.description = "VLAN port mapping", +		.set = swconfig_set_vlan_ports, +		.get = swconfig_get_vlan_ports, +	}, +}; + + +static void swconfig_defaults_init(struct switch_dev *dev) +{ +	dev->def_global = 0; +	dev->def_vlan = 0; +	dev->def_port = 0; + +	if (dev->get_vlan_ports || dev->set_vlan_ports) +		set_bit(VLAN_PORTS, &dev->def_vlan); + +	/* always present, can be no-op */ +	set_bit(GLOBAL_APPLY, &dev->def_global); +} + + +static struct genl_family switch_fam = { +	.id = GENL_ID_GENERATE, +	.name = "switch", +	.hdrsize = 0, +	.version = 1, +	.maxattr = SWITCH_ATTR_MAX, +}; + +static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = { +	[SWITCH_ATTR_ID] = { .type = NLA_U32 }, +	[SWITCH_ATTR_OP_ID] = { .type = NLA_U32 }, +	[SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 }, +	[SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 }, +	[SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 }, +	[SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING }, +	[SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED }, +	[SWITCH_ATTR_TYPE] = { .type = NLA_U32 }, +}; + +static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = { +	[SWITCH_PORT_ID] = { .type = NLA_U32 }, +	[SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG }, +}; + +static inline void +swconfig_lock(void) +{ +	spin_lock(&swdevs_lock); +} + +static inline void +swconfig_unlock(void) +{ +	spin_unlock(&swdevs_lock); +} + +static struct switch_dev * +swconfig_get_dev(struct genl_info *info) +{ +	struct switch_dev *dev = NULL; +	struct switch_dev *p; +	int id; + +	if (!info->attrs[SWITCH_ATTR_ID]) +		goto done; + +	id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]); +	swconfig_lock(); +	list_for_each_entry(p, &swdevs, dev_list) { +		if (id != p->id) +			continue; + +		dev = p; +		break; +	} +	if (dev) +		spin_lock(&dev->lock); +	else +		DPRINTF("device %d not found\n", id); +	swconfig_unlock(); +done: +	return dev; +} + +static inline void +swconfig_put_dev(struct switch_dev *dev) +{ +	spin_unlock(&dev->lock); +} + +static int +swconfig_dump_attr(struct swconfig_callback *cb, void *arg) +{ +	struct switch_attr *op = arg; +	struct genl_info *info = cb->info; +	struct sk_buff *msg = cb->msg; +	int id = cb->args[0]; +	void *hdr; + +	hdr = genlmsg_put(msg, info->snd_pid, info->snd_seq, &switch_fam, +			NLM_F_MULTI, SWITCH_CMD_NEW_ATTR); +	if (IS_ERR(hdr)) +		return -1; + +	NLA_PUT_U32(msg, SWITCH_ATTR_OP_ID, id); +	NLA_PUT_U32(msg, SWITCH_ATTR_OP_TYPE, op->type); +	NLA_PUT_STRING(msg, SWITCH_ATTR_OP_NAME, op->name); +	if (op->description) +		NLA_PUT_STRING(msg, SWITCH_ATTR_OP_DESCRIPTION, +			op->description); + +	return genlmsg_end(msg, hdr); +nla_put_failure: +	genlmsg_cancel(msg, hdr); +	return -EMSGSIZE; +} + +/* spread multipart messages across multiple message buffers */ +static int +swconfig_send_multipart(struct swconfig_callback *cb, void *arg) +{ +	struct genl_info *info = cb->info; +	int restart = 0; +	int err; + +	do { +		if (!cb->msg) { +			cb->msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); +			if (cb->msg == NULL) +				goto error; +		} + +		if (!(cb->fill(cb, arg) < 0)) +			break; + +		/* fill failed, check if this was already the second attempt */ +		if (restart) +			goto error; + +		/* try again in a new message, send the current one */ +		restart = 1; +		if (cb->close) { +			if (cb->close(cb, arg) < 0) +				goto error; +		} +		err = genlmsg_unicast(cb->msg, info->snd_pid); +		cb->msg = NULL; +		if (err < 0) +			goto error; + +	} while (restart); + +	return 0; + +error: +	if (cb->msg) +		nlmsg_free(cb->msg); +	return -1; +} + +static int +swconfig_list_attrs(struct sk_buff *skb, struct genl_info *info) +{ +	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); +	const struct switch_attrlist *alist; +	struct switch_dev *dev; +	struct swconfig_callback cb; +	int err = -EINVAL; +	int i; + +	/* defaults */ +	struct switch_attr *def_list; +	unsigned long *def_active; +	int n_def; + +	dev = swconfig_get_dev(info); +	if (!dev) +		return -EINVAL; + +	switch(hdr->cmd) { +	case SWITCH_CMD_LIST_GLOBAL: +		alist = &dev->attr_global; +		def_list = default_global; +		def_active = &dev->def_global; +		n_def = ARRAY_SIZE(default_global); +		break; +	case SWITCH_CMD_LIST_VLAN: +		alist = &dev->attr_vlan; +		def_list = default_vlan; +		def_active = &dev->def_vlan; +		n_def = ARRAY_SIZE(default_vlan); +		break; +	case SWITCH_CMD_LIST_PORT: +		alist = &dev->attr_port; +		def_list = default_port; +		def_active = &dev->def_port; +		n_def = ARRAY_SIZE(default_port); +		break; +	default: +		WARN_ON(1); +		goto out; +	} + +	memset(&cb, 0, sizeof(cb)); +	cb.info = info; +	cb.fill = swconfig_dump_attr; +	for (i = 0; i < alist->n_attr; i++) { +		if (alist->attr[i].disabled) +			continue; +		cb.args[0] = i; +		err = swconfig_send_multipart(&cb, &alist->attr[i]); +		if (err < 0) +			goto error; +	} + +	/* defaults */ +	for (i = 0; i < n_def; i++) { +		if (!test_bit(i, def_active)) +			continue; +		cb.args[0] = SWITCH_ATTR_DEFAULTS_OFFSET + i; +		err = swconfig_send_multipart(&cb, &def_list[i]); +		if (err < 0) +			goto error; +	} +	swconfig_put_dev(dev); + +	if (!cb.msg) +		return 0; + +	return genlmsg_unicast(cb.msg, info->snd_pid); + +error: +	if (cb.msg) +		nlmsg_free(cb.msg); +out: +	swconfig_put_dev(dev); +	return err; +} + +static struct switch_attr * +swconfig_lookup_attr(struct switch_dev *dev, struct genl_info *info, +		struct switch_val *val) +{ +	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); +	const struct switch_attrlist *alist; +	struct switch_attr *attr = NULL; +	int attr_id; + +	/* defaults */ +	struct switch_attr *def_list; +	unsigned long *def_active; +	int n_def; + +	if (!info->attrs[SWITCH_ATTR_OP_ID]) +		goto done; + +	switch(hdr->cmd) { +	case SWITCH_CMD_SET_GLOBAL: +	case SWITCH_CMD_GET_GLOBAL: +		alist = &dev->attr_global; +		def_list = default_global; +		def_active = &dev->def_global; +		n_def = ARRAY_SIZE(default_global); +		break; +	case SWITCH_CMD_SET_VLAN: +	case SWITCH_CMD_GET_VLAN: +		alist = &dev->attr_vlan; +		def_list = default_vlan; +		def_active = &dev->def_vlan; +		n_def = ARRAY_SIZE(default_vlan); +		if (!info->attrs[SWITCH_ATTR_OP_VLAN]) +			goto done; +		val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_VLAN]); +		break; +	case SWITCH_CMD_SET_PORT: +	case SWITCH_CMD_GET_PORT: +		alist = &dev->attr_port; +		def_list = default_port; +		def_active = &dev->def_port; +		n_def = ARRAY_SIZE(default_port); +		if (!info->attrs[SWITCH_ATTR_OP_PORT]) +			goto done; +		val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_PORT]); +		break; +	default: +		WARN_ON(1); +		goto done; +	} + +	if (!alist) +		goto done; + +	attr_id = nla_get_u32(info->attrs[SWITCH_ATTR_OP_ID]); +	if (attr_id >= SWITCH_ATTR_DEFAULTS_OFFSET) { +		attr_id -= SWITCH_ATTR_DEFAULTS_OFFSET; +		if (attr_id >= n_def) +			goto done; +		if (!test_bit(attr_id, def_active)) +			goto done; +		attr = &def_list[attr_id]; +	} else { +		if (attr_id >= alist->n_attr) +			goto done; +		attr = &alist->attr[attr_id]; +	} + +	if (attr->disabled) +		attr = NULL; + +done: +	if (!attr) +		DPRINTF("attribute lookup failed\n"); +	val->attr = attr; +	return attr; +} + +static int +swconfig_parse_ports(struct sk_buff *msg, struct nlattr *head, +		struct switch_val *val, int max) +{ +	struct nlattr *nla; +	int rem; + +	val->len = 0; +	nla_for_each_nested(nla, head, rem) { +		struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1]; +		struct switch_port *port = &val->value.ports[val->len]; + +		if (val->len >= max) +			return -EINVAL; + +		if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, +				port_policy)) +			return -EINVAL; + +		if (!tb[SWITCH_PORT_ID]) +			return -EINVAL; + +		port->id = nla_get_u32(tb[SWITCH_PORT_ID]); +		if (tb[SWITCH_PORT_FLAG_TAGGED]) +			port->flags |= (1 << SWITCH_PORT_FLAG_TAGGED); +		val->len++; +	} + +	return 0; +} + +static int +swconfig_set_attr(struct sk_buff *skb, struct genl_info *info) +{ +	struct switch_attr *attr; +	struct switch_dev *dev; +	struct switch_val val; +	int err = -EINVAL; + +	dev = swconfig_get_dev(info); +	if (!dev) +		return -EINVAL; + +	memset(&val, 0, sizeof(val)); +	attr = swconfig_lookup_attr(dev, info, &val); +	if (!attr || !attr->set) +		goto error; + +	val.attr = attr; +	switch(attr->type) { +	case SWITCH_TYPE_NOVAL: +		break; +	case SWITCH_TYPE_INT: +		if (!info->attrs[SWITCH_ATTR_OP_VALUE_INT]) +			goto error; +		val.value.i = +			nla_get_u32(info->attrs[SWITCH_ATTR_OP_VALUE_INT]); +		break; +	case SWITCH_TYPE_STRING: +		if (!info->attrs[SWITCH_ATTR_OP_VALUE_STR]) +			goto error; +		val.value.s = +			nla_data(info->attrs[SWITCH_ATTR_OP_VALUE_STR]); +		break; +	case SWITCH_TYPE_PORTS: +		val.value.ports = dev->portbuf; +		memset(dev->portbuf, 0, +			sizeof(struct switch_port) * dev->ports); + +		/* TODO: implement multipart? */ +		if (info->attrs[SWITCH_ATTR_OP_VALUE_PORTS]) { +			err = swconfig_parse_ports(skb, +				info->attrs[SWITCH_ATTR_OP_VALUE_PORTS], &val, dev->ports); +			if (err < 0) +				goto error; +		} else { +			val.len = 0; +			err = 0; +		} +		break; +	default: +		goto error; +	} + +	err = attr->set(dev, attr, &val); +error: +	swconfig_put_dev(dev); +	return err; +} + +static int +swconfig_close_portlist(struct swconfig_callback *cb, void *arg) +{ +	if (cb->nest[0]) +		nla_nest_end(cb->msg, cb->nest[0]); +	return 0; +} + +static int +swconfig_send_port(struct swconfig_callback *cb, void *arg) +{ +	const struct switch_port *port = arg; +	struct nlattr *p = NULL; + +	if (!cb->nest[0]) { +		cb->nest[0] = nla_nest_start(cb->msg, cb->cmd); +		if (!cb->nest[0]) +			return -1; +	} + +	p = nla_nest_start(cb->msg, SWITCH_ATTR_PORT); +	if (!p) +		goto error; + +	NLA_PUT_U32(cb->msg, SWITCH_PORT_ID, port->id); +	if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) +		NLA_PUT_FLAG(cb->msg, SWITCH_PORT_FLAG_TAGGED); + +	nla_nest_end(cb->msg, p); +	return 0; + +nla_put_failure: +		nla_nest_cancel(cb->msg, p); +error: +	nla_nest_cancel(cb->msg, cb->nest[0]); +	return -1; +} + +static int +swconfig_send_ports(struct sk_buff **msg, struct genl_info *info, int attr, +		const struct switch_val *val) +{ +	struct swconfig_callback cb; +	int err = 0; +	int i; + +	if (!val->value.ports) +		return -EINVAL; + +	memset(&cb, 0, sizeof(cb)); +	cb.cmd = attr; +	cb.msg = *msg; +	cb.info = info; +	cb.fill = swconfig_send_port; +	cb.close = swconfig_close_portlist; + +	cb.nest[0] = nla_nest_start(cb.msg, cb.cmd); +	for (i = 0; i < val->len; i++) { +		err = swconfig_send_multipart(&cb, &val->value.ports[i]); +		if (err) +			goto done; +	} +	err = val->len; +	swconfig_close_portlist(&cb, NULL); +	*msg = cb.msg; + +done: +	return err; +} + +static int +swconfig_get_attr(struct sk_buff *skb, struct genl_info *info) +{ +	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); +	struct switch_attr *attr; +	struct switch_dev *dev; +	struct sk_buff *msg = NULL; +	struct switch_val val; +	int err = -EINVAL; +	int cmd = hdr->cmd; + +	dev = swconfig_get_dev(info); +	if (!dev) +		return -EINVAL; + +	memset(&val, 0, sizeof(val)); +	attr = swconfig_lookup_attr(dev, info, &val); +	if (!attr || !attr->get) +		goto error_dev; + +	if (attr->type == SWITCH_TYPE_PORTS) { +		val.value.ports = dev->portbuf; +		memset(dev->portbuf, 0, +			sizeof(struct switch_port) * dev->ports); +	} + +	err = attr->get(dev, attr, &val); +	if (err) +		goto error; + +	msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); +	if (!msg) +		goto error; + +	hdr = genlmsg_put(msg, info->snd_pid, info->snd_seq, &switch_fam, +			0, cmd); +	if (IS_ERR(hdr)) +		goto nla_put_failure; + +	switch(attr->type) { +	case SWITCH_TYPE_INT: +		NLA_PUT_U32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i); +		break; +	case SWITCH_TYPE_STRING: +		NLA_PUT_STRING(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s); +		break; +	case SWITCH_TYPE_PORTS: +		err = swconfig_send_ports(&msg, info, +				SWITCH_ATTR_OP_VALUE_PORTS, &val); +		if (err < 0) +			goto nla_put_failure; +		break; +	default: +		DPRINTF("invalid type in attribute\n"); +		err = -EINVAL; +		goto error; +	} +	err = genlmsg_end(msg, hdr); +	if (err < 0) +		goto nla_put_failure; + +	swconfig_put_dev(dev); +	return genlmsg_unicast(msg, info->snd_pid); + +nla_put_failure: +	if (msg) +		nlmsg_free(msg); +error_dev: +	swconfig_put_dev(dev); +error: +	if (!err) +		err = -ENOMEM; +	return err; +} + +static int +swconfig_send_switch(struct sk_buff *msg, u32 pid, u32 seq, int flags, +		const struct switch_dev *dev) +{ +	void *hdr; + +	hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags, +			SWITCH_CMD_NEW_ATTR); +	if (IS_ERR(hdr)) +		return -1; + +	NLA_PUT_U32(msg, SWITCH_ATTR_ID, dev->id); +	NLA_PUT_STRING(msg, SWITCH_ATTR_NAME, dev->name); +	NLA_PUT_STRING(msg, SWITCH_ATTR_DEV_NAME, dev->devname); +	NLA_PUT_U32(msg, SWITCH_ATTR_VLANS, dev->vlans); +	NLA_PUT_U32(msg, SWITCH_ATTR_PORTS, dev->ports); + +	return genlmsg_end(msg, hdr); +nla_put_failure: +	genlmsg_cancel(msg, hdr); +	return -EMSGSIZE; +} + +static int swconfig_dump_switches(struct sk_buff *skb, +		struct netlink_callback *cb) +{ +	struct switch_dev *dev; +	int start = cb->args[0]; +	int idx = 0; + +	swconfig_lock(); +	list_for_each_entry(dev, &swdevs, dev_list) { +		if (++idx <= start) +			continue; +		if (swconfig_send_switch(skb, NETLINK_CB(cb->skb).pid, +				cb->nlh->nlmsg_seq, NLM_F_MULTI, +				dev) < 0) +			break; +	} +	swconfig_unlock(); +	cb->args[0] = idx; + +	return skb->len; +} + +static int +swconfig_done(struct netlink_callback *cb) +{ +	return 0; +} + +static struct genl_ops swconfig_ops[] = { +	{ +		.cmd = SWITCH_CMD_LIST_GLOBAL, +		.doit = swconfig_list_attrs, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_LIST_VLAN, +		.doit = swconfig_list_attrs, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_LIST_PORT, +		.doit = swconfig_list_attrs, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_GET_GLOBAL, +		.doit = swconfig_get_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_GET_VLAN, +		.doit = swconfig_get_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_GET_PORT, +		.doit = swconfig_get_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_SET_GLOBAL, +		.doit = swconfig_set_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_SET_VLAN, +		.doit = swconfig_set_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_SET_PORT, +		.doit = swconfig_set_attr, +		.policy = switch_policy, +	}, +	{ +		.cmd = SWITCH_CMD_GET_SWITCH, +		.dumpit = swconfig_dump_switches, +		.policy = switch_policy, +		.done = swconfig_done, +	} +}; + +int +register_switch(struct switch_dev *dev, struct net_device *netdev) +{ +	INIT_LIST_HEAD(&dev->dev_list); +	if (netdev) { +		dev->netdev = netdev; +		if (!dev->devname) +			dev->devname = netdev->name; +	} +	BUG_ON(!dev->devname); + +	if (dev->ports > 0) { +		dev->portbuf = kzalloc(sizeof(struct switch_port) * dev->ports, +				GFP_KERNEL); +		if (!dev->portbuf) +			return -ENOMEM; +	} +	dev->id = ++swdev_id; +	swconfig_defaults_init(dev); +	spin_lock_init(&dev->lock); +	swconfig_lock(); +	list_add(&dev->dev_list, &swdevs); +	swconfig_unlock(); + +	return 0; +} +EXPORT_SYMBOL_GPL(register_switch); + +void +unregister_switch(struct switch_dev *dev) +{ +	kfree(dev->portbuf); +	spin_lock(&dev->lock); +	swconfig_lock(); +	list_del(&dev->dev_list); +	swconfig_unlock(); +} +EXPORT_SYMBOL_GPL(unregister_switch); + + +static int __init +swconfig_init(void) +{ +	int i, err; + +	INIT_LIST_HEAD(&swdevs); +	err = genl_register_family(&switch_fam); +	if (err) +		return err; + +	for (i = 0; i < ARRAY_SIZE(swconfig_ops); i++) { +		err = genl_register_ops(&switch_fam, &swconfig_ops[i]); +		if (err) +			goto unregister; +	} + +	return 0; + +unregister: +	genl_unregister_family(&switch_fam); +	return err; +} + +static void __exit +swconfig_exit(void) +{ +	genl_unregister_family(&switch_fam); +} + +module_init(swconfig_init); +module_exit(swconfig_exit); + diff --git a/target/linux/generic-2.6/files-2.6.27/include/linux/switch.h b/target/linux/generic-2.6/files-2.6.27/include/linux/switch.h new file mode 100644 index 000000000..9411e84bc --- /dev/null +++ b/target/linux/generic-2.6/files-2.6.27/include/linux/switch.h @@ -0,0 +1,168 @@ +/* + * switch.h: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> + * + * This program 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#ifndef __LINUX_SWITCH_H +#define __LINUX_SWITCH_H + +#include <linux/types.h> +#include <linux/netdevice.h> +#include <linux/netlink.h> +#include <linux/genetlink.h> +#ifndef __KERNEL__ +#include <netlink/netlink.h> +#include <netlink/genl/genl.h> +#include <netlink/genl/ctrl.h> +#else +#include <net/genetlink.h> +#endif + +/* main attributes */ +enum { +	SWITCH_ATTR_UNSPEC, +	/* global */ +	SWITCH_ATTR_TYPE, +	/* device */ +	SWITCH_ATTR_ID, +	SWITCH_ATTR_NAME, +	SWITCH_ATTR_DEV_NAME, +	SWITCH_ATTR_VLANS, +	SWITCH_ATTR_PORTS, +	/* attributes */ +	SWITCH_ATTR_OP_ID, +	SWITCH_ATTR_OP_TYPE, +	SWITCH_ATTR_OP_NAME, +	SWITCH_ATTR_OP_PORT, +	SWITCH_ATTR_OP_VLAN, +	SWITCH_ATTR_OP_VALUE_INT, +	SWITCH_ATTR_OP_VALUE_STR, +	SWITCH_ATTR_OP_VALUE_PORTS, +	SWITCH_ATTR_OP_DESCRIPTION, +	/* port lists */ +	SWITCH_ATTR_PORT, +	SWITCH_ATTR_MAX +}; + +/* commands */ +enum { +	SWITCH_CMD_UNSPEC, +	SWITCH_CMD_GET_SWITCH, +	SWITCH_CMD_NEW_ATTR, +	SWITCH_CMD_LIST_GLOBAL, +	SWITCH_CMD_GET_GLOBAL, +	SWITCH_CMD_SET_GLOBAL, +	SWITCH_CMD_LIST_PORT, +	SWITCH_CMD_GET_PORT, +	SWITCH_CMD_SET_PORT, +	SWITCH_CMD_LIST_VLAN, +	SWITCH_CMD_GET_VLAN, +	SWITCH_CMD_SET_VLAN +}; + +/* data types */ +enum switch_val_type { +	SWITCH_TYPE_UNSPEC, +	SWITCH_TYPE_INT, +	SWITCH_TYPE_STRING, +	SWITCH_TYPE_PORTS, +	SWITCH_TYPE_NOVAL, +}; + +/* port nested attributes */ +enum { +	SWITCH_PORT_UNSPEC, +	SWITCH_PORT_ID, +	SWITCH_PORT_FLAG_TAGGED, +	SWITCH_PORT_ATTR_MAX +}; + +#define SWITCH_ATTR_DEFAULTS_OFFSET	0x1000 + +#ifdef __KERNEL__ + +struct switch_dev; +struct switch_op; +struct switch_val; +struct switch_attr; +struct switch_attrlist; + +int register_switch(struct switch_dev *dev, struct net_device *netdev); +void unregister_switch(struct switch_dev *dev); + +struct switch_attrlist { +	/* filled in by the driver */ +	int n_attr; +	struct switch_attr *attr; +}; + + +struct switch_dev { +	int id; +	void *priv; +	const char *name; + +	/* NB: either devname or netdev must be set */ +	const char *devname; +	struct net_device *netdev; + +	int ports; +	int vlans; +	int cpu_port; +	struct switch_attrlist attr_global, attr_port, attr_vlan; + +	spinlock_t lock; +	struct switch_port *portbuf; +	struct list_head dev_list; +	unsigned long def_global, def_port, def_vlan; + +	int (*get_vlan_ports)(struct switch_dev *dev, struct switch_val *val); +	int (*set_vlan_ports)(struct switch_dev *dev, struct switch_val *val); +	int (*apply_config)(struct switch_dev *dev); +}; + +struct switch_port { +	u32 id; +	u32 flags; +}; + +struct switch_val { +	struct switch_attr *attr; +	int port_vlan; +	int len; +	union { +		const char *s; +		u32 i; +		struct switch_port *ports; +	} value; +}; + +struct switch_attr { +	int disabled; +	int type; +	const char *name; +	const char *description; + +	int (*set)(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val); +	int (*get)(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val); + +	/* for driver internal use */ +	int id; +	int ofs; +	int max; +}; + +#endif + +#endif diff --git a/target/linux/generic-2.6/patches-2.6.26/650-swconfig.patch b/target/linux/generic-2.6/patches-2.6.26/650-swconfig.patch new file mode 100644 index 000000000..6825037ce --- /dev/null +++ b/target/linux/generic-2.6/patches-2.6.26/650-swconfig.patch @@ -0,0 +1,25 @@ +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -13,6 +13,12 @@ menuconfig PHYLIB +  + if PHYLIB +  ++config SWCONFIG ++	tristate "Switch configuration API" ++	---help--- ++	  Switch configuration API using netlink. This allows ++	  you to configure the VLAN features of certain switches. ++ + comment "MII PHY device drivers" +  + config MARVELL_PHY +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -3,6 +3,7 @@ + libphy-objs			:= phy.o phy_device.o mdio_bus.o +  + obj-$(CONFIG_PHYLIB)		+= libphy.o ++obj-$(CONFIG_SWCONFIG)		+= swconfig.o + obj-$(CONFIG_MARVELL_PHY)	+= marvell.o + obj-$(CONFIG_DAVICOM_PHY)	+= davicom.o + obj-$(CONFIG_CICADA_PHY)	+= cicada.o diff --git a/target/linux/generic-2.6/patches-2.6.27/650-swconfig.patch b/target/linux/generic-2.6/patches-2.6.27/650-swconfig.patch new file mode 100644 index 000000000..6825037ce --- /dev/null +++ b/target/linux/generic-2.6/patches-2.6.27/650-swconfig.patch @@ -0,0 +1,25 @@ +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -13,6 +13,12 @@ menuconfig PHYLIB +  + if PHYLIB +  ++config SWCONFIG ++	tristate "Switch configuration API" ++	---help--- ++	  Switch configuration API using netlink. This allows ++	  you to configure the VLAN features of certain switches. ++ + comment "MII PHY device drivers" +  + config MARVELL_PHY +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -3,6 +3,7 @@ + libphy-objs			:= phy.o phy_device.o mdio_bus.o +  + obj-$(CONFIG_PHYLIB)		+= libphy.o ++obj-$(CONFIG_SWCONFIG)		+= swconfig.o + obj-$(CONFIG_MARVELL_PHY)	+= marvell.o + obj-$(CONFIG_DAVICOM_PHY)	+= davicom.o + obj-$(CONFIG_CICADA_PHY)	+= cicada.o | 
