mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-11-28 22:49:43 +02:00
56f15bb5f9
Let swconfig provide the cpu port index in its help page. This is needed as e.g. Atheros switches have their cpu port at port 0, not port 5. This could allow e.g. luci to get a rough overview of the layout of the switch. Signed-off-by: Jonas Gorski <jonas.gorski+openwrt@gmail.com> git-svn-id: svn://svn.openwrt.org/openwrt/trunk@20939 3c298f89-4303-0410-b956-a3cf2f4a3e73
926 lines
19 KiB
C
926 lines
19 KiB
C
/*
|
|
* 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, const 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);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
swconfig_set_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
|
|
{
|
|
struct switch_port *ports = val->value.ports;
|
|
int i;
|
|
|
|
if (val->port_vlan >= dev->vlans)
|
|
return -EINVAL;
|
|
|
|
/* validate ports */
|
|
if (val->len > dev->ports)
|
|
return -EINVAL;
|
|
|
|
if (!dev->set_vlan_ports)
|
|
return -EOPNOTSUPP;
|
|
|
|
for (i = 0; i < val->len; i++) {
|
|
if (ports[i].id >= dev->ports)
|
|
return -EINVAL;
|
|
|
|
if (dev->set_port_pvid && !(ports[i].flags & SWITCH_PORT_FLAG_TAGGED))
|
|
dev->set_port_pvid(dev, ports[i].id, val->port_vlan);
|
|
}
|
|
|
|
return dev->set_vlan_ports(dev, val);
|
|
}
|
|
|
|
static int
|
|
swconfig_set_pvid(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
|
|
{
|
|
if (val->port_vlan >= dev->ports)
|
|
return -EINVAL;
|
|
|
|
if (!dev->set_port_pvid)
|
|
return -EOPNOTSUPP;
|
|
|
|
return dev->set_port_pvid(dev, val->port_vlan, val->value.i);
|
|
}
|
|
|
|
static int
|
|
swconfig_get_pvid(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
|
|
{
|
|
if (val->port_vlan >= dev->ports)
|
|
return -EINVAL;
|
|
|
|
if (!dev->get_port_pvid)
|
|
return -EOPNOTSUPP;
|
|
|
|
return dev->get_port_pvid(dev, val->port_vlan, &val->value.i);
|
|
}
|
|
|
|
static int
|
|
swconfig_apply_config(struct switch_dev *dev, const 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);
|
|
}
|
|
|
|
static int
|
|
swconfig_reset_switch(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
|
|
{
|
|
/* don't complain if not supported by the switch driver */
|
|
if (!dev->reset_switch)
|
|
return 0;
|
|
|
|
return dev->reset_switch(dev);
|
|
}
|
|
|
|
enum global_defaults {
|
|
GLOBAL_APPLY,
|
|
GLOBAL_RESET,
|
|
};
|
|
|
|
enum vlan_defaults {
|
|
VLAN_PORTS,
|
|
};
|
|
|
|
enum port_defaults {
|
|
PORT_PVID,
|
|
};
|
|
|
|
static struct switch_attr default_global[] = {
|
|
[GLOBAL_APPLY] = {
|
|
.type = SWITCH_TYPE_NOVAL,
|
|
.name = "apply",
|
|
.description = "Activate changes in the hardware",
|
|
.set = swconfig_apply_config,
|
|
},
|
|
[GLOBAL_RESET] = {
|
|
.type = SWITCH_TYPE_NOVAL,
|
|
.name = "reset",
|
|
.description = "Reset the switch",
|
|
.set = swconfig_reset_switch,
|
|
}
|
|
};
|
|
|
|
static struct switch_attr default_port[] = {
|
|
[PORT_PVID] = {
|
|
.type = SWITCH_TYPE_INT,
|
|
.name = "pvid",
|
|
.description = "Primary VLAN ID",
|
|
.set = swconfig_set_pvid,
|
|
.get = swconfig_get_pvid,
|
|
}
|
|
};
|
|
|
|
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);
|
|
|
|
if (dev->get_port_pvid || dev->set_port_pvid)
|
|
set_bit(PORT_PVID, &dev->def_port);
|
|
|
|
/* always present, can be no-op */
|
|
set_bit(GLOBAL_APPLY, &dev->def_global);
|
|
set_bit(GLOBAL_RESET, &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, (void *) &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, (void *) &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 const 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;
|
|
const 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]);
|
|
if (val->port_vlan >= dev->vlans)
|
|
goto done;
|
|
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]);
|
|
if (val->port_vlan >= dev->ports)
|
|
goto done;
|
|
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)
|
|
{
|
|
const 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);
|
|
const 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);
|
|
NLA_PUT_U32(msg, SWITCH_ATTR_CPU_PORT, dev->cpu_port);
|
|
|
|
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);
|
|
|