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