aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ubicom32/files/arch/ubicom32/mach-common/switch-core.c
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/ubicom32/files/arch/ubicom32/mach-common/switch-core.c')
-rw-r--r--target/linux/ubicom32/files/arch/ubicom32/mach-common/switch-core.c737
1 files changed, 737 insertions, 0 deletions
diff --git a/target/linux/ubicom32/files/arch/ubicom32/mach-common/switch-core.c b/target/linux/ubicom32/files/arch/ubicom32/mach-common/switch-core.c
new file mode 100644
index 000000000..855aa068d
--- /dev/null
+++ b/target/linux/ubicom32/files/arch/ubicom32/mach-common/switch-core.c
@@ -0,0 +1,737 @@
+/*
+ * arch/ubicom32/mach-common/switch-core.c
+ * Ubicom32 architecture switch and /proc/switch/... implementation.
+ *
+ * (C) Copyright 2009, Ubicom, Inc.
+ * Copyright (C) 2005 Felix Fietkau <openwrt@nbd.name>
+ *
+ * This file is part of the Ubicom32 Linux Kernel Port.
+ *
+ * The Ubicom32 Linux Kernel Port 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.
+ *
+ * The Ubicom32 Linux Kernel Port 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with the Ubicom32 Linux Kernel Port. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Ubicom32 implementation derived from (with many thanks):
+ * arch/m68knommu
+ * arch/blackfin
+ * arch/parisc
+ *
+ * Basic doc of driver's /proc interface:
+ * /proc/switch/<interface>/
+ * registers: read-only
+ * counters: read-only
+ * reset: write causes hardware reset
+ * enable: "0", "1"
+ * enable_vlan: "0", "1"
+ * port/<port-number>/
+ * enabled: "0", "1"
+ * link state: read-only
+ * media: "AUTO", "1000FD", "100FD", "100HD", "10FD", "10HD"
+ * vlan/<port-number>/
+ * ports: same syntax as for nvram's vlan*ports (eg. "1 2 3 4 5*")
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/ctype.h>
+#include <linux/proc_fs.h>
+#include <linux/list.h>
+#include <linux/rwsem.h>
+#include <linux/device.h>
+
+#include "switch-core.h"
+
+/*
+ * Pointer to the root of our filesystem
+ */
+static struct proc_dir_entry *switch_root;
+
+/*
+ * Lock used to manage access to the switch list
+ */
+DECLARE_RWSEM(switch_list_lock);
+EXPORT_SYMBOL_GPL(switch_list_lock);
+
+/*
+ * List of switches we are managing
+ */
+LIST_HEAD(switch_list);
+EXPORT_SYMBOL_GPL(switch_list);
+
+/*
+ * List of handlers we have
+ */
+LIST_HEAD(switch_handler_list);
+EXPORT_SYMBOL_GPL(switch_handler_list);
+
+/*
+ * Keep track of all the handlers we added
+ */
+struct switch_handler_entry {
+ struct list_head node;
+ struct proc_dir_entry *parent;
+ struct switch_device *dev;
+ const struct switch_handler *handler;
+ int inst;
+};
+
+/*
+ * Keep track of all VLAN dirs we created
+ */
+struct switch_vlan_entry {
+ struct list_head node;
+ struct proc_dir_entry *pde;
+ int vlan_id;
+ const struct switch_handler *handlers;
+};
+
+/*
+ * switch_parse_vlan_ports
+ * Parse the vlan properties written to <driver>/vlan/<vlan_id>/ports
+ */
+void switch_parse_vlan_ports(struct switch_device *switch_dev,
+ char *buf, u32_t *untag,
+ u32_t *ports, u32_t *def)
+{
+ u32_t tag = 0;
+ *untag = 0;
+ *ports = 0;
+ *def = 0;
+
+
+ /*
+ * Skip any leading spaces
+ */
+ while (isspace(*buf)) {
+ buf++;
+ }
+
+ /*
+ * Parse out the string
+ */
+ while (*buf) {
+ u32_t port = simple_strtoul(buf, &buf, 10);
+ u32_t mask = (1 << port);
+
+ /*
+ * Parse out any flags
+ */
+ while (*buf && !isspace(*buf)) {
+ switch (*buf++) {
+ case 't':
+ tag |= mask;
+ break;
+ case '*':
+ *def |= mask;
+ break;
+ }
+ }
+ *ports |= mask;
+
+ /*
+ * Skip any spaces
+ */
+ while (isspace(*buf)) {
+ buf++;
+ }
+ }
+
+ *untag = ~tag & *ports;
+}
+
+/*
+ * switch_proc_read
+ * Handle reads from the procfs, dispatches the driver specific handler
+ */
+static ssize_t switch_proc_read(struct file *file, char *buf, size_t count,
+ loff_t *ppos)
+{
+ struct proc_dir_entry *pde = PDE(file->f_dentry->d_inode);
+ char *page;
+ int len = 0;
+
+ page = kmalloc(SWITCH_MAX_BUFSZ, GFP_KERNEL);
+ if (!page) {
+ return -ENOBUFS;
+ }
+
+ if (pde->data != NULL) {
+ struct switch_handler_entry *she =
+ (struct switch_handler_entry *)pde->data;
+ if (she->handler->read) {
+ len += she->handler->read(she->dev, page + len,
+ she->inst);
+ }
+ }
+ len += 1;
+
+ if (*ppos < len) {
+ len = min_t(int, len - *ppos, count);
+ if (copy_to_user(buf, (page + *ppos), len)) {
+ kfree(page);
+ return -EFAULT;
+ }
+ *ppos += len;
+ } else {
+ len = 0;
+ }
+
+ kfree(page);
+
+ return len;
+}
+
+/*
+ * switch_proc_write
+ * Handle writes from the procfs, dispatches the driver specific handler
+ */
+static ssize_t switch_proc_write(struct file *file, const char *buf,
+ size_t count, loff_t *data)
+{
+ struct proc_dir_entry *pde = PDE(file->f_dentry->d_inode);
+ char *page;
+ int ret = -EINVAL;
+
+ page = kmalloc(count + 1, GFP_KERNEL);
+ if (page == NULL)
+ return -ENOBUFS;
+
+ if (copy_from_user(page, buf, count)) {
+ kfree(page);
+ return -EINVAL;
+ }
+ page[count] = 0;
+
+ if (pde->data != NULL) {
+ struct switch_handler_entry *she =
+ (struct switch_handler_entry *)pde->data;
+ if (she->handler->write) {
+ ret = she->handler->write(she->dev, page, she->inst);
+ if (ret >= 0) {
+ ret = count;
+ }
+ }
+ }
+
+ kfree(page);
+ return ret;
+}
+
+/*
+ * File operations for the proc_fs, we must cast here since proc_fs' definitions
+ * differ from file_operations definitions.
+ */
+static struct file_operations switch_proc_fops = {
+ .read = (ssize_t (*) (struct file *, char __user *,
+ size_t, loff_t *))switch_proc_read,
+ .write = (ssize_t (*) (struct file *, const char __user *,
+ size_t, loff_t *))switch_proc_write,
+};
+
+/*
+ * switch_add_handler
+ */
+static int switch_add_handler(struct switch_device *switch_dev,
+ struct proc_dir_entry *parent,
+ const struct switch_handler *handler,
+ int inst)
+{
+ struct switch_handler_entry *she;
+ struct proc_dir_entry *pde;
+ int mode;
+
+ she = (struct switch_handler_entry *)
+ kzalloc(sizeof(struct switch_handler_entry), GFP_KERNEL);
+ if (!she) {
+ return -ENOMEM;
+ }
+
+ INIT_LIST_HEAD(&she->node);
+ she->parent = parent;
+ she->dev = switch_dev;
+ she->inst = inst;
+ she->handler = handler;
+ list_add(&she->node, &switch_dev->handlers);
+
+ mode = 0;
+ if (handler->read != NULL) {
+ mode |= S_IRUSR;
+ }
+ if (handler->write != NULL) {
+ mode |= S_IWUSR;
+ }
+
+ pde = create_proc_entry(handler->name, mode, parent);
+ if (!pde) {
+ kfree(she);
+ printk("Failed to create node '%s' in parent %p\n",
+ handler->name, parent);
+ return -ENOMEM;
+ }
+ pde->data = (void *)she;
+ pde->proc_fops = &switch_proc_fops;
+
+ return 0;
+}
+
+/*
+ * switch_add_handlers
+ */
+static int switch_add_handlers(struct switch_device *switch_dev,
+ struct proc_dir_entry *parent,
+ const struct switch_handler *handlers,
+ int inst)
+{
+ while (handlers->name) {
+ int ret = switch_add_handler(switch_dev,
+ parent, handlers, inst);
+ if (ret) {
+ return ret;
+ }
+ handlers++;
+ }
+
+ return 0;
+}
+
+/*
+ * switch_remove_vlan_dirs
+ * Removes all vlan directories
+ *
+ * Assumes all vlan directories are empty, should be called after
+ * switch_remove_handlers
+ */
+static void switch_remove_vlan_dirs(struct switch_device *switch_dev)
+{
+ struct list_head *pos;
+ struct list_head *tmp;
+ struct switch_vlan_entry *sve;
+
+ list_for_each_safe(pos, tmp, &switch_dev->vlan_dirs) {
+ sve = list_entry(pos, struct switch_vlan_entry, node);
+ list_del(pos);
+ remove_proc_entry(sve->pde->name, switch_dev->vlan_dir);
+ kfree(sve);
+ }
+}
+
+/*
+ * switch_remove_handlers
+ * Removes all handlers registered to the given switch_device
+ */
+static void switch_remove_handlers(struct switch_device *switch_dev)
+{
+ struct list_head *pos;
+ struct list_head *tmp;
+ struct switch_handler_entry *she;
+
+ list_for_each_safe(pos, tmp, &switch_dev->handlers) {
+ she = list_entry(pos, struct switch_handler_entry, node);
+ list_del(pos);
+ remove_proc_entry(she->handler->name, she->parent);
+ kfree(she);
+ }
+}
+
+/*
+ * switch_unregister_proc_nodes
+ * Unregisters all proc nodes related to switch_dev
+ */
+void switch_unregister_proc_nodes(struct switch_device *switch_dev)
+{
+ switch_remove_handlers(switch_dev);
+
+ if (switch_dev->port_dirs) {
+ int i;
+
+ for (i = 0; i < switch_dev->ports; i++) {
+ if (switch_dev->port_dirs[i]) {
+ remove_proc_entry(
+ switch_dev->port_dirs[i]->name,
+ switch_dev->port_dir);
+ }
+ }
+ }
+
+ if (switch_dev->port_dir) {
+ remove_proc_entry("port", switch_dev->driver_dir);
+ switch_dev->port_dir = NULL;
+ }
+
+ if (switch_dev->reg_dir) {
+ remove_proc_entry("reg", switch_dev->reg_dir);
+ switch_dev->reg_dir = NULL;
+ }
+
+ if (switch_dev->vlan_dir) {
+ switch_remove_vlan_dirs(switch_dev);
+ remove_proc_entry("vlan", switch_dev->driver_dir);
+ switch_dev->vlan_dir = NULL;
+ }
+
+ if (switch_dev->driver_dir) {
+ remove_proc_entry(switch_dev->name, switch_root);
+ switch_dev->driver_dir = NULL;
+ }
+}
+
+/*
+ * switch_remove_vlan_dir
+ * Removes vlan dir in switch/<switch_driver>/vlan/<vlan_id>
+ */
+int switch_remove_vlan_dir(struct switch_device *switch_dev, int vlan_id)
+{
+ struct list_head *pos;
+ struct switch_vlan_entry *sve = NULL;
+
+ list_for_each(pos, &switch_dev->vlan_dirs) {
+ struct switch_vlan_entry *tmp =
+ list_entry(pos, struct switch_vlan_entry, node);
+ if (tmp->vlan_id == vlan_id) {
+ sve = tmp;
+ break;
+ }
+ }
+
+ if (!sve) {
+ return -ENOENT;
+ }
+
+ /*
+ * Remove it from the list
+ */
+ list_del(pos);
+
+ /*
+ * Remove the handlers
+ */
+ while (sve->handlers->name) {
+ remove_proc_entry(sve->handlers->name, sve->pde);
+ sve->handlers++;
+ }
+
+ /*
+ * Remove the proc entry for the <vlan_id> dir
+ */
+ remove_proc_entry(sve->pde->name, switch_dev->vlan_dir);
+
+ kfree(sve);
+
+ return 0;
+}
+
+/*
+ * switch_create_vlan_dir
+ * Creates vlan dir in switch/<switch_driver>/vlan/<vlan_id>
+ */
+int switch_create_vlan_dir(struct switch_device *switch_dev,
+ int vlan_id, const struct switch_handler *handlers)
+{
+ char s[14];
+ struct proc_dir_entry *pde = NULL;
+ struct switch_vlan_entry *sve = NULL;
+ int ret;
+ struct list_head *pos;
+
+ /*
+ * Check to see if it exists already
+ */
+ list_for_each(pos, &switch_dev->vlan_dirs) {
+ sve = list_entry(pos, struct switch_vlan_entry, node);
+ if (sve->vlan_id == vlan_id) {
+ return -EEXIST;
+ }
+ }
+ sve = NULL;
+
+ /*
+ * Create the vlan directory if we didn't have it before
+ */
+ if (!switch_dev->vlan_dir) {
+ switch_dev->vlan_dir = proc_mkdir("vlan",
+ switch_dev->driver_dir);
+ if (!switch_dev->vlan_dir) {
+ goto fail;
+ }
+ if (switch_dev->vlan_handlers) {
+ ret = switch_add_handlers(switch_dev,
+ switch_dev->vlan_dir,
+ switch_dev->vlan_handlers, 0);
+ if (ret) {
+ goto fail;
+ }
+ }
+ }
+
+ /*
+ * Create the vlan_id directory
+ */
+ snprintf(s, 14, "%d", vlan_id);
+ pde = proc_mkdir(s, switch_dev->vlan_dir);
+ if (!pde) {
+ goto fail;
+ }
+
+ /*
+ * Create the handlers for this vlan
+ */
+ if (handlers) {
+ ret = switch_add_handlers(switch_dev, pde, handlers, vlan_id);
+ if (ret) {
+ goto fail;
+ }
+ }
+
+ /*
+ * Keep track of all the switch vlan entries created
+ */
+ sve = (struct switch_vlan_entry *)
+ kzalloc(sizeof(struct switch_vlan_entry), GFP_KERNEL);
+ if (!sve) {
+ goto fail;
+ }
+ INIT_LIST_HEAD(&sve->node);
+ sve->handlers = handlers;
+ sve->vlan_id = vlan_id;
+ sve->pde = pde;
+ list_add(&sve->node, &switch_dev->vlan_dirs);
+
+ return 0;
+
+fail:
+ if (sve) {
+ kfree(sve);
+ }
+
+ if (pde) {
+ /*
+ * Remove any proc entries we might have created
+ */
+ while (handlers->name) {
+ remove_proc_entry(handlers->name, pde);
+ handlers++;
+ }
+
+ remove_proc_entry(s, switch_dev->driver_dir);
+ }
+
+ return -ENOMEM;
+}
+
+/*
+ * switch_register_proc_nodes
+ */
+int switch_register_proc_nodes(struct switch_device *switch_dev)
+{
+ int i;
+ int n;
+
+ switch_dev->port_dirs = kzalloc(switch_dev->ports *
+ sizeof(struct proc_dir_entry *),
+ GFP_KERNEL);
+ if (!switch_dev->port_dirs) {
+ return -ENOMEM;
+ }
+
+ /*
+ * Create a new proc entry for this switch
+ */
+ switch_dev->driver_dir = proc_mkdir(switch_dev->name, switch_root);
+ if (!switch_dev->driver_dir) {
+ goto fail;
+ }
+ if (switch_dev->driver_handlers) {
+ switch_add_handlers(switch_dev,
+ switch_dev->driver_dir,
+ switch_dev->driver_handlers,
+ 0);
+ }
+
+ /*
+ * Create the ports
+ */
+ switch_dev->port_dir = proc_mkdir("port", switch_dev->driver_dir);
+ if (!switch_dev->port_dir) {
+ goto fail;
+ }
+ for (n = 0, i = 0; i < (SWITCH_PORT_MASK_SIZE * 32); i++) {
+ if (switch_dev->port_mask[i / 32] & (1 << i % 32)) {
+ char s[14];
+
+ snprintf(s, 14, "%d", i);
+ switch_dev->port_dirs[n] =
+ proc_mkdir(s, switch_dev->port_dir);
+ if (!switch_dev->port_dirs[n]) {
+ goto fail;
+ }
+ if (switch_dev->port_handlers) {
+ switch_add_handlers(switch_dev,
+ switch_dev->port_dirs[n],
+ switch_dev->port_handlers,
+ i);
+ }
+ n++;
+ }
+ }
+
+ /*
+ * Create the register directory for switch register access.
+ */
+ if (switch_dev->reg_handlers) {
+ switch_dev->reg_dir = proc_mkdir("reg", switch_dev->driver_dir);
+ if (!switch_dev->reg_dir) {
+ goto fail;
+ }
+
+ switch_add_handlers(switch_dev,
+ switch_dev->reg_dir,
+ switch_dev->reg_handlers,
+ 0);
+ }
+
+ /*
+ * Create the vlan directory
+ */
+ if (switch_dev->vlan_handlers) {
+ switch_dev->vlan_dir = proc_mkdir("vlan",
+ switch_dev->driver_dir);
+ if (!switch_dev->vlan_dir) {
+ goto fail;
+ }
+ if (switch_dev->vlan_handlers) {
+ switch_add_handlers(switch_dev,
+ switch_dev->vlan_dir,
+ switch_dev->vlan_handlers,
+ 0);
+ }
+ }
+
+ return 0;
+
+fail:
+ switch_unregister_proc_nodes(switch_dev);
+ return -ENOMEM;
+}
+
+/*
+ * switch_release
+ */
+void switch_release(struct switch_device *switch_dev)
+{
+ kfree(switch_dev);
+}
+
+/*
+ * switch_alloc
+ */
+struct switch_device *switch_alloc(void)
+{
+ struct switch_device *switch_dev =
+ kzalloc(sizeof(struct switch_device),
+ GFP_KERNEL);
+ INIT_LIST_HEAD(&switch_dev->node);
+ INIT_LIST_HEAD(&switch_dev->vlan_dirs);
+ INIT_LIST_HEAD(&switch_dev->handlers);
+ return switch_dev;
+}
+
+/*
+ * switch_register
+ */
+int switch_register(struct switch_device *switch_dev)
+{
+ int ret;
+ int i;
+
+ /*
+ * Make sure that the number of ports and the port mask make sense
+ */
+ for (ret = 0, i = 0; i < (SWITCH_PORT_MASK_SIZE * 32); i++) {
+ if (switch_dev->port_mask[i / 32] & (1 << i % 32)) {
+ ret++;
+ }
+ }
+ if (ret > switch_dev->ports) {
+ return -EINVAL;
+ }
+
+ /*
+ * Create the /proc entries
+ */
+ ret = switch_register_proc_nodes(switch_dev);
+ if (ret) {
+ return ret;
+ }
+
+ /*
+ * Add it to the list of switches
+ */
+ down_write(&switch_list_lock);
+ list_add_tail(&switch_dev->node, &switch_list);
+ up_write(&switch_list_lock);
+
+ printk(KERN_INFO "Registered switch device: %s\n", switch_dev->name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(switch_register);
+
+/*
+ * switch_unregister
+ * Unregisters a previously registered switch_device object
+ */
+void switch_unregister(struct switch_device *switch_dev)
+{
+ /*
+ * remove the proc entries
+ */
+ switch_unregister_proc_nodes(switch_dev);
+
+ /*
+ * Remove it from the list of switches
+ */
+ down_write(&switch_list_lock);
+ list_del(&switch_dev->node);
+ up_write(&switch_list_lock);
+
+ printk(KERN_INFO "Unregistered switch device: %s\n", switch_dev->name);
+}
+EXPORT_SYMBOL_GPL(switch_unregister);
+
+/*
+ * switch_init
+ */
+static int __init switch_init(void)
+{
+ switch_root = proc_mkdir("switch", NULL);
+ if (!switch_root) {
+ printk(KERN_WARNING "Failed to make root switch node\n");
+ return -ENODEV;
+ }
+ return 0;
+}
+module_init(switch_init);
+
+/*
+ * switch_exit
+ */
+static void __exit switch_exit(void)
+{
+ remove_proc_entry("switch", NULL);
+}
+module_exit(switch_exit);
+
+MODULE_AUTHOR("Patrick Tjin");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Ethernet Switch Class Interface");