Commit | Line | Data |
---|---|---|
0b52b749 BP |
1 | /* |
2 | * | |
3 | * Copyright 1999 Digi International (www.digi.com) | |
4 | * James Puzzo <jamesp at digi dot com> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2, or (at your option) | |
9 | * any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the | |
13 | * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR | |
14 | * PURPOSE. See the GNU General Public License for more details. | |
15 | * | |
16 | */ | |
17 | ||
18 | /* | |
19 | * | |
20 | * Filename: | |
21 | * | |
22 | * dgrp_specproc.c | |
23 | * | |
24 | * Description: | |
25 | * | |
26 | * Handle the "config" proc entry for the linux realport device driver | |
27 | * and provide slots for the "net" and "mon" devices | |
28 | * | |
29 | * Author: | |
30 | * | |
31 | * James A. Puzzo | |
32 | * | |
33 | */ | |
34 | ||
35 | #include <linux/module.h> | |
36 | #include <linux/tty.h> | |
37 | #include <linux/sched.h> | |
38 | #include <linux/cred.h> | |
39 | #include <linux/proc_fs.h> | |
0d01ff25 | 40 | #include <linux/slab.h> |
0b52b749 BP |
41 | #include <linux/ctype.h> |
42 | #include <linux/seq_file.h> | |
ee98581f | 43 | #include <linux/uaccess.h> |
4dac2116 | 44 | #include <linux/vmalloc.h> |
0b52b749 BP |
45 | |
46 | #include "dgrp_common.h" | |
47 | ||
0b52b749 BP |
48 | static struct proc_dir_entry *dgrp_proc_dir_entry; |
49 | ||
50 | static int dgrp_add_id(long id); | |
51 | static int dgrp_remove_nd(struct nd_struct *nd); | |
af064cdd | 52 | static struct proc_dir_entry *add_proc_file(struct nd_struct *node, |
0b52b749 | 53 | struct proc_dir_entry *root, |
af064cdd | 54 | const struct file_operations *fops); |
0b52b749 BP |
55 | |
56 | /* File operation declarations */ | |
0b52b749 BP |
57 | static int parse_write_config(char *); |
58 | ||
900a72a6 TR |
59 | static ssize_t dgrp_config_proc_write(struct file *file, |
60 | const char __user *buffer, | |
61 | size_t count, loff_t *pos); | |
0b52b749 | 62 | |
900a72a6 TR |
63 | static int dgrp_nodeinfo_proc_open(struct inode *inode, struct file *file); |
64 | static int dgrp_info_proc_open(struct inode *inode, struct file *file); | |
65 | static int dgrp_config_proc_open(struct inode *inode, struct file *file); | |
0b52b749 | 66 | |
75ef9de1 | 67 | static const struct file_operations config_proc_file_ops = { |
0b52b749 | 68 | .owner = THIS_MODULE, |
900a72a6 | 69 | .open = dgrp_config_proc_open, |
0b52b749 BP |
70 | .read = seq_read, |
71 | .llseek = seq_lseek, | |
72 | .release = seq_release, | |
900a72a6 | 73 | .write = dgrp_config_proc_write, |
0b52b749 BP |
74 | }; |
75 | ||
75ef9de1 | 76 | static const struct file_operations info_proc_file_ops = { |
0b52b749 | 77 | .owner = THIS_MODULE, |
900a72a6 | 78 | .open = dgrp_info_proc_open, |
0b52b749 BP |
79 | .read = seq_read, |
80 | .llseek = seq_lseek, | |
69a7c503 | 81 | .release = single_release, |
0b52b749 BP |
82 | }; |
83 | ||
75ef9de1 | 84 | static const struct file_operations nodeinfo_proc_file_ops = { |
0b52b749 | 85 | .owner = THIS_MODULE, |
900a72a6 | 86 | .open = dgrp_nodeinfo_proc_open, |
0b52b749 BP |
87 | .read = seq_read, |
88 | .llseek = seq_lseek, | |
89 | .release = seq_release, | |
90 | }; | |
91 | ||
0b52b749 BP |
92 | static struct proc_dir_entry *net_entry_pointer; |
93 | static struct proc_dir_entry *mon_entry_pointer; | |
94 | static struct proc_dir_entry *dpa_entry_pointer; | |
95 | static struct proc_dir_entry *ports_entry_pointer; | |
96 | ||
af064cdd AV |
97 | static void remove_files(struct nd_struct *nd) |
98 | { | |
99 | char buf[3]; | |
100 | ID_TO_CHAR(nd->nd_ID, buf); | |
101 | dgrp_remove_node_class_sysfs_files(nd); | |
102 | if (nd->nd_net_de) | |
103 | remove_proc_entry(buf, net_entry_pointer); | |
104 | if (nd->nd_mon_de) | |
105 | remove_proc_entry(buf, mon_entry_pointer); | |
106 | if (nd->nd_dpa_de) | |
107 | remove_proc_entry(buf, dpa_entry_pointer); | |
108 | if (nd->nd_ports_de) | |
109 | remove_proc_entry(buf, ports_entry_pointer); | |
110 | } | |
111 | ||
0b52b749 BP |
112 | void dgrp_unregister_proc(void) |
113 | { | |
0b52b749 BP |
114 | net_entry_pointer = NULL; |
115 | mon_entry_pointer = NULL; | |
116 | dpa_entry_pointer = NULL; | |
117 | ports_entry_pointer = NULL; | |
118 | ||
119 | if (dgrp_proc_dir_entry) { | |
08f3d07d | 120 | struct nd_struct *nd; |
af064cdd AV |
121 | list_for_each_entry(nd, &nd_struct_list, list) |
122 | remove_files(nd); | |
08f3d07d AV |
123 | remove_proc_entry("dgrp/config", NULL); |
124 | remove_proc_entry("dgrp/info", NULL); | |
125 | remove_proc_entry("dgrp/nodeinfo", NULL); | |
126 | remove_proc_entry("dgrp/net", NULL); | |
127 | remove_proc_entry("dgrp/mon", NULL); | |
128 | remove_proc_entry("dgrp/dpa", NULL); | |
129 | remove_proc_entry("dgrp/ports", NULL); | |
130 | remove_proc_entry("dgrp", NULL); | |
0b52b749 BP |
131 | dgrp_proc_dir_entry = NULL; |
132 | } | |
0b52b749 BP |
133 | } |
134 | ||
135 | void dgrp_register_proc(void) | |
136 | { | |
137 | /* | |
138 | * Register /proc/dgrp | |
139 | */ | |
aa66d7bb | 140 | dgrp_proc_dir_entry = proc_mkdir("dgrp", NULL); |
08f3d07d | 141 | if (!dgrp_proc_dir_entry) |
d7c4660c | 142 | return; |
895b5599 AV |
143 | proc_create("dgrp/config", 0644, NULL, &config_proc_file_ops); |
144 | proc_create("dgrp/info", 0644, NULL, &info_proc_file_ops); | |
145 | proc_create("dgrp/nodeinfo", 0644, NULL, &nodeinfo_proc_file_ops); | |
08f3d07d AV |
146 | net_entry_pointer = proc_mkdir_mode("dgrp/net", 0500, NULL); |
147 | mon_entry_pointer = proc_mkdir_mode("dgrp/mon", 0500, NULL); | |
148 | dpa_entry_pointer = proc_mkdir_mode("dgrp/dpa", 0500, NULL); | |
149 | ports_entry_pointer = proc_mkdir_mode("dgrp/ports", 0500, NULL); | |
0b52b749 BP |
150 | } |
151 | ||
900a72a6 | 152 | static void *dgrp_config_proc_start(struct seq_file *m, loff_t *pos) |
0b52b749 BP |
153 | { |
154 | return seq_list_start_head(&nd_struct_list, *pos); | |
155 | } | |
156 | ||
900a72a6 | 157 | static void *dgrp_config_proc_next(struct seq_file *p, void *v, loff_t *pos) |
0b52b749 BP |
158 | { |
159 | return seq_list_next(v, &nd_struct_list, pos); | |
160 | } | |
161 | ||
900a72a6 | 162 | static void dgrp_config_proc_stop(struct seq_file *m, void *v) |
0b52b749 BP |
163 | { |
164 | } | |
165 | ||
900a72a6 | 166 | static int dgrp_config_proc_show(struct seq_file *m, void *v) |
0b52b749 BP |
167 | { |
168 | struct nd_struct *nd; | |
169 | char tmp_id[4]; | |
170 | ||
171 | if (v == &nd_struct_list) { | |
172 | seq_puts(m, "#-----------------------------------------------------------------------------\n"); | |
173 | seq_puts(m, "# Avail\n"); | |
174 | seq_puts(m, "# ID Major State Ports\n"); | |
175 | return 0; | |
176 | } | |
177 | ||
178 | nd = list_entry(v, struct nd_struct, list); | |
179 | ||
180 | ID_TO_CHAR(nd->nd_ID, tmp_id); | |
181 | ||
182 | seq_printf(m, " %-2.2s %-5ld %-10.10s %-5d\n", | |
183 | tmp_id, | |
184 | nd->nd_major, | |
185 | ND_STATE_STR(nd->nd_state), | |
186 | nd->nd_chan_count); | |
187 | ||
188 | return 0; | |
189 | } | |
190 | ||
191 | static const struct seq_operations proc_config_ops = { | |
900a72a6 TR |
192 | .start = dgrp_config_proc_start, |
193 | .next = dgrp_config_proc_next, | |
194 | .stop = dgrp_config_proc_stop, | |
195 | .show = dgrp_config_proc_show, | |
0b52b749 BP |
196 | }; |
197 | ||
900a72a6 | 198 | static int dgrp_config_proc_open(struct inode *inode, struct file *file) |
0b52b749 BP |
199 | { |
200 | return seq_open(file, &proc_config_ops); | |
201 | } | |
202 | ||
203 | ||
204 | /* | |
205 | * When writing configuration information, each "record" (i.e. each | |
206 | * write) is treated as an independent request. See the "parse" | |
207 | * description for more details. | |
208 | */ | |
900a72a6 TR |
209 | static ssize_t dgrp_config_proc_write(struct file *file, |
210 | const char __user *buffer, | |
211 | size_t count, loff_t *pos) | |
0b52b749 BP |
212 | { |
213 | ssize_t retval; | |
214 | char *inbuf, *sp; | |
215 | char *line, *ldelim; | |
216 | ||
217 | if (count > 32768) | |
218 | return -EINVAL; | |
219 | ||
220 | inbuf = sp = vzalloc(count + 1); | |
221 | if (!inbuf) | |
222 | return -ENOMEM; | |
223 | ||
224 | if (copy_from_user(inbuf, buffer, count)) { | |
225 | retval = -EFAULT; | |
226 | goto done; | |
227 | } | |
228 | ||
229 | inbuf[count] = 0; | |
230 | ||
231 | ldelim = "\n"; | |
232 | ||
233 | line = strpbrk(sp, ldelim); | |
234 | while (line) { | |
235 | *line = 0; | |
236 | retval = parse_write_config(sp); | |
237 | if (retval) | |
238 | goto done; | |
239 | ||
240 | sp = line + 1; | |
241 | line = strpbrk(sp, ldelim); | |
242 | } | |
243 | ||
244 | retval = count; | |
245 | done: | |
246 | vfree(inbuf); | |
247 | return retval; | |
248 | } | |
249 | ||
250 | /* | |
251 | * ------------------------------------------------------------------------ | |
252 | * | |
253 | * The following are the functions to parse input | |
254 | * | |
255 | * ------------------------------------------------------------------------ | |
256 | */ | |
257 | static inline char *skip_past_ws(const char *str) | |
258 | { | |
259 | while ((*str) && !isspace(*str)) | |
260 | ++str; | |
261 | ||
262 | return skip_spaces(str); | |
263 | } | |
264 | ||
265 | static int parse_id(char **c, char *cID) | |
266 | { | |
267 | int tmp = **c; | |
268 | ||
269 | if (isalnum(tmp) || (tmp == '_')) | |
270 | cID[0] = tmp; | |
271 | else | |
272 | return -EINVAL; | |
273 | ||
274 | (*c)++; tmp = **c; | |
275 | ||
276 | if (isalnum(tmp) || (tmp == '_')) { | |
277 | cID[1] = tmp; | |
278 | (*c)++; | |
279 | } else | |
280 | cID[1] = 0; | |
281 | ||
282 | return 0; | |
283 | } | |
284 | ||
285 | static int parse_add_config(char *buf) | |
286 | { | |
287 | char *c = buf; | |
288 | int retval; | |
289 | char cID[2]; | |
290 | long ID; | |
291 | ||
292 | c = skip_past_ws(c); | |
293 | ||
294 | retval = parse_id(&c, cID); | |
295 | if (retval < 0) | |
296 | return retval; | |
297 | ||
298 | ID = CHAR_TO_ID(cID); | |
299 | ||
300 | c = skip_past_ws(c); | |
301 | ||
302 | return dgrp_add_id(ID); | |
303 | } | |
304 | ||
305 | static int parse_del_config(char *buf) | |
306 | { | |
307 | char *c = buf; | |
308 | int retval; | |
309 | struct nd_struct *nd; | |
310 | char cID[2]; | |
311 | long ID; | |
312 | long major; | |
313 | ||
314 | c = skip_past_ws(c); | |
315 | ||
316 | retval = parse_id(&c, cID); | |
317 | if (retval < 0) | |
318 | return retval; | |
319 | ||
320 | ID = CHAR_TO_ID(cID); | |
321 | ||
322 | c = skip_past_ws(c); | |
323 | ||
324 | retval = kstrtol(c, 10, &major); | |
325 | if (retval) | |
326 | return retval; | |
327 | ||
328 | nd = nd_struct_get(major); | |
329 | if (!nd) | |
330 | return -EINVAL; | |
331 | ||
332 | if ((nd->nd_major != major) || (nd->nd_ID != ID)) | |
333 | return -EINVAL; | |
334 | ||
335 | return dgrp_remove_nd(nd); | |
336 | } | |
337 | ||
338 | static int parse_chg_config(char *buf) | |
339 | { | |
340 | return -EINVAL; | |
341 | } | |
342 | ||
343 | /* | |
344 | * The passed character buffer represents a single configuration request. | |
345 | * If the first character is a "+", it is parsed as a request to add a | |
346 | * PortServer | |
347 | * If the first character is a "-", it is parsed as a request to delete a | |
348 | * PortServer | |
349 | * If the first character is a "*", it is parsed as a request to change a | |
350 | * PortServer | |
351 | * Any other character (including whitespace) causes the record to be | |
352 | * ignored. | |
353 | */ | |
354 | static int parse_write_config(char *buf) | |
355 | { | |
356 | int retval; | |
357 | ||
358 | switch (buf[0]) { | |
359 | case '+': | |
360 | retval = parse_add_config(buf); | |
361 | break; | |
362 | case '-': | |
363 | retval = parse_del_config(buf); | |
364 | break; | |
365 | case '*': | |
366 | retval = parse_chg_config(buf); | |
367 | break; | |
368 | default: | |
369 | retval = -EINVAL; | |
370 | } | |
371 | ||
372 | return retval; | |
373 | } | |
374 | ||
900a72a6 | 375 | static int dgrp_info_proc_show(struct seq_file *m, void *v) |
0b52b749 BP |
376 | { |
377 | seq_printf(m, "version: %s\n", DIGI_VERSION); | |
378 | seq_puts(m, "register_with_sysfs: 1\n"); | |
0b52b749 BP |
379 | seq_printf(m, "pollrate: 0x%08x\t(%d)\n", |
380 | dgrp_poll_tick, dgrp_poll_tick); | |
381 | ||
382 | return 0; | |
383 | } | |
384 | ||
900a72a6 | 385 | static int dgrp_info_proc_open(struct inode *inode, struct file *file) |
0b52b749 | 386 | { |
900a72a6 | 387 | return single_open(file, dgrp_info_proc_show, NULL); |
0b52b749 BP |
388 | } |
389 | ||
390 | ||
900a72a6 | 391 | static void *dgrp_nodeinfo_start(struct seq_file *m, loff_t *pos) |
0b52b749 BP |
392 | { |
393 | return seq_list_start_head(&nd_struct_list, *pos); | |
394 | } | |
395 | ||
900a72a6 | 396 | static void *dgrp_nodeinfo_next(struct seq_file *p, void *v, loff_t *pos) |
0b52b749 BP |
397 | { |
398 | return seq_list_next(v, &nd_struct_list, pos); | |
399 | } | |
400 | ||
900a72a6 | 401 | static void dgrp_nodeinfo_stop(struct seq_file *m, void *v) |
0b52b749 BP |
402 | { |
403 | } | |
404 | ||
900a72a6 | 405 | static int dgrp_nodeinfo_show(struct seq_file *m, void *v) |
0b52b749 BP |
406 | { |
407 | struct nd_struct *nd; | |
408 | char hwver[8]; | |
409 | char swver[8]; | |
410 | char tmp_id[4]; | |
411 | ||
412 | if (v == &nd_struct_list) { | |
413 | seq_puts(m, "#-----------------------------------------------------------------------------\n"); | |
414 | seq_puts(m, "# HW HW SW\n"); | |
415 | seq_puts(m, "# ID State Version ID Version Description\n"); | |
416 | return 0; | |
417 | } | |
418 | ||
419 | nd = list_entry(v, struct nd_struct, list); | |
420 | ||
421 | ID_TO_CHAR(nd->nd_ID, tmp_id); | |
422 | ||
423 | if (nd->nd_state == NS_READY) { | |
424 | sprintf(hwver, "%d.%d", (nd->nd_hw_ver >> 8) & 0xff, | |
425 | nd->nd_hw_ver & 0xff); | |
426 | sprintf(swver, "%d.%d", (nd->nd_sw_ver >> 8) & 0xff, | |
427 | nd->nd_sw_ver & 0xff); | |
428 | seq_printf(m, " %-2.2s %-10.10s %-7.7s %-3d %-7.7s %-35.35s\n", | |
429 | tmp_id, | |
430 | ND_STATE_STR(nd->nd_state), | |
431 | hwver, | |
432 | nd->nd_hw_id, | |
433 | swver, | |
434 | nd->nd_ps_desc); | |
435 | ||
436 | } else { | |
437 | seq_printf(m, " %-2.2s %-10.10s\n", | |
438 | tmp_id, | |
439 | ND_STATE_STR(nd->nd_state)); | |
440 | } | |
441 | ||
442 | return 0; | |
443 | } | |
444 | ||
445 | ||
446 | static const struct seq_operations nodeinfo_ops = { | |
900a72a6 TR |
447 | .start = dgrp_nodeinfo_start, |
448 | .next = dgrp_nodeinfo_next, | |
449 | .stop = dgrp_nodeinfo_stop, | |
450 | .show = dgrp_nodeinfo_show, | |
0b52b749 BP |
451 | }; |
452 | ||
900a72a6 | 453 | static int dgrp_nodeinfo_proc_open(struct inode *inode, struct file *file) |
0b52b749 BP |
454 | { |
455 | return seq_open(file, &nodeinfo_ops); | |
456 | } | |
457 | ||
458 | /** | |
459 | * dgrp_add_id() -- creates new nd struct and adds it to list | |
460 | * @id: id of device to add | |
461 | */ | |
462 | static int dgrp_add_id(long id) | |
463 | { | |
464 | struct nd_struct *nd; | |
465 | int ret; | |
466 | int i; | |
467 | ||
468 | nd = kzalloc(sizeof(struct nd_struct), GFP_KERNEL); | |
469 | if (!nd) | |
470 | return -ENOMEM; | |
471 | ||
472 | nd->nd_major = 0; | |
473 | nd->nd_ID = id; | |
474 | ||
475 | spin_lock_init(&nd->nd_lock); | |
476 | ||
477 | init_waitqueue_head(&nd->nd_tx_waitq); | |
478 | init_waitqueue_head(&nd->nd_mon_wqueue); | |
479 | init_waitqueue_head(&nd->nd_dpa_wqueue); | |
af064cdd AV |
480 | sema_init(&nd->nd_mon_semaphore, 1); |
481 | sema_init(&nd->nd_net_semaphore, 1); | |
482 | spin_lock_init(&nd->nd_dpa_lock); | |
483 | nd->nd_state = NS_CLOSED; | |
0b52b749 BP |
484 | for (i = 0; i < SEQ_MAX; i++) |
485 | init_waitqueue_head(&nd->nd_seq_wque[i]); | |
486 | ||
487 | /* setup the structures to get the major number */ | |
488 | ret = dgrp_tty_init(nd); | |
489 | if (ret) | |
490 | goto error_out; | |
491 | ||
492 | nd->nd_major = nd->nd_serial_ttdriver->major; | |
493 | ||
494 | ret = nd_struct_add(nd); | |
495 | if (ret) | |
496 | goto error_out; | |
497 | ||
af064cdd AV |
498 | dgrp_create_node_class_sysfs_files(nd); |
499 | nd->nd_net_de = add_proc_file(nd, net_entry_pointer, &dgrp_net_ops); | |
500 | nd->nd_mon_de = add_proc_file(nd, mon_entry_pointer, &dgrp_mon_ops); | |
501 | nd->nd_dpa_de = add_proc_file(nd, dpa_entry_pointer, &dgrp_dpa_ops); | |
502 | nd->nd_ports_de = add_proc_file(nd, ports_entry_pointer, | |
503 | &dgrp_ports_ops); | |
0b52b749 BP |
504 | return 0; |
505 | ||
191c5f10 JS |
506 | /* FIXME this guy should free the tty driver stored in nd and destroy |
507 | * all channel ports */ | |
0b52b749 BP |
508 | error_out: |
509 | kfree(nd); | |
510 | return ret; | |
511 | ||
512 | } | |
513 | ||
514 | static int dgrp_remove_nd(struct nd_struct *nd) | |
515 | { | |
516 | int ret; | |
517 | ||
518 | /* Check to see if the selected structure is in use */ | |
519 | if (nd->nd_tty_ref_cnt) | |
520 | return -EBUSY; | |
521 | ||
af064cdd | 522 | remove_files(nd); |
0b52b749 BP |
523 | |
524 | dgrp_tty_uninit(nd); | |
525 | ||
526 | ret = nd_struct_del(nd); | |
527 | if (ret) | |
528 | return ret; | |
529 | ||
530 | kfree(nd); | |
531 | return 0; | |
532 | } | |
533 | ||
af064cdd | 534 | static struct proc_dir_entry *add_proc_file(struct nd_struct *node, |
0b52b749 | 535 | struct proc_dir_entry *root, |
af064cdd | 536 | const struct file_operations *fops) |
0b52b749 BP |
537 | { |
538 | char buf[3]; | |
0b52b749 | 539 | ID_TO_CHAR(node->nd_ID, buf); |
895b5599 | 540 | return proc_create_data(buf, 0600, root, fops, node); |
0b52b749 | 541 | } |