switch: export cpuport, ports and vlans via /proc
[15.05/openwrt.git] / package / switch / src / switch-core.c
1 /*
2  * switch-core.c
3  *
4  * Copyright (C) 2005 Felix Fietkau <openwrt@nbd.name>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19  *
20  *
21  * Basic doc of driver's /proc interface:
22  * /proc/switch/<interface>/
23  *   registers:              read-only
24  *   counters:               read-only
25  *   reset:                  write causes hardware reset
26  *   enable_vlan:            "0", "1"
27  *   port/<port-number>/
28  *     enabled:              "0", "1"
29  *     media:                "AUTO", "1000FD", "1000HD", "100FD", "100HD", "10FD", "10HD"
30  *   vlan/<port-number>/
31  *     ports: same syntax as for nvram's vlan*ports (eg. "1 2 3 4 5*")
32  */
33
34 #include <linux/module.h>
35 #include <linux/init.h>
36 #include <asm/uaccess.h>
37 #include <linux/proc_fs.h>
38 #include <linux/list.h>
39
40 #include "switch-core.h"
41
42 static int drv_num = 0;
43 static struct proc_dir_entry *switch_root;
44 switch_driver drivers;
45
46 typedef struct {
47         struct list_head list;
48         struct proc_dir_entry *parent;
49         int nr;
50         void *driver;
51         switch_config handler;
52 } switch_proc_handler;
53
54 typedef struct {
55         struct proc_dir_entry *driver_dir, *port_dir, *vlan_dir;
56         struct proc_dir_entry **ports, **vlans;
57         switch_proc_handler data;
58         int nr;
59 } switch_priv;
60
61 static ssize_t switch_proc_read(struct file *file, char *buf, size_t count, loff_t *ppos);
62 static ssize_t switch_proc_write(struct file *file, const char *buf, size_t count, void *data);
63
64 static struct file_operations switch_proc_fops = {
65         .read = (ssize_t (*) (struct file *, char __user *, size_t, loff_t *))switch_proc_read,
66         .write = (ssize_t (*) (struct file *, const char __user *, size_t, loff_t *))switch_proc_write
67 };
68
69 static ssize_t switch_proc_read(struct file *file, char *buf, size_t count, loff_t *ppos)
70 {
71         struct proc_dir_entry *dent = PDE(file->f_dentry->d_inode);
72         char *page;
73         int len = 0;
74
75         if ((page = kmalloc(SWITCH_MAX_BUFSZ, GFP_KERNEL)) == NULL)
76                 return -ENOBUFS;
77
78         if (dent->data != NULL) {
79                 switch_proc_handler *handler = (switch_proc_handler *) dent->data;
80                 if (handler->handler.read != NULL)
81                         len += handler->handler.read(handler->driver, page + len, handler->nr);
82         }
83         len += 1;
84
85         if (*ppos < len) {
86                 len = min_t(int, len - *ppos, count);
87                 if (copy_to_user(buf, (page + *ppos), len)) {
88                         kfree(page);
89                         return -EFAULT;
90                 }
91                 *ppos += len;
92         } else {
93                 len = 0;
94         }
95
96         kfree(page);
97         return len;
98 }
99
100
101 static ssize_t switch_proc_write(struct file *file, const char *buf, size_t count, void *data)
102 {
103         struct proc_dir_entry *dent = PDE(file->f_dentry->d_inode);
104         char *page;
105         int ret = -EINVAL;
106
107         if ((page = kmalloc(count + 1, GFP_KERNEL)) == NULL)
108                 return -ENOBUFS;
109
110         if (copy_from_user(page, buf, count)) {
111                 kfree(page);
112                 return -EINVAL;
113         }
114         page[count] = 0;
115
116         if (dent->data != NULL) {
117                 switch_proc_handler *handler = (switch_proc_handler *) dent->data;
118                 if (handler->handler.write != NULL) {
119                         if ((ret = handler->handler.write(handler->driver, page, handler->nr)) >= 0)
120                                 ret = count;
121                 }
122         }
123
124         kfree(page);
125         return ret;
126 }
127
128 static int handle_driver_name(void *driver, char *buf, int nr)
129 {
130         const char *name = ((switch_driver *) driver)->name;
131         return sprintf(buf, "%s\n", name);
132 }
133
134 static int handle_driver_version(void *driver, char *buf, int nr)
135 {
136         const char *version = ((switch_driver *) driver)->version;
137         strcpy(buf, version);
138         return sprintf(buf, "%s\n", version);
139 }
140
141 static int handle_driver_cpuport(void *driver, char *buf, int nr)
142 {
143         int cpuport = ((switch_driver *) driver)->cpuport;
144         return sprintf(buf, "%i\n", cpuport);
145 }
146
147 static int handle_driver_ports(void *driver, char *buf, int nr)
148 {
149         int ports = ((switch_driver *) driver)->ports;
150         return sprintf(buf, "%i\n", ports);
151 }
152
153 static int handle_driver_vlans(void *driver, char *buf, int nr)
154 {
155         int vlans = ((switch_driver *) driver)->vlans;
156         return sprintf(buf, "%i\n", vlans);
157 }
158
159 static void add_handler(switch_driver *driver, const switch_config *handler, struct proc_dir_entry *parent, int nr)
160 {
161         switch_priv *priv = (switch_priv *) driver->data;
162         struct proc_dir_entry *p;
163         int mode;
164
165         switch_proc_handler *tmp;
166         tmp = (switch_proc_handler *) kmalloc(sizeof(switch_proc_handler), GFP_KERNEL);
167         if (!tmp)
168                 return;
169         INIT_LIST_HEAD(&tmp->list);
170         tmp->parent = parent;
171         tmp->nr = nr;
172         tmp->driver = driver;
173         memcpy(&tmp->handler, handler, sizeof(switch_config));
174         list_add(&tmp->list, &priv->data.list);
175
176         mode = 0;
177         if (handler->read != NULL) mode |= S_IRUSR;
178         if (handler->write != NULL) mode |= S_IWUSR;
179
180         if ((p = create_proc_entry(handler->name, mode, parent)) != NULL) {
181                 p->data = (void *) tmp;
182                 p->proc_fops = &switch_proc_fops;
183         }
184 }
185
186 static inline void add_handlers(switch_driver *driver, const switch_config *handlers, struct proc_dir_entry *parent, int nr)
187 {
188         int i;
189
190         for (i = 0; handlers[i].name != NULL; i++) {
191                 add_handler(driver, &(handlers[i]), parent, nr);
192         }
193 }
194
195 static void remove_handlers(switch_priv *priv)
196 {
197         struct list_head *pos, *q;
198         switch_proc_handler *tmp;
199
200         list_for_each_safe(pos, q, &priv->data.list) {
201                 tmp = list_entry(pos, switch_proc_handler, list);
202                 list_del(pos);
203                 remove_proc_entry(tmp->handler.name, tmp->parent);
204                 kfree(tmp);
205         }
206 }
207
208
209 static void do_unregister(switch_driver *driver)
210 {
211         char buf[4];
212         int i;
213         switch_priv *priv = (switch_priv *) driver->data;
214
215         remove_handlers(priv);
216
217         for(i = 0; priv->ports[i] != NULL; i++) {
218                 sprintf(buf, "%d", i);
219                 remove_proc_entry(buf, priv->port_dir);
220         }
221         kfree(priv->ports);
222         remove_proc_entry("port", priv->driver_dir);
223
224         for(i = 0; priv->vlans[i] != NULL; i++) {
225                 sprintf(buf, "%d", i);
226                 remove_proc_entry(buf, priv->vlan_dir);
227         }
228         kfree(priv->vlans);
229         remove_proc_entry("vlan", priv->driver_dir);
230
231         remove_proc_entry(driver->interface, switch_root);
232
233         if (priv->nr == (drv_num - 1))
234                 drv_num--;
235
236         kfree(priv);
237 }
238
239 switch_config global_driver_handlers[] = {
240         {"driver", handle_driver_name, NULL},
241         {"version", handle_driver_version, NULL},
242         {"cpuport", handle_driver_cpuport, NULL},
243         {"ports", handle_driver_ports, NULL},
244         {"vlans", handle_driver_vlans, NULL},
245         {NULL, NULL, NULL}
246 };
247
248 static int do_register(switch_driver *driver)
249 {
250         switch_priv *priv;
251         int i;
252         char buf[4];
253
254         priv = kmalloc(sizeof(switch_priv), GFP_KERNEL);
255         if (!priv)
256                 return -ENOMEM;
257         driver->data = (void *) priv;
258
259         priv->ports = kmalloc((driver->ports + 1) * sizeof(struct proc_dir_entry *),
260                               GFP_KERNEL);
261         if (!priv->ports) {
262                 kfree(priv);
263                 return -ENOMEM;
264         }
265         priv->vlans = kmalloc((driver->vlans + 1) * sizeof(struct proc_dir_entry *),
266                               GFP_KERNEL);
267         if (!priv->vlans) {
268                 kfree(priv->ports);
269                 kfree(priv);
270                 return -ENOMEM;
271         }
272
273         INIT_LIST_HEAD(&priv->data.list);
274
275         priv->nr = drv_num++;
276         priv->driver_dir = proc_mkdir(driver->interface, switch_root);
277         if (driver->driver_handlers != NULL) {
278                 add_handlers(driver, driver->driver_handlers, priv->driver_dir, 0);
279                 add_handlers(driver, global_driver_handlers, priv->driver_dir, 0);
280         }
281
282         priv->port_dir = proc_mkdir("port", priv->driver_dir);
283         for (i = 0; i < driver->ports; i++) {
284                 sprintf(buf, "%d", i);
285                 priv->ports[i] = proc_mkdir(buf, priv->port_dir);
286                 if (driver->port_handlers != NULL)
287                         add_handlers(driver, driver->port_handlers, priv->ports[i], i);
288         }
289         priv->ports[i] = NULL;
290
291         priv->vlan_dir = proc_mkdir("vlan", priv->driver_dir);
292         for (i = 0; i < driver->vlans; i++) {
293                 sprintf(buf, "%d", i);
294                 priv->vlans[i] = proc_mkdir(buf, priv->vlan_dir);
295                 if (driver->vlan_handlers != NULL)
296                         add_handlers(driver, driver->vlan_handlers, priv->vlans[i], i);
297         }
298         priv->vlans[i] = NULL;
299
300
301         return 0;
302 }
303
304 static inline int isspace(char c) {
305         switch(c) {
306                 case ' ':
307                 case 0x09:
308                 case 0x0a:
309                 case 0x0d:
310                         return 1;
311                 default:
312                         return 0;
313         }
314 }
315
316 #define toupper(c) (islower(c) ? ((c) ^ 0x20) : (c))
317 #define islower(c) (((unsigned char)((c) - 'a')) < 26)
318
319 int switch_parse_media(char *buf)
320 {
321         char *str = buf;
322         while (*buf != 0) {
323                 *buf = toupper(*buf);
324                 buf++;
325         }
326
327         if (strncmp(str, "AUTO", 4) == 0)
328                 return SWITCH_MEDIA_AUTO;
329         else if (strncmp(str, "1000FD", 6) == 0)
330                 return SWITCH_MEDIA_1000 | SWITCH_MEDIA_FD;
331         else if (strncmp(str, "1000HD", 6) == 0)
332                 return SWITCH_MEDIA_1000;
333         else if (strncmp(str, "100FD", 5) == 0)
334                 return SWITCH_MEDIA_100 | SWITCH_MEDIA_FD;
335         else if (strncmp(str, "100HD", 5) == 0)
336                 return SWITCH_MEDIA_100;
337         else if (strncmp(str, "10FD", 4) == 0)
338                 return SWITCH_MEDIA_FD;
339         else if (strncmp(str, "10HD", 4) == 0)
340                 return 0;
341         else return -1;
342 }
343
344 int switch_print_media(char *buf, int media)
345 {
346         int len = 0;
347
348         if (media & SWITCH_MEDIA_AUTO)
349                 len = sprintf(buf, "Auto");
350         else if (media == (SWITCH_MEDIA_1000 | SWITCH_MEDIA_FD))
351                 len = sprintf(buf, "1000FD");
352         else if (media == SWITCH_MEDIA_1000)
353                 len = sprintf(buf, "1000HD");
354         else if (media == (SWITCH_MEDIA_100 | SWITCH_MEDIA_FD))
355                 len = sprintf(buf, "100FD");
356         else if (media == SWITCH_MEDIA_100)
357                 len = sprintf(buf, "100HD");
358         else if (media == SWITCH_MEDIA_FD)
359                 len = sprintf(buf, "10FD");
360         else if (media == 0)
361                 len = sprintf(buf, "10HD");
362         else
363                 len = sprintf(buf, "Invalid");
364
365         return len;
366 }
367
368 switch_vlan_config *switch_parse_vlan(switch_driver *driver, char *buf)
369 {
370         switch_vlan_config *c;
371         int j, u, p, s;
372
373         c = kzalloc(sizeof(switch_vlan_config), GFP_KERNEL);
374         if (!c)
375                 return NULL;
376
377         while (isspace(*buf)) buf++;
378         j = 0;
379         while (*buf >= '0' && *buf <= '9') {
380                 j *= 10;
381                 j += *buf++ - '0';
382
383                 u = ((j == driver->cpuport) ? 0 : 1);
384                 p = 0;
385                 s = !(*buf >= '0' && *buf <= '9');
386
387                 if (s) {
388                         while (s && !isspace(*buf) && (*buf != 0)) {
389                                 switch(*buf) {
390                                         case 'u':
391                                                 u = 1;
392                                                 break;
393                                         case 't':
394                                                 u = 0;
395                                                 break;
396                                         case '*':
397                                                 p = 1;
398                                                 break;
399                                 }
400                                 buf++;
401                         }
402                         c->port |= (1 << j);
403                         if (u)
404                                 c->untag |= (1 << j);
405                         if (p)
406                                 c->pvid |= (1 << j);
407
408                         j = 0;
409                 }
410
411                 while (isspace(*buf)) buf++;
412         }
413         if (*buf != 0) {
414                 kfree(c);
415                 return NULL;
416         }
417
418         c->port &= (1 << driver->ports) - 1;
419         c->untag &= (1 << driver->ports) - 1;
420         c->pvid &= (1 << driver->ports) - 1;
421
422         return c;
423 }
424
425
426 int switch_device_registered (char* device) {
427         struct list_head *pos;
428
429         list_for_each(pos, &drivers.list) {
430                 if (strcmp(list_entry(pos, switch_driver, list)->interface, device) == 0) {
431                         printk("There is already a switch registered on the device '%s'\n", device);
432                         return -EINVAL;
433                 }
434         }
435
436         return 0;
437 }
438
439
440 int switch_register_driver(switch_driver *driver)
441 {
442         struct list_head *pos;
443         switch_driver *new;
444         int ret;
445
446         list_for_each(pos, &drivers.list) {
447                 if (strcmp(list_entry(pos, switch_driver, list)->name, driver->name) == 0) {
448                         printk("Switch driver '%s' already exists in the kernel\n", driver->name);
449                         return -EINVAL;
450                 }
451                 if (strcmp(list_entry(pos, switch_driver, list)->interface, driver->interface) == 0) {
452                         printk("There is already a switch registered on the device '%s'\n", driver->interface);
453                         return -EINVAL;
454                 }
455         }
456
457         new = kmalloc(sizeof(switch_driver), GFP_KERNEL);
458         if (!new)
459                 return -ENOMEM;
460         memcpy(new, driver, sizeof(switch_driver));
461         new->name = strdup(driver->name);
462         new->interface = strdup(driver->interface);
463
464         if ((ret = do_register(new)) < 0) {
465                 kfree(new->name);
466                 kfree(new);
467                 return ret;
468         }
469         INIT_LIST_HEAD(&new->list);
470         list_add(&new->list, &drivers.list);
471
472         return 0;
473 }
474
475 void switch_unregister_driver(char *name) {
476         struct list_head *pos, *q;
477         switch_driver *tmp;
478
479         list_for_each_safe(pos, q, &drivers.list) {
480                 tmp = list_entry(pos, switch_driver, list);
481                 if (strcmp(tmp->name, name) == 0) {
482                         do_unregister(tmp);
483                         list_del(pos);
484                         kfree(tmp->name);
485                         kfree(tmp);
486
487                         return;
488                 }
489         }
490 }
491
492 static int __init switch_init(void)
493 {
494         if ((switch_root = proc_mkdir("switch", NULL)) == NULL) {
495                 printk("%s: proc_mkdir failed.\n", __FILE__);
496                 return -ENODEV;
497         }
498
499         INIT_LIST_HEAD(&drivers.list);
500
501         return 0;
502 }
503
504 static void __exit switch_exit(void)
505 {
506         remove_proc_entry("switch", NULL);
507 }
508
509 MODULE_AUTHOR("Felix Fietkau <openwrt@nbd.name>");
510 MODULE_LICENSE("GPL");
511
512 EXPORT_SYMBOL(switch_device_registered);
513 EXPORT_SYMBOL(switch_register_driver);
514 EXPORT_SYMBOL(switch_unregister_driver);
515 EXPORT_SYMBOL(switch_parse_vlan);
516 EXPORT_SYMBOL(switch_parse_media);
517 EXPORT_SYMBOL(switch_print_media);
518
519 module_init(switch_init);
520 module_exit(switch_exit);