Commit | Line | Data |
---|---|---|
50ee11fe BB |
1 | /* Copyright (C) 2007-2008 One Stop Systems |
2 | * Copyright (C) 2003-2006 SBE, Inc. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | */ | |
14 | ||
e6e4d05d JP |
15 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
16 | ||
50ee11fe BB |
17 | #include <linux/types.h> |
18 | #include <linux/netdevice.h> | |
19 | #include <linux/hdlc.h> | |
20 | #include <linux/if_arp.h> | |
21 | #include <linux/init.h> | |
22 | #include <asm/uaccess.h> | |
23 | #include <linux/rtnetlink.h> | |
24 | #include <linux/skbuff.h> | |
25 | #include "pmcc4_sysdep.h" | |
26 | #include "sbecom_inline_linux.h" | |
27 | #include "libsbew.h" | |
28 | #include "pmcc4.h" | |
29 | #include "pmcc4_ioctls.h" | |
30 | #include "pmcc4_private.h" | |
31 | #include "sbeproc.h" | |
32 | ||
33 | /***************************************************************************************** | |
34 | * Error out early if we have compiler trouble. | |
35 | * | |
36 | * (This section is included from the kernel's init/main.c as a friendly | |
37 | * spiderman recommendation...) | |
38 | * | |
39 | * Versions of gcc older than that listed below may actually compile and link | |
40 | * okay, but the end product can have subtle run time bugs. To avoid associated | |
41 | * bogus bug reports, we flatly refuse to compile with a gcc that is known to be | |
42 | * too old from the very beginning. | |
43 | */ | |
44 | #if (__GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 2) | |
45 | #error Sorry, your GCC is too old. It builds incorrect kernels. | |
46 | #endif | |
47 | ||
48 | #if __GNUC__ == 4 && __GNUC_MINOR__ == 1 && __GNUC_PATCHLEVEL__ == 0 | |
49 | #warning gcc-4.1.0 is known to miscompile the kernel. A different compiler version is recommended. | |
50 | #endif | |
51 | ||
52 | /*****************************************************************************************/ | |
53 | ||
54 | #ifdef SBE_INCLUDE_SYMBOLS | |
55 | #define STATIC | |
56 | #else | |
57 | #define STATIC static | |
58 | #endif | |
59 | ||
60 | #define CHANNAME "hdlc" | |
61 | ||
62 | /*******************************************************************/ | |
63 | /* forward references */ | |
64 | status_t c4_chan_work_init (mpi_t *, mch_t *); | |
65 | void musycc_wq_chan_restart (void *); | |
66 | status_t __init c4_init (ci_t *, u_char *, u_char *); | |
67 | status_t __init c4_init2 (ci_t *); | |
68 | ci_t *__init c4_new (void *); | |
69 | int __init c4hw_attach_all (void); | |
70 | void __init hdw_sn_get (hdw_info_t *, int); | |
71 | ||
72 | #ifdef CONFIG_SBE_PMCC4_NCOMM | |
73 | irqreturn_t c4_ebus_intr_th_handler (void *); | |
74 | ||
75 | #endif | |
76 | int c4_frame_rw (ci_t *, struct sbecom_port_param *); | |
77 | status_t c4_get_port (ci_t *, int); | |
78 | int c4_loop_port (ci_t *, int, u_int8_t); | |
79 | int c4_musycc_rw (ci_t *, struct c4_musycc_param *); | |
80 | int c4_new_chan (ci_t *, int, int, void *); | |
81 | status_t c4_set_port (ci_t *, int); | |
82 | int c4_pld_rw (ci_t *, struct sbecom_port_param *); | |
83 | void cleanup_devs (void); | |
84 | void cleanup_ioremap (void); | |
85 | status_t musycc_chan_down (ci_t *, int); | |
86 | irqreturn_t musycc_intr_th_handler (void *); | |
87 | int musycc_start_xmit (ci_t *, int, void *); | |
88 | ||
89 | extern char pmcc4_OSSI_release[]; | |
90 | extern ci_t *CI; | |
91 | extern struct s_hdw_info hdw_info[]; | |
92 | ||
93 | #if defined(CONFIG_SBE_HDLC_V7) || defined(CONFIG_SBE_WAN256T3_HDLC_V7) || \ | |
94 | defined(CONFIG_SBE_HDLC_V7_MODULE) || defined(CONFIG_SBE_WAN256T3_HDLC_V7_MODULE) | |
95 | #define _v7_hdlc_ 1 | |
96 | #else | |
97 | #define _v7_hdlc_ 0 | |
98 | #endif | |
99 | ||
100 | #if _v7_hdlc_ | |
101 | #define V7(x) (x ## _v7) | |
102 | extern int hdlc_netif_rx_v7 (hdlc_device *, struct sk_buff *); | |
103 | extern int register_hdlc_device_v7 (hdlc_device *); | |
104 | extern int unregister_hdlc_device_v7 (hdlc_device *); | |
105 | ||
106 | #else | |
107 | #define V7(x) x | |
108 | #endif | |
109 | ||
110 | int error_flag; /* module load error reporting */ | |
111 | int log_level = LOG_ERROR; | |
112 | int log_level_default = LOG_ERROR; | |
113 | module_param(log_level, int, 0444); | |
114 | ||
115 | int max_mru = MUSYCC_MRU; | |
116 | int max_mru_default = MUSYCC_MRU; | |
117 | module_param(max_mru, int, 0444); | |
118 | ||
119 | int max_mtu = MUSYCC_MTU; | |
120 | int max_mtu_default = MUSYCC_MTU; | |
121 | module_param(max_mtu, int, 0444); | |
122 | ||
123 | int max_txdesc_used = MUSYCC_TXDESC_MIN; | |
124 | int max_txdesc_default = MUSYCC_TXDESC_MIN; | |
125 | module_param(max_txdesc_used, int, 0444); | |
126 | ||
127 | int max_rxdesc_used = MUSYCC_RXDESC_MIN; | |
128 | int max_rxdesc_default = MUSYCC_RXDESC_MIN; | |
129 | module_param(max_rxdesc_used, int, 0444); | |
130 | ||
131 | /****************************************************************************/ | |
132 | /****************************************************************************/ | |
133 | /****************************************************************************/ | |
134 | ||
135 | void * | |
136 | getuserbychan (int channum) | |
137 | { | |
138 | mch_t *ch; | |
139 | ||
140 | ch = c4_find_chan (channum); | |
141 | return ch ? ch->user : 0; | |
142 | } | |
143 | ||
144 | ||
50ee11fe BB |
145 | char * |
146 | get_hdlc_name (hdlc_device * hdlc) | |
147 | { | |
148 | struct c4_priv *priv = hdlc->priv; | |
149 | struct net_device *dev = getuserbychan (priv->channum); | |
150 | ||
151 | return dev->name; | |
152 | } | |
50ee11fe BB |
153 | |
154 | ||
155 | static status_t | |
156 | mkret (int bsd) | |
157 | { | |
158 | if (bsd > 0) | |
159 | return -bsd; | |
160 | else | |
161 | return bsd; | |
162 | } | |
163 | ||
164 | /***************************************************************************/ | |
50ee11fe BB |
165 | #include <linux/workqueue.h> |
166 | ||
167 | /*** | |
168 | * One workqueue (wq) per port (since musycc allows simultaneous group | |
169 | * commands), with individual data for each channel: | |
170 | * | |
171 | * mpi_t -> struct workqueue_struct *wq_port; (dynamically allocated using | |
172 | * create_workqueue()) | |
173 | * | |
174 | * With work structure (work) statically allocated for each channel: | |
175 | * | |
176 | * mch_t -> struct work_struct ch_work; (statically allocated using ???) | |
177 | * | |
178 | ***/ | |
179 | ||
180 | ||
181 | /* | |
182 | * Called by the start transmit routine when a channel TX_ENABLE is to be | |
183 | * issued. This queues the transmission start request among other channels | |
184 | * within a port's group. | |
185 | */ | |
186 | void | |
187 | c4_wk_chan_restart (mch_t * ch) | |
188 | { | |
189 | mpi_t *pi = ch->up; | |
190 | ||
191 | #ifdef RLD_RESTART_DEBUG | |
694a9807 JP |
192 | pr_info(">> %s: queueing Port %d Chan %d, mch_t @ %p\n", |
193 | __func__, pi->portnum, ch->channum, ch); | |
50ee11fe BB |
194 | #endif |
195 | ||
196 | /* create new entry w/in workqueue for this channel and let'er rip */ | |
197 | ||
198 | /** queue_work (struct workqueue_struct *queue, | |
199 | ** struct work_struct *work); | |
200 | **/ | |
201 | queue_work (pi->wq_port, &ch->ch_work); | |
202 | } | |
203 | ||
204 | status_t | |
205 | c4_wk_chan_init (mpi_t * pi, mch_t * ch) | |
206 | { | |
207 | /* | |
208 | * this will be used to restart a stopped channel | |
209 | */ | |
210 | ||
211 | /** INIT_WORK (struct work_struct *work, | |
212 | ** void (*function)(void *), | |
213 | ** void *data); | |
214 | **/ | |
215 | INIT_WORK(&ch->ch_work, (void *)musycc_wq_chan_restart); | |
216 | return 0; /* success */ | |
217 | } | |
218 | ||
219 | status_t | |
220 | c4_wq_port_init (mpi_t * pi) | |
221 | { | |
222 | ||
223 | char name[16], *np; /* NOTE: name of the queue limited by system | |
224 | * to 10 characters */ | |
225 | ||
226 | if (pi->wq_port) | |
227 | return 0; /* already initialized */ | |
228 | ||
229 | np = name; | |
230 | memset (name, 0, 16); | |
231 | sprintf (np, "%s%d", pi->up->devname, pi->portnum); /* IE pmcc4-01) */ | |
232 | ||
233 | #ifdef RLD_RESTART_DEBUG | |
694a9807 JP |
234 | pr_info(">> %s: creating workqueue <%s> for Port %d.\n", |
235 | __func__, name, pi->portnum); /* RLD DEBUG */ | |
50ee11fe BB |
236 | #endif |
237 | if (!(pi->wq_port = create_singlethread_workqueue (name))) | |
238 | return ENOMEM; | |
239 | return 0; /* success */ | |
240 | } | |
241 | ||
242 | void | |
243 | c4_wq_port_cleanup (mpi_t * pi) | |
244 | { | |
245 | /* | |
246 | * PORT POINT: cannot call this if WQ is statically allocated w/in | |
247 | * structure since it calls kfree(wq); | |
248 | */ | |
249 | if (pi->wq_port) | |
250 | { | |
251 | destroy_workqueue (pi->wq_port); /* this also calls | |
252 | * flush_workqueue() */ | |
253 | pi->wq_port = 0; | |
254 | } | |
255 | } | |
50ee11fe BB |
256 | |
257 | /***************************************************************************/ | |
258 | ||
259 | irqreturn_t | |
260 | c4_linux_interrupt (int irq, void *dev_instance) | |
261 | { | |
262 | struct net_device *ndev = dev_instance; | |
263 | ||
264 | return musycc_intr_th_handler(netdev_priv(ndev)); | |
265 | } | |
266 | ||
267 | ||
268 | #ifdef CONFIG_SBE_PMCC4_NCOMM | |
269 | irqreturn_t | |
270 | c4_ebus_interrupt (int irq, void *dev_instance) | |
271 | { | |
272 | struct net_device *ndev = dev_instance; | |
273 | ||
274 | return c4_ebus_intr_th_handler(netdev_priv(ndev)); | |
275 | } | |
276 | #endif | |
277 | ||
278 | ||
279 | static int | |
280 | void_open (struct net_device * ndev) | |
281 | { | |
694a9807 | 282 | pr_info("%s: trying to open master device !\n", ndev->name); |
50ee11fe BB |
283 | return -1; |
284 | } | |
285 | ||
286 | ||
50ee11fe BB |
287 | STATIC int |
288 | chan_open (struct net_device * ndev) | |
289 | { | |
290 | hdlc_device *hdlc = dev_to_hdlc (ndev); | |
291 | const struct c4_priv *priv = hdlc->priv; | |
292 | int ret; | |
293 | ||
294 | if ((ret = hdlc_open (ndev))) | |
295 | { | |
694a9807 | 296 | pr_info("hdlc_open failure, err %d.\n", ret); |
50ee11fe BB |
297 | return ret; |
298 | } | |
299 | if ((ret = c4_chan_up (priv->ci, priv->channum))) | |
300 | return -ret; | |
301 | try_module_get (THIS_MODULE); | |
302 | netif_start_queue (ndev); | |
303 | return 0; /* no error = success */ | |
304 | } | |
50ee11fe | 305 | |
50ee11fe | 306 | |
50ee11fe BB |
307 | STATIC int |
308 | chan_close (struct net_device * ndev) | |
309 | { | |
310 | hdlc_device *hdlc = dev_to_hdlc (ndev); | |
311 | const struct c4_priv *priv = hdlc->priv; | |
312 | ||
313 | netif_stop_queue (ndev); | |
314 | musycc_chan_down ((ci_t *) 0, priv->channum); | |
315 | hdlc_close (ndev); | |
316 | module_put (THIS_MODULE); | |
317 | return 0; | |
318 | } | |
50ee11fe BB |
319 | |
320 | ||
50ee11fe BB |
321 | STATIC int |
322 | chan_dev_ioctl (struct net_device * dev, struct ifreq * ifr, int cmd) | |
323 | { | |
324 | return hdlc_ioctl (dev, ifr, cmd); | |
325 | } | |
326 | ||
327 | ||
328 | STATIC int | |
50ee11fe | 329 | chan_attach_noop (struct net_device * ndev, unsigned short foo_1, unsigned short foo_2) |
50ee11fe BB |
330 | { |
331 | return 0; /* our driver has nothing to do here, show's | |
332 | * over, go home */ | |
333 | } | |
50ee11fe BB |
334 | |
335 | ||
336 | STATIC struct net_device_stats * | |
337 | chan_get_stats (struct net_device * ndev) | |
338 | { | |
339 | mch_t *ch; | |
340 | struct net_device_stats *nstats; | |
341 | struct sbecom_chan_stats *stats; | |
342 | int channum; | |
343 | ||
50ee11fe BB |
344 | { |
345 | struct c4_priv *priv; | |
346 | ||
347 | priv = (struct c4_priv *) dev_to_hdlc (ndev)->priv; | |
348 | channum = priv->channum; | |
349 | } | |
50ee11fe BB |
350 | |
351 | ch = c4_find_chan (channum); | |
352 | if (ch == NULL) | |
353 | return NULL; | |
354 | ||
355 | nstats = &ndev->stats; | |
356 | stats = &ch->s; | |
357 | ||
358 | memset (nstats, 0, sizeof (struct net_device_stats)); | |
359 | nstats->rx_packets = stats->rx_packets; | |
360 | nstats->tx_packets = stats->tx_packets; | |
361 | nstats->rx_bytes = stats->rx_bytes; | |
362 | nstats->tx_bytes = stats->tx_bytes; | |
363 | nstats->rx_errors = stats->rx_length_errors + | |
364 | stats->rx_over_errors + | |
365 | stats->rx_crc_errors + | |
366 | stats->rx_frame_errors + | |
367 | stats->rx_fifo_errors + | |
368 | stats->rx_missed_errors; | |
369 | nstats->tx_errors = stats->tx_dropped + | |
370 | stats->tx_aborted_errors + | |
371 | stats->tx_fifo_errors; | |
372 | nstats->rx_dropped = stats->rx_dropped; | |
373 | nstats->tx_dropped = stats->tx_dropped; | |
374 | ||
375 | nstats->rx_length_errors = stats->rx_length_errors; | |
376 | nstats->rx_over_errors = stats->rx_over_errors; | |
377 | nstats->rx_crc_errors = stats->rx_crc_errors; | |
378 | nstats->rx_frame_errors = stats->rx_frame_errors; | |
379 | nstats->rx_fifo_errors = stats->rx_fifo_errors; | |
380 | nstats->rx_missed_errors = stats->rx_missed_errors; | |
381 | ||
382 | nstats->tx_aborted_errors = stats->tx_aborted_errors; | |
383 | nstats->tx_fifo_errors = stats->tx_fifo_errors; | |
384 | ||
385 | return nstats; | |
386 | } | |
387 | ||
388 | ||
389 | static ci_t * | |
390 | get_ci_by_dev (struct net_device * ndev) | |
391 | { | |
392 | return (ci_t *)(netdev_priv(ndev)); | |
393 | } | |
394 | ||
395 | ||
50ee11fe BB |
396 | STATIC int |
397 | c4_linux_xmit (struct sk_buff * skb, struct net_device * ndev) | |
398 | { | |
399 | const struct c4_priv *priv; | |
400 | int rval; | |
401 | ||
50ee11fe BB |
402 | hdlc_device *hdlc = dev_to_hdlc (ndev); |
403 | ||
404 | priv = hdlc->priv; | |
50ee11fe BB |
405 | |
406 | rval = musycc_start_xmit (priv->ci, priv->channum, skb); | |
407 | return -rval; | |
408 | } | |
50ee11fe BB |
409 | |
410 | static const struct net_device_ops chan_ops = { | |
411 | .ndo_open = chan_open, | |
412 | .ndo_stop = chan_close, | |
413 | .ndo_start_xmit = c4_linux_xmit, | |
414 | .ndo_do_ioctl = chan_dev_ioctl, | |
415 | .ndo_get_stats = chan_get_stats, | |
416 | }; | |
417 | ||
418 | STATIC struct net_device * | |
419 | create_chan (struct net_device * ndev, ci_t * ci, | |
420 | struct sbecom_chan_param * cp) | |
421 | { | |
422 | hdlc_device *hdlc; | |
423 | struct net_device *dev; | |
424 | hdw_info_t *hi; | |
425 | int ret; | |
426 | ||
427 | if (c4_find_chan (cp->channum)) | |
428 | return 0; /* channel already exists */ | |
429 | ||
430 | { | |
431 | struct c4_priv *priv; | |
432 | ||
433 | /* allocate then fill in private data structure */ | |
434 | priv = OS_kmalloc (sizeof (struct c4_priv)); | |
435 | if (!priv) | |
436 | { | |
e6e4d05d | 437 | pr_warning("%s: no memory for net_device !\n", ci->devname); |
50ee11fe BB |
438 | return 0; |
439 | } | |
440 | dev = alloc_hdlcdev (priv); | |
441 | if (!dev) | |
442 | { | |
e6e4d05d | 443 | pr_warning("%s: no memory for hdlc_device !\n", ci->devname); |
50ee11fe BB |
444 | OS_kfree (priv); |
445 | return 0; | |
446 | } | |
447 | priv->ci = ci; | |
448 | priv->channum = cp->channum; | |
449 | } | |
450 | ||
451 | hdlc = dev_to_hdlc (dev); | |
452 | ||
453 | dev->base_addr = 0; /* not I/O mapped */ | |
454 | dev->irq = ndev->irq; | |
455 | dev->type = ARPHRD_RAWHDLC; | |
456 | *dev->name = 0; /* default ifconfig name = "hdlc" */ | |
457 | ||
458 | hi = (hdw_info_t *) ci->hdw_info; | |
459 | if (hi->mfg_info_sts == EEPROM_OK) | |
460 | { | |
461 | switch (hi->promfmt) | |
462 | { | |
463 | case PROM_FORMAT_TYPE1: | |
464 | memcpy (dev->dev_addr, (FLD_TYPE1 *) (hi->mfg_info.pft1.Serial), 6); | |
465 | break; | |
466 | case PROM_FORMAT_TYPE2: | |
467 | memcpy (dev->dev_addr, (FLD_TYPE2 *) (hi->mfg_info.pft2.Serial), 6); | |
468 | break; | |
469 | default: | |
470 | memset (dev->dev_addr, 0, 6); | |
471 | break; | |
472 | } | |
473 | } else | |
474 | { | |
475 | memset (dev->dev_addr, 0, 6); | |
476 | } | |
477 | ||
478 | hdlc->xmit = c4_linux_xmit; | |
479 | ||
480 | dev->netdev_ops = &chan_ops; | |
481 | /* | |
482 | * The native hdlc stack calls this 'attach' routine during | |
483 | * hdlc_raw_ioctl(), passing parameters for line encoding and parity. | |
484 | * Since hdlc_raw_ioctl() stack does not interrogate whether an 'attach' | |
485 | * routine is actually registered or not, we supply a dummy routine which | |
486 | * does nothing (since encoding and parity are setup for our driver via a | |
487 | * special configuration application). | |
488 | */ | |
489 | ||
490 | hdlc->attach = chan_attach_noop; | |
491 | ||
492 | rtnl_unlock (); /* needed due to Ioctl calling sequence */ | |
493 | ret = register_hdlc_device (dev); | |
494 | /* NOTE: <stats> setting must occur AFTER registration in order to "take" */ | |
495 | dev->tx_queue_len = MAX_DEFAULT_IFQLEN; | |
496 | ||
497 | rtnl_lock (); /* needed due to Ioctl calling sequence */ | |
498 | if (ret) | |
499 | { | |
500 | if (log_level >= LOG_WARN) | |
694a9807 | 501 | pr_info("%s: create_chan[%d] registration error = %d.\n", |
50ee11fe BB |
502 | ci->devname, cp->channum, ret); |
503 | free_netdev (dev); /* cleanup */ | |
504 | return 0; /* failed to register */ | |
505 | } | |
506 | return dev; | |
507 | } | |
508 | ||
509 | ||
510 | /* the idea here is to get port information and pass it back (using pointer) */ | |
511 | STATIC status_t | |
512 | do_get_port (struct net_device * ndev, void *data) | |
513 | { | |
514 | int ret; | |
515 | ci_t *ci; /* ci stands for card information */ | |
516 | struct sbecom_port_param pp;/* copy data to kernel land */ | |
517 | ||
518 | if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param))) | |
519 | return -EFAULT; | |
520 | if (pp.portnum >= MUSYCC_NPORTS) | |
521 | return -EFAULT; | |
522 | ci = get_ci_by_dev (ndev); | |
523 | if (!ci) | |
524 | return -EINVAL; /* get card info */ | |
525 | ||
526 | ret = mkret (c4_get_port (ci, pp.portnum)); | |
527 | if (ret) | |
528 | return ret; | |
529 | if (copy_to_user (data, &ci->port[pp.portnum].p, | |
530 | sizeof (struct sbecom_port_param))) | |
531 | return -EFAULT; | |
532 | return 0; | |
533 | } | |
534 | ||
535 | /* this function copys the user data and then calls the real action function */ | |
536 | STATIC status_t | |
537 | do_set_port (struct net_device * ndev, void *data) | |
538 | { | |
539 | ci_t *ci; /* ci stands for card information */ | |
540 | struct sbecom_port_param pp;/* copy data to kernel land */ | |
541 | ||
542 | if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param))) | |
543 | return -EFAULT; | |
544 | if (pp.portnum >= MUSYCC_NPORTS) | |
545 | return -EFAULT; | |
546 | ci = get_ci_by_dev (ndev); | |
547 | if (!ci) | |
548 | return -EINVAL; /* get card info */ | |
549 | ||
550 | if (pp.portnum >= ci->max_port) /* sanity check */ | |
551 | return ENXIO; | |
552 | ||
553 | memcpy (&ci->port[pp.portnum].p, &pp, sizeof (struct sbecom_port_param)); | |
554 | return mkret (c4_set_port (ci, pp.portnum)); | |
555 | } | |
556 | ||
557 | /* work the port loopback mode as per directed */ | |
558 | STATIC status_t | |
559 | do_port_loop (struct net_device * ndev, void *data) | |
560 | { | |
561 | struct sbecom_port_param pp; | |
562 | ci_t *ci; | |
563 | ||
564 | if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param))) | |
565 | return -EFAULT; | |
566 | ci = get_ci_by_dev (ndev); | |
567 | if (!ci) | |
568 | return -EINVAL; | |
569 | return mkret (c4_loop_port (ci, pp.portnum, pp.port_mode)); | |
570 | } | |
571 | ||
572 | /* set the specified register with the given value / or just read it */ | |
573 | STATIC status_t | |
574 | do_framer_rw (struct net_device * ndev, void *data) | |
575 | { | |
576 | struct sbecom_port_param pp; | |
577 | ci_t *ci; | |
578 | int ret; | |
579 | ||
580 | if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param))) | |
581 | return -EFAULT; | |
582 | ci = get_ci_by_dev (ndev); | |
583 | if (!ci) | |
584 | return -EINVAL; | |
585 | ret = mkret (c4_frame_rw (ci, &pp)); | |
586 | if (ret) | |
587 | return ret; | |
588 | if (copy_to_user (data, &pp, sizeof (struct sbecom_port_param))) | |
589 | return -EFAULT; | |
590 | return 0; | |
591 | } | |
592 | ||
593 | /* set the specified register with the given value / or just read it */ | |
594 | STATIC status_t | |
595 | do_pld_rw (struct net_device * ndev, void *data) | |
596 | { | |
597 | struct sbecom_port_param pp; | |
598 | ci_t *ci; | |
599 | int ret; | |
600 | ||
601 | if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param))) | |
602 | return -EFAULT; | |
603 | ci = get_ci_by_dev (ndev); | |
604 | if (!ci) | |
605 | return -EINVAL; | |
606 | ret = mkret (c4_pld_rw (ci, &pp)); | |
607 | if (ret) | |
608 | return ret; | |
609 | if (copy_to_user (data, &pp, sizeof (struct sbecom_port_param))) | |
610 | return -EFAULT; | |
611 | return 0; | |
612 | } | |
613 | ||
614 | /* set the specified register with the given value / or just read it */ | |
615 | STATIC status_t | |
616 | do_musycc_rw (struct net_device * ndev, void *data) | |
617 | { | |
618 | struct c4_musycc_param mp; | |
619 | ci_t *ci; | |
620 | int ret; | |
621 | ||
622 | if (copy_from_user (&mp, data, sizeof (struct c4_musycc_param))) | |
623 | return -EFAULT; | |
624 | ci = get_ci_by_dev (ndev); | |
625 | if (!ci) | |
626 | return -EINVAL; | |
627 | ret = mkret (c4_musycc_rw (ci, &mp)); | |
628 | if (ret) | |
629 | return ret; | |
630 | if (copy_to_user (data, &mp, sizeof (struct c4_musycc_param))) | |
631 | return -EFAULT; | |
632 | return 0; | |
633 | } | |
634 | ||
635 | STATIC status_t | |
636 | do_get_chan (struct net_device * ndev, void *data) | |
637 | { | |
638 | struct sbecom_chan_param cp; | |
639 | int ret; | |
640 | ||
641 | if (copy_from_user (&cp, data, | |
642 | sizeof (struct sbecom_chan_param))) | |
643 | return -EFAULT; | |
644 | ||
645 | if ((ret = mkret (c4_get_chan (cp.channum, &cp)))) | |
646 | return ret; | |
647 | ||
648 | if (copy_to_user (data, &cp, sizeof (struct sbecom_chan_param))) | |
649 | return -EFAULT; | |
650 | return 0; | |
651 | } | |
652 | ||
653 | STATIC status_t | |
654 | do_set_chan (struct net_device * ndev, void *data) | |
655 | { | |
656 | struct sbecom_chan_param cp; | |
657 | int ret; | |
658 | ci_t *ci; | |
659 | ||
660 | if (copy_from_user (&cp, data, sizeof (struct sbecom_chan_param))) | |
661 | return -EFAULT; | |
662 | ci = get_ci_by_dev (ndev); | |
663 | if (!ci) | |
664 | return -EINVAL; | |
665 | switch (ret = mkret (c4_set_chan (cp.channum, &cp))) | |
666 | { | |
667 | case 0: | |
668 | return 0; | |
669 | default: | |
670 | return ret; | |
671 | } | |
672 | } | |
673 | ||
674 | STATIC status_t | |
675 | do_create_chan (struct net_device * ndev, void *data) | |
676 | { | |
677 | ci_t *ci; | |
678 | struct net_device *dev; | |
679 | struct sbecom_chan_param cp; | |
680 | int ret; | |
681 | ||
682 | if (copy_from_user (&cp, data, sizeof (struct sbecom_chan_param))) | |
683 | return -EFAULT; | |
684 | ci = get_ci_by_dev (ndev); | |
685 | if (!ci) | |
686 | return -EINVAL; | |
687 | dev = create_chan (ndev, ci, &cp); | |
688 | if (!dev) | |
689 | return -EBUSY; | |
690 | ret = mkret (c4_new_chan (ci, cp.port, cp.channum, dev)); | |
691 | if (ret) | |
692 | { | |
50ee11fe BB |
693 | rtnl_unlock (); /* needed due to Ioctl calling sequence */ |
694 | unregister_hdlc_device (dev); | |
695 | rtnl_lock (); /* needed due to Ioctl calling sequence */ | |
696 | free_netdev (dev); | |
50ee11fe BB |
697 | } |
698 | return ret; | |
699 | } | |
700 | ||
701 | STATIC status_t | |
702 | do_get_chan_stats (struct net_device * ndev, void *data) | |
703 | { | |
704 | struct c4_chan_stats_wrap ccs; | |
705 | int ret; | |
706 | ||
707 | if (copy_from_user (&ccs, data, | |
708 | sizeof (struct c4_chan_stats_wrap))) | |
709 | return -EFAULT; | |
710 | switch (ret = mkret (c4_get_chan_stats (ccs.channum, &ccs.stats))) | |
711 | { | |
712 | case 0: | |
713 | break; | |
714 | default: | |
715 | return ret; | |
716 | } | |
717 | if (copy_to_user (data, &ccs, | |
718 | sizeof (struct c4_chan_stats_wrap))) | |
719 | return -EFAULT; | |
720 | return 0; | |
721 | } | |
722 | STATIC status_t | |
723 | do_set_loglevel (struct net_device * ndev, void *data) | |
724 | { | |
725 | unsigned int log_level; | |
726 | ||
727 | if (copy_from_user (&log_level, data, sizeof (int))) | |
728 | return -EFAULT; | |
729 | sbecom_set_loglevel (log_level); | |
730 | return 0; | |
731 | } | |
732 | ||
733 | STATIC status_t | |
734 | do_deluser (struct net_device * ndev, int lockit) | |
735 | { | |
736 | if (ndev->flags & IFF_UP) | |
737 | return -EBUSY; | |
738 | ||
739 | { | |
740 | ci_t *ci; | |
741 | mch_t *ch; | |
742 | const struct c4_priv *priv; | |
743 | int channum; | |
744 | ||
50ee11fe | 745 | priv = (struct c4_priv *) dev_to_hdlc (ndev)->priv; |
50ee11fe BB |
746 | ci = priv->ci; |
747 | channum = priv->channum; | |
748 | ||
749 | ch = c4_find_chan (channum); | |
750 | if (ch == NULL) | |
751 | return -ENOENT; | |
752 | ch->user = 0; /* will be freed, below */ | |
753 | } | |
754 | ||
50ee11fe BB |
755 | if (lockit) |
756 | rtnl_unlock (); /* needed if Ioctl calling sequence */ | |
757 | unregister_hdlc_device (ndev); | |
758 | if (lockit) | |
759 | rtnl_lock (); /* needed if Ioctl calling sequence */ | |
760 | free_netdev (ndev); | |
50ee11fe BB |
761 | return 0; |
762 | } | |
763 | ||
764 | int | |
765 | do_del_chan (struct net_device * musycc_dev, void *data) | |
766 | { | |
767 | struct sbecom_chan_param cp; | |
768 | char buf[sizeof (CHANNAME) + 3]; | |
769 | struct net_device *dev; | |
770 | int ret; | |
771 | ||
772 | if (copy_from_user (&cp, data, | |
773 | sizeof (struct sbecom_chan_param))) | |
774 | return -EFAULT; | |
775 | sprintf (buf, CHANNAME "%d", cp.channum); | |
776 | if (!(dev = dev_get_by_name (&init_net, buf))) | |
777 | return -ENOENT; | |
778 | dev_put (dev); | |
779 | ret = do_deluser (dev, 1); | |
780 | if (ret) | |
781 | return ret; | |
782 | return c4_del_chan (cp.channum); | |
783 | } | |
784 | int c4_reset_board (void *); | |
785 | ||
786 | int | |
787 | do_reset (struct net_device * musycc_dev, void *data) | |
788 | { | |
789 | const struct c4_priv *priv; | |
790 | int i; | |
791 | ||
792 | for (i = 0; i < 128; i++) | |
793 | { | |
794 | struct net_device *ndev; | |
795 | char buf[sizeof (CHANNAME) + 3]; | |
796 | ||
797 | sprintf (buf, CHANNAME "%d", i); | |
798 | if (!(ndev = dev_get_by_name(&init_net, buf))) | |
799 | continue; | |
800 | priv = dev_to_hdlc (ndev)->priv; | |
801 | ||
802 | if ((unsigned long) (priv->ci) == | |
803 | (unsigned long) (netdev_priv(musycc_dev))) | |
804 | { | |
805 | ndev->flags &= ~IFF_UP; | |
806 | dev_put (ndev); | |
807 | netif_stop_queue (ndev); | |
808 | do_deluser (ndev, 1); | |
809 | } else | |
810 | dev_put (ndev); | |
811 | } | |
812 | return 0; | |
813 | } | |
814 | ||
815 | int | |
816 | do_reset_chan_stats (struct net_device * musycc_dev, void *data) | |
817 | { | |
818 | struct sbecom_chan_param cp; | |
819 | ||
820 | if (copy_from_user (&cp, data, | |
821 | sizeof (struct sbecom_chan_param))) | |
822 | return -EFAULT; | |
823 | return mkret (c4_del_chan_stats (cp.channum)); | |
824 | } | |
825 | ||
826 | STATIC status_t | |
827 | c4_ioctl (struct net_device * ndev, struct ifreq * ifr, int cmd) | |
828 | { | |
829 | ci_t *ci; | |
830 | void *data; | |
831 | int iocmd, iolen; | |
832 | status_t ret; | |
833 | static struct data | |
834 | { | |
835 | union | |
836 | { | |
837 | u_int8_t c; | |
838 | u_int32_t i; | |
839 | struct sbe_brd_info bip; | |
840 | struct sbe_drv_info dip; | |
841 | struct sbe_iid_info iip; | |
842 | struct sbe_brd_addr bap; | |
843 | struct sbecom_chan_stats stats; | |
844 | struct sbecom_chan_param param; | |
845 | struct temux_card_stats cards; | |
846 | struct sbecom_card_param cardp; | |
847 | struct sbecom_framer_param frp; | |
848 | } u; | |
849 | } arg; | |
850 | ||
851 | ||
852 | if (!capable (CAP_SYS_ADMIN)) | |
853 | return -EPERM; | |
854 | if (cmd != SIOCDEVPRIVATE + 15) | |
855 | return -EINVAL; | |
856 | if (!(ci = get_ci_by_dev (ndev))) | |
857 | return -EINVAL; | |
858 | if (ci->state != C_RUNNING) | |
859 | return -ENODEV; | |
860 | if (copy_from_user (&iocmd, ifr->ifr_data, sizeof (iocmd))) | |
861 | return -EFAULT; | |
862 | #if 0 | |
863 | if (copy_from_user (&len, ifr->ifr_data + sizeof (iocmd), sizeof (len))) | |
864 | return -EFAULT; | |
865 | #endif | |
866 | ||
867 | #if 0 | |
694a9807 | 868 | pr_info("c4_ioctl: iocmd %x, dir %x type %x nr %x iolen %d.\n", iocmd, |
50ee11fe BB |
869 | _IOC_DIR (iocmd), _IOC_TYPE (iocmd), _IOC_NR (iocmd), |
870 | _IOC_SIZE (iocmd)); | |
871 | #endif | |
872 | iolen = _IOC_SIZE (iocmd); | |
873 | data = ifr->ifr_data + sizeof (iocmd); | |
874 | if (copy_from_user (&arg, data, iolen)) | |
875 | return -EFAULT; | |
876 | ||
877 | ret = 0; | |
878 | switch (iocmd) | |
879 | { | |
880 | case SBE_IOC_PORT_GET: | |
694a9807 | 881 | //pr_info(">> SBE_IOC_PORT_GET Ioctl...\n"); |
50ee11fe BB |
882 | ret = do_get_port (ndev, data); |
883 | break; | |
884 | case SBE_IOC_PORT_SET: | |
694a9807 | 885 | //pr_info(">> SBE_IOC_PORT_SET Ioctl...\n"); |
50ee11fe BB |
886 | ret = do_set_port (ndev, data); |
887 | break; | |
888 | case SBE_IOC_CHAN_GET: | |
694a9807 | 889 | //pr_info(">> SBE_IOC_CHAN_GET Ioctl...\n"); |
50ee11fe BB |
890 | ret = do_get_chan (ndev, data); |
891 | break; | |
892 | case SBE_IOC_CHAN_SET: | |
694a9807 | 893 | //pr_info(">> SBE_IOC_CHAN_SET Ioctl...\n"); |
50ee11fe BB |
894 | ret = do_set_chan (ndev, data); |
895 | break; | |
896 | case C4_DEL_CHAN: | |
694a9807 | 897 | //pr_info(">> C4_DEL_CHAN Ioctl...\n"); |
50ee11fe BB |
898 | ret = do_del_chan (ndev, data); |
899 | break; | |
900 | case SBE_IOC_CHAN_NEW: | |
901 | ret = do_create_chan (ndev, data); | |
902 | break; | |
903 | case SBE_IOC_CHAN_GET_STAT: | |
904 | ret = do_get_chan_stats (ndev, data); | |
905 | break; | |
906 | case SBE_IOC_LOGLEVEL: | |
907 | ret = do_set_loglevel (ndev, data); | |
908 | break; | |
909 | case SBE_IOC_RESET_DEV: | |
910 | ret = do_reset (ndev, data); | |
911 | break; | |
912 | case SBE_IOC_CHAN_DEL_STAT: | |
913 | ret = do_reset_chan_stats (ndev, data); | |
914 | break; | |
915 | case C4_LOOP_PORT: | |
916 | ret = do_port_loop (ndev, data); | |
917 | break; | |
918 | case C4_RW_FRMR: | |
919 | ret = do_framer_rw (ndev, data); | |
920 | break; | |
921 | case C4_RW_MSYC: | |
922 | ret = do_musycc_rw (ndev, data); | |
923 | break; | |
924 | case C4_RW_PLD: | |
925 | ret = do_pld_rw (ndev, data); | |
926 | break; | |
927 | case SBE_IOC_IID_GET: | |
928 | ret = (iolen == sizeof (struct sbe_iid_info)) ? c4_get_iidinfo (ci, &arg.u.iip) : -EFAULT; | |
929 | if (ret == 0) /* no error, copy data */ | |
930 | if (copy_to_user (data, &arg, iolen)) | |
931 | return -EFAULT; | |
932 | break; | |
933 | default: | |
694a9807 | 934 | //pr_info(">> c4_ioctl: EINVAL - unknown iocmd <%x>\n", iocmd); |
50ee11fe BB |
935 | ret = -EINVAL; |
936 | break; | |
937 | } | |
938 | return mkret (ret); | |
939 | } | |
940 | ||
941 | static const struct net_device_ops c4_ops = { | |
942 | .ndo_open = void_open, | |
943 | .ndo_start_xmit = c4_linux_xmit, | |
944 | .ndo_do_ioctl = c4_ioctl, | |
945 | }; | |
946 | ||
947 | static void c4_setup(struct net_device *dev) | |
948 | { | |
949 | dev->type = ARPHRD_VOID; | |
950 | dev->netdev_ops = &c4_ops; | |
951 | } | |
952 | ||
953 | struct net_device *__init | |
954 | c4_add_dev (hdw_info_t * hi, int brdno, unsigned long f0, unsigned long f1, | |
955 | int irq0, int irq1) | |
956 | { | |
957 | struct net_device *ndev; | |
958 | ci_t *ci; | |
959 | ||
960 | ndev = alloc_netdev(sizeof(ci_t), SBE_IFACETMPL, c4_setup); | |
961 | if (!ndev) | |
962 | { | |
e6e4d05d | 963 | pr_warning("%s: no memory for struct net_device !\n", hi->devname); |
50ee11fe BB |
964 | error_flag = ENOMEM; |
965 | return 0; | |
966 | } | |
967 | ci = (ci_t *)(netdev_priv(ndev)); | |
968 | ndev->irq = irq0; | |
969 | ||
970 | ci->hdw_info = hi; | |
971 | ci->state = C_INIT; /* mark as hardware not available */ | |
972 | ci->next = c4_list; | |
973 | c4_list = ci; | |
974 | ci->brdno = ci->next ? ci->next->brdno + 1 : 0; | |
975 | ||
976 | if (CI == 0) | |
977 | CI = ci; /* DEBUG, only board 0 usage */ | |
978 | ||
979 | strcpy (ci->devname, hi->devname); | |
980 | ci->release = &pmcc4_OSSI_release[0]; | |
981 | ||
982 | /* tasklet */ | |
983 | #if defined(SBE_ISR_TASKLET) | |
984 | tasklet_init (&ci->ci_musycc_isr_tasklet, | |
985 | (void (*) (unsigned long)) musycc_intr_bh_tasklet, | |
986 | (unsigned long) ci); | |
987 | ||
988 | if (atomic_read (&ci->ci_musycc_isr_tasklet.count) == 0) | |
989 | tasklet_disable_nosync (&ci->ci_musycc_isr_tasklet); | |
990 | #elif defined(SBE_ISR_IMMEDIATE) | |
991 | ci->ci_musycc_isr_tq.routine = (void *) (unsigned long) musycc_intr_bh_tasklet; | |
992 | ci->ci_musycc_isr_tq.data = ci; | |
993 | #endif | |
994 | ||
995 | ||
996 | if (register_netdev (ndev) || | |
997 | (c4_init (ci, (u_char *) f0, (u_char *) f1) != SBE_DRVR_SUCCESS)) | |
998 | { | |
999 | OS_kfree (netdev_priv(ndev)); | |
1000 | OS_kfree (ndev); | |
1001 | error_flag = ENODEV; | |
1002 | return 0; | |
1003 | } | |
1004 | /************************************************************* | |
1005 | * int request_irq(unsigned int irq, | |
1006 | * void (*handler)(int, void *, struct pt_regs *), | |
1007 | * unsigned long flags, const char *dev_name, void *dev_id); | |
1008 | * wherein: | |
1009 | * irq -> The interrupt number that is being requested. | |
1010 | * handler -> Pointer to handling function being installed. | |
1011 | * flags -> A bit mask of options related to interrupt management. | |
1012 | * dev_name -> String used in /proc/interrupts to show owner of interrupt. | |
1013 | * dev_id -> Pointer (for shared interrupt lines) to point to its own | |
1014 | * private data area (to identify which device is interrupting). | |
1015 | * | |
1016 | * extern void free_irq(unsigned int irq, void *dev_id); | |
1017 | **************************************************************/ | |
1018 | ||
1019 | if (request_irq (irq0, &c4_linux_interrupt, | |
1020 | #if defined(SBE_ISR_TASKLET) | |
1021 | IRQF_DISABLED | IRQF_SHARED, | |
1022 | #elif defined(SBE_ISR_IMMEDIATE) | |
1023 | IRQF_DISABLED | IRQF_SHARED, | |
1024 | #elif defined(SBE_ISR_INLINE) | |
1025 | IRQF_SHARED, | |
1026 | #endif | |
1027 | ndev->name, ndev)) | |
1028 | { | |
e6e4d05d | 1029 | pr_warning("%s: MUSYCC could not get irq: %d\n", ndev->name, irq0); |
50ee11fe BB |
1030 | unregister_netdev (ndev); |
1031 | OS_kfree (netdev_priv(ndev)); | |
1032 | OS_kfree (ndev); | |
1033 | error_flag = EIO; | |
1034 | return 0; | |
1035 | } | |
1036 | #ifdef CONFIG_SBE_PMCC4_NCOMM | |
1037 | if (request_irq (irq1, &c4_ebus_interrupt, IRQF_SHARED, ndev->name, ndev)) | |
1038 | { | |
e6e4d05d | 1039 | pr_warning("%s: EBUS could not get irq: %d\n", hi->devname, irq1); |
50ee11fe BB |
1040 | unregister_netdev (ndev); |
1041 | free_irq (irq0, ndev); | |
d87d909a | 1042 | OS_kfree (netdev_priv(ndev)); |
50ee11fe BB |
1043 | OS_kfree (ndev); |
1044 | error_flag = EIO; | |
1045 | return 0; | |
1046 | } | |
1047 | #endif | |
1048 | ||
1049 | /* setup board identification information */ | |
1050 | ||
1051 | { | |
1052 | u_int32_t tmp; | |
1053 | ||
1054 | hdw_sn_get (hi, brdno); /* also sets PROM format type (promfmt) | |
1055 | * for later usage */ | |
1056 | ||
1057 | switch (hi->promfmt) | |
1058 | { | |
1059 | case PROM_FORMAT_TYPE1: | |
1060 | memcpy (ndev->dev_addr, (FLD_TYPE1 *) (hi->mfg_info.pft1.Serial), 6); | |
1061 | memcpy (&tmp, (FLD_TYPE1 *) (hi->mfg_info.pft1.Id), 4); /* unaligned data | |
1062 | * acquisition */ | |
1063 | ci->brd_id = cpu_to_be32 (tmp); | |
1064 | break; | |
1065 | case PROM_FORMAT_TYPE2: | |
1066 | memcpy (ndev->dev_addr, (FLD_TYPE2 *) (hi->mfg_info.pft2.Serial), 6); | |
1067 | memcpy (&tmp, (FLD_TYPE2 *) (hi->mfg_info.pft2.Id), 4); /* unaligned data | |
1068 | * acquisition */ | |
1069 | ci->brd_id = cpu_to_be32 (tmp); | |
1070 | break; | |
1071 | default: | |
1072 | ci->brd_id = 0; | |
1073 | memset (ndev->dev_addr, 0, 6); | |
1074 | break; | |
1075 | } | |
1076 | ||
1077 | #if 1 | |
1078 | sbeid_set_hdwbid (ci); /* requires bid to be preset */ | |
1079 | #else | |
1080 | sbeid_set_bdtype (ci); /* requires hdw_bid to be preset */ | |
1081 | #endif | |
1082 | ||
1083 | } | |
1084 | ||
1085 | #ifdef CONFIG_PROC_FS | |
1086 | sbecom_proc_brd_init (ci); | |
1087 | #endif | |
1088 | #if defined(SBE_ISR_TASKLET) | |
1089 | tasklet_enable (&ci->ci_musycc_isr_tasklet); | |
1090 | #endif | |
1091 | ||
1092 | ||
1093 | if ((error_flag = c4_init2 (ci)) != SBE_DRVR_SUCCESS) | |
1094 | { | |
1095 | #ifdef CONFIG_PROC_FS | |
1096 | sbecom_proc_brd_cleanup (ci); | |
1097 | #endif | |
1098 | unregister_netdev (ndev); | |
1099 | free_irq (irq1, ndev); | |
1100 | free_irq (irq0, ndev); | |
1101 | OS_kfree (netdev_priv(ndev)); | |
1102 | OS_kfree (ndev); | |
1103 | return 0; /* failure, error_flag is set */ | |
1104 | } | |
1105 | return ndev; | |
1106 | } | |
1107 | ||
1108 | STATIC int __init | |
1109 | c4_mod_init (void) | |
1110 | { | |
1111 | int rtn; | |
1112 | ||
e6e4d05d | 1113 | pr_warning("%s\n", pmcc4_OSSI_release); |
50ee11fe BB |
1114 | if ((rtn = c4hw_attach_all ())) |
1115 | return -rtn; /* installation failure - see system log */ | |
1116 | ||
1117 | /* housekeeping notifications */ | |
1118 | if (log_level != log_level_default) | |
e6e4d05d JP |
1119 | pr_info("NOTE: driver parameter <log_level> changed from default %d to %d.\n", |
1120 | log_level_default, log_level); | |
50ee11fe | 1121 | if (max_mru != max_mru_default) |
e6e4d05d JP |
1122 | pr_info("NOTE: driver parameter <max_mru> changed from default %d to %d.\n", |
1123 | max_mru_default, max_mru); | |
50ee11fe | 1124 | if (max_mtu != max_mtu_default) |
e6e4d05d JP |
1125 | pr_info("NOTE: driver parameter <max_mtu> changed from default %d to %d.\n", |
1126 | max_mtu_default, max_mtu); | |
50ee11fe BB |
1127 | if (max_rxdesc_used != max_rxdesc_default) |
1128 | { | |
1129 | if (max_rxdesc_used > 2000) | |
1130 | max_rxdesc_used = 2000; /* out-of-bounds reset */ | |
e6e4d05d JP |
1131 | pr_info("NOTE: driver parameter <max_rxdesc_used> changed from default %d to %d.\n", |
1132 | max_rxdesc_default, max_rxdesc_used); | |
50ee11fe BB |
1133 | } |
1134 | if (max_txdesc_used != max_txdesc_default) | |
1135 | { | |
1136 | if (max_txdesc_used > 1000) | |
1137 | max_txdesc_used = 1000; /* out-of-bounds reset */ | |
e6e4d05d JP |
1138 | pr_info("NOTE: driver parameter <max_txdesc_used> changed from default %d to %d.\n", |
1139 | max_txdesc_default, max_txdesc_used); | |
50ee11fe BB |
1140 | } |
1141 | return 0; /* installation success */ | |
1142 | } | |
1143 | ||
1144 | ||
1145 | /* | |
1146 | * find any still allocated hdlc registrations and unregister via call to | |
1147 | * do_deluser() | |
1148 | */ | |
1149 | ||
1150 | STATIC void __exit | |
1151 | cleanup_hdlc (void) | |
1152 | { | |
1153 | hdw_info_t *hi; | |
1154 | ci_t *ci; | |
1155 | struct net_device *ndev; | |
1156 | int i, j, k; | |
1157 | ||
1158 | for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++) | |
1159 | { | |
1160 | if (hi->ndev) /* a board has been attached */ | |
1161 | { | |
1162 | ci = (ci_t *)(netdev_priv(hi->ndev)); | |
1163 | for (j = 0; j < ci->max_port; j++) | |
1164 | for (k = 0; k < MUSYCC_NCHANS; k++) | |
1165 | if ((ndev = ci->port[j].chan[k]->user)) | |
1166 | { | |
1167 | do_deluser (ndev, 0); | |
1168 | } | |
1169 | } | |
1170 | } | |
1171 | } | |
1172 | ||
1173 | ||
1174 | STATIC void __exit | |
1175 | c4_mod_remove (void) | |
1176 | { | |
1177 | cleanup_hdlc (); /* delete any missed channels */ | |
1178 | cleanup_devs (); | |
1179 | c4_cleanup (); | |
1180 | cleanup_ioremap (); | |
e6e4d05d | 1181 | pr_info("SBE - driver removed.\n"); |
50ee11fe BB |
1182 | } |
1183 | ||
1184 | module_init (c4_mod_init); | |
1185 | module_exit (c4_mod_remove); | |
1186 | ||
50ee11fe BB |
1187 | MODULE_AUTHOR ("SBE Technical Services <support@sbei.com>"); |
1188 | MODULE_DESCRIPTION ("wanPCI-CxT1E1 Generic HDLC WAN Driver module"); | |
1189 | #ifdef MODULE_LICENSE | |
1190 | MODULE_LICENSE ("GPL"); | |
1191 | #endif | |
1192 | ||
1193 | /*** End-of-File ***/ |